From e501dfad60791346ade0cd389fd04e20e97d427f Mon Sep 17 00:00:00 2001 From: Le Juez Victor <90587919+Bigfoot71@users.noreply.github.com> Date: Sat, 25 Oct 2025 23:13:24 +0200 Subject: [PATCH 001/430] [rlsw] Subpixel correction (#5300) * fix triangle cracking * subpixel corretion for quads * replace Bresenham for DDA + subpixel correction * consistency * adding note * style tweaks --- src/external/rlsw.h | 502 ++++++++++++++++++++++---------------------- 1 file changed, 252 insertions(+), 250 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index bc7a813d3..50a60d74f 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -819,8 +819,8 @@ typedef struct { float clearColor[4]; // Color used to clear the screen float clearDepth; // Depth value used to clear the screen - int vpCenter[2]; // Viewport center - int vpHalfSize[2]; // Viewport half dimensions + float vpCenter[2]; // Viewport center + float vpHalf[2]; // Viewport half dimensions int vpSize[2]; // Viewport dimensions (minus one) int vpMin[2]; // Viewport minimum renderable point (top-left) int vpMax[2]; // Viewport maximum renderable point (bottom-right) @@ -1040,6 +1040,34 @@ static inline void sw_add_vertex_grad_PTCH(sw_vertex_t *SW_RESTRICT out, const s out->homogeneous[3] += gradients->homogeneous[3]; } +static inline void sw_add_vertex_grad_scaled_PTCH( + sw_vertex_t *SW_RESTRICT out, + const sw_vertex_t *SW_RESTRICT gradients, + float scale) +{ + // Add gradients to Position + out->position[0] += gradients->position[0]*scale; + out->position[1] += gradients->position[1]*scale; + out->position[2] += gradients->position[2]*scale; + out->position[3] += gradients->position[3]*scale; + + // Add gradients to Texture coordinates + out->texcoord[0] += gradients->texcoord[0]*scale; + out->texcoord[1] += gradients->texcoord[1]*scale; + + // Add gradients to Color + out->color[0] += gradients->color[0]*scale; + out->color[1] += gradients->color[1]*scale; + out->color[2] += gradients->color[2]*scale; + out->color[3] += gradients->color[3]*scale; + + // Add gradients to Homogeneous coordinates + out->homogeneous[0] += gradients->homogeneous[0]*scale; + out->homogeneous[1] += gradients->homogeneous[1]*scale; + out->homogeneous[2] += gradients->homogeneous[2]*scale; + out->homogeneous[3] += gradients->homogeneous[3]*scale; +} + static inline void sw_float_to_unorm8_simd(uint8_t dst[4], const float src[4]) { #if defined(SW_HAS_NEON) @@ -1977,18 +2005,18 @@ static inline void sw_texture_sample_linear(float *color, const sw_texture_t *te } } -static inline void sw_texture_sample(float *color, const sw_texture_t *tex, float u, float v, float duDx, float duDy, float dvDx, float dvDy) +static inline void sw_texture_sample(float *color, const sw_texture_t *tex, float u, float v, float dUdx, float dUdy, float dVdx, float dVdy) { // Previous method: There is no need to compute the square root // because using the squared value, the comparison remains `L2 > 1.0f*1.0f` - //float du = sqrtf(duDx*duDx + duDy*duDy); - //float dv = sqrtf(dvDx*dvDx + dvDy*dvDy); + //float du = sqrtf(dUdx*dUdx + dUdy*dUdy); + //float dv = sqrtf(dVdx*dVdx + dVdy*dVdy); //float L = (du > dv)? du : dv; // Calculate the derivatives for each axis - float du2 = duDx*duDx + duDy*duDy; - float dv2 = dvDx*dvDx + dvDy*dvDy; - float L2 = (du2 > dv2)? du2 : dv2; + float dU2 = dUdx*dUdx + dUdy*dUdy; + float dV2 = dVdx*dVdx + dVdy*dVdy; + float L2 = (dU2 > dV2)? dU2 : dV2; SWfilter filter = (L2 > 1.0f)? tex->minFilter : tex->magFilter; @@ -2081,8 +2109,8 @@ static inline void sw_blend_colors(float *SW_RESTRICT dst/*[4]*/, const float *S //------------------------------------------------------------------------------------------- static inline void sw_project_ndc_to_screen(float screen[2], const float ndc[4]) { - screen[0] = RLSW.vpCenter[0] + ndc[0]*RLSW.vpHalfSize[0]; - screen[1] = RLSW.vpCenter[1] - ndc[1]*RLSW.vpHalfSize[1]; + screen[0] = RLSW.vpCenter[0] + ndc[0]*RLSW.vpHalf[0] + 0.5f; + screen[1] = RLSW.vpCenter[1] - ndc[1]*RLSW.vpHalf[1] + 0.5f; } //------------------------------------------------------------------------------------------- @@ -2295,67 +2323,73 @@ static inline void sw_triangle_clip_and_project(void) #define DEFINE_TRIANGLE_RASTER_SCANLINE(FUNC_NAME, ENABLE_TEXTURE, ENABLE_DEPTH_TEST, ENABLE_COLOR_BLEND) \ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, \ - const sw_vertex_t *end, float duDy, float dvDy) \ + const sw_vertex_t *end, float dUdy, float dVdy) \ { \ - /* Convert and center the screen coordinates */ \ - int xStart = (int)(start->screen[0] + 0.5f); \ - int xEnd = (int)(end->screen[0] + 0.5f); \ - int y = (int)start->screen[1]; \ + /* Gets the start and end coordinates */ \ + int xStart = (int)start->screen[0]; \ + int xEnd = (int)end->screen[0]; \ + \ + /* Avoid empty lines */ \ + if (xStart == xEnd) return; \ + \ + /* Compute the subpixel distance to traverse before the first pixel */ \ + float xSubstep = 1.0f - sw_fract(start->screen[0]); \ \ /* Compute the inverse horizontal distance along the X axis */ \ - float dx = end->screen[0] - start->screen[0]; \ - if (fabsf(dx) < 1e-6f) return; \ - float dxRcp = 1.0f/dx; \ + float dxRcp = 1.0f/(end->screen[0] - start->screen[0]); \ \ /* Compute the interpolation steps along the X axis */ \ - float dzDx = (end->homogeneous[2] - start->homogeneous[2])*dxRcp; \ - float dwDx = (end->homogeneous[3] - start->homogeneous[3])*dxRcp; \ + float dZdx = (end->homogeneous[2] - start->homogeneous[2])*dxRcp; \ + float dWdx = (end->homogeneous[3] - start->homogeneous[3])*dxRcp; \ \ - float dcDx[4] = { 0 }; \ - dcDx[0] = (end->color[0] - start->color[0])*dxRcp; \ - dcDx[1] = (end->color[1] - start->color[1])*dxRcp; \ - dcDx[2] = (end->color[2] - start->color[2])*dxRcp; \ - dcDx[3] = (end->color[3] - start->color[3])*dxRcp; \ + float dCdx[4] = { \ + (end->color[0] - start->color[0])*dxRcp, \ + (end->color[1] - start->color[1])*dxRcp, \ + (end->color[2] - start->color[2])*dxRcp, \ + (end->color[3] - start->color[3])*dxRcp \ + }; \ \ - float duDx = 0.0f, dvDx = 0.0f; \ + float dUdx = 0.0f; \ + float dVdx = 0.0f; \ if (ENABLE_TEXTURE) { \ - duDx = (end->texcoord[0] - start->texcoord[0])*dxRcp; \ - dvDx = (end->texcoord[1] - start->texcoord[1])*dxRcp; \ + dUdx = (end->texcoord[0] - start->texcoord[0])*dxRcp; \ + dVdx = (end->texcoord[1] - start->texcoord[1])*dxRcp; \ } \ \ /* Initializing the interpolation starting values */ \ - float z = start->homogeneous[2]; \ - float w = start->homogeneous[3]; \ + float z = start->homogeneous[2] + dZdx*xSubstep; \ + float w = start->homogeneous[3] + dWdx*xSubstep; \ \ - float color[4] = { 0 }; \ - color[0] = start->color[0]; \ - color[1] = start->color[1]; \ - color[2] = start->color[2]; \ - color[3] = start->color[3]; \ + float color[4] = { \ + start->color[0] + dCdx[0]*xSubstep, \ + start->color[1] + dCdx[1]*xSubstep, \ + start->color[2] + dCdx[2]*xSubstep, \ + start->color[3] + dCdx[3]*xSubstep \ + }; \ \ - float u = 0.0f, v = 0.0f; \ + float u = 0.0f; \ + float v = 0.0f; \ if (ENABLE_TEXTURE) { \ - u = start->texcoord[0]; \ - v = start->texcoord[1]; \ + u = start->texcoord[0] + dUdx*xSubstep; \ + v = start->texcoord[1] + dVdx*xSubstep; \ } \ \ /* Pre-calculate the starting pointers for the framebuffer row */ \ + int y = (int)start->screen[1]; \ void *cptr = GET_COLOR_PTR(RLSW.framebuffer.color, y*RLSW.framebuffer.width + xStart); \ void *dptr = GET_DEPTH_PTR(RLSW.framebuffer.depth, y*RLSW.framebuffer.width + xStart); \ \ /* Scanline rasterization */ \ for (int x = xStart; x < xEnd; x++) \ { \ - /* Pixel color computation */ \ float wRcp = 1.0f/w; \ float srcColor[4] = { \ - color[0]*wRcp, \ - color[1]*wRcp, \ - color[2]*wRcp, \ - color[3]*wRcp \ + color[0]*wRcp, \ + color[1]*wRcp, \ + color[2]*wRcp, \ + color[3]*wRcp \ }; \ \ - /* Test and write depth */ \ if (ENABLE_DEPTH_TEST) \ { \ /* TODO: Implement different depth funcs? */ \ @@ -2363,6 +2397,7 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, if (z > depth) goto discard; \ } \ \ + /* TODO: Implement depth mask */ \ sw_framebuffer_write_depth(dptr, z); \ \ if (ENABLE_TEXTURE) \ @@ -2370,7 +2405,7 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, float texColor[4]; \ float s = u*wRcp; \ float t = v*wRcp; \ - sw_texture_sample(texColor, tex, s, t, duDx, duDy, dvDx, dvDy); \ + sw_texture_sample(texColor, tex, s, t, dUdx, dUdy, dVdx, dVdy); \ srcColor[0] *= texColor[0]; \ srcColor[1] *= texColor[1]; \ srcColor[2] *= texColor[2]; \ @@ -2391,16 +2426,16 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, \ /* Increment the interpolation parameter, UVs, and pointers */ \ discard: \ - z += dzDx; \ - w += dwDx; \ - color[0] += dcDx[0]; \ - color[1] += dcDx[1]; \ - color[2] += dcDx[2]; \ - color[3] += dcDx[3]; \ + z += dZdx; \ + w += dWdx; \ + color[0] += dCdx[0]; \ + color[1] += dCdx[1]; \ + color[2] += dCdx[2]; \ + color[3] += dCdx[3]; \ if (ENABLE_TEXTURE) \ { \ - u += duDx; \ - v += dvDx; \ + u += dUdx; \ + v += dVdx; \ } \ \ INC_COLOR_PTR(cptr); \ @@ -2430,60 +2465,71 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1, if (h02 < 1e-6f) return; \ \ /* Precompute the inverse values without additional checks */ \ - float invH02 = 1.0f/h02; \ - float invH01 = (h01 > 1e-6f)? 1.0f/h01 : 0.0f; \ - float invH12 = (h12 > 1e-6f)? 1.0f/h12 : 0.0f; \ + float h02Rcp = 1.0f/h02; \ + float h01Rcp = (h01 > 1e-6f)? 1.0f/h01 : 0.0f; \ + float h12Rcp = (h12 > 1e-6f)? 1.0f/h12 : 0.0f; \ \ /* Pre-calculation of slopes */ \ - float dx02 = (x2 - x0)*invH02; \ - float dx01 = (x1 - x0)*invH01; \ - float dx12 = (x2 - x1)*invH12; \ + float dXdy02 = (x2 - x0)*h02Rcp; \ + float dXdy01 = (x1 - x0)*h01Rcp; \ + float dXdy12 = (x2 - x1)*h12Rcp; \ + \ + /* Y subpixel correction */ \ + float y0Substep = 1.0f - sw_fract(y0); \ + float y1Substep = 1.0f - sw_fract(y1); \ \ /* Y bounds (vertical clipping) */ \ - int yTop = (int)(y0 + 0.5f); \ - int yMiddle = (int)(y1 + 0.5f); \ - int yBottom = (int)(y2 + 0.5f); \ + int yTop = (int)y0; \ + int yMid = (int)y1; \ + int yBot = (int)y2; \ \ /* Compute gradients for each side of the triangle */ \ - sw_vertex_t vDy02, vDy01, vDy12; \ - sw_get_vertex_grad_PTCH(&vDy02, v0, v2, invH02); \ - sw_get_vertex_grad_PTCH(&vDy01, v0, v1, invH01); \ - sw_get_vertex_grad_PTCH(&vDy12, v1, v2, invH12); \ + sw_vertex_t dVXdy02, dVXdy01, dVXdy12; \ + sw_get_vertex_grad_PTCH(&dVXdy02, v0, v2, h02Rcp); \ + sw_get_vertex_grad_PTCH(&dVXdy01, v0, v1, h01Rcp); \ + sw_get_vertex_grad_PTCH(&dVXdy12, v1, v2, h12Rcp); \ \ - /* Initializing scanline variables */ \ - sw_vertex_t vLeft = *v0; \ - vLeft.screen[0] = x0; \ - sw_vertex_t vRight = *v0; \ - vRight.screen[0] = x0; \ + /* Get a copy of vertices for interpolation and apply substep correction */ \ + sw_vertex_t vLeft = *v0, vRight = *v0; \ + sw_add_vertex_grad_scaled_PTCH(&vLeft, &dVXdy02, y0Substep); \ + sw_add_vertex_grad_scaled_PTCH(&vRight, &dVXdy01, y0Substep); \ + \ + vLeft.screen[0] += dXdy02*y0Substep; \ + vRight.screen[0] += dXdy01*y0Substep; \ \ /* Scanline for the upper part of the triangle */ \ - for (int y = yTop; y < yMiddle; y++) \ + for (int y = yTop; y < yMid; y++) \ { \ vLeft.screen[1] = vRight.screen[1] = y; \ \ - if (vLeft.screen[0] < vRight.screen[0]) FUNC_SCANLINE(tex, &vLeft, &vRight, vDy02.texcoord[0], vDy02.texcoord[1]); \ - else FUNC_SCANLINE(tex, &vRight, &vLeft, vDy02.texcoord[0], vDy02.texcoord[1]); \ + if (vLeft.screen[0] < vRight.screen[0]) FUNC_SCANLINE(tex, &vLeft, &vRight, dVXdy02.texcoord[0], dVXdy02.texcoord[1]); \ + else FUNC_SCANLINE(tex, &vRight, &vLeft, dVXdy02.texcoord[0], dVXdy02.texcoord[1]); \ \ - sw_add_vertex_grad_PTCH(&vLeft, &vDy02); \ - vLeft.screen[0] += dx02; \ - sw_add_vertex_grad_PTCH(&vRight, &vDy01); \ - vRight.screen[0] += dx01; \ + sw_add_vertex_grad_PTCH(&vLeft, &dVXdy02); \ + vLeft.screen[0] += dXdy02; \ + \ + sw_add_vertex_grad_PTCH(&vRight, &dVXdy01); \ + vRight.screen[0] += dXdy01; \ } \ \ - /* Scanline for the lower part of the triangle */ \ - vRight = *v1, vRight.screen[0] = x1; \ + /* Get a copy of next right for interpolation and apply substep correction */ \ + vRight = *v1; \ + sw_add_vertex_grad_scaled_PTCH(&vRight, &dVXdy12, y1Substep); \ + vRight.screen[0] += dXdy12*y1Substep; \ \ - for (int y = yMiddle; y < yBottom; y++) \ + /* Scanline for the lower part of the triangle */ \ + for (int y = yMid; y < yBot; y++) \ { \ vLeft.screen[1] = vRight.screen[1] = y; \ \ - if (vLeft.screen[0] < vRight.screen[0]) FUNC_SCANLINE(tex, &vLeft, &vRight, vDy02.texcoord[0], vDy02.texcoord[1]); \ - else FUNC_SCANLINE(tex, &vRight, &vLeft, vDy02.texcoord[0], vDy02.texcoord[1]); \ + if (vLeft.screen[0] < vRight.screen[0]) FUNC_SCANLINE(tex, &vLeft, &vRight, dVXdy02.texcoord[0], dVXdy02.texcoord[1]); \ + else FUNC_SCANLINE(tex, &vRight, &vLeft, dVXdy02.texcoord[0], dVXdy02.texcoord[1]); \ \ - sw_add_vertex_grad_PTCH(&vLeft, &vDy02); \ - vLeft.screen[0] += dx02; \ - sw_add_vertex_grad_PTCH(&vRight, &vDy12); \ - vRight.screen[0] += dx12; \ + sw_add_vertex_grad_PTCH(&vLeft, &dVXdy02); \ + vLeft.screen[0] += dXdy02; \ + \ + sw_add_vertex_grad_PTCH(&vRight, &dVXdy12); \ + vRight.screen[0] += dXdy12; \ } \ } @@ -2716,41 +2762,46 @@ static inline void FUNC_NAME(void) const sw_vertex_t *v3 = sortedVerts[3]; \ \ /* Screen bounds (axis-aligned) */ \ - int xMin = (int)(v0->screen[0] + 0.5f); \ - int yMin = (int)(v0->screen[1] + 0.5f); \ - int xMax = (int)(v2->screen[0] + 0.5f); \ - int yMax = (int)(v2->screen[1] + 0.5f); \ + int xMin = (int)v0->screen[0]; \ + int yMin = (int)v0->screen[1]; \ + int xMax = (int)v2->screen[0]; \ + int yMax = (int)v2->screen[1]; \ \ - int width = xMax - xMin; \ - int height = yMax - yMin; \ + float w = v2->screen[0] - v0->screen[0]; \ + float h = v2->screen[1] - v0->screen[1]; \ \ - if ((width == 0) || (height == 0)) return; \ + if ((w == 0) || (h == 0)) return; \ \ - float wRcp = (width > 0.0f)? 1.0f/width : 0.0f; \ - float hRcp = (height > 0.0f)? 1.0f/height : 0.0f; \ + float wRcp = (w > 0.0f)? 1.0f/w : 0.0f; \ + float hRcp = (h > 0.0f)? 1.0f/h : 0.0f; \ + \ + /* Subpixel corrections */ \ + float xSubstep = 1.0f - sw_fract(v0->screen[0]); \ + float ySubstep = 1.0f - sw_fract(v0->screen[1]); \ \ /* Calculation of vertex gradients in X and Y */ \ - float tcDx[2], tcDy[2]; \ + float dUdx, dVdx; \ + float dUdy, dVdy; \ if (ENABLE_TEXTURE) { \ - tcDx[0] = (v1->texcoord[0] - v0->texcoord[0])*wRcp; \ - tcDx[1] = (v1->texcoord[1] - v0->texcoord[1])*wRcp; \ - tcDy[0] = (v3->texcoord[0] - v0->texcoord[0])*hRcp; \ - tcDy[1] = (v3->texcoord[1] - v0->texcoord[1])*hRcp; \ + dUdx = (v1->texcoord[0] - v0->texcoord[0])*wRcp; \ + dVdx = (v1->texcoord[1] - v0->texcoord[1])*wRcp; \ + dUdy = (v3->texcoord[0] - v0->texcoord[0])*hRcp; \ + dVdy = (v3->texcoord[1] - v0->texcoord[1])*hRcp; \ } \ \ - float cDx[4], cDy[4]; \ - cDx[0] = (v1->color[0] - v0->color[0])*wRcp; \ - cDx[1] = (v1->color[1] - v0->color[1])*wRcp; \ - cDx[2] = (v1->color[2] - v0->color[2])*wRcp; \ - cDx[3] = (v1->color[3] - v0->color[3])*wRcp; \ - cDy[0] = (v3->color[0] - v0->color[0])*hRcp; \ - cDy[1] = (v3->color[1] - v0->color[1])*hRcp; \ - cDy[2] = (v3->color[2] - v0->color[2])*hRcp; \ - cDy[3] = (v3->color[3] - v0->color[3])*hRcp; \ + float dCdx[4], dCdy[4]; \ + dCdx[0] = (v1->color[0] - v0->color[0])*wRcp; \ + dCdx[1] = (v1->color[1] - v0->color[1])*wRcp; \ + dCdx[2] = (v1->color[2] - v0->color[2])*wRcp; \ + dCdx[3] = (v1->color[3] - v0->color[3])*wRcp; \ + dCdy[0] = (v3->color[0] - v0->color[0])*hRcp; \ + dCdy[1] = (v3->color[1] - v0->color[1])*hRcp; \ + dCdy[2] = (v3->color[2] - v0->color[2])*hRcp; \ + dCdy[3] = (v3->color[3] - v0->color[3])*hRcp; \ \ - float zDx, zDy; \ - zDx = (v1->homogeneous[2] - v0->homogeneous[2])*wRcp; \ - zDy = (v3->homogeneous[2] - v0->homogeneous[2])*hRcp; \ + float dZdx, dZdy; \ + dZdx = (v1->homogeneous[2] - v0->homogeneous[2])*wRcp; \ + dZdy = (v3->homogeneous[2] - v0->homogeneous[2])*hRcp; \ \ /* Start of quad rasterization */ \ const sw_texture_t *tex; \ @@ -2760,15 +2811,15 @@ static inline void FUNC_NAME(void) void *dDstBase = RLSW.framebuffer.depth; \ int wDst = RLSW.framebuffer.width; \ \ - float zScanline = v0->homogeneous[2]; \ - float uScanline = v0->texcoord[0]; \ - float vScanline = v0->texcoord[1]; \ + float zScanline = v0->homogeneous[2] + dZdx*xSubstep + dZdy*ySubstep; \ + float uScanline = v0->texcoord[0] + dUdx*xSubstep + dUdy*ySubstep; \ + float vScanline = v0->texcoord[1] + dVdx*xSubstep + dVdy*ySubstep; \ \ float colorScanline[4] = { \ - v0->color[0], \ - v0->color[1], \ - v0->color[2], \ - v0->color[3] \ + v0->color[0] + dCdx[0]*xSubstep + dCdy[0]*ySubstep, \ + v0->color[1] + dCdx[1]*xSubstep + dCdy[1]*ySubstep, \ + v0->color[2] + dCdx[2]*xSubstep + dCdy[2]*ySubstep, \ + v0->color[3] + dCdx[3]*xSubstep + dCdy[3]*ySubstep \ }; \ \ for (int y = yMin; y < yMax; y++) \ @@ -2806,12 +2857,13 @@ static inline void FUNC_NAME(void) if (z > depth) goto discard; \ } \ \ + /* TODO: Implement depth mask */ \ sw_framebuffer_write_depth(dptr, z); \ \ if (ENABLE_TEXTURE) \ { \ float texColor[4]; \ - sw_texture_sample(texColor, tex, u, v, tcDx[0], tcDy[0], tcDx[1], tcDy[1]); \ + sw_texture_sample(texColor, tex, u, v, dUdx, dUdy, dVdx, dVdy); \ srcColor[0] *= texColor[0]; \ srcColor[1] *= texColor[1]; \ srcColor[2] *= texColor[2]; \ @@ -2828,32 +2880,32 @@ static inline void FUNC_NAME(void) else sw_framebuffer_write_color(cptr, srcColor); \ \ discard: \ - z += zDx; \ - color[0] += cDx[0]; \ - color[1] += cDx[1]; \ - color[2] += cDx[2]; \ - color[3] += cDx[3]; \ + z += dZdx; \ + color[0] += dCdx[0]; \ + color[1] += dCdx[1]; \ + color[2] += dCdx[2]; \ + color[3] += dCdx[3]; \ \ if (ENABLE_TEXTURE) \ { \ - u += tcDx[0]; \ - v += tcDx[1]; \ + u += dUdx; \ + v += dVdx; \ } \ \ INC_COLOR_PTR(cptr); \ INC_DEPTH_PTR(dptr); \ } \ \ - zScanline += zDy; \ - colorScanline[0] += cDy[0]; \ - colorScanline[1] += cDy[1]; \ - colorScanline[2] += cDy[2]; \ - colorScanline[3] += cDy[3]; \ + zScanline += dZdy; \ + colorScanline[0] += dCdy[0]; \ + colorScanline[1] += dCdy[1]; \ + colorScanline[2] += dCdy[2]; \ + colorScanline[3] += dCdy[3]; \ \ if (ENABLE_TEXTURE) \ { \ - uScanline += tcDy[0]; \ - vScanline += tcDy[1]; \ + uScanline += dUdy; \ + vScanline += dVdy; \ } \ } \ } @@ -3021,137 +3073,87 @@ static inline bool sw_line_clip_and_project(sw_vertex_t *v0, sw_vertex_t *v1) #define DEFINE_LINE_RASTER(FUNC_NAME, ENABLE_DEPTH_TEST, ENABLE_COLOR_BLEND) \ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ { \ - int x1 = (int)(v0->screen[0] + 0.5f); \ - int y1 = (int)(v0->screen[1] + 0.5f); \ - int x2 = (int)(v1->screen[0] + 0.5f); \ - int y2 = (int)(v1->screen[1] + 0.5f); \ + float x0 = v0->screen[0]; \ + float y0 = v0->screen[1]; \ + float x1 = v1->screen[0]; \ + float y1 = v1->screen[1]; \ \ - int dx = x2 - x1; \ - int dy = y2 - y1; \ + float dx = x1 - x0; \ + float dy = y1 - y0; \ \ - /* Handling of lines that are more horizontal or vertical */ \ - if ((dx == 0) && (dy == 0)) \ + /* Compute dominant axis and subpixel offset */ \ + float steps, substep; \ + if (fabsf(dx) > fabsf(dy)) \ { \ - /* TODO: A point should be rendered here */ \ - return; \ - } \ - \ - bool yLonger = (abs(dy) > abs(dx)); \ - int longLen, shortLen; \ - \ - if (yLonger) \ - { \ - longLen = dy; \ - shortLen = dx; \ + steps = fabsf(dx); \ + if (steps < 1.0f) return; \ + substep = (dx >= 0.0f)? (1.0f - sw_fract(x0)) : sw_fract(x0); \ } \ else \ { \ - longLen = dx; \ - shortLen = dy; \ + steps = fabsf(dy); \ + if (steps < 1.0f) return; \ + substep = (dy >= 0.0f)? (1.0f - sw_fract(y0)) : sw_fract(y0); \ } \ \ - /* Handling of traversal direction */ \ - int sgnInc = (longLen < 0)? -1 : 1; \ - int abslongLen = abs(longLen); \ + /* Compute per pixel increments */ \ + float xInc = dx/steps; \ + float yInc = dy/steps; \ + float stepRcp = 1.0f/steps; \ \ - /* Calculation of the increment step for the shorter coordinate */ \ - int decInc = 0; \ - if (abslongLen != 0) decInc = (shortLen << 16)/abslongLen; \ + float zInc = (v1->homogeneous[2] - v0->homogeneous[2])*stepRcp; \ + float rInc = (v1->color[0] - v0->color[0])*stepRcp; \ + float gInc = (v1->color[1] - v0->color[1])*stepRcp; \ + float bInc = (v1->color[2] - v0->color[2])*stepRcp; \ + float aInc = (v1->color[3] - v0->color[3])*stepRcp; \ \ - float longLenRcp = (abslongLen != 0)? (1.0f/abslongLen) : 0.0f; \ - \ - /* Calculation of interpolation steps */ \ - const float zStep = (v1->homogeneous[2] - v0->homogeneous[2])*longLenRcp; \ - const float rStep = (v1->color[0] - v0->color[0])*longLenRcp; \ - const float gStep = (v1->color[1] - v0->color[1])*longLenRcp; \ - const float bStep = (v1->color[2] - v0->color[2])*longLenRcp; \ - const float aStep = (v1->color[3] - v0->color[3])*longLenRcp; \ - \ - float z = v0->homogeneous[2]; \ - \ - float color[4] = { \ - v0->color[0], \ - v0->color[1], \ - v0->color[2], \ - v0->color[3] \ - }; \ + /* Initializing the interpolation starting values */ \ + float x = x0 + xInc*substep; \ + float y = y0 + yInc*substep; \ + float z = v0->homogeneous[2] + zInc*substep; \ + float r = v0->color[0] + rInc*substep; \ + float g = v0->color[1] + gInc*substep; \ + float b = v0->color[2] + bInc*substep; \ + float a = v0->color[3] + aInc*substep; \ \ const int fbWidth = RLSW.framebuffer.width; \ void *cBuffer = RLSW.framebuffer.color; \ void *dBuffer = RLSW.framebuffer.depth; \ \ - int j = 0; \ - if (yLonger) \ + int numPixels = (int)(steps - substep) + 1; \ + \ + for (int i = 0; i < numPixels; i++) \ { \ - for (int i = 0; i != longLen; i += sgnInc) \ + /* REVIEW: May require reviewing projection details */ \ + int px = (int)(x - 0.5f); \ + int py = (int)(y - 0.5f); \ + \ + int offset = py*fbWidth + px; \ + void *dptr = GET_DEPTH_PTR(dBuffer, offset); \ + \ + if (ENABLE_DEPTH_TEST) \ { \ - int offset = (y1 + i)*fbWidth + (x1 + (j >> 16)); \ - void *dptr = GET_DEPTH_PTR(dBuffer, offset); \ - void *cptr = NULL; \ - \ - if (ENABLE_DEPTH_TEST) \ - { \ - float depth = sw_framebuffer_read_depth(dptr); \ - if (z > depth) goto discardA; \ - } \ - \ - sw_framebuffer_write_depth(dptr, z); \ - \ - cptr = GET_COLOR_PTR(cBuffer, offset); \ - \ - if (ENABLE_COLOR_BLEND) \ - { \ - float dstColor[4]; \ - sw_framebuffer_read_color(dstColor, cptr); \ - sw_blend_colors(dstColor, color); \ - sw_framebuffer_write_color(cptr, dstColor); \ - } \ - else sw_framebuffer_write_color(cptr, color); \ - \ - discardA: \ - j += decInc; \ - z += zStep; \ - color[0] += rStep; \ - color[1] += gStep; \ - color[2] += bStep; \ - color[3] += aStep; \ + float depth = sw_framebuffer_read_depth(dptr); \ + if (z > depth) goto discard; \ } \ - } \ - else \ - { \ - for (int i = 0; i != longLen; i += sgnInc) \ + \ + sw_framebuffer_write_depth(dptr, z); \ + \ + void *cptr = GET_COLOR_PTR(cBuffer, offset); \ + float color[4] = {r, g, b, a}; \ + \ + if (ENABLE_COLOR_BLEND) \ { \ - int offset = (y1 + (j >> 16))*fbWidth + (x1 + i); \ - void *dptr = GET_DEPTH_PTR(dBuffer, offset); \ - void *cptr = NULL; \ - \ - if (ENABLE_DEPTH_TEST) \ - { \ - float depth = sw_framebuffer_read_depth(dptr); \ - if (z > depth) goto discardB; \ - } \ - \ - sw_framebuffer_write_depth(dptr, z); \ - \ - cptr = GET_COLOR_PTR(cBuffer, offset); \ - \ - if (ENABLE_COLOR_BLEND) \ - { \ - float dstColor[4]; \ - sw_framebuffer_read_color(dstColor, cptr); \ - sw_blend_colors(dstColor, color); \ - sw_framebuffer_write_color(cptr, dstColor); \ - } \ - else sw_framebuffer_write_color(cptr, color); \ - \ - discardB: \ - j += decInc; \ - z += zStep; \ - color[0] += rStep; \ - color[1] += gStep; \ - color[2] += bStep; \ - color[3] += aStep; \ + float dstColor[4]; \ + sw_framebuffer_read_color(dstColor, cptr); \ + sw_blend_colors(dstColor, color); \ + sw_framebuffer_write_color(cptr, dstColor); \ } \ + else sw_framebuffer_write_color(cptr, color); \ + \ + discard: \ + x += xInc; y += yInc; z += zInc; \ + r += rInc; g += gInc; b += bInc; a += aInc; \ } \ } @@ -3857,11 +3859,11 @@ void swViewport(int x, int y, int width, int height) RLSW.vpSize[0] = width; RLSW.vpSize[1] = height; - RLSW.vpHalfSize[0] = (int)(width/2.0f + 0.5f); - RLSW.vpHalfSize[1] = (int)(height/2.0f + 0.5f); + RLSW.vpHalf[0] = width/2.0f; + RLSW.vpHalf[1] = height/2.0f; - RLSW.vpCenter[0] = x + RLSW.vpHalfSize[0]; - RLSW.vpCenter[1] = y + RLSW.vpHalfSize[1]; + RLSW.vpCenter[0] = (float)x + RLSW.vpHalf[0]; + RLSW.vpCenter[1] = (float)y + RLSW.vpHalf[1]; RLSW.vpMin[0] = sw_clampi(x, 0, RLSW.framebuffer.width - 1); RLSW.vpMin[1] = sw_clampi(y, 0, RLSW.framebuffer.height - 1); From 1c20b5588d349c08a0e0df114477e5f4090f6966 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 25 Oct 2025 23:20:49 +0200 Subject: [PATCH 002/430] Updatee project id --- .../shapes_math_angle_rotation.vcxproj | 2 +- projects/VS2022/raylib.sln | 27 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/projects/VS2022/examples/shapes_math_angle_rotation.vcxproj b/projects/VS2022/examples/shapes_math_angle_rotation.vcxproj index 0bee76192..48da9d846 100644 --- a/projects/VS2022/examples/shapes_math_angle_rotation.vcxproj +++ b/projects/VS2022/examples/shapes_math_angle_rotation.vcxproj @@ -51,7 +51,7 @@ - {6B1A933E-71B8-4C1F-9E79-02D98830E671} + {84DE22BB-C25F-425C-A7FE-0120CF107B83} Win32Proj shapes_math_angle_rotation 10.0 diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index 8d2704abf..81f700bec 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -395,7 +395,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_text_file_loading", "e EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shaders_mandelbrot_set", "examples\shaders_mandelbrot_set.vcxproj", "{1C829D1A-892C-451C-AF0B-AC65C85F5CC6}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_math_angle_rotation", "examples\shapes_math_angle_rotation.vcxproj", "{6B1A933E-71B8-4C1F-9E79-02D98830E671}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_math_angle_rotation", "examples\shapes_math_angle_rotation.vcxproj", "{84DE22BB-C25F-425C-A7FE-0120CF107B83}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -4899,6 +4899,30 @@ Global {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release|x64.Build.0 = Release|x64 {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release|x86.ActiveCfg = Release|Win32 {1C829D1A-892C-451C-AF0B-AC65C85F5CC6}.Release|x86.Build.0 = Release|Win32 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Debug|ARM64.Build.0 = Debug|ARM64 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Debug|x64.ActiveCfg = Debug|x64 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Debug|x64.Build.0 = Debug|x64 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Debug|x86.ActiveCfg = Debug|Win32 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Debug|x86.Build.0 = Debug|Win32 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Release|ARM64.ActiveCfg = Release|ARM64 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Release|ARM64.Build.0 = Release|ARM64 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Release|x64.ActiveCfg = Release|x64 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Release|x64.Build.0 = Release|x64 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Release|x86.ActiveCfg = Release|Win32 + {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5098,6 +5122,7 @@ Global {9DE2FC01-A839-4F89-8319-9071D4C54821} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {2F578155-D51F-4C03-AB7F-5C5122CA46CC} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {1C829D1A-892C-451C-AF0B-AC65C85F5CC6} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} + {84DE22BB-C25F-425C-A7FE-0120CF107B83} = {278D8859-20B1-428F-8448-064F46E1F021} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} From a818508158119f74e99b4dc98345c28ce112733c Mon Sep 17 00:00:00 2001 From: Le Juez Victor <90587919+Bigfoot71@users.noreply.github.com> Date: Sun, 26 Oct 2025 00:58:56 +0200 Subject: [PATCH 003/430] [rlsw] Completeness of `glDraw` functions (#5304) * adding `glDrawElements` * tweaks * fix `glDrawArrays` and `glDrawElements` behavior --- src/external/rlsw.h | 303 +++++++++++++++++++++++++++++++++----------- 1 file changed, 229 insertions(+), 74 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index 50a60d74f..e049e707e 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -376,6 +376,7 @@ typedef double GLclampd; #define glTexCoordPointer(sz, t, s, p) swBindArray(SW_TEXTURE_COORD_ARRAY, (p)) #define glColorPointer(sz, t, s, p) swBindArray(SW_COLOR_ARRAY, (p)) #define glDrawArrays(m, o, c) swDrawArrays((m), (o), (c)) +#define glDrawElements(m,c,t,i) swDrawElements((m),(c),(t),(i)) #define glGenTextures(c, v) swGenTextures((c), (v)) #define glDeleteTextures(c, v) swDeleteTextures((c), (v)) #define glTexImage2D(tr, l, if, w, h, b, f, t, p) swTexImage2D((w), (h), (f), (t), (p)) @@ -393,7 +394,6 @@ typedef double GLclampd; #define glDepthFunc(X) ((void)(X)) #define glTexSubImage2D(X,Y,Z,W,A,B,C,D,E) ((void)(X),(void)(Y),(void)(Z),(void)(W),(void)(A),(void)(B),(void)(C),(void)(D),(void)(E)) #define glGetTexImage(X,Y,Z,W,A) ((void)(X),(void)(Y),(void)(Z),(void)(W),(void)(A)) -#define glDrawElements(X,Y,Z,W) ((void)(X),(void)(Y),(void)(Z),(void)(W)) #define glNormal3f(X,Y,Z) ((void)(X),(void)(Y),(void)(Z)) #define glNormal3fv(X) ((void)(X)) #define glNormalPointer(X,Y,Z) ((void)(X),(void)(Y),(void)(Z)) @@ -589,6 +589,7 @@ SWAPI void swTexCoord2fv(const float *v); SWAPI void swBindArray(SWarray type, void *buffer); SWAPI void swDrawArrays(SWdraw mode, int offset, int count); +SWAPI void swDrawElements(SWdraw mode, int count, int type, const void *indices); SWAPI void swGenTextures(int count, uint32_t *textures); SWAPI void swDeleteTextures(int count, uint32_t *textures); @@ -674,6 +675,7 @@ SWAPI void swBindTexture(uint32_t id); // Defines and Macros //---------------------------------------------------------------------------------- #define SW_PI 3.14159265358979323846f +#define SW_INV_255 0.00392156862745098f #define SW_DEG2RAD (SW_PI/180.0f) #define SW_RAD2DEG (180.0f/SW_PI) @@ -1120,26 +1122,26 @@ static inline void sw_float_from_unorm8_simd(float dst[4], const uint8_t src[4]) uint32x4_t ints = vmovl_u16(vget_low_u16(bytes16)); float32x4_t floats = vcvtq_f32_u32(ints); - floats = vmulq_n_f32(floats, 1.0f/255.0f); + floats = vmulq_n_f32(floats, SW_INV_255); vst1q_f32(dst, floats); #elif defined(SW_HAS_SSE41) __m128i bytes = _mm_cvtsi32_si128(*(const uint32_t*)src); __m128i ints = _mm_cvtepu8_epi32(bytes); __m128 floats = _mm_cvtepi32_ps(ints); - floats = _mm_mul_ps(floats, _mm_set1_ps(1.0f/255.0f)); + floats = _mm_mul_ps(floats, _mm_set1_ps(SW_INV_255)); _mm_storeu_ps(dst, floats); #elif defined(SW_HAS_SSE2) __m128i bytes = _mm_cvtsi32_si128(*(const uint32_t*)src); bytes = _mm_unpacklo_epi8(bytes, _mm_setzero_si128()); __m128i ints = _mm_unpacklo_epi16(bytes, _mm_setzero_si128()); __m128 floats = _mm_cvtepi32_ps(ints); - floats = _mm_mul_ps(floats, _mm_set1_ps(1.0f/255.0f)); + floats = _mm_mul_ps(floats, _mm_set1_ps(SW_INV_255)); _mm_storeu_ps(dst, floats); #else - dst[0] = (float)src[0]/255.0f; - dst[1] = (float)src[1]/255.0f; - dst[2] = (float)src[2]/255.0f; - dst[3] = (float)src[3]/255.0f; + dst[0] = (float)src[0]*SW_INV_255; + dst[1] = (float)src[1]*SW_INV_255; + dst[2] = (float)src[2]*SW_INV_255; + dst[3] = (float)src[3]*SW_INV_255; #endif } @@ -1894,7 +1896,7 @@ static inline void sw_get_pixel(uint8_t *color, const void *pixels, uint32_t off case SW_PIXELFORMAT_UNCOMPRESSED_R16: { uint16_t val = ((const uint16_t*)pixels)[offset]; - uint8_t gray = sw_half_to_float(val)/255.0f; + uint8_t gray = sw_half_to_float(val)*SW_INV_255; color[0] = gray; color[1] = gray; color[2] = gray; @@ -1904,19 +1906,19 @@ static inline void sw_get_pixel(uint8_t *color, const void *pixels, uint32_t off case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: { const uint16_t *src = &((const uint16_t*)pixels)[offset*3]; - color[0] = sw_half_to_float(src[0])/255.0f; - color[1] = sw_half_to_float(src[1])/255.0f; - color[2] = sw_half_to_float(src[2])/255.0f; + color[0] = sw_half_to_float(src[0])*SW_INV_255; + color[1] = sw_half_to_float(src[1])*SW_INV_255; + color[2] = sw_half_to_float(src[2])*SW_INV_255; color[3] = 255; break; } case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: { const uint16_t *src = &((const uint16_t*)pixels)[offset*4]; - color[0] = sw_half_to_float(src[0])/255.0f; - color[1] = sw_half_to_float(src[1])/255.0f; - color[2] = sw_half_to_float(src[2])/255.0f; - color[3] = sw_half_to_float(src[3])/255.0f; + color[0] = sw_half_to_float(src[0])*SW_INV_255; + color[1] = sw_half_to_float(src[1])*SW_INV_255; + color[2] = sw_half_to_float(src[2])*SW_INV_255; + color[3] = sw_half_to_float(src[3])*SW_INV_255; break; } case SW_PIXELFORMAT_UNKNOWN: @@ -3404,7 +3406,7 @@ static inline void sw_point_render(sw_vertex_t *v) } //------------------------------------------------------------------------------------------- -// Polygon modes mendering logic +// Polygon modes rendering logic //------------------------------------------------------------------------------------------- static inline void sw_poly_point_render(void) { @@ -3436,9 +3438,47 @@ static inline void sw_poly_fill_render(void) case SW_QUADS: sw_quad_render(); break; } } +//------------------------------------------------------------------------------------------- + +// Immediate rendering logic +//------------------------------------------------------------------------------------------- +void sw_immediate_push_vertex(const float position[4], const float color[4], const float texcoord[2]) +{ + // Copy the attributes in the current vertex + sw_vertex_t *vertex = &RLSW.vertexBuffer[RLSW.vertexCounter++]; + for (int i = 0; i < 4; i++) + { + vertex->position[i] = position[i]; + if (i < 2) vertex->texcoord[i] = texcoord[i]; + vertex->color[i] = color[i]; + } + + // Calculate homogeneous coordinates + const float *m = RLSW.matMVP, *v = vertex->position; + vertex->homogeneous[0] = m[0]*v[0] + m[4]*v[1] + m[8]*v[2] + m[12]*v[3]; + vertex->homogeneous[1] = m[1]*v[0] + m[5]*v[1] + m[9]*v[2] + m[13]*v[3]; + vertex->homogeneous[2] = m[2]*v[0] + m[6]*v[1] + m[10]*v[2] + m[14]*v[3]; + vertex->homogeneous[3] = m[3]*v[0] + m[7]*v[1] + m[11]*v[2] + m[15]*v[3]; + + // Immediate rendering of the primitive if the required number is reached + if (RLSW.vertexCounter == RLSW.reqVertices) + { + switch (RLSW.polyMode) + { + case SW_FILL: sw_poly_fill_render(); break; + case SW_LINE: sw_poly_line_render(); break; + case SW_POINT: sw_poly_point_render(); break; + default: break; + } + + RLSW.vertexCounter = 0; + } +} + +//------------------------------------------------------------------------------------------- // Validity check helper functions - +//------------------------------------------------------------------------------------------- static inline bool sw_is_texture_valid(uint32_t id) { bool valid = true; @@ -4301,89 +4341,62 @@ void swEnd(void) void swVertex2i(int x, int y) { const float v[4] = { (float)x, (float)y, 0.0f, 1.0f }; - swVertex4fv(v); + sw_immediate_push_vertex(v, RLSW.current.color, RLSW.current.texcoord); } void swVertex2f(float x, float y) { const float v[4] = { x, y, 0.0f, 1.0f }; - swVertex4fv(v); + sw_immediate_push_vertex(v, RLSW.current.color, RLSW.current.texcoord); } void swVertex2fv(const float *v) { const float v4[4] = { v[0], v[1], 0.0f, 1.0f }; - swVertex4fv(v4); + sw_immediate_push_vertex(v, RLSW.current.color, RLSW.current.texcoord); } void swVertex3i(int x, int y, int z) { const float v[4] = { (float)x, (float)y, (float)z, 1.0f }; - swVertex4fv(v); + sw_immediate_push_vertex(v, RLSW.current.color, RLSW.current.texcoord); } void swVertex3f(float x, float y, float z) { const float v[4] = { x, y, z, 1.0f }; - swVertex4fv(v); + sw_immediate_push_vertex(v, RLSW.current.color, RLSW.current.texcoord); } void swVertex3fv(const float *v) { const float v4[4] = { v[0], v[1], v[2], 1.0f }; - swVertex4fv(v4); + sw_immediate_push_vertex(v, RLSW.current.color, RLSW.current.texcoord); } void swVertex4i(int x, int y, int z, int w) { const float v[4] = { (float)x, (float)y, (float)z, (float)w }; - swVertex4fv(v); + sw_immediate_push_vertex(v, RLSW.current.color, RLSW.current.texcoord); } void swVertex4f(float x, float y, float z, float w) { const float v[4] = { x, y, z, w }; - swVertex4fv(v); + sw_immediate_push_vertex(v, RLSW.current.color, RLSW.current.texcoord); } void swVertex4fv(const float *v) { - // Copy the position in the current vertex - sw_vertex_t *vertex = &RLSW.vertexBuffer[RLSW.vertexCounter++]; - for (int i = 0; i < 4; i++) vertex->position[i] = v[i]; - - // Copy additonal vertex data - for (int i = 0; i < 2; i++) vertex->texcoord[i] = RLSW.current.texcoord[i]; - for (int i = 0; i < 4; i++) vertex->color[i] = RLSW.current.color[i]; - - // Calculation of homogeneous coordinates - const float *m = RLSW.matMVP; - vertex->homogeneous[0] = m[0]*v[0] + m[4]*v[1] + m[8]*v[2] + m[12]*v[3]; - vertex->homogeneous[1] = m[1]*v[0] + m[5]*v[1] + m[9]*v[2] + m[13]*v[3]; - vertex->homogeneous[2] = m[2]*v[0] + m[6]*v[1] + m[10]*v[2] + m[14]*v[3]; - vertex->homogeneous[3] = m[3]*v[0] + m[7]*v[1] + m[11]*v[2] + m[15]*v[3]; - - // Immediate rendering of the primitive if the required number is reached - if (RLSW.vertexCounter == RLSW.reqVertices) - { - switch (RLSW.polyMode) - { - case SW_FILL: sw_poly_fill_render(); break; - case SW_LINE: sw_poly_line_render(); break; - case SW_POINT: sw_poly_point_render(); break; - default: break; - } - - RLSW.vertexCounter = 0; - } + sw_immediate_push_vertex(v, RLSW.current.color, RLSW.current.texcoord); } void swColor3ub(uint8_t r, uint8_t g, uint8_t b) { float cv[4]; - cv[0] = (float)r/255; - cv[1] = (float)g/255; - cv[2] = (float)b/255; + cv[0] = (float)r*SW_INV_255; + cv[1] = (float)g*SW_INV_255; + cv[2] = (float)b*SW_INV_255; cv[3] = 1.0f; swColor4fv(cv); @@ -4392,9 +4405,9 @@ void swColor3ub(uint8_t r, uint8_t g, uint8_t b) void swColor3ubv(const uint8_t *v) { float cv[4]; - cv[0] = (float)v[0]/255; - cv[1] = (float)v[1]/255; - cv[2] = (float)v[2]/255; + cv[0] = (float)v[0]*SW_INV_255; + cv[1] = (float)v[1]*SW_INV_255; + cv[2] = (float)v[2]*SW_INV_255; cv[3] = 1.0f; swColor4fv(cv); @@ -4425,10 +4438,10 @@ void swColor3fv(const float *v) void swColor4ub(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { float cv[4]; - cv[0] = (float)r/255; - cv[1] = (float)g/255; - cv[2] = (float)b/255; - cv[3] = (float)a/255; + cv[0] = (float)r*SW_INV_255; + cv[1] = (float)g*SW_INV_255; + cv[2] = (float)b*SW_INV_255; + cv[3] = (float)a*SW_INV_255; swColor4fv(cv); } @@ -4436,10 +4449,10 @@ void swColor4ub(uint8_t r, uint8_t g, uint8_t b, uint8_t a) void swColor4ubv(const uint8_t *v) { float cv[4]; - cv[0] = (float)v[0]/255; - cv[1] = (float)v[1]/255; - cv[2] = (float)v[2]/255; - cv[3] = (float)v[3]/255; + cv[0] = (float)v[0]*SW_INV_255; + cv[1] = (float)v[1]*SW_INV_255; + cv[2] = (float)v[2]*SW_INV_255; + cv[3] = (float)v[3]*SW_INV_255; swColor4fv(cv); } @@ -4497,14 +4510,156 @@ void swDrawArrays(SWdraw mode, int offset, int count) swBegin(mode); { - swTexCoord2f(0.0f, 0.0f); - swColor4f(1.0f, 1.0f, 1.0f, 1.0f); + const float *texMatrix = RLSW.stackTexture[RLSW.stackTextureCounter - 1]; + const float *defaultTexcoord = RLSW.current.texcoord; + const float *defaultColor = RLSW.current.color; + + const float *positions = RLSW.array.positions; + const float *texcoords = RLSW.array.texcoords; + const uint8_t *colors = RLSW.array.colors; - for (int i = offset; i < count; i++) + int end = offset + count; + + for (int i = offset; i < end; i++) { - if (RLSW.array.texcoords) swTexCoord2fv(RLSW.array.texcoords + 2*i); - if (RLSW.array.colors) swColor4ubv(RLSW.array.colors + 4*i); - swVertex3fv(RLSW.array.positions + 3*i); + float u, v; + if (texcoords) + { + int idx = 2 * i; + u = texcoords[idx]; + v = texcoords[idx + 1]; + } + else + { + u = defaultTexcoord[0]; + v = defaultTexcoord[1]; + } + + float texcoord[2]; + texcoord[0] = texMatrix[0] * u + texMatrix[4] * v + texMatrix[12]; + texcoord[1] = texMatrix[1] * u + texMatrix[5] * v + texMatrix[13]; + + float color[4] = { + defaultColor[0], + defaultColor[1], + defaultColor[2], + defaultColor[3] + }; + + if (colors) + { + int idx = 4 * i; + color[0] *= (float)colors[idx]*SW_INV_255; + color[1] *= (float)colors[idx + 1]*SW_INV_255; + color[2] *= (float)colors[idx + 2]*SW_INV_255; + color[3] *= (float)colors[idx + 3]*SW_INV_255; + } + + int idx = 3 * i; + float position[4] = { + positions[idx], + positions[idx + 1], + positions[idx + 2], + 1.0f + }; + + sw_immediate_push_vertex(position, color, texcoord); + } + } + swEnd(); +} + +void swDrawElements(SWdraw mode, int count, int type, const void *indices) +{ + if (RLSW.array.positions == 0) + { + RLSW.errCode = SW_INVALID_OPERATION; + return; + } + + if (count < 0) + { + RLSW.errCode = SW_INVALID_VALUE; + return; + } + + const uint8_t *indicesUb = NULL; + const uint16_t *indicesUs = NULL; + const uint32_t *indicesUi = NULL; + + switch (type) + { + case SW_UNSIGNED_BYTE: + indicesUb = (const uint8_t *)indices; + break; + case SW_UNSIGNED_SHORT: + indicesUs = (const uint16_t *)indices; + break; + case SW_UNSIGNED_INT: + indicesUi = (const uint32_t *)indices; + break; + default: + RLSW.errCode = SW_INVALID_ENUM; + return; + } + + swBegin(mode); + { + const float *texMatrix = RLSW.stackTexture[RLSW.stackTextureCounter - 1]; + const float *defaultTexcoord = RLSW.current.texcoord; + const float *defaultColor = RLSW.current.color; + + const float *positions = RLSW.array.positions; + const float *texcoords = RLSW.array.texcoords; + const uint8_t *colors = RLSW.array.colors; + + for (int i = 0; i < count; i++) + { + int index = indicesUb ? indicesUb[i] : + (indicesUs ? indicesUs[i] : indicesUi[i]); + + float u, v; + if (texcoords) + { + int idx = 2 * index; + u = texcoords[idx]; + v = texcoords[idx + 1]; + } + else + { + u = defaultTexcoord[0]; + v = defaultTexcoord[1]; + } + + float texcoord[2]; + texcoord[0] = texMatrix[0] * u + texMatrix[4] * v + texMatrix[12]; + texcoord[1] = texMatrix[1] * u + texMatrix[5] * v + texMatrix[13]; + + float color[4] = { + defaultColor[0], + defaultColor[1], + defaultColor[2], + defaultColor[3] + }; + + if (colors) + { + int idx = 4 * index; + color[0] *= (float)colors[idx]*SW_INV_255; + color[1] *= (float)colors[idx + 1]*SW_INV_255; + color[2] *= (float)colors[idx + 2]*SW_INV_255; + color[3] *= (float)colors[idx + 3]*SW_INV_255; + } + + int idx = 3 * index; + float position[4] = { + positions[idx], + positions[idx + 1], + positions[idx + 2], + 1.0f + }; + + sw_immediate_push_vertex(position, color, texcoord); } } swEnd(); From e244cf297abd7c3cac887cbb38ad194d4c20a53d Mon Sep 17 00:00:00 2001 From: themushroompirates <59015901+themushroompirates@users.noreply.github.com> Date: Sun, 26 Oct 2025 18:15:45 +0100 Subject: [PATCH 004/430] examples_model_decals : Fixed unload crash, added buttons (#5306) --- examples/models/models_decals.c | 497 +++++++++++++++++------------- examples/models/models_decals.png | Bin 62387 -> 67453 bytes 2 files changed, 283 insertions(+), 214 deletions(-) diff --git a/examples/models/models_decals.c b/examples/models/models_decals.c index af1ecfc6d..2786168f6 100644 --- a/examples/models/models_decals.c +++ b/examples/models/models_decals.c @@ -43,8 +43,10 @@ typedef struct MeshBuilder { static void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]); static void FreeMeshBuilder(MeshBuilder *mb); static Mesh BuildMesh(MeshBuilder *mb); -static Mesh GenMeshDecal(Mesh inputMesh, Ray ray); +static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, float decalOffset); static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s); +#define FreeDecalMeshData() GenMeshDecal((Model){ .meshCount = -1.0f }, (Matrix){ 0 }, 0.0f, 0.0f) +static bool Button(Rectangle rec, char *label); //------------------------------------------------------------------------------------ // Program main entry point @@ -105,10 +107,6 @@ int main(void) decalMaterial.maps[MATERIAL_MAP_DIFFUSE].texture = decalTexture; decalMaterial.maps[MATERIAL_MAP_DIFFUSE].color = RAYWHITE; - // We're going to use these to build up our decal meshes - // They'll resize automatically as we go, we'll free them at the end - MeshBuilder meshBuilders[2] = { 0 }; - bool showModel = true; Model decalModels[MAX_DECALS] = { 0 }; int decalCount = 0; @@ -123,8 +121,6 @@ int main(void) //---------------------------------------------------------------------------------- if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) UpdateCamera(&camera, CAMERA_THIRD_PERSON); - if (IsKeyPressed(KEY_SPACE)) showModel = !showModel; - // Display information about closest hit RayCollision collision = { 0 }; collision.distance = FLT_MAX; @@ -165,206 +161,12 @@ int main(void) // Spin the placement around a bit splat = MatrixMultiply(splat, MatrixRotateZ(DEG2RAD*((float)GetRandomValue(-180, 180)))); - Matrix splatInv = MatrixInvert(splat); - // Reset the mesh builders - meshBuilders[0].vertexCount = 0; - meshBuilders[1].vertexCount = 0; - - // We'll be flip-flopping between the two mesh builders - // Reading from one and writing to the other, then swapping - int mbIndex = 0; - - // First pass, just get any triangle inside the bounding box (for each mesh of the model) - for (int meshIndex = 0; meshIndex < model.meshCount; meshIndex++) - { - Mesh mesh = model.meshes[meshIndex]; - for (int tri = 0; tri < mesh.triangleCount; tri++) - { - Vector3 vertices[3] = { 0 }; - - // The way we calculate the vertices of the mesh triangle - // depend on whether the mesh vertices are indexed or not - if (mesh.indices == 0) - { - for (int v = 0; v < 3; v++) - { - vertices[v] = (Vector3){ - mesh.vertices[3*3*tri + 3*v + 0], - mesh.vertices[3*3*tri + 3*v + 1], - mesh.vertices[3*3*tri + 3*v + 2] - }; - } - } - else - { - for (int v = 0; v < 3; v++) - { - vertices[v] = (Vector3){ - mesh.vertices[ 3*mesh.indices[3*tri+0] + v], - mesh.vertices[ 3*mesh.indices[3*tri+1] + v], - mesh.vertices[ 3*mesh.indices[3*tri+2] + v] - }; - } - } - - // Transform all 3 vertices of the triangle - // and check if they are inside our decal box - int insideCount = 0; - for (int i = 0; i < 3; i++) - { - // To splat space - Vector3 v = Vector3Transform(vertices[i], splat); - - if ((fabsf(v.x) < decalSize) || (fabsf(v.y) <= decalSize) || (fabsf(v.z) <= decalSize)) insideCount++; - - // We need to keep the transformed vertex - vertices[i] = v; - } - - // If any of them are inside, we add the triangle - we'll clip it later - if (insideCount > 0) AddTriangleToMeshBuilder(&meshBuilders[mbIndex], vertices); - } - } - - // Clipping time! We need to clip against all 6 directions - Vector3 planes[6] = { - { 1, 0, 0 }, - { -1, 0, 0 }, - { 0, 1, 0 }, - { 0, -1, 0 }, - { 0, 0, 1 }, - { 0, 0, -1 } - }; - - for (int face = 0; face < 6; face++) - { - // Swap current model builder (so we read from the one we just wrote to) - mbIndex = 1 - mbIndex; - - MeshBuilder *inMesh = &meshBuilders[1 - mbIndex]; - MeshBuilder *outMesh = &meshBuilders[mbIndex]; - - // Reset write builder - outMesh->vertexCount = 0; - - float s = 0.5f*decalSize; - - for (int i = 0; i < inMesh->vertexCount; i += 3) - { - Vector3 nV1, nV2, nV3, nV4; - - float d1 = Vector3DotProduct(inMesh->vertices[ i + 0 ], planes[face] ) - s; - float d2 = Vector3DotProduct(inMesh->vertices[ i + 1 ], planes[face] ) - s; - float d3 = Vector3DotProduct(inMesh->vertices[ i + 2 ], planes[face] ) - s; - - int v1Out = (d1 > 0); - int v2Out = (d2 > 0); - int v3Out = (d3 > 0); - - // Calculate, how many vertices of the face lie outside of the clipping plane - int total = v1Out + v2Out + v3Out; - - switch (total) - { - case 0: - { - // The entire face lies inside of the plane, no clipping needed - AddTriangleToMeshBuilder(outMesh, (Vector3[3]){inMesh->vertices[i], inMesh->vertices[i+1], inMesh->vertices[i+2]}); - } break; - case 1: - { - // One vertex lies outside of the plane, perform clipping - if (v1Out) - { - nV1 = inMesh->vertices[i + 1]; - nV2 = inMesh->vertices[i + 2]; - nV3 = ClipSegment(inMesh->vertices[i], nV1, planes[face], s); - nV4 = ClipSegment(inMesh->vertices[i], nV2, planes[face], s); - } - - if (v2Out) - { - nV1 = inMesh->vertices[i]; - nV2 = inMesh->vertices[i + 2]; - nV3 = ClipSegment(inMesh->vertices[i + 1], nV1, planes[face], s); - nV4 = ClipSegment(inMesh->vertices[i + 1], nV2, planes[face], s); - - AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV3, nV2, nV1}); - AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV2, nV3, nV4}); - break; - } - - if (v3Out) - { - nV1 = inMesh->vertices[i]; - nV2 = inMesh->vertices[i + 1]; - nV3 = ClipSegment(inMesh->vertices[i + 2], nV1, planes[face], s); - nV4 = ClipSegment(inMesh->vertices[i + 2], nV2, planes[face], s); - } - - AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); - AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV4, nV3, nV2}); - } break; - case 2: - { - // Two vertices lies outside of the plane, perform clipping - if (!v1Out) - { - nV1 = inMesh->vertices[i]; - nV2 = ClipSegment(nV1, inMesh->vertices[i + 1], planes[face], s); - nV3 = ClipSegment(nV1, inMesh->vertices[i + 2], planes[face], s); - AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); - } - - if (!v2Out) - { - nV1 = inMesh->vertices[i + 1]; - nV2 = ClipSegment(nV1, inMesh->vertices[i + 2], planes[face], s); - nV3 = ClipSegment(nV1, inMesh->vertices[i], planes[face], s); - AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); - } - - if (!v3Out) - { - nV1 = inMesh->vertices[i + 2]; - nV2 = ClipSegment(nV1, inMesh->vertices[i], planes[face], s); - nV3 = ClipSegment(nV1, inMesh->vertices[i + 1], planes[face], s); - AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); - } - } break; - case 3: // The entire face lies outside of the plane, so let's discard the corresponding vertices - default: break; - } - } - } - - // Now we just need to re-transform the vertices - MeshBuilder *theMesh = &meshBuilders[mbIndex]; - - // Allocate room for UVs - if (theMesh->vertexCount > 0) - { - theMesh->uvs = (Vector2 *)MemAlloc(sizeof(Vector2)*theMesh->vertexCount); - - for (int i = 0; i < theMesh->vertexCount; i++) - { - // Calculate the UVs based on the projected coords - // They are clipped to (-decalSize .. decalSize) and we want them (0..1) - theMesh->uvs[i].x = (theMesh->vertices[i].x/decalSize + 0.5f); - theMesh->uvs[i].y = (theMesh->vertices[i].y/decalSize + 0.5f); - - // From splat space to world space - theMesh->vertices[i] = Vector3Transform(theMesh->vertices[i], splatInv); - - // Tiny nudge in the normal direction so it renders properly over the mesh - theMesh->vertices[i] = Vector3Add(theMesh->vertices[i], Vector3Scale(collision.normal, decalOffset)); - } - - // Decal model data ready, create it and add it + Mesh decalMesh = GenMeshDecal(model, splat, decalSize, decalOffset); + if (decalMesh.vertexCount > 0) { int decalIndex = decalCount++; - decalModels[decalIndex] = LoadModelFromMesh(BuildMesh(theMesh)); - decalModels[decalIndex].materials[0] = decalMaterial; + decalModels[decalIndex] = LoadModelFromMesh(decalMesh); + decalModels[decalIndex].materials[0].maps[0] = decalMaterial.maps[0]; } } //---------------------------------------------------------------------------------- @@ -381,7 +183,7 @@ int main(void) // Draw the decal models for (int i = 0; i < decalCount; i++) DrawModel(decalModels[i], (Vector3){0}, 1.0f, WHITE); - // If we hit the mesh, draw the box for the decal + // If we hit the mesh, draw the box for the decal if (collision.hit) { Vector3 origin = Vector3Add(collision.point, Vector3Scale(collision.normal, 1.0f)); @@ -418,13 +220,20 @@ int main(void) for (int i = 0; i < decalCount; i++) { - DrawText(TextFormat("Decal #%d", i+1), x0, yPos, 10, LIME); - DrawText(TextFormat("%d", decalModels[i].meshes[0].vertexCount), x1, yPos, 10, LIME); - DrawText(TextFormat("%d", decalModels[i].meshes[0].triangleCount), x2, yPos, 10, LIME); + if (i == 20) { + DrawText("...", x0, yPos, 10, LIME); + yPos += 15; + } + + if (i < 20) { + DrawText(TextFormat("Decal #%d", i+1), x0, yPos, 10, LIME); + DrawText(TextFormat("%d", decalModels[i].meshes[0].vertexCount), x1, yPos, 10, LIME); + DrawText(TextFormat("%d", decalModels[i].meshes[0].triangleCount), x2, yPos, 10, LIME); + yPos += 15; + } vertexCount += decalModels[i].meshes[0].vertexCount; triangleCount += decalModels[i].meshes[0].triangleCount; - yPos += 15; } DrawText("TOTAL", x0, yPos, 10, LIME); @@ -434,6 +243,23 @@ int main(void) DrawText("Hold RMB to move camera", 10, 430, 10, GRAY); DrawText("(c) Character model and texture from kenney.nl", screenWidth - 260, screenHeight - 20, 10, GRAY); + + Rectangle rect = (Rectangle){ 10, screenHeight - 100, 100, 60 }; + + if (Button(rect, showModel ? "Hide Model" : "Show Model")) { + showModel = !showModel; + } + + rect.x += rect.width + 10; + + if (Button(rect, "Clear Decals")) { + for (int i = 0; i < decalCount; i++) + { + UnloadModel(decalModels[i]); + } + decalCount = 0; + } + DrawFPS(10, 10); @@ -446,13 +272,14 @@ int main(void) UnloadModel(model); UnloadTexture(modelTexture); - // TODO: WARNING: This line crashes program on closing - //for (int i = 0; i < decalCount; i++) UnloadModel(decalModels[i]); + for (int i = 0; i < decalCount; i++) { + UnloadModel(decalModels[i]); + } UnloadTexture(decalTexture); - FreeMeshBuilder(&meshBuilders[0]); - FreeMeshBuilder(&meshBuilders[1]); + // Free the data for decal generation + FreeDecalMeshData(); CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- @@ -536,3 +363,245 @@ static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s) return position; } + +static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, float decalOffset) +{ + // We're going to use these to build up our decal meshes + // They'll resize automatically as we go, we'll free them at the end + static MeshBuilder meshBuilders[2] = { 0 }; + + // Ugly way of telling us to free the static MeshBuilder data + if (inputModel.meshCount == -1) { + FreeMeshBuilder(&meshBuilders[0]); + FreeMeshBuilder(&meshBuilders[1]); + return (Mesh){0}; + } + + // We're going to need the inverse matrix + Matrix invProj = MatrixInvert(projection); + + // Reset the mesh builders + meshBuilders[0].vertexCount = 0; + meshBuilders[1].vertexCount = 0; + + // We'll be flip-flopping between the two mesh builders + // Reading from one and writing to the other, then swapping + int mbIndex = 0; + + // First pass, just get any triangle inside the bounding box (for each mesh of the model) + for (int meshIndex = 0; meshIndex < inputModel.meshCount; meshIndex++) + { + Mesh mesh = inputModel.meshes[meshIndex]; + for (int tri = 0; tri < mesh.triangleCount; tri++) + { + Vector3 vertices[3] = { 0 }; + + // The way we calculate the vertices of the mesh triangle + // depend on whether the mesh vertices are indexed or not + if (mesh.indices == 0) + { + for (int v = 0; v < 3; v++) + { + vertices[v] = (Vector3){ + mesh.vertices[3*3*tri + 3*v + 0], + mesh.vertices[3*3*tri + 3*v + 1], + mesh.vertices[3*3*tri + 3*v + 2] + }; + } + } + else + { + for (int v = 0; v < 3; v++) + { + vertices[v] = (Vector3){ + mesh.vertices[ 3*mesh.indices[3*tri+0] + v], + mesh.vertices[ 3*mesh.indices[3*tri+1] + v], + mesh.vertices[ 3*mesh.indices[3*tri+2] + v] + }; + } + } + + // Transform all 3 vertices of the triangle + // and check if they are inside our decal box + int insideCount = 0; + for (int i = 0; i < 3; i++) + { + // To projection space + Vector3 v = Vector3Transform(vertices[i], projection); + + if ((fabsf(v.x) < decalSize) || (fabsf(v.y) <= decalSize) || (fabsf(v.z) <= decalSize)) insideCount++; + + // We need to keep the transformed vertex + vertices[i] = v; + } + + // If any of them are inside, we add the triangle - we'll clip it later + if (insideCount > 0) AddTriangleToMeshBuilder(&meshBuilders[mbIndex], vertices); + } + } + + // Clipping time! We need to clip against all 6 directions + Vector3 planes[6] = { + { 1, 0, 0 }, + { -1, 0, 0 }, + { 0, 1, 0 }, + { 0, -1, 0 }, + { 0, 0, 1 }, + { 0, 0, -1 } + }; + + for (int face = 0; face < 6; face++) + { + // Swap current model builder (so we read from the one we just wrote to) + mbIndex = 1 - mbIndex; + + MeshBuilder *inMesh = &meshBuilders[1 - mbIndex]; + MeshBuilder *outMesh = &meshBuilders[mbIndex]; + + // Reset write builder + outMesh->vertexCount = 0; + + float s = 0.5f*decalSize; + + for (int i = 0; i < inMesh->vertexCount; i += 3) + { + Vector3 nV1, nV2, nV3, nV4; + + float d1 = Vector3DotProduct(inMesh->vertices[ i + 0 ], planes[face] ) - s; + float d2 = Vector3DotProduct(inMesh->vertices[ i + 1 ], planes[face] ) - s; + float d3 = Vector3DotProduct(inMesh->vertices[ i + 2 ], planes[face] ) - s; + + int v1Out = (d1 > 0); + int v2Out = (d2 > 0); + int v3Out = (d3 > 0); + + // Calculate, how many vertices of the face lie outside of the clipping plane + int total = v1Out + v2Out + v3Out; + + switch (total) + { + case 0: + { + // The entire face lies inside of the plane, no clipping needed + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){inMesh->vertices[i], inMesh->vertices[i+1], inMesh->vertices[i+2]}); + } break; + case 1: + { + // One vertex lies outside of the plane, perform clipping + if (v1Out) + { + nV1 = inMesh->vertices[i + 1]; + nV2 = inMesh->vertices[i + 2]; + nV3 = ClipSegment(inMesh->vertices[i], nV1, planes[face], s); + nV4 = ClipSegment(inMesh->vertices[i], nV2, planes[face], s); + } + + if (v2Out) + { + nV1 = inMesh->vertices[i]; + nV2 = inMesh->vertices[i + 2]; + nV3 = ClipSegment(inMesh->vertices[i + 1], nV1, planes[face], s); + nV4 = ClipSegment(inMesh->vertices[i + 1], nV2, planes[face], s); + + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV3, nV2, nV1}); + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV2, nV3, nV4}); + break; + } + + if (v3Out) + { + nV1 = inMesh->vertices[i]; + nV2 = inMesh->vertices[i + 1]; + nV3 = ClipSegment(inMesh->vertices[i + 2], nV1, planes[face], s); + nV4 = ClipSegment(inMesh->vertices[i + 2], nV2, planes[face], s); + } + + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV4, nV3, nV2}); + } break; + case 2: + { + // Two vertices lies outside of the plane, perform clipping + if (!v1Out) + { + nV1 = inMesh->vertices[i]; + nV2 = ClipSegment(nV1, inMesh->vertices[i + 1], planes[face], s); + nV3 = ClipSegment(nV1, inMesh->vertices[i + 2], planes[face], s); + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); + } + + if (!v2Out) + { + nV1 = inMesh->vertices[i + 1]; + nV2 = ClipSegment(nV1, inMesh->vertices[i + 2], planes[face], s); + nV3 = ClipSegment(nV1, inMesh->vertices[i], planes[face], s); + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); + } + + if (!v3Out) + { + nV1 = inMesh->vertices[i + 2]; + nV2 = ClipSegment(nV1, inMesh->vertices[i], planes[face], s); + nV3 = ClipSegment(nV1, inMesh->vertices[i + 1], planes[face], s); + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); + } + } break; + case 3: // The entire face lies outside of the plane, so let's discard the corresponding vertices + default: break; + } + } + } + + // Now we just need to re-transform the vertices + MeshBuilder *theMesh = &meshBuilders[mbIndex]; + + // Allocate room for UVs + if (theMesh->vertexCount > 0) + { + theMesh->uvs = (Vector2 *)MemAlloc(sizeof(Vector2)*theMesh->vertexCount); + + for (int i = 0; i < theMesh->vertexCount; i++) + { + // Calculate the UVs based on the projected coords + // They are clipped to (-decalSize .. decalSize) and we want them (0..1) + theMesh->uvs[i].x = (theMesh->vertices[i].x/decalSize + 0.5f); + theMesh->uvs[i].y = (theMesh->vertices[i].y/decalSize + 0.5f); + + // Tiny nudge in the normal direction so it renders properly over the mesh + theMesh->vertices[i].z -= decalOffset; + + // From projection space to world space + theMesh->vertices[i] = Vector3Transform(theMesh->vertices[i], invProj); + } + + // Decal model data ready, create the mesh and return it + return BuildMesh(theMesh); + } + else + { + // Return a blank mesh as there's nothing to add + return (Mesh){ 0 }; + } +} + +static bool Button(Rectangle rec, char *label) +{ + Color bgColor = GRAY; + bool pressed = false; + if (CheckCollisionPointRec(GetMousePosition(), rec)) { + bgColor = LIGHTGRAY; + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { + pressed = true; + } + } + + DrawRectangleRec(rec, bgColor); + DrawRectangleLinesEx(rec, 2.0f, DARKGRAY); + + float fontSize = 10.0f; + float textWidth = MeasureText(label, fontSize); + + DrawText(label, (int)(rec.x + rec.width*0.5f - textWidth*0.5f), (int)(rec.y + rec.height*0.5f - fontSize*0.5f), fontSize, DARKGRAY); + + return pressed; +} \ No newline at end of file diff --git a/examples/models/models_decals.png b/examples/models/models_decals.png index 48e027fe2d1711237f457995d46ddcc0647a102e..6bdbaf17ca034c1e481e5afa69c9f954693dd79e 100644 GIT binary patch literal 67453 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 I7&=G%A8Fzq&Hw-a literal 62387 zcmZs@c{tSH8$Lb@GlRiMF@v!r)VnZ@ofs5Kp||fQXY~2~zW;pxsB13QH8baR&Uv2ae(w8z=C+lEDL=0mF9ZVNKVo+1 zBm@GNhCrYdCtVKR_5Ib1@(hhD`#eh zObMBpnNr8*XJ^AM+1uL_8d)qBf!D;?L5*un97Zo$T^K**waNAC_@zsiXypl_nff6O z363s$sS+l}6eStLXvcZPrSqLFY)O77?adn{+WYtKTN1GRVTO1Zy-0k7G`y%`hHf9B z)s=VkO6%us@H?`)`lU7U@B)q>(0~1r>JACwDv61SUdrV#eh~+HwV>IK&(v^9LU#K7 zmPD|OTu~Uq0@-lL>bbFk-90^~r^?+?=v55KQ#8Ba-o2mQ zwUXuEE`@z$8D2h5yV=cx@tR~KcdkzGczBD5$kQde-H|T&-dKKUt1-l;#wq`%L24p+ z8nXAIlbR5Lq9dj2Hzkq2fj!R0(%|y)xsdR4;DD_5=4@jIrGon+(#8AM5BEA1F<7n) z!8*k{wpuvYsDCoP&*5%_wzVlWh_gBh2^G^65+i@_o+Ru*_7R95Dn@Bq>^MGKhGM{F z0^altV}%0WbgpazhNNjENK8h=JiFV1VM@ZXKS*Wob5i4ymLYUOWr%W|H#Qo{YIB*m z>6)||+(0pPq@-0af%(GW2ZNV)or#lA6Ox3{pPu8qu@meK7Hgs_t#*_02R*>53-mP`GEXG7vCVSjbjf-Fm`7~>L@nWc&KiJmFD5g8o8IlMA z`&G*`_S{cK%10w0;D`GWbt@S{QRQW|$pJnnyY*E~*_PuO)h?cLv z!IgL#WH%RB0+pa1;~6K__ymrJWt-=jv`J@SNWDwYll9fr)eJHVfd~x^rS+A`h_@Q1 zW6nDu58q31QX_%$1xGtJQR9vPN7RnNPEye2J5`FbL)66Ezz1_SZ@;54q;wwITHsj4 zi#T=a6z%3{lZuv(j%vxRd4z|AKaH#K*p+-?)zmL0H&8K>#k#4R;A8XQs(Ee9v61DL z0~SY7%(zrqkE_lOW!@6MB>}BkEDncDs*J#T4_DzV`a&vZrnf?AqEh31eez9YZP$x}_uFmXV|*z0JePM8`)h~UIX zCe|;pcD?Es&=eyF@7udvb*%31B+qF6aK`kHI1|p@ck$<$2(k^D2_Klc>xZ`N3*quJ zGYR;;vN@^@pSyjIK7-=nA=iHWYQ!UEVPQcNRidUgGhcGLT@bh@A)~a9G^UT=FdSFg-Z-!Uh3EddeA@q*cLw=;0!xA@g z%6H#8{Wn4t{%)kaLFOC2y;ZZF_;+mX&4A5;c%^q0_=1LG$I#G`3+VOk=u+N6SP9w_ zQG#x!q{j;vWDwFmuSJmez^z7y78Kfr-;$owk7w%R-53-x`>d=iG*Uo<@(TJ$<)p4m z>-+cUYzOB^$ZI!)8TB;NHnGbn>8M@%(kU&3XUC6FTV4!t5Vz)5_q~3bY9t^ZrB{2$Qc&ABrd{oha-RFH zYFX_NvnfhKmvH6NjCLHqHBGue;i#FE(o)P~&~4>ACe!*jy{g5r9s64cE36NTWkK6# z9mq}&kQU}6AzGgS4!gPG#7-1;!wmj%>c@>dX!&BLnVzWkP=21*8x3x1})%ZZRz(q&hfB5h}*{(C)m zIj6eW9T`F1iKbz&Jrb7o2V#`oJ<H4(|i=Te%?MeIXQ{e*VlLE4hjxF2p1Ix0sy^o5FjoClcmdz4Y4d7d?ec@sFL8>;;fW&{yBVEucIzfjoEHA@K0VW^c7}p|z zyfuEW-HWAJtkOCZHT&ns+e3=Z@*3fEur@&MUMHQBPRG?R9aDPH~%2Syk0Ua%;|!)AK_h2V%e0*Pmz* zZwfSeDe~5t;p63udjg~P`uKiJ7#_=wa{5#A*t)j#{iy%wEjBnlg z0j|1^oF#kiKSV`(eCXDeO1J-V_(1)V6nd0W|5iVEt!ZYUzJUB7ic~XczqZI%Wl4YB z_@Z+Kg3Cxs;@c1!b!4+@q@9vRm?Zyz_i7EF{CpE?RCe;B;_-S44yY|rIQ^(*h zR&x&$iK)Ag*HGx{Uv}9a@C)Y3daAr=^nr-G6`{=gjFD>wUuV`Qk+f?Dq5MA&#D;Xg zdG<{HM9pNPu(XhnQ2#G$x>EpoHR_6j^Mc62y%T)l$@W4zE0^;R5>!F9bwNMSmCnYw zfqHxr#h#zK-*PGsL2ywKo}v(-@x!HS6Wx$aYgXb5M?zqCH(L&J$N5Ia2Kh-Jl=%2)R=O; z5~=-Z$TvmF?yIz{tnobKx{r@b2ZIbZCXpcBZw55BFV#%5Uxrl*A29yK4Y{9tOxUJFNFO$c3IR-tK`IWRyeJx($<;9^|W$yC20LjC+%yGPIM z>eWU@;F2f8%8ko)d@LiOrCa^95kzB6Ma(H%h9Cy3jD*zTF_-a%8XdCk-t%mCkte<= zy7If$)>b3!tL7(9?x;Gt&{mwGAN&=fTgumd3A&G#-pl~P}d6Rg435>!6)Y90N%=pg0w7AVh~gAs2Cb#R))ZSZq1)kY&rH|q)F3d zhs(D%&1qbmeNm&=c&{)m)Hq0<%ez}?6;x62rqSk1gT3!pe->OTPsd>RZ?dgQ6}Z}= zbq5aQ+$tO_a-*;};0s?e9T=aZTBD&oYbVa0rIFD^2IL@*{Rc=2^5vEHI%{ch87~i= zKPDvnWqA;xy>2?aKrhh}PluAR+~7{6W3b1-f_@cSB5npGi3mBPZmK$=uB*8e25jwP zb=nBxB)|CU?zv`UIvs=+7bl{Vn~;0?QG9&FG=tEL@~>a-4Ak9M=y!&u^4#0lNHGLx z$ZVI*LJ2qCCOgH-Ay&hq&eCGIDTJ=+YN$k-j*u?qwPE?^PsuBCv2}e5TMvFJ#PMJG zy^Y`0&cxCn+%kmI%DfVGy@$`gcY{rFx13CH235+n&}&qP_2{u+)M*8-jY2yf9v%ey zdNK7ip?`Ro%g@Ne!vpKtTf*bp>*Rop7?_Wj`N5NQuA@Ti{Fqv8h*5TN;c!cjg*%tj z`Gap-f`X=S|2^1|JFil-m<;rAY_TP6XxwgY$8NM+9=YBlsY^zGLzV<}xP!Y;RH|WS{x#nW|szdI_SiYX81 zZ{(1x^iC|-tjceRr}lV9oCxB!tduhPPa36i+-Hd=Gfs~6e1FY8UJds911F&N2+a2r zN#x~)NnKXD!duF7*HT1NTnX7qZz9f@VHSQ<}p0I(E%FEKSL3^&o zjSdYu6HeYoI$1po-Q1)OKxa^We$2SqpYa#58KoPwGZKZie+=Nm_CoR|@`$q{uC%_; zO{M~d2q)H=xqVjVeN9Jg~{cVO5~MM zuI=U{b7I~2m|jBBA@_V$1x2n}J4sEkrgomN7AJqxQ?2eO63CKmDuwr9dqbG~E6thZ z0^B`ff{s>Bi^Eezhj%tp62t@E8U!Jx#i5Elt8*I)a8%!|)jOB{V*87J1xYK3<+$?R zjgNQh_;gjg+wA<=5yN7Y;I*N1*}oPTnvfQHB4mtO>zs*>n`r`Z06%u*bPuJX0R_a`}rQTj@V%wwu!I*MM z%?iBKih|^}&91N9oRM?2%+z;DNKSSo5Q$D!=gzrd?JldQE|O)K*|`EP;TjQJ&#y4M z1LhJ3z7M6G{vd2Kn*HQSxD1hav%_RL|56(>9rFG=PEQQ4zp&&mu{;TAc;AIb|tBiS;O9q<;4p^A@*BH_+lyy}h z;?X0W?7tm4QfJPbL4*BZ>5xnOXT!;ZmA4%X!YwMq>;lv;2(UpLx)c)g>{Sbf?)fi% z+7rNCd2St+d_Gp`t=RF7c0OpAuaSRP!ShqU&w`lv^htx(-ri2m#3Wq1p3*N7w5AWT z$3jcDC0GjwV4MKU+W5Dumom72%Tfw9rue2~a4LofPXS-rTZ53qUvdD3vSUc6Pq#BH z_dIy;0KK(koLnEPRO;x!^nEAxQF&h0+fe$wo{o5ubW~U4D7^{!`GZv9AY5Mm^tCXl zm)v|=%cR90?%iztWK)2TCJSZ$w+Z`S8&OP;#-Xw=webu%J2^30&bV~=jQm%ka{Prs zY;0^)u;b^zB_&px_D$xdM1%R;v0y%(0M_jc%{weH|62Zo4>zR!$X6f&krBK_-U+sT zyK9g)YMgRuRaQ#Yj9>f-pMHU_{B8{);2bRKe?RAWa!f4-O4rYCNdaU zPTc0;$@pe5YX$gsdbZnWln4*FM6)vk#s~w0vX%vPF&Nn4U)wt@|HANpJ3P`a03FT_ zc=6##PvhywhNtWk2Nv5>(3bY4ZvJzt2U(eAp|^Pv9_a}b3SmSp~R@1qS!&^U`YgS$@HY@W#+__o%JR_+aU_MaahvYPusu_5cLjWhV@(Dn6L z2ji8k%`GS9cb+3hj*$K5NBE6z+)%AEEWXyKk5Eu}ZYU}TAg81CCl6eQ9lfc>LI3Y% zMo+lKQMgBr=6472=6$70@N>_v<>yO11kKt{75|gHphoBK#Ii9EA03ha#BK-biJ^jZ zYzBz0y-0IZP||nT{?LF2heoY>WysDB&KS{QC(OVo)K@=*xW2ZOIWS!oNSr@R^~^ENhvAR6tCA8%R;Q= z%-r1E3Ik2zyX6!wn_UgM*X2(GM4PU%u@6o}-PORve69rp=$~#535Gk4hwu0o5xrKZ zuxZ#ybcymcB4K0YaM;bd=>!5%MaApg2Vob@9ND=PL#Ym&%ISM9X0|2xH*d?zGNGW1 z^rVC}bLc{S|R9?Hdt>$Bub0pD^xzW5x%UGkt_YIbIAq|l<~3DH^7*jj zbAZcL$$j;T7-KJ@mAZ%sL@`3Pm@|>4NdwE@iqUSK`VPzM8w&jCXfZycnxylhPEVT? z$gc!7otrZb`Y-taqAh&>*XG7rdAeYN$<3QLXIyRf@81uoyih^2Bt_do4CvOi9lXcC zxpx}A!eBYo<0?qx!5{*7zPlgIJ^~s}-x&G40Sqiu%GPE1g<~~iUd>smcB<_MwlZ=V zo4+du{CkS!@-K9sIDS$-_gyocq9gM=aW^{h)na&0z{;Ep0*}XY_%H`F%*@P;K$-eM zy6E{dH@DVnZSg8?aL^IKR^4l58fiZpxVmtOmYBGs&?o!3eQdH~J{o#M87~q0V0)Wd z|E=my)&-EsbxefYtT zS7yyB4fQ)>@BaPwhdnekHEB6HdpNi&HBhwh5LRCp{ywj}^Wnhqiny`-qegAt;1PTL)&aN#p_>hDYr9{Ikw|f3zu4&asSlNsDJ6w>>o{<)43@^ zp0j-=bP zUH|OTn5vHUo9ZUJU%BE0nd}SL+C0d5a`-N`>*r70m*6!MqB6xZbCbh)1hG%5np9^! zcyHonK(h#no!Y>@E&$8>@-O1AABuwHwO#++zR&+TtD(We+jzxYOl(nxS>9-zBsG07 zxRJ3@xr?@+`OAW;Vns6jdpVkqsOEp3uBEK5{(wBv@W?t{V#kgO$nTtnhbmMuz)$GZ z)KoOREuUmMA3kR=7IEP6+AeRuy4j(cf$BGPD2birb&_8^yj$*o5Em8&&g0Y5at4WUKl>wjyS z(N_f0oC=pF_^DBkGyv3%{NPs2wviz`ULu4 zny9ipq5a1Xr$S9-<#_V1uMg0jot^#1$3A`fgicRSN58c%b%gkc{OymZNE8-wua<4v#ohCJPG$eZL_Kob5bNml(mZMWE(O=I z!Z@Acq!lnE!&2BiO@sQ#C+O}3VjH^FMLC-P51sOrjFIY_;4zc5L2{Q49BQss%nOU^ zW>Y)k8|~0k2qoFR<;Ehs@Av{boAO46qRbyxExaK-1i(XtD1-x@uTX$!g@*;%rlB*x z1iLM-j>FHFFL0!jn@kHzNd$qK^-{u)x?1~G7ws07qVQ#H{QZI+I)a;xa- z5(^U7c|QWUQ|c0hq9pRMpukPbs~-Z-ZP5!{^q9fR(5ofJ4>g{mn@HS+zl>vPaF z4{NQtGV>(&?}(X=CYmlsU;1tkcSim!SieIhmHj`Tz~jc z0FD%&y4-4VDq~=|(G-0#bo0q2-wMq@*!zc8NbYtr*NbAOf@J8Y67!%};@yoM$&al9 zt6?@#uJn583(X;v^{Dzjs)`;hFrA{@U^+zId}q)Fh%If1&2uLpT(S0Ui*XP;eSyh{ zy+;hidMEPo(5+8kXsNOwH~+P9zB(C1>fu|+33PPZ`1^_!X6cU1vJD5w)jJAR??5k! zrsMGp3zRz#W^Ak{cx_Q-?Dua~XIEF%=(~5R(_Ecpp$2gGNj*NsDfQr0@0qFyX_bUv zy6}gPjnyiKXO9jIAn+24vB^p9<{akE0o~c(zt$|h&0ik?8dU&qqW`3mlM9(}>H)bU z^j9xsWcRQDulpjG3*whP9PX~I$l^vI9vGMJo#IM&N9}+#rZV?9d{D@DPvC#%^LHV2 zgZU}njiF@fgoQ%RiAmUnAKt2!0>a}wTC=F~4tXcYgyS-h$q0&t-JP2S&i;!dvQ!a< z$|}EBEC25Z%HnB?D~|NI;kL?p&07O>-zB}HaT5Y;dmfKK{pjoa0~EM&YU=84b6{RS zE}l-8M(o?SkFW{XtnaZ`^Q={%FhT79HnN@9@_GE{O5l- zK{#N(y~z=1<46X@F1%(=~r71g_O~ExHMJ$-`7qkgzMibHu4V1buSE|yf^`&Vk zy9#y>^fqxuCnL;wbuB0k`=I|G2>1PkNRk&#Z>d-M(=XSqZ5 zK;O;80hO6&*V!i(6SUl=Zo?R1U->35m>aX>YnC|YFuYhtW8d(q6}qq!pYJ`!K^5@K+EaI7%2J}`a zOp&|KU@m3AfI+OTg_m?n2BqSxB!)8#%CH_EcVbN(kr7s_IX`+7Do2FbjT_m41}0bH zK-@`eR~4gu*8xk{;s}f$2m)91SOmpWZ|ObTNy~d&g!Q^c88-~92&9zn+F~z-OQJFD zD|>A}#zVc4h&N8|M`la2@9D6F9Zbijhz(CkbUCt7P&fLe>LB|4lW=r*+63Bm!UHWa z0g`T8Cdr=Lhwh-3OLTnmqLP6q%)2sbSNnb+Tcz05)fF8jDCO$l=4N_574$8vSaD6} z14%}uaeCp3iY<|J$ zk^yb&Vu&hXj_Uad*%$+{tNmInEms*e7|K>0%f7WM6lFC(C7f`)V@lu959%&_P?6Jr z&2RT#gWLL7?Wuas9iPw>gHaW@^kJGAWy>>>hDMaAu+01TS1i(+o};ZI^M-O?up?%0 z$WhvR3z-KTAZkN1at!;Ux-#_R^L4!%Xu7VMQtzz>h)#t_J4JN(mn(yP($VVvF1P*> z2e*DMUmX&Dhf&snispK&{mOOyFtu0SlTgOo+R`*tGmEMtLw_XbOYXOw|X`St-<9K7#)Z+m8uP6_g)R{KLwt|1VBMHSYKTU%%=;b_4RgM6U1?0o&HY4ZSF%Ea%s+Dj*ef zT&w}}ARnEk=YAHE-_EPnWqEn8&C6O-N1{Z~h0Nb8w!>oXy%b<7!L5t6%5J2MRbV%7O@PoMEu zaFQ!&B^YU+TcYlLiSOR2z}-y*R!002CMz2ASWw9p1%h^g6vLU=0cdFyzUN_{aqFHl2x7KrH7K58b`0;Mx?&f zTK&4h->VB<;<=YDw9r)Z)I0@Cs!v@960%9y@QY5KELSt9KM_euNd$^T*~{0jBW*tV zPF_H;UA-Sda)*rKbb&N7o@likX5PrSz|~D@f>g{jA%8!UzIyl)?Ud$Jyz8hTmSgP# z1_ZN52cfr+D>tgcbV{$Nkz;90FTU-SR*iACx&jmj+F2CE^&sQ(^6xfH^nKrS*S&KD zQ#0Aj?-AKOCBq$ucMdI1_0&u6+s9Mg*w~0>n5;WYM^JRkYVj|Q+?Nm2mHFV->{u3-jxG&1?6%T(V_8Ju8)!BJ%`)wBI97yC0A7NO?g9 zEmgSH9`6s@9OAj>!sTNA#{~{GQx&cTfanZfm9l_ zqzkC<@Ue&X?CT{2-F@Rhlm-VXKG>YK$2s@Es2|=Yt|kb7Z`CJ@2X5UHkh3~@7RQK) z>&;bYC)1x9llirW5%`7>kEti7(qXO!J9g~Ib=|e^xnIvan|er5nh<~@JqJSPM~*XW zNH^%E3eo~&i~h6|q8SW>>UFQLz!SztTi_m=iTjHVW!{ z*0n^SCumc{_}P6S|7$4y$mi1sT}V?w$I^9n{TZ1A&9e%iz~~6Rj~3NWT+FCCX7t#3 zM&r0!b4H|%>D;rM3l`#xPRoWPc1f$vM&`IOA$qB_|7kC%<*v2Gp;7uVHfVMdiT|O+ zd!p1w#yVHZMaCGNf+{L1MO8e#ZN-%R3#7jWEbi!=02oV4zG4uXv+y(nE;yGd%O@MKoQU-Qv6nufh!s zV&V(2mk1s(rPue9AGly+Ah(i4KU2f5E~JPMMLKlU^U=@C{U?<2h+$v!_`IJtjg9zs zjfe$}3)*>ln?x+ibpUJ99kSm4usaioGx~P>X_hJR>=ww3hx!M%I8H#4}W zv{$y~mEp2d$O=!ZhwzPx+GyPJDpU53kLoMLRY~cmaMIl-*c$6@g2qBhm1*BS2}v0Gd9%H3xxSg?Ux78+-1 z%2X}g1f7@Wm$T7*l9&Kwy;q5MLVK|#i5z$Jrk+{Q!jv%W1cAx96EZ{?$BsGD0}A>JY^h9b|sQt4?UYz&!R%DV{FC_g+Hhu|1U7;buS@AWh=r zPioXqCrtG(U@N8e`DG@gemDJd3oztEpn`10BM8^Q^NeGU?*pK_1Te2fhqY21!h$6A zuO94$SP6+byYu^!PQdl?ziZ_f*!^Bf8vx%sBL@LM!~rW!|0<8KQFQ*eF5`OWpDG&~ zt3Z-y8?#Z8&gy{++=P1ky!GCe!17=05SD^XY!5+$HlF;Ov19j z_yXaM7wgw%*~(W)k$2(=>>k>M;G-+@s?p--2{GYez;yeRN)L1c`hZDxSS^NRZ~>dc zzq0VArP$Ks{tiVzVts;H&NJmXA--$IwgJ%(g6&gV{1bBd77sm!rz8N@d_VSi6 znIr&m6INFP7}_$#A4IDzo|Uy}DgWyxlWMx4}rK1^Ahv;tpgEpKVPk8|Jl93132O)*cHf}|)6@Zz+|`h`1Jzr^EpeF=cbx01*`fBrmR!eVel z5_}e&)Gah8RY+XlD3C%U-x@eWCNIiVUpJt9ko(ir?d*V@I{xnLgYs8TF_*@1W-{&< zqw_EZn@^pzj;%M7it7S=53Ct5&LeeJcqjDEvT6Pe0ES5r88VOy0qPPfrB>yODV4f$oLQar^JzK6Xv2zBZR zm=&PDe+X%6;+mU4HrNge@O-^w&vjrE+P!mYT53{9+Ee~Qp&c_OzmHpIe?@KBXL2y` zKY1mAE=g)39?7}J$-Y_2)3DbE!aj@nY7dQ#+p)@&Bqu45A^-moHK4th-p%FT(TSx$ zXE^32508@BNju1-K5v1dNNpvhxZeZ7;% zsicM>G`L6~q@+d1{``qIsgmXmYXP{bPUsverbD2XgHnzGe-O~gEKs@TyiNzs4LUlJ z8CJ=T{X@=f);@E*-PY`u&pkaoYJ6i?e|}QIiixS}-J0X-=7IJcviNGx$3Akd;NlmB z`rxuK(OEmh6t4U`)G%X%0T3>vv0dU|0Pt`AS)1x9J~3gOxeI#a$yyjrtGKef`sjTx zuF0Qdr*gYpsyCR!n?yp4WT&b-A3ul-0L-(_J&W%RR=x3OKb?Bjg|6&bRErg+wA*i<3VuN%nJENQ|hc+cL8I zSlkU$l#oW*+Fa~FU+CP0Z(Cuj86|7S*vU;z77Vc~SFRv1i0i;wR86?rrA7nN+>^1I zkB9iYO`{-!3dbu>o_r|PW;^`L{up~hlAz+-@aSR7uXAvlH_W&mJg^Id{8}69Q9!dq zVxl=ayB}oa?VWv!=|?EB?tJ*}0`Vbz*;-{uSJzZY%Q5S{W@q^j!-$1QKG89e5#@_t zoH4t0asM8~S%TFWpCiea|O}WS))B;Q0fGGM% zC=Q#P-JX5unmyy1IP0J z(&d$8BU}FVm%}+iC}UrxuMtU<_x%Uht1zju+V^;y8tz*wX8`qLf74LhV*h{tVM)ua zPtDOl5ad$+Ve~zs`$gL?S7dVs>ZX;GwumhC^RwO5#GJj)#0^CiVewrq!!E|P8{yuq z>}dyyV;`hClnP}3F5Wcaf&P$-AOtTf?LJg?K3&X{+87dj`?d;N19DfegFAL?ilxv* z;N2?#UFZ}Bd?I@6(o9^>1w?{Yk;^E0gc|7}K5VL4bIQO_;MKn?zvBug4YvB=;RvVo0Y@N zhw67^T>DDDY3evdv24&}av5FSDY#&AFiQZgZjQF5ihq_pbY%@W|E{NXRDTFzwDAURIIMA8dHIbU}dd?+7or+m7KdzZ5-ahC6rHX^WSv zk2xpY1QXm0Zrr$0D-cy%Ylgr8u2;QKR`uq{u{>A1NZ&(Pidr7Af?F@^j&RNG^J&aq zH^cR8(E(po(XL{5&|{*eMhC*CZj(*Yy#H>s2{LxdDqKc7@15LWs_PoP)cRr;sa{Bq z)xO}T?*K+)p7sGRlP(wV^JfL8ioFLeJka2AH*I&VSB)uN3S6Jd6=h{vgwV}(X#7qa z8yip}d99IpztPC6_Qf8Wcab_>4K z4ZmtSJt%g`R4N_Gjrk%Jcw=a%jayXWf=ZGSKIc=Psd?y}5#_=xAz+E;*FG;Z9})+6 ziD++^c&Z#1dnuS=ku7$>lTT344Ptk? z*FhsMWm&&!2?>c@$(=enI%sKI8yh~2KoEWX0zB^3NN*UnC{i@*g9ttDNX$DOYFwfk zH3DNG=|`RpTHk`5U#zOF(H|yC?Tb~utoiUoPZsVDh#;MSY&o3`BZ^4+Wsf&fU?jjc zP2l#wAlQAG69f(pMv%1wsDM$Yt<4RpQaCRkAC4rx*HFB~Fpl+&X8_jwcEBySdIq$w zGj;Zr{t>Z{9+mf5$A;bC6}Stg8<3J5HaId;4XFS%%WZjP0Pa7I%O&2J8$N=*l9EaATK9SiZv&> zo1Akx#0dxVZaf;8dbQ;5W<W&c-xL|pb;lTr zP?N8O>Bvf>f9mnx#Ecw{E!35oDU}M(^-DmHWc#(aDb4uocDPZv(4Wd({_O?iQPcs` zyG_Ugl2L=q>f43Zt3}>guwG6s$?=juDKgdZ(K!Mcs z(kNpiIyTk?RAcdldx#>HgZlcCQx+2+AZX_KgturLrCzd@_q-5%EKb-veiowndJXPo zuJ<bWSF!jx$LBCNq#{9#Y~I5Z`^!s8Hc^lN+J)v0+gES!r42-*fv}tBCsP7HYXoH6q(_%aEm<74B^!a4+l`#|HuI;`T z>@<-JMM8)~2sX2t1P2m$sO~~2uUHP{S?d>zj3*YBk0?2AyW`fY5ZJF44cj=Tqjv;5w0j%da z(*wp11Ym@Cqt1F>Z~9J)|NTzBcK6Kd!#h|&yTHjGPpwQyNzvT1dxy7Q9*nBwJIn`_9ah;`(9!-8i-VpawsvSg*Cu+X0!c^C}6bR6Hv zimNQ1tht6j900Z&C2~b&0>qoe(x!Xe{Yv!L`2dD!^Yf_D@e+7UaxK{?wegflr9sqgb}O5(S|5 z%`h+&_A0Et3b=COD8 zYg3OQeNYNqnsSkLS%jy+4bA71sDmcNzmP~^+2P1%YU|rm8#D)$!{dYGC(jH>BHbmo z?hmxTIEAKX3j$IS*U->_ZU6QSXQKHae&i7c?1W`uVDxN%!`R~$o*T{mPx!SxMe*E zfEp#nF{hPmuLq3!(B;e-jg7$kueD)gZ{O5EKCZo&H3WQmXu1Hb;X2)dTO}RitRA5x z$z@0DXlXG#w=?wv{}K}VIffY`tK%tE5%)eFT5_bjygY1x83y;NLLd}pw;eU#r3(&s3bMy9 zj!8lqeZ9QA2n&mgs%cu+!?Q|D)2BkX8oyZ8w+`el!GG*k{&a$TA*LbQ@3L2{33X7Q zeFgg!gH@@>vr>Y!Ff9gY@3(NI*~9wBKw9EJ6AVX*b$g&D3c)x zlRpcHxi4R;a$N7ghJGLFZHYL@l4#Gpd;7NGyTmkI5(z<3660#2$I&7qx&7;Qxu04C z#gK`YV2|7!{^haj5JI8NTF_Ii{|gcS0WY99oF~@tWapW$t@=**=6?|>*_`{Vpyp2pJg}#s~5TiErP&_5X6C3+IxSo`6B3wu9$S+?-$Wsu!js zu&VOI1}EFY8;pA4;SKWY5@fJR3KwIOzXGHfWK7}qc0-PNC$<3JQr-5TI&dFa8B^MW zr3pQZ4Qb$Hb%abtKGgB~&zjeJ)Fd?2fTIZlRU?HoX!FT~Tw7b~(Esb#(@QhdBl|R4T6>vICJ>`^g3eEy0Ns24<0ivEb5cQuC8x=4 zctk`?m$eZ2DJD&Z1IJC1nue4CyI{f@vmK1NUz$9Q`d0Ae82uPH$uWgEb6%WtCx<40gq2MPJ(8uom&#o#`1FkTb z>jYE$sjb^J7)&o&q9}1gPt_=-$EAj@6tUlo#-E^ixl?t<9$sKij?ri|G~MMq5JKo} z*~g9@tNrBL`Q8P6`0!z`jU;b}D-}<=O)Xf0#OpD`V&c{juN)UuLNPIiDO_P9kQ3Eps8`5;)Y!M~fS|*UzfMKtJ zaJ_X8V%M%+wC9$YsuDWh(K0$ZsnB-qdnqZ%;@`mSw&$~e+FC+prsabsByfZUOWNAn z?yaDMp;q1FB)s6cbLS9jxw{McAwQa$1SXzwXsX8#z)DQ+4uBZojIVH9Jpaon?kLJ) zm{k#)&w1xWq7gUTGOH6vk41#MVAmnRt}W;gRinN65ix3DqVt3IaRW{nqeQC6iNrUB5YVXq z-=H$cVV#}TTDA=xAPCZpAh%8hfbme)t8;Ie^$$q(Z%F2ENPZDu%#msb*U>h&vP#H$ z`!*ixTfnXF7Ou0OE@wwjZ||6g7Pa7ACEY)0kA1r7tVx+lfcZQjJr6iLm)%&+`KgP;GawF1Fb=rbh5x$`>oSlo3V3b4*za_wWBh)tM0l(_-%$vA3m zjw5{#EH5ud)4?Ro{V$~YGI@(Mvw}Sz!OShg4tP4SScY9LqWE;e1)=aiJB%K#}Ww{cJAA*69m;c|<=|n*$o_QyHq?_nY zoHTey1lA&)Yg1ZX|JYb9B$em(-Mda>6BEt}NlBGn@ekn`bsYXe|fbLxPM zjlcyH#N$V|W(T>KwR#OgH`zWbyK>=NH-y0)1+pqHZy~K#+ zMTN!^g&0d{la{-rZ7eaBLfI-YV;CxXMH{AQ*PWu0q_Jel(ufLKBPQ9&l4ag=xO<-G zeUG;f9Y>$K=DPmNdH%N3SGtelRNtP>RzAg&Yzp;%1r0E#DIStqT=!vb2T>B{;+5_4 zN=bIe&o+p?QMZ~^e%C$FP+nN%yA6c6A}n%@HR7dIgi8*Yv+XC;^hBD;-Q#pR0}h~D z$Qz{TkuDsX|KLzUqfqTlz3-7~asCD~Qe=lWyxDBC_0+xx0S$V8`t41cEBBJ$fD{}n z^+Fih0qo{gpoLQYJ$ke#T!c2GojTW1|H6$pXg`$nf9ejI`f0zQAT`~vg_BU2vH_;W z%X5EgHY%R$Sca-GDmXurT-4s4?%Gur>zl%GOh&cl_zibhu|Yz|=4Df$OXR0XyMQW7 zlA1P$s41+d{BS3NcW>KGBIJ2g0rV5XE-kfXkB**N>;~jrs|vK!=cYb_@Y7=1C_-Oj zIGe#WzHEJzd^_k^pBk1z_}V1p?Hc)H7j9?NmXKW9y-++SYV9d6Qq#*il1%gcLgB}j z{CoE=vu50wq0+t-!Q-x-0?wlh;j$BBdQV}66x(Y++l%qgI+CL%D5_OmsUVf~iFbEn2JtQ0 zD9O`uIDl`LLaTc^PCSS;34Syqe31GDG!ib8Xgrc%T0}cNH&4$rY%Y7*7iqEI$ zO@hq0zNBW#id-CKDUb73jXc4B(wnhScs;gA(=dll{SGri{#TYk-zqK1eDiCmzSD6v zeEx}wpp!zyH}V^w@?b16Elu+4=JD!sfdDjC0yi4s6CoxBG6Av(F+J7+9|U!skEjRO-~KKnTHuttPzi@>GOZ3<7K^06dufJ+x%4g(%aH&fmXqC4x2S zem73{j2vedr}lD3lLkEj~ydjzfPPtG_XqE!}K%(9~%uZBA9oj zA=)a75Z?t+{&r3}Xx)9o_~b=~-mcQSq}&LCkr~Rg@{)^a zKXiWtXvE~JLt_Fhh*)VT5r7;OUI0jVgTh0hSokJkjU`B<8sIJvjA>yZrp2#0rbU#w zaV^+-1Ao9^_+s5y1rf&6$h>F6skdOfm5s{Ibo{N1!B6USI zYGQ@(Pi^ybZb{~i`$wN2-1f~EJ@Ate z=rGSp{%3Bkipwad77NZ_g6`kF+E&VEWJrkK z1{*byL$(a2Raw+QdeY99Jqw|ZfEJb*?S zt}ch_#34=Sf+B>J!VSd%BNiBXm*y;Nyb2*>P1+{)cNeux!ctU=>`p=m+mVp|Z#t%y!ZR4>7?MiXg;Pzgvnc$+MB?i6*v9?}(3)`z z*($#;4XDZrM5>X&s>oUji{P%-vQ{8uTFH87g}8;BOE?e;nQ64bcceuAum)-gMClX>~N zOhL;pH92v#A%9TPv&U7*EBjlGtI>m9))&3(Qx3XH>F%@-tGvDqx%PX4nK{|m8*+&f z7O*GSg8-)sD^npZ!B!uhHXPhC2x@lwu3KX2h;)(Heo2oyMVo!+f0S=$m!zCDESMR; z+WG$GBw?sYx|E~3#SL{A){peKbpE05z>~;(~1Q*D^;F3j|`?k7*S+&Ak?>iYh@ zm~#2@<7=x(A=FJDQ!)C(OMP)S7l!AJsZ( zWCb;%U5blD+Tv{lfio0@`fzupT=tXWU;h=UH}tILTwHBnXy_Mc;mn-egU92gb92LpKeAdU?>&N_CC^bpX`pgOB|8H=Z;x@+@QVC?ZW1(h*-$= zcm{@273+>)RlNkzI)Gid%cKN|SJFIc$bV||hxk7yumKjn2Yc+ids(U%kgw%fk8RL> z*SMxy)!aErPIgjaBI(M}CFbJR?`!yw83BGaz3AWlzv9zLfw%IDhZTa&pC;Z=Mz0>L z;77#hy)I6(=KX(Y5yk-WVHlB%FbxdK46+!mSr)4&awp8sLQ#$NSWIODJ1BP=gr`Pg zNT>T<@1M02*3_zaYPX#yovec8P~^>>_ z#o-m+mBgFilvr8Uq)R~j?r`ztQ*P|9`^*`;Qp_~5lo{-N#Cte)IE*+&P$p7cC0za( zyZB1fE;@3LEHGWti?I>@Q0DK(Bc02;>aTXu>Q~&#tMMJv^|#XyeW zl6$+l+@~h`N#^`_7QtL@^@ma@sH0_7T6SjYPNGbrnSNE&yVqA}*+{U@K{AZc$d>6b zIuAxe6Sa4QbVtiuD7vtEDjhv1>t91h)eo?tO@!NrX!3 zx;mrnCxSHAS0%;%!}{8}qS#akq7eA>mN3q8&D~-nonNNcotw}pxw3K>F*=p>y`zJM z2-wruB%|e|9%N3HxD73DMuhPL!wTp-738RHVPW$zt|!ll`&PF2VFNl6Ur{zbFK8ir zZ&4Q(jvVE4hR?isLbPbNu({fJb%~sZgYPT1Yj1y3HucP50s|ke%_*ZKbXX2| zKTU6E;uv$d!&B=_e-%+QA?*++G+05QMK)Nt@HQQRyAF@^PL;eip%LFk(Rn*dJgN9# z4C#W!G$R~q_DLm(Oqr?yPGMu<_{zJnU79zBl%37>(YUB=s-{_)i85EczPY?}71%92rkV}CEzexSbGe$FE>Uu0nra#qijW zW%edu&|Mj+gBX{y8!sWSx@N_mdi*pgUeCnsr4R2rrn=>Y2`qNhPK=M+?!bMzdG+dh zM01RzV>#M`_yCPIeG&D^cSi6>1+B40t2>bo_;Z!no~-^ZMmH_sO- zIo>h)$cOt@wUO}SM>oya$jPgtmt;NplxlL-a8`gz8$bkE%-alZm_GAEwB+!|_KY9( z(s-m(ue*?`e7Las6ChRqkeqQ?{lV4@r*d@WlGkI%zF>wa8NK9zO~7;wz$4LO1`xXd zDH4`>F3!DZP2c(1GtXHn4bx{Jh~eVAe*g(9C+eD1Ju4VlT@u!@K{W=97csNu1Omqq z&v!l`X1eSa*fGACt|BO&N+Li?jc2f6dUZ+p({_@@`I(|&@jQEs(Jyt;0|wY6g(V%X zG!L3`%NfOnBE*33H!yNk1cKXnlWM-Cu8$d$TsJ8(F_J1`pP1YqQfB`E$*!maR?b_s zjEAR%RTAThG!f1~qqp#plBLP_x{#leUoWZS-<>d69U@cT(o#(ZZ|&^}(csxf+1bMh z7whNeL$Qr@sS(PO%^JleCFniumT7WO54y?1NvpKFn($tfv)nl|(3*y&jGngfw4th? zmhQn~rSG}5cPI_#9pXLtd4MY^X7){*xCK=s9iF_GsSnvOY8v@rmghbW_l};vRY}X$ zFqnEi@qZRN_-P>?^KuOx&xzz#jcJk6PV8ft9GFMg`-le~2!D(`aq^xQ)dHQ?9q|Nh z(6DSwDDNR}jd>H`?@t6w718umX!aK-0cdDwV4Xvjs=O_+x7Mvu!05gOE}fce@0D?L+lf~aFUJ|HE%-9qGtQuazJ6csyFH$z zyG>Z1pZ+O~Dq=Lk{yepmW+VH0&Y`CMiS~tr+b=QV{;SQOon-62y)#jbI~BYILJJPO zJ$tpNvGE$x{}{<3F3Az^VEN&>4sSl8om&^?gu@irV}f1Da$UpKEI7O#)c>B8@(9D} z9S%43;AT%v&MVb*`kI)fcV2i&WNqE^n{JQMZQv>G$SsIfUPx6^>lbrIOTE4wiygX!u0j5d>#zr!{PI`#MZSzovE0$?AB>5(uto0dqbmKbeDh*%=;l+x_Ioc- z`XzMxi0A{heROmEjB$RB8UR*Vx9zKHKhobXD2*t&i!DgFeF&amrj~QlK@!KyTbm_5 zIyux{cg5BXpJ1>*&j2L$6=&{{`KYAy9p*QQ0mCojpQh_Ynu8r4{`1 zJjoTLyY}gGYeynu)e)wnJ%MQG90T=K4ctQ z#QU|Z_nD)gF(-dA~&WNb@exs=Gg)CC~H(h*^VTw7V{B6Sx6jmGKa2<(kJ=^y6 zD=zRTSZi7j5b}44G>9T~%g;^?tN$|)8D!mY|A;IS}h{AVpHP8A|fTg(DYE zC7s2d&eb-Gd;f8{{Vpz$>nmqGX;mDs%3w=hfFcYyX#Dc>(eJ0$l|9$VE*2yx6GAu(#o+j4e#8UeCx6DR66ig~A9nLQyPV zdR==`CF1x{qO9}~#2i0p4qEbWWLgk|p;J>`?LnbXxP2ZwcGRiM+y~>Bmvf%4`i_$# zC!BFBTXMXl(EX(u@#0m=Z1_mu{MLlvoldfThIqlB8NRAJ(kM(f5G-RD%VcWbxV|_pZ2KL zL2j3F|Ej6`Bt8xN)}JC_ zDpyK3hXkdAtPxPi+E_G459F#pR*JThI^|rSKZd-xdpM@zu@v0)v10;m6Y@$lHUL3A zw~?##m3i}PnAM06aR!G*Z|&DgnCmh_mu#12KMy-^Ur|!ccsPSJ$4=ss%&V0X8YiC; z2;|vcIFAx@`gNlB9_*DM>dg;UXBZNV>(Q=L2eldvm(ss<^+wKg19-+9jUSKQ&mkq| z=&KUe#H*<4PE`_+LfuJiIem1Yt>nP12I$IBsPZZZ$PIp>Y*E#Ou_3GbD@H~~Kk4cO z4K#~bE(?5MM}7u!2e~g*RmZ6XH!fdBzf{Pbz1Sv+2(Y86$i&p*`)yb9PH9a^ba9Vt z%GL-KSgn~y2e|N>I_@5HdO=x#?M#JdQFq4{F5_|FKIXag;BX{zRU~QUHX+H#jq8>f$2O5`>XB=z79#s;lpq)IkcV;ywSn|)YJBrCpe zE6{o`OjUg^S6&mb3S9C)!@yL@`oJkvfCXf&{H@=vE;4CX;PHkXDy`MR+0 zM+oA~7#?aZ_`oyr6Dv^@vp+hrQJTI~bKN?kO(oR&B+%mG0$S$h=Wfy`fwz;0K1{26 z+7LN~J?uO`k8Ua|{ZPRrwDivnm4n%JJM)}<;VeM0O1ZOQo!yq01A^WbG*;SEe!7klHcJYJnRjvRA%vARyZDbv3;i66^&i_Sj+usP2Cy1qf-S$M;F>MB&;-@=*Q3 zG?i;}ABR!FkLs@9_aRBPhZPM^b(!3W`3UL)yqFm3ab#)*#+0pauc@Xc7WqvFPv2$E zN4+ajEP@Y1SaYbOP=JK@9*CZ%`cqG~ zERiWc=B7VVt6NE#XUAK5KN5a?OlQ6j5}>YSF(d*=y&{!t1|0EnQcCE@1&D*+-E%dH zxsrac+z2soMgvCj;Z`AEAwsH>4b6CH+68BEbDDC<^jpj19F=HW zmpyy1F9XSr@9wa}NX$tO$|;1&6}0|kG7haVd}Wo>{Rrebn^D0~*f62;om4xi<7;Tb zv)$6K#}bp1v5t>9km9M+5eR~6!La($Ib$hf>&bJ-F%itUoyQyinGlo(Xf zBF$R_mp3%lpGFYZnWxvA240vMvM_8IEaVOkS>7}E{(}C}NiMsG*+QcxbW!h=?ZjW^VeexGsd$XL2 z5+FcYA;V)uL?S8gmR5@-uMNV};tC z#yQ>Y+6c<2=ZusC`UX<{w_|P2Up9T1F{0h)L*1!;TgGiXu8dxCHuvlT-@;v#803Oq z#+LnGca#?I6g!Df{qER1^7)E8Xkl;7cv&EaU1;VYq^?)~;>aB(ks#qMPPdaHZAt9Iwb(&1`V?m?lxHDv4N8 zXt9XIN2FkRc~ah8Y~4t;ZG_Y*918rJPu%^sx|=rIHkO?}kfW4oE55dxMs!28e$38R zz2K_P+hL;E!&*HCozud#)FFku5{D}a6**cS{v4qc^wYDCV9k7}eQpC?MhikLq_F{Z z)E+7SC`@j_Ru`8WoxlbW+ad=9JRXlslow_o(tUiq@_-B5lT#sD1tucqi^|bc@t#t- z((7g>&?WulYl`j!1OyO(?+Pw)05Jm6KbrCA(JBxes}XxbBRZBtmee|>xLsL{nY~m) z{c7u5EQ49~RB0^x_RCGFSFqQhjC5#h=Qzr9f==0Oc2BtG*iJ9G7TOddtg=_`ve*{QwNx_i5j~Grj1W3a+U#>({Tx0>LD-w+!0_o^4b6 z-q&~0WNwLqf}xSI@%MjD1I&OuapFX9uRCO>5Hefz!%t47TSo6UwEHl++D)8kxTeiO zUgaHjkK~P3YFn38MGT}{8-gILdX;ecdKW)g@OgM_oJvyk^+0@1IaEPJ|DGs1ohXM{NvI#(o}|I5hSykdjm z>qn0+x%M^vA8O5RvPdhlakqur+w-DzZ{ zYyB<(g>idFDB4b${J4B=QcS@RIyLrWhp$>}*%Gy>??-dFw-4f_Kd5EoXpC<=FGPM? zEQHDiq2F_Oi-_Oq<%M#~e%u5Qz(c>gICYOo5DHL(hDW4(rn zjM_S1hPsiF{J-b(0w=J~4XsbM#I%8&a`~d`03eYRC~k5fmv0>=8q$AFRoTA{qdXu0 z*p~AaYy`9?aBSi-BuRD|#f(LCKB_VKEanIu#? z0YFFq2-gc*7DmVRTkS$dCMXS-`0vHju_8-{ zI7be0Bqiy|N3@qXaD0)km25NI6An|i_|kW=jQ zT>!`qEkc*a7op2TY?xGm2r#CWF(-l;{Gx(#))$%WACh#PO725?DCctbj~_dX@3IEd zse{psZEw$&BX)k{@wiZSp)C@RLN1$jU~Rf(I?^Bghd(c6 zo89=tlF*y*W{Blf8lGIWWa-l4s}hmJ_L2%aM|bAu6Il7uAVP{*%Tvd(?i!*#u%y&1 zJch@{l%B}_rCNIyAJ}8zfoV5II_qDcekl)lOQ%Or?FHexOX8X$e_~Gf<4$NE&ZZ#` zl{7Xr2{l9)ZhL~K2ME?0iYTva0ulx(Uz?k6Aai$%=N=&Bu2(UtzjOb7IU;|#YIj`C zO%p?*_v^EZvopiX4O*^*gv_2oDeH%r2CLnB_eQNouWiV9Ay2|;fHt0$Fk^M}1jXMj zKR=%cAUw$~wS}i%ijVsQwbwheumBWp7qJy1wJ*KS>##b|r8uCZb63g*xK? zy|@QsjKWEbKdBQGu)?I+WkI&a-i=&DAA>QN4o~Ce{OY7i<7aT$c1Suv=BR5u`zIvIYRTRcX#aXM`X(~pz>ZfI zvKfs(^Kj{gAtFf_;(TjCvIa(!Gdy-EzFAf95*OnU`S6U&r5Iig_KrL`mtl-tChB%#Y335ME4Ng}S_&uu;eODN!)fK6pA)UxyF&LxY%&R+c3=M=&u&B%-zyuYc z#GrLSVvsErHcyK$89~^1u zRSWW%c_|MRbD#?O8kCOyi7Cq2Km00k)Gn{s<`x|jqiIH`=J=2aE~a?XyAruE(rl}g zqv3<^kRw+2U%Wjt#zq>wK|LoN_QC-8O2K{4_m|d;-yWA>zIBMd{N%Mw%myK&u9taY z6I%T6fuE_$egN78;e z0dDlX8b_%UC&WCYc&F|)H@mP&hMtTy)|gg%1EQymHl%~hinyTg#d)Ku54xBwE0NC* z7X%j}ieZ&{HMV`O;*3m7u&Q%{2v&yoSg}@v7;?LHoHy>dBCkIR>VxOw;YOXmhM{nd z@r@J_J?ka0UfD08+s-Wbik9}cB=&45vWcYnD98U^%!Jgaev$PE{}{t3=qUJujDWgz z*hQo-!5k5|wGB0CnA(z^XtjJ@w3XH2bnRZIo{C3;+P3+>!Un-2Tc0o7wM-cmR903N zO~5jKUV=jTyZ=tXO_D5H^NNR$wwKAsSY(&2t;pfH$KsmnIXgmmJ72q($HRf)AdLDq zWJt;}ROG2c;Ou5$#pT=vvu^L5+Jq>)tdZ zky7oQG~S3B%^*z!UyYPu@1&ZVc3GXxx)VjZeVuTiZ`0Z+S9xgy()%ZAucF$vnTG97 z!aIBGg60M!iF9?Y?(z~uaYs$zrt1J6S?P5t{i`C|UAA^qCJ&uRiBmDd9#ZXvcub2_ zf`66)KXWB7v64I~j8VCs06Jd-uIaY%b;i>@Z?*&d=SnL0yu$m-Dt-M#SBsMMSYGQY zGNF%={p=aDRVtr+<=m!wS5JGULecU-zz zcf@ym(?44?E(9%{DZ;-HVKD1$897U=KZdxbsR)sufsBZ<$x~nR7B%n!Y#Kz057VyS zsw$3pZaeM1;foh9^cfQwWV2(!I9)3%E8pAY>F++Z)CiMhwzRagU^PRihE@;>UVhYM zbc61rM~}4oXkZ#6(@V_w<0gj>ABK4_9!5j|sdBm0Md~?U=|8owEKKDgJj(klneE6U zR9-&%#70j!(M%0=RRqX|Mfc%&_q=&KDmfa*trQ%?C9#OuPb^1$o4`hJNOh)c4ln(A z#+UK<@#@wR`nQ)S2R{>aa!Bc!{aYwMH!~voDJrLk9)&^ ztY5dzspa^om86n6h~lL^gX;bAz^l6D*4M1;YoDjqEvJrG9-8{@=}%HV|D85YJoKNd z9X>UH(v3Tx-2Ox&5hl-btwWo0j}oA@rs;XF=z$`W5EvLpXlrZJmce|(p=2}5M<1sg z4x>a2hPTv){_gt+JAQNOXlmHjZQDq+EnULocE@EJf^alYz8A0u zgANyC<0BvBv5h}{_@P3(125{NXOOQiQFqzj>-f>)WE;-ke{WW2m>Z({VhBAOG%98+ zsD_g0R6L|XTIGnp2^#W5q@EQq4OQ^B$DZv)H>fjTFY%X6UT1=Jx!IW!enUZ^tf*_VvSvi~GtPXCdYe=Bw) zJhHJc@u_Md6yGnf^;!7QPmjUw1|syU$n9vDlh$#>j`PEYgM9&o0Gcmr?GeRPYzOEI zCoYkEv+gL#7w&RGNJt0)hAR;`myE2cx_N8rN`u@;T3T9;z07L#!C;51nM6>GZ7|L8 zta0n;>G4FIpqG1nq<8++2{$g{ih6%v4qKhEZpU$43C;PAh>Ih}Y{Ox$6Xx-=R>$mG9 zMgP+4Fu4J+w31i*?(B$@LIzVBk{w#zAqzh7t_)9~X%aY!Qk@xKtgH zKK%656lI;6nc=nT*In1)rW+p9bvNdlDg>gIj_%ZUF+=25i{-9tudfF)k^8#~7esA< zP$@gFDuheTM9za5xCfJ>WaSB%>NTA;jEQvGH#wB)LGdQ4bCj*Kb8v3RU)(rmk-pvW zS1X2yKx7#jNHe95aUq|M{Km@uWw_}wbY~n)zhG`Zjas|Mr@c@@^`EB?AG#ve#wFU1 zEnmL8vlb2W2_9g?`;_adP9_|LdxHRElRH9X6QNmw)i#XNWre@CcX3f1G=jZ@l`i}7 zW!p_-{aJuNJ)XiG4`Cl^$oZH{GsHKjBVBn(>!npUjHdiYGmsbYz4ia|jFE`+%K}E^ z22c|>a4@OX8-2cne5Y4``Y>GF_Y<7=b$$vl)0- zox_E<7DiVonVj@MtbGKpMm!K3%|7v5^})8@@w{&74b`CC1$?7!&%XyM!QfZHxqJ6L z;>~09y$LA02w;>#olZkrpvNxLttIO*&K45NXm zp@GXRJYB`2@c+YqT5u*(47svHbRSw}3UmVA`|1JqCsB{jLZxA~%Zr(e0Gk(cGW|fl z{{HcXEZwqsGf~)yB&4N5mPAB+zaG!- zJW=hXrdTdgId`jA%8b>PZ-edcwkJ=*a#VHjqAa->FdRed;h-Ijo@mAYJP}74p;EVH>^#}KAL19Q30qHnAjE_(BW%6c zM~Jz@!)`G9E^Fzeh4Fv9&ik|}eWkpvVk;mlkOyf=wF(_-+kkaqDdsARuNR|gSrko; z=X*=_|Pv}!+{8V55 z8U{@;2Qqs_V)&^tyi+-K){D6Y!Jv-TFUVL48Kw)T3fmo@{ainr_>GcHbTObjf2L0v z9oC-}%w1y2XwM72DPZmNRM~=b3(D+kzYCr1to;NaFFg{b=3~`e92iyyqao@OI`Nk% z@MZbC8`DeH-7Uf>e#RYS;0&W+=!ac`6Ekl zmUHAN6#ZcNDRXjmt`N3&dzF2CI|^myfB*X0vB8*AGmu%PPLG%5Yj-d|EUPG=2`}~- zcW$ry4!?kHzt9Hk8vXB@A<%^0qLAiSiTQdRJ+8FLORls%JMHxq07HP=U5$2QJA?VT zI*y5qxV=iS;an-r@0Xa3WxFKE!OOXKI6s^~H9+5vdMzoUJ9h>&U{h)F01msY#pZT~ zz?dZ3>muL!^>nnU25+r^d9Cf$`=O?sDA(5f0C0tMfzy>kMXFSZ-%0ebj8b`bX{hla zProp;4>4Mm)P22wM(tTk$trB$w}}O0dQi!A3E;FCBod9`s_J1vZVJ>dHO?nz5C&& zHcJ9~>O7K#Gtj zVxS3;YAVv~A!&AU??DxVjL1Ty$_Aafl0m4pJVdm#9ch7*U#5ty1r{zL@CdyB4y6Uu z8M**hS=81KE(D!N&ATUKBXd@xM`5(X<;Rc`Qt=Y!|6K05C-0uW-2*Pw$J9Php&z5u zE%%zAxSLuo&+1XUwy~YcsQx!W+n@J?y z4*(YN7jc3Y3nw>RCBAqh%n~y`t}uLIZ|-p1_3NY(;qbNRm&xUY6B83VVKemD@y;*h zY{6S?NyU~FN_A(mQW+wnW*aW!>93V4EO>RL$G>D*JySfh%Xn)zZ$2I_@`U~nD50_A zao?Rjtl+06)>$t$`@V~J*v9|!ze_qo?1nv)hV7nm898u58_j=U41&B$NyN*1VFXX9T!@lr^vBXP$EKV9R>!DaUuH04fYy zU~giDUujhJF;oBFkw={6OKyx4v5oH|IY(?jknk*y*U%8L zdShYa`D|MeKK{@(pffr?m7D-627i3zfxy)Dr?EPpAz0?#NC1@@^l!qOS+QvY2evET zZjBiX7=^A9@~VZ(dB%SYCWT#hf=_I zZYUhuoaLE4Fhk^L3Xz|QLyU<=^RW3_oYTYbKUF=YX3S^)5zU?5ykm!7=@oHK?Se`3 z-^1@}Dfza?bHSB@{Ij^0?t<)l;oL$*K}BNuUXO85&tdyhM%>Jiq$?2M)5J8tf|MNR-6$5;#yy`lDeu!}F_%6nUZCgA!zx+KpN2F=p3Lao|L52^;hWQ+p?ai@# zN1A_orgC_uG?bmbf3Lyp`kf1Y?>TbQDv zuB$7N6-x0j#jTePL?vIoEO&5MR92P_Ohi{Hxw?8U@lZi9)6L|MNW@fJJJ`1%U*}v> zt7N`wh<1AD#@Gs!3JPILLq9JGnut^_%%F5%E?51-DlnYzjC%cCZ6W+!3drqD1#Gp%Ft)ZXWz|(1@aqe+6_tNWx|I7BiLA*?$PoyWbBZb|=(Y-?{8wclj^ZGYb(=SD zCP0xzC@3iSk2;4e_XXQQ+TZ;|P)8hV7ifJ{7lh4I>9d0P&hpXg1i@kJlket(W>%F* zk$W80c+q4Nx!D~be~S~sYE@lnAmo44Cn%pJyqolPo0|N-68&bcHkw|BZbjGyI#%>o zrCa*0Y)sODDnk=2rB!4-J;{{&+uUzIBxq6@=hb%z^EUgVc)R@Hb+(0l^eYUaQpf5X5^R=el*-G8mYuRL)q_ zLQ9o#N&xJRrG9YH-W0yLvtIuFT+T4$wUT5U`Xr>QzPj z7ei>c($^>N7yIPLs3Yt9t==DPnPgC8^x!h;7sveimk*2(DDcXHy7#wRZFyc?TucmS zv2aG8JAkQBwyVYl`bz<0kx}(DR8Hk-_7N9OT!k=T9$W8CT4=wlV7UD`kbLt0?vIx+ zeax14;j}zGcfR`4D}vx%7>ho|%0T{})+iA?J?uo76AUhaC};6II)6?Np!Ng^{ckjc zURUu7(fvsx-#Cu%w)JNI?x3d1Io-!Z#HRGq9NJGShWs+)_XPQe0ZJLKuA#v~=H?P2 ztnP2Tnw)IVlIO=d`4FFrRPoGd$MU`%hO_3~va)I++aMfEvGvxvL}Bhn z#t)Em0tqIw*z|Axudk&^M<9H~MS6_ImwDRK3<-pnfwaS0Q+XeUwIHB`>IUtgI| z2>orHzMWx14ExSvQ$0*^hv1h2*a_!6sFs5%i7CS;nqZqZwir3frE83jS~cpwgysTg zw#46hCs3{}O@|9ZT%Do5AeLY>U}6H5=&wk8F&hLelzhhUfB>;J+Dd7D(+eg4*?+HG zkuPz;LSM41X$A-<^snps?$`rEpa{43vR!0X7xMP$eG58s_{&NLB_PeSaDya2;I;-e zDS7D!o$!+qLYR#0n}B#QA`rZn+nsKpFE?;{#nCfZ@1WS(^Apj`&Y{EMJ-nQ;qFy5P zH!(86f$Hz-s$L?D)2CLQq%OT3v>9eQ09*>`)6i^z!_Cmv&hCzPsn+|%3j5)U7cO*u z7BjWFbPGYOEFTIl{smw%H*+?nn}co*WBn^?q=AwJIpe~@=RyUyCEHH!FX%ydBwRCO z;^Q1zRSqXdBU=(nmlu|`1dxVs<<6+9i1XUSd3L_iFZYLCbFGZLN#L+``h)kb*d%Mb zC+E3aK<(*{p23vEg@$CG7FAvchAsYd#eh#FP9;UWFGi>VxGoFMSl)Bp@w@vu!&u#q z$X0o&{rNxo`aV#+YL&4(f|!110E`sNGYEmJ>6~T7>#aVSYVX9GLnQkc36}#wj~2O0 z>463+|96=G(J%D1i<4vUIz|vQyBL|YumQM{+=18iB#Xgs2tP#fTeT^@Tj;g3HbH5g z4%@aJffL4N1oJ?>xv5FCupv&h5Z#JK0o+bg7nLykS$zyE+Gsb1bZpE^KIwWo8BByW zbh6LMuGo5NIRq%F^x9m;Z*tNY@R@ny-%h^a>J=^JX@3R@L4!+ zj#rJxv+P{o78KBX_IKB3@KrM_R9*>KUI!%FbvD5q&drp8#%C<`_3KH6MXey`afM!+ zwl;g$6RQ32cQd3oQY5org81~Ia4G7443`b)s_*oV7&xT1hG=HfI z`k%jzvy~jmL84c{Te|i`4Bk5(&p$+Mc$frEDAxHAg*-LZb<#|mBrxY}2)o?b`igU8 zBYJ5B-O!FxrWqgpiL>Uxx2e_df3yq z_iz}V7W4J5>&3olpu|@5vhdNW8_I%mS1E2RgIQunMKV(R%F#=SzHywu}h+kbnUuy*O z>x7XQJ^%ACk%-%I__Drb)j#YwG6ObIlKKq0QgJacZXr6g6ys8u6Ii3jhQ=JLs;c^E z+U4-!^m+B@Cu7J{B3;7{wb_oM{8C-sJchRwPd{x|cy}j9vZDQ6X8L!`W~HgP-zv2t z7=GiyQ^f@N6_A#|i5j4LdE}ZZt^~aRjJ198x2@rT)xsY2@HgStUT$OphS&Y6;Rw;Q z)mHhL8Sq%c+gg*8lL^_eFZ}%c2%IfnBvs8TD969(uz^%JkXKM(ZFfVhEv6DRapD&z zCIVq7$w`?POqE(>7ze#@pkry_!QT{ea;z z&I>#GsqFo?tdu@kW#p%1s*ZsZyB;hQV|yn*GmNtnO9x30wR0BPbP$aMTLoq89Iy zDX&#+sO_yr(-8SIjNP{LAR%1J90o^MyHtmET#Bdz+WOeByhZQz4M-kY*p@}|sO&CMB z5*9Zbls&Rb4E*d+=hE`6)+j z9nIy-NzQ&PBgK?zq_$5&&h#zS9JQ%%)$^Vn{$r+ax&MF56QT{n$sW_~NoO5MP7&`% z^I}A@(5Nw4`UQz-i5mmKFc%Q*y$78uh4GKjexM%!g9m*zOc6{W*bxFSJ~gsZ)-t_Q{RNeOwSMoq#G zV!5zadh!LvZ!XZT|5Fu3N^`%;F!R;?eM+h4MEptW9|-uuT@UdAj=m?1%bTWYG-7oS zBSF>#6r@1cV2|ne*bK!ms?*@YXI;GbA-ZPfit^hn15{Q3g_NZs|anV(HGkrF`wkT4z_tto!26{qeU5WJ@!GbfWzUqy5M(0df*a&xHAqT?CW1P&dj_ z1yp`iA`{GCUY-3RoM2J~b2O@Xc|@Z%-pB^hm3U`QTk;PP6Xt*~#i@PWT2r+WcltJ} z&p+|oi1p#n$m0{Jmqy1p#hv$c#L?bifT&fqwT0&1f}6w&6n5pqaVskRUUl;7h!a*U z?VU4n)Za&s?L_y9SC@^3VH-~jTr=`>T>fHgM9@1-tSJxd*VENd@OM+>Yw9*87INIr zN1eV+9qcckHd7!KUVqLUvBybbrRVPyK~ib^`+ZQgHH;AXwK(m;gjNtbVl3j6rqj~v ztivG5&~$;0IJ%%q0Rtm;8=iYsjVCiFu+ch3Q%Tro+bRKNMnCF^$ey#hBTNebILfqeg5~g+hgPA?4nOa)a`09+@E&F5J%Bbpm9L# z%(Q|-c0oZ1xUgKlN!(ZjSIhqXbb>mgu8De#CRZo3L!lczgUTfwBNw>SXpHQo57pjN zH))A^N}`i%aR($_b)SCWy+cQ|!T$dw_OcdlVltVaw}zS$kd!~$H`l(MFmpJa)G+Vq z=HW*3aPvT@yaCGuA#gP;rD$g8*Gjs*NA3qD!eToU^?%x#Eg3hM%)B`K=s_~NQ>9z( zD(Jy#xr~-E>QF+*cMUV;mR@epq=2x_Y^^{b^)uI;2akY~`a)v17XHiOa1kz#M2c-5 z?y~Od2U*Fpfti);?L(H5eYqli9$ZgLXJ=<36;AdhMV<}-H?H1sxB%Oh{u9KsbTfSy z98~zNi+h3px5CekM2M)!@J{eqv=@WL2&~D6;*HapvF8f*_PueeXIFesti0RP^i*N2)xsnk z`m+C6VGer?vuXZvhvs;#*r{H0 z`g(rq74>U3tYBPuzx}>_P1#kKvg4qWQkfPX)!q<4w;+#sHrYT9vS4qG;2dZct(GTS zB%pLl{%93d3=8VRu-=C1)m&1-M{a#r-ahr3BCneYjJM{^qNFEFkAln$`hxn#zsE?j zG-B<&9f}RkE^XS!sFE1M4j*Iy{O7Y=D6@{6%S&6RhAVlX@Cv2HsG{O_h9eJ9}2l2p!(<=@cbiCJ-~ft@PC(OD#+Qlco(c za#3b_A7ARw*0tYSpRaj5*E93z!l@?rKacZLnw+DNbk8(;5H%g&mO(DHkB<-#2pBW9 zXvQCbJG~8wx;e-BeV4^rf4RhTdG=e)ipfN$j9u0r`pXlC6n2Ck)7E*1T0gbMcjXfA z$|Zjca^4k8DZD9|o)`b_mAkJ3OnSsaX_o6(KRxVcjTOlZ-va##_y}OSb zl<|P%9M6dR4siPFVj00EU#UMQ25kc@I6dgkV!=@omHx9ZBAuORFk`hDv*Yy7rO$HI zVBT%XyQiJC5U0T|?#ERWg$d@4OwGWs6LCXv^jRoK%|`97F_}MfJqfdOlj%&{87yX- zD1-xP8VTRVjm*HsHsay`9x%gdN1D-}H`>-lKM)@c|IM`fj4R%f{f6qsO}+p*Y|PXy?uGjh~3{#m|uaWAL+&t8r|jV43{~iO692p7kf+JKglZ_tE;vT#Y^Y-UuuDL9vBR%5H8F!nw4`lVt88dK94?s zhVa@(yl@fY1SgrC!43C^|7o>h4(!4z(|3Qx+sf6Wwz}zgCw!?K``XGR|m1 zzeK8l2SR(29z86hBASt!P}i5_0_8S%)xcq~hi#*k}!EuWtejQH$UD_wamPxX!2|3lWd$1~mj|8F+iFsB^aay~`nFo&FDOb#jO z>`7fbjohF6_xSzOAO2|9`~AMI z>veh_SaJDTSy|9{tI4>6uDOL7KVC|xHxV0@4~=L?w)`L*8#2-LY^_g?&F{6}OBys{ zCwU#*_@%MLsz6Na>NK`S-=}g~706ZaL~mAsOCWZgM~U@{ll%CmK_y0>7Z32FX6`Iy zm~C)IL;`B}1h+URB(d%$h48ck5^6uW6xG6ZVM|b|i_BSD3N2#zy1{6ih%tPqfLBlg z)wDY`p&HwV&o3kc(XQa?jML5vcTG2eZZ;5R{{HW8TA|oa`SryDc2i`2(XVccu`}Ab zRk-XvK1)1K4supo*Px}rC&a?y^wF(8UAq2r-oTTbvHksm(R<9}TJ!WT5Q`WKgqXC% zdhe#@X3wOwBwuz|FmkBa4Iei}@UxMF;ytKp%(^y326IOy7@g9dkFQVbQ35#3RXH9B zz+PUNfunQ9AO*wR!t3ewxffJfMm$I$&tPGwtW1Ln>W8j|EPC3gQ9s4@vcNCXj(qr( zyHaRHQuYFeJ2X#or~^VjFqs1^Qw7%HdL8AaqOzNUuZlzjP!v;D!Y#Uwuct53*UR?I z^lokVp*^HLtBYBeymX?U1b!j!u)cZ9D_LttE<_3MWjAbj{fhiu?e?IK zk8#EC_XAE8wzuXyaD@YoqWWI`{!Kvx4CuvZv}v1UNb$!Ntp`}vt*C|Rgh8OOzJeSM z2PAi%m>;jj%q+Tndq-Svm8pia3h+K*ZHy(w`1w!_DqfgJ>y36#193jac5xu zodj0znA+&1D4-jZPx2Q~8_@aK%;7;sm45s>(RAMD+~FVqm1af!Ea_&J2>km=jp0P$ z)12D^tN@e>jCofQn8t=QCnRSp!gz1P&!Yx;zMM0=i^>zQilM{eplk)r@LDy7R(0~6?y}kx8mQ>C1cLw zz@gH^>FMo#r&HwAl!Y|Hs+J$dRyC;E-MI{$jj?>}U88{C#Qx}F2ZrUFRyEeaX2-$H zSmMwHzPIAg&Y~P~7r)_=#N9rTG(lnM>t<-@gwZjsx6#0xKj+crU3wI~?{TI)9aiDP zZE0R)9+#=kgk+L8|1`Jsr<3p2ICtGr_%>42lf-<6Uh=)(1IBi{aq^FvzN;| zKc(xyo##jZD97Wc0sabcn~=07kyUwI0v3?degat!Hw<7D1N`1ZDq`y}? ziCREovc#2!k9aL&jsH-6AyiXP@9xw0Nklv6p9w0p?|2;tz~AHy72qhk;|SjeWLy9h zwcdCceDrK^AD3q0wY(AQQWD_hU1xhbZVEN-`z==gj1J8*E~sZ)oAPog8HjqY@BljmOYS4r=c;dnOKk3@fZ z97k5#cHUWSS4WYYzhWTRnn5-~kFq=;KxJlZyI!KJfjC4i!wJ=t)gR0C3}5`A4(N(p z%(a-`2p=wy=_8b^HvqwVh8%{pa@I4WeiXx0rNkg_w*9-g^bgL*u5IvtNq2%t7{ z?*3;|8tYl0dM6YC;P=?}t}Rkl@(7a{?Q-9^tDXXXd~Z6!Ym6V~S()HYSDpY<#2^&i zSw$9~um$Dr;)3IiAfc5uj$m5l8S){*;L6`%3&ARp!b*{!;>n{)Vnb0bjmb1OFTVry zFbi6wY*Rr7mtvHV2EuIAS zai6hJtw(&VVPdA=1*NCU8ERe?OZmoq4pYZmN`8SNfniPh2|}vDrWc-h?=njZ?d-_5 z5}*I>J)$VNn=FE6otEL{7ZhV7^{`#>8|84r$yFUeVzy#YwaBJZqg#6{ix}KkG@6)8 zwmSQJ+Um^urct$8icu*=TP^==EUZB!>fC~CUHg;vvM+l)q_!dCu_ood(NUl!UVV&p z*2fa|z$9BBdtQzFP;2aEH9EhXW7L3*TIW^tUNgz_$86_jP`xsg7>AD>>BFbcX*3!U z;t5=ic(E_;u1lrAI80PjG+XqdOd93iOABl`i_?SdK?~Mt&>20{1wmaNpbdkS(M1K%=m$Lv##Kjh5p}0K zQWI~BiBcZiP^3uGKM{}lDv!Q6+QP+1#sqh${lhUx#j?V~?PePMQMitBSe)!*9`YwM z9^l5{=WOxg>z_V-YFDzRP6xQuq&cnX-p3-%+|+>=D3EZpmK;JqzgFu_gr!fXoT2No zNo@;$HxAmt57AUw{A7zgxmW-Ru142-~+Oc!m_6V*7rVQJC8I z843-Pj)`4<$(~>IyB$8;52XI`zg&d3u9Fn^qV2g%IcIzMs}wf-MYU%~)_`A!*dXk* zp`SO=OWESv3%}oQh@67NxlI0$uBpOWOuWG_pcn%lr_&5YKN5y|?(Jh>x|Svz!12Ht z?ci+T;7`Eq9?;O%_RS8R;wNwh-=O_GYLOmY$jnC67H2cr{_Aem(?Kv#LC^K_U#^SK z^Y1;YrBkn2mOP5^=0F{oTxMn@-Z8^2OICC;A8neRwLyD$k8^RZozU-Rd)_5M#LiV`ThgcJ!|?sqh)ovJQCw zTgla1ZC1Gf`6v9nZ#LfHA3fh==`3(Wn3W*i%AVBS>xmPWvMEzPXA5`7cL%(tRJlr3 zFK)w`syLHlxrzmPiVfc++KR4S(a_eRHej~g#Bd?g!Yr?Y; z?#_(#S*4FY(AAW$X>T!&Ko5b)+N$gvtRWhCy~6Ukzx=R*Q_M zlCGXgdo{F{D?b+_8BPR7lL7*?Hf>_Ey}*>H4uH6s1!9Q>rC5C4yFHSf4^^yp!f78} z$=@j-e=1abhxa<&XcI^U0%NvQA-aIHG8?Qsy4U)|)7%da6$9Jlub&{T>lTOh30QA+ z?7SMB9%f-Xeqt7gVQjY2dlnIG@N*?&L63{!WflrUxoRF$xj8wyj5#X9sI+8U@vS(s zM;D_>PPLGjRyg!+h+7n-J6&+7#3Xby9`XoS2Kidy&qX+Qe|zh4Az@P9+0KU5Bt4#r z0$r^JAc>rgbOW+d-+^f@NeiA$^`bN8QdCv(Jre2Kof~$2TKOCVOZakKmUKac@Y6y? zHMNAICjjUH#9*5)mLHnZ^+Vw|4vZxc^$S?Nf^G=o$8E@pXm(B}vvUE`8biA3k#;R= z>uj(RZ!J;jCvVtcp9tCO$1_;axc{lPZX5eUTuHBYlmGtt#w%Y zj`FL+K3hlg#+lI~;iB+6#|x+WN=~i=t4SSsR*dC)dzi{E2tCmr=X`#Xbg*Wz&YOzm zwl_t~X(^RS^_@RSSiKJm7%eP-Srr*mvp#gfK9;AOt67-pZOs)}xo;3BQJ->6t_%+K zNU>8oSyqFG!xN%NW}lFaOSQ;=!|^&{T?Qw!KR*fAx(_Xw*6#=k{7IddVUa zJ(e?Ta^c)8tFQRzy@2qlticai$o(^@QPijQbJ}}a&7DiBA0BQQO?TXbCURnDMCu;i z6{0_viLS^b>N1-Yzsj|P_PZyB-x$0lV`|Ij$gj>>Kn4Y$N|gX{X|B6^wD;r3l5nWO zkYj2gu#V~oKz-@5Rt|aTXRxfIZMu|^&`OG(BbyF7S$~5~$lFdhwvWZd~veHjLVPh@M>-K?9Q=j)#k`|A_C|;`h_+ZofEJ2b z=VBKpXtD91l9!}vixe9(?j_a&ULofgXZI8Tgdcw}tque;cv%fvNOD6Fz|RTs_a@vr zYd)2QJXa>)bnVMkG<3QRGXh}c0Et}qSPE87AnzzSX*F4ZAGYP213? zqnCSTWqXav^D$n{zL~OQ_5jE?`3wW{i!BOCKexlM9tvcJtAx2MYnEtJ_|*wDXU0i3w2k18V~o zU6wHT%hQ&H`hFrT2QUEiW&v^?!QEY^jt3|MP~@iI%}Xz6Dp^?K`JFi7xK@LKB2UK> zy2Cy*h{F%y!ItrWJvK9>ZjjakN}It0r#rl`(!nTlM$LgvnGU?lqZ#sqOVcv#IGW>Y z1QeYGwq$h*f*(&3Bzr~(N^kulA9hEpsl0Qh&+hE1Z@U&@V5DUeTH#4KG3e7d`2gXv zI*4i9lZuLq&`QZWFJ4^p1WccSkL{bzB!cp}4wxEXB1!5#8^mcnB zTw{9b%~5Lm{AAY+7@ti3<}F)fTH$q?aQ~-hA?f` z#C&fsxCe>;2TsN=ahM$}Ruja1j9W@eXMU^Z1NXv=kKCPsLG}Bi7&^5pXjX+{|MSHJy?4FEP2Bb#;Z(tEkWG^!Xq@fl0qX z`$`eyyan#G;G(bjeDSj9T*-TMQBaGB|jD23Zdl zbzHi~L2x>(gyL(7Z{#akgdVaR{t7&OS*CJ|e|Q$ke{l4ncUE5g*R~z2$|{5$j`@`_ z*gjKe@SVi70pXhO&N$}MRLq&22FyHxLpy4FF{)g;uDRbr1AD)9Jo#ce(!W{i@ldFp z;{@0RJ9%(~lol4=(qVkCE)h-7dk<(a*raXuV-3vy(pG@nIyf7{G!WA0U{fchr0DaA zA^(u;f#Zge!m*>M9;pM)R@HyR_B(WFBFblUyc}FJ5+vEJrxvzul2p0yQD;iP6 z_?eC=YKKa`gShMubdB^C_lvyi1Q20tDDRpey%o#$EpBy=T!0e%{L~46Eamc?hxr6p zD;OZ?w90&@dq?i^wJZ})>xgNp__&)yH@9C?YolNL|c zjR_u(;|DF?pO?siUwja9W+!i}y#n@KDk?|2&tj#w5-xv`X|;3w3FHTO<~9i3Py)mN zWN`T?_YIH_)fSO6zx_9_OoJHf`8K}fL*EL%4zxGF1|8d1>U2zF^JcHAs0BU3sZ(05 zd_g?zhKIXs;Fz+=Oyi)6;g&n1Y^?AsP^16S)wP#h@eoKWko$Cj;{uM`l&OLSLkGYv zeDGT?#O5a`8asx?_8CrJnBFg}&-1|BEWZr&%=E49I0_t>ill?Lwz$pq?5H!a7#2aL zd7YdE;X4kLpy0J}#5nC@H&yv3;~Bmx0jEweKLEx)2A@}cTq^g#fdj;n^Rre@$|)Pz zH17Z`?ljI%@y^-3!GqeYsA036K0ZDe!@$p}U(^7JGN`exu`zLwJy5P`H^3M;-F zd&3DbeKQg8fQZlWjS!0V0r(kkQZRTqMIahdWrkl)OHTHL{J8V+(aH6cTemh*0PhSo zit-dwxDhn?G6AiaIvFlM?@ZPCc#xcM?B=caNpO(8fzCwmG~FPJQ#7j?;EQ-`75DTE z(0=xmM1ynF2h$^oBR3p?9jGum{fP)U5@rn2=So??PRqpdHKNlP@_t`DJ&9kuJzc^? zLzH`DE=xS{7uN$mOFZi(UtY2blmPa&OS5ksT0cb=t_ET(u+iboVvWts$30)wrS_kOnD`vPr%!R?8{l1TF8wGm6_QInvk|FOcvdu0H z&QPzQ+hYXv;qtvZq}qAf$~h4iKZ8z?H8$e=dw+G_GtBTVTPy=bJrXwh?OP+{_OP_3 z3(#}w9rVuuNrTE3xP@g5-#oikCl-S@-r=x8lL~IDAI?vK4tUiJ*-QJydAP$w{$;$G z0+6KuO8rguq8I1LcMXh;kb!#0P;ES4(KZF(g#`_)PV9qB{rJ%e*-!)xy@+>w47_2Q za6q)V+HM^;dOpQUrL*7!b1RQwue?VePV>{nkEFNxr2cb16Qof3iWJQePow)P3iHO# zCc`U9V0s7ciz1PxV*`rtXQDY(IoN~M(frji@rL?JMIw+g3BHPX(nImD(A393v;%7O z;yt|g;!0obMRcB$q2X01!0)z3GET=J<-S1ODx~D(T8W^^4~PVHkmpMBg@k@tvzP1} zm2rcC!5b*2oDdB$8-T$7Q#DbwQqMH)D;w@U00$_DH@rhv?B{cHgM4I&+*!h_A17b+ zC+G8s8?8Mce(6~c^uMn~d&<5J22Uf#>2h-<^yLKp6DEfnz-7C)doo<#!ye*zoiSf*oa8 z$6emPFlV6RJ6%&d-GK6+#>y~S(P=x!@P5W5h@(6bySX*#&_U{8;9f!4Otp#2HXrt- zf?`K2#3z9jHyjVK9nm7s;9|ar@F`>x;pW#v=L>5PGIxP&EF`y6@zq_R{|?&%(<1Qe z0#bF;^cSO4?}%p6O|0faB?|$=JYLSLQIFJ4Szf?inLH72$BF|z6bhuUT09?w(L}~z zb2qtw)@!^icoK}!roXjgc=Vj2k`h8@qNm-Tu<=cL3^PT%gwiENRi^|+otnrQGP$E` zi`%RHS28yeFr_&5x|f?6j;Om$j|IbuCqPT)w;s9OLhd`ZU$_s#J}t6&Dis}jbO~gp zbL;ECf&dm8AU6aA%@jcVk*DDir*0owXm@(h*F-TcSy)ZoS0aqz3V4OONeHwzo=u4L zV3}bN@BK$sr|&zJ*oEZef)sLQ&J`f?g<=V0GJ|BxBr0sIGL>?ULaI$_K*g;gN}B9| zY=UcjUFYX@Gq=?0{60MO>9c2g05ILL4F>>K%&;8*xmfM9Uio7T3rwY>Lrc`wtW7!$ zSA>F!BoXdzkZC>t=ox=CGq-Ax)(i+jJJyxI&{9`=Q$#vhyu7^h8i=#$gx79=mvN49 z%yqY}?9SI+>a0|OGBE$Ccdna<)yxVGBWbn77}LhmrlM}QwgIBIZN)s%?pnsxv=X(I zkOgMUyx#g3ZLoVkByJA zy9;9a(OL{}-#$Cif~Db)yofTnU6Y!M{&v4lsaFd$yGm~U`%Ei37%I3&Ol?zms4Yn8 znIMHb>Ql)AAKXfkZy*}OC7)GHMFR;FF(;*_&tfrZl8teulF#40WtRFZ{QeymC~7yq zD=Ny|1=ZtPObwi`jTJRSgrq#Jtp!>wL=O*nmPdH$g`70abAZX3+7^|1b#-75#<_(n z(^K|kL*AAyEg()010Bn0Ord8WP%+JI*oV}cm`i=O- zbEg8f0OVg}!@DUD#Z3tK0SjwZ*PXX--$I29Iy#>#zOjn2P%mofP=fT*`=9)fbU4ks z=UW$6Y7g+Z{UFfCTjYKLg8970hYcX_&kB&2v5uJEkFOC)lh%zVzQ-a4YUCIqC>0fn zTvJ=>g+Rt4!QwMzobXvuWL^D}g?tB_Y;q>$5_$X446=iL68XTPtU>NK9dxSsCv+;q zQH)y?*}wpjnHkgbI(0wa-N6dJzmNQ=FLo}5VUEiE>TvL_H}P^0(dEr+eraDH=(S1y zi6?S-2b}<5g6{p=F6(I8$TWB*JWNZ`QK0gIm;=U9g1qW)6x9!qhWezr6q2?T?1uvO zZlzs!JJ_SjaK`w3`i7P!$V50~-URocLVpQXaIpcBcbg-6P23Zr-F~9uk}qd*M2liF zWJJ9;^9}R*-ce-UF-@g5Hds9SmRl;L4gRoP6J}?RgFU>V3cG|^zUjD=kB_JiCg9L1 zV2t$X&aGSIyGKj?;j^N!@Dhuc+v<<{^5y=)zpy-Rux$+h4VGiEa%A>lU6pXW-1#K{ z*aOF@q0)A2d6#=p+VLA(omr>Vkv~MCVah=A6dFL{QcMxieUd7Kld1ZYoNI`>l=t@s z&xI^;`G3UIFU6ke=V>4CgtEv%05XdR*0xnOlC%mtGxkg`D9OGg!~&D!%8)Df?%s@Q z6BVYc$LQM8ba+f>Z1afAOi}D8UK&z{%gp-T_)BdpY{enFt zEmjRWo|v7@^4C~-g<|4-Y^)f~%@$BeRYs=6w436ze#o^9QTR(UA zALJV&LF)c#`Y7v%gc~}4`S#5y*I*5L3qS>mD~OurbfEVIZa#xdAfWYAsl(D+m(YJz zLQJ^I*J|LrHEEwxYD-eOpvphnirP@4tk2xoSb`e%r!h4X`gEVNx)xJWY2(g=vt(0c zAl%#!U=rx}wVrhd1Q^Fvw0s2G!UP{5Raoh@EwIug^yylVo)lqenDQ{PNEf8FBVD_8 zLu=03+rh2Aorn-{3fr3e<79-Pz+!Qd6kw6=64sD(%rGAM^iSHU$M9|uc6k$+Fmhv} z?Rs|FggEzXf`;1QuPO$*?6$Wjj!sT)G~HyM12L1^yP5b)laV7D)hMZt)3tVL@Zz+y z!@GUZX0}-#X(Noa{mNMdw7i50L1zKiAttl4-gpBa8jVcP_enZ+GzmCmC7?q_;>44M zg>aXh1fRLRb#6F(jo&Cfeju@mKjfwKVF~(KSgx)o{)|Mtiqb|t-y)s%brAU<=XpwA zVX`t`zO@`}q5IF9P}j%0KG1pca>R>OE*^yKd4N5DQTGH5zrWUEyc?UnvYd@C0c(mb zaU7qHJ+eVJSHmtCMKZcxx|I+I>B**s;y}wbiOHCtGmLKQdlle_K6uTB-`pvkI&QyZ z5Zp{Gt^Hg(^F+)ioBV8kx7$+U;?Vb7zM(()iyS$@e+GJHl1Pldw;OiE z7Ca@k;16+yT)WbC)mUYFh#?Q4Qcrs*?4qe`g98)yH_vwk9*`zVvP9Ca>aOhw_UevEpn!Y;KnymvWn%b%Hrok_syfJN z(oinHm5A(cI;zbHH5(RcyRq6`qoYo?W*^^&7LgBc)9+38PZdhqtsk|JK-dnSMl$6k zE*3aH;)>4~X=-P92GMnqcfbQ25#bX~Zl% zud~ujmViI#wr)(zKamXGLD@7cgr`M`u3r}IC8c!8?LI&GON8t%AQYbj=ylTH33lvIQJR)ncOo{P4fw`3?sDRQ|UkR{Scq ziaZAq4a|ef5+hLzqw5rNzOnO+^p=Ll%=}EeUHmWuKTc9SHMT5zxZ;=3cnCeY0?NCl~|!=d6x4HtGQ9apDg*vug8vlgnC%CG(2&&$shgOr2G1ybem8?TqJ6)L{UZeNT&Ji@h`kW+djVND;hD(Z%YDO#u9 zr);Vl?Yh$$wK~iK&jJ!_9>)BN%0@U|SiIv^C@UDavax9Y{5GuBnd#Vu>H7#w=LqI z49VP4$o9OSiVg}pe{wtF<|}vc%0Kq2NMjJjAshHcv*hC9?R3m!cge$~`52CiRkh}x z#HEERJ_x|hUnmo2Fy-zP6CblxJ5>LZ2iceSM54Bk-R%9aM8J337s;xT%CyU)2LhRsKj+th% z*>_{;s7n31#Q{YYt#S7?rm)b@Gyhm~VYgJsUlhzw`!c!-%?J0Ed(>$=3P_QY2-4q1+MZB|VX z^Cs$V&aNT1Pe0apwfE+k-EVCW(Ctn7MrHB}yYknb2OI^kK>%dg0r(^}p`cL!!^f+; zHl0X-_+xtl)lNzdWlt*j5jo4^-GVnPU_Elm15{IMnH~{ju)D&kIhfB#-(rUIM1m$5 zbVf;xh2RE0hwq+jb}eaMF{@ndKc8L=kbu&G_e2!|kWiLfIxkWmd@D#^7b_!Kf~l?X zBfdCynY_>wgLu`hqkES*npCuz9BPiC$&A@Y(w{CqtQI~v1xzRRY`y){82&-Yi?f^Y zfO}@@2zJ+%|2&S$N_%KmGWe+mF@XHUua}SWwFW;Jmx=^8W2DNA|6Ub!I6%5a9tt={ zj0swIz>J+P_K%$((~Ika#xBn|UVlyb@O@z1YT3Jg>(4^k$z^@ExrN9<_`wU?xA|au zVn5hU#7?*9%srCb74)B9Hh^c}xpPOx)_VoE3#23zm6VtO1lbA@!mruDF@m~1MOv3< z$2@niJjE&W68R|-x zfOnJM0F1&AaL0k5+MJdz203M`lgy4kd4-45m6B=zfgv(QN+1#=dHb=i+D;p9DV0Sl3z=kh5lIIkvt zkvD+4vJF=!M0)t^n=Et-AXycqCnY7J4tir^qZ3-FOG|N^MJ=zcf5>cK8d8CcwuxS| zYh*;eaM*DDd&8s`TNBlK{ZddV?88lR$kpYHfA0M6v6w)pA+N)(8&DF_ajQWVEs#K> z+~u!}6=_-);VH&Bs}Eb!^`W@}AkB~AF1NBrT4m7Q*;BG0o!`{N;QZMJ$Wf02LPWD^!!Wz*D8_F+CoUt?c=Ca_kADj*dVk_FY1cnc4OEF zfd^f*MpnGYUifDX(GRIHm+D(bHU3D7C0=wwo+#TivwVzBr(<^Q{Le|i!7)H|!PL}q z+e#@%18(lYbwKt)kCZ;kZ*mT(YGvjG-0a(naXCGSI4^Io$b89{)3nSUgu%*&fTmN! z>(_$5l=`5Ab@mECj`G6nIbAKPW&TT|ZatV0MfGoYDAFQ$9^^ZyqF@=_M!eFq^O8N& zq#Ifx>@JMR2X4V?&tv}l_U{ETU~N^XRla-$5T#KPTghK8ZPJAA;0wSk`&W^l-alIF z8@{|`!twV5qL83$1LR32&|#y}>9{r$5TH<~iyeJ6eSaMSUHJ7Bvp_V}{Vi7|%w-^j z9~f6~Q+J|q@7^ZqCb_!otpBE3bRHa4q&t_>{ID~AO^On%1T2C_OV31 zDfVSGt1*>KhW4By zU@UvihMmB$)V3PcecqAIZ`%laGXax_5uy%bl1z844?tYEBY)b?#E~S`i+*MF1bp6> z%m2Rv%d`FjN`#jH%N2bIC|20wc>wkZdIpf#WXNTLTAJqAs7eR6G(t8JfsOY%Dvwf8u0v_6Pj6M~sG7 zW5x)?#rMHhSr;C}L_iIHZuu?#vP5l74jGgHMXUNxUSKbHN`tZcc53WJM0*_k18&Lh zD?Ct)t5c2wvgUKtCBGqC0SKT~xhE}M>1=#}7_(&Uth&a%k>Hn9xaIf;dg#uitjq$}& zd_~WV04&zqI1Ki9aKoccu0mDqkU~(hG)jN@_bUeKg*af&#z{-)zkJB;%o}sIxGEiW zoVJZEFGh%iHgJ9$)-}VPV==YWUPR6`mHb_!8s4v=)nb`B90nq=3ioQhy_?Ydb|2&5xXvFd4a6rbB zIv@rCit=*;q{;Q29UVY!TLD{h)Mr!`)iJG{Qoo^yQY;)C{#^DJS;W_Rp{Na^vQDPq z4$D-YR--`g%{cb!J6h!NRXA+)=TIZs(9qBZsmk#UlNYCQ&95`s1cJP-W#*87wr)eb zQYa)9WCNugE4hun1JUXXC;m}G4!5Xve!^U1L?3Eqd{{?Y7sqF|xkx3!b3R-1u7fY6 zPubF~<3AVjH8CPKEp;VaWXkY)d3m9MPI3st@D2sc<~+iT4Z7g|I;c@?QO<=@F5r9y z{Eo|n9{Vn7ue3*B-sK-llJ)Bg(>DC6JTSvwJ@=%&?VLdY7>XcC2(+>5YDVRAh#X-_ zNSHn5MJ!OzY8zQv|DFhu+uE``s00fbGTHfNbox%`-c?8=Xq}w{R=YrXta#-b7+8r} z1`;nTejD7l6j;1y$XHax6y-G+d!f-~d2fKQ7wtypxtl7d35f>Zt>omr2%A(@<69sN z{n5mw84CIPo0EvSMLSHDyj+< z+)no?;_)uG*&3&MTEHg#0aX zcs%;k)(X3_R-QPV0I9SBJf#9A(D(NQ`Obi&=0NM{QF31AeV9>XNx&as{TCM>-IiA- zI~rlTB3+{qLHbWLk3-piU@HP*z8H`)=TD?XYb>*1#ALr?Iw3B z`r6Xlc}}soB|F!W?HB!c*WDsMW{Cr}IF$xOEur!Ja5z18=GXWk>h3}d@l#0P2J065 zE9KC|K}#W|i-K1#Cdu)|N4peW#kxv$g??C7@e5tqo-|orv_l@c4*k)pAza}->Jf|i zf*X!Oge#cZKq8W*ZQu?{JujMLt0G?Sm{vW%p&Bhz24ua3sy&wg_+%Bpa3md-_XpB^ z2DlA?V2>lQfbfbB#53-yuAc7FptLs!=@HED7Lw2alZLi8q%{EG ze968RHrux|=i5kg>&Q5NTi^`)^Y^cl)b%~qeM|2eoKup!IG6q z+<<=N3Ue|5-~0C?jEsz6z`82Dha&!?XBMC)SJHU;k(P3FtXe2$dFF!hTjlm}k~R+h z>`(oYG$Pn+cggm;fA43=DbN9u`0f9CWgc4OfFgdZ7CGS(HwOa^4yQbu#@Vmi>K=?2 z?JK1R;2V3znE)%?zIn;dr3e2C5mL4u2*WeW0jhzqO?b>FYH=PEgw z9PBRw!E?8L`d}?hb8;xGSmx6{zI+Vu6197{@b4m4bPu-ycIugxuQ8%TIt7{z#}eO) zhIQzbPd-AD9x68Z63kQvdZD9|(2*mt_8Vybh?hM|j5|Uaw3G=_Xu2P0yiM5cAO4qc z0)OQ^;d4HjOv%E3gGHt+`Y*3(v4D*j0!j_Q#NCt5i-3U8PkeGO>&g}84|u^1ez$x4 z&y%AKOq)cZY~NWTvOYSF?SypO%~#?LT}Sd#-5CBJ6|@mZ;bmU;7x;PBkCzK;;>$& zCM)_IF}sq)O0n>zSrbY`fXRM|*A0sH2xk46)-c6-E63t%Q%856F__(bl+p2U88sp= zRn(lSa7_;f)zS1>T9CT4jT>QuUEblZi=)%MGaD=7CoKKvh7_t)!h)^FN9BPA|G#Sh zW}d*aW@Gc(8lpf+9cE*nBJX$X71&XdvYaUU@grvZm6)7-J;e0s zg^TgioMwGQ$_+PF70d$}yJ_DDn(nCF<^eQnV)ouPA=|T@|7{~hkj{Y4mFlWwj0S-? zTS1sE;WrTC>k@u0CHqKaTA`N770>$pfX&{w6?B;AMZW~|%p?0;?xUP}zz7R@WS1<;shfX#WsZjWo3K1?6E-bICDUd9c-sztuBOz;(^ z>HJ!p`Km%GzieFhCSyv&Nu@}5I&~*~^Eb=cE*nHWHG9AwjvWg_vuBVYfdOR-w z!u$=hC)nWJe_q`G`bXgQRFVQMB=L@8iS$dA_&1MjOC;9uQAa+YV`HPd@z)wip*G8P zrZO(3a&$Ijbg^Agd*e$sr(5PW{GTX ze2@w%f~yD|L0-27#7viX2oOx^h!Lmx;m@U}4!4dY1M~efET1MYN2K-Zy1d^r3uv*7 zwx(LkT7g$v^^JfjPFQfq1k#_Im`paT^V7CPoEIis=x!zx8nM?m@GOn=DSmi{V_i`0 zj$sd}vSlv1>`leBA32t}5C79w0?G`$%piAVyVNPe0qMzjozeX|Q-!XHYINIUH?(7h zhc{>DD-5Qhza`2L{`M&m9>#}p?Zsrfuy9`#-nv5jN`csn&ymls#k5KZS45I3E}GZh zi{&!2)UE?XJ`Bd$OqKidAIBuk+zlE$?s28_8e)bCbSglHKyuZyqTHIO8t4%_J*O59 zM0AQbAC4RNy3c*_0;cV7)obV4aCJ}TRZf!HS3iqn7eMhLsGShr2C?db1O zLBfFh76jJw%!MvNAR6p*o$u{FeBL0@CLjjs8h0uTUQ@>x*23*X|EEC-+IYN%eD12k z?_(ugm*M^UwIOKvtN;d96oWLl)QK4v@tB*P`eqbl2Yg1Mt*Imqm;1*P|Ct2cE7Y|w zG+OkNxzEE*4!E1lIr}iBocPC$uBSF~qTwzpeP`a6i-Ky4^3_dT^1mYhI0DU-LvJ zL6-7YWD>Bl2I&!UkEdYUl&#)m|E}!AuyULmb9ox8Kc4}~lzsQ;YNStFK4O=bxrxe; zUa6;jIp(77RN#raoelrQhK?N|_WFyg_o~07U0fqx=_F7#!($)06|Du_0RT(`H^lzc z4}6_KEDQ9on~dV5+|kEOJL(-?T*~!)H*;rze`ms>b9(TklentnO?5+91DQCTuF^is zWI*t_1^BYBLvMW1L@nuA-)KX$^@>jvh{Rh0_j+k;Yx~O1UA^sA5d{(}l9V9a6oJm= zLAWa1v}b+D4V|QIn2cZBC!^R8>H3u?pE07Rk;doGaL3P67cz8kQJfi==A`oV`T=zP zQu&1O(2@fEm!J>G7Acgz z(FRIM&e{;4YQ0k6cCW1j=nOMIU~{Ttx51{biPF|{T#YeZU(>LXRdDC1+1W*PYN*aV z{li@Ag@m||Fd+c+QHRsj zn2eSr;|caw=a{Y<33Om=tooKVS8NVer13!J&GkK*uvXPyyN}j)JUU?%A`R~~wk{9{ zW+1h501imwZD7s@51o5D8^NZ&czDCY+GVs)dosp_GT6`kSv$lsuQT2 z|C=p2sbc539bvRb4H0utgwCqva%zbj+31cdJ#g)thOI*67WhA62$DP`_Bi_Se-2W z`zowZMg2N_dV+=@M7{QT1H$Hvc*Qa4Pgj6Q+Pxj?L2UU@LQK%gfRCs~@@f<7oPI(U9B&nH6ePmy}+=Y7H9f z7NREh1C^BXXBEPuCSwr}A|C8Mg?z@xj!;fLM)E0q&(&j@L*4e`1cXp%D8eZ$>|!M# zJM7=TpD`F$o;lTjh#Kv8U!TrK;g%MET`Fe;riOZlzy6hFb-+HAoRQ*ZAn>19ow+*e zdAIhF!K*!Id(WfhHT}$xZIB;a)h_i)l{RJ=Cja!#YZdgQkNw$csGr4tOjWiEv-EJ^ z!d$v%d)|4X$r%xOgyBYCs#N>Eq5x<0#^=}Ka?!TAw^dK_hcbYEukNhwGWSi9c3UHT zs^vhCdVF#kWsqCgZ`1!3#DzAQ!*&{&kBnUsOV7lBWtwq~^JuEn7#1*+WrN!&!_*{s_Zn=e-Q;SL_!2(;6G ze-`zz1ASAG7XH519=uQpQh*?WsRZ>om%Au5k-Y>x?Sc$*BAq{)ZKCwhUPk5;<3!5+ zA3hwB+4*G7ht81y#5@}i^AJ7;w^nj;PzM!QLn`4yk8Q>qMVr2o=s-k5&7Gbj?gj9k z>ivsz*F4{kKS#?lZXOKIUmEBY5%v&An0ZD}8yRY@!}3p3WMv}{Uu3-E>L5-zm>`s5 zEG}9LzMp6WsTjthmJ2a-++mG2l?WEDmJ|XO6Y*T`asv-_S+(zF)63Tkc(r+E@)hA~ zzZ(`USD`nijDn>3L&nxODT=zLbpnq6zrZM}{c6*2Z0)4uQ3D@!F2;uGQwrlv>)t3F z#l|cx5;{WP*H-(oMDiDC#BZLoxJj<_ptSO0<(BM(>Xr#~=5;DsIUow%Lp4=RSa%O- z7ZJjZjj>nrKeiXaCp5{NqBi(YRt7ptq1Luy$P(r=H)ORtQo;3H`^t}v?FZf!t&L^b`&#R;t^R8*O`Jb#g^?C|D3@!So?!l;A^5!Nk_YDP|; zla^7Hq`!%D>e=y0Z`|4MohLOsvJZ(jg7lNm!e9&cHE=y}AmaE--q39y{_9YYwt6GJ zU0b25QV?f^AK{^yb-9-NOAZVRpS`DCb$UE}#gG1VU!1%6uovNCCu2ZefA&=qfppw}y4C>#Z6`Yv5r|(O-q`f2 zc|iRZ)A{}+V?gz~{l3NzFf(}ozXtj1Ch3>Jthg_BZ%nYzg-qA1C)Zni1}a1iW}p{g zT8^+E(N>3$&&tjiX#MB(FRwn(YGDVS3J&~Olu~H=`1$kk4&%7XcUU+Vq2@h(?}|#I zTFDO;a1^!ea0iQpF&@UB%1Y{v6=(XNY6{|Zq87lN#7eazz{Kj(oeezs7RkxhalDx7>B!m+qycC=r&dTAkn zj1A8j>YggZEb9%t__NgZ-U!`=t|&Ra=A8mwuGp$WM#SIK%YY<8bRR0P^2?gHyoS8o zs6dj-JePsF7ZbDn@$T0*!M6WROp;cI73OP?P9l&l_lgEMZIz_?Kn5kIjr}z)FedsKFgo8a zhiIDyc^AcBhwRk5p)b`U_wL$HuzAEFTIbt;#Yi!HKv0u-wjzP1Nl3mRMYwTUo81;< zdp+uknkWAuaDn)PKiT*lqkM( zH)p&-SdH1&jKg{Z02o1OaTyY5*m%=-C?yZQ>xqaS$*KNCO?1-WEQk$(vC)(*-#k6N zgQN~@-24wduk%dnvsZe~v!4X?XgYH6Y05*rM?caY)^XDv8; z4e*6o<7Z5{$w@j^7N0vq{_|Ds|Al6(Oy-FgDoHOx3?>nz)MII`4yGM}X2?^smtAFz@e~$EeS~@8|6gfAl%?%$)O_^EhWv^sLBrx;raXO{YM0ld_Ae`R+pio0@#+T;GG~`feEsrN z6$(qPMthXP*zA<@fxsD+U>N*rv@$ibj*R7FG0v{%tvdrc`~(|qJ^e{pBbZ(DiOa9Z zfY+Ql^VWQxngZpCKV``++5y^{8jWX@2hvt}ibd+tVDXv1FVjbGVwo)D6RMl^61T|* zT=aS7P#I|E`a|IQfFArVNGBTxSbGJ`-+udvqpIR_Yk^x+X6&)F)7aP6vdux;0LwSd zSuT`0M^x6|G1~d3h3LSkwuz}&_LulAA>DDKTQnK{y0Z|IbKY>v9 znRWtSyTgA@L)3>LiAf#noXHawzLm8`gds*Sa{6f-%I&C8Z=aac?j{$^$> zIR!}mAOLejf)CKjl069k#8Z<68&s>E7_;ADmt=%vCQcnYU83g#33yQ>mhM6=jnpc& zmwWwjB;KOFlgt)4|NNFeNjn2~^e%ioU!HuA=6VD2d47w6n%g#l8r&c#%%9Dg+;vyc z)N1^*?4IDDbFTyQc1r0X?yAaoBD)L5`_LSlzy>=W(znIhcP)%S1C858X*RHV_W#!iWcgy5zrcpw4}86-WifhzlR+cRd}fAnWXlnVck! zY4vVMrV>cNx5#=Qo?>Iz=~N>P zQ=E~$25y9MVZSK|y8|{2HIl&s-diBj?5G9Pu z)M#F@X_gm*^w@Y@u_A^+*L!bcT8^(j!5I)^>duRNzu9EDNq0ZM2sooz_e?ekerF)E zLMN&2dHeLAqc8bxPXgK#qPyrq;rd*xA5qV1Vc4jzI9`t2I1){zsySB-Zs(ki$8yO8T)Kh(Dd9hYy@xFHkxd#Q@ z!Il{@@i+?4A3Pb)<@r^uHh^iVxOu}PdN>%g-MCJ4wJUY5no}_TyN}7GPd_ZSbab&L zOBb7tEY`(Rm*=hs;ol|ojf3Ls0|ySAu;8&=H9DH0Y+tucTq6@zN;qNdKs z564E3HkwReO}Yl&J|O4X#N_{)G$j`2>JB%kB>Ko}ByQa2c(E^Gd`^=vZdVB{2ROd? z>49ZbpxgPb?*lzV(HUvp{*^Z|oc5?Yi*IOYmcCJsuENZb2VRzwa$p51*dJdS{F~W~ zHz-_+q13@GQgLMH*(&^EnB8ij!=qY>gWeT_mG<4%R!@yvY?6H}+4?cDWSP2@l=`D7 z{wT}1F(@K1~E0t~))x9H@IE z&SdQGJs~$`PGV*)=xJ^z9vC#{pIIxU2Qm*bzHw`)g^}a)OR^n--r%1){pu0kD{_tS z(+>|gH89Cgc&wUxSg_oFuwv5X7Ldi2dnOzhK9}@^j{9dyKMKJWn3G$ia*-)R6y9Hb z#Klymsm{cP?S3FS=a)XOD?h0A(bSidOlP;md@YKnHYP#s%Z?+L;5toZf%JVuqpMrW z4+hu>XIFXt{y}0W<$CKr{ENT+ef=rjotNEv-W?nUl}OjU47yJ31$7t-5AJl{fC)l} zVusNXXUEC9MJr}m9qw3KHEc0h>A6WeoZzLf?e`-?tWRJo6^!5~H%SeCtI@c9MVf|? zkBWT~q1(L77Mu zyh^!Jv?kdz*ud||s7?E|ZbeRZJ(ECAyz+B(lpjcbdQ?k$uoE4IYUxVhQ+PEE%eq+h zxwDvOI(G!HDFnkhA0NMk^sd};@spw6L7U=#^P0vPW9vw{`vEgg+tK8Lo$vj6DST5wtDkko!m@+OGr9>6oxOC1sUWYcA zo_ZVMjMdajevT8`#Tm1jIVv)x7hs;aH7EblH2XuI)kN$vG9p%^!j!z#1t0+rkt;~L z^{)K|bnR>kBl`>=wJ-2j;byjGN_?cU89);K4M>P0l zNk5nT)Lqf%gl1HLA0pXw5w$5TLp}0l3Js*$*r@u$JmRveS{WH8z0BYMKg68pN-6zb zaH-C8`hkr?-}g0X#3RYZ#%50PRZ2nc$Vv3-f#Q&yobc80CU-?!=e@{G*}GDb3TdM7 z@lE;L%RH4 z*kPJh&FZJ|UISrXb|(wDnXMAeMd9ns=rh;OJaa#T;nno|HSKsWypG~}{n}Q9)p1fR zd1EXTBIh9i@<6Wvw^l3+v9YNTMrb1(WFWDOOOh0FK3bM}D%}pA!09H!n*L=?R&@qo zE-PvSqtH?7K^s;|8|K4fK1Z7cDFfsS`lOC>*ptv!4}NF`S4&x?Ad4Io>;=javn;Qc zl~D^ClQx;E-;(qkWW16FIGow|uQIr7YG_FN$)|e6Wf6k-d43kwYm6g07HE&d1 z@1x@D(bkQ_s#gYfsHHfV-APq=k<+6+kdT_s@A~{n>)Q0>vtwNbSNWR{E(PVB0D$8v z2K7#)mU%Y8WS{Z*d~=W2Y59AixR^ZikAVj1G?}fRI6Oy6?dsB{n&|5 zVc?d^>TftdLqjN`XH?;GAMCCBi#I`houas=+R?O4@_O18u!5eIft}-lR=|h6qN}rL z5g2Rm+=#*3j(gvheqWhl8dzj3SW zH_o&w+CFO^E-zeV3EOu4!87O0)-Eou9l_mxQ&`JR9{9`0YTe>wSK`7jMsXxqz&v`f z;i!5SH{rQ*anchN%tO4teR^GWel9hJ^MVnME6J(;M}Osd(!F_W+ML2@S3jDj#7SNw z6LxVRi6x&@(Zyts^ZWxYXU>*a+h5`H#}Q$FPGY9v!~2w-JHt5Y*?_Z z6`w2q^JaW=i0znViS_0NdBfYy4YD`K6Stb~tyy3=A+X$9*51`-BE3i8Y7A64Q!q6kP*PbGlcgM z70`v02zjZ4tTkWGHT|37G4h^u)sgs2T%GUWIbgTDY>VY~WsOA-?KRIc`ptE(y=jfV zy8Wq1=CPd~i>#vl)o7vh5QwM-UT*-MAl4Eu+3>VM0qhYr?_6 zz<_$eCZ-Aj-sl$q#<>YO+k>)g*x_aT;ebU76)-0aj}Gp2OUk9?NCxvKOm zS8pdTL`T1V?H^*v@W;YhZb=iKvH)9b$VJTWW_ajVzR)SyeX;aW^$v?Wqqytlvu>;3 zDihfjt-79Q7^=BxS-GH7tHm?A1HYMg(s7TRk}n~zoU!{2AP#Q7S^rHWSCZSdpZok} zbK7yvsWQ>}6m`!iM^NRsc4IoXU{Sf4zUC%iWedMS3;|=A+Ot|FpwL9w$rx$a`{(n( z%|QIN)jIltS)P)sa?{bdAa3qhamu2H(uI#!jP$*a=XX_7WtvBi@!c9A%f;uGB=ikr z{MD}EdK*?hbm^MA#^y<5I^e1Yq_Hgt0Oy3?kZ4r~U#_bxQ|xY;9#8CreXdIa(qUwy zVWvp>Z69h_(A|^k>bag9tT}c}Yw2jnmD-os)G9}+; zD9IEba+4?x)ujMX%sNY5GO9li9weJSNE0E#lOSD`747JOG9ZRwOunL#{-|^`5J3Zw ziz?Cjga&R!__P4d(F(PJ|41#4b*Yjiig0!)&re`=Qm+|D#nBK!qUJSpq^Kga8r-cItKnZJGj|jw`Ils54@Ct Ad;kCd From cb58ca63d482bd38363057f4e121b67fe7997747 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 26 Oct 2025 18:21:24 +0100 Subject: [PATCH 005/430] Review formating #5306 --- examples/models/models_decals.c | 172 ++++++++++++++++---------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/examples/models/models_decals.c b/examples/models/models_decals.c index 2786168f6..6fa2bd75b 100644 --- a/examples/models/models_decals.c +++ b/examples/models/models_decals.c @@ -71,46 +71,46 @@ int main(void) // Load character model Model model = LoadModel("resources/models/obj/character.obj"); - + // Apply character skin Texture2D modelTexture = LoadTexture("resources/models/obj/character_diffuse.png"); SetTextureFilter(modelTexture, TEXTURE_FILTER_BILINEAR); model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = modelTexture; - + BoundingBox modelBBox = GetMeshBoundingBox(model.meshes[0]); // Get mesh bounding box - + camera.target = Vector3Lerp(modelBBox.min, modelBBox.max, 0.5f); camera.position = Vector3Scale(modelBBox.max, 1.0f); camera.position.x *= 0.1f; - + float modelSize = fminf( fminf(fabsf(modelBBox.max.x - modelBBox.min.x), fabsf(modelBBox.max.y - modelBBox.min.y)), fabsf(modelBBox.max.z - modelBBox.min.z)); - + camera.position = (Vector3){ 0.0f, modelBBox.max.y*1.2f, modelSize*3.0f }; - + float decalSize = modelSize*0.25f; float decalOffset = 0.01f; - + Model placementCube = LoadModelFromMesh(GenMeshCube(decalSize, decalSize, decalSize)); placementCube.materials[0].maps[0].color = LIME; - + Material decalMaterial = LoadMaterialDefault(); decalMaterial.maps[0].color = YELLOW; - + Image decalImage = LoadImage("resources/raylib_logo.png"); ImageResizeNN(&decalImage, decalImage.width/4, decalImage.height/4); Texture decalTexture = LoadTextureFromImage(decalImage); UnloadImage(decalImage); - + SetTextureFilter(decalTexture, TEXTURE_FILTER_BILINEAR); decalMaterial.maps[MATERIAL_MAP_DIFFUSE].texture = decalTexture; decalMaterial.maps[MATERIAL_MAP_DIFFUSE].color = RAYWHITE; - + bool showModel = true; Model decalModels[MAX_DECALS] = { 0 }; int decalCount = 0; - + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- @@ -151,19 +151,21 @@ int main(void) if (meshHitInfo.hit) collision = meshHitInfo; } - + // Add decal to mesh on hit point if (collision.hit && IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && (decalCount < MAX_DECALS)) { // Create the transformation to project the decal Vector3 origin = Vector3Add(collision.point, Vector3Scale(collision.normal, 1.0f)); Matrix splat = MatrixLookAt(collision.point, origin, (Vector3){ 0.0f, 1.0f, 0.0f }); - + // Spin the placement around a bit splat = MatrixMultiply(splat, MatrixRotateZ(DEG2RAD*((float)GetRandomValue(-180, 180)))); - + Mesh decalMesh = GenMeshDecal(model, splat, decalSize, decalOffset); - if (decalMesh.vertexCount > 0) { + + if (decalMesh.vertexCount > 0) + { int decalIndex = decalCount++; decalModels[decalIndex] = LoadModelFromMesh(decalMesh); decalModels[decalIndex].materials[0].maps[0] = decalMaterial.maps[0]; @@ -179,7 +181,7 @@ int main(void) BeginMode3D(camera); // Draw the model at the origin and default scale if (showModel) DrawModel(model, (Vector3){0.0f, 0.0f, 0.0f}, 1.0f, WHITE); - + // Draw the decal models for (int i = 0; i < decalCount; i++) DrawModel(decalModels[i], (Vector3){0}, 1.0f, WHITE); @@ -199,43 +201,45 @@ int main(void) float x0 = GetScreenWidth() - 300; float x1 = x0 + 100; float x2 = x1 + 100; - + DrawText("Vertices", x1, yPos, 10, LIME); DrawText("Triangles", x2, yPos, 10, LIME); yPos += 15; - + int vertexCount = 0; int triangleCount = 0; - + for (int i = 0; i < model.meshCount; i++) { vertexCount += model.meshes[i].vertexCount; triangleCount += model.meshes[i].triangleCount; } - + DrawText("Main model", x0, yPos, 10, LIME); DrawText(TextFormat("%d", vertexCount), x1, yPos, 10, LIME); DrawText(TextFormat("%d", triangleCount), x2, yPos, 10, LIME); yPos += 15; - + for (int i = 0; i < decalCount; i++) { - if (i == 20) { + if (i == 20) + { DrawText("...", x0, yPos, 10, LIME); yPos += 15; } - - if (i < 20) { + + if (i < 20) + { DrawText(TextFormat("Decal #%d", i+1), x0, yPos, 10, LIME); DrawText(TextFormat("%d", decalModels[i].meshes[0].vertexCount), x1, yPos, 10, LIME); DrawText(TextFormat("%d", decalModels[i].meshes[0].triangleCount), x2, yPos, 10, LIME); yPos += 15; } - + vertexCount += decalModels[i].meshes[0].vertexCount; triangleCount += decalModels[i].meshes[0].triangleCount; } - + DrawText("TOTAL", x0, yPos, 10, LIME); DrawText(TextFormat("%d", vertexCount), x1, yPos, 10, LIME); DrawText(TextFormat("%d", triangleCount), x2, yPos, 10, LIME); @@ -243,23 +247,18 @@ int main(void) DrawText("Hold RMB to move camera", 10, 430, 10, GRAY); DrawText("(c) Character model and texture from kenney.nl", screenWidth - 260, screenHeight - 20, 10, GRAY); - + Rectangle rect = (Rectangle){ 10, screenHeight - 100, 100, 60 }; - - if (Button(rect, showModel ? "Hide Model" : "Show Model")) { - showModel = !showModel; - } - + + if (Button(rect, showModel ? "Hide Model" : "Show Model")) showModel = !showModel; + rect.x += rect.width + 10; - - if (Button(rect, "Clear Decals")) { - for (int i = 0; i < decalCount; i++) - { - UnloadModel(decalModels[i]); - } + + if (Button(rect, "Clear Decals")) + { + for (int i = 0; i < decalCount; i++) UnloadModel(decalModels[i]); decalCount = 0; } - DrawFPS(10, 10); @@ -271,15 +270,13 @@ int main(void) //-------------------------------------------------------------------------------------- UnloadModel(model); UnloadTexture(modelTexture); - - for (int i = 0; i < decalCount; i++) { - UnloadModel(decalModels[i]); - } + + // Unload decal models + for (int i = 0; i < decalCount; i++) UnloadModel(decalModels[i]); UnloadTexture(decalTexture); - - // Free the data for decal generation - FreeDecalMeshData(); + + FreeDecalMeshData(); // Free the data for decal generation CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- @@ -298,21 +295,21 @@ static void AddTriangleToMeshBuilder(MeshBuilder *mb, Vector3 vertices[3]) { int newVertexCapacity = (1 + (mb->vertexCapacity/256))*256; Vector3 *newVertices = (Vector3 *)MemAlloc(newVertexCapacity*sizeof(Vector3)); - + if (mb->vertexCapacity > 0) { memcpy(newVertices, mb->vertices, mb->vertexCount*sizeof(Vector3)); MemFree(mb->vertices); } - + mb->vertices = newVertices; mb->vertexCapacity = newVertexCapacity; } - + // Add 3 vertices int index = mb->vertexCount; mb->vertexCount += 3; - + for (int i = 0; i < 3; i++) mb->vertices[index+i] = vertices[i]; } @@ -328,7 +325,7 @@ static void FreeMeshBuilder(MeshBuilder *mb) static Mesh BuildMesh(MeshBuilder *mb) { Mesh outMesh = { 0 }; - + outMesh.vertexCount = mb->vertexCount; outMesh.triangleCount = mb->vertexCount/3; outMesh.vertices = MemAlloc(outMesh.vertexCount*3*sizeof(float)); @@ -339,16 +336,16 @@ static Mesh BuildMesh(MeshBuilder *mb) outMesh.vertices[3*i+0] = mb->vertices[i].x; outMesh.vertices[3*i+1] = mb->vertices[i].y; outMesh.vertices[3*i+2] = mb->vertices[i].z; - + if (mb->uvs) { outMesh.texcoords[2*i+0] = mb->uvs[i].x; outMesh.texcoords[2*i+1] = mb->uvs[i].y; } } - + UploadMesh(&outMesh, false); - + return outMesh; } @@ -364,22 +361,24 @@ static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s) return position; } +// Generate mesh decals for provided model static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, float decalOffset) { // We're going to use these to build up our decal meshes // They'll resize automatically as we go, we'll free them at the end static MeshBuilder meshBuilders[2] = { 0 }; - + // Ugly way of telling us to free the static MeshBuilder data - if (inputModel.meshCount == -1) { + if (inputModel.meshCount == -1) + { FreeMeshBuilder(&meshBuilders[0]); FreeMeshBuilder(&meshBuilders[1]); - return (Mesh){0}; + return (Mesh){ 0 }; } - + // We're going to need the inverse matrix Matrix invProj = MatrixInvert(projection); - + // Reset the mesh builders meshBuilders[0].vertexCount = 0; meshBuilders[1].vertexCount = 0; @@ -395,7 +394,7 @@ static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, f for (int tri = 0; tri < mesh.triangleCount; tri++) { Vector3 vertices[3] = { 0 }; - + // The way we calculate the vertices of the mesh triangle // depend on whether the mesh vertices are indexed or not if (mesh.indices == 0) @@ -420,7 +419,7 @@ static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, f }; } } - + // Transform all 3 vertices of the triangle // and check if they are inside our decal box int insideCount = 0; @@ -428,13 +427,13 @@ static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, f { // To projection space Vector3 v = Vector3Transform(vertices[i], projection); - + if ((fabsf(v.x) < decalSize) || (fabsf(v.y) <= decalSize) || (fabsf(v.z) <= decalSize)) insideCount++; - + // We need to keep the transformed vertex vertices[i] = v; } - + // If any of them are inside, we add the triangle - we'll clip it later if (insideCount > 0) AddTriangleToMeshBuilder(&meshBuilders[mbIndex], vertices); } @@ -454,15 +453,15 @@ static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, f { // Swap current model builder (so we read from the one we just wrote to) mbIndex = 1 - mbIndex; - + MeshBuilder *inMesh = &meshBuilders[1 - mbIndex]; MeshBuilder *outMesh = &meshBuilders[mbIndex]; - + // Reset write builder outMesh->vertexCount = 0; - + float s = 0.5f*decalSize; - + for (int i = 0; i < inMesh->vertexCount; i += 3) { Vector3 nV1, nV2, nV3, nV4; @@ -515,7 +514,7 @@ static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, f nV3 = ClipSegment(inMesh->vertices[i + 2], nV1, planes[face], s); nV4 = ClipSegment(inMesh->vertices[i + 2], nV2, planes[face], s); } - + AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV4, nV3, nV2}); } break; @@ -529,7 +528,7 @@ static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, f nV3 = ClipSegment(nV1, inMesh->vertices[i + 2], planes[face], s); AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); } - + if (!v2Out) { nV1 = inMesh->vertices[i + 1]; @@ -537,7 +536,7 @@ static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, f nV3 = ClipSegment(nV1, inMesh->vertices[i], planes[face], s); AddTriangleToMeshBuilder(outMesh, (Vector3[3]){nV1, nV2, nV3}); } - + if (!v3Out) { nV1 = inMesh->vertices[i + 2]; @@ -559,21 +558,21 @@ static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, f if (theMesh->vertexCount > 0) { theMesh->uvs = (Vector2 *)MemAlloc(sizeof(Vector2)*theMesh->vertexCount); - + for (int i = 0; i < theMesh->vertexCount; i++) { // Calculate the UVs based on the projected coords // They are clipped to (-decalSize .. decalSize) and we want them (0..1) theMesh->uvs[i].x = (theMesh->vertices[i].x/decalSize + 0.5f); theMesh->uvs[i].y = (theMesh->vertices[i].y/decalSize + 0.5f); - + // Tiny nudge in the normal direction so it renders properly over the mesh theMesh->vertices[i].z -= decalOffset; - + // From projection space to world space theMesh->vertices[i] = Vector3Transform(theMesh->vertices[i], invProj); } - + // Decal model data ready, create the mesh and return it return BuildMesh(theMesh); } @@ -584,24 +583,25 @@ static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, f } } -static bool Button(Rectangle rec, char *label) +// Button UI element +static bool Button(Rectangle rec, const char *label) { Color bgColor = GRAY; bool pressed = false; - if (CheckCollisionPointRec(GetMousePosition(), rec)) { + + if (CheckCollisionPointRec(GetMousePosition(), rec)) + { bgColor = LIGHTGRAY; - if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { - pressed = true; - } + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) pressed = true; } - + DrawRectangleRec(rec, bgColor); DrawRectangleLinesEx(rec, 2.0f, DARKGRAY); - + float fontSize = 10.0f; float textWidth = MeasureText(label, fontSize); - + DrawText(label, (int)(rec.x + rec.width*0.5f - textWidth*0.5f), (int)(rec.y + rec.height*0.5f - fontSize*0.5f), fontSize, DARKGRAY); - + return pressed; -} \ No newline at end of file +} From 0fbc4272d0b6e777a19c212027b4825c90f55f77 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 26 Oct 2025 18:22:23 +0100 Subject: [PATCH 006/430] Remove trailing spaces --- examples/examples_template.c | 2 +- .../shaders/shaders_normalmap_rendering.c | 2 +- examples/shapes/shapes_bullet_hell.c | 2 +- examples/shapes/shapes_starfield_effect.c | 36 +++++++++---------- src/platforms/rcore_desktop_sdl.c | 2 +- src/platforms/rcore_desktop_win32.c | 2 +- src/rcore.c | 2 +- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/examples/examples_template.c b/examples/examples_template.c index 39a19351d..211749e82 100644 --- a/examples/examples_template.c +++ b/examples/examples_template.c @@ -36,7 +36,7 @@ 10. Have fun! - The following files must be updated when adding a new example, + The following files must be updated when adding a new example, but it can be automatically done using the raylib provided tool: rexm So, no worries if just the .c/.png are provided when adding the example. diff --git a/examples/shaders/shaders_normalmap_rendering.c b/examples/shaders/shaders_normalmap_rendering.c index 4859ec813..07399a78e 100644 --- a/examples/shaders/shaders_normalmap_rendering.c +++ b/examples/shaders/shaders_normalmap_rendering.c @@ -49,7 +49,7 @@ int main(void) camera.projection = CAMERA_PERSPECTIVE; // Load basic normal map lighting shader - Shader shader = LoadShader(TextFormat("resources/shaders/glsl%i/normalmap.vs", GLSL_VERSION), + Shader shader = LoadShader(TextFormat("resources/shaders/glsl%i/normalmap.vs", GLSL_VERSION), TextFormat("resources/shaders/glsl%i/normalmap.fs", GLSL_VERSION)); // Get some required shader locations diff --git a/examples/shapes/shapes_bullet_hell.c b/examples/shapes/shapes_bullet_hell.c index 235df4d16..95abc4dc9 100644 --- a/examples/shapes/shapes_bullet_hell.c +++ b/examples/shapes/shapes_bullet_hell.c @@ -197,7 +197,7 @@ int main(void) bullets[i].color); } } - } + } else { // Draw bullets using DrawCircle(), less performant diff --git a/examples/shapes/shapes_starfield_effect.c b/examples/shapes/shapes_starfield_effect.c index 8dd90fe0b..ca409c926 100644 --- a/examples/shapes/shapes_starfield_effect.c +++ b/examples/shapes/shapes_starfield_effect.c @@ -32,18 +32,18 @@ int main(void) const int screenHeight = 450; InitWindow(screenWidth, screenHeight, "raylib [shapes] example - starfield effect"); - + Color bgColor = ColorLerp(DARKBLUE, BLACK, 0.69f); - + // Speed at which we fly forward float speed = 10.0f/9.0f; - + // We're either drawing lines or circles bool drawLines = true; - + Vector3 stars[STAR_COUNT] = { 0 }; Vector2 starsScreenPos[STAR_COUNT] = { 0 }; - + // Setup the stars with a random position for (int i = 0; i < STAR_COUNT; i++) { @@ -65,22 +65,22 @@ int main(void) if ((int)mouseMove != 0) speed += 2.0f*mouseMove/9.0f; if (speed < 0.0f) speed = 0.1f; else if (speed > 2.0f) speed = 2.0f; - + // Toggle lines / points with space bar if (IsKeyPressed(KEY_SPACE)) drawLines = !drawLines; - + float dt = GetFrameTime(); - for (int i = 0; i < STAR_COUNT; i++) + for (int i = 0; i < STAR_COUNT; i++) { // Update star's timer stars[i].z -= dt*speed; - + // Calculate the screen position starsScreenPos[i] = (Vector2){ screenWidth*0.5f + stars[i].x/stars[i].z, screenHeight*0.5f + stars[i].y/stars[i].z, }; - + // If the star is too old, or offscreen, it dies and we make a new random one if ((stars[i].z < 0.0f) || (starsScreenPos[i].x < 0) || (starsScreenPos[i].y < 0.0f) || (starsScreenPos[i].x > screenWidth) || (starsScreenPos[i].y > screenHeight)) @@ -97,14 +97,14 @@ int main(void) BeginDrawing(); ClearBackground(bgColor); - + for (int i = 0; i < STAR_COUNT; i++) { if (drawLines) { // Get the time a little while ago for this star, but clamp it float t = Clamp(stars[i].z + 1.0f/32.0f, 0.0f, 1.0f); - + // If it's different enough from the current time, we proceed if ((t - stars[i].z) > 1e-3) { @@ -113,26 +113,26 @@ int main(void) screenWidth*0.5f + stars[i].x/t, screenHeight*0.5f + stars[i].y/t, }; - + // Draw a line connecting the old point to the current point DrawLineV(startPos, starsScreenPos[i], RAYWHITE); } } - else + else { // Make the radius grow as the star ages float radius = Lerp(stars[i].z, 1.0f, 5.0f); - + // Draw the circle DrawCircleV(starsScreenPos[i], radius, RAYWHITE); } } - + DrawText(TextFormat("[MOUSE WHEEL] Current Speed: %.0f", 9.0f*speed/2.0f), 10, 40, 20, RAYWHITE); DrawText(TextFormat("[SPACE] Current draw mode: %s", drawLines ? "Lines" : "Circles"), 10, 70, 20, RAYWHITE); - + DrawFPS(10, 10); - + EndDrawing(); //---------------------------------------------------------------------------------- } diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index e135cd848..03bad80f9 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -1243,7 +1243,7 @@ void EnableCursor(void) SDL_SetRelativeMouseMode(SDL_FALSE); #if defined(USING_VERSION_SDL3) - // NOTE: SDL_ShowCursor() has been split into three functions: + // NOTE: SDL_ShowCursor() has been split into three functions: // SDL_ShowCursor(), SDL_HideCursor(), and SDL_CursorVisible() SDL_ShowCursor(); #else diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 5b67c72b0..1dadd5586 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -1139,7 +1139,7 @@ void ShowCursor(void) // Hides mouse cursor void HideCursor(void) { - // NOTE: We use SetCursor() instead of ShowCursor() because + // NOTE: We use SetCursor() instead of ShowCursor() because // it makes it easy to only hide the cursor while it's inside the client area SetCursor(NULL); CORE.Input.Mouse.cursorHidden = true; diff --git a/src/rcore.c b/src/rcore.c index e4628e77c..d06e3089b 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -709,7 +709,7 @@ void InitWindow(int width, int height, const char *title) //-------------------------------------------------------------- // Initialize rlgl default data (buffers and shaders) - // NOTE: CORE.Window.currentFbo.width and CORE.Window.currentFbo.height not used, just stored as globals in rlgl + // NOTE: Current fbo size stored as globals in rlgl for convenience rlglInit(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); isGpuReady = true; // Flag to note GPU has been initialized successfully From 70f491169836d5cbdcba943d3d49ed83299ca05f Mon Sep 17 00:00:00 2001 From: JordSant <77529699+JordSant@users.noreply.github.com> Date: Sun, 26 Oct 2025 18:24:19 +0100 Subject: [PATCH 007/430] [examples] Added `shaders_color_correction` (#5307) * [examples] Added `shaders_color_correction` * Add _CRT_SECURE_NO_WARNINGS to VS Project * Added to makefiles --- examples/Makefile | 1 + examples/Makefile.Web | 9 + examples/README.md | 5 +- examples/examples_list.txt | 1 + examples/shaders/raygui.h | 5757 +++++++++++++++++ examples/shaders/resources/LICENSE.md | 5 +- examples/shaders/resources/cat.png | Bin 0 -> 388467 bytes examples/shaders/resources/mandrill.png | Bin 0 -> 197452 bytes examples/shaders/resources/parrots.png | Bin 0 -> 294960 bytes .../shaders/glsl100/color_correction.fs | 34 + .../shaders/glsl120/color_correction.fs | 31 + .../shaders/glsl330/color_correction.fs | 34 + examples/shaders/shaders_color_correction.c | 149 + examples/shaders/shaders_color_correction.png | Bin 0 -> 311867 bytes .../examples/shaders_color_correction.vcxproj | 569 ++ projects/VS2022/raylib.sln | 27 + 16 files changed, 6619 insertions(+), 3 deletions(-) create mode 100644 examples/shaders/raygui.h create mode 100644 examples/shaders/resources/cat.png create mode 100644 examples/shaders/resources/mandrill.png create mode 100644 examples/shaders/resources/parrots.png create mode 100644 examples/shaders/resources/shaders/glsl100/color_correction.fs create mode 100644 examples/shaders/resources/shaders/glsl120/color_correction.fs create mode 100644 examples/shaders/resources/shaders/glsl330/color_correction.fs create mode 100644 examples/shaders/shaders_color_correction.c create mode 100644 examples/shaders/shaders_color_correction.png create mode 100644 projects/VS2022/examples/shaders_color_correction.vcxproj diff --git a/examples/Makefile b/examples/Makefile index 9ae8bb480..c601c72b2 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -655,6 +655,7 @@ SHADERS = \ shaders/shaders_ascii_rendering \ shaders/shaders_basic_lighting \ shaders/shaders_basic_pbr \ + shaders/shaders_color_correction \ shaders/shaders_custom_uniform \ shaders/shaders_deferred_rendering \ shaders/shaders_depth_rendering \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 0e9b9f92f..99be781ac 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -655,6 +655,7 @@ SHADERS = \ shaders/shaders_ascii_rendering \ shaders/shaders_basic_lighting \ shaders/shaders_basic_pbr \ + shaders/shaders_color_correction \ shaders/shaders_custom_uniform \ shaders/shaders_deferred_rendering \ shaders/shaders_depth_rendering \ @@ -1261,6 +1262,14 @@ shaders/shaders_basic_pbr: shaders/shaders_basic_pbr.c --preload-file shaders/resources/road_mra.png@resources/road_mra.png \ --preload-file shaders/resources/road_n.png@resources/road_n.png +shapes/shapes_recursive_tree: shaders/shaders_color_correction.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ + --preload-file shaders/resources/shaders/glsl100/color_correction.fs@resources/shaders/glsl100/color_correction.fs \ + --preload-file shaders/resources/parrots.png@resources/parrots.png \ + --preload-file shaders/resources/cat.png@resources/cat.png \ + --preload-file shaders/resources/mandrill.png@resources/mandrill.png \ + --preload-file shaders/resources/fudesumi.png@resources/fudesumi.png + shaders/shaders_custom_uniform: shaders/shaders_custom_uniform.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file shaders/resources/models/barracks.obj@resources/models/barracks.obj \ diff --git a/examples/README.md b/examples/README.md index bbb0a0388..de28c931f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -17,7 +17,7 @@ You may find it easier to use than other toolchains, especially when it comes to - `zig build [module]` to compile all examples for a module (e.g. `zig build core`) - `zig build [example]` to compile _and run_ a particular example (e.g. `zig build core_basic_window`) -## EXAMPLES COLLECTION [TOTAL: 187] +## EXAMPLES COLLECTION [TOTAL: 188] ### category: core [45] @@ -196,7 +196,7 @@ Examples using raylib models functionality, including models loading/generation | [models_basic_voxel](models/models_basic_voxel.c) | models_basic_voxel | ⭐⭐☆☆ | 5.5 | 5.5 | [Tim Little](https://github.com/timlittle) | | [models_rotating_cube](models/models_rotating_cube.c) | models_rotating_cube | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Jopestpe](https://github.com/jopestpe) | -### category: shaders [31] +### category: shaders [32] Examples using raylib shaders functionality, including shaders loading, parameters configuration and drawing using them (model shaders and postprocessing shaders). This functionality is directly provided by raylib [rlgl](../src/rlgl.c) module. @@ -233,6 +233,7 @@ Examples using raylib shaders functionality, including shaders loading, paramete | [shaders_rounded_rectangle](shaders/shaders_rounded_rectangle.c) | shaders_rounded_rectangle | ⭐⭐⭐☆ | 5.5 | 5.5 | [Anstro Pleuton](https://github.com/anstropleuton) | | [shaders_depth_rendering](shaders/shaders_depth_rendering.c) | shaders_depth_rendering | ⭐⭐⭐☆ | 5.6-dev | 5.6-dev | [Luís Almeida](https://github.com/luis605) | | [shaders_mandelbrot_set](shaders/shaders_mandelbrot_set.c) | shaders_mandelbrot_set | ⭐⭐⭐☆ | 5.6 | 5.6 | [Jordi Santonja](https://github.com/JordSant) | +| [shaders_color_correction](shaders/shaders_color_correction.c) | shaders_color_correction | ⭐⭐☆☆ | 5.6 | 5.6 | [Jordi Santonja](https://github.com/JordSant) | ### category: audio [8] diff --git a/examples/examples_list.txt b/examples/examples_list.txt index 4805a37ec..f24cb9905 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -163,6 +163,7 @@ shaders;shaders_texture_outline;★★★☆;4.0;4.0;2021;2025;"Serenity Skiff"; shaders;shaders_texture_waves;★★☆☆;2.5;3.7;2019;2025;"Anata";@anatagawa shaders;shaders_julia_set;★★★☆;2.5;4.0;2019;2025;"Josh Colclough";@joshcol9232 shaders;shaders_mandelbrot_set;★★★☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant +shaders;shaders_color_correction;★★☆☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant shaders;shaders_eratosthenes_sieve;★★★☆;2.5;4.0;2019;2025;"ProfJski";@ProfJski shaders;shaders_fog_rendering;★★★☆;2.5;3.7;2019;2025;"Chris Camacho";@chriscamacho shaders;shaders_simple_mask;★★☆☆;2.5;3.7;2019;2025;"Chris Camacho";@chriscamacho diff --git a/examples/shaders/raygui.h b/examples/shaders/raygui.h new file mode 100644 index 000000000..a3fc51f0f --- /dev/null +++ b/examples/shaders/raygui.h @@ -0,0 +1,5757 @@ +/******************************************************************************************* +* +* raygui v4.5-dev - A simple and easy-to-use immediate-mode gui library +* +* DESCRIPTION: +* raygui is a tools-dev-focused immediate-mode-gui library based on raylib but also +* available as a standalone library, as long as input and drawing functions are provided. +* +* FEATURES: +* - Immediate-mode gui, minimal retained data +* - +25 controls provided (basic and advanced) +* - Styling system for colors, font and metrics +* - Icons supported, embedded as a 1-bit icons pack +* - Standalone mode option (custom input/graphics backend) +* - Multiple support tools provided for raygui development +* +* POSSIBLE IMPROVEMENTS: +* - Better standalone mode API for easy plug of custom backends +* - Externalize required inputs, allow user easier customization +* +* LIMITATIONS: +* - No editable multi-line word-wraped text box supported +* - No auto-layout mechanism, up to the user to define controls position and size +* - Standalone mode requires library modification and some user work to plug another backend +* +* NOTES: +* - WARNING: GuiLoadStyle() and GuiLoadStyle{Custom}() functions, allocate memory for +* font atlas recs and glyphs, freeing that memory is (usually) up to the user, +* no unload function is explicitly provided... but note that GuiLoadStyleDefault() unloads +* by default any previously loaded font (texture, recs, glyphs). +* - Global UI alpha (guiAlpha) is applied inside GuiDrawRectangle() and GuiDrawText() functions +* +* CONTROLS PROVIDED: +* # Container/separators Controls +* - WindowBox --> StatusBar, Panel +* - GroupBox --> Line +* - Line +* - Panel --> StatusBar +* - ScrollPanel --> StatusBar +* - TabBar --> Button +* +* # Basic Controls +* - Label +* - LabelButton --> Label +* - Button +* - Toggle +* - ToggleGroup --> Toggle +* - ToggleSlider +* - CheckBox +* - ComboBox +* - DropdownBox +* - TextBox +* - ValueBox --> TextBox +* - Spinner --> Button, ValueBox +* - Slider +* - SliderBar --> Slider +* - ProgressBar +* - StatusBar +* - DummyRec +* - Grid +* +* # Advance Controls +* - ListView +* - ColorPicker --> ColorPanel, ColorBarHue +* - MessageBox --> Window, Label, Button +* - TextInputBox --> Window, Label, TextBox, Button +* +* It also provides a set of functions for styling the controls based on its properties (size, color). +* +* +* RAYGUI STYLE (guiStyle): +* raygui uses a global data array for all gui style properties (allocated on data segment by default), +* when a new style is loaded, it is loaded over the global style... but a default gui style could always be +* recovered with GuiLoadStyleDefault() function, that overwrites the current style to the default one +* +* The global style array size is fixed and depends on the number of controls and properties: +* +* static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)]; +* +* guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB +* +* Note that the first set of BASE properties (by default guiStyle[0..15]) belong to the generic style +* used for all controls, when any of those base values is set, it is automatically populated to all +* controls, so, specific control values overwriting generic style should be set after base values. +* +* After the first BASE set we have the EXTENDED properties (by default guiStyle[16..23]), those +* properties are actually common to all controls and can not be overwritten individually (like BASE ones) +* Some of those properties are: TEXT_SIZE, TEXT_SPACING, LINE_COLOR, BACKGROUND_COLOR +* +* Custom control properties can be defined using the EXTENDED properties for each independent control. +* +* TOOL: rGuiStyler is a visual tool to customize raygui style: github.com/raysan5/rguistyler +* +* +* RAYGUI ICONS (guiIcons): +* raygui could use a global array containing icons data (allocated on data segment by default), +* a custom icons set could be loaded over this array using GuiLoadIcons(), but loaded icons set +* must be same RAYGUI_ICON_SIZE and no more than RAYGUI_ICON_MAX_ICONS will be loaded +* +* Every icon is codified in binary form, using 1 bit per pixel, so, every 16x16 icon +* requires 8 integers (16*16/32) to be stored in memory. +* +* When the icon is draw, actually one quad per pixel is drawn if the bit for that pixel is set. +* +* The global icons array size is fixed and depends on the number of icons and size: +* +* static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS]; +* +* guiIcons size is by default: 256*(16*16/32) = 2048*4 = 8192 bytes = 8 KB +* +* TOOL: rGuiIcons is a visual tool to customize/create raygui icons: github.com/raysan5/rguiicons +* +* RAYGUI LAYOUT: +* raygui currently does not provide an auto-layout mechanism like other libraries, +* layouts must be defined manually on controls drawing, providing the right bounds Rectangle for it. +* +* TOOL: rGuiLayout is a visual tool to create raygui layouts: github.com/raysan5/rguilayout +* +* CONFIGURATION: +* #define RAYGUI_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RAYGUI_STANDALONE +* Avoid raylib.h header inclusion in this file. Data types defined on raylib are defined +* internally in the library and input management and drawing functions must be provided by +* the user (check library implementation for further details). +* +* #define RAYGUI_NO_ICONS +* Avoid including embedded ricons data (256 icons, 16x16 pixels, 1-bit per pixel, 2KB) +* +* #define RAYGUI_CUSTOM_ICONS +* Includes custom ricons.h header defining a set of custom icons, +* this file can be generated using rGuiIcons tool +* +* #define RAYGUI_DEBUG_RECS_BOUNDS +* Draw control bounds rectangles for debug +* +* #define RAYGUI_DEBUG_TEXT_BOUNDS +* Draw text bounds rectangles for debug +* +* VERSIONS HISTORY: +* 4.5-dev (Sep-2024) Current dev version... +* ADDED: guiControlExclusiveMode and guiControlExclusiveRec for exclusive modes +* ADDED: GuiValueBoxFloat() +* ADDED: GuiDropdonwBox() properties: DROPDOWN_ARROW_HIDDEN, DROPDOWN_ROLL_UP +* ADDED: GuiListView() property: LIST_ITEMS_BORDER_WIDTH +* ADDED: Multiple new icons +* REVIEWED: GuiTabBar(), close tab with mouse middle button +* REVIEWED: GuiScrollPanel(), scroll speed proportional to content +* REVIEWED: GuiDropdownBox(), support roll up and hidden arrow +* REVIEWED: GuiTextBox(), cursor position initialization +* REVIEWED: GuiSliderPro(), control value change check +* REVIEWED: GuiGrid(), simplified implementation +* REVIEWED: GuiIconText(), increase buffer size and reviewed padding +* REVIEWED: GuiDrawText(), improved wrap mode drawing +* REVIEWED: GuiScrollBar(), minor tweaks +* REVIEWED: Functions descriptions, removed wrong return value reference +* REDESIGNED: GuiColorPanel(), improved HSV <-> RGBA convertion +* +* 4.0 (12-Sep-2023) ADDED: GuiToggleSlider() +* ADDED: GuiColorPickerHSV() and GuiColorPanelHSV() +* ADDED: Multiple new icons, mostly compiler related +* ADDED: New DEFAULT properties: TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE +* ADDED: New enum values: GuiTextAlignment, GuiTextAlignmentVertical, GuiTextWrapMode +* ADDED: Support loading styles with custom font charset from external file +* REDESIGNED: GuiTextBox(), support mouse cursor positioning +* REDESIGNED: GuiDrawText(), support multiline and word-wrap modes (read only) +* REDESIGNED: GuiProgressBar() to be more visual, progress affects border color +* REDESIGNED: Global alpha consideration moved to GuiDrawRectangle() and GuiDrawText() +* REDESIGNED: GuiScrollPanel(), get parameters by reference and return result value +* REDESIGNED: GuiToggleGroup(), get parameters by reference and return result value +* REDESIGNED: GuiComboBox(), get parameters by reference and return result value +* REDESIGNED: GuiCheckBox(), get parameters by reference and return result value +* REDESIGNED: GuiSlider(), get parameters by reference and return result value +* REDESIGNED: GuiSliderBar(), get parameters by reference and return result value +* REDESIGNED: GuiProgressBar(), get parameters by reference and return result value +* REDESIGNED: GuiListView(), get parameters by reference and return result value +* REDESIGNED: GuiColorPicker(), get parameters by reference and return result value +* REDESIGNED: GuiColorPanel(), get parameters by reference and return result value +* REDESIGNED: GuiColorBarAlpha(), get parameters by reference and return result value +* REDESIGNED: GuiColorBarHue(), get parameters by reference and return result value +* REDESIGNED: GuiGrid(), get parameters by reference and return result value +* REDESIGNED: GuiGrid(), added extra parameter +* REDESIGNED: GuiListViewEx(), change parameters order +* REDESIGNED: All controls return result as int value +* REVIEWED: GuiScrollPanel() to avoid smallish scroll-bars +* REVIEWED: All examples and specially controls_test_suite +* RENAMED: gui_file_dialog module to gui_window_file_dialog +* UPDATED: All styles to include ISO-8859-15 charset (as much as possible) +* +* 3.6 (10-May-2023) ADDED: New icon: SAND_TIMER +* ADDED: GuiLoadStyleFromMemory() (binary only) +* REVIEWED: GuiScrollBar() horizontal movement key +* REVIEWED: GuiTextBox() crash on cursor movement +* REVIEWED: GuiTextBox(), additional inputs support +* REVIEWED: GuiLabelButton(), avoid text cut +* REVIEWED: GuiTextInputBox(), password input +* REVIEWED: Local GetCodepointNext(), aligned with raylib +* REDESIGNED: GuiSlider*()/GuiScrollBar() to support out-of-bounds +* +* 3.5 (20-Apr-2023) ADDED: GuiTabBar(), based on GuiToggle() +* ADDED: Helper functions to split text in separate lines +* ADDED: Multiple new icons, useful for code editing tools +* REMOVED: Unneeded icon editing functions +* REMOVED: GuiTextBoxMulti(), very limited and broken +* REMOVED: MeasureTextEx() dependency, logic directly implemented +* REMOVED: DrawTextEx() dependency, logic directly implemented +* REVIEWED: GuiScrollBar(), improve mouse-click behaviour +* REVIEWED: Library header info, more info, better organized +* REDESIGNED: GuiTextBox() to support cursor movement +* REDESIGNED: GuiDrawText() to divide drawing by lines +* +* 3.2 (22-May-2022) RENAMED: Some enum values, for unification, avoiding prefixes +* REMOVED: GuiScrollBar(), only internal +* REDESIGNED: GuiPanel() to support text parameter +* REDESIGNED: GuiScrollPanel() to support text parameter +* REDESIGNED: GuiColorPicker() to support text parameter +* REDESIGNED: GuiColorPanel() to support text parameter +* REDESIGNED: GuiColorBarAlpha() to support text parameter +* REDESIGNED: GuiColorBarHue() to support text parameter +* REDESIGNED: GuiTextInputBox() to support password +* +* 3.1 (12-Jan-2022) REVIEWED: Default style for consistency (aligned with rGuiLayout v2.5 tool) +* REVIEWED: GuiLoadStyle() to support compressed font atlas image data and unload previous textures +* REVIEWED: External icons usage logic +* REVIEWED: GuiLine() for centered alignment when including text +* RENAMED: Multiple controls properties definitions to prepend RAYGUI_ +* RENAMED: RICON_ references to RAYGUI_ICON_ for library consistency +* Projects updated and multiple tweaks +* +* 3.0 (04-Nov-2021) Integrated ricons data to avoid external file +* REDESIGNED: GuiTextBoxMulti() +* REMOVED: GuiImageButton*() +* Multiple minor tweaks and bugs corrected +* +* 2.9 (17-Mar-2021) REMOVED: Tooltip API +* 2.8 (03-May-2020) Centralized rectangles drawing to GuiDrawRectangle() +* 2.7 (20-Feb-2020) ADDED: Possible tooltips API +* 2.6 (09-Sep-2019) ADDED: GuiTextInputBox() +* REDESIGNED: GuiListView*(), GuiDropdownBox(), GuiSlider*(), GuiProgressBar(), GuiMessageBox() +* REVIEWED: GuiTextBox(), GuiSpinner(), GuiValueBox(), GuiLoadStyle() +* Replaced property INNER_PADDING by TEXT_PADDING, renamed some properties +* ADDED: 8 new custom styles ready to use +* Multiple minor tweaks and bugs corrected +* +* 2.5 (28-May-2019) Implemented extended GuiTextBox(), GuiValueBox(), GuiSpinner() +* 2.3 (29-Apr-2019) ADDED: rIcons auxiliar library and support for it, multiple controls reviewed +* Refactor all controls drawing mechanism to use control state +* 2.2 (05-Feb-2019) ADDED: GuiScrollBar(), GuiScrollPanel(), reviewed GuiListView(), removed Gui*Ex() controls +* 2.1 (26-Dec-2018) REDESIGNED: GuiCheckBox(), GuiComboBox(), GuiDropdownBox(), GuiToggleGroup() > Use combined text string +* REDESIGNED: Style system (breaking change) +* 2.0 (08-Nov-2018) ADDED: Support controls guiLock and custom fonts +* REVIEWED: GuiComboBox(), GuiListView()... +* 1.9 (09-Oct-2018) REVIEWED: GuiGrid(), GuiTextBox(), GuiTextBoxMulti(), GuiValueBox()... +* 1.8 (01-May-2018) Lot of rework and redesign to align with rGuiStyler and rGuiLayout +* 1.5 (21-Jun-2017) Working in an improved styles system +* 1.4 (15-Jun-2017) Rewritten all GUI functions (removed useless ones) +* 1.3 (12-Jun-2017) Complete redesign of style system +* 1.1 (01-Jun-2017) Complete review of the library +* 1.0 (07-Jun-2016) Converted to header-only by Ramon Santamaria. +* 0.9 (07-Mar-2016) Reviewed and tested by Albert Martos, Ian Eito, Sergio Martinez and Ramon Santamaria. +* 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria. +* +* DEPENDENCIES: +* raylib 5.0 - Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing +* +* STANDALONE MODE: +* By default raygui depends on raylib mostly for the inputs and the drawing functionality but that dependency can be disabled +* with the config flag RAYGUI_STANDALONE. In that case is up to the user to provide another backend to cover library needs. +* +* The following functions should be redefined for a custom backend: +* +* - Vector2 GetMousePosition(void); +* - float GetMouseWheelMove(void); +* - bool IsMouseButtonDown(int button); +* - bool IsMouseButtonPressed(int button); +* - bool IsMouseButtonReleased(int button); +* - bool IsKeyDown(int key); +* - bool IsKeyPressed(int key); +* - int GetCharPressed(void); // -- GuiTextBox(), GuiValueBox() +* +* - void DrawRectangle(int x, int y, int width, int height, Color color); // -- GuiDrawRectangle() +* - void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // -- GuiColorPicker() +* +* - Font GetFontDefault(void); // -- GuiLoadStyleDefault() +* - Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // -- GuiLoadStyle() +* - Texture2D LoadTextureFromImage(Image image); // -- GuiLoadStyle(), required to load texture from embedded font atlas image +* - void SetShapesTexture(Texture2D tex, Rectangle rec); // -- GuiLoadStyle(), required to set shapes rec to font white rec (optimization) +* - char *LoadFileText(const char *fileName); // -- GuiLoadStyle(), required to load charset data +* - void UnloadFileText(char *text); // -- GuiLoadStyle(), required to unload charset data +* - const char *GetDirectoryPath(const char *filePath); // -- GuiLoadStyle(), required to find charset/font file from text .rgs +* - int *LoadCodepoints(const char *text, int *count); // -- GuiLoadStyle(), required to load required font codepoints list +* - void UnloadCodepoints(int *codepoints); // -- GuiLoadStyle(), required to unload codepoints list +* - unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // -- GuiLoadStyle() +* +* CONTRIBUTORS: +* Ramon Santamaria: Supervision, review, redesign, update and maintenance +* Vlad Adrian: Complete rewrite of GuiTextBox() to support extended features (2019) +* Sergio Martinez: Review, testing (2015) and redesign of multiple controls (2018) +* Adria Arranz: Testing and implementation of additional controls (2018) +* Jordi Jorba: Testing and implementation of additional controls (2018) +* Albert Martos: Review and testing of the library (2015) +* Ian Eito: Review and testing of the library (2015) +* Kevin Gato: Initial implementation of basic components (2014) +* Daniel Nicolas: Initial implementation of basic components (2014) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2024 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYGUI_H +#define RAYGUI_H + +#define RAYGUI_VERSION_MAJOR 4 +#define RAYGUI_VERSION_MINOR 5 +#define RAYGUI_VERSION_PATCH 0 +#define RAYGUI_VERSION "4.5-dev" + +#if !defined(RAYGUI_STANDALONE) + #include "raylib.h" +#endif + +// Function specifiers in case library is build/used as a shared library (Windows) +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +#if defined(_WIN32) + #if defined(BUILD_LIBTYPE_SHARED) + #define RAYGUIAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RAYGUIAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) + #endif +#endif + +// Function specifiers definition +#ifndef RAYGUIAPI + #define RAYGUIAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// Allow custom memory allocators +#ifndef RAYGUI_MALLOC + #define RAYGUI_MALLOC(sz) malloc(sz) +#endif +#ifndef RAYGUI_CALLOC + #define RAYGUI_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RAYGUI_FREE + #define RAYGUI_FREE(p) free(p) +#endif + +// Simple log system to avoid printf() calls if required +// NOTE: Avoiding those calls, also avoids const strings memory usage +#define RAYGUI_SUPPORT_LOG_INFO +#if defined(RAYGUI_SUPPORT_LOG_INFO) + #define RAYGUI_LOG(...) printf(__VA_ARGS__) +#else + #define RAYGUI_LOG(...) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +// NOTE: Some types are required for RAYGUI_STANDALONE usage +//---------------------------------------------------------------------------------- +#if defined(RAYGUI_STANDALONE) + #ifndef __cplusplus + // Boolean type + #ifndef true + typedef enum { false, true } bool; + #endif + #endif + + // Vector2 type + typedef struct Vector2 { + float x; + float y; + } Vector2; + + // Vector3 type // -- ConvertHSVtoRGB(), ConvertRGBtoHSV() + typedef struct Vector3 { + float x; + float y; + float z; + } Vector3; + + // Color type, RGBA (32bit) + typedef struct Color { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; + } Color; + + // Rectangle type + typedef struct Rectangle { + float x; + float y; + float width; + float height; + } Rectangle; + + // TODO: Texture2D type is very coupled to raylib, required by Font type + // It should be redesigned to be provided by user + typedef struct Texture2D { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) + } Texture2D; + + // Image, pixel data stored in CPU memory (RAM) + typedef struct Image { + void *data; // Image raw data + int width; // Image base width + int height; // Image base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) + } Image; + + // GlyphInfo, font characters glyphs info + typedef struct GlyphInfo { + int value; // Character value (Unicode) + int offsetX; // Character offset X when drawing + int offsetY; // Character offset Y when drawing + int advanceX; // Character advance position X + Image image; // Character image data + } GlyphInfo; + + // TODO: Font type is very coupled to raylib, mostly required by GuiLoadStyle() + // It should be redesigned to be provided by user + typedef struct Font { + int baseSize; // Base size (default chars height) + int glyphCount; // Number of glyph characters + int glyphPadding; // Padding around the glyph characters + Texture2D texture; // Texture atlas containing the glyphs + Rectangle *recs; // Rectangles in texture for the glyphs + GlyphInfo *glyphs; // Glyphs info data + } Font; +#endif + +// Style property +// NOTE: Used when exporting style as code for convenience +typedef struct GuiStyleProp { + unsigned short controlId; // Control identifier + unsigned short propertyId; // Property identifier + int propertyValue; // Property value +} GuiStyleProp; + +/* +// Controls text style -NOT USED- +// NOTE: Text style is defined by control +typedef struct GuiTextStyle { + unsigned int size; + int charSpacing; + int lineSpacing; + int alignmentH; + int alignmentV; + int padding; +} GuiTextStyle; +*/ + +// Gui control state +typedef enum { + STATE_NORMAL = 0, + STATE_FOCUSED, + STATE_PRESSED, + STATE_DISABLED +} GuiState; + +// Gui control text alignment +typedef enum { + TEXT_ALIGN_LEFT = 0, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_RIGHT +} GuiTextAlignment; + +// Gui control text alignment vertical +// NOTE: Text vertical position inside the text bounds +typedef enum { + TEXT_ALIGN_TOP = 0, + TEXT_ALIGN_MIDDLE, + TEXT_ALIGN_BOTTOM +} GuiTextAlignmentVertical; + +// Gui control text wrap mode +// NOTE: Useful for multiline text +typedef enum { + TEXT_WRAP_NONE = 0, + TEXT_WRAP_CHAR, + TEXT_WRAP_WORD +} GuiTextWrapMode; + +// Gui controls +typedef enum { + // Default -> populates to all controls when set + DEFAULT = 0, + + // Basic controls + LABEL, // Used also for: LABELBUTTON + BUTTON, + TOGGLE, // Used also for: TOGGLEGROUP + SLIDER, // Used also for: SLIDERBAR, TOGGLESLIDER + PROGRESSBAR, + CHECKBOX, + COMBOBOX, + DROPDOWNBOX, + TEXTBOX, // Used also for: TEXTBOXMULTI + VALUEBOX, + SPINNER, // Uses: BUTTON, VALUEBOX + LISTVIEW, + COLORPICKER, + SCROLLBAR, + STATUSBAR +} GuiControl; + +// Gui base properties for every control +// NOTE: RAYGUI_MAX_PROPS_BASE properties (by default 16 properties) +typedef enum { + BORDER_COLOR_NORMAL = 0, // Control border color in STATE_NORMAL + BASE_COLOR_NORMAL, // Control base color in STATE_NORMAL + TEXT_COLOR_NORMAL, // Control text color in STATE_NORMAL + BORDER_COLOR_FOCUSED, // Control border color in STATE_FOCUSED + BASE_COLOR_FOCUSED, // Control base color in STATE_FOCUSED + TEXT_COLOR_FOCUSED, // Control text color in STATE_FOCUSED + BORDER_COLOR_PRESSED, // Control border color in STATE_PRESSED + BASE_COLOR_PRESSED, // Control base color in STATE_PRESSED + TEXT_COLOR_PRESSED, // Control text color in STATE_PRESSED + BORDER_COLOR_DISABLED, // Control border color in STATE_DISABLED + BASE_COLOR_DISABLED, // Control base color in STATE_DISABLED + TEXT_COLOR_DISABLED, // Control text color in STATE_DISABLED + BORDER_WIDTH, // Control border size, 0 for no border + //TEXT_SIZE, // Control text size (glyphs max height) -> GLOBAL for all controls + //TEXT_SPACING, // Control text spacing between glyphs -> GLOBAL for all controls + //TEXT_LINE_SPACING // Control text spacing between lines -> GLOBAL for all controls + TEXT_PADDING, // Control text padding, not considering border + TEXT_ALIGNMENT, // Control text horizontal alignment inside control text bound (after border and padding) + //TEXT_WRAP_MODE // Control text wrap-mode inside text bounds -> GLOBAL for all controls +} GuiControlProperty; + +// TODO: Which text styling properties should be global or per-control? +// At this moment TEXT_PADDING and TEXT_ALIGNMENT is configured and saved per control while +// TEXT_SIZE, TEXT_SPACING, TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE are global and +// should be configured by user as needed while defining the UI layout + +// Gui extended properties depend on control +// NOTE: RAYGUI_MAX_PROPS_EXTENDED properties (by default, max 8 properties) +//---------------------------------------------------------------------------------- +// DEFAULT extended properties +// NOTE: Those properties are common to all controls or global +// WARNING: We only have 8 slots for those properties by default!!! -> New global control: TEXT? +typedef enum { + TEXT_SIZE = 16, // Text size (glyphs max height) + TEXT_SPACING, // Text spacing between glyphs + LINE_COLOR, // Line control color + BACKGROUND_COLOR, // Background color + TEXT_LINE_SPACING, // Text spacing between lines + TEXT_ALIGNMENT_VERTICAL, // Text vertical alignment inside text bounds (after border and padding) + TEXT_WRAP_MODE // Text wrap-mode inside text bounds + //TEXT_DECORATION // Text decoration: 0-None, 1-Underline, 2-Line-through, 3-Overline + //TEXT_DECORATION_THICK // Text decoration line thickness +} GuiDefaultProperty; + +// Other possible text properties: +// TEXT_WEIGHT // Normal, Italic, Bold -> Requires specific font change +// TEXT_INDENT // Text indentation -> Now using TEXT_PADDING... + +// Label +//typedef enum { } GuiLabelProperty; + +// Button/Spinner +//typedef enum { } GuiButtonProperty; + +// Toggle/ToggleGroup +typedef enum { + GROUP_PADDING = 16, // ToggleGroup separation between toggles +} GuiToggleProperty; + +// Slider/SliderBar +typedef enum { + SLIDER_WIDTH = 16, // Slider size of internal bar + SLIDER_PADDING // Slider/SliderBar internal bar padding +} GuiSliderProperty; + +// ProgressBar +typedef enum { + PROGRESS_PADDING = 16, // ProgressBar internal padding +} GuiProgressBarProperty; + +// ScrollBar +typedef enum { + ARROWS_SIZE = 16, // ScrollBar arrows size + ARROWS_VISIBLE, // ScrollBar arrows visible + SCROLL_SLIDER_PADDING, // ScrollBar slider internal padding + SCROLL_SLIDER_SIZE, // ScrollBar slider size + SCROLL_PADDING, // ScrollBar scroll padding from arrows + SCROLL_SPEED, // ScrollBar scrolling speed +} GuiScrollBarProperty; + +// CheckBox +typedef enum { + CHECK_PADDING = 16 // CheckBox internal check padding +} GuiCheckBoxProperty; + +// ComboBox +typedef enum { + COMBO_BUTTON_WIDTH = 16, // ComboBox right button width + COMBO_BUTTON_SPACING // ComboBox button separation +} GuiComboBoxProperty; + +// DropdownBox +typedef enum { + ARROW_PADDING = 16, // DropdownBox arrow separation from border and items + DROPDOWN_ITEMS_SPACING, // DropdownBox items separation + DROPDOWN_ARROW_HIDDEN, // DropdownBox arrow hidden + DROPDOWN_ROLL_UP // DropdownBox roll up flag (default rolls down) +} GuiDropdownBoxProperty; + +// TextBox/TextBoxMulti/ValueBox/Spinner +typedef enum { + TEXT_READONLY = 16, // TextBox in read-only mode: 0-text editable, 1-text no-editable +} GuiTextBoxProperty; + +// Spinner +typedef enum { + SPIN_BUTTON_WIDTH = 16, // Spinner left/right buttons width + SPIN_BUTTON_SPACING, // Spinner buttons separation +} GuiSpinnerProperty; + +// ListView +typedef enum { + LIST_ITEMS_HEIGHT = 16, // ListView items height + LIST_ITEMS_SPACING, // ListView items separation + SCROLLBAR_WIDTH, // ListView scrollbar size (usually width) + SCROLLBAR_SIDE, // ListView scrollbar side (0-SCROLLBAR_LEFT_SIDE, 1-SCROLLBAR_RIGHT_SIDE) + LIST_ITEMS_BORDER_WIDTH // ListView items border width +} GuiListViewProperty; + +// ColorPicker +typedef enum { + COLOR_SELECTOR_SIZE = 16, + HUEBAR_WIDTH, // ColorPicker right hue bar width + HUEBAR_PADDING, // ColorPicker right hue bar separation from panel + HUEBAR_SELECTOR_HEIGHT, // ColorPicker right hue bar selector height + HUEBAR_SELECTOR_OVERFLOW // ColorPicker right hue bar selector overflow +} GuiColorPickerProperty; + +#define SCROLLBAR_LEFT_SIDE 0 +#define SCROLLBAR_RIGHT_SIDE 1 + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +// Global gui state control functions +RAYGUIAPI void GuiEnable(void); // Enable gui controls (global state) +RAYGUIAPI void GuiDisable(void); // Disable gui controls (global state) +RAYGUIAPI void GuiLock(void); // Lock gui controls (global state) +RAYGUIAPI void GuiUnlock(void); // Unlock gui controls (global state) +RAYGUIAPI bool GuiIsLocked(void); // Check if gui is locked (global state) +RAYGUIAPI void GuiSetAlpha(float alpha); // Set gui controls alpha (global state), alpha goes from 0.0f to 1.0f +RAYGUIAPI void GuiSetState(int state); // Set gui state (global state) +RAYGUIAPI int GuiGetState(void); // Get gui state (global state) + +// Font set/get functions +RAYGUIAPI void GuiSetFont(Font font); // Set gui custom font (global state) +RAYGUIAPI Font GuiGetFont(void); // Get gui custom font (global state) + +// Style set/get functions +RAYGUIAPI void GuiSetStyle(int control, int property, int value); // Set one style property +RAYGUIAPI int GuiGetStyle(int control, int property); // Get one style property + +// Styles loading functions +RAYGUIAPI void GuiLoadStyle(const char *fileName); // Load style file over global style variable (.rgs) +RAYGUIAPI void GuiLoadStyleDefault(void); // Load style default over global style + +// Tooltips management functions +RAYGUIAPI void GuiEnableTooltip(void); // Enable gui tooltips (global state) +RAYGUIAPI void GuiDisableTooltip(void); // Disable gui tooltips (global state) +RAYGUIAPI void GuiSetTooltip(const char *tooltip); // Set tooltip string + +// Icons functionality +RAYGUIAPI const char *GuiIconText(int iconId, const char *text); // Get text with icon id prepended (if supported) +#if !defined(RAYGUI_NO_ICONS) +RAYGUIAPI void GuiSetIconScale(int scale); // Set default icon drawing size +RAYGUIAPI unsigned int *GuiGetIcons(void); // Get raygui icons data pointer +RAYGUIAPI char **GuiLoadIcons(const char *fileName, bool loadIconsName); // Load raygui icons file (.rgi) into internal icons data +RAYGUIAPI void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color); // Draw icon using pixel size at specified position +#endif + +// Controls +//---------------------------------------------------------------------------------------------------------- +// Container/separator controls, useful for controls organization +RAYGUIAPI int GuiWindowBox(Rectangle bounds, const char *title); // Window Box control, shows a window that can be closed +RAYGUIAPI int GuiGroupBox(Rectangle bounds, const char *text); // Group Box control with text name +RAYGUIAPI int GuiLine(Rectangle bounds, const char *text); // Line separator control, could contain text +RAYGUIAPI int GuiPanel(Rectangle bounds, const char *text); // Panel control, useful to group controls +RAYGUIAPI int GuiTabBar(Rectangle bounds, const char **text, int count, int *active); // Tab Bar control, returns TAB to be closed or -1 +RAYGUIAPI int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view); // Scroll Panel control + +// Basic controls set +RAYGUIAPI int GuiLabel(Rectangle bounds, const char *text); // Label control +RAYGUIAPI int GuiButton(Rectangle bounds, const char *text); // Button control, returns true when clicked +RAYGUIAPI int GuiLabelButton(Rectangle bounds, const char *text); // Label button control, returns true when clicked +RAYGUIAPI int GuiToggle(Rectangle bounds, const char *text, bool *active); // Toggle Button control +RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int *active); // Toggle Group control +RAYGUIAPI int GuiToggleSlider(Rectangle bounds, const char *text, int *active); // Toggle Slider control +RAYGUIAPI int GuiCheckBox(Rectangle bounds, const char *text, bool *checked); // Check Box control, returns true when active +RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int *active); // Combo Box control + +RAYGUIAPI int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode); // Dropdown Box control +RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control +RAYGUIAPI int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers +RAYGUIAPI int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode); // Value box control for float values +RAYGUIAPI int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text + +RAYGUIAPI int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider control +RAYGUIAPI int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider Bar control +RAYGUIAPI int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Progress Bar control +RAYGUIAPI int GuiStatusBar(Rectangle bounds, const char *text); // Status Bar control, shows info text +RAYGUIAPI int GuiDummyRec(Rectangle bounds, const char *text); // Dummy control for placeholders +RAYGUIAPI int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell); // Grid control + +// Advance controls set +RAYGUIAPI int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active); // List View control +RAYGUIAPI int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus); // List View with extended parameters +RAYGUIAPI int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons); // Message Box control, displays a message +RAYGUIAPI int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive); // Text Input Box control, ask for text, supports secret +RAYGUIAPI int GuiColorPicker(Rectangle bounds, const char *text, Color *color); // Color Picker control (multiple color controls) +RAYGUIAPI int GuiColorPanel(Rectangle bounds, const char *text, Color *color); // Color Panel control +RAYGUIAPI int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha); // Color Bar Alpha control +RAYGUIAPI int GuiColorBarHue(Rectangle bounds, const char *text, float *value); // Color Bar Hue control +RAYGUIAPI int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Picker control that avoids conversion to RGB on each call (multiple color controls) +RAYGUIAPI int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Panel control that updates Hue-Saturation-Value color value, used by GuiColorPickerHSV() +//---------------------------------------------------------------------------------------------------------- + +#if !defined(RAYGUI_NO_ICONS) + +#if !defined(RAYGUI_CUSTOM_ICONS) +//---------------------------------------------------------------------------------- +// Icons enumeration +//---------------------------------------------------------------------------------- +typedef enum { + ICON_NONE = 0, + ICON_FOLDER_FILE_OPEN = 1, + ICON_FILE_SAVE_CLASSIC = 2, + ICON_FOLDER_OPEN = 3, + ICON_FOLDER_SAVE = 4, + ICON_FILE_OPEN = 5, + ICON_FILE_SAVE = 6, + ICON_FILE_EXPORT = 7, + ICON_FILE_ADD = 8, + ICON_FILE_DELETE = 9, + ICON_FILETYPE_TEXT = 10, + ICON_FILETYPE_AUDIO = 11, + ICON_FILETYPE_IMAGE = 12, + ICON_FILETYPE_PLAY = 13, + ICON_FILETYPE_VIDEO = 14, + ICON_FILETYPE_INFO = 15, + ICON_FILE_COPY = 16, + ICON_FILE_CUT = 17, + ICON_FILE_PASTE = 18, + ICON_CURSOR_HAND = 19, + ICON_CURSOR_POINTER = 20, + ICON_CURSOR_CLASSIC = 21, + ICON_PENCIL = 22, + ICON_PENCIL_BIG = 23, + ICON_BRUSH_CLASSIC = 24, + ICON_BRUSH_PAINTER = 25, + ICON_WATER_DROP = 26, + ICON_COLOR_PICKER = 27, + ICON_RUBBER = 28, + ICON_COLOR_BUCKET = 29, + ICON_TEXT_T = 30, + ICON_TEXT_A = 31, + ICON_SCALE = 32, + ICON_RESIZE = 33, + ICON_FILTER_POINT = 34, + ICON_FILTER_BILINEAR = 35, + ICON_CROP = 36, + ICON_CROP_ALPHA = 37, + ICON_SQUARE_TOGGLE = 38, + ICON_SYMMETRY = 39, + ICON_SYMMETRY_HORIZONTAL = 40, + ICON_SYMMETRY_VERTICAL = 41, + ICON_LENS = 42, + ICON_LENS_BIG = 43, + ICON_EYE_ON = 44, + ICON_EYE_OFF = 45, + ICON_FILTER_TOP = 46, + ICON_FILTER = 47, + ICON_TARGET_POINT = 48, + ICON_TARGET_SMALL = 49, + ICON_TARGET_BIG = 50, + ICON_TARGET_MOVE = 51, + ICON_CURSOR_MOVE = 52, + ICON_CURSOR_SCALE = 53, + ICON_CURSOR_SCALE_RIGHT = 54, + ICON_CURSOR_SCALE_LEFT = 55, + ICON_UNDO = 56, + ICON_REDO = 57, + ICON_REREDO = 58, + ICON_MUTATE = 59, + ICON_ROTATE = 60, + ICON_REPEAT = 61, + ICON_SHUFFLE = 62, + ICON_EMPTYBOX = 63, + ICON_TARGET = 64, + ICON_TARGET_SMALL_FILL = 65, + ICON_TARGET_BIG_FILL = 66, + ICON_TARGET_MOVE_FILL = 67, + ICON_CURSOR_MOVE_FILL = 68, + ICON_CURSOR_SCALE_FILL = 69, + ICON_CURSOR_SCALE_RIGHT_FILL = 70, + ICON_CURSOR_SCALE_LEFT_FILL = 71, + ICON_UNDO_FILL = 72, + ICON_REDO_FILL = 73, + ICON_REREDO_FILL = 74, + ICON_MUTATE_FILL = 75, + ICON_ROTATE_FILL = 76, + ICON_REPEAT_FILL = 77, + ICON_SHUFFLE_FILL = 78, + ICON_EMPTYBOX_SMALL = 79, + ICON_BOX = 80, + ICON_BOX_TOP = 81, + ICON_BOX_TOP_RIGHT = 82, + ICON_BOX_RIGHT = 83, + ICON_BOX_BOTTOM_RIGHT = 84, + ICON_BOX_BOTTOM = 85, + ICON_BOX_BOTTOM_LEFT = 86, + ICON_BOX_LEFT = 87, + ICON_BOX_TOP_LEFT = 88, + ICON_BOX_CENTER = 89, + ICON_BOX_CIRCLE_MASK = 90, + ICON_POT = 91, + ICON_ALPHA_MULTIPLY = 92, + ICON_ALPHA_CLEAR = 93, + ICON_DITHERING = 94, + ICON_MIPMAPS = 95, + ICON_BOX_GRID = 96, + ICON_GRID = 97, + ICON_BOX_CORNERS_SMALL = 98, + ICON_BOX_CORNERS_BIG = 99, + ICON_FOUR_BOXES = 100, + ICON_GRID_FILL = 101, + ICON_BOX_MULTISIZE = 102, + ICON_ZOOM_SMALL = 103, + ICON_ZOOM_MEDIUM = 104, + ICON_ZOOM_BIG = 105, + ICON_ZOOM_ALL = 106, + ICON_ZOOM_CENTER = 107, + ICON_BOX_DOTS_SMALL = 108, + ICON_BOX_DOTS_BIG = 109, + ICON_BOX_CONCENTRIC = 110, + ICON_BOX_GRID_BIG = 111, + ICON_OK_TICK = 112, + ICON_CROSS = 113, + ICON_ARROW_LEFT = 114, + ICON_ARROW_RIGHT = 115, + ICON_ARROW_DOWN = 116, + ICON_ARROW_UP = 117, + ICON_ARROW_LEFT_FILL = 118, + ICON_ARROW_RIGHT_FILL = 119, + ICON_ARROW_DOWN_FILL = 120, + ICON_ARROW_UP_FILL = 121, + ICON_AUDIO = 122, + ICON_FX = 123, + ICON_WAVE = 124, + ICON_WAVE_SINUS = 125, + ICON_WAVE_SQUARE = 126, + ICON_WAVE_TRIANGULAR = 127, + ICON_CROSS_SMALL = 128, + ICON_PLAYER_PREVIOUS = 129, + ICON_PLAYER_PLAY_BACK = 130, + ICON_PLAYER_PLAY = 131, + ICON_PLAYER_PAUSE = 132, + ICON_PLAYER_STOP = 133, + ICON_PLAYER_NEXT = 134, + ICON_PLAYER_RECORD = 135, + ICON_MAGNET = 136, + ICON_LOCK_CLOSE = 137, + ICON_LOCK_OPEN = 138, + ICON_CLOCK = 139, + ICON_TOOLS = 140, + ICON_GEAR = 141, + ICON_GEAR_BIG = 142, + ICON_BIN = 143, + ICON_HAND_POINTER = 144, + ICON_LASER = 145, + ICON_COIN = 146, + ICON_EXPLOSION = 147, + ICON_1UP = 148, + ICON_PLAYER = 149, + ICON_PLAYER_JUMP = 150, + ICON_KEY = 151, + ICON_DEMON = 152, + ICON_TEXT_POPUP = 153, + ICON_GEAR_EX = 154, + ICON_CRACK = 155, + ICON_CRACK_POINTS = 156, + ICON_STAR = 157, + ICON_DOOR = 158, + ICON_EXIT = 159, + ICON_MODE_2D = 160, + ICON_MODE_3D = 161, + ICON_CUBE = 162, + ICON_CUBE_FACE_TOP = 163, + ICON_CUBE_FACE_LEFT = 164, + ICON_CUBE_FACE_FRONT = 165, + ICON_CUBE_FACE_BOTTOM = 166, + ICON_CUBE_FACE_RIGHT = 167, + ICON_CUBE_FACE_BACK = 168, + ICON_CAMERA = 169, + ICON_SPECIAL = 170, + ICON_LINK_NET = 171, + ICON_LINK_BOXES = 172, + ICON_LINK_MULTI = 173, + ICON_LINK = 174, + ICON_LINK_BROKE = 175, + ICON_TEXT_NOTES = 176, + ICON_NOTEBOOK = 177, + ICON_SUITCASE = 178, + ICON_SUITCASE_ZIP = 179, + ICON_MAILBOX = 180, + ICON_MONITOR = 181, + ICON_PRINTER = 182, + ICON_PHOTO_CAMERA = 183, + ICON_PHOTO_CAMERA_FLASH = 184, + ICON_HOUSE = 185, + ICON_HEART = 186, + ICON_CORNER = 187, + ICON_VERTICAL_BARS = 188, + ICON_VERTICAL_BARS_FILL = 189, + ICON_LIFE_BARS = 190, + ICON_INFO = 191, + ICON_CROSSLINE = 192, + ICON_HELP = 193, + ICON_FILETYPE_ALPHA = 194, + ICON_FILETYPE_HOME = 195, + ICON_LAYERS_VISIBLE = 196, + ICON_LAYERS = 197, + ICON_WINDOW = 198, + ICON_HIDPI = 199, + ICON_FILETYPE_BINARY = 200, + ICON_HEX = 201, + ICON_SHIELD = 202, + ICON_FILE_NEW = 203, + ICON_FOLDER_ADD = 204, + ICON_ALARM = 205, + ICON_CPU = 206, + ICON_ROM = 207, + ICON_STEP_OVER = 208, + ICON_STEP_INTO = 209, + ICON_STEP_OUT = 210, + ICON_RESTART = 211, + ICON_BREAKPOINT_ON = 212, + ICON_BREAKPOINT_OFF = 213, + ICON_BURGER_MENU = 214, + ICON_CASE_SENSITIVE = 215, + ICON_REG_EXP = 216, + ICON_FOLDER = 217, + ICON_FILE = 218, + ICON_SAND_TIMER = 219, + ICON_WARNING = 220, + ICON_HELP_BOX = 221, + ICON_INFO_BOX = 222, + ICON_PRIORITY = 223, + ICON_LAYERS_ISO = 224, + ICON_LAYERS2 = 225, + ICON_MLAYERS = 226, + ICON_MAPS = 227, + ICON_HOT = 228, + ICON_229 = 229, + ICON_230 = 230, + ICON_231 = 231, + ICON_232 = 232, + ICON_233 = 233, + ICON_234 = 234, + ICON_235 = 235, + ICON_236 = 236, + ICON_237 = 237, + ICON_238 = 238, + ICON_239 = 239, + ICON_240 = 240, + ICON_241 = 241, + ICON_242 = 242, + ICON_243 = 243, + ICON_244 = 244, + ICON_245 = 245, + ICON_246 = 246, + ICON_247 = 247, + ICON_248 = 248, + ICON_249 = 249, + ICON_250 = 250, + ICON_251 = 251, + ICON_252 = 252, + ICON_253 = 253, + ICON_254 = 254, + ICON_255 = 255, +} GuiIconName; +#endif + +#endif + +#if defined(__cplusplus) +} // Prevents name mangling of functions +#endif + +#endif // RAYGUI_H + +/*********************************************************************************** +* +* RAYGUI IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RAYGUI_IMPLEMENTATION) + +#include // required for: isspace() [GuiTextBox()] +#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf(), vsprintf() [GuiLoadStyle(), GuiLoadIcons()] +#include // Required for: malloc(), calloc(), free() [GuiLoadStyle(), GuiLoadIcons()] +#include // Required for: strlen() [GuiTextBox(), GuiValueBox()], memset(), memcpy() +#include // Required for: va_list, va_start(), vfprintf(), va_end() [TextFormat()] +#include // Required for: roundf() [GuiColorPicker()] + +#ifdef __cplusplus + #define RAYGUI_CLITERAL(name) name +#else + #define RAYGUI_CLITERAL(name) (name) +#endif + +// Check if two rectangles are equal, used to validate a slider bounds as an id +#ifndef CHECK_BOUNDS_ID + #define CHECK_BOUNDS_ID(src, dst) ((src.x == dst.x) && (src.y == dst.y) && (src.width == dst.width) && (src.height == dst.height)) +#endif + +#if !defined(RAYGUI_NO_ICONS) && !defined(RAYGUI_CUSTOM_ICONS) + +// Embedded icons, no external file provided +#define RAYGUI_ICON_SIZE 16 // Size of icons in pixels (squared) +#define RAYGUI_ICON_MAX_ICONS 256 // Maximum number of icons +#define RAYGUI_ICON_MAX_NAME_LENGTH 32 // Maximum length of icon name id + +// Icons data is defined by bit array (every bit represents one pixel) +// Those arrays are stored as unsigned int data arrays, so, +// every array element defines 32 pixels (bits) of information +// One icon is defined by 8 int, (8 int * 32 bit = 256 bit = 16*16 pixels) +// NOTE: Number of elemens depend on RAYGUI_ICON_SIZE (by default 16x16 pixels) +#define RAYGUI_ICON_DATA_ELEMENTS (RAYGUI_ICON_SIZE*RAYGUI_ICON_SIZE/32) + +//---------------------------------------------------------------------------------- +// Icons data for all gui possible icons (allocated on data segment by default) +// +// NOTE 1: Every icon is codified in binary form, using 1 bit per pixel, so, +// every 16x16 icon requires 8 integers (16*16/32) to be stored +// +// NOTE 2: A different icon set could be loaded over this array using GuiLoadIcons(), +// but loaded icons set must be same RAYGUI_ICON_SIZE and no more than RAYGUI_ICON_MAX_ICONS +// +// guiIcons size is by default: 256*(16*16/32) = 2048*4 = 8192 bytes = 8 KB +//---------------------------------------------------------------------------------- +static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_NONE + 0x3ff80000, 0x2f082008, 0x2042207e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x00007ffe, // ICON_FOLDER_FILE_OPEN + 0x3ffe0000, 0x44226422, 0x400247e2, 0x5ffa4002, 0x57ea500a, 0x500a500a, 0x40025ffa, 0x00007ffe, // ICON_FILE_SAVE_CLASSIC + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x41024002, 0x44424282, 0x793e4102, 0x00000100, // ICON_FOLDER_OPEN + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x41024102, 0x44424102, 0x793e4282, 0x00000000, // ICON_FOLDER_SAVE + 0x3ff00000, 0x201c2010, 0x20042004, 0x21042004, 0x24442284, 0x21042104, 0x20042104, 0x00003ffc, // ICON_FILE_OPEN + 0x3ff00000, 0x201c2010, 0x20042004, 0x21042004, 0x21042104, 0x22842444, 0x20042104, 0x00003ffc, // ICON_FILE_SAVE + 0x3ff00000, 0x201c2010, 0x00042004, 0x20041004, 0x20844784, 0x00841384, 0x20042784, 0x00003ffc, // ICON_FILE_EXPORT + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x22042204, 0x22042f84, 0x20042204, 0x00003ffc, // ICON_FILE_ADD + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x25042884, 0x25042204, 0x20042884, 0x00003ffc, // ICON_FILE_DELETE + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042ff4, 0x20042ff4, 0x20042ff4, 0x20042004, 0x00003ffc, // ICON_FILETYPE_TEXT + 0x3ff00000, 0x201c2010, 0x27042004, 0x244424c4, 0x26442444, 0x20642664, 0x20042004, 0x00003ffc, // ICON_FILETYPE_AUDIO + 0x3ff00000, 0x201c2010, 0x26042604, 0x20042004, 0x35442884, 0x2414222c, 0x20042004, 0x00003ffc, // ICON_FILETYPE_IMAGE + 0x3ff00000, 0x201c2010, 0x20c42004, 0x22442144, 0x22442444, 0x20c42144, 0x20042004, 0x00003ffc, // ICON_FILETYPE_PLAY + 0x3ff00000, 0x3ffc2ff0, 0x3f3c2ff4, 0x3dbc2eb4, 0x3dbc2bb4, 0x3f3c2eb4, 0x3ffc2ff4, 0x00002ff4, // ICON_FILETYPE_VIDEO + 0x3ff00000, 0x201c2010, 0x21842184, 0x21842004, 0x21842184, 0x21842184, 0x20042184, 0x00003ffc, // ICON_FILETYPE_INFO + 0x0ff00000, 0x381c0810, 0x28042804, 0x28042804, 0x28042804, 0x28042804, 0x20102ffc, 0x00003ff0, // ICON_FILE_COPY + 0x00000000, 0x701c0000, 0x079c1e14, 0x55a000f0, 0x079c00f0, 0x701c1e14, 0x00000000, 0x00000000, // ICON_FILE_CUT + 0x01c00000, 0x13e41bec, 0x3f841004, 0x204420c4, 0x20442044, 0x20442044, 0x207c2044, 0x00003fc0, // ICON_FILE_PASTE + 0x00000000, 0x3aa00fe0, 0x2abc2aa0, 0x2aa42aa4, 0x20042aa4, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_CURSOR_HAND + 0x00000000, 0x003c000c, 0x030800c8, 0x30100c10, 0x10202020, 0x04400840, 0x01800280, 0x00000000, // ICON_CURSOR_POINTER + 0x00000000, 0x00180000, 0x01f00078, 0x03e007f0, 0x07c003e0, 0x04000e40, 0x00000000, 0x00000000, // ICON_CURSOR_CLASSIC + 0x00000000, 0x04000000, 0x11000a00, 0x04400a80, 0x01100220, 0x00580088, 0x00000038, 0x00000000, // ICON_PENCIL + 0x04000000, 0x15000a00, 0x50402880, 0x14102820, 0x05040a08, 0x015c028c, 0x007c00bc, 0x00000000, // ICON_PENCIL_BIG + 0x01c00000, 0x01400140, 0x01400140, 0x0ff80140, 0x0ff80808, 0x0aa80808, 0x0aa80aa8, 0x00000ff8, // ICON_BRUSH_CLASSIC + 0x1ffc0000, 0x5ffc7ffe, 0x40004000, 0x00807f80, 0x01c001c0, 0x01c001c0, 0x01c001c0, 0x00000080, // ICON_BRUSH_PAINTER + 0x00000000, 0x00800000, 0x01c00080, 0x03e001c0, 0x07f003e0, 0x036006f0, 0x000001c0, 0x00000000, // ICON_WATER_DROP + 0x00000000, 0x3e003800, 0x1f803f80, 0x0c201e40, 0x02080c10, 0x00840104, 0x00380044, 0x00000000, // ICON_COLOR_PICKER + 0x00000000, 0x07800300, 0x1fe00fc0, 0x3f883fd0, 0x0e021f04, 0x02040402, 0x00f00108, 0x00000000, // ICON_RUBBER + 0x00c00000, 0x02800140, 0x08200440, 0x20081010, 0x2ffe3004, 0x03f807fc, 0x00e001f0, 0x00000040, // ICON_COLOR_BUCKET + 0x00000000, 0x21843ffc, 0x01800180, 0x01800180, 0x01800180, 0x01800180, 0x03c00180, 0x00000000, // ICON_TEXT_T + 0x00800000, 0x01400180, 0x06200340, 0x0c100620, 0x1ff80c10, 0x380c1808, 0x70067004, 0x0000f80f, // ICON_TEXT_A + 0x78000000, 0x50004000, 0x00004800, 0x03c003c0, 0x03c003c0, 0x00100000, 0x0002000a, 0x0000000e, // ICON_SCALE + 0x75560000, 0x5e004002, 0x54001002, 0x41001202, 0x408200fe, 0x40820082, 0x40820082, 0x00006afe, // ICON_RESIZE + 0x00000000, 0x3f003f00, 0x3f003f00, 0x3f003f00, 0x00400080, 0x001c0020, 0x001c001c, 0x00000000, // ICON_FILTER_POINT + 0x6d800000, 0x00004080, 0x40804080, 0x40800000, 0x00406d80, 0x001c0020, 0x001c001c, 0x00000000, // ICON_FILTER_BILINEAR + 0x40080000, 0x1ffe2008, 0x14081008, 0x11081208, 0x10481088, 0x10081028, 0x10047ff8, 0x00001002, // ICON_CROP + 0x00100000, 0x3ffc0010, 0x2ab03550, 0x22b02550, 0x20b02150, 0x20302050, 0x2000fff0, 0x00002000, // ICON_CROP_ALPHA + 0x40000000, 0x1ff82000, 0x04082808, 0x01082208, 0x00482088, 0x00182028, 0x35542008, 0x00000002, // ICON_SQUARE_TOGGLE + 0x00000000, 0x02800280, 0x06c006c0, 0x0ea00ee0, 0x1e901eb0, 0x3e883e98, 0x7efc7e8c, 0x00000000, // ICON_SYMMETRY + 0x01000000, 0x05600100, 0x1d480d50, 0x7d423d44, 0x3d447d42, 0x0d501d48, 0x01000560, 0x00000100, // ICON_SYMMETRY_HORIZONTAL + 0x01800000, 0x04200240, 0x10080810, 0x00001ff8, 0x00007ffe, 0x0ff01ff8, 0x03c007e0, 0x00000180, // ICON_SYMMETRY_VERTICAL + 0x00000000, 0x010800f0, 0x02040204, 0x02040204, 0x07f00308, 0x1c000e00, 0x30003800, 0x00000000, // ICON_LENS + 0x00000000, 0x061803f0, 0x08240c0c, 0x08040814, 0x0c0c0804, 0x23f01618, 0x18002400, 0x00000000, // ICON_LENS_BIG + 0x00000000, 0x00000000, 0x1c7007c0, 0x638e3398, 0x1c703398, 0x000007c0, 0x00000000, 0x00000000, // ICON_EYE_ON + 0x00000000, 0x10002000, 0x04700fc0, 0x610e3218, 0x1c703098, 0x001007a0, 0x00000008, 0x00000000, // ICON_EYE_OFF + 0x00000000, 0x00007ffc, 0x40047ffc, 0x10102008, 0x04400820, 0x02800280, 0x02800280, 0x00000100, // ICON_FILTER_TOP + 0x00000000, 0x40027ffe, 0x10082004, 0x04200810, 0x02400240, 0x02400240, 0x01400240, 0x000000c0, // ICON_FILTER + 0x00800000, 0x00800080, 0x00000080, 0x3c9e0000, 0x00000000, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_POINT + 0x00800000, 0x00800080, 0x00800080, 0x3f7e01c0, 0x008001c0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_SMALL + 0x00800000, 0x00800080, 0x03e00080, 0x3e3e0220, 0x03e00220, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_BIG + 0x01000000, 0x04400280, 0x01000100, 0x43842008, 0x43849ab2, 0x01002008, 0x04400100, 0x01000280, // ICON_TARGET_MOVE + 0x01000000, 0x04400280, 0x01000100, 0x41042108, 0x41049ff2, 0x01002108, 0x04400100, 0x01000280, // ICON_CURSOR_MOVE + 0x781e0000, 0x500a4002, 0x04204812, 0x00000240, 0x02400000, 0x48120420, 0x4002500a, 0x0000781e, // ICON_CURSOR_SCALE + 0x00000000, 0x20003c00, 0x24002800, 0x01000200, 0x00400080, 0x00140024, 0x003c0004, 0x00000000, // ICON_CURSOR_SCALE_RIGHT + 0x00000000, 0x0004003c, 0x00240014, 0x00800040, 0x02000100, 0x28002400, 0x3c002000, 0x00000000, // ICON_CURSOR_SCALE_LEFT + 0x00000000, 0x00100020, 0x10101fc8, 0x10001020, 0x10001000, 0x10001000, 0x00001fc0, 0x00000000, // ICON_UNDO + 0x00000000, 0x08000400, 0x080813f8, 0x00080408, 0x00080008, 0x00080008, 0x000003f8, 0x00000000, // ICON_REDO + 0x00000000, 0x3ffc0000, 0x20042004, 0x20002000, 0x20402000, 0x3f902020, 0x00400020, 0x00000000, // ICON_REREDO + 0x00000000, 0x3ffc0000, 0x20042004, 0x27fc2004, 0x20202000, 0x3fc82010, 0x00200010, 0x00000000, // ICON_MUTATE + 0x00000000, 0x0ff00000, 0x10081818, 0x11801008, 0x10001180, 0x18101020, 0x00100fc8, 0x00000020, // ICON_ROTATE + 0x00000000, 0x04000200, 0x240429fc, 0x20042204, 0x20442004, 0x3f942024, 0x00400020, 0x00000000, // ICON_REPEAT + 0x00000000, 0x20001000, 0x22104c0e, 0x00801120, 0x11200040, 0x4c0e2210, 0x10002000, 0x00000000, // ICON_SHUFFLE + 0x7ffe0000, 0x50024002, 0x44024802, 0x41024202, 0x40424082, 0x40124022, 0x4002400a, 0x00007ffe, // ICON_EMPTYBOX + 0x00800000, 0x03e00080, 0x08080490, 0x3c9e0808, 0x08080808, 0x03e00490, 0x00800080, 0x00000000, // ICON_TARGET + 0x00800000, 0x00800080, 0x00800080, 0x3ffe01c0, 0x008001c0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_SMALL_FILL + 0x00800000, 0x00800080, 0x03e00080, 0x3ffe03e0, 0x03e003e0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_BIG_FILL + 0x01000000, 0x07c00380, 0x01000100, 0x638c2008, 0x638cfbbe, 0x01002008, 0x07c00100, 0x01000380, // ICON_TARGET_MOVE_FILL + 0x01000000, 0x07c00380, 0x01000100, 0x610c2108, 0x610cfffe, 0x01002108, 0x07c00100, 0x01000380, // ICON_CURSOR_MOVE_FILL + 0x781e0000, 0x6006700e, 0x04204812, 0x00000240, 0x02400000, 0x48120420, 0x700e6006, 0x0000781e, // ICON_CURSOR_SCALE_FILL + 0x00000000, 0x38003c00, 0x24003000, 0x01000200, 0x00400080, 0x000c0024, 0x003c001c, 0x00000000, // ICON_CURSOR_SCALE_RIGHT_FILL + 0x00000000, 0x001c003c, 0x0024000c, 0x00800040, 0x02000100, 0x30002400, 0x3c003800, 0x00000000, // ICON_CURSOR_SCALE_LEFT_FILL + 0x00000000, 0x00300020, 0x10301ff8, 0x10001020, 0x10001000, 0x10001000, 0x00001fc0, 0x00000000, // ICON_UNDO_FILL + 0x00000000, 0x0c000400, 0x0c081ff8, 0x00080408, 0x00080008, 0x00080008, 0x000003f8, 0x00000000, // ICON_REDO_FILL + 0x00000000, 0x3ffc0000, 0x20042004, 0x20002000, 0x20402000, 0x3ff02060, 0x00400060, 0x00000000, // ICON_REREDO_FILL + 0x00000000, 0x3ffc0000, 0x20042004, 0x27fc2004, 0x20202000, 0x3ff82030, 0x00200030, 0x00000000, // ICON_MUTATE_FILL + 0x00000000, 0x0ff00000, 0x10081818, 0x11801008, 0x10001180, 0x18301020, 0x00300ff8, 0x00000020, // ICON_ROTATE_FILL + 0x00000000, 0x06000200, 0x26042ffc, 0x20042204, 0x20442004, 0x3ff42064, 0x00400060, 0x00000000, // ICON_REPEAT_FILL + 0x00000000, 0x30001000, 0x32107c0e, 0x00801120, 0x11200040, 0x7c0e3210, 0x10003000, 0x00000000, // ICON_SHUFFLE_FILL + 0x00000000, 0x30043ffc, 0x24042804, 0x21042204, 0x20442084, 0x20142024, 0x3ffc200c, 0x00000000, // ICON_EMPTYBOX_SMALL + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX + 0x00000000, 0x23c43ffc, 0x23c423c4, 0x200423c4, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP + 0x00000000, 0x3e043ffc, 0x3e043e04, 0x20043e04, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x3e043e04, 0x3e043e04, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x3e042004, 0x3e043e04, 0x3ffc3e04, 0x00000000, // ICON_BOX_BOTTOM_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x23c42004, 0x23c423c4, 0x3ffc23c4, 0x00000000, // ICON_BOX_BOTTOM + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x207c2004, 0x207c207c, 0x3ffc207c, 0x00000000, // ICON_BOX_BOTTOM_LEFT + 0x00000000, 0x20043ffc, 0x20042004, 0x207c207c, 0x207c207c, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_LEFT + 0x00000000, 0x207c3ffc, 0x207c207c, 0x2004207c, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP_LEFT + 0x00000000, 0x20043ffc, 0x20042004, 0x23c423c4, 0x23c423c4, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_CENTER + 0x7ffe0000, 0x40024002, 0x47e24182, 0x4ff247e2, 0x47e24ff2, 0x418247e2, 0x40024002, 0x00007ffe, // ICON_BOX_CIRCLE_MASK + 0x7fff0000, 0x40014001, 0x40014001, 0x49555ddd, 0x4945495d, 0x400149c5, 0x40014001, 0x00007fff, // ICON_POT + 0x7ffe0000, 0x53327332, 0x44ce4cce, 0x41324332, 0x404e40ce, 0x48125432, 0x4006540e, 0x00007ffe, // ICON_ALPHA_MULTIPLY + 0x7ffe0000, 0x53327332, 0x44ce4cce, 0x41324332, 0x5c4e40ce, 0x44124432, 0x40065c0e, 0x00007ffe, // ICON_ALPHA_CLEAR + 0x7ffe0000, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x00007ffe, // ICON_DITHERING + 0x07fe0000, 0x1ffa0002, 0x7fea000a, 0x402a402a, 0x5b2a512a, 0x5128552a, 0x40205128, 0x00007fe0, // ICON_MIPMAPS + 0x00000000, 0x1ff80000, 0x12481248, 0x12481ff8, 0x1ff81248, 0x12481248, 0x00001ff8, 0x00000000, // ICON_BOX_GRID + 0x12480000, 0x7ffe1248, 0x12481248, 0x12487ffe, 0x7ffe1248, 0x12481248, 0x12487ffe, 0x00001248, // ICON_GRID + 0x00000000, 0x1c380000, 0x1c3817e8, 0x08100810, 0x08100810, 0x17e81c38, 0x00001c38, 0x00000000, // ICON_BOX_CORNERS_SMALL + 0x700e0000, 0x700e5ffa, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x5ffa700e, 0x0000700e, // ICON_BOX_CORNERS_BIG + 0x3f7e0000, 0x21422142, 0x21422142, 0x00003f7e, 0x21423f7e, 0x21422142, 0x3f7e2142, 0x00000000, // ICON_FOUR_BOXES + 0x00000000, 0x3bb80000, 0x3bb83bb8, 0x3bb80000, 0x3bb83bb8, 0x3bb80000, 0x3bb83bb8, 0x00000000, // ICON_GRID_FILL + 0x7ffe0000, 0x7ffe7ffe, 0x77fe7000, 0x77fe77fe, 0x777e7700, 0x777e777e, 0x777e777e, 0x0000777e, // ICON_BOX_MULTISIZE + 0x781e0000, 0x40024002, 0x00004002, 0x01800000, 0x00000180, 0x40020000, 0x40024002, 0x0000781e, // ICON_ZOOM_SMALL + 0x781e0000, 0x40024002, 0x00004002, 0x03c003c0, 0x03c003c0, 0x40020000, 0x40024002, 0x0000781e, // ICON_ZOOM_MEDIUM + 0x781e0000, 0x40024002, 0x07e04002, 0x07e007e0, 0x07e007e0, 0x400207e0, 0x40024002, 0x0000781e, // ICON_ZOOM_BIG + 0x781e0000, 0x5ffa4002, 0x1ff85ffa, 0x1ff81ff8, 0x1ff81ff8, 0x5ffa1ff8, 0x40025ffa, 0x0000781e, // ICON_ZOOM_ALL + 0x00000000, 0x2004381c, 0x00002004, 0x00000000, 0x00000000, 0x20040000, 0x381c2004, 0x00000000, // ICON_ZOOM_CENTER + 0x00000000, 0x1db80000, 0x10081008, 0x10080000, 0x00001008, 0x10081008, 0x00001db8, 0x00000000, // ICON_BOX_DOTS_SMALL + 0x35560000, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x35562002, 0x00000000, // ICON_BOX_DOTS_BIG + 0x7ffe0000, 0x40024002, 0x48124ff2, 0x49924812, 0x48124992, 0x4ff24812, 0x40024002, 0x00007ffe, // ICON_BOX_CONCENTRIC + 0x00000000, 0x10841ffc, 0x10841084, 0x1ffc1084, 0x10841084, 0x10841084, 0x00001ffc, 0x00000000, // ICON_BOX_GRID_BIG + 0x00000000, 0x00000000, 0x10000000, 0x04000800, 0x01040200, 0x00500088, 0x00000020, 0x00000000, // ICON_OK_TICK + 0x00000000, 0x10080000, 0x04200810, 0x01800240, 0x02400180, 0x08100420, 0x00001008, 0x00000000, // ICON_CROSS + 0x00000000, 0x02000000, 0x00800100, 0x00200040, 0x00200010, 0x00800040, 0x02000100, 0x00000000, // ICON_ARROW_LEFT + 0x00000000, 0x00400000, 0x01000080, 0x04000200, 0x04000800, 0x01000200, 0x00400080, 0x00000000, // ICON_ARROW_RIGHT + 0x00000000, 0x00000000, 0x00000000, 0x08081004, 0x02200410, 0x00800140, 0x00000000, 0x00000000, // ICON_ARROW_DOWN + 0x00000000, 0x00000000, 0x01400080, 0x04100220, 0x10040808, 0x00000000, 0x00000000, 0x00000000, // ICON_ARROW_UP + 0x00000000, 0x02000000, 0x03800300, 0x03e003c0, 0x03e003f0, 0x038003c0, 0x02000300, 0x00000000, // ICON_ARROW_LEFT_FILL + 0x00000000, 0x00400000, 0x01c000c0, 0x07c003c0, 0x07c00fc0, 0x01c003c0, 0x004000c0, 0x00000000, // ICON_ARROW_RIGHT_FILL + 0x00000000, 0x00000000, 0x00000000, 0x0ff81ffc, 0x03e007f0, 0x008001c0, 0x00000000, 0x00000000, // ICON_ARROW_DOWN_FILL + 0x00000000, 0x00000000, 0x01c00080, 0x07f003e0, 0x1ffc0ff8, 0x00000000, 0x00000000, 0x00000000, // ICON_ARROW_UP_FILL + 0x00000000, 0x18a008c0, 0x32881290, 0x24822686, 0x26862482, 0x12903288, 0x08c018a0, 0x00000000, // ICON_AUDIO + 0x00000000, 0x04800780, 0x004000c0, 0x662000f0, 0x08103c30, 0x130a0e18, 0x0000318e, 0x00000000, // ICON_FX + 0x00000000, 0x00800000, 0x08880888, 0x2aaa0a8a, 0x0a8a2aaa, 0x08880888, 0x00000080, 0x00000000, // ICON_WAVE + 0x00000000, 0x00600000, 0x01080090, 0x02040108, 0x42044204, 0x24022402, 0x00001800, 0x00000000, // ICON_WAVE_SINUS + 0x00000000, 0x07f80000, 0x04080408, 0x04080408, 0x04080408, 0x7c0e0408, 0x00000000, 0x00000000, // ICON_WAVE_SQUARE + 0x00000000, 0x00000000, 0x00a00040, 0x22084110, 0x08021404, 0x00000000, 0x00000000, 0x00000000, // ICON_WAVE_TRIANGULAR + 0x00000000, 0x00000000, 0x04200000, 0x01800240, 0x02400180, 0x00000420, 0x00000000, 0x00000000, // ICON_CROSS_SMALL + 0x00000000, 0x18380000, 0x12281428, 0x10a81128, 0x112810a8, 0x14281228, 0x00001838, 0x00000000, // ICON_PLAYER_PREVIOUS + 0x00000000, 0x18000000, 0x11801600, 0x10181060, 0x10601018, 0x16001180, 0x00001800, 0x00000000, // ICON_PLAYER_PLAY_BACK + 0x00000000, 0x00180000, 0x01880068, 0x18080608, 0x06081808, 0x00680188, 0x00000018, 0x00000000, // ICON_PLAYER_PLAY + 0x00000000, 0x1e780000, 0x12481248, 0x12481248, 0x12481248, 0x12481248, 0x00001e78, 0x00000000, // ICON_PLAYER_PAUSE + 0x00000000, 0x1ff80000, 0x10081008, 0x10081008, 0x10081008, 0x10081008, 0x00001ff8, 0x00000000, // ICON_PLAYER_STOP + 0x00000000, 0x1c180000, 0x14481428, 0x15081488, 0x14881508, 0x14281448, 0x00001c18, 0x00000000, // ICON_PLAYER_NEXT + 0x00000000, 0x03c00000, 0x08100420, 0x10081008, 0x10081008, 0x04200810, 0x000003c0, 0x00000000, // ICON_PLAYER_RECORD + 0x00000000, 0x0c3007e0, 0x13c81818, 0x14281668, 0x14281428, 0x1c381c38, 0x08102244, 0x00000000, // ICON_MAGNET + 0x07c00000, 0x08200820, 0x3ff80820, 0x23882008, 0x21082388, 0x20082108, 0x1ff02008, 0x00000000, // ICON_LOCK_CLOSE + 0x07c00000, 0x08000800, 0x3ff80800, 0x23882008, 0x21082388, 0x20082108, 0x1ff02008, 0x00000000, // ICON_LOCK_OPEN + 0x01c00000, 0x0c180770, 0x3086188c, 0x60832082, 0x60034781, 0x30062002, 0x0c18180c, 0x01c00770, // ICON_CLOCK + 0x0a200000, 0x1b201b20, 0x04200e20, 0x04200420, 0x04700420, 0x0e700e70, 0x0e700e70, 0x04200e70, // ICON_TOOLS + 0x01800000, 0x3bdc318c, 0x0ff01ff8, 0x7c3e1e78, 0x1e787c3e, 0x1ff80ff0, 0x318c3bdc, 0x00000180, // ICON_GEAR + 0x01800000, 0x3ffc318c, 0x1c381ff8, 0x781e1818, 0x1818781e, 0x1ff81c38, 0x318c3ffc, 0x00000180, // ICON_GEAR_BIG + 0x00000000, 0x08080ff8, 0x08081ffc, 0x0aa80aa8, 0x0aa80aa8, 0x0aa80aa8, 0x08080aa8, 0x00000ff8, // ICON_BIN + 0x00000000, 0x00000000, 0x20043ffc, 0x08043f84, 0x04040f84, 0x04040784, 0x000007fc, 0x00000000, // ICON_HAND_POINTER + 0x00000000, 0x24400400, 0x00001480, 0x6efe0e00, 0x00000e00, 0x24401480, 0x00000400, 0x00000000, // ICON_LASER + 0x00000000, 0x03c00000, 0x08300460, 0x11181118, 0x11181118, 0x04600830, 0x000003c0, 0x00000000, // ICON_COIN + 0x00000000, 0x10880080, 0x06c00810, 0x366c07e0, 0x07e00240, 0x00001768, 0x04200240, 0x00000000, // ICON_EXPLOSION + 0x00000000, 0x3d280000, 0x2528252c, 0x3d282528, 0x05280528, 0x05e80528, 0x00000000, 0x00000000, // ICON_1UP + 0x01800000, 0x03c003c0, 0x018003c0, 0x0ff007e0, 0x0bd00bd0, 0x0a500bd0, 0x02400240, 0x02400240, // ICON_PLAYER + 0x01800000, 0x03c003c0, 0x118013c0, 0x03c81ff8, 0x07c003c8, 0x04400440, 0x0c080478, 0x00000000, // ICON_PLAYER_JUMP + 0x3ff80000, 0x30183ff8, 0x30183018, 0x3ff83ff8, 0x03000300, 0x03c003c0, 0x03e00300, 0x000003e0, // ICON_KEY + 0x3ff80000, 0x3ff83ff8, 0x33983ff8, 0x3ff83398, 0x3ff83ff8, 0x00000540, 0x0fe00aa0, 0x00000fe0, // ICON_DEMON + 0x00000000, 0x0ff00000, 0x20041008, 0x25442004, 0x10082004, 0x06000bf0, 0x00000300, 0x00000000, // ICON_TEXT_POPUP + 0x00000000, 0x11440000, 0x07f00be8, 0x1c1c0e38, 0x1c1c0c18, 0x07f00e38, 0x11440be8, 0x00000000, // ICON_GEAR_EX + 0x00000000, 0x20080000, 0x0c601010, 0x07c00fe0, 0x07c007c0, 0x0c600fe0, 0x20081010, 0x00000000, // ICON_CRACK + 0x00000000, 0x20080000, 0x0c601010, 0x04400fe0, 0x04405554, 0x0c600fe0, 0x20081010, 0x00000000, // ICON_CRACK_POINTS + 0x00000000, 0x00800080, 0x01c001c0, 0x1ffc3ffe, 0x03e007f0, 0x07f003e0, 0x0c180770, 0x00000808, // ICON_STAR + 0x0ff00000, 0x08180810, 0x08100818, 0x0a100810, 0x08180810, 0x08100818, 0x08100810, 0x00001ff8, // ICON_DOOR + 0x0ff00000, 0x08100810, 0x08100810, 0x10100010, 0x4f902010, 0x10102010, 0x08100010, 0x00000ff0, // ICON_EXIT + 0x00040000, 0x001f000e, 0x0ef40004, 0x12f41284, 0x0ef41214, 0x10040004, 0x7ffc3004, 0x10003000, // ICON_MODE_2D + 0x78040000, 0x501f600e, 0x0ef44004, 0x12f41284, 0x0ef41284, 0x10140004, 0x7ffc300c, 0x10003000, // ICON_MODE_3D + 0x7fe00000, 0x50286030, 0x47fe4804, 0x44224402, 0x44224422, 0x241275e2, 0x0c06140a, 0x000007fe, // ICON_CUBE + 0x7fe00000, 0x5ff87ff0, 0x47fe4ffc, 0x44224402, 0x44224422, 0x241275e2, 0x0c06140a, 0x000007fe, // ICON_CUBE_FACE_TOP + 0x7fe00000, 0x50386030, 0x47c2483c, 0x443e443e, 0x443e443e, 0x241e75fe, 0x0c06140e, 0x000007fe, // ICON_CUBE_FACE_LEFT + 0x7fe00000, 0x50286030, 0x47fe4804, 0x47fe47fe, 0x47fe47fe, 0x27fe77fe, 0x0ffe17fe, 0x000007fe, // ICON_CUBE_FACE_FRONT + 0x7fe00000, 0x50286030, 0x47fe4804, 0x44224402, 0x44224422, 0x3bf27be2, 0x0bfe1bfa, 0x000007fe, // ICON_CUBE_FACE_BOTTOM + 0x7fe00000, 0x70286030, 0x7ffe7804, 0x7c227c02, 0x7c227c22, 0x3c127de2, 0x0c061c0a, 0x000007fe, // ICON_CUBE_FACE_RIGHT + 0x7fe00000, 0x6fe85ff0, 0x781e77e4, 0x7be27be2, 0x7be27be2, 0x24127be2, 0x0c06140a, 0x000007fe, // ICON_CUBE_FACE_BACK + 0x00000000, 0x2a0233fe, 0x22022602, 0x22022202, 0x2a022602, 0x00a033fe, 0x02080110, 0x00000000, // ICON_CAMERA + 0x00000000, 0x200c3ffc, 0x000c000c, 0x3ffc000c, 0x30003000, 0x30003000, 0x3ffc3004, 0x00000000, // ICON_SPECIAL + 0x00000000, 0x0022003e, 0x012201e2, 0x0100013e, 0x01000100, 0x79000100, 0x4f004900, 0x00007800, // ICON_LINK_NET + 0x00000000, 0x44007c00, 0x45004600, 0x00627cbe, 0x00620022, 0x45007cbe, 0x44004600, 0x00007c00, // ICON_LINK_BOXES + 0x00000000, 0x0044007c, 0x0010007c, 0x3f100010, 0x3f1021f0, 0x3f100010, 0x3f0021f0, 0x00000000, // ICON_LINK_MULTI + 0x00000000, 0x0044007c, 0x00440044, 0x0010007c, 0x00100010, 0x44107c10, 0x440047f0, 0x00007c00, // ICON_LINK + 0x00000000, 0x0044007c, 0x00440044, 0x0000007c, 0x00000010, 0x44007c10, 0x44004550, 0x00007c00, // ICON_LINK_BROKE + 0x02a00000, 0x22a43ffc, 0x20042004, 0x20042ff4, 0x20042ff4, 0x20042ff4, 0x20042004, 0x00003ffc, // ICON_TEXT_NOTES + 0x3ffc0000, 0x20042004, 0x245e27c4, 0x27c42444, 0x2004201e, 0x201e2004, 0x20042004, 0x00003ffc, // ICON_NOTEBOOK + 0x00000000, 0x07e00000, 0x04200420, 0x24243ffc, 0x24242424, 0x24242424, 0x3ffc2424, 0x00000000, // ICON_SUITCASE + 0x00000000, 0x0fe00000, 0x08200820, 0x40047ffc, 0x7ffc5554, 0x40045554, 0x7ffc4004, 0x00000000, // ICON_SUITCASE_ZIP + 0x00000000, 0x20043ffc, 0x3ffc2004, 0x13c81008, 0x100813c8, 0x10081008, 0x1ff81008, 0x00000000, // ICON_MAILBOX + 0x00000000, 0x40027ffe, 0x5ffa5ffa, 0x5ffa5ffa, 0x40025ffa, 0x03c07ffe, 0x1ff81ff8, 0x00000000, // ICON_MONITOR + 0x0ff00000, 0x6bfe7ffe, 0x7ffe7ffe, 0x68167ffe, 0x08106816, 0x08100810, 0x0ff00810, 0x00000000, // ICON_PRINTER + 0x3ff80000, 0xfffe2008, 0x870a8002, 0x904a888a, 0x904a904a, 0x870a888a, 0xfffe8002, 0x00000000, // ICON_PHOTO_CAMERA + 0x0fc00000, 0xfcfe0cd8, 0x8002fffe, 0x84428382, 0x84428442, 0x80028382, 0xfffe8002, 0x00000000, // ICON_PHOTO_CAMERA_FLASH + 0x00000000, 0x02400180, 0x08100420, 0x20041008, 0x23c42004, 0x22442244, 0x3ffc2244, 0x00000000, // ICON_HOUSE + 0x00000000, 0x1c700000, 0x3ff83ef8, 0x3ff83ff8, 0x0fe01ff0, 0x038007c0, 0x00000100, 0x00000000, // ICON_HEART + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0xe000c000, // ICON_CORNER + 0x00000000, 0x14001c00, 0x15c01400, 0x15401540, 0x155c1540, 0x15541554, 0x1ddc1554, 0x00000000, // ICON_VERTICAL_BARS + 0x00000000, 0x03000300, 0x1b001b00, 0x1b601b60, 0x1b6c1b60, 0x1b6c1b6c, 0x1b6c1b6c, 0x00000000, // ICON_VERTICAL_BARS_FILL + 0x00000000, 0x00000000, 0x403e7ffe, 0x7ffe403e, 0x7ffe0000, 0x43fe43fe, 0x00007ffe, 0x00000000, // ICON_LIFE_BARS + 0x7ffc0000, 0x43844004, 0x43844284, 0x43844004, 0x42844284, 0x42844284, 0x40044384, 0x00007ffc, // ICON_INFO + 0x40008000, 0x10002000, 0x04000800, 0x01000200, 0x00400080, 0x00100020, 0x00040008, 0x00010002, // ICON_CROSSLINE + 0x00000000, 0x1ff01ff0, 0x18301830, 0x1f001830, 0x03001f00, 0x00000300, 0x03000300, 0x00000000, // ICON_HELP + 0x3ff00000, 0x2abc3550, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x00003ffc, // ICON_FILETYPE_ALPHA + 0x3ff00000, 0x201c2010, 0x22442184, 0x28142424, 0x29942814, 0x2ff42994, 0x20042004, 0x00003ffc, // ICON_FILETYPE_HOME + 0x07fe0000, 0x04020402, 0x7fe20402, 0x44224422, 0x44224422, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_LAYERS_VISIBLE + 0x07fe0000, 0x04020402, 0x7c020402, 0x44024402, 0x44024402, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_LAYERS + 0x00000000, 0x40027ffe, 0x7ffe4002, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_WINDOW + 0x09100000, 0x09f00910, 0x09100910, 0x00000910, 0x24a2779e, 0x27a224a2, 0x709e20a2, 0x00000000, // ICON_HIDPI + 0x3ff00000, 0x201c2010, 0x2a842e84, 0x2e842a84, 0x2ba42004, 0x2aa42aa4, 0x20042ba4, 0x00003ffc, // ICON_FILETYPE_BINARY + 0x00000000, 0x00000000, 0x00120012, 0x4a5e4bd2, 0x485233d2, 0x00004bd2, 0x00000000, 0x00000000, // ICON_HEX + 0x01800000, 0x381c0660, 0x23c42004, 0x23c42044, 0x13c82204, 0x08101008, 0x02400420, 0x00000180, // ICON_SHIELD + 0x007e0000, 0x20023fc2, 0x40227fe2, 0x400a403a, 0x400a400a, 0x400a400a, 0x4008400e, 0x00007ff8, // ICON_FILE_NEW + 0x00000000, 0x0042007e, 0x40027fc2, 0x44024002, 0x5f024402, 0x44024402, 0x7ffe4002, 0x00000000, // ICON_FOLDER_ADD + 0x44220000, 0x12482244, 0xf3cf0000, 0x14280420, 0x48122424, 0x08100810, 0x1ff81008, 0x03c00420, // ICON_ALARM + 0x0aa00000, 0x1ff80aa0, 0x1068700e, 0x1008706e, 0x1008700e, 0x1008700e, 0x0aa01ff8, 0x00000aa0, // ICON_CPU + 0x07e00000, 0x04201db8, 0x04a01c38, 0x04a01d38, 0x04a01d38, 0x04a01d38, 0x04201d38, 0x000007e0, // ICON_ROM + 0x00000000, 0x03c00000, 0x3c382ff0, 0x3c04380c, 0x01800000, 0x03c003c0, 0x00000180, 0x00000000, // ICON_STEP_OVER + 0x01800000, 0x01800180, 0x01800180, 0x03c007e0, 0x00000180, 0x01800000, 0x03c003c0, 0x00000180, // ICON_STEP_INTO + 0x01800000, 0x07e003c0, 0x01800180, 0x01800180, 0x00000180, 0x01800000, 0x03c003c0, 0x00000180, // ICON_STEP_OUT + 0x00000000, 0x0ff003c0, 0x181c1c34, 0x303c301c, 0x30003000, 0x1c301800, 0x03c00ff0, 0x00000000, // ICON_RESTART + 0x00000000, 0x00000000, 0x07e003c0, 0x0ff00ff0, 0x0ff00ff0, 0x03c007e0, 0x00000000, 0x00000000, // ICON_BREAKPOINT_ON + 0x00000000, 0x00000000, 0x042003c0, 0x08100810, 0x08100810, 0x03c00420, 0x00000000, 0x00000000, // ICON_BREAKPOINT_OFF + 0x00000000, 0x00000000, 0x1ff81ff8, 0x1ff80000, 0x00001ff8, 0x1ff81ff8, 0x00000000, 0x00000000, // ICON_BURGER_MENU + 0x00000000, 0x00000000, 0x00880070, 0x0c880088, 0x1e8810f8, 0x3e881288, 0x00000000, 0x00000000, // ICON_CASE_SENSITIVE + 0x00000000, 0x02000000, 0x07000a80, 0x07001fc0, 0x02000a80, 0x00300030, 0x00000000, 0x00000000, // ICON_REG_EXP + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_FOLDER + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x00003ffc, // ICON_FILE + 0x1ff00000, 0x20082008, 0x17d02fe8, 0x05400ba0, 0x09200540, 0x23881010, 0x2fe827c8, 0x00001ff0, // ICON_SAND_TIMER + 0x01800000, 0x02400240, 0x05a00420, 0x09900990, 0x11881188, 0x21842004, 0x40024182, 0x00003ffc, // ICON_WARNING + 0x7ffe0000, 0x4ff24002, 0x4c324ff2, 0x4f824c02, 0x41824f82, 0x41824002, 0x40024182, 0x00007ffe, // ICON_HELP_BOX + 0x7ffe0000, 0x41824002, 0x40024182, 0x41824182, 0x41824182, 0x41824182, 0x40024182, 0x00007ffe, // ICON_INFO_BOX + 0x01800000, 0x04200240, 0x10080810, 0x7bde2004, 0x0a500a50, 0x08500bd0, 0x08100850, 0x00000ff0, // ICON_PRIORITY + 0x01800000, 0x18180660, 0x80016006, 0x98196006, 0x99996666, 0x19986666, 0x01800660, 0x00000000, // ICON_LAYERS_ISO + 0x07fe0000, 0x1c020402, 0x74021402, 0x54025402, 0x54025402, 0x500857fe, 0x40205ff8, 0x00007fe0, // ICON_LAYERS2 + 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x422a422a, 0x422e422a, 0x40384e28, 0x00007fe0, // ICON_MLAYERS + 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x5b2a512a, 0x512e552a, 0x40385128, 0x00007fe0, // ICON_MAPS + 0x04200000, 0x1cf00c60, 0x11f019f0, 0x0f3807b8, 0x1e3c0f3c, 0x1c1c1e1c, 0x1e3c1c1c, 0x00000f70, // ICON_HOT + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_229 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_230 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_231 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_232 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_233 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_234 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_235 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_236 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_237 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_238 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_239 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_240 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_241 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_242 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_243 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_244 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_245 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_246 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_247 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_248 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_249 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_250 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_251 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_252 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_253 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_254 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_255 +}; + +// NOTE: A pointer to current icons array should be defined +static unsigned int *guiIconsPtr = guiIcons; + +#endif // !RAYGUI_NO_ICONS && !RAYGUI_CUSTOM_ICONS + +#ifndef RAYGUI_ICON_SIZE + #define RAYGUI_ICON_SIZE 0 +#endif + +// WARNING: Those values define the total size of the style data array, +// if changed, previous saved styles could become incompatible +#define RAYGUI_MAX_CONTROLS 16 // Maximum number of controls +#define RAYGUI_MAX_PROPS_BASE 16 // Maximum number of base properties +#define RAYGUI_MAX_PROPS_EXTENDED 8 // Maximum number of extended properties + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Gui control property style color element +typedef enum { BORDER = 0, BASE, TEXT, OTHER } GuiPropertyElement; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static GuiState guiState = STATE_NORMAL; // Gui global state, if !STATE_NORMAL, forces defined state + +static Font guiFont = { 0 }; // Gui current font (WARNING: highly coupled to raylib) +static bool guiLocked = false; // Gui lock state (no inputs processed) +static float guiAlpha = 1.0f; // Gui controls transparency + +static unsigned int guiIconScale = 1; // Gui icon default scale (if icons enabled) + +static bool guiTooltip = false; // Tooltip enabled/disabled +static const char *guiTooltipPtr = NULL; // Tooltip string pointer (string provided by user) + +static bool guiControlExclusiveMode = false; // Gui control exclusive mode (no inputs processed except current control) +static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds rectangle, used as an unique identifier + +static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*() +//static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking +static int autoCursorCooldownCounter = 0; // Cooldown frame counter for automatic cursor movement on key-down +static int autoCursorDelayCounter = 0; // Delay frame counter for automatic cursor movement + +//---------------------------------------------------------------------------------- +// Style data array for all gui style properties (allocated on data segment by default) +// +// NOTE 1: First set of BASE properties are generic to all controls but could be individually +// overwritten per control, first set of EXTENDED properties are generic to all controls and +// can not be overwritten individually but custom EXTENDED properties can be used by control +// +// NOTE 2: A new style set could be loaded over this array using GuiLoadStyle(), +// but default gui style could always be recovered with GuiLoadStyleDefault() +// +// guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB +//---------------------------------------------------------------------------------- +static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)] = { 0 }; + +static bool guiStyleLoaded = false; // Style loaded flag for lazy style initialization + +//---------------------------------------------------------------------------------- +// Standalone Mode Functions Declaration +// +// NOTE: raygui depend on some raylib input and drawing functions +// To use raygui as standalone library, below functions must be defined by the user +//---------------------------------------------------------------------------------- +#if defined(RAYGUI_STANDALONE) + +#define KEY_RIGHT 262 +#define KEY_LEFT 263 +#define KEY_DOWN 264 +#define KEY_UP 265 +#define KEY_BACKSPACE 259 +#define KEY_ENTER 257 + +#define MOUSE_LEFT_BUTTON 0 + +// Input required functions +//------------------------------------------------------------------------------- +static Vector2 GetMousePosition(void); +static float GetMouseWheelMove(void); +static bool IsMouseButtonDown(int button); +static bool IsMouseButtonPressed(int button); +static bool IsMouseButtonReleased(int button); + +static bool IsKeyDown(int key); +static bool IsKeyPressed(int key); +static int GetCharPressed(void); // -- GuiTextBox(), GuiValueBox() +//------------------------------------------------------------------------------- + +// Drawing required functions +//------------------------------------------------------------------------------- +static void DrawRectangle(int x, int y, int width, int height, Color color); // -- GuiDrawRectangle() +static void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // -- GuiColorPicker() +//------------------------------------------------------------------------------- + +// Text required functions +//------------------------------------------------------------------------------- +static Font GetFontDefault(void); // -- GuiLoadStyleDefault() +static Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // -- GuiLoadStyle(), load font + +static Texture2D LoadTextureFromImage(Image image); // -- GuiLoadStyle(), required to load texture from embedded font atlas image +static void SetShapesTexture(Texture2D tex, Rectangle rec); // -- GuiLoadStyle(), required to set shapes rec to font white rec (optimization) + +static char *LoadFileText(const char *fileName); // -- GuiLoadStyle(), required to load charset data +static void UnloadFileText(char *text); // -- GuiLoadStyle(), required to unload charset data + +static const char *GetDirectoryPath(const char *filePath); // -- GuiLoadStyle(), required to find charset/font file from text .rgs + +static int *LoadCodepoints(const char *text, int *count); // -- GuiLoadStyle(), required to load required font codepoints list +static void UnloadCodepoints(int *codepoints); // -- GuiLoadStyle(), required to unload codepoints list + +static unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // -- GuiLoadStyle() +//------------------------------------------------------------------------------- + +// raylib functions already implemented in raygui +//------------------------------------------------------------------------------- +static Color GetColor(int hexValue); // Returns a Color struct from hexadecimal value +static int ColorToInt(Color color); // Returns hexadecimal value for a Color +static bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle +static const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed' +static const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings +static int TextToInteger(const char *text); // Get integer value from text +static float TextToFloat(const char *text); // Get float value from text + +static int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded text +static const char *CodepointToUTF8(int codepoint, int *byteSize); // Encode codepoint into UTF-8 text (char array size returned as parameter) + +static void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2); // Draw rectangle vertical gradient +//------------------------------------------------------------------------------- + +#endif // RAYGUI_STANDALONE + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize); // Load style from memory (binary only) + +static int GetTextWidth(const char *text); // Gui get text width using gui font and style +static Rectangle GetTextBounds(int control, Rectangle bounds); // Get text bounds considering control bounds +static const char *GetTextIcon(const char *text, int *iconId); // Get text icon if provided and move text cursor + +static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, Color tint); // Gui draw text using default font +static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, Color color); // Gui draw rectangle using default raygui style + +static const char **GuiTextSplit(const char *text, char delimiter, int *count, int *textRow); // Split controls text into multiple strings +static Vector3 ConvertHSVtoRGB(Vector3 hsv); // Convert color data from HSV to RGB +static Vector3 ConvertRGBtoHSV(Vector3 rgb); // Convert color data from RGB to HSV + +static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue); // Scroll bar control, used by GuiScrollPanel() +static void GuiTooltip(Rectangle controlRec); // Draw tooltip using control rec position + +static Color GuiFade(Color color, float alpha); // Fade color by an alpha factor + +//---------------------------------------------------------------------------------- +// Gui Setup Functions Definition +//---------------------------------------------------------------------------------- +// Enable gui global state +// NOTE: We check for STATE_DISABLED to avoid messing custom global state setups +void GuiEnable(void) { if (guiState == STATE_DISABLED) guiState = STATE_NORMAL; } + +// Disable gui global state +// NOTE: We check for STATE_NORMAL to avoid messing custom global state setups +void GuiDisable(void) { if (guiState == STATE_NORMAL) guiState = STATE_DISABLED; } + +// Lock gui global state +void GuiLock(void) { guiLocked = true; } + +// Unlock gui global state +void GuiUnlock(void) { guiLocked = false; } + +// Check if gui is locked (global state) +bool GuiIsLocked(void) { return guiLocked; } + +// Set gui controls alpha global state +void GuiSetAlpha(float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + guiAlpha = alpha; +} + +// Set gui state (global state) +void GuiSetState(int state) { guiState = (GuiState)state; } + +// Get gui state (global state) +int GuiGetState(void) { return guiState; } + +// Set custom gui font +// NOTE: Font loading/unloading is external to raygui +void GuiSetFont(Font font) +{ + if (font.texture.id > 0) + { + // NOTE: If we try to setup a font but default style has not been + // lazily loaded before, it will be overwritten, so we need to force + // default style loading first + if (!guiStyleLoaded) GuiLoadStyleDefault(); + + guiFont = font; + } +} + +// Get custom gui font +Font GuiGetFont(void) +{ + return guiFont; +} + +// Set control style property value +void GuiSetStyle(int control, int property, int value) +{ + if (!guiStyleLoaded) GuiLoadStyleDefault(); + guiStyle[control*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property] = value; + + // Default properties are propagated to all controls + if ((control == 0) && (property < RAYGUI_MAX_PROPS_BASE)) + { + for (int i = 1; i < RAYGUI_MAX_CONTROLS; i++) guiStyle[i*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property] = value; + } +} + +// Get control style property value +int GuiGetStyle(int control, int property) +{ + if (!guiStyleLoaded) GuiLoadStyleDefault(); + return guiStyle[control*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property]; +} + +//---------------------------------------------------------------------------------- +// Gui Controls Functions Definition +//---------------------------------------------------------------------------------- + +// Window Box control +int GuiWindowBox(Rectangle bounds, const char *title) +{ + // Window title bar height (including borders) + // NOTE: This define is also used by GuiMessageBox() and GuiTextInputBox() + #if !defined(RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT) + #define RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT 24 + #endif + + int result = 0; + //GuiState state = guiState; + + int statusBarHeight = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT; + + Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)statusBarHeight }; + if (bounds.height < statusBarHeight*2.0f) bounds.height = statusBarHeight*2.0f; + + Rectangle windowPanel = { bounds.x, bounds.y + (float)statusBarHeight - 1, bounds.width, bounds.height - (float)statusBarHeight + 1 }; + Rectangle closeButtonRec = { statusBar.x + statusBar.width - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - 20, + statusBar.y + statusBarHeight/2.0f - 18.0f/2.0f, 18, 18 }; + + // Update control + //-------------------------------------------------------------------- + // NOTE: Logic is directly managed by button + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiStatusBar(statusBar, title); // Draw window header as status bar + GuiPanel(windowPanel, NULL); // Draw window base + + // Draw window close button + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); +#if defined(RAYGUI_NO_ICONS) + result = GuiButton(closeButtonRec, "x"); +#else + result = GuiButton(closeButtonRec, GuiIconText(ICON_CROSS_SMALL, NULL)); +#endif + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlignment); + //-------------------------------------------------------------------- + + return result; // Window close button clicked: result = 1 +} + +// Group Box control with text name +int GuiGroupBox(Rectangle bounds, const char *text) +{ + #if !defined(RAYGUI_GROUPBOX_LINE_THICK) + #define RAYGUI_GROUPBOX_LINE_THICK 1 + #endif + + int result = 0; + GuiState state = guiState; + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, RAYGUI_GROUPBOX_LINE_THICK }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + + GuiLine(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y - GuiGetStyle(DEFAULT, TEXT_SIZE)/2, bounds.width, (float)GuiGetStyle(DEFAULT, TEXT_SIZE) }, text); + //-------------------------------------------------------------------- + + return result; +} + +// Line control +int GuiLine(Rectangle bounds, const char *text) +{ + #if !defined(RAYGUI_LINE_ORIGIN_SIZE) + #define RAYGUI_LINE_MARGIN_TEXT 12 + #endif + #if !defined(RAYGUI_LINE_TEXT_PADDING) + #define RAYGUI_LINE_TEXT_PADDING 4 + #endif + + int result = 0; + GuiState state = guiState; + + Color color = GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR)); + + // Draw control + //-------------------------------------------------------------------- + if (text == NULL) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height/2, bounds.width, 1 }, 0, BLANK, color); + else + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = bounds.height; + textBounds.x = bounds.x + RAYGUI_LINE_MARGIN_TEXT; + textBounds.y = bounds.y; + + // Draw line with embedded text label: "--- text --------------" + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height/2, RAYGUI_LINE_MARGIN_TEXT - RAYGUI_LINE_TEXT_PADDING, 1 }, 0, BLANK, color); + GuiDrawText(text, textBounds, TEXT_ALIGN_LEFT, color); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + 12 + textBounds.width + 4, bounds.y + bounds.height/2, bounds.width - textBounds.width - RAYGUI_LINE_MARGIN_TEXT - RAYGUI_LINE_TEXT_PADDING, 1 }, 0, BLANK, color); + } + //-------------------------------------------------------------------- + + return result; +} + +// Panel control +int GuiPanel(Rectangle bounds, const char *text) +{ + #if !defined(RAYGUI_PANEL_BORDER_WIDTH) + #define RAYGUI_PANEL_BORDER_WIDTH 1 + #endif + + int result = 0; + GuiState state = guiState; + + // Text will be drawn as a header bar (if provided) + Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT }; + if ((text != NULL) && (bounds.height < RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f)) bounds.height = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f; + + if (text != NULL) + { + // Move panel bounds after the header bar + bounds.y += (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; + bounds.height -= (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; + } + + // Draw control + //-------------------------------------------------------------------- + if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar + + GuiDrawRectangle(bounds, RAYGUI_PANEL_BORDER_WIDTH, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED: (int)LINE_COLOR)), + GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? BASE_COLOR_DISABLED : BACKGROUND_COLOR))); + //-------------------------------------------------------------------- + + return result; +} + +// Tab Bar control +// NOTE: Using GuiToggle() for the TABS +int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) +{ + #define RAYGUI_TABBAR_ITEM_WIDTH 160 + + int result = -1; + //GuiState state = guiState; + + Rectangle tabBounds = { bounds.x, bounds.y, RAYGUI_TABBAR_ITEM_WIDTH, bounds.height }; + + if (*active < 0) *active = 0; + else if (*active > count - 1) *active = count - 1; + + int offsetX = 0; // Required in case tabs go out of screen + offsetX = (*active + 2)*RAYGUI_TABBAR_ITEM_WIDTH - GetScreenWidth(); + if (offsetX < 0) offsetX = 0; + + bool toggle = false; // Required for individual toggles + + // Draw control + //-------------------------------------------------------------------- + for (int i = 0; i < count; i++) + { + tabBounds.x = bounds.x + (RAYGUI_TABBAR_ITEM_WIDTH + 4)*i - offsetX; + + if (tabBounds.x < GetScreenWidth()) + { + // Draw tabs as toggle controls + int textAlignment = GuiGetStyle(TOGGLE, TEXT_ALIGNMENT); + int textPadding = GuiGetStyle(TOGGLE, TEXT_PADDING); + GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(TOGGLE, TEXT_PADDING, 8); + + if (i == (*active)) + { + toggle = true; + GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); + } + else + { + toggle = false; + GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); + if (toggle) *active = i; + } + + // Close tab with middle mouse button pressed + if (CheckCollisionPointRec(GetMousePosition(), tabBounds) && IsMouseButtonPressed(MOUSE_MIDDLE_BUTTON)) result = i; + + GuiSetStyle(TOGGLE, TEXT_PADDING, textPadding); + GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, textAlignment); + + // Draw tab close button + // NOTE: Only draw close button for current tab: if (CheckCollisionPointRec(mousePosition, tabBounds)) + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); +#if defined(RAYGUI_NO_ICONS) + if (GuiButton(RAYGUI_CLITERAL(Rectangle){ tabBounds.x + tabBounds.width - 14 - 5, tabBounds.y + 5, 14, 14 }, "x")) result = i; +#else + if (GuiButton(RAYGUI_CLITERAL(Rectangle){ tabBounds.x + tabBounds.width - 14 - 5, tabBounds.y + 5, 14, 14 }, GuiIconText(ICON_CROSS_SMALL, NULL))) result = i; +#endif + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlignment); + } + } + + // Draw tab-bar bottom line + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, 1 }, 0, BLANK, GetColor(GuiGetStyle(TOGGLE, BORDER_COLOR_NORMAL))); + //-------------------------------------------------------------------- + + return result; // Return as result the current TAB closing requested +} + +// Scroll Panel control +int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view) +{ + #define RAYGUI_MIN_SCROLLBAR_WIDTH 40 + #define RAYGUI_MIN_SCROLLBAR_HEIGHT 40 + #define RAYGUI_MIN_MOUSE_WHEEL_SPEED 20 + + int result = 0; + GuiState state = guiState; + + Rectangle temp = { 0 }; + if (view == NULL) view = &temp; + + Vector2 scrollPos = { 0.0f, 0.0f }; + if (scroll != NULL) scrollPos = *scroll; + + // Text will be drawn as a header bar (if provided) + Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT }; + if (bounds.height < RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f) bounds.height = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f; + + if (text != NULL) + { + // Move panel bounds after the header bar + bounds.y += (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; + bounds.height -= (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + 1; + } + + bool hasHorizontalScrollBar = (content.width > bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH))? true : false; + bool hasVerticalScrollBar = (content.height > bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH))? true : false; + + // Recheck to account for the other scrollbar being visible + if (!hasHorizontalScrollBar) hasHorizontalScrollBar = (hasVerticalScrollBar && (content.width > (bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH))))? true : false; + if (!hasVerticalScrollBar) hasVerticalScrollBar = (hasHorizontalScrollBar && (content.height > (bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH))))? true : false; + + int horizontalScrollBarWidth = hasHorizontalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; + int verticalScrollBarWidth = hasVerticalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; + Rectangle horizontalScrollBar = { + (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + verticalScrollBarWidth : (float)bounds.x) + GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)bounds.y + bounds.height - horizontalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)bounds.width - verticalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)horizontalScrollBarWidth + }; + Rectangle verticalScrollBar = { + (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)bounds.x + bounds.width - verticalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH)), + (float)bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)verticalScrollBarWidth, + (float)bounds.height - horizontalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) + }; + + // Make sure scroll bars have a minimum width/height + if (horizontalScrollBar.width < RAYGUI_MIN_SCROLLBAR_WIDTH) horizontalScrollBar.width = RAYGUI_MIN_SCROLLBAR_WIDTH; + if (verticalScrollBar.height < RAYGUI_MIN_SCROLLBAR_HEIGHT) verticalScrollBar.height = RAYGUI_MIN_SCROLLBAR_HEIGHT; + + // Calculate view area (area without the scrollbars) + *view = (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? + RAYGUI_CLITERAL(Rectangle){ bounds.x + verticalScrollBarWidth + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth } : + RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth }; + + // Clip view area to the actual content size + if (view->width > content.width) view->width = content.width; + if (view->height > content.height) view->height = content.height; + + float horizontalMin = hasHorizontalScrollBar? ((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)-verticalScrollBarWidth : 0) - (float)GuiGetStyle(DEFAULT, BORDER_WIDTH) : (((float)GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)-verticalScrollBarWidth : 0) - (float)GuiGetStyle(DEFAULT, BORDER_WIDTH); + float horizontalMax = hasHorizontalScrollBar? content.width - bounds.width + (float)verticalScrollBarWidth + GuiGetStyle(DEFAULT, BORDER_WIDTH) - (((float)GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)verticalScrollBarWidth : 0) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); + float verticalMin = hasVerticalScrollBar? 0.0f : -1.0f; + float verticalMax = hasVerticalScrollBar? content.height - bounds.height + (float)horizontalScrollBarWidth + (float)GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + +#if defined(SUPPORT_SCROLLBAR_KEY_INPUT) + if (hasHorizontalScrollBar) + { + if (IsKeyDown(KEY_RIGHT)) scrollPos.x -= GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + if (IsKeyDown(KEY_LEFT)) scrollPos.x += GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + } + + if (hasVerticalScrollBar) + { + if (IsKeyDown(KEY_DOWN)) scrollPos.y -= GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + if (IsKeyDown(KEY_UP)) scrollPos.y += GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + } +#endif + float wheelMove = GetMouseWheelMove(); + + // Set scrolling speed with mouse wheel based on ratio between bounds and content + Vector2 mouseWheelSpeed = { content.width/bounds.width, content.height/bounds.height }; + if (mouseWheelSpeed.x < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.x = RAYGUI_MIN_MOUSE_WHEEL_SPEED; + if (mouseWheelSpeed.y < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.y = RAYGUI_MIN_MOUSE_WHEEL_SPEED; + + // Horizontal and vertical scrolling with mouse wheel + if (hasHorizontalScrollBar && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_LEFT_SHIFT))) scrollPos.x += wheelMove*mouseWheelSpeed.x; + else scrollPos.y += wheelMove*mouseWheelSpeed.y; // Vertical scroll + } + } + + // Normalize scroll values + if (scrollPos.x > -horizontalMin) scrollPos.x = -horizontalMin; + if (scrollPos.x < -horizontalMax) scrollPos.x = -horizontalMax; + if (scrollPos.y > -verticalMin) scrollPos.y = -verticalMin; + if (scrollPos.y < -verticalMax) scrollPos.y = -verticalMax; + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar + + GuiDrawRectangle(bounds, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background + + // Save size of the scrollbar slider + const int slider = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + + // Draw horizontal scrollbar if visible + if (hasHorizontalScrollBar) + { + // Change scrollbar slider size to show the diff in size between the content width and the widget width + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)(((bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth)/(int)content.width)*((int)bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth))); + scrollPos.x = (float)-GuiScrollBar(horizontalScrollBar, (int)-scrollPos.x, (int)horizontalMin, (int)horizontalMax); + } + else scrollPos.x = 0.0f; + + // Draw vertical scrollbar if visible + if (hasVerticalScrollBar) + { + // Change scrollbar slider size to show the diff in size between the content height and the widget height + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)(((bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth)/(int)content.height)*((int)bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth))); + scrollPos.y = (float)-GuiScrollBar(verticalScrollBar, (int)-scrollPos.y, (int)verticalMin, (int)verticalMax); + } + else scrollPos.y = 0.0f; + + // Draw detail corner rectangle if both scroll bars are visible + if (hasHorizontalScrollBar && hasVerticalScrollBar) + { + Rectangle corner = { (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) + 2) : (horizontalScrollBar.x + horizontalScrollBar.width + 2), verticalScrollBar.y + verticalScrollBar.height + 2, (float)horizontalScrollBarWidth - 4, (float)verticalScrollBarWidth - 4 }; + GuiDrawRectangle(corner, 0, BLANK, GetColor(GuiGetStyle(LISTVIEW, TEXT + (state*3)))); + } + + // Draw scrollbar lines depending on current state + GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + (state*3))), BLANK); + + // Set scrollbar slider size back to the way it was before + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, slider); + //-------------------------------------------------------------------- + + if (scroll != NULL) *scroll = scrollPos; + + return result; +} + +// Label control +int GuiLabel(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Update control + //-------------------------------------------------------------------- + //... + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawText(text, GetTextBounds(LABEL, bounds), GuiGetStyle(LABEL, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Button control, returns true when clicked +int GuiButton(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(BUTTON, BORDER_WIDTH), GetColor(GuiGetStyle(BUTTON, BORDER + (state*3))), GetColor(GuiGetStyle(BUTTON, BASE + (state*3)))); + GuiDrawText(text, GetTextBounds(BUTTON, bounds), GuiGetStyle(BUTTON, TEXT_ALIGNMENT), GetColor(GuiGetStyle(BUTTON, TEXT + (state*3)))); + + if (state == STATE_FOCUSED) GuiTooltip(bounds); + //------------------------------------------------------------------ + + return result; // Button pressed: result = 1 +} + +// Label button control +int GuiLabelButton(Rectangle bounds, const char *text) +{ + GuiState state = guiState; + bool pressed = false; + + // NOTE: We force bounds.width to be all text + float textWidth = (float)GetTextWidth(text); + if ((bounds.width - 2*GuiGetStyle(LABEL, BORDER_WIDTH) - 2*GuiGetStyle(LABEL, TEXT_PADDING)) < textWidth) bounds.width = textWidth + 2*GuiGetStyle(LABEL, BORDER_WIDTH) + 2*GuiGetStyle(LABEL, TEXT_PADDING) + 2; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check checkbox state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) pressed = true; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawText(text, GetTextBounds(LABEL, bounds), GuiGetStyle(LABEL, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return pressed; +} + +// Toggle Button control +int GuiToggle(Rectangle bounds, const char *text, bool *active) +{ + int result = 0; + GuiState state = guiState; + + bool temp = false; + if (active == NULL) active = &temp; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check toggle button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + state = STATE_NORMAL; + *active = !(*active); + } + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_NORMAL) + { + GuiDrawRectangle(bounds, GuiGetStyle(TOGGLE, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, ((*active)? BORDER_COLOR_PRESSED : (BORDER + state*3)))), GetColor(GuiGetStyle(TOGGLE, ((*active)? BASE_COLOR_PRESSED : (BASE + state*3))))); + GuiDrawText(text, GetTextBounds(TOGGLE, bounds), GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TOGGLE, ((*active)? TEXT_COLOR_PRESSED : (TEXT + state*3))))); + } + else + { + GuiDrawRectangle(bounds, GuiGetStyle(TOGGLE, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, BORDER + state*3)), GetColor(GuiGetStyle(TOGGLE, BASE + state*3))); + GuiDrawText(text, GetTextBounds(TOGGLE, bounds), GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TOGGLE, TEXT + state*3))); + } + + if (state == STATE_FOCUSED) GuiTooltip(bounds); + //-------------------------------------------------------------------- + + return result; +} + +// Toggle Group control +int GuiToggleGroup(Rectangle bounds, const char *text, int *active) +{ + #if !defined(RAYGUI_TOGGLEGROUP_MAX_ITEMS) + #define RAYGUI_TOGGLEGROUP_MAX_ITEMS 32 + #endif + + int result = 0; + float initBoundsX = bounds.x; + + int temp = 0; + if (active == NULL) active = &temp; + + bool toggle = false; // Required for individual toggles + + // Get substrings items from text (items pointers) + int rows[RAYGUI_TOGGLEGROUP_MAX_ITEMS] = { 0 }; + int itemCount = 0; + const char **items = GuiTextSplit(text, ';', &itemCount, rows); + + int prevRow = rows[0]; + + for (int i = 0; i < itemCount; i++) + { + if (prevRow != rows[i]) + { + bounds.x = initBoundsX; + bounds.y += (bounds.height + GuiGetStyle(TOGGLE, GROUP_PADDING)); + prevRow = rows[i]; + } + + if (i == (*active)) + { + toggle = true; + GuiToggle(bounds, items[i], &toggle); + } + else + { + toggle = false; + GuiToggle(bounds, items[i], &toggle); + if (toggle) *active = i; + } + + bounds.x += (bounds.width + GuiGetStyle(TOGGLE, GROUP_PADDING)); + } + + return result; +} + +// Toggle Slider control extended +int GuiToggleSlider(Rectangle bounds, const char *text, int *active) +{ + int result = 0; + GuiState state = guiState; + + int temp = 0; + if (active == NULL) active = &temp; + + //bool toggle = false; // Required for individual toggles + + // Get substrings items from text (items pointers) + int itemCount = 0; + const char **items = GuiTextSplit(text, ';', &itemCount, NULL); + + Rectangle slider = { + 0, // Calculated later depending on the active toggle + bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), + (bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - (itemCount + 1)*GuiGetStyle(SLIDER, SLIDER_PADDING))/itemCount, + bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + (*active)++; + result = 1; + } + else state = STATE_FOCUSED; + } + + if ((*active) && (state != STATE_FOCUSED)) state = STATE_PRESSED; + } + + if (*active >= itemCount) *active = 0; + slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH) + (*active + 1)*GuiGetStyle(SLIDER, SLIDER_PADDING) + (*active)*slider.width; + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, BORDER + (state*3))), + GetColor(GuiGetStyle(TOGGLE, BASE_COLOR_NORMAL))); + + // Draw internal slider + if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); + else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_FOCUSED))); + else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); + + // Draw text in slider + if (text != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(text); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = slider.x + slider.width/2 - textBounds.width/2; + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(items[*active], textBounds, GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(TOGGLE, TEXT + (state*3))), guiAlpha)); + } + //-------------------------------------------------------------------- + + return result; +} + +// Check Box control, returns 1 when state changed +int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) +{ + int result = 0; + GuiState state = guiState; + + bool temp = false; + if (checked == NULL) checked = &temp; + + Rectangle textBounds = { 0 }; + + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(CHECKBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + Rectangle totalBounds = { + (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT)? textBounds.x : bounds.x, + bounds.y, + bounds.width + textBounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING), + bounds.height, + }; + + // Check checkbox state + if (CheckCollisionPointRec(mousePoint, totalBounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + *checked = !(*checked); + result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(CHECKBOX, BORDER_WIDTH), GetColor(GuiGetStyle(CHECKBOX, BORDER + (state*3))), BLANK); + + if (*checked) + { + Rectangle check = { bounds.x + GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING), + bounds.y + GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING), + bounds.width - 2*(GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING)), + bounds.height - 2*(GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING)) }; + GuiDrawRectangle(check, 0, BLANK, GetColor(GuiGetStyle(CHECKBOX, TEXT + state*3))); + } + + GuiDrawText(text, textBounds, (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Combo Box control +int GuiComboBox(Rectangle bounds, const char *text, int *active) +{ + int result = 0; + GuiState state = guiState; + + int temp = 0; + if (active == NULL) active = &temp; + + bounds.width -= (GuiGetStyle(COMBOBOX, COMBO_BUTTON_WIDTH) + GuiGetStyle(COMBOBOX, COMBO_BUTTON_SPACING)); + + Rectangle selector = { (float)bounds.x + bounds.width + GuiGetStyle(COMBOBOX, COMBO_BUTTON_SPACING), + (float)bounds.y, (float)GuiGetStyle(COMBOBOX, COMBO_BUTTON_WIDTH), (float)bounds.height }; + + // Get substrings items from text (items pointers, lengths and count) + int itemCount = 0; + const char **items = GuiTextSplit(text, ';', &itemCount, NULL); + + if (*active < 0) *active = 0; + else if (*active > (itemCount - 1)) *active = itemCount - 1; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && (itemCount > 1) && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds) || + CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + *active += 1; + if (*active >= itemCount) *active = 0; // Cyclic combobox + } + + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + // Draw combo box main + GuiDrawRectangle(bounds, GuiGetStyle(COMBOBOX, BORDER_WIDTH), GetColor(GuiGetStyle(COMBOBOX, BORDER + (state*3))), GetColor(GuiGetStyle(COMBOBOX, BASE + (state*3)))); + GuiDrawText(items[*active], GetTextBounds(COMBOBOX, bounds), GuiGetStyle(COMBOBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(COMBOBOX, TEXT + (state*3)))); + + // Draw selector using a custom button + // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + GuiButton(selector, TextFormat("%i/%i", *active + 1, itemCount)); + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + //-------------------------------------------------------------------- + + return result; +} + +// Dropdown Box control +// NOTE: Returns mouse click +int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode) +{ + int result = 0; + GuiState state = guiState; + + int temp = 0; + if (active == NULL) active = &temp; + + int itemSelected = *active; + int itemFocused = -1; + + int direction = 0; // Dropdown box open direction: down (default) + if (GuiGetStyle(DROPDOWNBOX, DROPDOWN_ROLL_UP) == 1) direction = 1; // Up + + // Get substrings items from text (items pointers, lengths and count) + int itemCount = 0; + const char **items = GuiTextSplit(text, ';', &itemCount, NULL); + + Rectangle boundsOpen = bounds; + boundsOpen.height = (itemCount + 1)*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + if (direction == 1) boundsOpen.y -= itemCount*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)) + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING); + + Rectangle itemBounds = bounds; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && (editMode || !guiLocked) && (itemCount > 1) && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + if (editMode) + { + state = STATE_PRESSED; + + // Check if mouse has been pressed or released outside limits + if (!CheckCollisionPointRec(mousePoint, boundsOpen)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1; + } + + // Check if already selected item has been pressed again + if (CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + + // Check focused and selected item + for (int i = 0; i < itemCount; i++) + { + // Update item rectangle y position for next item + if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + + if (CheckCollisionPointRec(mousePoint, itemBounds)) + { + itemFocused = i; + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + itemSelected = i; + result = 1; // Item selected + } + break; + } + } + + itemBounds = bounds; + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + result = 1; + state = STATE_PRESSED; + } + else state = STATE_FOCUSED; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (editMode) GuiPanel(boundsOpen, NULL); + + GuiDrawRectangle(bounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER + state*3)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE + state*3))); + GuiDrawText(items[itemSelected], GetTextBounds(DROPDOWNBOX, bounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + state*3))); + + if (editMode) + { + // Draw visible items + for (int i = 0; i < itemCount; i++) + { + // Update item rectangle y position for next item + if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + + if (i == itemSelected) + { + GuiDrawRectangle(itemBounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_PRESSED))); + GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_PRESSED))); + } + else if (i == itemFocused) + { + GuiDrawRectangle(itemBounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_FOCUSED))); + GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_FOCUSED))); + } + else GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_NORMAL))); + } + } + + if (!GuiGetStyle(DROPDOWNBOX, DROPDOWN_ARROW_HIDDEN)) + { + // Draw arrows (using icon if available) +#if defined(RAYGUI_NO_ICONS) + GuiDrawText("v", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 2, 10, 10 }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); +#else + GuiDrawText(direction? "#121#" : "#120#", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 6, 10, 10 }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); // ICON_ARROW_DOWN_FILL +#endif + } + //-------------------------------------------------------------------- + + *active = itemSelected; + + // TODO: Use result to return more internal states: mouse-press out-of-bounds, mouse-press over selected-item... + return result; // Mouse click: result = 1 +} + +// Text Box control +// NOTE: Returns true on ENTER pressed (useful for data validation) +int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) +{ + #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) + #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 40 // Frames to wait for autocursor movement + #endif + #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) + #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement + #endif + + int result = 0; + GuiState state = guiState; + + bool multiline = false; // TODO: Consider multiline text input + int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); + + Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); + int textLength = (int)strlen(text); // Get current text length + int thisCursorIndex = textBoxCursorIndex; + if (thisCursorIndex > textLength) thisCursorIndex = textLength; + int textWidth = GetTextWidth(text) - GetTextWidth(text + thisCursorIndex); + int textIndexOffset = 0; // Text index offset to start drawing in the box + + // Cursor rectangle + // NOTE: Position X value should be updated + Rectangle cursor = { + textBounds.x + textWidth + GuiGetStyle(DEFAULT, TEXT_SPACING), + textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE), + 2, + (float)GuiGetStyle(DEFAULT, TEXT_SIZE)*2 + }; + + if (cursor.height >= bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; + if (cursor.y < (bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH))) cursor.y = bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH); + + // Mouse cursor rectangle + // NOTE: Initialized outside of screen + Rectangle mouseCursor = cursor; + mouseCursor.x = -1; + mouseCursor.width = 1; + + // Auto-cursor movement logic + // NOTE: Cursor moves automatically when key down after some time + if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCooldownCounter++; + else + { + autoCursorCooldownCounter = 0; // GLOBAL: Cursor cooldown counter + autoCursorDelayCounter = 0; // GLOBAL: Cursor delay counter + } + + // Blink-cursor frame counter + //if (!autoCursorMode) blinkCursorFrameCounter++; + //else blinkCursorFrameCounter = 0; + + // Update control + //-------------------------------------------------------------------- + // WARNING: Text editing is only supported under certain conditions: + if ((state != STATE_DISABLED) && // Control not disabled + !GuiGetStyle(TEXTBOX, TEXT_READONLY) && // TextBox not on read-only mode + !guiLocked && // Gui not locked + !guiControlExclusiveMode && // No gui slider on dragging + (wrapMode == TEXT_WRAP_NONE)) // No wrap mode + { + Vector2 mousePosition = GetMousePosition(); + + if (editMode) + { + state = STATE_PRESSED; + + if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; + + // If text does not fit in the textbox and current cursor position is out of bounds, + // we add an index offset to text for drawing only what requires depending on cursor + while (textWidth >= textBounds.width) + { + int nextCodepointSize = 0; + GetCodepointNext(text + textIndexOffset, &nextCodepointSize); + + textIndexOffset += nextCodepointSize; + + textWidth = GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex); + } + + int codepoint = GetCharPressed(); // Get Unicode codepoint + if (multiline && IsKeyPressed(KEY_ENTER)) codepoint = (int)'\n'; + + // Encode codepoint as UTF-8 + int codepointSize = 0; + const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); + + // Add codepoint to text, at current cursor position + // NOTE: Make sure we do not overflow buffer size + if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) + { + // Move forward data from cursor position + for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; + + // Add new codepoint in current cursor position + for (int i = 0; i < codepointSize; i++) text[textBoxCursorIndex + i] = charEncoded[i]; + + textBoxCursorIndex += codepointSize; + textLength += codepointSize; + + // Make sure text last character is EOL + text[textLength] = '\0'; + } + + // Move cursor to start + if ((textLength > 0) && IsKeyPressed(KEY_HOME)) textBoxCursorIndex = 0; + + // Move cursor to end + if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength; + + // Delete codepoint from text, after current cursor position + if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + { + autoCursorDelayCounter++; + + if (IsKeyPressed(KEY_DELETE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + { + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + // Move backward text from cursor position + for (int i = textBoxCursorIndex; i < textLength; i++) text[i] = text[i + nextCodepointSize]; + + textLength -= codepointSize; + if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; + + // Make sure text last character is EOL + text[textLength] = '\0'; + } + } + + // Delete related codepoints from text, before current cursor position + if ((textLength > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + int i = textBoxCursorIndex - 1; + int accCodepointSize = 0; + + // Move cursor to the end of word if on space already + while ((i > 0) && isspace(text[i])) + { + int prevCodepointSize = 0; + GetCodepointPrevious(text + i, &prevCodepointSize); + i -= prevCodepointSize; + accCodepointSize += prevCodepointSize; + } + + // Move cursor to the start of the word + while ((i > 0) && !isspace(text[i])) + { + int prevCodepointSize = 0; + GetCodepointPrevious(text + i, &prevCodepointSize); + i -= prevCodepointSize; + accCodepointSize += prevCodepointSize; + } + + // Move forward text from cursor position + for (int j = (textBoxCursorIndex - accCodepointSize); j < textLength; j++) text[j] = text[j + accCodepointSize]; + + // Prevent cursor index from decrementing past 0 + if (textBoxCursorIndex > 0) + { + textBoxCursorIndex -= accCodepointSize; + textLength -= accCodepointSize; + } + + // Make sure text last character is EOL + text[textLength] = '\0'; + } + else if ((textLength > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + { + autoCursorDelayCounter++; + + if (IsKeyPressed(KEY_BACKSPACE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + { + int prevCodepointSize = 0; + + // Prevent cursor index from decrementing past 0 + if (textBoxCursorIndex > 0) + { + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + + // Move backward text from cursor position + for (int i = (textBoxCursorIndex - prevCodepointSize); i < textLength; i++) text[i] = text[i + prevCodepointSize]; + + textBoxCursorIndex -= codepointSize; + textLength -= codepointSize; + } + + // Make sure text last character is EOL + text[textLength] = '\0'; + } + } + + // Move cursor position with keys + if (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) + { + autoCursorDelayCounter++; + + if (IsKeyPressed(KEY_LEFT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + { + int prevCodepointSize = 0; + if (textBoxCursorIndex > 0) GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + + if (textBoxCursorIndex >= prevCodepointSize) textBoxCursorIndex -= prevCodepointSize; + } + } + else if (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) + { + autoCursorDelayCounter++; + + if (IsKeyPressed(KEY_RIGHT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + { + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + if ((textBoxCursorIndex + nextCodepointSize) <= textLength) textBoxCursorIndex += nextCodepointSize; + } + } + + // Move cursor position with mouse + if (CheckCollisionPointRec(mousePosition, textBounds)) // Mouse hover text + { + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/(float)guiFont.baseSize; + int codepointIndex = 0; + float glyphWidth = 0.0f; + float widthToMouseX = 0; + int mouseCursorIndex = 0; + + for (int i = textIndexOffset; i < textLength; i++) + { + codepoint = GetCodepointNext(&text[i], &codepointSize); + codepointIndex = GetGlyphIndex(guiFont, codepoint); + + if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); + else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); + + if (mousePosition.x <= (textBounds.x + (widthToMouseX + glyphWidth/2))) + { + mouseCursor.x = textBounds.x + widthToMouseX; + mouseCursorIndex = i; + break; + } + + widthToMouseX += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + + // Check if mouse cursor is at the last position + int textEndWidth = GetTextWidth(text + textIndexOffset); + if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2)) + { + mouseCursor.x = textBounds.x + textEndWidth; + mouseCursorIndex = textLength; + } + + // Place cursor at required index on mouse click + if ((mouseCursor.x >= 0) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + cursor.x = mouseCursor.x; + textBoxCursorIndex = mouseCursorIndex; + } + } + else mouseCursor.x = -1; + + // Recalculate cursor position.y depending on textBoxCursorIndex + cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); + //if (multiline) cursor.y = GetTextLines() + + // Finish text editing on ENTER or mouse click outside bounds + if ((!multiline && IsKeyPressed(KEY_ENTER)) || + (!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) + { + textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index + result = 1; + } + } + else + { + if (CheckCollisionPointRec(mousePosition, bounds)) + { + state = STATE_FOCUSED; + + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text + result = 1; + } + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_PRESSED) + { + GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); + } + else if (state == STATE_DISABLED) + { + GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED))); + } + else GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK); + + // Draw text considering index offset if required + // NOTE: Text index offset depends on cursor position + GuiDrawText(text + textIndexOffset, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3)))); + + // Draw cursor + if (editMode && !GuiGetStyle(TEXTBOX, TEXT_READONLY)) + { + //if (autoCursorMode || ((blinkCursorFrameCounter/40)%2 == 0)) + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); + + // Draw mouse position cursor (if required) + if (mouseCursor.x >= 0) GuiDrawRectangle(mouseCursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); + } + else if (state == STATE_FOCUSED) GuiTooltip(bounds); + //-------------------------------------------------------------------- + + return result; // Mouse button pressed: result = 1 +} + +/* +// Text Box control with multiple lines and word-wrap +// NOTE: This text-box is readonly, no editing supported by default +bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) +{ + bool pressed = false; + + GuiSetStyle(TEXTBOX, TEXT_READONLY, 1); + GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_WORD); // WARNING: If wrap mode enabled, text editing is not supported + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP); + + // TODO: Implement methods to calculate cursor position properly + pressed = GuiTextBox(bounds, text, textSize, editMode); + + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); + GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_NONE); + GuiSetStyle(TEXTBOX, TEXT_READONLY, 0); + + return pressed; +} +*/ + +// Spinner control, returns selected value +int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) +{ + int result = 1; + GuiState state = guiState; + + int tempValue = *value; + + Rectangle spinner = { bounds.x + GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING), bounds.y, + bounds.width - 2*(GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING)), bounds.height }; + Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; + Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; + + Rectangle textBounds = { 0 }; + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(SPINNER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SPINNER, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check spinner state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + } + } + +#if defined(RAYGUI_NO_ICONS) + if (GuiButton(leftButtonBound, "<")) tempValue--; + if (GuiButton(rightButtonBound, ">")) tempValue++; +#else + if (GuiButton(leftButtonBound, GuiIconText(ICON_ARROW_LEFT_FILL, NULL))) tempValue--; + if (GuiButton(rightButtonBound, GuiIconText(ICON_ARROW_RIGHT_FILL, NULL))) tempValue++; +#endif + + if (!editMode) + { + if (tempValue < minValue) tempValue = minValue; + if (tempValue > maxValue) tempValue = maxValue; + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + result = GuiValueBox(spinner, NULL, &tempValue, minValue, maxValue, editMode); + + // Draw value selector custom buttons + // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(SPINNER, BORDER_WIDTH)); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + + // Draw text label if provided + GuiDrawText(text, textBounds, (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + *value = tempValue; + return result; +} + +// Value Box control, updates input text with numbers +// NOTE: Requires static variables: frameCounter +int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) +{ + #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) + #define RAYGUI_VALUEBOX_MAX_CHARS 32 + #endif + + int result = 0; + GuiState state = guiState; + + char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; + sprintf(textValue, "%i", *value); + + Rectangle textBounds = { 0 }; + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + bool valueHasChanged = false; + + if (editMode) + { + state = STATE_PRESSED; + + int keyCount = (int)strlen(textValue); + + // Only allow keys in range [48..57] + if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + { + if (GetTextWidth(textValue) < bounds.width) + { + int key = GetCharPressed(); + if ((key >= 48) && (key <= 57)) + { + textValue[keyCount] = (char)key; + keyCount++; + valueHasChanged = true; + } + } + } + + // Delete text + if (keyCount > 0) + { + if (IsKeyPressed(KEY_BACKSPACE)) + { + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; + } + } + + if (valueHasChanged) *value = TextToInteger(textValue); + + // NOTE: We are not clamp values until user input finishes + //if (*value > maxValue) *value = maxValue; + //else if (*value < minValue) *value = minValue; + + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) + { + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + + result = 1; + } + } + else + { + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + Color baseColor = BLANK; + if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); + else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); + + GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); + GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); + + // Draw cursor + if (editMode) + { + // NOTE: ValueBox internal text is always centered + Rectangle cursor = { bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH) }; + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); + } + + // Draw text label if provided + GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Floating point Value Box control, updates input val_str with numbers +// NOTE: Requires static variables: frameCounter +int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode) +{ + #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) + #define RAYGUI_VALUEBOX_MAX_CHARS 32 + #endif + + int result = 0; + GuiState state = guiState; + + //char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; + //sprintf(textValue, "%2.2f", *value); + + Rectangle textBounds = {0}; + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + bool valueHasChanged = false; + + if (editMode) + { + state = STATE_PRESSED; + + int keyCount = (int)strlen(textValue); + + // Only allow keys in range [48..57] + if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + { + if (GetTextWidth(textValue) < bounds.width) + { + int key = GetCharPressed(); + if (((key >= 48) && (key <= 57)) || + (key == '.') || + ((keyCount == 0) && (key == '+')) || // NOTE: Sign can only be in first position + ((keyCount == 0) && (key == '-'))) + { + textValue[keyCount] = (char)key; + keyCount++; + + valueHasChanged = true; + } + } + } + + // Pressed backspace + if (IsKeyPressed(KEY_BACKSPACE)) + { + if (keyCount > 0) + { + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; + } + } + + if (valueHasChanged) *value = TextToFloat(textValue); + + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) result = 1; + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + Color baseColor = BLANK; + if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); + else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); + + GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); + GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); + + // Draw cursor + if (editMode) + { + // NOTE: ValueBox internal text is always centered + Rectangle cursor = {bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, + bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, + bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH)}; + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); + } + + // Draw text label if provided + GuiDrawText(text, textBounds, + (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, + GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Slider control with pro parameters +// NOTE: Other GuiSlider*() controls use this one +int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue, int sliderWidth) +{ + int result = 0; + GuiState state = guiState; + + float temp = (maxValue - minValue)/2.0f; + if (value == NULL) value = &temp; + float oldValue = *value; + + Rectangle slider = { bounds.x, bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), + 0, bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + // Get equivalent value and slider position from mousePosition.x + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + if (!CheckCollisionPointRec(mousePoint, slider)) + { + // Get equivalent value and slider position from mousePosition.x + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; + } + } + else state = STATE_FOCUSED; + } + + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + } + + // Control value change check + if (oldValue == *value) result = 0; + else result = 1; + + // Slider bar limits check + float sliderValue = (((*value - minValue)/(maxValue - minValue))*(bounds.width - sliderWidth - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))); + if (sliderWidth > 0) // Slider + { + slider.x += sliderValue; + slider.width = (float)sliderWidth; + if (slider.x <= (bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH))) slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH); + else if ((slider.x + slider.width) >= (bounds.x + bounds.width)) slider.x = bounds.x + bounds.width - slider.width - GuiGetStyle(SLIDER, BORDER_WIDTH); + } + else if (sliderWidth == 0) // SliderBar + { + slider.x += GuiGetStyle(SLIDER, BORDER_WIDTH); + slider.width = sliderValue; + if (slider.width > bounds.width) slider.width = bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH); + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), GetColor(GuiGetStyle(SLIDER, BORDER + (state*3))), GetColor(GuiGetStyle(SLIDER, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED))); + + // Draw slider internal bar (depends on state) + if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); + else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_FOCUSED))); + else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_PRESSED))); + + // Draw left/right text if provided + if (textLeft != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textLeft); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SLIDER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); + } + + if (textRight != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textRight); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(SLIDER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); + } + //-------------------------------------------------------------------- + + return result; +} + +// Slider control extended, returns selected value and has text +int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) +{ + return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, GuiGetStyle(SLIDER, SLIDER_WIDTH)); +} + +// Slider Bar control extended, returns selected value +int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) +{ + return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, 0); +} + +// Progress Bar control extended, shows current progress value +int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) +{ + int result = 0; + GuiState state = guiState; + + float temp = (maxValue - minValue)/2.0f; + if (value == NULL) value = &temp; + + // Progress bar + Rectangle progress = { bounds.x + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), + bounds.y + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) + GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING), 0, + bounds.height - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - 2*GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING) }; + + // Update control + //-------------------------------------------------------------------- + if (*value > maxValue) *value = maxValue; + + // WARNING: Working with floats could lead to rounding issues + if ((state != STATE_DISABLED)) progress.width = (float)(*value/(maxValue - minValue))*bounds.width - ((*value >= maxValue)? (float)(2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)) : 0.0f); + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_DISABLED) + { + GuiDrawRectangle(bounds, GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), GetColor(GuiGetStyle(PROGRESSBAR, BORDER + (state*3))), BLANK); + } + else + { + if (*value > minValue) + { + // Draw progress bar with colored border, more visual + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + } + else GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + + if (*value >= maxValue) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + progress.width + 1, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + else + { + // Draw borders not yet reached by value + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y + bounds.height - 1, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + } + + // Draw slider internal progress bar (depends on state) + GuiDrawRectangle(progress, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BASE_COLOR_PRESSED))); + } + + // Draw left/right text if provided + if (textLeft != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textLeft); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x - textBounds.width - GuiGetStyle(PROGRESSBAR, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); + } + + if (textRight != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textRight); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(PROGRESSBAR, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); + } + //-------------------------------------------------------------------- + + return result; +} + +// Status Bar control +int GuiStatusBar(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(STATUSBAR, BORDER_WIDTH), GetColor(GuiGetStyle(STATUSBAR, BORDER + (state*3))), GetColor(GuiGetStyle(STATUSBAR, BASE + (state*3)))); + GuiDrawText(text, GetTextBounds(STATUSBAR, bounds), GuiGetStyle(STATUSBAR, TEXT_ALIGNMENT), GetColor(GuiGetStyle(STATUSBAR, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Dummy rectangle control, intended for placeholding +int GuiDummyRec(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED))); + GuiDrawText(text, GetTextBounds(DEFAULT, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(BUTTON, (state != STATE_DISABLED)? TEXT_COLOR_NORMAL : TEXT_COLOR_DISABLED))); + //------------------------------------------------------------------ + + return result; +} + +// List View control +int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active) +{ + int result = 0; + int itemCount = 0; + const char **items = NULL; + + if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); + + result = GuiListViewEx(bounds, items, itemCount, scrollIndex, active, NULL); + + return result; +} + +// List View control with extended parameters +int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus) +{ + int result = 0; + GuiState state = guiState; + + int itemFocused = (focus == NULL)? -1 : *focus; + int itemSelected = (active == NULL)? -1 : *active; + + // Check if we need a scroll bar + bool useScrollBar = false; + if ((GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING))*count > bounds.height) useScrollBar = true; + + // Define base item rectangle [0] + Rectangle itemBounds = { 0 }; + itemBounds.x = bounds.x + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING); + itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); + itemBounds.width = bounds.width - 2*GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) - GuiGetStyle(DEFAULT, BORDER_WIDTH); + itemBounds.height = (float)GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + if (useScrollBar) itemBounds.width -= GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH); + + // Get items on the list + int visibleItems = (int)bounds.height/(GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); + if (visibleItems > count) visibleItems = count; + + int startIndex = (scrollIndex == NULL)? 0 : *scrollIndex; + if ((startIndex < 0) || (startIndex > (count - visibleItems))) startIndex = 0; + int endIndex = startIndex + visibleItems; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check mouse inside list view + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + + // Check focused and selected item + for (int i = 0; i < visibleItems; i++) + { + if (CheckCollisionPointRec(mousePoint, itemBounds)) + { + itemFocused = startIndex + i; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + if (itemSelected == (startIndex + i)) itemSelected = -1; + else itemSelected = startIndex + i; + } + break; + } + + // Update item rectangle y position for next item + itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); + } + + if (useScrollBar) + { + int wheelMove = (int)GetMouseWheelMove(); + startIndex -= wheelMove; + + if (startIndex < 0) startIndex = 0; + else if (startIndex > (count - visibleItems)) startIndex = count - visibleItems; + + endIndex = startIndex + visibleItems; + if (endIndex > count) endIndex = count; + } + } + else itemFocused = -1; + + // Reset item rectangle y to [0] + itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background + + // Draw visible items + for (int i = 0; ((i < visibleItems) && (text != NULL)); i++) + { + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_NORMAL)), BLANK); + + if (state == STATE_DISABLED) + { + if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); + + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED))); + } + else + { + if (((startIndex + i) == itemSelected) && (active != NULL)) + { + // Draw item selected + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED))); + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED))); + } + else if (((startIndex + i) == itemFocused)) // && (focus != NULL)) // NOTE: We want items focused, despite not returned! + { + // Draw item focused + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED))); + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED))); + } + else + { + // Draw item normal + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL))); + } + } + + // Update item rectangle y position for next item + itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); + } + + if (useScrollBar) + { + Rectangle scrollBarBounds = { + bounds.x + bounds.width - GuiGetStyle(LISTVIEW, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), + bounds.y + GuiGetStyle(LISTVIEW, BORDER_WIDTH), (float)GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), + bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) + }; + + // Calculate percentage of visible items and apply same percentage to scrollbar + float percentVisible = (float)(endIndex - startIndex)/count; + float sliderSize = bounds.height*percentVisible; + + int prevSliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); // Save default slider size + int prevScrollSpeed = GuiGetStyle(SCROLLBAR, SCROLL_SPEED); // Save default scroll speed + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)sliderSize); // Change slider size + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, count - visibleItems); // Change scroll speed + + startIndex = GuiScrollBar(scrollBarBounds, startIndex, 0, count - visibleItems); + + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, prevScrollSpeed); // Reset scroll speed to default + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, prevSliderSize); // Reset slider size to default + } + //-------------------------------------------------------------------- + + if (active != NULL) *active = itemSelected; + if (focus != NULL) *focus = itemFocused; + if (scrollIndex != NULL) *scrollIndex = startIndex; + + return result; +} + +// Color Panel control - Color (RGBA) variant. +int GuiColorPanel(Rectangle bounds, const char *text, Color *color) +{ + int result = 0; + + Vector3 vcolor = { (float)color->r/255.0f, (float)color->g/255.0f, (float)color->b/255.0f }; + Vector3 hsv = ConvertRGBtoHSV(vcolor); + Vector3 prevHsv = hsv; // workaround to see if GuiColorPanelHSV modifies the hsv. + + GuiColorPanelHSV(bounds, text, &hsv); + + // Check if the hsv was changed, only then change the color. + // This is required, because the Color->HSV->Color conversion has precision errors. + // Thus the assignment from HSV to Color should only be made, if the HSV has a new user-entered value. + // Otherwise GuiColorPanel would often modify it's color without user input. + // TODO: GuiColorPanelHSV could return 1 if the slider was dragged, to simplify this check. + if (hsv.x != prevHsv.x || hsv.y != prevHsv.y || hsv.z != prevHsv.z) + { + Vector3 rgb = ConvertHSVtoRGB(hsv); + + // NOTE: Vector3ToColor() only available on raylib 1.8.1 + *color = RAYGUI_CLITERAL(Color){ (unsigned char)(255.0f*rgb.x), + (unsigned char)(255.0f*rgb.y), + (unsigned char)(255.0f*rgb.z), + color->a }; + } + return result; +} + +// Color Bar Alpha control +// NOTE: Returns alpha value normalized [0..1] +int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) +{ + #if !defined(RAYGUI_COLORBARALPHA_CHECKED_SIZE) + #define RAYGUI_COLORBARALPHA_CHECKED_SIZE 10 + #endif + + int result = 0; + GuiState state = guiState; + Rectangle selector = { (float)bounds.x + (*alpha)*bounds.width - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.y - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT), (float)bounds.height + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2 }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + + *alpha = (mousePoint.x - bounds.x)/bounds.width; + if (*alpha <= 0.0f) *alpha = 0.0f; + if (*alpha >= 1.0f) *alpha = 1.0f; + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + *alpha = (mousePoint.x - bounds.x)/bounds.width; + if (*alpha <= 0.0f) *alpha = 0.0f; + if (*alpha >= 1.0f) *alpha = 1.0f; + //selector.x = bounds.x + (int)(((alpha - 0)/(100 - 0))*(bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))) - selector.width/2; + } + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + + // Draw alpha bar: checked background + if (state != STATE_DISABLED) + { + int checksX = (int)bounds.width/RAYGUI_COLORBARALPHA_CHECKED_SIZE; + int checksY = (int)bounds.height/RAYGUI_COLORBARALPHA_CHECKED_SIZE; + + for (int x = 0; x < checksX; x++) + { + for (int y = 0; y < checksY; y++) + { + Rectangle check = { bounds.x + x*RAYGUI_COLORBARALPHA_CHECKED_SIZE, bounds.y + y*RAYGUI_COLORBARALPHA_CHECKED_SIZE, RAYGUI_COLORBARALPHA_CHECKED_SIZE, RAYGUI_COLORBARALPHA_CHECKED_SIZE }; + GuiDrawRectangle(check, 0, BLANK, ((x + y)%2)? Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.4f) : Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.4f)); + } + } + + DrawRectangleGradientEx(bounds, RAYGUI_CLITERAL(Color){ 255, 255, 255, 0 }, RAYGUI_CLITERAL(Color){ 255, 255, 255, 0 }, Fade(RAYGUI_CLITERAL(Color){ 0, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 0, 255 }, guiAlpha)); + } + else DrawRectangleGradientEx(bounds, Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha)); + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); + + // Draw alpha bar: selector + GuiDrawRectangle(selector, 0, BLANK, GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3))); + //-------------------------------------------------------------------- + + return result; +} + +// Color Bar Hue control +// Returns hue value normalized [0..1] +// NOTE: Other similar bars (for reference): +// Color GuiColorBarSat() [WHITE->color] +// Color GuiColorBarValue() [BLACK->color], HSV/HSL +// float GuiColorBarLuminance() [BLACK->WHITE] +int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) +{ + int result = 0; + GuiState state = guiState; + Rectangle selector = { (float)bounds.x - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)bounds.y + (*hue)/360.0f*bounds.height - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2, (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT) }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + + *hue = (mousePoint.y - bounds.y)*360/bounds.height; + if (*hue <= 0.0f) *hue = 0.0f; + if (*hue >= 359.0f) *hue = 359.0f; + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + *hue = (mousePoint.y - bounds.y)*360/bounds.height; + if (*hue <= 0.0f) *hue = 0.0f; + if (*hue >= 359.0f) *hue = 359.0f; + + } + else state = STATE_FOCUSED; + + /*if (IsKeyDown(KEY_UP)) + { + hue -= 2.0f; + if (hue <= 0.0f) hue = 0.0f; + } + else if (IsKeyDown(KEY_DOWN)) + { + hue += 2.0f; + if (hue >= 360.0f) hue = 360.0f; + }*/ + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state != STATE_DISABLED) + { + // Draw hue bar:color bars + // TODO: Use directly DrawRectangleGradientEx(bounds, color1, color2, color2, color1); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + bounds.height/6), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 2*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 3*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 4*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 5*(bounds.height/6)), (int)bounds.width, (int)(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha)); + } + else DrawRectangleGradientV((int)bounds.x, (int)bounds.y, (int)bounds.width, (int)bounds.height, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha)); + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); + + // Draw hue bar: selector + GuiDrawRectangle(selector, 0, BLANK, GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3))); + //-------------------------------------------------------------------- + + return result; +} + +// Color Picker control +// NOTE: It's divided in multiple controls: +// Color GuiColorPanel(Rectangle bounds, Color color) +// float GuiColorBarAlpha(Rectangle bounds, float alpha) +// float GuiColorBarHue(Rectangle bounds, float value) +// NOTE: bounds define GuiColorPanel() size +// NOTE: this picker converts RGB to HSV, which can cause the Hue control to jump. If you have this problem, consider using the HSV variant instead +int GuiColorPicker(Rectangle bounds, const char *text, Color *color) +{ + int result = 0; + + Color temp = { 200, 0, 0, 255 }; + if (color == NULL) color = &temp; + + GuiColorPanel(bounds, NULL, color); + + Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; + //Rectangle boundsAlpha = { bounds.x, bounds.y + bounds.height + GuiGetStyle(COLORPICKER, BARS_PADDING), bounds.width, GuiGetStyle(COLORPICKER, BARS_THICK) }; + + // NOTE: this conversion can cause low hue-resolution, if the r, g and b value are very similar, which causes the hue bar to shift around when only the GuiColorPanel is used. + Vector3 hsv = ConvertRGBtoHSV(RAYGUI_CLITERAL(Vector3){ (*color).r/255.0f, (*color).g/255.0f, (*color).b/255.0f }); + + GuiColorBarHue(boundsHue, NULL, &hsv.x); + + //color.a = (unsigned char)(GuiColorBarAlpha(boundsAlpha, (float)color.a/255.0f)*255.0f); + Vector3 rgb = ConvertHSVtoRGB(hsv); + + *color = RAYGUI_CLITERAL(Color){ (unsigned char)roundf(rgb.x*255.0f), (unsigned char)roundf(rgb.y*255.0f), (unsigned char)roundf(rgb.z*255.0f), (*color).a }; + + return result; +} + +// Color Picker control that avoids conversion to RGB and back to HSV on each call, thus avoiding jittering. +// The user can call ConvertHSVtoRGB() to convert *colorHsv value to RGB. +// NOTE: It's divided in multiple controls: +// int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) +// int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) +// float GuiColorBarHue(Rectangle bounds, float value) +// NOTE: bounds define GuiColorPanelHSV() size +int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) +{ + int result = 0; + + Vector3 tempHsv = { 0 }; + + if (colorHsv == NULL) + { + const Vector3 tempColor = { 200.0f/255.0f, 0.0f, 0.0f }; + tempHsv = ConvertRGBtoHSV(tempColor); + colorHsv = &tempHsv; + } + + GuiColorPanelHSV(bounds, NULL, colorHsv); + + const Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; + + GuiColorBarHue(boundsHue, NULL, &colorHsv->x); + + return result; +} + +// Color Panel control - HSV variant +int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) +{ + int result = 0; + GuiState state = guiState; + Vector2 pickerSelector = { 0 }; + + const Color colWhite = { 255, 255, 255, 255 }; + const Color colBlack = { 0, 0, 0, 255 }; + + pickerSelector.x = bounds.x + (float)colorHsv->y*bounds.width; // HSV: Saturation + pickerSelector.y = bounds.y + (1.0f - (float)colorHsv->z)*bounds.height; // HSV: Value + + Vector3 maxHue = { colorHsv->x, 1.0f, 1.0f }; + Vector3 rgbHue = ConvertHSVtoRGB(maxHue); + Color maxHueCol = { (unsigned char)(255.0f*rgbHue.x), + (unsigned char)(255.0f*rgbHue.y), + (unsigned char)(255.0f*rgbHue.z), 255 }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + pickerSelector = mousePoint; + + if (pickerSelector.x < bounds.x) pickerSelector.x = bounds.x; + if (pickerSelector.x > bounds.x + bounds.width) pickerSelector.x = bounds.x + bounds.width; + if (pickerSelector.y < bounds.y) pickerSelector.y = bounds.y; + if (pickerSelector.y > bounds.y + bounds.height) pickerSelector.y = bounds.y + bounds.height; + + // Calculate color from picker + Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; + + colorPick.x /= (float)bounds.width; // Get normalized value on x + colorPick.y /= (float)bounds.height; // Get normalized value on y + + colorHsv->y = colorPick.x; + colorHsv->z = 1.0f - colorPick.y; + + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; + pickerSelector = mousePoint; + + // Calculate color from picker + Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; + + colorPick.x /= (float)bounds.width; // Get normalized value on x + colorPick.y /= (float)bounds.height; // Get normalized value on y + + colorHsv->y = colorPick.x; + colorHsv->z = 1.0f - colorPick.y; + } + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state != STATE_DISABLED) + { + DrawRectangleGradientEx(bounds, Fade(colWhite, guiAlpha), Fade(colWhite, guiAlpha), Fade(maxHueCol, guiAlpha), Fade(maxHueCol, guiAlpha)); + DrawRectangleGradientEx(bounds, Fade(colBlack, 0), Fade(colBlack, guiAlpha), Fade(colBlack, guiAlpha), Fade(colBlack, 0)); + + // Draw color picker: selector + Rectangle selector = { pickerSelector.x - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, pickerSelector.y - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE), (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE) }; + GuiDrawRectangle(selector, 0, BLANK, colWhite); + } + else + { + DrawRectangleGradientEx(bounds, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.6f), guiAlpha)); + } + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); + //-------------------------------------------------------------------- + + return result; +} + +// Message Box control +int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons) +{ + #if !defined(RAYGUI_MESSAGEBOX_BUTTON_HEIGHT) + #define RAYGUI_MESSAGEBOX_BUTTON_HEIGHT 24 + #endif + #if !defined(RAYGUI_MESSAGEBOX_BUTTON_PADDING) + #define RAYGUI_MESSAGEBOX_BUTTON_PADDING 12 + #endif + + int result = -1; // Returns clicked button from buttons list, 0 refers to closed window button + + int buttonCount = 0; + const char **buttonsText = GuiTextSplit(buttons, ';', &buttonCount, NULL); + Rectangle buttonBounds = { 0 }; + buttonBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; + buttonBounds.y = bounds.y + bounds.height - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT - RAYGUI_MESSAGEBOX_BUTTON_PADDING; + buttonBounds.width = (bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; + buttonBounds.height = RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; + + //int textWidth = GetTextWidth(message) + 2; + + Rectangle textBounds = { 0 }; + textBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; + textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + RAYGUI_MESSAGEBOX_BUTTON_PADDING; + textBounds.width = bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*2; + textBounds.height = bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 3*RAYGUI_MESSAGEBOX_BUTTON_PADDING - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; + + // Draw control + //-------------------------------------------------------------------- + if (GuiWindowBox(bounds, title)) result = 0; + + int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiLabel(textBounds, message); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment); + + prevTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + for (int i = 0; i < buttonCount; i++) + { + if (GuiButton(buttonBounds, buttonsText[i])) result = i + 1; + buttonBounds.x += (buttonBounds.width + RAYGUI_MESSAGEBOX_BUTTON_PADDING); + } + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevTextAlignment); + //-------------------------------------------------------------------- + + return result; +} + +// Text Input Box control, ask for text +int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive) +{ + #if !defined(RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT) + #define RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT 24 + #endif + #if !defined(RAYGUI_TEXTINPUTBOX_BUTTON_PADDING) + #define RAYGUI_TEXTINPUTBOX_BUTTON_PADDING 12 + #endif + #if !defined(RAYGUI_TEXTINPUTBOX_HEIGHT) + #define RAYGUI_TEXTINPUTBOX_HEIGHT 26 + #endif + + // Used to enable text edit mode + // WARNING: No more than one GuiTextInputBox() should be open at the same time + static bool textEditMode = false; + + int result = -1; + + int buttonCount = 0; + const char **buttonsText = GuiTextSplit(buttons, ';', &buttonCount, NULL); + Rectangle buttonBounds = { 0 }; + buttonBounds.x = bounds.x + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + buttonBounds.y = bounds.y + bounds.height - RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + buttonBounds.width = (bounds.width - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; + buttonBounds.height = RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT; + + int messageInputHeight = (int)bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT - 2*RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + + Rectangle textBounds = { 0 }; + if (message != NULL) + { + int textSize = GetTextWidth(message) + 2; + + textBounds.x = bounds.x + bounds.width/2 - textSize/2; + textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + messageInputHeight/4 - (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + textBounds.width = (float)textSize; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + } + + Rectangle textBoxBounds = { 0 }; + textBoxBounds.x = bounds.x + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + textBoxBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - RAYGUI_TEXTINPUTBOX_HEIGHT/2; + if (message == NULL) textBoxBounds.y = bounds.y + 24 + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + else textBoxBounds.y += (messageInputHeight/2 + messageInputHeight/4); + textBoxBounds.width = bounds.width - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING*2; + textBoxBounds.height = RAYGUI_TEXTINPUTBOX_HEIGHT; + + // Draw control + //-------------------------------------------------------------------- + if (GuiWindowBox(bounds, title)) result = 0; + + // Draw message if available + if (message != NULL) + { + int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiLabel(textBounds, message); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment); + } + + if (secretViewActive != NULL) + { + static char stars[] = "****************"; + if (GuiTextBox(RAYGUI_CLITERAL(Rectangle){ textBoxBounds.x, textBoxBounds.y, textBoxBounds.width - 4 - RAYGUI_TEXTINPUTBOX_HEIGHT, textBoxBounds.height }, + ((*secretViewActive == 1) || textEditMode)? text : stars, textMaxSize, textEditMode)) textEditMode = !textEditMode; + + GuiToggle(RAYGUI_CLITERAL(Rectangle){ textBoxBounds.x + textBoxBounds.width - RAYGUI_TEXTINPUTBOX_HEIGHT, textBoxBounds.y, RAYGUI_TEXTINPUTBOX_HEIGHT, RAYGUI_TEXTINPUTBOX_HEIGHT }, (*secretViewActive == 1)? "#44#" : "#45#", secretViewActive); + } + else + { + if (GuiTextBox(textBoxBounds, text, textMaxSize, textEditMode)) textEditMode = !textEditMode; + } + + int prevBtnTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + for (int i = 0; i < buttonCount; i++) + { + if (GuiButton(buttonBounds, buttonsText[i])) result = i + 1; + buttonBounds.x += (buttonBounds.width + RAYGUI_MESSAGEBOX_BUTTON_PADDING); + } + + if (result >= 0) textEditMode = false; + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevBtnTextAlignment); + //-------------------------------------------------------------------- + + return result; // Result is the pressed button index +} + +// Grid control +// NOTE: Returns grid mouse-hover selected cell +// About drawing lines at subpixel spacing, simple put, not easy solution: +// https://stackoverflow.com/questions/4435450/2d-opengl-drawing-lines-that-dont-exactly-fit-pixel-raster +int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell) +{ + // Grid lines alpha amount + #if !defined(RAYGUI_GRID_ALPHA) + #define RAYGUI_GRID_ALPHA 0.15f + #endif + + int result = 0; + GuiState state = guiState; + + Vector2 mousePoint = GetMousePosition(); + Vector2 currentMouseCell = { -1, -1 }; + + float spaceWidth = spacing/(float)subdivs; + int linesV = (int)(bounds.width/spaceWidth) + 1; + int linesH = (int)(bounds.height/spaceWidth) + 1; + + int color = GuiGetStyle(DEFAULT, LINE_COLOR); + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + // NOTE: Cell values must be the upper left of the cell the mouse is in + currentMouseCell.x = floorf((mousePoint.x - bounds.x)/spacing); + currentMouseCell.y = floorf((mousePoint.y - bounds.y)/spacing); + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_DISABLED) color = GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED); + + if (subdivs > 0) + { + // Draw vertical grid lines + for (int i = 0; i < linesV; i++) + { + Rectangle lineV = { bounds.x + spacing*i/subdivs, bounds.y, 1, bounds.height + 1 }; + GuiDrawRectangle(lineV, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); + } + + // Draw horizontal grid lines + for (int i = 0; i < linesH; i++) + { + Rectangle lineH = { bounds.x, bounds.y + spacing*i/subdivs, bounds.width + 1, 1 }; + GuiDrawRectangle(lineH, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); + } + } + + if (mouseCell != NULL) *mouseCell = currentMouseCell; + return result; +} + +//---------------------------------------------------------------------------------- +// Tooltip management functions +// NOTE: Tooltips requires some global variables: tooltipPtr +//---------------------------------------------------------------------------------- +// Enable gui tooltips (global state) +void GuiEnableTooltip(void) { guiTooltip = true; } + +// Disable gui tooltips (global state) +void GuiDisableTooltip(void) { guiTooltip = false; } + +// Set tooltip string +void GuiSetTooltip(const char *tooltip) { guiTooltipPtr = tooltip; } + +//---------------------------------------------------------------------------------- +// Styles loading functions +//---------------------------------------------------------------------------------- + +// Load raygui style file (.rgs) +// NOTE: By default a binary file is expected, that file could contain a custom font, +// in that case, custom font image atlas is GRAY+ALPHA and pixel data can be compressed (DEFLATE) +void GuiLoadStyle(const char *fileName) +{ + #define MAX_LINE_BUFFER_SIZE 256 + + bool tryBinary = false; + if (!guiStyleLoaded) GuiLoadStyleDefault(); + + // Try reading the files as text file first + FILE *rgsFile = fopen(fileName, "rt"); + + if (rgsFile != NULL) + { + char buffer[MAX_LINE_BUFFER_SIZE] = { 0 }; + fgets(buffer, MAX_LINE_BUFFER_SIZE, rgsFile); + + if (buffer[0] == '#') + { + int controlId = 0; + int propertyId = 0; + unsigned int propertyValue = 0; + + while (!feof(rgsFile)) + { + switch (buffer[0]) + { + case 'p': + { + // Style property: p + + sscanf(buffer, "p %d %d 0x%x", &controlId, &propertyId, &propertyValue); + GuiSetStyle(controlId, propertyId, (int)propertyValue); + + } break; + case 'f': + { + // Style font: f + + int fontSize = 0; + char charmapFileName[256] = { 0 }; + char fontFileName[256] = { 0 }; + sscanf(buffer, "f %d %s %[^\r\n]s", &fontSize, charmapFileName, fontFileName); + + Font font = { 0 }; + int *codepoints = NULL; + int codepointCount = 0; + + if (charmapFileName[0] != '0') + { + // Load text data from file + // NOTE: Expected an UTF-8 array of codepoints, no separation + char *textData = LoadFileText(TextFormat("%s/%s", GetDirectoryPath(fileName), charmapFileName)); + codepoints = LoadCodepoints(textData, &codepointCount); + UnloadFileText(textData); + } + + if (fontFileName[0] != '\0') + { + // In case a font is already loaded and it is not default internal font, unload it + if (font.texture.id != GetFontDefault().texture.id) UnloadTexture(font.texture); + + if (codepointCount > 0) font = LoadFontEx(TextFormat("%s/%s", GetDirectoryPath(fileName), fontFileName), fontSize, codepoints, codepointCount); + else font = LoadFontEx(TextFormat("%s/%s", GetDirectoryPath(fileName), fontFileName), fontSize, NULL, 0); // Default to 95 standard codepoints + } + + // If font texture not properly loaded, revert to default font and size/spacing + if (font.texture.id == 0) + { + font = GetFontDefault(); + GuiSetStyle(DEFAULT, TEXT_SIZE, 10); + GuiSetStyle(DEFAULT, TEXT_SPACING, 1); + } + + UnloadCodepoints(codepoints); + + if ((font.texture.id > 0) && (font.glyphCount > 0)) GuiSetFont(font); + + } break; + default: break; + } + + fgets(buffer, MAX_LINE_BUFFER_SIZE, rgsFile); + } + } + else tryBinary = true; + + fclose(rgsFile); + } + + if (tryBinary) + { + rgsFile = fopen(fileName, "rb"); + + if (rgsFile != NULL) + { + fseek(rgsFile, 0, SEEK_END); + int fileDataSize = ftell(rgsFile); + fseek(rgsFile, 0, SEEK_SET); + + if (fileDataSize > 0) + { + unsigned char *fileData = (unsigned char *)RAYGUI_MALLOC(fileDataSize*sizeof(unsigned char)); + fread(fileData, sizeof(unsigned char), fileDataSize, rgsFile); + + GuiLoadStyleFromMemory(fileData, fileDataSize); + + RAYGUI_FREE(fileData); + } + + fclose(rgsFile); + } + } +} + +// Load style default over global style +void GuiLoadStyleDefault(void) +{ + // We set this variable first to avoid cyclic function calls + // when calling GuiSetStyle() and GuiGetStyle() + guiStyleLoaded = true; + + // Initialize default LIGHT style property values + // WARNING: Default value are applied to all controls on set but + // they can be overwritten later on for every custom control + GuiSetStyle(DEFAULT, BORDER_COLOR_NORMAL, 0x838383ff); + GuiSetStyle(DEFAULT, BASE_COLOR_NORMAL, 0xc9c9c9ff); + GuiSetStyle(DEFAULT, TEXT_COLOR_NORMAL, 0x686868ff); + GuiSetStyle(DEFAULT, BORDER_COLOR_FOCUSED, 0x5bb2d9ff); + GuiSetStyle(DEFAULT, BASE_COLOR_FOCUSED, 0xc9effeff); + GuiSetStyle(DEFAULT, TEXT_COLOR_FOCUSED, 0x6c9bbcff); + GuiSetStyle(DEFAULT, BORDER_COLOR_PRESSED, 0x0492c7ff); + GuiSetStyle(DEFAULT, BASE_COLOR_PRESSED, 0x97e8ffff); + GuiSetStyle(DEFAULT, TEXT_COLOR_PRESSED, 0x368bafff); + GuiSetStyle(DEFAULT, BORDER_COLOR_DISABLED, 0xb5c1c2ff); + GuiSetStyle(DEFAULT, BASE_COLOR_DISABLED, 0xe6e9e9ff); + GuiSetStyle(DEFAULT, TEXT_COLOR_DISABLED, 0xaeb7b8ff); + GuiSetStyle(DEFAULT, BORDER_WIDTH, 1); + GuiSetStyle(DEFAULT, TEXT_PADDING, 0); + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + // Initialize default extended property values + // NOTE: By default, extended property values are initialized to 0 + GuiSetStyle(DEFAULT, TEXT_SIZE, 10); // DEFAULT, shared by all controls + GuiSetStyle(DEFAULT, TEXT_SPACING, 1); // DEFAULT, shared by all controls + GuiSetStyle(DEFAULT, LINE_COLOR, 0x90abb5ff); // DEFAULT specific property + GuiSetStyle(DEFAULT, BACKGROUND_COLOR, 0xf5f5f5ff); // DEFAULT specific property + GuiSetStyle(DEFAULT, TEXT_LINE_SPACING, 15); // DEFAULT, 15 pixels between lines + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); // DEFAULT, text aligned vertically to middle of text-bounds + + // Initialize control-specific property values + // NOTE: Those properties are in default list but require specific values by control type + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 2); + GuiSetStyle(SLIDER, TEXT_PADDING, 4); + GuiSetStyle(PROGRESSBAR, TEXT_PADDING, 4); + GuiSetStyle(CHECKBOX, TEXT_PADDING, 4); + GuiSetStyle(CHECKBOX, TEXT_ALIGNMENT, TEXT_ALIGN_RIGHT); + GuiSetStyle(DROPDOWNBOX, TEXT_PADDING, 0); + GuiSetStyle(DROPDOWNBOX, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiSetStyle(TEXTBOX, TEXT_PADDING, 4); + GuiSetStyle(TEXTBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(VALUEBOX, TEXT_PADDING, 0); + GuiSetStyle(VALUEBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(SPINNER, TEXT_PADDING, 0); + GuiSetStyle(SPINNER, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(STATUSBAR, TEXT_PADDING, 8); + GuiSetStyle(STATUSBAR, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + + // Initialize extended property values + // NOTE: By default, extended property values are initialized to 0 + GuiSetStyle(TOGGLE, GROUP_PADDING, 2); + GuiSetStyle(SLIDER, SLIDER_WIDTH, 16); + GuiSetStyle(SLIDER, SLIDER_PADDING, 1); + GuiSetStyle(PROGRESSBAR, PROGRESS_PADDING, 1); + GuiSetStyle(CHECKBOX, CHECK_PADDING, 1); + GuiSetStyle(COMBOBOX, COMBO_BUTTON_WIDTH, 32); + GuiSetStyle(COMBOBOX, COMBO_BUTTON_SPACING, 2); + GuiSetStyle(DROPDOWNBOX, ARROW_PADDING, 16); + GuiSetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING, 2); + GuiSetStyle(SPINNER, SPIN_BUTTON_WIDTH, 24); + GuiSetStyle(SPINNER, SPIN_BUTTON_SPACING, 2); + GuiSetStyle(SCROLLBAR, BORDER_WIDTH, 0); + GuiSetStyle(SCROLLBAR, ARROWS_VISIBLE, 0); + GuiSetStyle(SCROLLBAR, ARROWS_SIZE, 6); + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING, 0); + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, 16); + GuiSetStyle(SCROLLBAR, SCROLL_PADDING, 0); + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, 12); + GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 28); + GuiSetStyle(LISTVIEW, LIST_ITEMS_SPACING, 2); + GuiSetStyle(LISTVIEW, SCROLLBAR_WIDTH, 12); + GuiSetStyle(LISTVIEW, SCROLLBAR_SIDE, SCROLLBAR_RIGHT_SIDE); + GuiSetStyle(COLORPICKER, COLOR_SELECTOR_SIZE, 8); + GuiSetStyle(COLORPICKER, HUEBAR_WIDTH, 16); + GuiSetStyle(COLORPICKER, HUEBAR_PADDING, 8); + GuiSetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT, 8); + GuiSetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW, 2); + + if (guiFont.texture.id != GetFontDefault().texture.id) + { + // Unload previous font texture + UnloadTexture(guiFont.texture); + RL_FREE(guiFont.recs); + RL_FREE(guiFont.glyphs); + guiFont.recs = NULL; + guiFont.glyphs = NULL; + + // Setup default raylib font + guiFont = GetFontDefault(); + + // NOTE: Default raylib font character 95 is a white square + Rectangle whiteChar = guiFont.recs[95]; + + // NOTE: We set up a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering + SetShapesTexture(guiFont.texture, RAYGUI_CLITERAL(Rectangle){ whiteChar.x + 1, whiteChar.y + 1, whiteChar.width - 2, whiteChar.height - 2 }); + } +} + +// Get text with icon id prepended +// NOTE: Useful to add icons by name id (enum) instead of +// a number that can change between ricon versions +const char *GuiIconText(int iconId, const char *text) +{ +#if defined(RAYGUI_NO_ICONS) + return NULL; +#else + static char buffer[1024] = { 0 }; + static char iconBuffer[16] = { 0 }; + + if (text != NULL) + { + memset(buffer, 0, 1024); + sprintf(buffer, "#%03i#", iconId); + + for (int i = 5; i < 1024; i++) + { + buffer[i] = text[i - 5]; + if (text[i - 5] == '\0') break; + } + + return buffer; + } + else + { + sprintf(iconBuffer, "#%03i#", iconId); + + return iconBuffer; + } +#endif +} + +#if !defined(RAYGUI_NO_ICONS) +// Get full icons data pointer +unsigned int *GuiGetIcons(void) { return guiIconsPtr; } + +// Load raygui icons file (.rgi) +// NOTE: In case nameIds are required, they can be requested with loadIconsName, +// they are returned as a guiIconsName[iconCount][RAYGUI_ICON_MAX_NAME_LENGTH], +// WARNING: guiIconsName[]][] memory should be manually freed! +char **GuiLoadIcons(const char *fileName, bool loadIconsName) +{ + // Style File Structure (.rgi) + // ------------------------------------------------------ + // Offset | Size | Type | Description + // ------------------------------------------------------ + // 0 | 4 | char | Signature: "rGI " + // 4 | 2 | short | Version: 100 + // 6 | 2 | short | reserved + + // 8 | 2 | short | Num icons (N) + // 10 | 2 | short | Icons size (Options: 16, 32, 64) (S) + + // Icons name id (32 bytes per name id) + // foreach (icon) + // { + // 12+32*i | 32 | char | Icon NameId + // } + + // Icons data: One bit per pixel, stored as unsigned int array (depends on icon size) + // S*S pixels/32bit per unsigned int = K unsigned int per icon + // foreach (icon) + // { + // ... | K | unsigned int | Icon Data + // } + + FILE *rgiFile = fopen(fileName, "rb"); + + char **guiIconsName = NULL; + + if (rgiFile != NULL) + { + char signature[5] = { 0 }; + short version = 0; + short reserved = 0; + short iconCount = 0; + short iconSize = 0; + + fread(signature, 1, 4, rgiFile); + fread(&version, sizeof(short), 1, rgiFile); + fread(&reserved, sizeof(short), 1, rgiFile); + fread(&iconCount, sizeof(short), 1, rgiFile); + fread(&iconSize, sizeof(short), 1, rgiFile); + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'I') && + (signature[3] == ' ')) + { + if (loadIconsName) + { + guiIconsName = (char **)RAYGUI_MALLOC(iconCount*sizeof(char **)); + for (int i = 0; i < iconCount; i++) + { + guiIconsName[i] = (char *)RAYGUI_MALLOC(RAYGUI_ICON_MAX_NAME_LENGTH); + fread(guiIconsName[i], 1, RAYGUI_ICON_MAX_NAME_LENGTH, rgiFile); + } + } + else fseek(rgiFile, iconCount*RAYGUI_ICON_MAX_NAME_LENGTH, SEEK_CUR); + + // Read icons data directly over internal icons array + fread(guiIconsPtr, sizeof(unsigned int), iconCount*(iconSize*iconSize/32), rgiFile); + } + + fclose(rgiFile); + } + + return guiIconsName; +} + +// Draw selected icon using rectangles pixel-by-pixel +void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color) +{ + #define BIT_CHECK(a,b) ((a) & (1u<<(b))) + + for (int i = 0, y = 0; i < RAYGUI_ICON_SIZE*RAYGUI_ICON_SIZE/32; i++) + { + for (int k = 0; k < 32; k++) + { + if (BIT_CHECK(guiIconsPtr[iconId*RAYGUI_ICON_DATA_ELEMENTS + i], k)) + { + #if !defined(RAYGUI_STANDALONE) + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ (float)posX + (k%RAYGUI_ICON_SIZE)*pixelSize, (float)posY + y*pixelSize, (float)pixelSize, (float)pixelSize }, 0, BLANK, color); + #endif + } + + if ((k == 15) || (k == 31)) y++; + } + } +} + +// Set icon drawing size +void GuiSetIconScale(int scale) +{ + if (scale >= 1) guiIconScale = scale; +} + +#endif // !RAYGUI_NO_ICONS + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- + +// Load style from memory +// WARNING: Binary files only +static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + + char signature[5] = { 0 }; + short version = 0; + short reserved = 0; + int propertyCount = 0; + + memcpy(signature, fileDataPtr, 4); + memcpy(&version, fileDataPtr + 4, sizeof(short)); + memcpy(&reserved, fileDataPtr + 4 + 2, sizeof(short)); + memcpy(&propertyCount, fileDataPtr + 4 + 2 + 2, sizeof(int)); + fileDataPtr += 12; + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'S') && + (signature[3] == ' ')) + { + short controlId = 0; + short propertyId = 0; + unsigned int propertyValue = 0; + + for (int i = 0; i < propertyCount; i++) + { + memcpy(&controlId, fileDataPtr, sizeof(short)); + memcpy(&propertyId, fileDataPtr + 2, sizeof(short)); + memcpy(&propertyValue, fileDataPtr + 2 + 2, sizeof(unsigned int)); + fileDataPtr += 8; + + if (controlId == 0) // DEFAULT control + { + // If a DEFAULT property is loaded, it is propagated to all controls + // NOTE: All DEFAULT properties should be defined first in the file + GuiSetStyle(0, (int)propertyId, propertyValue); + + if (propertyId < RAYGUI_MAX_PROPS_BASE) for (int j = 1; j < RAYGUI_MAX_CONTROLS; j++) GuiSetStyle(j, (int)propertyId, propertyValue); + } + else GuiSetStyle((int)controlId, (int)propertyId, propertyValue); + } + + // Font loading is highly dependant on raylib API to load font data and image + +#if !defined(RAYGUI_STANDALONE) + // Load custom font if available + int fontDataSize = 0; + memcpy(&fontDataSize, fileDataPtr, sizeof(int)); + fileDataPtr += 4; + + if (fontDataSize > 0) + { + Font font = { 0 }; + int fontType = 0; // 0-Normal, 1-SDF + + memcpy(&font.baseSize, fileDataPtr, sizeof(int)); + memcpy(&font.glyphCount, fileDataPtr + 4, sizeof(int)); + memcpy(&fontType, fileDataPtr + 4 + 4, sizeof(int)); + fileDataPtr += 12; + + // Load font white rectangle + Rectangle fontWhiteRec = { 0 }; + memcpy(&fontWhiteRec, fileDataPtr, sizeof(Rectangle)); + fileDataPtr += 16; + + // Load font image parameters + int fontImageUncompSize = 0; + int fontImageCompSize = 0; + memcpy(&fontImageUncompSize, fileDataPtr, sizeof(int)); + memcpy(&fontImageCompSize, fileDataPtr + 4, sizeof(int)); + fileDataPtr += 8; + + Image imFont = { 0 }; + imFont.mipmaps = 1; + memcpy(&imFont.width, fileDataPtr, sizeof(int)); + memcpy(&imFont.height, fileDataPtr + 4, sizeof(int)); + memcpy(&imFont.format, fileDataPtr + 4 + 4, sizeof(int)); + fileDataPtr += 12; + + if ((fontImageCompSize > 0) && (fontImageCompSize != fontImageUncompSize)) + { + // Compressed font atlas image data (DEFLATE), it requires DecompressData() + int dataUncompSize = 0; + unsigned char *compData = (unsigned char *)RAYGUI_MALLOC(fontImageCompSize); + memcpy(compData, fileDataPtr, fontImageCompSize); + fileDataPtr += fontImageCompSize; + + imFont.data = DecompressData(compData, fontImageCompSize, &dataUncompSize); + + // Security check, dataUncompSize must match the provided fontImageUncompSize + if (dataUncompSize != fontImageUncompSize) RAYGUI_LOG("WARNING: Uncompressed font atlas image data could be corrupted"); + + RAYGUI_FREE(compData); + } + else + { + // Font atlas image data is not compressed + imFont.data = (unsigned char *)RAYGUI_MALLOC(fontImageUncompSize); + memcpy(imFont.data, fileDataPtr, fontImageUncompSize); + fileDataPtr += fontImageUncompSize; + } + + if (font.texture.id != GetFontDefault().texture.id) UnloadTexture(font.texture); + font.texture = LoadTextureFromImage(imFont); + + RAYGUI_FREE(imFont.data); + + // Validate font atlas texture was loaded correctly + if (font.texture.id != 0) + { + // Load font recs data + int recsDataSize = font.glyphCount*sizeof(Rectangle); + int recsDataCompressedSize = 0; + + // WARNING: Version 400 adds the compression size parameter + if (version >= 400) + { + // RGS files version 400 support compressed recs data + memcpy(&recsDataCompressedSize, fileDataPtr, sizeof(int)); + fileDataPtr += sizeof(int); + } + + if ((recsDataCompressedSize > 0) && (recsDataCompressedSize != recsDataSize)) + { + // Recs data is compressed, uncompress it + unsigned char *recsDataCompressed = (unsigned char *)RAYGUI_MALLOC(recsDataCompressedSize); + + memcpy(recsDataCompressed, fileDataPtr, recsDataCompressedSize); + fileDataPtr += recsDataCompressedSize; + + int recsDataUncompSize = 0; + font.recs = (Rectangle *)DecompressData(recsDataCompressed, recsDataCompressedSize, &recsDataUncompSize); + + // Security check, data uncompressed size must match the expected original data size + if (recsDataUncompSize != recsDataSize) RAYGUI_LOG("WARNING: Uncompressed font recs data could be corrupted"); + + RAYGUI_FREE(recsDataCompressed); + } + else + { + // Recs data is uncompressed + font.recs = (Rectangle *)RAYGUI_CALLOC(font.glyphCount, sizeof(Rectangle)); + for (int i = 0; i < font.glyphCount; i++) + { + memcpy(&font.recs[i], fileDataPtr, sizeof(Rectangle)); + fileDataPtr += sizeof(Rectangle); + } + } + + // Load font glyphs info data + int glyphsDataSize = font.glyphCount*16; // 16 bytes data per glyph + int glyphsDataCompressedSize = 0; + + // WARNING: Version 400 adds the compression size parameter + if (version >= 400) + { + // RGS files version 400 support compressed glyphs data + memcpy(&glyphsDataCompressedSize, fileDataPtr, sizeof(int)); + fileDataPtr += sizeof(int); + } + + // Allocate required glyphs space to fill with data + font.glyphs = (GlyphInfo *)RAYGUI_CALLOC(font.glyphCount, sizeof(GlyphInfo)); + + if ((glyphsDataCompressedSize > 0) && (glyphsDataCompressedSize != glyphsDataSize)) + { + // Glyphs data is compressed, uncompress it + unsigned char *glypsDataCompressed = (unsigned char *)RAYGUI_MALLOC(glyphsDataCompressedSize); + + memcpy(glypsDataCompressed, fileDataPtr, glyphsDataCompressedSize); + fileDataPtr += glyphsDataCompressedSize; + + int glyphsDataUncompSize = 0; + unsigned char *glyphsDataUncomp = DecompressData(glypsDataCompressed, glyphsDataCompressedSize, &glyphsDataUncompSize); + + // Security check, data uncompressed size must match the expected original data size + if (glyphsDataUncompSize != glyphsDataSize) RAYGUI_LOG("WARNING: Uncompressed font glyphs data could be corrupted"); + + unsigned char *glyphsDataUncompPtr = glyphsDataUncomp; + + for (int i = 0; i < font.glyphCount; i++) + { + memcpy(&font.glyphs[i].value, glyphsDataUncompPtr, sizeof(int)); + memcpy(&font.glyphs[i].offsetX, glyphsDataUncompPtr + 4, sizeof(int)); + memcpy(&font.glyphs[i].offsetY, glyphsDataUncompPtr + 8, sizeof(int)); + memcpy(&font.glyphs[i].advanceX, glyphsDataUncompPtr + 12, sizeof(int)); + glyphsDataUncompPtr += 16; + } + + RAYGUI_FREE(glypsDataCompressed); + RAYGUI_FREE(glyphsDataUncomp); + } + else + { + // Glyphs data is uncompressed + for (int i = 0; i < font.glyphCount; i++) + { + memcpy(&font.glyphs[i].value, fileDataPtr, sizeof(int)); + memcpy(&font.glyphs[i].offsetX, fileDataPtr + 4, sizeof(int)); + memcpy(&font.glyphs[i].offsetY, fileDataPtr + 8, sizeof(int)); + memcpy(&font.glyphs[i].advanceX, fileDataPtr + 12, sizeof(int)); + fileDataPtr += 16; + } + } + } + else font = GetFontDefault(); // Fallback in case of errors loading font atlas texture + + GuiSetFont(font); + + // Set font texture source rectangle to be used as white texture to draw shapes + // NOTE: It makes possible to draw shapes and text (full UI) in a single draw call + if ((fontWhiteRec.x > 0) && + (fontWhiteRec.y > 0) && + (fontWhiteRec.width > 0) && + (fontWhiteRec.height > 0)) SetShapesTexture(font.texture, fontWhiteRec); + } +#endif + } +} + +// Gui get text width considering icon +static int GetTextWidth(const char *text) +{ + #if !defined(ICON_TEXT_PADDING) + #define ICON_TEXT_PADDING 4 + #endif + + Vector2 textSize = { 0 }; + int textIconOffset = 0; + + if ((text != NULL) && (text[0] != '\0')) + { + if (text[0] == '#') + { + for (int i = 1; (i < 5) && (text[i] != '\0'); i++) + { + if (text[i] == '#') + { + textIconOffset = i; + break; + } + } + } + + text += textIconOffset; + + // Make sure guiFont is set, GuiGetStyle() initializes it lazynessly + float fontSize = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + + // Custom MeasureText() implementation + if ((guiFont.texture.id > 0) && (text != NULL)) + { + // Get size in bytes of text, considering end of line and line break + int size = 0; + for (int i = 0; i < MAX_LINE_BUFFER_SIZE; i++) + { + if ((text[i] != '\0') && (text[i] != '\n')) size++; + else break; + } + + float scaleFactor = fontSize/(float)guiFont.baseSize; + textSize.y = (float)guiFont.baseSize*scaleFactor; + float glyphWidth = 0.0f; + + for (int i = 0, codepointSize = 0; i < size; i += codepointSize) + { + int codepoint = GetCodepointNext(&text[i], &codepointSize); + int codepointIndex = GetGlyphIndex(guiFont, codepoint); + + if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); + else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); + + textSize.x += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + } + + if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE + ICON_TEXT_PADDING); + } + + return (int)textSize.x; +} + +// Get text bounds considering control bounds +static Rectangle GetTextBounds(int control, Rectangle bounds) +{ + Rectangle textBounds = bounds; + + textBounds.x = bounds.x + GuiGetStyle(control, BORDER_WIDTH); + textBounds.y = bounds.y + GuiGetStyle(control, BORDER_WIDTH) + GuiGetStyle(control, TEXT_PADDING); + textBounds.width = bounds.width - 2*GuiGetStyle(control, BORDER_WIDTH) - 2*GuiGetStyle(control, TEXT_PADDING); + textBounds.height = bounds.height - 2*GuiGetStyle(control, BORDER_WIDTH) - 2*GuiGetStyle(control, TEXT_PADDING); // NOTE: Text is processed line per line! + + // Depending on control, TEXT_PADDING and TEXT_ALIGNMENT properties could affect the text-bounds + switch (control) + { + case COMBOBOX: + case DROPDOWNBOX: + case LISTVIEW: + // TODO: Special cases (no label): COMBOBOX, DROPDOWNBOX, LISTVIEW + case SLIDER: + case CHECKBOX: + case VALUEBOX: + case SPINNER: + // TODO: More special cases (label on side): SLIDER, CHECKBOX, VALUEBOX, SPINNER + default: + { + // TODO: WARNING: TEXT_ALIGNMENT is already considered in GuiDrawText() + if (GuiGetStyle(control, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT) textBounds.x -= GuiGetStyle(control, TEXT_PADDING); + else textBounds.x += GuiGetStyle(control, TEXT_PADDING); + } + break; + } + + return textBounds; +} + +// Get text icon if provided and move text cursor +// NOTE: We support up to 999 values for iconId +static const char *GetTextIcon(const char *text, int *iconId) +{ +#if !defined(RAYGUI_NO_ICONS) + *iconId = -1; + if (text[0] == '#') // Maybe we have an icon! + { + char iconValue[4] = { 0 }; // Maximum length for icon value: 3 digits + '\0' + + int pos = 1; + while ((pos < 4) && (text[pos] >= '0') && (text[pos] <= '9')) + { + iconValue[pos - 1] = text[pos]; + pos++; + } + + if (text[pos] == '#') + { + *iconId = TextToInteger(iconValue); + + // Move text pointer after icon + // WARNING: If only icon provided, it could point to EOL character: '\0' + if (*iconId >= 0) text += (pos + 1); + } + } +#endif + + return text; +} + +// Get text divided into lines (by line-breaks '\n') +const char **GetTextLines(const char *text, int *count) +{ + #define RAYGUI_MAX_TEXT_LINES 128 + + static const char *lines[RAYGUI_MAX_TEXT_LINES] = { 0 }; + for (int i = 0; i < RAYGUI_MAX_TEXT_LINES; i++) lines[i] = NULL; // Init NULL pointers to substrings + + int textSize = (int)strlen(text); + + lines[0] = text; + int len = 0; + *count = 1; + //int lineSize = 0; // Stores current line size, not returned + + for (int i = 0, k = 0; (i < textSize) && (*count < RAYGUI_MAX_TEXT_LINES); i++) + { + if (text[i] == '\n') + { + //lineSize = len; + k++; + lines[k] = &text[i + 1]; // WARNING: next value is valid? + len = 0; + *count += 1; + } + else len++; + } + + //lines[*count - 1].size = len; + + return lines; +} + +// Get text width to next space for provided string +static float GetNextSpaceWidth(const char *text, int *nextSpaceIndex) +{ + float width = 0; + int codepointByteCount = 0; + int codepoint = 0; + int index = 0; + float glyphWidth = 0; + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; + + for (int i = 0; text[i] != '\0'; i++) + { + if (text[i] != ' ') + { + codepoint = GetCodepoint(&text[i], &codepointByteCount); + index = GetGlyphIndex(guiFont, codepoint); + glyphWidth = (guiFont.glyphs[index].advanceX == 0)? guiFont.recs[index].width*scaleFactor : guiFont.glyphs[index].advanceX*scaleFactor; + width += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + else + { + *nextSpaceIndex = i; + break; + } + } + + return width; +} + +// Gui draw text using default font +static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, Color tint) +{ + #define TEXT_VALIGN_PIXEL_OFFSET(h) ((int)h%2) // Vertical alignment for pixel perfect + + #if !defined(ICON_TEXT_PADDING) + #define ICON_TEXT_PADDING 4 + #endif + + if ((text == NULL) || (text[0] == '\0')) return; // Security check + + // PROCEDURE: + // - Text is processed line per line + // - For every line, horizontal alignment is defined + // - For all text, vertical alignment is defined (multiline text only) + // - For every line, wordwrap mode is checked (useful for GuitextBox(), read-only) + + // Get text lines (using '\n' as delimiter) to be processed individually + // WARNING: We can't use GuiTextSplit() function because it can be already used + // before the GuiDrawText() call and its buffer is static, it would be overriden :( + int lineCount = 0; + const char **lines = GetTextLines(text, &lineCount); + + // Text style variables + //int alignment = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT); + int alignmentVertical = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL); + int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); // Wrap-mode only available in read-only mode, no for text editing + + // TODO: WARNING: This totalHeight is not valid for vertical alignment in case of word-wrap + float totalHeight = (float)(lineCount*GuiGetStyle(DEFAULT, TEXT_SIZE) + (lineCount - 1)*GuiGetStyle(DEFAULT, TEXT_SIZE)/2); + float posOffsetY = 0.0f; + + for (int i = 0; i < lineCount; i++) + { + int iconId = 0; + lines[i] = GetTextIcon(lines[i], &iconId); // Check text for icon and move cursor + + // Get text position depending on alignment and iconId + //--------------------------------------------------------------------------------- + Vector2 textBoundsPosition = { textBounds.x, textBounds.y }; + float textBoundsWidthOffset = 0.0f; + + // NOTE: We get text size after icon has been processed + // WARNING: GetTextWidth() also processes text icon to get width! -> Really needed? + int textSizeX = GetTextWidth(lines[i]); + + // If text requires an icon, add size to measure + if (iconId >= 0) + { + textSizeX += RAYGUI_ICON_SIZE*guiIconScale; + + // WARNING: If only icon provided, text could be pointing to EOF character: '\0' +#if !defined(RAYGUI_NO_ICONS) + if ((lines[i] != NULL) && (lines[i][0] != '\0')) textSizeX += ICON_TEXT_PADDING; +#endif + } + + // Check guiTextAlign global variables + switch (alignment) + { + case TEXT_ALIGN_LEFT: textBoundsPosition.x = textBounds.x; break; + case TEXT_ALIGN_CENTER: textBoundsPosition.x = textBounds.x + textBounds.width/2 - textSizeX/2; break; + case TEXT_ALIGN_RIGHT: textBoundsPosition.x = textBounds.x + textBounds.width - textSizeX; break; + default: break; + } + + if (textSizeX > textBounds.width && (lines[i] != NULL) && (lines[i][0] != '\0')) textBoundsPosition.x = textBounds.x; + + switch (alignmentVertical) + { + // Only valid in case of wordWrap = 0; + case TEXT_ALIGN_TOP: textBoundsPosition.y = textBounds.y + posOffsetY; break; + case TEXT_ALIGN_MIDDLE: textBoundsPosition.y = textBounds.y + posOffsetY + textBounds.height/2 - totalHeight/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height); break; + case TEXT_ALIGN_BOTTOM: textBoundsPosition.y = textBounds.y + posOffsetY + textBounds.height - totalHeight + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height); break; + default: break; + } + + // NOTE: Make sure we get pixel-perfect coordinates, + // In case of decimals we got weird text positioning + textBoundsPosition.x = (float)((int)textBoundsPosition.x); + textBoundsPosition.y = (float)((int)textBoundsPosition.y); + //--------------------------------------------------------------------------------- + + // Draw text (with icon if available) + //--------------------------------------------------------------------------------- +#if !defined(RAYGUI_NO_ICONS) + if (iconId >= 0) + { + // NOTE: We consider icon height, probably different than text size + GuiDrawIcon(iconId, (int)textBoundsPosition.x, (int)(textBounds.y + textBounds.height/2 - RAYGUI_ICON_SIZE*guiIconScale/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height)), guiIconScale, tint); + textBoundsPosition.x += (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + textBoundsWidthOffset = (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + } +#endif + // Get size in bytes of text, + // considering end of line and line break + int lineSize = 0; + for (int c = 0; (lines[i][c] != '\0') && (lines[i][c] != '\n') && (lines[i][c] != '\r'); c++, lineSize++){ } + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; + + int lastSpaceIndex = 0; + bool tempWrapCharMode = false; + + int textOffsetY = 0; + float textOffsetX = 0.0f; + float glyphWidth = 0; + + int ellipsisWidth = GetTextWidth("..."); + bool textOverflow = false; + for (int c = 0, codepointSize = 0; c < lineSize; c += codepointSize) + { + int codepoint = GetCodepointNext(&lines[i][c], &codepointSize); + int index = GetGlyphIndex(guiFont, codepoint); + + // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol moving one byte + if (codepoint == 0x3f) codepointSize = 1; // TODO: Review not recognized codepoints size + + // Get glyph width to check if it goes out of bounds + if (guiFont.glyphs[index].advanceX == 0) glyphWidth = ((float)guiFont.recs[index].width*scaleFactor); + else glyphWidth = (float)guiFont.glyphs[index].advanceX*scaleFactor; + + // Wrap mode text measuring, to validate if + // it can be drawn or a new line is required + if (wrapMode == TEXT_WRAP_CHAR) + { + // Jump to next line if current character reach end of the box limits + if ((textOffsetX + glyphWidth) > textBounds.width - textBoundsWidthOffset) + { + textOffsetX = 0.0f; + textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + + if (tempWrapCharMode) // Wrap at char level when too long words + { + wrapMode = TEXT_WRAP_WORD; + tempWrapCharMode = false; + } + } + } + else if (wrapMode == TEXT_WRAP_WORD) + { + if (codepoint == 32) lastSpaceIndex = c; + + // Get width to next space in line + int nextSpaceIndex = 0; + float nextSpaceWidth = GetNextSpaceWidth(lines[i] + c, &nextSpaceIndex); + + int nextSpaceIndex2 = 0; + float nextWordSize = GetNextSpaceWidth(lines[i] + lastSpaceIndex + 1, &nextSpaceIndex2); + + if (nextWordSize > textBounds.width - textBoundsWidthOffset) + { + // Considering the case the next word is longer than bounds + tempWrapCharMode = true; + wrapMode = TEXT_WRAP_CHAR; + } + else if ((textOffsetX + nextSpaceWidth) > textBounds.width - textBoundsWidthOffset) + { + textOffsetX = 0.0f; + textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + } + } + + if (codepoint == '\n') break; // WARNING: Lines are already processed manually, no need to keep drawing after this codepoint + else + { + // TODO: There are multiple types of spaces in Unicode, + // maybe it's a good idea to add support for more: http://jkorpela.fi/chars/spaces.html + if ((codepoint != ' ') && (codepoint != '\t')) // Do not draw codepoints with no glyph + { + if (wrapMode == TEXT_WRAP_NONE) + { + // Draw only required text glyphs fitting the textBounds.width + if (textSizeX > textBounds.width) + { + if (textOffsetX <= (textBounds.width - glyphWidth - textBoundsWidthOffset - ellipsisWidth)) + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + else if (!textOverflow) + { + textOverflow = true; + + for (int j = 0; j < ellipsisWidth; j += ellipsisWidth/3) + { + DrawTextCodepoint(guiFont, '.', RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX + j, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + } + else + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + else if ((wrapMode == TEXT_WRAP_CHAR) || (wrapMode == TEXT_WRAP_WORD)) + { + // Draw only glyphs inside the bounds + if ((textBoundsPosition.y + textOffsetY) <= (textBounds.y + textBounds.height - GuiGetStyle(DEFAULT, TEXT_SIZE))) + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + } + + if (guiFont.glyphs[index].advanceX == 0) textOffsetX += ((float)guiFont.recs[index].width*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + else textOffsetX += ((float)guiFont.glyphs[index].advanceX*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + } + + if (wrapMode == TEXT_WRAP_NONE) posOffsetY += (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + else if ((wrapMode == TEXT_WRAP_CHAR) || (wrapMode == TEXT_WRAP_WORD)) posOffsetY += (textOffsetY + (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING)); + //--------------------------------------------------------------------------------- + } + +#if defined(RAYGUI_DEBUG_TEXT_BOUNDS) + GuiDrawRectangle(textBounds, 0, WHITE, Fade(BLUE, 0.4f)); +#endif +} + +// Gui draw rectangle using default raygui plain style with borders +static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, Color color) +{ + if (color.a > 0) + { + // Draw rectangle filled with color + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, GuiFade(color, guiAlpha)); + } + + if (borderWidth > 0) + { + // Draw rectangle border lines with color + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, borderWidth, GuiFade(borderColor, guiAlpha)); + DrawRectangle((int)rec.x, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, GuiFade(borderColor, guiAlpha)); + DrawRectangle((int)rec.x + (int)rec.width - borderWidth, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, GuiFade(borderColor, guiAlpha)); + DrawRectangle((int)rec.x, (int)rec.y + (int)rec.height - borderWidth, (int)rec.width, borderWidth, GuiFade(borderColor, guiAlpha)); + } + +#if defined(RAYGUI_DEBUG_RECS_BOUNDS) + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, Fade(RED, 0.4f)); +#endif +} + +// Draw tooltip using control bounds +static void GuiTooltip(Rectangle controlRec) +{ + if (!guiLocked && guiTooltip && (guiTooltipPtr != NULL) && !guiControlExclusiveMode) + { + Vector2 textSize = MeasureTextEx(GuiGetFont(), guiTooltipPtr, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + + if ((controlRec.x + textSize.x + 16) > GetScreenWidth()) controlRec.x -= (textSize.x + 16 - controlRec.width); + + GuiPanel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, NULL); + + int textPadding = GuiGetStyle(LABEL, TEXT_PADDING); + int textAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_PADDING, 0); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiLabel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, guiTooltipPtr); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, textAlignment); + GuiSetStyle(LABEL, TEXT_PADDING, textPadding); + } +} + +// Split controls text into multiple strings +// Also check for multiple columns (required by GuiToggleGroup()) +static const char **GuiTextSplit(const char *text, char delimiter, int *count, int *textRow) +{ + // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) + // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, + // all used memory is static... it has some limitations: + // 1. Maximum number of possible split strings is set by RAYGUI_TEXTSPLIT_MAX_ITEMS + // 2. Maximum size of text to split is RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE + // NOTE: Those definitions could be externally provided if required + + // TODO: HACK: GuiTextSplit() - Review how textRows are returned to user + // textRow is an externally provided array of integers that stores row number for every splitted string + + #if !defined(RAYGUI_TEXTSPLIT_MAX_ITEMS) + #define RAYGUI_TEXTSPLIT_MAX_ITEMS 128 + #endif + #if !defined(RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE) + #define RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE 1024 + #endif + + static const char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { NULL }; // String pointers array (points to buffer data) + static char buffer[RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE] = { 0 }; // Buffer data (text input copy with '\0' added) + memset(buffer, 0, RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE); + + result[0] = buffer; + int counter = 1; + + if (textRow != NULL) textRow[0] = 0; + + // Count how many substrings we have on text and point to every one + for (int i = 0; i < RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE; i++) + { + buffer[i] = text[i]; + if (buffer[i] == '\0') break; + else if ((buffer[i] == delimiter) || (buffer[i] == '\n')) + { + result[counter] = buffer + i + 1; + + if (textRow != NULL) + { + if (buffer[i] == '\n') textRow[counter] = textRow[counter - 1] + 1; + else textRow[counter] = textRow[counter - 1]; + } + + buffer[i] = '\0'; // Set an end of string at this point + + counter++; + if (counter > RAYGUI_TEXTSPLIT_MAX_ITEMS) break; + } + } + + *count = counter; + + return result; +} + +// Convert color data from RGB to HSV +// NOTE: Color data should be passed normalized +static Vector3 ConvertRGBtoHSV(Vector3 rgb) +{ + Vector3 hsv = { 0 }; + float min = 0.0f; + float max = 0.0f; + float delta = 0.0f; + + min = (rgb.x < rgb.y)? rgb.x : rgb.y; + min = (min < rgb.z)? min : rgb.z; + + max = (rgb.x > rgb.y)? rgb.x : rgb.y; + max = (max > rgb.z)? max : rgb.z; + + hsv.z = max; // Value + delta = max - min; + + if (delta < 0.00001f) + { + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } + + if (max > 0.0f) + { + // NOTE: If max is 0, this divide would cause a crash + hsv.y = (delta/max); // Saturation + } + else + { + // NOTE: If max is 0, then r = g = b = 0, s = 0, h is undefined + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } + + // NOTE: Comparing float values could not work properly + if (rgb.x >= max) hsv.x = (rgb.y - rgb.z)/delta; // Between yellow & magenta + else + { + if (rgb.y >= max) hsv.x = 2.0f + (rgb.z - rgb.x)/delta; // Between cyan & yellow + else hsv.x = 4.0f + (rgb.x - rgb.y)/delta; // Between magenta & cyan + } + + hsv.x *= 60.0f; // Convert to degrees + + if (hsv.x < 0.0f) hsv.x += 360.0f; + + return hsv; +} + +// Convert color data from HSV to RGB +// NOTE: Color data should be passed normalized +static Vector3 ConvertHSVtoRGB(Vector3 hsv) +{ + Vector3 rgb = { 0 }; + float hh = 0.0f, p = 0.0f, q = 0.0f, t = 0.0f, ff = 0.0f; + long i = 0; + + // NOTE: Comparing float values could not work properly + if (hsv.y <= 0.0f) + { + rgb.x = hsv.z; + rgb.y = hsv.z; + rgb.z = hsv.z; + return rgb; + } + + hh = hsv.x; + if (hh >= 360.0f) hh = 0.0f; + hh /= 60.0f; + + i = (long)hh; + ff = hh - i; + p = hsv.z*(1.0f - hsv.y); + q = hsv.z*(1.0f - (hsv.y*ff)); + t = hsv.z*(1.0f - (hsv.y*(1.0f - ff))); + + switch (i) + { + case 0: + { + rgb.x = hsv.z; + rgb.y = t; + rgb.z = p; + } break; + case 1: + { + rgb.x = q; + rgb.y = hsv.z; + rgb.z = p; + } break; + case 2: + { + rgb.x = p; + rgb.y = hsv.z; + rgb.z = t; + } break; + case 3: + { + rgb.x = p; + rgb.y = q; + rgb.z = hsv.z; + } break; + case 4: + { + rgb.x = t; + rgb.y = p; + rgb.z = hsv.z; + } break; + case 5: + default: + { + rgb.x = hsv.z; + rgb.y = p; + rgb.z = q; + } break; + } + + return rgb; +} + +// Scroll bar control (used by GuiScrollPanel()) +static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) +{ + GuiState state = guiState; + + // Is the scrollbar horizontal or vertical? + bool isVertical = (bounds.width > bounds.height)? false : true; + + // The size (width or height depending on scrollbar type) of the spinner buttons + const int spinnerSize = GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)? + (isVertical? (int)bounds.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) : + (int)bounds.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH)) : 0; + + // Arrow buttons [<] [>] [∧] [∨] + Rectangle arrowUpLeft = { 0 }; + Rectangle arrowDownRight = { 0 }; + + // Actual area of the scrollbar excluding the arrow buttons + Rectangle scrollbar = { 0 }; + + // Slider bar that moves --[///]----- + Rectangle slider = { 0 }; + + // Normalize value + if (value > maxValue) value = maxValue; + if (value < minValue) value = minValue; + + int valueRange = maxValue - minValue; + if (valueRange <= 0) valueRange = 1; + + int sliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + if (sliderSize < 1) sliderSize = 1; // TODO: Consider a minimum slider size + + // Calculate rectangles for all of the components + arrowUpLeft = RAYGUI_CLITERAL(Rectangle){ + (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), + (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), + (float)spinnerSize, (float)spinnerSize }; + + if (isVertical) + { + arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + bounds.height - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; + scrollbar = RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), arrowUpLeft.y + arrowUpLeft.height, bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)), bounds.height - arrowUpLeft.height - arrowDownRight.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) }; + + // Make sure the slider won't get outside of the scrollbar + sliderSize = (sliderSize >= scrollbar.height)? ((int)scrollbar.height - 2) : sliderSize; + slider = RAYGUI_CLITERAL(Rectangle){ + bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), + scrollbar.y + (int)(((float)(value - minValue)/valueRange)*(scrollbar.height - sliderSize)), + bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)), + (float)sliderSize }; + } + else // horizontal + { + arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + bounds.width - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; + scrollbar = RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x + arrowUpLeft.width, bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), bounds.width - arrowUpLeft.width - arrowDownRight.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH), bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)) }; + + // Make sure the slider won't get outside of the scrollbar + sliderSize = (sliderSize >= scrollbar.width)? ((int)scrollbar.width - 2) : sliderSize; + slider = RAYGUI_CLITERAL(Rectangle){ + scrollbar.x + (int)(((float)(value - minValue)/valueRange)*(scrollbar.width - sliderSize)), + bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), + (float)sliderSize, + bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)) }; + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && + !CheckCollisionPointRec(mousePoint, arrowUpLeft) && + !CheckCollisionPointRec(mousePoint, arrowDownRight)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + + if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); + else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + + // Handle mouse wheel + int wheel = (int)GetMouseWheelMove(); + if (wheel != 0) value += wheel; + + // Handle mouse button down + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + // Check arrows click + if (CheckCollisionPointRec(mousePoint, arrowUpLeft)) value -= valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + else if (CheckCollisionPointRec(mousePoint, arrowDownRight)) value += valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + else if (!CheckCollisionPointRec(mousePoint, slider)) + { + // If click on scrollbar position but not on slider, place slider directly on that position + if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); + else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); + } + + state = STATE_PRESSED; + } + + // Keyboard control on mouse hover scrollbar + /* + if (isVertical) + { + if (IsKeyDown(KEY_DOWN)) value += 5; + else if (IsKeyDown(KEY_UP)) value -= 5; + } + else + { + if (IsKeyDown(KEY_RIGHT)) value += 5; + else if (IsKeyDown(KEY_LEFT)) value -= 5; + } + */ + } + + // Normalize value + if (value > maxValue) value = maxValue; + if (value < minValue) value = minValue; + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SCROLLBAR, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED))); // Draw the background + + GuiDrawRectangle(scrollbar, 0, BLANK, GetColor(GuiGetStyle(BUTTON, BASE_COLOR_NORMAL))); // Draw the scrollbar active area background + GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BORDER + state*3))); // Draw the slider bar + + // Draw arrows (using icon if available) + if (GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)) + { +#if defined(RAYGUI_NO_ICONS) + GuiDrawText(isVertical? "^" : "<", + RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); + GuiDrawText(isVertical? "v" : ">", + RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); +#else + GuiDrawText(isVertical? "#121#" : "#118#", + RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_UP_FILL / ICON_ARROW_LEFT_FILL + GuiDrawText(isVertical? "#120#" : "#119#", + RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_DOWN_FILL / ICON_ARROW_RIGHT_FILL +#endif + } + //-------------------------------------------------------------------- + + return value; +} + +// Color fade-in or fade-out, alpha goes from 0.0f to 1.0f +// WARNING: It multiplies current alpha by alpha scale factor +static Color GuiFade(Color color, float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + Color result = { color.r, color.g, color.b, (unsigned char)(color.a*alpha) }; + + return result; +} + +#if defined(RAYGUI_STANDALONE) +// Returns a Color struct from hexadecimal value +static Color GetColor(int hexValue) +{ + Color color; + + color.r = (unsigned char)(hexValue >> 24) & 0xFF; + color.g = (unsigned char)(hexValue >> 16) & 0xFF; + color.b = (unsigned char)(hexValue >> 8) & 0xFF; + color.a = (unsigned char)hexValue & 0xFF; + + return color; +} + +// Returns hexadecimal value for a Color +static int ColorToInt(Color color) +{ + return (((int)color.r << 24) | ((int)color.g << 16) | ((int)color.b << 8) | (int)color.a); +} + +// Check if point is inside rectangle +static bool CheckCollisionPointRec(Vector2 point, Rectangle rec) +{ + bool collision = false; + + if ((point.x >= rec.x) && (point.x <= (rec.x + rec.width)) && + (point.y >= rec.y) && (point.y <= (rec.y + rec.height))) collision = true; + + return collision; +} + +// Formatting of text with variables to 'embed' +static const char *TextFormat(const char *text, ...) +{ + #if !defined(RAYGUI_TEXTFORMAT_MAX_SIZE) + #define RAYGUI_TEXTFORMAT_MAX_SIZE 256 + #endif + + static char buffer[RAYGUI_TEXTFORMAT_MAX_SIZE]; + + va_list args; + va_start(args, text); + vsprintf(buffer, text, args); + va_end(args); + + return buffer; +} + +// Draw rectangle with vertical gradient fill color +// NOTE: This function is only used by GuiColorPicker() +static void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2) +{ + Rectangle bounds = { (float)posX, (float)posY, (float)width, (float)height }; + DrawRectangleGradientEx(bounds, color1, color2, color2, color1); +} + +// Split string into multiple strings +const char **TextSplit(const char *text, char delimiter, int *count) +{ + // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) + // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, + // all used memory is static... it has some limitations: + // 1. Maximum number of possible split strings is set by RAYGUI_TEXTSPLIT_MAX_ITEMS + // 2. Maximum size of text to split is RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE + + #if !defined(RAYGUI_TEXTSPLIT_MAX_ITEMS) + #define RAYGUI_TEXTSPLIT_MAX_ITEMS 128 + #endif + #if !defined(RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE) + #define RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE 1024 + #endif + + static const char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { NULL }; + static char buffer[RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE] = { 0 }; + memset(buffer, 0, RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE); + + result[0] = buffer; + int counter = 0; + + if (text != NULL) + { + counter = 1; + + // Count how many substrings we have on text and point to every one + for (int i = 0; i < RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE; i++) + { + buffer[i] = text[i]; + if (buffer[i] == '\0') break; + else if (buffer[i] == delimiter) + { + buffer[i] = '\0'; // Set an end of string at this point + result[counter] = buffer + i + 1; + counter++; + + if (counter == RAYGUI_TEXTSPLIT_MAX_ITEMS) break; + } + } + } + + *count = counter; + return result; +} + +// Get integer value from text +// NOTE: This function replaces atoi() [stdlib.h] +static int TextToInteger(const char *text) +{ + int value = 0; + int sign = 1; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1; + text++; + } + + for (int i = 0; ((text[i] >= '0') && (text[i] <= '9')); ++i) value = value*10 + (int)(text[i] - '0'); + + return value*sign; +} + +// Get float value from text +// NOTE: This function replaces atof() [stdlib.h] +// WARNING: Only '.' character is understood as decimal point +static float TextToFloat(const char *text) +{ + float value = 0.0f; + float sign = 1.0f; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1.0f; + text++; + } + + int i = 0; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0'); + + if (text[i++] != '.') value *= sign; + else + { + float divisor = 10.0f; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) + { + value += ((float)(text[i] - '0'))/divisor; + divisor = divisor*10.0f; + } + } + + return value; +} + +// Encode codepoint into UTF-8 text (char array size returned as parameter) +static const char *CodepointToUTF8(int codepoint, int *byteSize) +{ + static char utf8[6] = { 0 }; + int size = 0; + + if (codepoint <= 0x7f) + { + utf8[0] = (char)codepoint; + size = 1; + } + else if (codepoint <= 0x7ff) + { + utf8[0] = (char)(((codepoint >> 6) & 0x1f) | 0xc0); + utf8[1] = (char)((codepoint & 0x3f) | 0x80); + size = 2; + } + else if (codepoint <= 0xffff) + { + utf8[0] = (char)(((codepoint >> 12) & 0x0f) | 0xe0); + utf8[1] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[2] = (char)((codepoint & 0x3f) | 0x80); + size = 3; + } + else if (codepoint <= 0x10ffff) + { + utf8[0] = (char)(((codepoint >> 18) & 0x07) | 0xf0); + utf8[1] = (char)(((codepoint >> 12) & 0x3f) | 0x80); + utf8[2] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[3] = (char)((codepoint & 0x3f) | 0x80); + size = 4; + } + + *byteSize = size; + + return utf8; +} + +// Get next codepoint in a UTF-8 encoded text, scanning until '\0' is found +// When a invalid UTF-8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned +// Total number of bytes processed are returned as a parameter +// NOTE: the standard says U+FFFD should be returned in case of errors +// but that character is not supported by the default font in raylib +static int GetCodepointNext(const char *text, int *codepointSize) +{ + const char *ptr = text; + int codepoint = 0x3f; // Codepoint (defaults to '?') + *codepointSize = 1; + + // Get current codepoint and bytes processed + if (0xf0 == (0xf8 & ptr[0])) + { + // 4 byte UTF-8 codepoint + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks + codepoint = ((0x07 & ptr[0]) << 18) | ((0x3f & ptr[1]) << 12) | ((0x3f & ptr[2]) << 6) | (0x3f & ptr[3]); + *codepointSize = 4; + } + else if (0xe0 == (0xf0 & ptr[0])) + { + // 3 byte UTF-8 codepoint */ + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks + codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]); + *codepointSize = 3; + } + else if (0xc0 == (0xe0 & ptr[0])) + { + // 2 byte UTF-8 codepoint + if ((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } //10xxxxxx checks + codepoint = ((0x1f & ptr[0]) << 6) | (0x3f & ptr[1]); + *codepointSize = 2; + } + else if (0x00 == (0x80 & ptr[0])) + { + // 1 byte UTF-8 codepoint + codepoint = ptr[0]; + *codepointSize = 1; + } + + return codepoint; +} +#endif // RAYGUI_STANDALONE + +#endif // RAYGUI_IMPLEMENTATION diff --git a/examples/shaders/resources/LICENSE.md b/examples/shaders/resources/LICENSE.md index 7210e34b2..078036df7 100644 --- a/examples/shaders/resources/LICENSE.md +++ b/examples/shaders/resources/LICENSE.md @@ -10,4 +10,7 @@ | space.png | ❔ | ❔ | - | | texel_checker.png | [@raysan5](https://github.com/raysan5) | [CC0](https://creativecommons.org/publicdomain/zero/1.0/) | Made with [UV Checker Map Maker](http://uvchecker.byvalle.com/) | | cubicmap.png | [@raysan5](https://github.com/raysan5) | [CC0](https://creativecommons.org/publicdomain/zero/1.0/) | - | -| spark_flame.png | [@raysan5](https://github.com/raysan5) | [CC0](https://creativecommons.org/publicdomain/zero/1.0/) | Made with [EffectTextureMaker](https://mebiusbox.github.io/contents/EffectTextureMaker/) | \ No newline at end of file +| spark_flame.png | [@raysan5](https://github.com/raysan5) | [CC0](https://creativecommons.org/publicdomain/zero/1.0/) | Made with [EffectTextureMaker](https://mebiusbox.github.io/contents/EffectTextureMaker/) | +| parrots.png | [Kodak set](http://r0k.us/graphics/kodak/) | ❔ | Original name: `kodim23.png` | +| cat.png | ❔ | ❔ | - | +| mandrill.png | ❔ | [CC0](https://creativecommons.org/publicdomain/zero/1.0/) | Mandrill (a.k.a. Baboon) | \ No newline at end of file diff --git a/examples/shaders/resources/cat.png b/examples/shaders/resources/cat.png new file mode 100644 index 0000000000000000000000000000000000000000..db56b9eadbcbe2a750734fd1bc1b5f9b416d2e95 GIT binary patch literal 388467 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≺Lq$x%fi1H<%?q?+t|Pf%U(^I<&D#ScJ&0W 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%0D5X0!j=z)-s`cNvIR1NrSZN|}bQ##|GAxeN< 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
AsN_KAz_ybUu_u!7m>DjEs|@#l_vC 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

gR@NcsWAim5;gitwqDXVDb5u~qt- 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 Z$HH@ws2L4o-(dlMCb!KE>*20R{|6pN_#FTM literal 0 HcmV?d00001 diff --git a/examples/shaders/resources/mandrill.png b/examples/shaders/resources/mandrill.png new file mode 100644 index 0000000000000000000000000000000000000000..02d058eb0d0d055c85c42f08ae2dfb34709642c9 GIT binary patch literal 197452 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≪*A^0V0m$nx;jPl&WZnMACBr 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%0ANvG=iG>~aPfKEl#)INQD5hNC!gR=M z0BA)8yN^V7=awkn|DVMT=I z5wET|c+)jHO?2%-M3*_i-ec70*+(O|ko&X8AFH4*g#q&-RBX*HBZS@!r$*8ly?*@Y zd$Uvyde^R5=GTEDzvo2DhI=um->x<>M8H{v!A`6uBYnp-nHG^=<;Oa z+}-Xu3iZVJlxv2LyL*^!Ja5#|uyBOi?$JKhr%^kDLNWgFU)Q8!raUsPOUM+V^H~uN zYQ>)|E((snksU;EL5)u+`^^d`zY2IjrLhg3h3Jiy$6(hj*YtM+uNKVU19v%|QoH zL-`4SW^}2~j402_1Vi4>X(s!6`RnJ`&!1nf{LA&@|M-```*D4}NUc|d5WVP(s}s6b zWIC`iGZGoP@>(JC%GL6TNP-!irCT&R2+@~7tE@Ly7={(G zZ86uPfHC~mfNmjm)f!@qs#;9%`&>0Bh}~-qm!ckD*%94ab|E5#C7F-mkajUWr0&7l%xMNf5i9h)Zn^->|6+He;(Tim1`JMpEbSYsu9!uRuqXtS(ct$t?RQBEWWO zp7&#-R={@)2HfAIWyF(&p2BtVe zUfMO+hz`cIuT;Qjtj3Ttp`G3%%)^EPa5pWqS~Uxe0aEzb`FwaXXvZf2X27noFBPm9 zaca1F@d!LEv(H+5#ITc40Mr9Zq5!8$5#?d|vggdA-4ihg7+dY7$Zlat>Prz|a^Q#I>Z%|^kH5a1VjsBtk6zfOsC>@w^_6FmW{4% zF}mL+!+M?$vY!&#-bJB%?*hKt_QrkR-TUdMcHhtaeebGY&+}Am?|1KMKyLR_6NC0d zY{?IJ15+*ZB!f-?{V3%oiPJuarwQk@r0`5sKe^@jr+>SS&~oIrEU2Y!dCGvZC>@8R zKM3%-8PCJ?T9{tnEY)%Xrg=yo(~OzA{d_3r5`gDej3@l#qFZj&7R{~?iYli6b&Ne{ z6th~{Tv#td&1}6a&EpHo`Iy7R3^vcdCT2@GFEBJmi_|;Pnl0&X^pOTpg`>NU`H~Pp z6t`lSM|Kvnj~z1}L_?kiKqRj(eZ9VZeEmJ+kH7r*x%36UUe{NG!B`6Z{JEf&2{Xlj z6xUkWV~3$bEv;CJ(D*f|AMnyEDM1nJC6h~R;Tc7yM z9Z#agm)V69%JBjsgz7DK_m2s3Okve9OYjXT!{z4?6>$%bQe%`g8cGZ1%ZM&*=HOQyCdkEuxgf&<2K6l;8s!&V|I6YXO!)Cy)l|8*s zqnhlRy{>>-Ts3`HR%qDyHD%fmHa-3cenj*hjV*h`q3*QvQxYcJTocDekD{LLW@y0( zl&N6j;{lsWImX~KJ%ou-h6#7CY107B$%)Px3RWy1z%rQ6oUZ7MBV%IgFrT4jA01cs z)NaXe!EDcprwq>~!%@u|Z7=sk#ph?x_WY^ECbIoOiq6D;fH10Y8gQ-;b2m~kXrux_ zCgUu|2H}u3=q6iINo|qXbDecxP|bIk+KTjNH_?nC&M2n5hNW&cd9g&(>ZdM5W5YEs zYwqYq_<4+QZ#U@2ba`{{?Ha4+bMFi^O=mT(v=K`!iupn^qCFbgzQUV8b48PkyMyOE zFifPD-K=gyRlPNQOHc25Z#Uz;pYQ!$?O)z^*ZZ!!pLV^U-nHHJ*yT1|?WZkNjYe+b zICC}gu>*Nd)vPug@@HgooT8_Gm$2REIt`QY&;R}^&)jsz<(_1za&z^J_#@CbUS|vs z<$Na3z;3wEr>bk@{QHHTT*Z7h1OnypLW4wYcF_;L!_D4e%LPAj(M7gu0=DjsftwYw zdBI6tMl=^RhxCAjm4MJZiwDJtsLo4BhU-x2yon|%(eg1NiLIc9S_ns|F#I$(#kEY4 zImtFKftYQKTLx`((~+QheeuWF_4V_|kJn#*eEp30`dU}!6?`#X0cFOOj3l>SUvw{q zmy+s*%&US^LpvY?9w!Qeh(&l;#n+47390ZB#kdF3UL~ynT7eBkh)TwSZ!nDxVof-m zf+O(kZnq*fw!`85SPuS(1ktH%G%{0nz_mF$5Hh_H+D1jFAXg^eRUNU#z~NVcuD;QJ z;9c+U-st{w?+5$o`wf**K-E)$4PY0%=YNQ*QA=pdc5M-}A3TfCm~7=qpc_(m_&h|o z1|7(cwFe?o+CxME)MJmCL1@cr1=u3wDuy8Bs9qd7O#fjSI5gnSu~rM9x)~Z=L4g5| zjy0tnTxT39+6w4XNOTo52_V#j#R35?a3VTOBv=XoErz^)Q>D*#Ya;p66Cgu9y9zpS z&|5ln1u>G{Dkr$lCgCW4t19OkJJ&pd<=EXB?G{*eyAwmLRlo)2?Dl?}e|db=(KssR zp3>!r^)R({+UG!T=2*gdws7=7_!=fs3Zd7EGQ$T*CUvaiWfFwk^!Q;MdJKEO9lZ@h z+l8(T+QoKfm(e4j7ONYiv0I91E+n~~p_OBxYKE~pjK>4d25m%qgA$8+m)qMJj}5qX zWuU9-`L@}3cGX=||JwKden0!W>aJ%${pa4*yPo~{+4bz+PdB#PWwyILW?!9X?_^6= z{yl`_$tK9*Xsgj`oY(aT-RUL^<%xR*XxSw>$QUVBiV|DczwNIL0|b&kXYC2m5I<& zP>NiN_yST41tXLKXP)7Rs8}=}<;py$4=_bxNC>{$Dc92FFrZBI77)3bved@pG{#q3kqkPzfC9!PaVbGq02hbPG^8J`P zgOWR<+ilbU2g^m#^@!O(=B1?v`643oDcZPlJ?>Z1!a_G-UzE#YUf(;3oH2`FV0C+C zyze%5_fxx@b-(wo@9*Nns{47HyYKhC?`Pk5?smDJa=EGvRC_;F<(X!56xzo?GfT;- zexWfd%{lR)tf7An=w)7`irI3_VfkDj_{@GgcRnYkL)@OT=Vs zUz_S?I~;5UuZ!zP{`mU3*4N6fTwiNlvH!yS5xgANk9@rr)z1p$6%n!WN_7Uw*MwMu ze+O%RZPM2~;LleyB^4RyFDR^B^UjSqr5X-KBpO_00(-?D-qm&rqLxEF*yV)?!66XJXq2(OmxAvZzA;Zz4)a`IzN!U-{uy52Z!_WOZZ9n(>srm)q zPgV7^tHL+-+ZD!ss(LQb4Uj#l9n26=o>b(7*by+KId}S_Zo^oSVotCXKIWnk{Br)Z z{H;nCWoTy5SZ2>*IH#sX8)8FjAQ551E+2foTd6n@AR>?&IRsk6_ZYsXGz3M*C|jCT z!Hu!0azaJqa7(zy+5CyRMMI(E z6&bt(6^QA6=o}U?UgUYBkQCxzfL(0_EONZ63&g2GqwLk}c}nda@Qs;763<@12Ky<% zu7b;b0GSSVS4F_7nd2H)BiFh|uPjHCf!OX0b=NTDfJdzA1iI=0&@Q8&``w-#Dt_&H z?{`1A)mtM=PZRCVvR-9@s{-8JFOK+`hziR#kmE}kJf z5EH*2qSE|?2YyiSd_@MB+tmaS|MYL!;9LwjO9G8bclN|*wlhcP`eZ%fvs3jj$)|@M z0L~*Ary=sU2*YUc08j7z3W*OhIeS?_?M!hs|flsW+jwGxWf>T~>$jsUL$k!x5- zX9qB=g5F11hTq^spA(oSZ8p#B0v)xb0ul1aA;&5%Xg-II*hO+Rw09#DCOU2|n|!RYa6>D1Guc72ewqkZLS(uRrXEn_^`Y1&0hP2vcym;n;BcIx1yVOXiEH3tL zPS_eN8bfP`q6zp4k#O?bC2}KeXFK3;`p>Ej-@OZ{_Xg^Jp!WT2~b0J z>b*CCr>cYk*;{0@NTBDiK<}v`axVVk$FiLb4!4Xa(J*x3LLOJPjid@shNT>ka|RsR zQ<5Va&QO^#?2#5IZ}+@iIzk-Uvw|@WkU`*~!&kEzMGAbpk12gZ0dPpoQ!}7NM`Oeb z^R#P%{b5rxK7T?oCUjbCfGLtK)znZt5F|f>9GH+35~w)HO*_nnJfOM(LF0B~S`4GO zJZ>j;Gx`(vY1UZe$0{?|9fG@v^CQ&&n&9qe_le6H*1TcWA$4El4oMvq+X{3~F3_4e z!vS>;a~)DgdIoaJo;T*HP)w$M_Wt2ej&LN@^9DVfYJo;?_B@{O-IJyDEmT!0viotn zDjJ1d8FU*gD1i*_Aqy%B4O7XWf-KQ0)y%USk(4Giw(SZF4y&qmba#L6de{CYpL%xh zck$`>``&-u_r2eRdg^}ey}h^hUAx-5s@Wwx7F_O47CBF*IS0z^nf&*_zh*hF^X#)V zCzszOD4j(8@Lr)&!p1-SZ%ah5$z&AJ$UfNr4?69`lRBL*#b=lM3H-$T68E_!=(8=D zPPlpa>)j^;v^U&v(ME5H?2>P|>6&HUR1@IYCXAY!iq%XZw$cphveHT`pg=!9!|Aqr()sKnE2`L!+36)!VE(zVrZ0CPbw}K=BTLB5&2AM zQl5QZzku6FCZUZcyZ}(ysP>uxNSB~&w@@aDeaL##)<_0pH^?^EfE)!j?}>jk;7*c` z7;VVt(o>JEqB1E}!%5YCIJ;NxT}jlv_ipZI@5lGkjjrdrp8u$Rj9=b+zg@mv4|*H5 z%hiy3k8^KqJV(6F;o8ij)WYn}6+yY@kQRvBcw#PyJKPiH9DLa09# zvtdebN&qsUJwfYHaL-?tqZgBfNhKW&sL;jM=E%x$sE0U{g5ja0gD!qBE*l)jHVI9~ zV|Lvn4VrlCn3fNiH4e!dWSlF6OHOOU4ZMK*I6EFG;L#Ra z%-VCBt?8LNcogGYjf`+)RS&|H!fX*{DtHSo^~^)2rZ;|4wjb}3$9vR(5j7*=+^v;I8s6ch|k&j$H=!e!BPV{qFties}lY&#$Wc ze)hBPdhS6IJ-h94H-KHuuG%J1n9u#_5Jzu;YPcIPdwh^H7&4^g*1%_de0kH&nx{Y5 zJqP}O{FdPXM z_x=F+izsj>EzUN0a6p4zgCptrhpUyXMFp5OGD*B!IncM3u1Ev~;MmJf4 zDQC1$LCjex0^z2n3qF;vFgWE%18`2l5XJWDiCZCSx49I{2z>p(>*8yz%-5A)FTGx0 zT6$f&Uh4;)i}m$d$S--lAXhAA`KRlVBlHIv$rp|a;kI6RP%PP=`Ar<}IMLLKZ#&%F zyk_3LYv^VSKok37(j|QXb`wfQ3zqJ#83PxQO@=QL%4S`d?z!G;e&^4na zID%w_PGz})l*_UMxE1XklDpI1M!>%J4WO%VH}JUdw8}=6%fNP_P)24>=B;BXm13aL z)Uz{_pAgz?=DhE)dG~4*gZEP{c~?E%y{q2ctcL5|{pb7L-@Uuu_xE4xermt_x%=s= z-M#m{+gm;T$J>vMSqB(xRP|Z*ZTwFE%v^Mewrq@Q8mwV2!$WkM_zq8Dw`ZnicKp|W z&lB?>ANDpgP_qNd0|2db9K`?dIyZ{YrbWDdIKMx!{ijuvz%&h4^9QB z@D4U=lSMS!X(vC&iNWhD%>h3okdCX)*+0Sj8-xydSQOfrLJSQy)NsKY9OZ3Ep6n^2 zUM^yviv$(2GlFz|020*ZY6dJf+j;zAh)p75?jrcQyk5x2$gdx-*VlE0UTg7deZAHT zdaZoDGSZjwT2fd+jW^Kg1yc+JCfbep)_2dX7685J+-&%0p4w@Hc$b#2$q0qndKf~Pie6ACOCld zfOA5L!PNX#kqatXkVcv{GfGTOyj>cbMo`e!JjI!0&v=U*=SkO;e21nE8pJBt%b)?UXhNu#zd9 z8o-ne!d&zZ>hIY9m(2h9=lX|s#0q;YW~~?Y-#Pzt3e?>(J&>mCdHf5ibf{-4u!xGe z|G9+0{*z$aOJ{w~k;#B@2;edu?33Q_HTNl&%Xa!yYBbB_uc*M zdTQ^gcDsA;Dt7OVF~0-t?l!w^R2S`@F(SE7U|t{04yj4&&)vk6C!D>R&uE62Q$|j@ zfV0J&h3D@vzt0q9CgB4;ocx6M*|M7O#%?SfS=fwd=Vql(|JH){XL&HFteV0mtJIC| zvLmEAq~;!MQ5R!=@8ggm2e>Uw!VUh+`+kr6&l4Zu1=pcZ&m1cP-rSZv@i4Fu3~5&_ zOqJAL-c)*C`eLyqfHO@kL^TnC=`BD4HZM?KQ%SK_#Ov}kZ-%;n%jnGOn8)KP^JQ$~ z>*6dF)i<^>1Kq3_bPEwQR~Q55lFiOP=W{l2O zhMSq)E~*Fm9^u_TyLYGsJYo6)1XnfKk9CnlPm%ew;ab!Sd>ihPqq*N@ueF~?@Kz9w zx*tJRiT;neCl&46zm~Y;m+ZKAx!H|c2BMC- zWOvEN1yt{tDgvU1C{882kxv0?A}2{5vTFpPZblTeI1I7JSgLLowSkIEq8DljcbXNlmLz z0<>`ve?k6d?Ee}3KNbEVbt#_KpZ`(+!Tx`F{ZC*0NX)VcR zY$sViR>Es;o&P)i>&Bmq|7Z7Kfo*s*KvYq|igkS2U8%p0B!|1WqM8PiBLl#LNbb$g zS{XuBi%fO3j0Nn){UAyuFonA*IJm#1a%30?RzvYZZ7E&P`LT2qzM~}Z0VAv%js~Zv z_RM=78x3_wkjdQJT?ScvP)WU2yG`tOCaW?OQklvOQ2WV9l5gS+ekD|YNTN7~$j>!m?tO<^XF`gNlSQ2%f@bfeGCB8Yj`(`!p8+MU4xR1U zh?)JH`fwH=XYo4&t9@gbUIWmWtuY^TQ=&Fo≥gel&W3iX^f;%QqrW(B@Qr=-G?t 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

D-j9_)3RRNR(_|yvBeFQ{FPH>^fa?& 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�CD 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�h2BueX94%Y2>*Mu_(`2&}h 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%0A-yN~@`HT%LFT7*b^pJ&bRg)h@= zbI13^x!7T>X=-n|>P&OJr8oc=v-jCtJ;Nt=abktuA?A*8YRJ9Iti=S3D#mAo80^Uw zjG|)*I}z}k2zo*st{_4j6aTGk5fj5WJS6(4HroSDQ~*OPfzF}WVhB?)NFv!HCi-u; zC$yp^(!|%_42RJXpN%59CAU;Aj`QrskZybk6};WeB%JXLA(L~T^U zin@^}&e*KN`${M;gun~qQ=x)U)&|v1T-T?Ss&$!otV>IAZlgW7&H=3N;2Oommnsk( z3Up17;EJAy_75~)z05|jl&YO$W+P#H$x47tNfltF~NJ^Jp|hv$07kK%fvk<_x0 zB-4qQtK=vll=YzMcvn`T8iDnylnt$v*Fpy=gilOaHK(OYlsoN8<&8rIhQqGx2h|dF zsHILtqH38G%DYsk9ugTuA*bcTpL*=p>u!S6urrEQV*yLTuBaSraD0UqTnZO&JsGg} z{&)V#8-Mw`Cbh)%;XnQ0$6ocu7kx{Fc;e3c9{Kpc`h#DBJqXS}|GQ!JJ@5Rt-}w5c zZoKIQ-}fUwao1gseDR*Ibi;{fJ@=Bg{?sqPh49ETk3HUddgZ1ouPNqV{M3mLE+^eu zv$4TUMLI*6W8~>qG`3MW&YZySp$M3aT8(G7R}#9F_h;;ttIa#M`%f0~(E&Rw*_V(r z-1r>sj(*@G?n@0=iUq@6I6a95K*FhtGb`S2^w}681JenqMFiPOR9+OVqMfiyVc}xX zuo!px-LbDwMfR~5q%{BlAOJ~3K~&`#NaK!i5gdj-%z=dEF==2i!-%{_5Jse$(oxoA z4Wy+?7=Uuvz{rsbtVSt}B>|@q78?)+Cg4CRj6qAhBP1w+4-g1)(u0|3f*}(aN9rfk zLhuNWN+|_RiPb2BmH=L$6gCCVAVE|hkO42L0x~5FI6(-c8P1b>tjB5M4%~<&Qy%jG z7GXJVp5v+gvyi|xI31apG-`iU{B#Yr_<(jr8+I4Y#;tgYOy;CMbjl4Sy-1Q^eU=1k zg;z~%m0`?akTo)6gvd%~s^P$Eky>eL=R{{HI#la*w|)84MT^@IeJVlLfXkuc#(8eM zhP%JW@o#drOUtpampcw{(e_9h&e6V^SKJKCV0h^|@4tt4{VKDM^VCuPJfWDyIhMK< znpfV+;Z@dp7=uf=WjEq1^m%NFdmp6H=GE77<^eu`FQ*=3ka795x$OY=A7tY$c1Hl@ zV_I#s#jdn<pg`xXs1TQMIKK zaTrHw(`bA5TUg!l+f7$Q4Y7!XSr!8tLZmE1OCmxYWU#^qFH8u=s3u5bY8&*hSqG~ewG$z+&L>_)USU%oMYLz3c7X$7vYBYV-*YVQs+jvEWyF1c^>jnuf%f_KY%(VtO`jXUydk zunV@qV%%`XTa%1h`sXDuTv% z+yK$U1%gC5YLC+dh0P|~TnK1~&C!x-$S6aM`^Xhmqa0r299~WW8jK(-2oht^8Y{5y z|FQSxQI?)}edp)d-uJGxc6E2PdRH%M$=WQ*M)D%t@`5n7AcjoLVh{#GNCt=#LV!tf z24)xz49sMhzySkhi;WZ8U>j@1)c`G^|1eW4qOOVqpVzv5A;1>(rxpScWk{YV5o2 z)r9hOmEcidgwR4uErM!5z=C&`4$_t~MAs6@(4sXz{g z|L7n$@8*MFpcruGNxtx9u9;=w3V!A<_}xEY+Z1~jIr3_zr})w-{05_C9(jb1e~tqF z^ES}TZcwfAl;`e;IkKPVDd4`k5j}o^*Cy~(TV1>@@cP&e?`s5utCiEF_q^mqe(qS0 zz>atnaOW4oue{K39K$#?sK%KFRU< zC9OYd^{If1wQF?j=o-5jKXTvv?%Abl=BI+ry=|tI^Tf2&))k^jwGdhtqbpl@+sJyu z1mnCUhAL#v8l!_PM^hp?HA>2uUD{|bt^Yok`sttk>Hi83^H-yv^C{606;*{dNRJxZ zO+b!was(2Dz(^{&=_Lq~Fpd}W1c~zq&;^}vNA}1Sz2j%B-4IhswT+WIg4sdWHtbs7 zANp)9j4C_uF@$J>Kzfm=5Tg>LL7GGi@^U3QUnXrCqE6ajZaX#`h(rsO=yus?D${|` zbqJkC8Du_C)hH{Mvyk`8JjVR&^{=|Q76+2`^E5p;%p94cyuij9dk?{7Usb+ZIJEQr zKlt+lZ#+PHS@qDLzyE!I`0k(oX`#k)Du3c5pZWM7fAEL@-Md=dSC79zfBS#^)u;OR z-u|86^t#u+d%So4`cM7L^0^1T?R&mao;M%*4Jb4&g))x{qVjAx9yvQ>!1Ty z@7ep#S6+3X>+Z=`M)?MVRa}qgk&gBZRgU(wXEB_C)`;OPWBv=TLy%y-)h^U3ZhW;F+3 zq3&KaYv}3tG(D(!SG{`w>D9C87XUa50?xpiC%PJ2URr#_XT*d`kU9pMm~RHrC=Z|v zQeidTp$ySuBZ)$cFAoUy6&AKn(UD3a3}Wo!GNc~aK!6g61Z8k`GXz?Vv!WHPfK!Nw z5BPwKXaJKyY`;kmIEB*n1ii#7gg}g0*B0%G3L%LRBF3>$a$xaga0~2$T{V*|smBRV zLk1C6>-tGrhhb;+oIzH1hR%Si$J^Sqv1puq)U5~oXQ4n3o_loTh4ZULsYM80MTbW^ z=@V%sIMb@66sjSD%~PdAr7Cjor7~TSHlq)sW6C`C^U=jY)mxleKe+2mULOC;yAED> zm}}-?9|&gW*tZ)wOVVIzohP5*!V@$MGZrqZ%PeOoo@V_NyDsBZui@6$@wV4+`C;~4 z!LgTdcn@baUpmfQn|HjL8{W#E8`*m!ho)%nq`iZ)LmqyP5Yly>GUA61-o36pMV&YRcuB(mm?$rzH zErBM4#CvI?D3u2nMCmFUd>F+v#E_Ilv@vtqRKW=m96mIBMGP*MHpJ?tTmQw^dy5$i z25FjJ+R-nq{~jw?ML;g6%2 z1avBbYN{!H@u_~WN(&`Y*_@gUuDNYT*jj2AR^#Tb^x;4J{k_MEZHou%7jMX(y?gbM>-Jv;Gr$)={;|_%|N4gOr*3`I zyWrrF*Z$z~^@l$HvEMvbWOw}IpNAb2FMJn%;QC{)xc=CGTwVV1U)=Ml6aA2tB!be? z?NVCu3&d>*g`Op$7uLRvzNXY`DNjz(^&DGcSg^Cj>M8v1vhY0|g*7-`ZvwI&lo~)E zs+`p&KkD}rS60y>6)vHnrW>-;N)FDeb{7dp>d2jWXunpIS7H5G#tj0v?)tM#6lwI3)T%XqQ~jc3EpEtMbtPCDnUvl-BI9VE19x6OLRCxA3`p_``d-;&HBd zg3FHJ1z1!YttmEWq?@#kmmOv26#)Je?|K(YPjJ_h6fGD+6L!GWOs(^y|BUbbUOx1D zeDbgP^7EWsM@6PVMwb0%;YZgmSU%wa-x4Cat^mcl6SqYf-9n|C`aSznbtP;0B4AJ_ zKgEewpsCxDq1JY?eo9Gw53)9drF9!|%SR<9!11;&XzKcHVmIe6H~m$~1PQ{|1@^N4s1z~~1xgUubRmoj z;Z8v$<|G_!T)chtu{>YOj6|ebBwdv>%ycJ|_};(|M=l$>tSCLHa#}@} z@B8Q%SDw6Q=bPWY{?PG{ed@#C@an}QFTb;P>^7c#edEiH-c&u?KRY}X6frOw zV)nCofi%HxAi#)RaAHs$3TD15yDgrUIM`!JO4=1|k4tFm`-*#7)Emx4I0L=Fsf&!t zY`s_cLZI4oB&ywPb>`>wHH)g-WS?L=Ou^+aSD%;DIuUJl!p4$;g)^I89M)hBR$zII z-6{zeVE`K?WdKJMLjj7X5WL%S0gDs! zY@NFARiW*Y z^&}_H(jSpjJh{yI)5PaFwL~(_bOS8qkYC8C`lAz@NRow zHN3FqycG>m$$657RNdgp*voykVn>A?6)|K@Azcy?iBu{gWu)>$-;-8bvJgWhrAj*{ zo!0%;BI=-maw2KWbhLJPFDsY(RcV8)M082hMi(jgT z2Nj&%tPKn=kSLl3&1!SkmHWlsm)Bg+6sFSo*)P8TlOlWO#_#`b5FdH}pEc9vcm3qQ zgqM%|3ip5L1ILel{JVejCtw@!`#=4QJ73ZI_B($Xw!!c>|K)p@&OZ0g|M!2hW8vHC z-kGWg@f=))3&a<&Jq8;z_8mO-x{aqE=-Xx0p&XOSo8%S2Q06c~PciT^&yS`}sX4QQ zr&pQr?1)H@$WUkDAdNZBf`#KXPJJDg;mj)j%xJh;mIasw-KyDaETpY{J2=u7J6jwO z?5ZQDf$cB{b9Kj}Qv)CM<};^+6^L*SdSKx+T!4)stc?SoE4(Er3PlaR76M;1t9!pHjV=m5ehk;s4Y+>Uf~4M;>2c&S6EVy_auTQ99ujJZ>~LO0dlC= zMDafd!#b#?U1vT{!Uiln4+$KEVcm`0Rb!Oa>#en@s~;%{A}cQx&!0Xy7!_`$1JyW* zT6Noo7@a^#=>kroT1uJH30I0_rWI8~Y2BEcjiOoghKbFlrZTHs5be$@_ar(tR5_eA zPdIbpzGrVb@erNInCqZ-)*a_H#1y*@u>Uetll_Njw>h`L;R77Lj+ul9ERTK}Te4SC zX?#Ue!Wo|HF&xq>IK9TXlQed*zeQCL=h?f+>}!-ugMY)G~d^1jTA#2IgN zrHlw|(JT=b7oD{-I?=E)MwxV(%2_vq4d2~SPA7VOlrUAMNt*Xcx1QN@pxaH!u(P9U zh`lo+N+)6f-;!~Nib@!(PCnaz>@S|Z`c1Ch;edJai!@Kc4Kyzj&aoQTo;d%+gAdQi z$M^2t-nsq`4jw`X`e)c!T7F{X=9`ST>c`-+kN*4r^X&0^|JASl3;3qZS1klnEs}F3yikIbgyy1s`@iYIVT7f>jghoWBgp&SI3kD%>YdT;yWzf<>#;HP{zt7F)XA<=_lfvItXk!r<6& zUBONm*5~MPuWyTiJqzdS#GzqLE9k>;%slNuma!2@EqJPk7r1~{WSXP`g(%S}NW4Nv zq@--ng`tN?n&JzJiX@>*2oV#>0t#|NC`l@cghW#WT%?&2bD|>Ccu$ldP=OFY5+zMf zk->@eBv#`+(jx_05-XHNMXE@mNi4ab9u=IeKS|@l#i(X@ zD_DSDt(HvPi&AwLY7aDF9QwV^CcnH{|10X~p>f<~6Dov_rd5_5pD#I``BFujA}eJ> z7zwFkcSe>2$|~sQP>RI3qUTbXkDA?PM5!qgi@wZbqhVFI-RK%4UGdEEY^m%omo^Jo zaF5I4x9@uH``jrITt5rPHu3%C8Bz-e*|`cG^pN&GE^4|J?JIc8e#)!({QaCMSkxpt z$qj2u9NI%15faLaJhRM>9pG^5%xjjHxbF-LlGk3t{9X><%EGVk(NFWpg|E!wnVO*x zCM;8v+|H!dSfUmmej*~8zg=&MS}kqC*|)4(@g-I*T^F~g6|$(GK&XXXS>IbSAq2&Q zW*W27#);l(t?GQOx3^di?K<9bjP>2Yf{QDrJ-rZcAreVBFgGJ=4 z^oMcSccVcNPOE_sjWMT%QSi#DQrL(VMrb#)T@mvB#Tmy%XX5f{+cHVow5?XmR^n(q zv!QGl8Vwqanev4ymr^xavQVYSg>_zdEZ+`XS0qilZxegli`p#e*=zgy>6d* z-yfem`PBFQ_}eho!E?`?J)ti?w{6GuARhSh-`iMv_Q!wim*5cV_b_|om<_Sb}IXJVunJ`$%o_YAGNAG>?4R5>UhVU8=Ubb`N=<)fdXJ)A+A;o*Npw*%j zILo#ehvFkA;t%%neZKL0M?TqRd4w5o?U=Ll9Q;Riy@y93hXmH)G^`AX)+#3x)1KCD zcQaXR>6Ye@W@n0o8IUjsje0_TtQEGw5FE^mgKpr3dctZv8F#v_*_?#o2IO#|Vl=`g zm;xX0o}dX4G)CfbGEGxbCIpy;(%?Z$rX^95RFs;;qZHauB#3~BYPLZvJ!CKYPr3=3D);TFfl(eL;niZAmET*Hr^JUR$rarpfP}p)bSjif@X8e4s z_xYzoGpZ>mM$_$P8oe8wUlYs2{^|Zk;iHtOMxggssXYFf{7sL4`gOOT<9ogjZh|)H zZa7bMFUc&-(Ycw<8fVuzxdEry)goQwwwt;4Ne%`$0C3rExPVAe9jwK_03)z$tdg~v z-OZJT1DXvB17>?1+Q)TQva-sO!$K)gakHDMCkj-emQ!0wtEyd7<8oUuA+zL*Jy^2V zRa@wGRoAQXI%n{uYiowl*LOlqH>G~8MqPNbwPvFBuy4sSV7{Ix%|Nj~=v8zHpvu=N3bG!yGNy8( z^2gKH&dKd<1_K74C*9!KjW7=x z%p7|CjYr=63c4*gvgx&2Xx#GM|8Amf;O=|>f4KkdKYq_I{Ls$*-wT}o%%hjzytCE% zS-_t={^*zP`|)4<4cHHn*;kIQdHln_clX_YaM|q#ZoKTuY1wM4f*Co}Zmzs>^^+fa z@=yQM7aBiw^s4U8gEMb9-G7>Sx)Ph?Gi(bvPb`V&aUG^QU!04VpDFKH9$XivpHCVc z!s%Qd-3Bu>2JirUfS0|K2gk!`j%1l)rD|qzyEL=bOied-cQGw4pU0;(;fC5LJWiV1 zs158z%?llawbo&y4i-6AJGj?i8P4UD6|8#JJwB(bHr;zFq4G#en&3;4209@GqQ@E9 zFw#g*+QgQ4jRX=>NiH!Nu7!7aPlyCVl@T;lNQKixPl#x_xuYc!8F2w&5Myd?pmJ22 z6l6pMv?0rArc@qhF$PO$VH|WAkrGqaTZlKRe@xi6F&%XCBzw55EhkxXox%PFi0MjJ5&By`9kpW`*O9=T2cpTojb& z**C|I9Xx#&33=SqG;+cnMeQ_?b<=e#L|IIfm$npG`4Xw*>sDYnG2B^Aj2!;WUzGK= zYNB?Mx1+?KRg~@e?xZF zWq!U{x&{Y#Y+oAi<>ehmwm;i6QASl&J~Wl~g|j2m*HPvnIyu5S?}PEi$2_`@k3%1< z7p91_mp0l<>yp6wdiDo3&OyWkDhUZ9)?pO^DG_lK?5^D=0dhPxuN219R7^MC?U!e= z)vRX*DyCIz5p-gU=tgmryFo8xne;Kr)T>H(r)6}a>{gXmO&>)JPKM~ymg&S|W8{KH zC8^O-S3&7w7+cQA5JX}d4U;qylvWi^l}agv*HuJ^RhydfzYp-0bYyp|@T`(fY#g{`E%q;E^lZ2d}z8UH3-G)i76I(#Ji?#on-& zKmX|Cv)ekmU;oB%lEv!||HOCv;nq~(&f9L>^02E$e&@gamHP6FV}RBppZMH;kN?)K zKl%o1UT=c;#p-JB>GtySYFq3)wD%jnD%A$v9Uq$!`}%`}{bYaFRZ-=Q=CcI{$AeJo@HuwR@r|$Lmz(r@^fuU4RUMbK zO*+?Pk7T|>GR@AE229u6|G3=MfFrdC$Y2F#VNjp7bxkeTaH_^YoEhRW=mpkFLP-^= z9B8bFptx%Q03ZNKL_t(Sdb|OJRmiy6>M?q=pi^qJzzR&DGAIxQra|S=4rTBoRD=#A zj|-%V08t~rJA8^0=!gx7NGR}%1V)C`;swE@6)jICX$eG=YJ^clM<-$vM2VI}OT#jP z(xD7akd*imKntWISY(9?c!|?siJGX%)|OgxEATRyhcopc{&@f2RXc0OP3jFOAcIgl zuEsr~Zk=Vc1M+%`aH|hn!VDM~K@)m}XIB z;hE=e|2O~lKX}Jyc-tG8yA|e`Z_wP$vM{`0_Gx8xP;jp6#d&ve9Nahlx{?sB2Ds z;`e_eg+bMJ@^Qzld~wT<2_IGYH=!I`5~Ua~IB<*>bevkYt7*4jY-))zY8b+vuPu3v0WLP$;0b5)0!D*}WlWh&D_^g_E!8|~j|z4^^={;&2}m$Ie*TM$^v z5jCaQ99$Ox=?NMNq(nGmitu<%Ff=5UM~^u}4qY)db8~n3@P#u=qi_}%3egZsTd6~# zM1ReXdcpQZF?2drGR8_Go%bpYm5bUZ4qr*~_x-1%SKsr>x8Kh8*TUssIQQtv6HlA@c4JOn{cYcj*bOhp z?W5hT!*Ch~(5XXd7Aa4YMrL;xWJ?eJMeEFyJKz4Jz|s?s-FMF~zvU-m$;B^5DV~Hy=9n)B~`wT{X8S-HT=*9dr>Ll7jVB*4LYXeDM%E zf%Cmq^Gvz(m>LYqu)UxminV-pcS(Ev^}Y@t=jBbV*v;L27KWt0u?xM}W>?DGJnbo_ zl1(J(RNalxumDr_dvCl8HDMY2*k1)p@H|);!2p)vxpCfM&(a%GMXV$gNQDVFjSo~Q zsYgY82kF35g3+WACrA`+O)=IcV*-fZtV5IntwCY~1fs_)q$G{>B}qySsmCgSwxl$u zfK^nHF=ahbM2CxXE2>CRp(WB0Q({I`n22xyWrPZ;krm3J0#Rcv9!!ap#DMi6sVcO@ zcwFRokE=NkhhZ5UWY7Qyr|Qgr3>IJmBAl-qNDG_MG-Gz?CIusd4h(7%X{aN~m!ShQ zo7@MT>un*ZQ7|esGCEsSKE_gLS&ozmr6LlfZlt;KrPb1uJ_wmRdtSTgGU{er%VKj@ z;VU<^?G`buOfzj(Rllk(l%bict};?;nI;EYSvGHr-lx{flfU!KdzOcMe}~z(L5J2N zmv_1EbL>CO!c9=YHg@?<;?t2M%m+@b@z)QNl-#@ruIBJGjfzI(e2=qdXjQ!YFzja6 zL(CmuC1XBtHe+cC=UDKYXfR&f=G!V|ZMEe7Mui|JG+CHv*lh_iQ%kF@T%E6KCT?AB zCg7#XgmG%}A#BZtkP~J+J)xYom>X#=ytWuxzSdr`{_D2-m1p4yNLUf9M#7l9YWaf) z%?lfs-FEe8&K4m#J2O+r3R@((uZ*{)@Lrl=$`G`yqE^1LdFWW1$edCJ?_-QEqZCpI z?=Nk%m)0eL^>tjq;w5;jM2v?AQc z)gtMp`Fh&8Y(|x~=;!oDwwIV#DC=cMSJH&@v9sL{O-Cq9Rm2Wbq|TMvi{1*hIAt1> zf9?QE*tX-wTR-)IpLyH6UqPp!b;Hapx7~Kj#gh*_{{CP4)ldG(&);&(@4WuaSDEYH z&f>9!#aDuXwc_EsKKbXr^W2knyRgw(=w5UAu~*-EYtiW5bI+->kKcRrl~=yvZ9gKB zfA;&o{J`CJz4f2I`{3KZ6Pm^O7e4X9U%TdQm+xuJZ*26|pI`dimmYZVk+b&1qyCQW zZLhm7tgg#l+ZOl8)dy9X%d|1Szjf`!jn9ufElr-1W%!GTBzNtpi+MjCVXLn8y_R^g zIo*sxm2o$K3E~uyqkG z*4sr=`+HAK%%B53DB-CJ*N0^olw>8thzFVCCB{>lRuZ&r6k2wo?7MO?#5Jify7VL= z$s{xcAu=vA4k3{Q36bdVl2J-g*7*Szkq|v-vH*cx(g+luh9-iGAPF8XNGwvJBd)|~ z5{(KNM+T`UDuN?uOr-QAj%o~cSE!PqqnlCzC5a(Agxq9hwFK6qA^}KfD%O{Qvo#BR zs+LX9L#WAB!&Mi;DX>R4136U$yRXr#2-Nf))tOCsGaRT?|Zp=i$dzVxfZN?0MaP11&`L_1U? z75ct4ovS2~Rn<%kvklj3KHiJ}>Jv-vIs5xN-;=QSEzqSg&rP>;`UxKSYp$)}8)^=% z1I_#q{4<<5$At}^JI#AW1^osGcZ0@1!N!QW4VGS@-{(*6;pFr5))*9D z(LGzXsxZMxS9Q~kze!_-y4>YnESSVZ&#tV05EBRn@sf!}TVEJ!#}yNJh%lk3#6%@5 z)IaOGfvD@}FD7DkTzwB=;<<%du@&$F><1fJgH?fwOo4)rp5UQ#?QcHPKGJ-4PJ)xs zIBlsiPm-v9a3RsXvYgJsNQbua7Ec+o+-ebB<&`c|8-noprH%H|x+Ji^j>~DJ2M7@n z2oM4?q9j%!3aAi@P$49YA&?3x8j8i4Z)+Xzo?q{;d!v(>xu!94SoPE>bAv2onX7Uk zG9`_2UK;Nz??sSgI@9Ph;AKiHZJG9H{X&H#QD`Be_bw~FmsU%?sN1t?PV`x@Mdm8+ zWgs<4M^w%wCW=HCgN8O`>?Fn!OcL`@zEzE6Fsd*!b0dFL;}KH&aOeX_Oh$Upt=@0j6=aoFt>7cMO9>Am9R zm%$lv=G^(GPCxvaFZI_75x96NcS9#xfPPm9str{gr+H{^=haKa{TE~5N4Nn)i!#hW z=KI;YstPS*yHrEPQbX9HeXwEn4$A4UF(_wsK#1Duu}tTA?zo&s+FUjz<_vUcE}+z= zR9b-rNa~$C9b-DcLRr_VY<&PUns&wl2?tf%7mAm(^)Y+iJE&r{014oBk?x1F$v$ z(9CM0fZ9@H!8DXoI@fs=(l~51NUMdmQAz1#5H99gdgc#Xd6Ug5-WzdPHN_&kcmIP>_%1YsuiF7+b^x%@gu+R*6+ES zZ~ZYi2AaKBGpkq|(l|{^!UFken$wU_RD8p2u*QG>0>u-oSsp&iJ8r;Fu>mS&SaEv5 z!5wT!OoQG@9)18hWOcNuMYpIXBQ|AtocL-KFENsTwfo_3|BaiFM{y#T@bB=wIq|cZ zz{=|hF(>OcYgt#(R9!!7Kvu_LtGXk(HO#nGS9=}?a1=DGM$UqVHf)Edb51_O6<tgqAR4V)J|7}4bcl?Vemx&(?v+*^9DJ^ za9E9om8(pMa>k0LCW9B7xOp`9iVu z!l%CY#XElZpOg=N^b?rruJ31+{+G{fh@)?L%RJj)8qS}4@<}^fJ9KbvhQ<2m z-MjxIzkm3()5zVB_QFWFQf*T2M=hQ0+c!652Q;>WUSIY*c5TyUKRfpBO*sGM+t$pV zZjH|A?Jz=*&?7%+?yxaRx$0<+8XenT8g#aau%j|BOD)EHW!h;O7Ws#fYMLhuomm!V z32GCMJzJlb^E$|_tm`NlWOaXV6_&x&$;~goSr{y{zD738>6Mg$qA=FXN}CGZipHeH zv~A9{Va*l|Gtjze)uF&pXgkAF$(&Xrjmt>`Wq=?kDvTls$|knJ24ac`=t$OpK%z(j zr6(9%goqRfO~Pg>a{>*Cix3co*#zw<6O=`1ti%-Plv1G~cZh(D1V`?W220zCf^|3uJ^IyJ)k$U>`kb7WDvv63g&w+gS{5Lq9rk5Z$`-=M*zHbv z6;*{>Uv0}G2JND26>9!SXXP~Ix@zn!M7v0Q9J*1gDw9j6i>Op7wHj>5zHGOK!5726 z)y^RqDgU7*clR%R@<;AFvHb_`;J|)(4c)6Dhx0u92#2qrxdYqBI9ihI0xiexZEP&F zeJ@wH$$EV5E_NK_S=cC8Jk=e!u@6oT~rD|2tXb^0giw6MxMFz9CH9-c6txWUb7^#8BioyE26}cmj4o6LL5Q zmq81T!qafC=j`3HZ?Ehv2QxFH#W1HbUQ3x|Ng;BZ6newX$hfSXa9XC;p2P#}r4?S@3l~+I;8r!C;EB0@H^IH!+aPLEhcP-p- z7!)5>ZX__CKPr9epG5v8;u3-Y+N|MnA)ebZyd-~KH(@$DH? zFM}&#JBQ(OU*Pfuc3lsOZHG1;-8b(htRkoJcabmAkc1u&4mf?98!xA?IQtMM&$D)d zGGS2Q#HJz{Hw5$A|K#iHj<2WfsQu>QYxkViH6;F)6rGpaxx&{h!nSe=Q;1?Sa-14B zIxc!OY6;~h;Am;xlMfTt`$6r~T7y1Z4)bunt|m9(2s{NZcs~B+?Is_*V%JbqibmqA zo@@G$=e{N5P(tC2);3hmrZLN=@Lo6(U8-GqX`}snuSr$(QoVI)T@qLTi9#3{=Lu>` zg%)Us2q=M3xDsQ~5*r8+5oucH_b+a9H}o=Hj7E8R(bE-Sz3g46mWN_E@>O2ROuNEH zfl`T9!N#lz);sW8Xepf-#9TzIY>7dfRY9m2r7wt0(Ma&7<-ADSsw{OFtvYWKV~p~? zC<+zjI5Cc*tPEtvDb*EnqY7oIX$krv?FKs%PM7PE{m;T-07lI1h8bAxw&nA-cOBPY z!rQ)s-JdN#@CToI=k~?jhhL6U2mn* zqbi$Q6e5Xa>$J9y5t^_JYg}<7C)%6ch4q>RFsu*qy7tX=fJ^}a0=o& zXCtT2L%(99&!D6cuxZolL`PwhhK?E&nx@jSo5noIP|?WkVl*3q%Em;A4U5IaptI5` zArX#&pt8sm4Tai_WKJ@?L28>`E%)u#`f{l7ka|(7tf0OB2MNlMJ9q^lD6Wc1d^s``?zMLdPS%a^IBt5!fKVrw^* zok%T=6P7O~s#eqw9UnZf7RI%x-m8FwD1fznuw9$V3%362cdFR*io2?5k)v^9tY!L?HI0LQ( zi?%3*isTwo5hPL)0tB2QEs2s&%Hi%?^Ap=P`aatzq$NsEDcBgrVc%7K7yF5|Aqa#_ zf)%;U1I|0)gjFJX8={Ij3SVJVRV8LHQYyy(hrRa>k|jH@JAdE3H#4iM{d@hqoq6L2 z<1Nh0VBkm$AQA*Y13~1HBB^Ca+|ZKTpeQkRL%Z6=#>R#yX$6X;C@3Tv5p)0z+yI0b z5Ezd!7;oO2H_vzXYg;Zeule?m>U>j!0Z1@JAVT81(a}+zm6cglU6uFT^PTS;EwaM4 z+A-O-YquvRsEyIpjoNgKt7BQG6woYcZi1RDmUWi3O4Pae$xw@5udJ&z#gwOvYI>U< zyzTX^dSsR@RyzA&4tP@E%DxAqqAU zJx0hCjRe64d`_oAm6&*L$nMQV;xkQVz{p>84z|DsbYKd)FaqrtiMC40kqibX%!Rxv#@ z5N(}4TDTe7e$TbD`Q9n*ntXaHKeLhNQ`0+hD?)dA^^~uRGHkR`9Aq)5lW44(!by-jg#~ja?n zSR?iuG-}eaNdY*y!UHcKoILdGo}|ucTuW&JVo04B2dS>{br6ggQmDKGZ<7sj-lpMi zvPw7(lFr@xTG;fh>~ntn_fjH^NJT7~Tw9Ptt>m6)(F7nCqZo)nF6`PrKhF)%^r!m$ zwT-eIWM!y&NsJOkEz0l%{4`9#5o~alEmCcUue{%s>UT; zCy^U(Fn*9I2hK!e#8Q_&1WXJh8*K^!*UIxqYieSqBQE)F*G*}ELtQ$!1cG>{qcYI$q#<$t{?gl4!wmvuYcS7TT?sZAAjW2|Kb;ZJo{K!|Iy8ph9-#b-o?Av!Bo64}0*ft!8+4YxC+;jJHN1r_N`a7=KG7DSU z@DkoJG3WT++b{dl+7rLET9Q_=ZHfwwREe0mx@yO|WlcF;cdDsx3+Znplc{XiY|||O zScN5cnH?9g1IngJ?F7ujpwW65I06&U1rIMaEpLmXoI&M8$(d#1$%@{9VNH@zI z71Kqk4Zam?iw^`7oXIO&R8}F_!kr8VmDpk-Mjz>C)WO!HLQoQ%!$&GXK?9`{6V7b* zuG$0vXK^_iXbaYol)@rPOlTrHM3@xHh+6uIyoHt+hliA*B^Jsdl0%>t)KWyKNtO`t z%E%JP)&xgO(EtG-ah7%q!+7o{7&2!y#J2O=PCUotHp;?g=}Mx*0kk>-Wa+&lVW+olV}G$rK$ya@f1^V2i)?Wt!v&XqfhxY4VmsD}hI zjK*c5)$jCU6fF2uPWY&`p4VXg*OmysPC@lr)HS|koUb}R{#F32QP@Zx38UV1Avs*Y z1VTUz*5VDW!huf|%J$YfPQS3TTXtiA)ptuRyPWl5IShwGn+864S9@;-Fea0feDtEI zA=X*$#AHs4s>DKbEoxI|5`q;|dKV;vTB=+mGOYbT3Kx7zI;_=PyW>Zb?D09y6n)Q?K`*azw?dow#{hPSH?6KPJhq$d|+nRuD|&6-}}S={N=0O zxPEB&?4ez`B)`} z31PVDzGl*<)V-57l~qO~id&D6D zXUG&U7^PBBk|N-gtVOJ;1Z#+bD$Zd7DQyxK%Nd+~nE8{9Rn-PefNvbFd_z9W!*ZkH zn}StXf$1jIWdPHgmJMdKPfix7B2&^7gf_@bd#=J;EnICx2chalqdrxMeua-otT}ka zVEU4*z1$O<$~Awwte1_@H>v!_)K)-TgAxx zB3f)B*G&_#`qh31TcF#Z6Bz?5X3VNeUk%sJfi@XSeXhR+*2*+Wc5TFuG%~E%;J*Nq zP&R`1b0ImSPs+K4k2VCDHogy|&yH)VqD514Y>jFE&#`o86JI!T+4R2E8+U9ZUusd= zZaZi+R$b@~1*3Sa%OWN;Ixk7nc?Rom0A`J2tgqvuz9IHH&sY5|0$8aexy?Ygkxhff zhHiJHgiB-|Z?Fji+qbu;)6HkjR>rSZ^I*_MxfLbO+jnkr8YM{FA>b*@=$&9qlfw_j{CxwE}9YGRknno6QcMdrQD za_{S^JvkG~uH86YC*La*MvciG>a(G#T(HD}Oix_$b-JjL{G7>)uiLi$f}@K^?)~^j zr%r$R_3u2yt$!D;u?MgG*?;qW|NdY8KcD&1M;`sfU;WV^fBNcoy`5|u%#0erX6CPX z-_LyLeQ;{woc^fs`pcyw zTZ0_$sRkE>V%Jb+s^M<8d#+HERtGY@sCKHXrm|`|R@>bSz8Yj9s!k=wqzZR#vyQ0E+~?VwAdu0mB0C$ptA$6~SXX(Grx*5Gzc91Y(P-B9FL4aMTI`DUgy( zNs4p0M9dl$vfI>3+rX1Y)9dEXHfcEpY&0EiohJ8YR3fCwaJ&EpFatf<(IjejU1uD^cXiG@J?JfXjoM4q^3#jmrS8TVpXaM7b)(M;sjSVx*J=bGRE(2Qkx>VO zkR{_Jg-l&UTCJ3l#L|*Qp(5EQd;S-G`{@t7G`#7~OW*=ngL&?|hu6Q3_IA1_2^;L) z&an+L&(sXPMAu-3ET5!=tFL6C$Ad3n+nYUpE=TH3v~iAt8bJz)o5(3^u9Ri7gKyj| zVuO2PgM_4En~H{@3a_EB?3hbJs}aKMYrNgZEwx+&eN5B&IC>_%<{7pdjOC!$R0Ctf za*cpstFb@Zd;(_E0`44YG!2iH#*DRwFfo=qy)xkO!Ja+kiLEZLW0B0z`8t+(A88R| zip~;KYE`i|HmcF{6YbwD#`-$G>Kl?(e+j2`9((=w2CzbgQIg=mM@UE_2oAJH1r=(C zg%Fqy?3lbGoY-Hxd~h~Yr5~nV=&r?nZ&=s4A4HX$D!F7dOG)Z})L2{4s3|9lZ4os3 z6o*Ew)jXlD2y|Lukk?^I$Wzsosuwh`mQvml=Y}ClOy0F54ozu{kV>0{rNPit$!;aB zkTR2X2sJ(uBxMPoTw(Sc+_xouoyBr99(?ke`EYEZxc`~u_2*8#JXrbRTi*inupc`A z;OGDMYj3*hcR%zm|JAR4`Oru2`TlqQ&gJj_7jR)i?hj!1?j3jh!+&_2>{ z94?-y+W*JT|G)e8e)zrby=(rK?{6l~{XllZwb#v-J7=bzeQw*aV++IH;>y{2V$xr{ zZQ`=qF1zNsH*)zqq2Q&@vwoJ#ZlrpKVh*kYd;Rr)?<=c+daVCqZWM-=L8#i!42z5^U|?7iGMr)^ zL&aqzgMzojA^k*hv}Ln>H6)B7g6m=@A<`CXAZ57Go-KS2n{9F#A(9CZ@_>3azupV#p)Tm%!a4ymc!tiu#6fx=|NU2JV~@l1`sxQXKuv^u6$467N2Y#(G z;wwYVkpO={fWODqvV+#1VOS3&8A-kr6`yccQdBVFrAk&!P?cm2F*!7e)DhJxIwR^O zS(S{ej>(8yP=4(TFFo<>(ZBzL7cp}Yd7H`YeEJA?ybYa&$i5j)EAcQ6@|>+xoSo*> z{p{b*i^q8GIJF@k;$%~JmD;-oAbJi%x@m8fYcl- zdv(3?oX6=ooA_aa7e@%}`0@P`L2Xi_$=aAI)udWyl?{R1m{^_%SpSvv z!LR%CFOggQwYHoe-+^I&>6d=#J8+sm?|o1NpNL9SY>t9WNDbL5S*s|m0ke}_x&5y4 zFjW2-LKZcM^Rr$Qcaqi*JM((D3Ve%A~{)7qbWI!8c0Z%iy@Fr zI0;!@*2$>!V?C&`Xzh|OQ?GADXjxlFlF_8ZWJ1(LsV>!*u*t2E&#KKpfd^`*NrL)z-%G!ST=Cs+ zz4640XC8m~i)C_Dd?$)<{j4|!;Q2nL%0ezkA<_1$@nUius&M(g2Hj=!b+dTigFRo^f~s^Cze@L$QK3nm-NVrHyvI19@#*K~2l#+G0fEQ=A}W3JN_lOlQ65AN`Z6YIy$#>E8JgS(^f&m6$5=bddSu{1XlJCj*|%)QY}v=# z2}AP}#}Wq5<>7<|9;LBJL2Z7{9dNvEc+OGXjIGPe7)8Wu0(@MvCB53P)Q%lia$FZh_xM|Sfo9#LVr^YF?b4K<$7K50YowtD06LWGpcR`aR4L%oN>(JEd zwWdy9V__u0~+Q%twpEsEm5_4)XbM;-wV9Xj+KJLBQQhk+YzxZyk)`fnIu z-M#w3C=^N(5Msm#MMZR|ViH(%)XgqjuxH!u_TO1}Vb{vp)uq9pURv;rD}JpTs)59T zR4Gb7kSA1QCK`h#jjGNXndC`~&2r;ytwFRTWmnSGc+)C_OTgE*X?YZT$i)Xgq+z|KU<0RPW z>#sfe$w#-oW82E9a%E7bUF}EjJGJ=WJ(s`zicK$r_THP{{(W1v?LK+zp=X{QKJe(# z&wu)` z&G)~qwdaPZi?3+yzl2@aV1(XN!{7htAAa`k6Gyi(!n`V-UyjAjj!S|ujnd;y%#I8f;A>6i z(i)sU|3NoE;hQFNY18TgH4E_1w@=;MlFYyEFpj+OGu6;P&7ws3`8Y4VgQJOOISr6 zGuNvhmjfDtP`Q{es1;+JiimNU2hA7H*?sq?e8%n{z(w z^;p&+=X|Y{psz@>QbS*kB7i)2ZK&PmQKPcvIsbQgHU48vbEZwYRRI~y2ovqenb~7g z69X+(gQ~Z6^1-U{s-i`t8YHN&|O*{R;#Cni>t%Mjnwbgen6;#YE81Kj7i1TbrfecXv(0l zNn@^nM5{)E-OM*kK9W)OV$9-3(4vx1`6y~@>wOxiNj{q_Gn*B&nVX;KWFb-KbGDeZ z<&w`+@Lt+Q%BO8K#;+t}T6T-sd;Q+;`g^ax;=t9Wb+9qfZb|~>=WvrF9dYsE@oiVx z{z86Y>D1I@)m^Ecc>c^3FCMhI{_Iy;2Y12j!9!QR<2^r-PgKu7_tbg}Czi7Z?tj+x zj~u$8fGyC14JbC*|6>33x4mJ?wLkq^pFDQ_tFsd_=W^DcVQQ2F+=I}V3?AY5eG8xc z=of$U*Z%0?uPmkNmUq2n_ccEZyT-)w3O19itLq;*9yd}Xc!(Hxx}4aJ#vrYM90-%v zm?$ArHKm*EXfKnOTm5b^Q4fk;b)qEzeb|oKkHRr9umM9j4iQd|e)l+X2D)%&NWVuf zVGJ@-g$o*Kwb6oHF)&C>jBKI|;)sf7vvP z;All!4zZBPlmwX)!lwUi5Q0Y|D+%H&B^L}PV*nv13zP;c3>|p{rM6Jx1aAp~L<~ej z9GK^As9N2_6WFQXpP@v!y5t6~xs6j2=9+&Ska9d+?G5TPP6gS&o zTjR{V2PS~#GcyH)#{Jd78mz-^s5a5q`bl0E=D~Sf2A1`FWnuB5TRkTVf`s z5=l&}u49ZL#uS2aBRHb5biu?}LcVoJ+<)~2&wXiaZDl&P_9uZr=I7@CwcfGq7nitHG_%(lCTNlE1Z*kHASSa0ZFgDp<|qu z8OzfA213+xf{8J9s?~_4%ou&8uP!X=H=UyV?q=ssz!rLxscQrE+wN7i8hxMNlrzjZ8e!d%44cxjQtQw zNpZcrZhzCQyXHI7dFP=A?w{EaXZFmWKD>Tn z>Ey=tsn7l9C!K$I?^QR!9Jouaxb3=|_AEX6oEW_D4VS2On8@`srQswv+AV#IfNMpICnQ?q@&sYma{H zcmLq`{`4ykKeGF(eRsU)?_T|eH{AP)FQ5L}XAVq0>vjn2hvkOYINlj7A6)J53QY)wEeM`IoX=bH`+k_REP_(Y62M=zo^M1}z$kPHGbkVU2y zACW{7q(UM_h)Q7zHCkg7QBq0rSRvJ@1&4{aKm;EU5Q{I!BFT~^Oa_n~8aBHNT}0fb z3zzl8#HHnNxAl#%1$s>zK?ZYBHK8XX)?(D&Hrb%<{V;@+FoYViroq!g$z;h|AF;Fp z#derw_0+L`dg0jO;N_DY85Ab;>N-T}_r0&A7V*JK1fS4^&yrXTE=#4|K4G%XOHVBI ztAPvZB&m%tI@cE8D@}+h#u*7XBf+X5A`(-|HKIb&vPdo|HW8gDViTe+CQ^)qu_m)<1V=ukv{+$i=mcEO&Z#Ne-Pa%1Rd0PzF4Sv- z)LpUVT2)mbNR>#rg2Y6wK_a-AV^ocXWDU5~jS&;oCUHYaU^RM=i3E*SO^ldeTnIq| z7@uRw>XJ$bPj@%IHguxJL@s%diJA6Pu#v)PsCz|L_o}qI7FJH3XhqvCynFe`)tAg) zwC4Z{FPvieYh<%rF;XJOrW|9K+JD*gii%IpeRqb5Q?XH)}wouDx&dQ#KPr*Csz!Pwo%}m{w*IB6W zi!cZ*_UTuMMO)Z_RW>}iVlz~54x_XJoAWgDi7NK}$S}zmM;v6!-=!kPATCy5flWLN~7^3Cg z1>SP;5xA|H?$%)*$_CAEZG=K|jVIDJsNhs%BtB38e7wf?Ww&RCABN~hqCt4?KDuSrBf~p#|l232da$WGF5M#91 zI%+j&S?5!-MomglTxBZ@UXx2;On?-?`YecIkdUlbHIl_5VMyn~b+GSnU zMZ#O#indnjIu_?A+P@wj@tMz@e-q9#SbvT2=W8EKmZVHT5vVMZ2!WvFN~O4jQ#u1K z*nMj}d&~NAT09f$#Zw!L3#mH{y>8_(I?y`9RO%ph&{{MlgOCy_CFeA$M2(0_R8@(I zJgBBvX&g1oR3v9eDj^xQ$*NczO_oLxC)SX}gskwByE8L8i_2^<^=iGkveI9w&Yr9W zD^b1IUWi&-t!KBoNwblidOCmbiLCB0e<8&Trv?lQwoJk#tirQA{s&%_+czfm4!+d$4YLrgd!Pp`piETtTfM z>uqy?Vb2!EJGUCxS=(L3@WoI@lY(iPzU)bLmYdw<#z2M%(1SN~-0?-|!AixEE(^<~ z(~+|!t9`l^(IN>KPzM3$P)FOKj$klCtH8Es1KDPlUj;;Zj%27Idel-TQUHTjG+`25 zp;HqRSxyTw#aWV)Iig2}mS6%Hk|!DpForTC1S$)PDluYmBvB_ai#j|=NeHw8s??Dp z+iZ*t6;hLg+@O)%VY1EE{w#xp$#EV{M6;P{P#OlKA{AJR>LydA0v3U+Ml*0I7=c9c z^p$JdhnaXYwBT5iFjzpl!BV4yxt&nKI^?j}&=jMod!{J`tu~YD98-qn=h0O85?m` z{>_;u)4&%uMr-*oY5BLlybWWBO8+YE%8l_=sj2S8G0G~u8dsfbW7IK-CQWs7v}LkE zX8EY?R$rB(=*DWA*%-eyT4$`K*0zabt#;o8Z?>mU4>hRw{g?$sQ!gf41TV3;{oOxv zp27Oo8LaR8IL}~xn}+oJ{qMkO{+DADwIU86)rg@`YQ-2#jn6SPF0ie|Z5RFY`g1of zETrSluB^QH!r;`Y{_>grFjjFWln^7iO;%DRNyHJ6OkI%5CNXGcvY;ANF;P-VF1IF^ zs){itl?29nBdSI;Vl$JHSet?e6-|mKS!+zprm?M7=&23`;|&EnNJ-}&gr?p>CZTitIIIs(uB=2Q~E1oXWLA>9hhxF8z$f%vhh}a9zI^PP@<<{ z4UVr<4iKSMvI0}nF{Es>>^W1A5+)D~WV91`dvopGYbq_HC?bt$jgO?9XrZJ9gG92} zggSB|ufQOdmZh604E3nTB9SR&Bq-B4gG5xE5)|)o4jTy$awK6Zh}4cG7^U`PH7ewa z*iFY#qm+<&>WUa?wW*adW4fY@*qq#h-ITf;Lsj9y)Tp7%{1gwF001BWNkl^Cx zU?31sE*(UY5$gtMDp1gz2wIH_|bf0xmIHUBMMPX)-uLrjx5DmW2{3oiQr5^TUlmej5YyDLc$PC zCMo4HdP#;%Yzk^@@*12v$tWchi9wc`=rbu(X1y_*JSn-|#qb}0_wMa?Ttk01tIMpP z;!Dr-*vm|0xJk0e*@|wp*%Da4y69%cG*W+g%8iUMN!r*M{I$l{#pB3poZ{oF?`2FT zKQfFMwFl;n=KUNGCu6rwgW=ABKiAX{r8!XxCSV2HP1DPkhTmL=9(dS**zBMUxi#vb zxRR%@-}|7M==xNaMm6|B2vuH%q0@mk)_ZIE=AZw@3*YW3=H$tf)6>)EXY}*qZzY4Z z{6WnK78{WSf?A9PB}LTYB022JnQy&uV{v}DAG@d52dB>V&#wDn71B^;U?~JiF_{!4 zU`-P1j3p(LxmY-9P36TzE1z_&tTI&*Z7sDkwjxCnL{yUmv=Bq?ZDygs3~j0;)JB7Z z+NGFFh^8{WFV#A6D4|YCMe^9PCe_xpwCH4JernG}Tee)XZ|{wl?l^R4W_sH#cYdI` zK7F&1!yG#f!K~eK=%zPaR6l#{-9PX{2lib17M^` z=++I)En^R#zV>bJ`>wZLX{S3czOerJ`=0&O?>+F;7yqDl_>ZU8@0pl6OnH>Y{)mZV zv@K8H *=o~AuYsVf@&?K$tA`JET-oX%U%Kk>rRQ+~%>wQt`}S`(~oY^z@`d;QAQ zF;Nr@TUbZYA`7@wTC)&jrqTn+_uE@f%oN9p+Um^q${bKTm5+HSI*MJyj1RBC8k`KA z>d_leCrrX9`KU3lz_gG=;c$vtC}_3F9G&bH^+*jB3>1U_Awfm}>v3Ql$)Ogf*c_)g zNQuId3`P-QAmo;$L?I<|k3uatK_QIH7b+y9j)=h-q$WhX1y7z4Wz#Ss`Av$ew&aPz zU;{403PzBOwlL)}fW;_LHcdwnq9qeh45-5doTD^2M^YSUc*WLI0y-+@C{YLrsadbM z{6Ga)j)nBj!W2}Ed;4sof8PUH!)&$3Oe$NAxBIzDQkUusk3Bl5dmE;`GPir+92Teo`Az?h$B1(#gLZW~e$tw15-@DV@*;{IzU1as}OT(qpvD+=nL7ft* zR;w6nZCnbHiE()=H%`?RWj5tIOeUzrq)rsO3FnX$Dbr;>nRqZ*4KD3PokWJ_n;MZ2$m=Vga(y=v?J>6tAvGN>hZ zJ2-t{ukFn4{U#Wz{03UL)`5#}d$*a`HGk19Z@l7)$3FAWWp~|Ht+l^=|9wZ-x{uy} z_QZV;-+W`u)Mc+aX6N8VuY2ol@BTa2T=%-=lZ*L|*&SDZ?_WId{HK2J?$7<%pM3e@ zCyzY)_C8m;)J$<-rqAS4+p$}H z<*c+&tRccG%(G*XU+$9@O3n`Gl}I4w7)vZLh9W2Bc*pD{ot!KuDK^`@n9nq`Mm2&G ziU`HXs8*3kj3A_lvk=J~DJPYP;FQ{K8e*xThzt|XAOj>3Gpa-~Buj!2>03g?3c+sv z#|BUcLxW9ZiisGZWoapSB4lKatRk~$g)umb2}Fe)anK?vSS*A#DItc`A=L~FIoN!2 zo{b7M8nFrqwO}xQbApUYva$i@KjUa+ntU*5LW~oWc3d!1Erf-m>ngQLk(8@M6D5K~P2vm*HNjSr#3dUQ zH7TX2A|@tHH36rpqODw*NTFxtG!!A!d8&gZQqqWs8Y^YJx_asES(c9R$T3c=(l&T$ zt_Ge#L*c}x%4W4Gz@=B58ve=*=5t0?>92{d7=udR5Os}rDVjz*Wyd=E&dIG}LsQBa z#*(ppOoOz}3EoqK)ogQDY{X}Km3_+2CZByoJ5RvQ#@_vE*fCZy9LdWj`cvEe5kHxd ztrSHvUnduWRs-;zmu)ba6Q|liTi+CiGM)OS~dTb;noDvNk#}zv- zGV8Z4_Iz(?ZRPn>{j-bZaHG_rsX|ObW-<~9v98l9qZrNAW};#Zi5HTVc2+3I@kR*v1t5!%U zOGOd49GboU1Gijv>z+Lq?%cj>cXZl4yRmYtyZGFZg){4wA6#?ecG&+7{M+|s8HZa>A$^{+PBnwZ*h z*-dZ%;hS#1{(_5dzVXdJdB^v?uep|G?qL{^C!{ zmp*pk%(Kk5;mW2?FofxYJG*~=_r1p#{PMyRZ+O!?nAj^!1=+DuZ!D#kyu&-}3`VE| zVu*$I?Y_Zqo7*v6)%zyv$1@j9)vv==?Qf)diqEpLiR3Vj ztc9<@qK?ASc1(;IHoFNH@varD7Eg*bHZIj?9yw$eC7#IYvn}$uOy;49zGe zF!}$_-kZf(mS)#^-~NYl?i52t} zz<_PS)`MXiZu!a3i~VBQFbzG}1Yv;YAd*dqVvB02n?19dHD^_2Rn8fa5pm<*d(Qd) ze{VnBd!nMM2ihP@FV6iCmB_krBks+J^RKhk+G`;*iXk1YLMpO>;+5PY%T0NQCB!I# zKy+lK2tKTCJZixVGd#FLaX15quP92|=fZ_E@KBGK!?9hjIh^mo^tdm71WrQ@*I@=b zATaLJVG|VMkU@U&`B&TZtpgj*2jfPzX4kJr+0ldfV;|jr^oyyzl;%w+8gdD%%mklQ zy%$yS+b6ow$v7+Pqcw_XnFlNfCn5OFtvj97AuyuXckGTRy2ExL$$8k=m&fIIn8DdhEBeBjafn*4A6|V z>_=4J_ASw;ZZ2aE!m%pl>X@172gi=lMgK1~L5>-=I7TBK&AXoMXg9FxA(ZQXC60ew z3V*Ku8+XjuzI-9|F&uW}%?f=ASM1@}pqC4W{l#g&f1Y`tO}otFTLQrgunzl>WMflb ziW_@zQD|L?SwvFynpK1H+N%ygboPUZAR&YW1gINv+ycGXf7ycSXMP7o(14v(nUBGH?LkgJF4 zC;yFyKls<4KKJDN)?Iw-o3Fj`)z@G6m#@G6y{p$=xjmcCBw3f`t=HeU^6Ga#^6dNJ zY;RIHnq@3|ftxV;VF(N+=QbbzB=W$=Klj($tFNZ!`oXO1I=;X6`Zs^`>kp6qWc}g$ z`)1$cNb7XJsCbf%4^Hm=#F;0)@Yqj%=tF<`?|k%&zx0ux`{HN+%BLQC^qH4l>Av-? zm;dnhzg^w@rx$VYzuIFDwF$hnBQ~ zHnfFK@7H`v%O>XYaJ#y5b+Fd8Gx6@E%%{V_yc(=Ue>ivR{r>yuZT{B_0z+IR)`Vry z)G|L4$`FfGiGg1kOEw|)hl2Jei0=cPTA_nhNP;UALlw!mmf~u%oO-?`o#R9lN_YU5OgAstS` zo!tvhWQx1?oK5Gx6!j7X@4RF$(VRIjX**fl9$I7FzSbjGa>$zSmBSB)B~&uMomCy4 zP@H-7UW164ns*clp#+yh73gZ)ipF&$sZ@AaW+QOW+oiCw?F3Kp7hnjuzpZtZpL8Eu)F2H(E87#%s6u}uj zkpYia=n%nY&ad(K+0Wj5bNj}X*`3#}w$oe9;;>6Cu9(x@iIZr-)!tWuJajVf6oF>z z6W32YOB4`HSU93kI>u+1Z2aWSKUnNsxw%+GZHpD#JQFuAo){2pevnHeF$eQfhe$!I zU}j#tMJENH?*IJxzx4Nhaxgq~^N-WP&H2^u+uf zrrrFN$DeryPWJ20a_nxjf_2#I9jZS#tE>n(; zu53)~pdF3MH4Fj>(|ojW0kl|ADMSVk9ty#Iy%9DIed*N-7>dU=|Bq_P{+Q<2h;)Nu+odX zraz31$56L1e)zf0(Px+B0A ztoI9s%eBD<*5GpAT6olExFj?h%Hi$VZ1?bB=de4xak<=S>IdJa%9g%$=AnbP{#Cnk zGtkA1i$X@SPris@#T75^%=lHh%D&Lo=?yqAobQbfcnTDfz} zgT$dQ@5sHFCrT+R@7a+^cO)0eF*H*hV}O1Xx{Ak~SU*NaJYLK!?=0c@F=*__L|+|a zs_0Q5$87a-?1da9IJjddZOL!p@V44R3?8%9WhufsZ1)Vi^?cRx56!XciFr?ho$lYF zt_NX>@nZg%#peSVD+RF*Szh2R2mRE~-DR-u&Ru}@p3U#x`jSRmfR^ZJJu$Af!laLU zoCrSs^v)%B^V;72tGC*{-7fFtq#_ZrsZ`FnXjN60%1aL9&fIM6)7Q5@MqDEcc%m+~ z0tSKgwTE8$-!EML;}=>@r6@r_<8c4v**dyIU%JxZ#)4TG52SKdc^Sn#H0Q8Nw=epS z{pUYBdv5K`KR8goGrMtZdUdaD<`(z6CU{pu40@2#VOCrwrP3YVnLY5ai(BKTj%5zM zYlc7K@82aJ-h1YekAD6q9(?iJ&(E9r&a`^zt(ya#wpXq{|K(r1{ncNu+y7;B@ghvo zuOb(~;6>Q_Vf2`TdUX2nXFva`-EaTtw=doNCtrW-@>l=h1E*f`r*0BHz~tdaFQjYF z{qv6Y=B*c>^uII6Up=||Vx#N}?Fa_s13Euys-K+43tc){aGsfN+N5rryY8w%^x0bz z!p@?8aKRa1-aCWN>eoIgJL`0tlnO?|AP^(9$5r@9@T+>4r3%sZYfo+8W1IW6w?R+H zET7psMndJO0u~X$Jc|TM^el`FNhpv!A)SO}OIYU7ObFy9Vl9+JfKV}1+CtP7 z+Zr4hqO49j2}H1l;OT_ukRBej4lB48tqDqYWJ6$)2_Ew+!XPL?u&^=_FOBnBFhL?_ zWLa69og-FgAqUK%4l$;g_n*3q-=KkDy{x(c>kwhm$B=G94SW4txIB9|VF0J0gHgXl zRxxdF-@5tcR1f!OX?}CD*ZDJ6)ZuLasKp^`7@t*#_OM6kS&c0jCj=DtN3bIB+6jouwiY zTJ`2BAv-C~FfT0I9txsX%oPzY*$N<1Bsgo6&-ZBe3up4D>##A;pv)Rlrb~EFNn}Hn z?w$LC$HCB%Gf96Kh&it9mmVkmnf7+qqbuuS(4YBPM}bw+Q%dsA??jG0#vQ|0uCIKK zj$*5lq?Lx~U>P4Q#ELPDg|Tr0CLn^rM!#^}^!1BNh?$O6Tqk%06b|}d+WxJpboNp6 zS7MmyxQIDtbJ0>Q8V#TQ*}DMi?%V}f@0~<==S$w9V2)x$M@fA+k0&U;WiSlo#s_a* zl7q|p-QM+fcGz@nTBznOEkK=y>;}aJpQX5kLl@hhxpC$bR0Gm7Jt#pW7$HI=z4+1R z|Iuq{x=@o!y_vc~X>7cqwNyGyu_mN84#%MuKOT)wpSHD;Xhn>Yi*)*ooUV?n7WasmM)ih+vZf%+LTAW<14>0U%d6@fB0+Li?3`B=DhhYINS8d zWTU?p9#bmA)(3y~Q=fkN%(ZK8eedOiul&jPF5Z9j zQs@YQ`J6jKQBsA4l>CFa8J1?9a8v4PuvSLhcQ^AZ3`SYbnRT-Jr{%n3!_bS93GA!Wm*vZmB*I)2GGUc&~xJevS2Iv?j%Uviz zAuofbA}-T;+LkZ@h(ay|PYdc9MASn@BbMomSU@R8O4O0k(M2kchl!^Zk`fEqa6ra) zWJj`9J|&(Otl$%5N=*x?#X*OzpJuA&PYbzU@-WuIu2V2TCR;wrHq zF1Z{fsEJ5f#=K@R=OmaF!F68n;#xJc6ODagEq_{^7>qp0Xq2`kd*~>J>FP)7IJgNg zJQg&1e2?$PM7oC_CmpAkekf@BQEP_s!yfF9iG6Y`AIpx3<@AV6-9&)C>D#{F$g(-&999*}o^*gd^$x_}DCFzt!JwtvhVZZ}V*bh56m zAjxMeS*}J^^X%vT)4Krc?%V}f@74VF?w5kY7px#g>mgAW5PTpMG_&Q`s`TW}OY{Bf z*X{O=!>(v^m~tu0_&k#s)s3Tyw}tvV4Nu+N{v@LfOhH!1k9lgt8XN7#TEvSsWSbu@p7{7P>%&Rk2lwZl(@%WtXa4G6p1=J1>zA(# zH&Yz0-?_b*ucfP+U;WPOm-d^le*OB_zxBIc{_>xG_xFBv_Ua!!^x%|92RGl5`M#R9 z>g-29@`b-OoL|56+Ux)Fo0nes#;@=GvwwE_#`gwWaE8qdPAn;!OFWR;##ld&_3rMKO9B?t+m1&3dN!;IF5iR=i8fdeB8RibgL=A6{5vP?q+M@}RtLX0RZ3>B(O zqvU`y7Bw2M2K8tmR^&pcXba}41kEs5xe}{L<8T>^C?rSmBnN^BrJ#-&a2|66j|i<} z$2hfDHqSXY2OH4!8Dw3r_e7ZXrsZXwWz`2XZ1myWyRZ(Mu*cr)+SSFaJNt*TJI zW3YGT-aNm4b9y^ZwVJn~tz+sWccql*)j@L6Z9jGDq0c|MHt{3Vn=jwqdFl4v?jm(5 zt4nI7D@|9%VOWp+{&YXG;8dL;kVyYwT^VQ)ODxy_{0m(D%aEUe`!n!aA(-rCRuFxrtdD{&p4ri-E z|*fYzqRl(8=e@71v$LO>rM~Pewe$9gjgWl9H zPz6SU8FNPmCNJ&E+T8pChW%H()B6sFeW_CJ+f8%-7HxZ`#^Tt$(Gq?+*vltxAO3{r za-9xh>?}C#p8djIfOU870<8CJe)IO1oW~jBS67Ybu#AV?;go2cil@3O58S$%^wzvR zTy$NRU6RtOYB3)ws!DVaNTHe1PC=Zzj@!~U!RFJ^@*&9 zQIcl-a!IMp1+wMVN@`lCZtAp<&DU#~+3p@u9{%}fWWMpnZ@;SB^Rz#0=E;e=L9NbP zn@TWA#ajt(hMvq8p8nJ?t(WrFx4!@M&wPA+dM%`Wr*!!GgCF|z=brue?&TN0 z|K^ojX0Kf*2kssBdyUK6?hzZF7}!0*!AfY`>+;#QoXfc(%!a2r-@FobZbxm0?Q?Nb zwVO{Dd0@fAOF6azzrVBY@)N^$n|q*!32eaztkviiv)CJsgTow2X#&%<+7RqAhlNE# z1D&``X&HK&Obzvbb1+^*EAF3Ajo6=ncR0tSclBnEE|JRe$6Y9%lIWI3cu+?w3=0x* z%E;3cTp{RNud3&{10zQ$zDw1G*X`S;p&wk=@KVy6koa@~>Q#b+Vp@wrkKNDaE zGf)`zF8Cp6FT)Y`+lFZscH7Fu=(>ZTiZ46Jp z|AG6zc=4VO+&jo!cYV5e{jKTMGD{0-jpCeuI_D)6pRx|d;jq=RDA8=7Za5yuWUU%> z>9DajcbT|!%qKIPq|PB;r8sdViQ$q{6A?qb7{N)dU4_%un_3Y4aJ~41*E3Ya0lv_H z7v>&w%sK#5I$QbptS4yzCo9w1k>hIJSnI=je<WFg?(t@ zp#Lhf{uM1zT?a$sgs=|{IGDrYSS|FSajkj3#ETnw2j*0t_`+S2``x(!zoxLwn zRvmClwZ$of6$>7WA&ljr^v1>MwRCvrPP3Smw8)EGiWQAcaZbWGO6xQ)LUpkIx!Kk; z)V8W0$+p_YTDmVw8o%}{n~Q6E!!vlTbq{C0cyCD@dEk658pT!u@kU}GL0xdqWP;7<>NpYSm3<@z5GN#DVbd|{ti6pQD z6-hBiE0hQc6&k}CrgVj3Zx_;ZLQa$#bEuLs&4A=#K&AMEudzs{sAJ)A9#>HUWOR9x z5Xc6GRs>sRN#sm&c*P7al)_?{dnY%jt3Eiif(v~7GYY&nuK31)a2BEImoOeUpjM_w9xUS94akjG$RcWK;lp7G1sT)QevBYf3+%s@E zBP;E(g}xZ9H!~o)!(28oAuFH87N=7}kaUxJ^=1LZFAt?n0;0-nC9ajz9=#}Ino4^YY=LI~=43?lM?+=Ptl{PZ=y9k%9z*Qb2@S(E^G%YR8%S z{N7s^4zC<^)2YtprE9w;n~5n-eDDIMO_9%psE zS$J*h@gVI5O9kfyS(@N67{oM-^F7xdNNO#&rCGQ(rS4!_n(pzxb>I9&x&GZN!wYC8 z-I$kX-pmT(@va5)UeYpOnjjA8vPy~?;?Vr``lo*W({LXgzB5(f&p#(NpZHlAp1t?U zPkr&1!+y@4ymX=x=!wraVZ_V{})Uy&C z92AeYl_e`d&PZSpq%hJ_dv8D_E|?UOW6d$@IPJ)p(~;WYCS*shU?8iT@T6pq7rG^C zNmwRUSjH8kTUpD~GA$ybBl2z}FpA+kgMcd#a0OAaa0pM&vz%IP3;O=& zZNC=;m+*B0JN+ius9(nvIOyAQfPL!4bbqn4zjv_Llmj{W=y(|SZ_XNjers^=wi}+P zFRYJ8?wL>AbN_?uAx{tA*vr@U^sr*RorGGG=%8|`h!VZ1xadv7$fIhF_ip{3b<0t@ zu5{$C>lTUBwN2~H6h(s~W|(9;HRnau)JIp;BE>52O>jkI(8b}fE0jeF&O&GR`h2R* z&(;p#QCDapR3u^lki(X?BPE=Y{0f(t!x^IB1{UYIVVQ`s=wxqFJR&C_N-f&iqS@oB4VcqZjYR{D`e6U1(|=<=O9@3%?YoQjC%?95!1 zO(wEfU^aYk_rySV2uUd?YYdITEl37%WNfD7)eDMTt zp_V+?G?#=^X=Uo1v-|#&XT;zC(r>>qy3lPrGP?G)!_GPd4S01HN}gHLQk4|x%#BPn zkV_aiE9S7ml{e?tr*E8Hd!`SmdavgG&;0%W>@$CxKl(pkdHuzckDs=1;rn0x7hC7* zb07HlbKiLGkACkPufBHeW|dy}y~}^})nEC$f9s`0K zS(TRYhTHJt~$-n?{SO)_vP#SVY zZ0TT_=>!~U8H;83fRB(VMieR`buK<=H;ws_G+* zYN%m$>?ExEy@F+wc%-9Sf$4(qNDiGBg$%-;y)-T+!Am& zBQDe1j)A#J?{n8ao|fLaZavSp%w@2pt6jJmZdP)7v%|x!J^*Mym%oKgVUZZZ<`R<$ zGvhgb_n^HycLCOWJJ~2kRaVtdc2w$EJ`Wi z^mOB=ZEc&<;lO)}U#+@`QAac~`{rY_>o-bOg_I1ZB!?Fg&MS&~RYK?D$jKrPYIoSG zs)(4U+5YUkXYU_f{?%98a&-_kZhz}`b7iI`q~g6Uj4C&#&UIwh0zRd(bYPj8YE^3& zOSc6k=8g4+xBfD?|PQWeL=&$|@{$}8R zEL?wb9qRy%~Bd#h9ma1>I0|x9sgyZsxP*@dI%k}=z+t9f+<8*m*ONCQe_SZED2CS6}h5J{cMn$(5y7?;`d%o-L01_#_XK$TK) z$=L8*8F8qDGjtQU9q@@+OTA1^4>$*fg;BK#&>`zI1&L_Oq(a>)tfBP1bwfon!65~N z+;^*pK_WS1f$FN)JS!GgDPFlRqSivUP5{Xa>k|~L-1h;C59zbze zp?FYSO*$k5_7i_{g{QZ^MSK<%F7>V639dqc%^rA-dv{NOx=)r|Hn1FmjfQ77+_w*{ z%@)Vor{db!O%{w!rE+WW%GK#Vd;Wp@Zt`;Z=5xIAjqPR+2E|V4WvYNPETD-|AusisrN^{o zwD;)z(jZTFr>!m)rlspb#;KY!XQG5Ek3X`z{Yh%WE8TKsT~I?Ra!uW`eeW+`{=X-8 z-ndn3Nlg(I0};al1?P-{SkOV^217Hf)1rb8&TRC-a~D4Q0a^bl5_!8C0R_4Fn5+i9JJzxYRPcsLnJzpYT&R3p19|UM?cnIkmg5;+<$TNE<;Cv z^*$bPt53739{tGYK5~BY$_syTFrV$W;ajg<<*hG2{ILT#L3y6*un$|k+hYvpU;>-a z_8!~1kF;JRMy@X?{$@SDK3RW0S8LsT7>A{$q*I zk28gH(878jZgsXllsX7-PUvbLT4$h`v6w9NEr)ta!Msw|3F58k!dlS{mBow=g@Q{2 zWwG?X7fjf4tY;>fQ)_6U3`ha$NL7*ZDjA_kl%+4wQv$B06tYK@;t0?vQ3%UyPtPE- z$XLW2c?i%AaBijOWg&T7OA0d-_YXQ-~q3g-z6xSBL4j&LpJk%}(R9YrgJRSe|P zXk{4*tH$Uhp`eZmVj>9=5!fv}y`!7~2BdBLJ}e->0M5fL7{CG+P{Zk-Jek87PV^`F z;XyvQdHKe{^gz@-@yxk3+rRdme}41T>+ST)t>6E?&YP1}Sn4I=&>lU3=D$Y^);A9^1zIXGDi>u83SO|i-l~?Hq3`wv3X`cwC@BHo5V+iI*S#(Enpz?MC``Zx=wz^J)cQ!o- zV<-&#{=g4nKtVhrc)=^qQOQc(tNjXlg@9Wsd@T|nU>%1E6lv*zx&H~CBnON7ht_t^SigcWD&_I zxPouNA%)80JDg+9Q_ap#-xxQ$ZQCuZow?F!&T2kjQbaxezMH2%YbRuVGQHsEi(KWR zNMHacKC`w?^T!{!^!u06VxFf(gB20XRibLn7e&NO)r&Y^ZEsiPoN{NL%58r5{Q6U8 zy6-gCU%out_G_ng_jS^IX3h(h6V!^=;+m43nhRz=JEAiaSG=L>tAS|r6RZ8^sq^a} z`tawD)q3T3=@5T@(e(%ZW?7$6-i74G5B#N1{o-HguHAn9m8)TV7yvq^`H7on-ImHb~EyJzU720@DnGQzl(=?q{rox0em9k!B!>^xN z<9c9W#0mTNG5H7&^v|d;>C*(30Rh1IKE`PXrvjT5&SAoOL?YeNbmFQeY3-`2Q@pbU zA>lH9LFEZbS#UZrjX0FvEp4`~_hP{^Lw6HmlFMkjGYMEdCa&;P#sA z?66h3gV^0ZfBu1;o73{fZ}Hak*-JNElh($0ZBp7nS&Xtw@@UZ1-oz`;jjB8xS*#az z#bAVxbeeN%b(Xr_Vuy>=C2Nwk-QuQpQ_K6>v_+ISC=qItjZrhIw64r~G0kS;1yp=c zCmNki)=HYIa~7quDrpJP|@O%`d4D z1o2c3Z#c#K{+7w`g>m=f{7s$TO3ieUEH#a&b>2{y9j+cWC;swm>v1Bjynf>F%4uu1 zqR(2jEsQ+_N91Q-`qjbh8@Icg3oDH$h^hBboH<2g^_%aDM0ICRXbR5FhbO}MFFvz< z@44=^#r5xBO48-Fo!yW!-=`#^xvJzMAt%$aD&3XNk`KA0B97n%6=#bQa>B>KFl?PV z`Jqq#qJP`r{;$D>;EsKK*K9uVv0r%n^v0il^IJPJ{pz*(&l=p{-+$lfT^GyDoAur= zvOEcnvcWS{Tr9LT%^GT?u$#N>+BYdoH>`+1>`>J+bN}epWOtoIBX()c{Af;mmbO~) zYN7wQ*()exI0A|E0i{#? zl0XfVj5;jigw|-mJ4(iRy5(*}piP({LbWV2fE;j86ayRL1Se!+oX8a}<8;+cEP*P} z31U=G;;QPWcmNmajMQ>r;~M@XWLEof%Pp_%p1~5>gAO)Waf&DUB-aCQgvo5$xUw_M zTzE7e+`hWC{oE6$%c%=_?DG1coPE|#f0V7KIs1^_`;?x4qFX!How|2%_Eg%~()AOI zxYoL^6t^JeO2p+dELux9)OM64ENqZE-y|z$B|FQZh|jS!CCy6o?PDJp~JDh;N9mKHwwu0dWmfyA~xK&2z zAQJ8C=C64Duyd3`RhA&H1f2G))v(HbD=zlX>Fy$-hLXae?mar9pVi+Rf>=Hg&i= zc<6embmSh(dmC$8v(1t1y1MwHQbMuLxe$u)qPb!wUPV|y z2A*Y^;Rx3H-s<(Y@5O&g7~SUiz|J81QF2^gAEri#WBfw3s|M*{z}3JtkNEhgGF}kB z#_nf0`2&o>2{-{W*nxAb+B$B7!P65S5#FlUJ>>SA+_%nc&#XmiZwZofFzvcEU+o*i zioDNSOYq2$7ADG_Ij0U;-=I@~P_xr=qQV9Ca%2-)LnbT=!%UYcj?|y6OJ=CmRigE1 z8I)j1AUV{nRtOzPAPE+55hs*xi2x{$xHQyzWVww9DH0>9L?uO}rFcX!k0mg~@Q$`x zkt54M`CJHzu0X&SR0%>`5tXWUH|mh@W-h+Y+54WOeu^@{--eyucl{AK*OMU)%;8L* zG8&Jy{isJ zrk!`L3`%jtta4f6VWr)Sn8U1CYJxd2lZ{~BcO{OiDl(gq3Zg6`A)`Xh{W_jQjiw`d zR54qDiO>tw{6J{C7HWqELVnwLUf#(UA1&6=H|OLZz*TyzfI0L}5(V#;K_aWqUy_|g zaD_M^7P?4lxDFA-p&sw?`2=ZKtG6s&a7#1>${op$(N<+t5PT>dDUk|5*OI_{hRUGe zSv3;I-UHC|zvD;5)zX^Q90OIZr?f-wGi&>p;N>ErPpc$%3lzU{6_lecYP);T-krMu z>%C=HJh+SmTtgBBf=3KfFkD93R2s~cQii3>yw}d~WL1$9hg&~ItXFGO!?Wn@g}D2` z8Ykzob@`*vUf08X%ZB^$bh!URlY2gP;;nDxotJlLTS*wD2x4N+p=!>#VAXhd;pZMd zd0`{J(cF3Sh22+f=R1eNawyKc6K~mEQH%3=q#jT=&ZKBT%sH3UY6WCu?4v$ADL$th z>a5h_raQNOMB&+A_%txh=3<~Q?5!#wX12GaJrwfO3!T_I1OCYs8pdCr+ zJhhS%rKK8@Ji${84T#XCm6cEoGQJQZ7MW+PqbOQa8yV=xMri0V%Y<#M@yekxa151f zu+(alEYyY!v4Z;5c{elyWJijGOz}9#ExrPWb>zVB75?Ux{rddZun&+=vbKO5kYF1W z9_sxyOMdbs>;bS3qh-H#U2HJgT0ghDbLF73d}RuPo`#w#U>C`P^TbG6uA_Oxk%mlC zw#3MjT)RFJ$>Wout!(&Mm~jmjnRZIcaGwt@|JjFghC{eRc8fNrMOlASZp_2Jv@bHa z&qT;=)Jwzh`al+~4Uw6HLK0mQx|lJIbmzx|Nw?e!s0l_3+2bRuo$R(S+=EdMk2Y8pKwWyj3yM`~d9$LgK?|2kQvD#U^;DIkw56Hu4? zeUUnYP&-h3T-`qDl7O%75q4a8&Fq5rlTSDI%-o>Z)hA zb8i}38bs5vPW2<(d+B{wq3>N^Qgn6yn&Pn>hovGugjOjT%d4fxbj;d+cka$zfc4%@ zXABAkE>I$tscK}INYtUAneI+$bzjoHh1MEZO7>u8m9ybf)9r_eg&c^64Ji*Zy?Rx@ zKTmsA8=7V=l>|+#Uh?(-!`_?3+LC4GdEZ)l?}#|(-2T2cyUM2eDyphT7EOx=AyBj_ z!GJ8o6f_VF+XEMD7!V8?8fxH~C${IbA;a>(G7L9>O~5o@kzfPS#ZE;RtIblj@4bB8 zZO=K;>}Gio5ieg>W+k(-8dg#nc!TrqJvX`&XYYUi>tFxBJM6rWp8i)4uKpVz?_SmW z-@d+E9`l9e<_%It zpel6A|Gwt$-c2lDrD?Gzkvq`99T>f`UVwHsGV13B!$(G0`7-AF_fPU=SK{$<`NaL( z&)&)}H)EszC&5z^3nM9RU@%+-1((U0s%EepqDjelShm?UDW#$E)j$pRXD`a};Fmx< zVJ&A112{r9uK7$=5FNblR?S|J^DaFswBnPLx~7Z6Vu)Hh7buof#~BGqR?i+vs@go%T2&b69>8T}?{xJb6Pfb3Zb> z$4a6oR0z~cQ7j`$ZB&9!RJ%|JN{pByetd2Q2`k*S5?ia2=2wHgCRI}VybV-_7%4Mi zO+XZ%QF}9Ox;$1Jt!LJ)o;kK|E)hOb>ksp_kFRxBYdy;Lk+~bj!Lm&LSP}XFg^(v! z??VFX!|g+W^&{QJQlL0Wp@Ko7qlcYDc9@VeF>f@jBuPwSYI&eYzbGzTy*T*!sqeiM z%%cM+_0RNYucfnfVQ?lx?#$FI=k0^CNojG0UnbpK+xK7+uP&W z=c_;Y#}8iqvsca9#j$I@R`QSrSDdM-nw83lSvJ+G%E`QL6G6OBxp>vVJC}>2;LVE0 zELHI-6r6}^gZCG1d&bp+A2JCz+HhaM2A+ZYP{9&TVSkpP9=`1l&u3xkG&y|wul>EB z`d|OiSHHIXN56OZpDy4UT%8xJCvbh949;Qit$9CgUevB2^2@@o+_u}VTuMWRn0EAT z_E+oXRvez)_N7lIhg(nIb9BbRs|?Tl`L?TjGo^Nard6)P07*naR7JgyG>MXM0R|4vSkzd}9pSK0HB^ah z$BA$zDQ9pEuHX{&3NQ3qaz;TqWQT5pEHOpnlujr;u}2Dc&JAaHkMD_Nyk^ZXils7T z>_QvcvlfI7=g9MS6h4l=rs>dW^kAkbm!Deq}p+;!+C?IHO zrLU?oZ2gX$zrGz#@7*rtE6XsPws}45e571{dz*92#9&74y!f#Rsg$CFD=Y8F$xE<4 zX;qglrz%4xS1LJKvC2fGD5g0P_RDgqSaX-*hXJtSEMy3e(LDFyYjcl%oPo~@?f`HI zZZ>Alv(otju0cOm5C6`u{@wfk)gQfhhyVFo{GBjE8b6l^A0>4>geT@1#68xbxC?9c zjK9(IFvf2^41I++c;!#FSDwkP5x@0k$J_mDH77mmj@|n_{A2Dv!;|j;M~>MgJ$wP~ zjxM2a-tduzbwj=4bRU08eMa|CLY41zWtAnML$U{f{iscfwg@s17OYzEw1NGe;@I}A z;n4Al5o-Dfs_m|9FCs;79=Tws2@Oe6BRZr&AQj3iLhlVp424E<70W~iK2B(9q?8Is zAPBvXTU2plW{y!Q;vr9hs;G#YVmb{fnM#nAW60=0Z)gKh_0y!E%M`*>zzZ6%jP=+O z2L8nkUtja7!-35yZDv)os9_Cz@GW?DE_d~?1J~d_6lmbwM2cVasSFQF@=KB3@~{p! z`dV=wDHG3<2GnpBU5&O7S=8i?Wlf9)TZ5~%dn((_@?z>%TtSIbXW2N@2Tv(1g#CtX z|IlmQ>nhu~7Jl7?YfBbUP6z87sXQ&tnR?Z{EjuAULwP=|4qUEQ_MoY*v@HSJOsU8~ z!4;w)N^poETau8$1k3NNNtYQ9l}7|mK?jD%vu^aA1?`jJg5d2f!8nt2seloHjDR!5 zPYDBRn8Vfhf(jXOHrIcN!?uRE05(*x3kmfc&g*S=im@thVAS@I+hDNIGl^Bk6f>sITLzZKrLZw z4D#aOU$*7HUAhkn?s+m%j9+w;%mI?%~l%j<4TwSC7B#{oRM}YY#u% zkKlV9`p0uhA0!QyB&r3jBZCgClr~TVRjiOBwL3o>o=vt+(i0<+Gr8K`9{x1V63v*- zyBZDoz}@8P-#&fgpX_y;I!YlE#rvg7M;c7?MU1W-MJfAz+sS3@=u%q`sV#!oa{n3Y z&t2cPT1&|y2&$=wuZ$s^SaDkyQ45wWsn$;WVNs+6Hd5-&QKwSy>Wii#Is|p~V#nJe z*(R>@55vekytPXB(maq4SV0sH<_`R^+h+j}9@9r$n}xX7;SAzf2z>Gv|CO8n%gtM_ z$3J>``|JT<9Ou6q^9cLMII)2TkBz5pmc%Zmmcs+%lTCjr{k`18zDsLs(p?vKF7%04G|5Xa?3Q`X667yCHm?rK@c zkS;?dbu1E=DOZC#Ff5u|id}HC=2oB%JR>~Vp;L0FC9!QB0^4((5IS5%%oI-;r>cS~ z*;Cr7Ox7ENP%2i1Qdl@l$z!gwu{7G6T*w(;uo~y+A|a6-t}r+(<11X5xP$D_gu2YV2*BJUyuqZV`L=1IUu+A;RYZocR>IG-5(G z6nE3?pNpJ$TZ$YoAi5*6kZ?QMfl+0@)1O>lVzcaS57N(MA7D7RLP-1}^ zSIBX~<{j+Jr#j4#%gsHKqx1h$a0gE38e*FJP}6)*(qlq+o%2rXDdOr2*?qxn$>{?@ zHoTox`cWV5$8eON{QL0khyI|maX+pSq2WBeq2NXrz%f3{6FOu-JW6(SrBs^QtST0r zbBpJ?`XdZVFe*=oWJQ^PS`gf`9RA|P$!}aK=ayO|8YyQ=ZQ6_aWZg-hsx#+ePALsx zupY_Ybooqb4y)}ev0nI?icclK?+Tgwc;gF>S9(DO+FWqHY&Lmulry#rXFu3Iy9?Ln1?B?QGk0_kc3EH09OB0oyhFG(Bds-zktwH;;1sUH zEvR4@mhc9w;K_gUZ~y%l|Lgz!?B4d*-{2X?^(W`iwwfKO+8Lx>!v13h&H7kBQQ>nn z55ZD**H-=F$*4D)%ij(I?(izbwJ1EO*@ti^wx{78N|@gpS!URW4Lk`iL4+-|fhUew zSL`VLF7>N4jX&A;OIJm7Xr-2Pv*D!ICxpBw7V&UgOWzS)AG>;tbzTRSj-3YWXuyoY z@^*KZZI5fo14J56t_ctq^jkEd0ZsG`IZ;P);xe-WSCb1W;HZs3@P!;e@sKk~@D&s? z3~nM9tf9{{S`swig=Cb7tdKMrPgah0Kpf@?2^Fa66B^I~O*9p4L;~8O4)0mRZx+%S zpFP^J?N}QNkAD{S;A?OOM>8S^wr~I^aIoLJQmvXoaC;&3%X_y@jxM@-L34x@icEs~ zBB&<}WT3$(EU_|%(NdfuKBtUtr0WI(>@PUYbTey`U?;F}EOppFs#v9)_C{Lo^mUi& zr+R-gZcB_`w>mbZ_O`(HK{lJ*RDm$OP~@l8UCB8%t5+|!7aKcujneH)pd`$(&ZtoQ z)P3+swQam9t}=P&8sTVk4ya6k>5*0N@J@`^Bggj9%_Kl{1jQ1jXeQfqxRDrROa^cS zC^A`hxO`9KX@ufXJvR45vgdm0I3R+RWAj^YXR0b)PQHMuJYzuE_&Q11?>&-mky^+D z;)sQ;m|=lZprDDZqBhZ2{ahvVk7>)w9KAj^ob16lcHFaBu%7@mg$>0)NKx%oO80|AmN%Y9DR=6U2;o?s1V3dbvVTb47B6u^H_T# zzjLLmC`H;-Tto-wGdX7|TVgL^Cp0CSr4(ur6{lr+_bXkr)YP@lxeb)!#YIWJ48;+> zDORg?fQ?F#m_rch)tN~tNopx6yJ7VED@AbFy6q23L%29y4>yMD{f9l*@MloqIruhQ zKzWm?+Ib$9urn`{_u)3w5a0}&c@Y}nMYsnC(7_SB1~0?sp8Lcvf9#3>{wv?^&cA;C ze--{c2hH46HO?qw)0Ll}kua;zcgAkBDJJ;J1%Jb#4VDgW9ei=ntFgZwuJ5|Ks-qnE zmyPQ-h*gV=aOG>9{Q~d3aO&XN+!y{axP%JsLWZYyz_?X`kbCrgJzSE8tlrXE9=iUZ zsqYDgMcsnSis;!Fb}3i-tX|epcMAR-BE4{?EF})5JQ(8HLh{B_C7hCl(xZy@)HT^~ ziCEzUOSA#;*oMIqGvYDBZy0K_n+O`08Ad}&N6x4sf{$dORA?a>$&rjYgQr)DA%feI zDyqofafRT}nymC-Mr{NKW5b-`j80jmDV|vT6ld~VO10+W%gp8t#8cM79#=xq{xZ9SF81?s|;qiT1Ly1m5h_6MbOPer?)HzfO-`EqSlrF3O~v0ZWJ z)oxeQrIvla{9^a;W?8{9`1aXOZaE%izN7l=syQl!p`#wZlp`w20Nyji=_e!7dONxr zQB)QJQ&&RpSPx@F(f1&(%%f>4ERGF&p`ae|m`*K?qiQ}RlA<7P!dsS*LJC*v>~g3H zqmzP`^n9{Kgd$|aIkem?avv_KDp;#NSB8}twJs{BQ7|KID4s>ZIV|CgJcd{dm`&&> zDWSk)FC2JBDH9N^=jGTqe(b@uImGgj@7Lpm-)3g5+&n})5_P#kbW=Ry2e3-`j??hl zF~_^S=ZA7K@3Ob|$S{4^9^Mf)y~jO#FAdqd-Zo%V+nOr9?R3Dr81i zCF2s=Ppd4Ks0(44oUF{BM-gK7NVq;!A15o>uskj9QJlj(5-3}|qgrzKGo_O9;CHIC z`zYF{*Dt-?f2HDc3)q3v zc?|Wi19>*fDRYXQ@g`_o!U=nxUv>PxyY;^{&z$K|zy4eG@VcfUb${r>C%ixCtrw1k zE8k@E3*UEb`5FLcOfJ`>FZnTC>{7vlW6pZCg4fu(x(RW+=eo^s0V~J0p&z*3P${Wl zUl;c_ti*hQT-tSaY_b)?k>9Q*ue{XktaVFPUWXup_fF^V4s0z&-*(Z@PutD>JL9(`0UsY1*3Y$z? zvFym6YDA`qW=XRH1-~?@u5M2Dx2Eev7YN-ewJvP<#Lgn`Hs83{?rEUTDTgm=`gE%4 z3Oj4k!VPw)WouS&-QJSTfHx?l?Uc1s6wM?-WFl|$dlmW|E+b=C!e~Thh@o%SY>nyB zV_7Xt)iIgBb81+^1OYX&8xaz&AsZw_$PhA4$N^1s{&SUIt6S)nDw9W@L%=c}xUdaoj+{%8MOl1g-CQLZK7kT4 z1GqU!w{+x;d90_}K|lYXY_6o=UtT=OmH5iV=o;A_mKM6s$F8>`c<-D@&{B%(Hj9ZY zOYFo&#Yh^0;zMftBC$2~YECqjRtY{AaVA>IB1s%dQimlGHKdFR1(*6Dj;3DrB}R9i ziQ&)Z$HUtO?h$5)jU6&+4l*`z7tntwxKf>+t7yI4!^TkB49F!gKA$*LG()eAB{RIlM3W zvxUzn^(*h+635Rr(of8?cY(`YZq!J`GJ1q_Ts#P+Pg^H@tfN%@px3?MdQ!z6q{7v! zJS-B|+{ofm=sW8*CHmy+9bsE(7eZemed{ZqO3%t;8EFwC6#9zbvC+<|#yNCN+fXYd z;tJ?Z@cwq^mnOo7Ex zV4P1E)P~QLf>h{0GRC&fWDEli{vXJfeD;}_8D8M@4Z<=06*!(HeLBPLe#EcKD^Kj4 z+;M%1nx!C@>-N>di*LMkbc5nhM~+yeuck=4UholV@QN3Pfx2qN)G*oTe(yOcTojfy z(qWFyu>lM0?n1#=XgbP0+rnjGaHzf3mwofUSh@S;KY8`yfPARBj5u>D zKPG@B9`CS$>~W)ILqSGoMWLBiVz`n`O+?UKS7GNFR>F4FQ&zOw>8tAIxUjJ!rg*Hy zA8`2T2jTn)|LrQB_dQOB?1Bo-V$V6P`ZiGpoF)@nX6Yf;8ceL9;v^IyQ#mIhUd4*^ zQp8fNWaqRGi$&k3Y%;ha9$YHo7)q&<>*`VwD8(04Eta+Sw&R*tB9*u@(Bj`G#2NTb9{9+~Q()9F}JZza(X9$EtABaFp4NoFn@#Y)>QEWR*AV z<|$e9b&*I@xlJkUV2hw_>WB_6lnM%UPfWy$rO+#(r8lacvI7nm35xXeHH%ST;~6rg zLmWw_DpJgJ4%xx?cuyWp7CKT*rKD=gN*Q{b5|z53N>rT35>}x#smDB0*m|7cGE(Rr z^=wmKC$b_&=lEU0-Q!co-@#q5QTBfdPT(%Q20sV)XZP+1a=?WYQOjmK(cR93R)_Pi z9_;?~S-p!Yy|MH#OlI`rCeA20=x`M|V&43Au@{CL=Y_4CV296aMz@JbCg zE1X37g0+a9$M{$G8nS)$R(Gx5Ki=CuXyb}(_&p6|!;A>cE@>OYu9C~5kR8bp+evga zKC6-_4#|@)J3bbYV{cv5@F)z&$euW+f|obn>E`mHD!#nw2sM^Ivtk7Y*%1xraAgKG z1$E>xSJIO*MM-1OQlSJgXgr!OO(>DlfRYmhiW@_^7N!g24d*^u_}TxO{BT%Cqc!m`#aC zfR*Q@=dq=r&VQKz*1M~belkJTcU9=#{S4pZ`M;O9y%)Cceb-FCuZ#KtkN*CV&t|jv zF&yUa|M6-EXWWla6@+UEIxf89I>OG?HvrZfYV zC;`ccvZ#m!r-VWt@WSvC?H^vhxK%$qDMK$IWLGV$uGwfB+IZUGn;28t>2um~CHehD zIp11Rw{TVpi$z_@woP`KG!GhvoXOd2S2cO(BHd zybc%j>5GJh>M$-ppLc)yh-YuWKjgjZ+`re28lCH3g$~Z(0={G1-w<2c6DoMPW$z*m zF_q#LTe}u&0!!s7p${x;Sn%nwK97|Eht_{83&Cr zgsH*~m=Ybekrm3+d)qjYC&}ampOw1977)n^1usOw6;=aZgfBKQTxa=9EUv*JxyKzs zzz*rJ&;W3zky?)BA0n2E@c_dN<;`YEVHSqEY*=UDJ-_2~W85}N8h6`o% z%;0VI?LuuN2a1nVrK~*mE`8+Vp!k9la9E;(R8tBm5k`wI#R(<=s)S6HNlKIni6=vp zMi>keTqb~TAd?E&O;}J3nS+@`sKPgky-fZUls0p`;+mrfZ0W$(n1W)7tn@VwQakZ= z#gPmRq(mOwB4&yG7>QT}S0-++jJ=;d&la{Yz!vVpgGad>lRLyDK3l*|Sil=&X3h*o z2iRr%r$6#p=GLuSdwY8y6nyqCn-A6p#Z}>h%3v)sWex!yy{CP~C!B(kpeeYBudOm) znR(puILmcPru6u6s_+zt8C5_%LrXtq$+rvzUsE#mN3h}4+N-h13avIu7ar@ z)G;TK%n+Io#YB@0)_C91EL_Z`9gMok^o`|6P7PH?2_|G{uu>yEPCQb4DWwukCc#O} z&bgRmf2mEd?dIaao$~SFJrg#Ea02(>lW+TUE@2n?IT!i6w*IYVppS2NX5+ut=PBYg zd;wPQ+Bbghch+(1#QNg9-0bE4P8{yN#R#k1q-9DQL0aiH?(4nsFhmwCXssk3TG1SKVsLzE0vAwls7G>#Y< zlsZ!heMOGc9#^KuLmk-6D&)c80;W`nEGQJEGCEHRl!D77rkuUOYbYu61I2f>z`f{Y~9akkYC<~#<)H=zhd`Ezxuu%+($2rQH zGCmnl3Pni$M8eseFg>1X9+g-EhJXxo!qO&2Y7}CIS<&ETxs}cuESV{*M{2D&)82&- zw|5~8H@J)rs7*eNd34(Q_^|Z1^p6gf_nRF3^#?_7v z1v*NfG^k?QSf91=tsrKp?1XAli%NT*3(;EBT9en>St-S9o74+hQbDztgpyEKRHaCf zqU%1Bz1Z&FT{k(Sk3;p{SuFbY65av?j-Y?1oaWtG{q&B+RsGg^z#h|-J~7)f?@S9x z7GuU=_l-aK)8G0(hKzW2>rIYS%}`f73|t!pPNVnLa?UbY&%~LBHEdxIs@Vp0e2C5_ z_53f@zxl1Y86JkS>f>EJ>dG_GJ=@pUu5&x`{2x;P$`AA?Ik+*~R*v38&yJmy%4Wrb z$X#PU)0gxxSK1`O$~p|>x^kO@ZCH4s<6w`op2aR{K$e_DJv7}e%8iZPUDhY5E7ZBl zNJrKxM^R7SE`3VA8M3F{qDnDb&!S-vN{<=IO)~IYkv*LuLdX6~4V@ zllixn7a{&LZhwYGkp+2|c={mo+V;l7yANVsXUd}9QkH6)9FBW7*){iGKDz$xi|}~{ zA+%J1E&+6bRiPj7Ej@$^NmK(OdK3x~e9ew=enfXh>JhNS5Lp`Snl^EfiDSCqa96sg zkB-TXEK>bnmH+@C07*naR9I$HDDBw`&n}*OQguVEh)~z`LM&J&%bY80q(U^v)T4#* z+zRJ`VWL|`j3u7FOdyQMFjkpcDv>E3@=-<1KnqUDHrw%)30NeZ%DOsBydCt8I>T6! zQ+h{KGf^+dwOd3> z?_~aPp8XI?eh9FBl$(vqB*i66iAE?iY3i`497F=h-a3S=uHoWrad6=>xzKo|Ck2Y5 z%7~CNKA}dX6eDF8j&@XYI-H|CN26!<&#nw_^t+AH1a|k@Gpm!|HfZf>e7Wc{QDRS3 zH9^bP#k>iP3zliJ;6w#9gdwHM8%bl1OYzAxRVfw)v{IsY@j?lZO|#`XrG8o4^^e?4 zy`#E?JO!7t%Rs|arFf)mKcAK4%(*waZ&kiOGN=0U3%+j+R=tFmpoS}O1zv*-XyNHE zpI*JZJUdzr=UeX@Tq=7NFYNIYb9Bo7EUhRoRu~p@xx~*f>)^4rsc{aw3k^5nXRCj@ z$)DYx9m4&gdsetr!qNHZwj3@W!WsNv9`^=oI0BiSGml5SX#tQiB2n{mf|Tp;}#ZO8(kkk|+0wQ9Pc>LDxYklOaHHGy$MjTt*-Y zs`yND3^ti*j2Sx-{D4zZA_#!M4^>>rpqH$V6hvV<<{y z6Y~Pz=w>tY@iQ|a7)d9WSo7pGCl%)DD>KToNT}cfWgrV$69x(lWr{`;0YMZiBtv!V zP{u}wcfqu+%_rG4VP({TMv*m9u^!Fjn$RPo0>jZ7qY$2PKjTq6B_U8bc9n(DWm2Bf z6GrXcc-2{GZH|j%y70wlB09q)(8(?YGa|IJtiCu-(+zbZ5cO zKgsS6tmhu0`(tP195CHJ7Wg`{X^()NHFpJ79`;m?Jx=QK2(*z!KTP7<6bT^bMWE*WwS0NDm}W2n++7a;U5LgWcr@m z5%ZJ?V~ToQ07U_ZRJaHkGtwwXe+*qs-#^1fk3O4B7RD6GJbyihnYJ1A-BG}%-sVX! zKPt*0jvft)j;3LmESMo_$|Vd?8$~F7b}KbZNk&poN}0}~Fe$5aI5$yd^Yli@I<;h8 zuE>FGY<8v&ZA-*LP6(K%*yOEXi3Xa6-I`jbTObvsq5(;?N~%yr{iH#Nuoc!iS-md+ zKRE#xK&KZSlUl2rW3}wDK%Nh`54R5)tREeY3xP6r)hHoR0@hPIVnhXTWWn#Td^)c# zoxhpn2BjjllpQRQM_Y#kLdb}r4%5l^VI;0xqO=U-=<2X12+e(8HTjkOE^bTgL$m06 zKlB!y#X5xLq711np~Pe&Nj0Ma#m-Ibun>KI`!dZh{g7}wya4BL zYYtmHo87uf&DkNW**(Zd8w+mLR-$|Pws*^pO{Qo*Y@9lBV!tBw#qEi|(3-N!lB(R9 z8%9NW&Vyd;RduVjN#)QFn>|-Lsj9RT+4k1w0W35UyTEGfyAL>z4U|MST!SRMqs)tb zwaFBFj3ge_II3u&YS=&sWRQZZsC$Z$Dtt}OR6-Oqkvz3fAY{t;B{DRU940tNG6tm< zyip(rfUFFeT=SxGujcvu;wLX&tlN+7)YrWocb(d>>IS>KN3r{E$l~M9g8XE(Q*&v% z{_UOo^g3UKhwV|R4m%cAVnywbBioqVcHr#?037?hlm@jeCyBRH&{TB*Uysq1?$M zg(Ubyc7${uM~s5o5yLXM;&A_B*X_MDkT;W$Nna6- z)Zhh%xu3{LqYysfjS3pU=jojDKr})_lVKL`+d$E2eYXY=tx!DOFq=~(47Q3ZQ&3M8D+3Y2A@yJD!Z*4*U*7F+iwbbL^m9BhLV;uA8-#8S!0F&)GuyO*voVHwCW~&vr{8n8pv>Zt1T~z2 z!fUgA%?aFs`|ykK671lP%gIUV{rZ%@@)Yaq>swnEo(ID2$PLwFE-zfbaxVYv!YQ1= z-drne-gXuv8oQ)HuI|*kVSo4FnSR@3*}5F@C1-a=^YRb*;o6^Hyb9Og0@5f0+2IXG z5SJ??^<_OQ)l4sU`np~?300e@2lhN7T-njvF^Ap3wYCbq#pXfSHZHU&I$v91a5AZM zCD6wyzfN_gOWZ6(j(9gY!{r)`GETm3YPUD=)im`pHuB3Pyy<7`8I=lJC07g`7kk|C z9P^^T{a{C*4&~YZpS?GGwk^BPynbWMxtiUa=5~3rRVq_yrAl%mH*$lkiNbbK72t;= z3W$mdAOZsa0Td7X2k-_Dyc7_@17HZCa1|5*6)s0%*%G#~TxDC5CD|z{rR;8dy1n;m z=A2{ju-3YHZK>3i(v$y6 zW-gl@V3V--$kcH3hvoPu>&bdjV7(VB33VVlgAo-S$q}F`MA#`?B?_V(-q$t9|CFb3 z!{h95a0fvqQlJte-qXNPh|W6G*QijGF`^wZ(Y6A8&$^|2fM;)C^{?^pch~!Os?d8A zO^@1cUl>b_Dvi{A>2rvy&Qqg3Noiu#N~{+#EBj!cRV+tw$D&nZD66V6Y740!$Qpu5 z%-V(PuQnh2(ZA?tmp+~jd1|e_0*P2H*n_FPHIauV_`&h=ySRiSN=5iBc!a)<$AGJOs$h)Nx+!! z=@*N{6(K?QhvynkVFwKlFMi^R6V=o0c0WG#jHI=W_xnDn^*!8&--I9eF4tIl7+?>Z z#nIn^t!NB-2Cdj2d8lQ5{*-O7Hqzv>j=@%p17U44l;gc7^(@s8$5p6z!+5Q>I`ME- z+)_3>re6=MK}#C_)<)m#%E?+&UDJ)%H~MfT><^TNWN4-h>;rW{16J@FgT@#})&Z%Y z&7aB#4GP$YBY{Hqy?^A~@Wu(C~X#zHdd6r9zDl9?YMs#AFE3hK09<#(` zCqNWRi#hMqTs_OWJ>N9v$6ST|3**(g6Yaapkxc1JKfLu@<}r|t8@bH?Xd9l|ea~N_ zIMqmI$x|+n0>KF)PgvbnpNm28OPEeKoTg&Q|N z{K62+oeST;_PJZ<=bUv^kq8GT8Y+Z(!Lu5hny#DUd!`gZRjQFJWL*qIAOVpX7{gTI zjNpU0Cb-Aj6z9BAOyxd zPiwf4l^Q~zI5`r)Mr1i!k*4@{bxOtdD24W^%Z_%HwtY3!TtxhUR)m2uVw365Fokgn zgeEv-5?DhdFc;#O@S5k@GJ~)`R+VVXyJLd2lwFCw;~Y&a1IZlg(EnEufhgy!f!0Y zei6Cg%1Q7-Y3JTlZqbZZP|X7aQ+k_b_BAClIyr)} zFLR$NLSNGsa!rKNQ3tv(GexHqgi<{wV}@B0o0{h3F>}$BQyhJsfom#uAd>-5NRy%d z8vC2*%`aEK|K;`JBb(i(I=0JQ*B$Dy+l`$DlhAh7Y92S`?`^i5JJ&x-9WZ4-5``GC zf=x~QLR+13bUo>doBBR^hrp_$1oqR3098_;-!o(?r8v1p653!j+sunAe)uER_U65_ z*YDhYE%aS@^DdpRiIhSxM4+YgFv&;>YN9Y!Gu~;E{_<30RHTV<9AT2v19TLj9++|f zHO(0Jcl&@!RdD9;nMYBEKHDJ7r<3TgW5j+J=_G)ZCt#R&zA6TS-#Xu*|W zsNvNdH9Z;a(HVv1iZTSl*975MZap|7SF{>{m0<;K*e86A?y(V%SR{^k1qBIcLIYlA z0O_R5vQ+;aF_J} zf7y6Yv=ZM9F0Y?+)Ud5Nsu$|KF49z7{+nj4FQ-u0rK|a9t}*>#Qp6f6tZc8s`^E+}O~=-pRds9B(HHy>5ag zvl?rUgH=AZu}iK;G-W)BdeQhvw{I%HZ}eK$xy~X7^GxnYZCQ1-3DS!0s?hd%y$x5p zc(f@}isq&thfNhP24w@IkQ{SDBuxM&rJl;5h}ax|r(r(*8qpDPN~TegV2Q9y5_lqp zIltHxo04{x8hf%{`Sca12nLPJkNByovkCbAD46&W#wgN3^*r(v?Mt6I{Zu^vwGZTX zik4w}JmxrDUN*yFNU5spb>8{!|NOJBuHV|ef@Z3k!7->48d)D$D~%ZA^{Rf@=Uu$I zM49I*>qs5xoGkP`IZ`RLPy(@@4@wU_z2Qfn{fuo8z1ahEGDD!tS1jUvJ!n&sTT$u&tnqvrr@@|W8zNr~u z62N?}pYmc$pQ5SdE3taQk^@w>sdAs*Ynm@%j$IU+J|nc|u%p^>7q8McNoQv?@=Or9nIVWRC zh9T2H$~04SL?9^^!8i!g(XJVUDv%CrMzo?tLZBGY7V@=IO(7FPs2K*-$Q`B3b`}yj z(Ii64>O-twNZt3|OJCvYEg9~u@^PyB)p(hMgqCxqDZV)D{(wuwp`rR~wxIq zy$**RR%rI(U*PJ`A|`O8;2A&K{KV(qCqP4CUKQTN-1^Y?qSGBQMzs1B^n(iSEYmk zq1BYjanOCSQZgvbls;#OqEr6cq>|Klq6uW54ewcq6?K~N4UCi)o8qNGN0QJ6hD1_i zqK^j5>7A(Wg2$uGgn{u9m}#1{4f7+iTHulOBMQM(W7n+SPxWH1fAI5soQq$-UX6Nc z`SAO(q}q~u$)%Eq_rmA@>+AAwJ$(9u$VhZ@AqksI4L7WU6Y6u)CfHr}Q#MN`o^TpS z#?G*WC~L>TD(V^&LZtMZ20n0u7jG4I!|QLY+j<=HwXtDm#1-ynzyg}ck(_7k&cO~K z1g8XiAXc+gEh}QgfER*;;uBvVys(1N8K+LCQj}N#f*^%aW|`QQ3r(kg2-9e^Q5P~q zqpQ&}0~dVB^O=rks>w>k47Il_o=MC;GX@G&LKJ#InJo@e;T$KIn2(YN6RJQR7)KIp zg;Jo4uH!oY0pWRVTJ-p=;VK~`V}l1mL7lKg6D5-r?Pki?gxZ?w@i1jYPbE=J8Hpej zo0i%bp{l7zlFj^xesXV2Gm?Q+7$Eqf{4F<%Ckob+^`yXhPnTv2luT5T(K>+kSUpEX zRDud4G&R{#149D>*38MRO+{^BbR)qCMg_SnFlQww1;zr7Ml2I2H1?ER-x3T9h!Kst zMxLTv7K;k3PS!7^Y|3jitf&CbaMZ zuvoJ8i~H&HF`7pASm8BQR|6cNgDo7vU3dtmkl^!6z|Cz=|H_gc{pRm{;{P1S(JYGQ zVOV|P!PQGQ?UvH@Y}>`m8)1NF7Ix{lywdb0y7;9B-X;C)7gkoyGe^G@$Dhca`_SzC z$WqY#XMOW(RYs?6g9`Cic=NkmU`-O-jip<)xM=QfBH{?OrI@=H|M!}DxUsZe3 zQH*xwVZ%_u8oAmuOk_z4qcRLgqzWih8IM#xr`#kVPW>@Mb)GfN6^)a9#=9pW8l6%% zj6x}NN`%pA-W6Bo<}FK>i7Z*!(~Q^D0IC9IG3%|C*iBhJ#VSBXyz#hJi3LI8kPL`y{9N8nb!qQK4k& zNTY05JPj$+XKY~YGpI$Jtc(*EN035Nu57+Pq@}_7gfjYMJt?r>>tz{T;(W)VC|U_u0C?5?(cpZ*lhptY#gB(t4<1Xo% z<@K=YYU-`uZ+X6E^J%W%A>aP)Ux5wWSYV_R0%J3|^c#*2)P}cyb}_CiZ(?&N+40y* zs~Y5ij&ammQQmHvlYIC9a@Vrl4(EqPDRD?AN7i4&xb~~DKjr9b@7J1fh~iqXc7KqV zZ7W&Fa2k}HbM#X3kyV|M2d5F-s6hiOrx$W07_Q_%Z&cHye@|{0Cb+223QG*+LRHUG zYZY`3P6+cOEC~gUCM~K!nCr7<;g`0L1;~QCjbFh|}kq#XRK8NqP6NDR)BPE`2FI*2TYw8PK?K|E%Cq}}ClIL8S8V*7*$~af4 z7(qT`;lq-*A!8slxxzEKri`f0;m4B~s#I8+J)sD0P$2{6*fh!M0F~p@^ue;ba9JEz zQ{J=BQ;O4e$YtTM+q;p{4l*E)#?F;u%Uq!o(wqhzGDCngQ`F}^qaji1Qi{WdRZTy# z8%d4@s^We)7tt`}*c^`|mS{(ACi0qYyxeWq=CQPtM2<+IsTjv)!ZF~A_pm_^0Io!# zayAtwPEf9d!LXWYpn)9b7AlVT42IPyqoBOYzVc=8=E+*tH;xPZ7dA}r$@&*v!7`;~ zeojkdjI)nq5LTI5P{T9YAVwTXibO0>8`0(s9*!wBWM`}>9TJJdyN{W9Xr@C14ew5m zhrp0fr>u|+M(PQHwSi!Tb%i^&pv*voXaxpZw&Y-QlpkUacO!`FkyL`xfC6o<8RscF(eTk+Vf@ z5Rw1@AOJ~3K~&%1+Fu9YbvT9{oInG=3D3f_U(@-Tj1SwzI64UvmmqKpt`w zc?pr5oOQZ%iKh~6gghcj6M5f{P`;nT>z5CHk9(iw;TN~v!{h2Q6e+L$>3Pm*4j@ z_QFSKBey12r}m7|Y{za9&?ZLPG)@?Lvf`1p!W{z9sf{*p<0E%VPCj2 z5;u4ukD&D9TmzMk04b7{pjToWDF=LuGC1_8;sP0sNQ5#Yk8?ncl2M_6Itj`Mc}6x& z)tERAZUm^Ophv@m=%{1DAV|Sv*4`hT-1(1+E_r`4GudA*1(Ze@6t$W7pp*A}#1&D@ zz-JkZ60#|Qb)*6_#-4uC&5gNVI`AAQPD^vmb~?11q5@yD6r6sj(`Z+l8gfo3XsiBR79y>Ym5f zdspR5ul#l2@#lW-=f3vq-&RucckzRKgZKT~Z~e1=_|I(2^B-%0(Pd$_XLK*QEMihuNlfj@T zC>j}*s$dl*Q#;;}(;Qclr)1}bP*aZC+(@xW)zPm1%FXR=-QGA#{b^HqTs?Jqy?y2T zUbs4c|5{T{v$>hffJUp8GUMh(d+<_4)IEPF2Utq1o_fda6Z<@=h?Gq=NjnaNm`aht1<(s?BzJs-b0;dpQw-knN zRmgRkj(RAit@Dr}?UP4|Ltx#F$(pSmj+=T&RMpBPtu70L$#FR(lP=s`=|kzOD0#?T z(R!3V`Fc|dVK>Cpz;<2V?X5{#OpLM#aW{Gq8naas&I*{|Mph~qCs?Ks;LuaosN$2i zeag?Nz=(+yC`Og(DxwfFInKY91l#3NJ_w;976{Zn#~Xyltj2jMp5?IuYidicm+LGn zoTI5rZ4uAtma3r6_C;?K? z!oJX=@4yrudJ|%#%gf~dG-T%_p{lvIq7<6QiEy%_t=S3d0~ZM&IlJI+!4Qa!6rS#Q zqk)1H#xl<&Rzm1Wlo(I5u2t{~Pk6))4|CUQGJZ{9f~l?wfwJtz#0f?g#PB$~JEv2Y zl?3jDLRLfxPMCgGnH13pJ!*67O&-BZZ8m^UlY_P3elt{*$pvNTV%RERMpe(+QgB3Y zotl!P$A*B_h>!wRh1NU`8ZY{*L-uvuk6kr%shz+!R5XrX-Ddq6ZN`%KO>^D+sAOo7 zK(sk6F;rNlR7izZh!ZQ0Ax<#ZLOPIbo`%>sK{25&bVixl5DEoPbV@MR&@a`M{>gY2 zK3)g>@dOpWNALK@|3RLt|DOx2Q1FTWnW<`2v4Bi|_Dp5C5`@O_j-fz3Gg*pIuSiCf zQ6~vjprC>1WC%`ZDI;DO9j&oM3G)PZF2oEGby7kEHHzWR8q8+HL$0u%SY}&s%ml?C zDw438)YA>vgZrQT_^?{VCZ4TN#?4Fn2Y0u3zHtBQ{1b=!cg8#U&e^bgm=6a@Mvy2b zM*7uXe*N_5GcUf{{@4%PdjEru+^|FY^2_`m{~7=4Pr=KebXTN%JcMT=evNj+JPFM* z;5C&XSBn$sWVWu`Ysdp+3I*&KdN$9%Gtk2w_#x=$bk;ksefrmb>+0(ADj&&ZA66z8 z{UY9@P7#S9p7&Gwha~=$uFlFe?-FQf75?H!|F-H>-DEX}_fH z^M~-)=x zgZfw+>2vQMchWbNw5(UgL$ThyQqSAUqE%UUZNY3sV3b2GwUyBOx>}tdh8Ti`EbHxA zKdzL~Y*ozSYH-gLMN=%*m6cT!I$)*qVV%IaW5cZ74npiv7;BQxHY^h;1fg=Yrpl)xXa38wk)5CIsbEAaGF#uz};irII6F zu!`e~jnTGH*jB99$iVJ`G}0Gj;Of9-Hxsu;NEvG>kVo7X0D+JT9*)FMS*Q)MnmJFN zurSL`=5r+!YH+g8M6PMdKnoIZBj!2EaNgq>DkQ;##vzj{yiklVbrRETS@80%(R(&y zDPCrNsn@uyRZy8jA~m9kJfZ(0QV3JNp@S{Fi4au_LO4)yaU`S0^CMjs^yqLeq|Szt z!|Gz4*UvJ1iG$ENheTMlg%4DRv~G8Ou1*c9NQGY57oyP^+M*5Ph!_Y>qEfWbHIP^@ zWWDKj5`~SEYr2fXSYc^qwYk#Ud=HW3|Mn-lo%eX*@|}I5{Zr{M2I83(oA^w7E2UmOn8MWA!3FordUs$2AL~r(2nR7 zp;IcE@1hD}>N6Cg5C&=?Pu~*qfUhtmK&}XZsw2Xv!0fWf0xL|RC)O}BjL6h~NHjGe zaVOPfJbLGyArELB^b>cp-2+mvYE+BaQYGSI(bY{SXLx+j$Mep=dM|zIQ?K9JeDU;F zcO<%IPJpMzs~1D=BhE}&gNRx1#=17Ct3 zeP;H9_yeTSybL2&xcD5$&lB#$bCa+5ckk}re)FBZ2az=H%gM=l7p0^Ur6k*&?e)#q z9-KVO5A5K0aamo!6*SN+{#;+Oai+Y)3At4-0!Uuyqb<+Lm*XJ+G#oCEKK39Ud3An> zAH>7Hd`}#H%iWXE!>xAxX1e-a4&V3PWMc7HZ8~KuZkF7i=ir`ln%K2?3WIq{+ABWf zE?A@wp}HESNHQE_QT1*Ed`q@wo>)ll%kgsU^PVIC6&>1a;bQ zENmm8rc<dGbPpkf32qxT21g z83!s8)*{d85Hf*`C)wO*RcY$f$})#JWg3PIiZNRdY~f|;q6bQu|Gn^7CbSj=vQb6c z8HFmNkOK|_m5XLL_*M};s$NiCOJo5Wn%Co!I z-`Gy9{B)piIo=_wQ!lvOiZ&bhV<8DEqpoOW225-OSz$nhCHflgDFb=U91JI#`gZ6eO5I$+2iDId%`@SI_;kpFjWP z(Y@0)tmOaw$)EY9r(XT#@A>FoJ$?RFYK0dTSIidPf$MM+x&@KtaLl9L!^5v~c*yAy z+v@=SIoMY5Qo_=UWY+K2CqyMz7P z-wV$!c)$qv;1$>}N2H4%3+66By9^IbZIr1maJ0L<|J{FOyc>qF3mc0cbh1et%iDE~ z=iY4OPS5ROb#mbk{_l7G$@e||lkdSrH+jmQf;Sek+6V$~)mYJum8jZjE|RUS9*o0r zs1K>gkk3v-(9xP8=T(YI>NMN6O0OE#p+E8=ws{zjb}4Vx7t*aYq+Myutlbx42(_dX zE@X5anzB0NQmLNhNZ`r~7U$DAnb};ah1|??QJx?~5hg-YMk2*16Y<-TLK7JvB>-;` zW1J?{ro0xJso=J_=F5bBfGqE}=0xTWV~}@?0X0zKR2GbAKquTctYoe_ijWd*HS0AV z!AJ#3w1I0k_;c5I0p}8G24UFimy&%ovw0(UuFjftZ7dwFsB2=&@tQ_y2ChFvtt6+t zWPd`qOP`1FJB3trAdjes^D?%jK;?TB@#65b%JM+0y=4a?JdP>CDCjb0y%JDno@7yuzL0lsq>R zV?x5*^q+*Qu+)NL8?h@bbOCRQ%ILZ>J);cOb-#V7XLr`=;&6Ajwd(0gsf2Wl3ZALJ z8_E?HNl+(9^cDRcg|NkBHlIboGv=g#?HQp`2BRpR=kdt|IZ*G3TaJ5bnWHp^3K52a zFBIKo88QBo)*}42s-nlg^0gwY6=FjO_&|142^FCr9s8D*kZrb}Owk__(SVKkxKM?9lF|t= zQ$>am3uK`-awHqAAU&1inO=y2M$r+0tXLo#gQLt(4q0I%8j+0cxVo#lUgtv9I-og5 zao5VK?1RNX)-uQ5L#9e10kJTKAf`bqXNy?~CFL0UOFiFPEl02aFS*iBy!6s1|H|L` z*&qL@zbMO5yCh>egL#L@Dl9bwe12ew*{5 zPVV(ZmP5`*p{l!B4N;?QQb-rxc+MMbB}XBfUIh+==Umz-2}vba=r|+@q(~JJC+WMy zgxTwC>@y+b6-AMOVnoFQSs0C~z=Vc}dcvOfXhU|YsW4*pvrprSW&|ZnGyb4XS;Pfd zeC}~6)f3D!B#IiN(}3vg!K$2`sRGq9Fc&L|;esZ7VjAqR5sc_;Z9zU9q2;-g?%L7) zPy1ODq>Y}wDy3JRDR79CLZ8_t>J4RNZ^R82ap*3QdQG^f(88vdnV5rW9Q38^w1*z~db6e_`V7OrsF{Hxm-R*9O zL}75e5GGOWChN+EvwNG(v#F%)t0E<2DJ5trDGFX`85J89m8e-Hm<9r^VIdT!QSs~% zY2i?)MTQ=v1kd&57vzio>EHk5U-*Up;6MI1Z@l!AG_Njn@(hvTJ{00H{($oxW1?y~ z`Y@*z+`%)UrrBZ}^a!4@-6wTdeg4y*|Gl%ff8*o7eE-3IJPdN!NF1$FbJvWsDwJqL zrKwO!4(7YP)t`QS{D+^mzqW-_xV=m(>WmSe1**8%+qcU;brQ7J%LDvrD0A%F&*q=k zG5)Hqb|LEiLblPlW^zrq6r|G4W!wEq_xew-UV0BMx<~ohzL@q0SO>IlCEhh2`&HOb zuJ#)*ecQFFwkM9cSMA)jE$qssuE$H4=JNeCqn4(s_SsT5Y|Xns@wmH)RipR2kgpd% z?CY@^mDX3ITZ%9UYhyQ%BN~_P8%#=tMSNh_V~Q&4%piEA4Mai09B)9tmE@?Cp>@g> zcAUt9Rx@(}hysumcd7*fP@EvJ^Z_T3nJ$k@S!O^d9V-eMasa3%Gm;~RSxBpDtOux_ z5D+8uq?$GmAJPcdR&WKUr1b1*`cSmr=B@@?jZJpXmpx5{Yp?Lm1Mayt(`0 z)t^2~zU=q=KZdvYPd>FVLkFVHwuh3CmMW29V;~$L3UL~JGJ=sCBte*?21O{5^)clx z5-5Sn5v7b&4Q09^l=*p63dXz*o)mkJs1qF#Mx|=70TV(ZH#2u|ni*p9YZlCAWLG|8 zydSULcr9VY4s;r_NioJfFX{^+kT>zfAw$7l-FbY7>fekwhgw-zzbNHrQl94s2uML9X7kCLiJb+3;0skuh)xCFf`bnbBB~G8eq%pb z;cULwxMP7JSR&TwNS+cBl~7#yvb8VWI-=s%E=MkaW$}qRIv)wnJZ?D#dM=I2PI5OTa68Lh1J5; zn?tlBL!K4U>d|;=peu8oD)YYU@T>3nQ|8S#-#k7({*zLlJ>g*e8Cic^Q8|UC2PGM* zw1tCF4~$A%=0<-sB3K|~@_<#uG8EqN<@v~sGtK< zQyaY@Q+1^j#U}*(Kxrp^i3r&Vi_9+&mELJ9x(<7raRdm?s;+m#FkHp1j3TaHXSYg3 z6fLz%(Gt-Fb&wzw)Lqr3(XqI=5O7r=MH=zoVGOPw1h6c*;xoS^{eSb1e&}N#`q)SR zrKh_Ro56<%`V7bpdxO4k?JHbArC-AlC2>^42Y@SfpZTp{`sM%mzk2=iZzPcrK3RN! z=&Lb}p(|FBiJ5qSEFOv@#5g@Ff?2Op2a&ivoIIP`)nZV+grj8-TEP+YkJ+uhoiM?d z`Ne;qv;X|!Z@=TG;b>=U$6-ga*0_lw+|O}%uW&ijtL~QA>R}p%GFFij2}fEU992hm z^>WnPr_~!5-ep_0{Z)=;`M9{RR+ri8B=yiVltHVeW3N|As=77?!jPsulSdKPvCDBK z17UNKuj&b_D~yWOc$qU@sz{}bBc>cW%E&&@I3*BQlmo#iarV8`HEBQ$T82qV2=ogo z+?^=MIP>0epjw6o=QdX$7*Uu83v3xts0wCCg*&6s>Rj^`=%;;1gJeR%lq7gYGp$3M zUPzsl4 zlq1$*{+wPICZtnAjc(6IkuBlC*pebzu8?3RF%lb6MvPJ+hB_sYE98JUQm{Y^2O}Fn ziPgN5a|oFvh@cZW!)7m)DA6HMg|E0=j7n_q0SQRPBNP-Ypv%o@PD<99YoI=}G(uO(Y@-uH`GkIcQ#Id$rF zU;UCuf}|*jkZcK-MuJBM{O8&OPizLIJ;_Rt&A1%eZP-hKYP=@`SRn}<>r?@OI?gvywwO9(LyCcZT54z zZTF`y+kMWb^Gy%`;j&4}Rz{|;1dPs_384j2(2NE=vDCJ(Oa+_ZyekyO(@<(l6qTp( z_IBOcrh~r%xc5&m_x~44uTQPjrv~e9Qla!jN+dEIHC0b%` zq{`}O(2OH=A=+;4UJA7{8(#LzoE}tTD6I4&kvUwj%$TTy2vWj7T`4{q)cO^q>FvAO3^C^ON8EFK%yN+B zc%-IemzH#jEJV{lhZ3nL)DMZ%tJhCCgB$DM862M&+*iAt?r#v){QKok{!acszZw6i zx8+c;qkJa*m!ZFBa&^fc{*3p3|Nn`t|1H)v449z8m+;F>z?Ai&j!bE{UN3UrlILZ} zutyaU9r>a<9@ksseDRB1N34@~pQ4?47**YGnibU#s&-$Oqf7JW*YWLIMf5(2-sdPV zwsAmZTCfEUoICRj2|MG;a3&Z+3giq*--PD|$^zN3L1`ovrU9Gs~1jt=r5W_(35k$z2E_!a!4Kkb_h zOdbs3xPb0}z&1cAgXJkV%vU1e6Cun>B$BWSBQp)m@TUOyT#(ok$&wO{m~o+YhO!po zky&V>_9hi898A2Q#?jIxr3$@sld_aH56L4f#g@>c$U!xu7A?7msfTIF5oqX0^hlGE zT#86aDI`If!8F>?tG~RnZ~uos|H*glaQvcL|LTwa^t0#BrG9Awa^U~~AOJ~3K~&D? zKN;75bocY0|NNVO@$O&#v-b6$HF7Ddy0|C3ytL!n%g(5r#r^Zbtf|^*jiC>OQfXc6;5-YSV!RV4~m_cP!Kq6L{_q}Rw8=gIs{d)E&k8Qum}f1h3GW!zRP~6i7YsbL00sH6sjU;4h`$H;P4XZSVv_S1C)kt zZ0ke_Wn0^9y5JMOC6G9AW>j1VArEvLgbVJ3Ku}{RoqC{|tIl}I{l{|tHUY=u<>M)} z7K!GPhiZz+%P^VBu#i>^Ei({v7^`^7s?asLM&QK=6G5gVuHi`~bZDrgBodIxR*vOz z*FT)>-T(H}`Q3rMMQZ$ZneR4V2de%C09E+YcSeJ7>U`h{iW-NHdY!6(a z6Jv+WpPdA&?ip^XG-FCwVq^-egU}~*fTF=v_aR8jq|MRqjIxWUELamck)e0g2=L5s zqH=5uBZRC-qfCeq+s51wki@V(Vs2*B1`>)7QbAVA7Fg`0Oh9_TXKE(eE-O-}Cfu=w zbPo@m$N1}V`{S3Hw*Nw^UdoZdGYGMQ$lnpom%veK% zoVSzW;|Q0uyA->DAhD+?+C;LQv7v$}ViF->R*HeE#zvJeM&pt3S3UebMPI+WKq4z`6 zDf>f~DKe$B2LabjI<3jLx1vzZ@TzFRB}bP z!T%k$NkRkNKtYAEon}TTIpYBfIq#z>8MH>7u|b8{3|4)Yt4=`{9)doT1nIlBSA+fy z{p-0LTlq4-{i%+S?$x4s3Y`ph*Q1kbb_^|~gx6uS#wE&9T-^-O+{G19&lV6a5~eEJ z#fwQ2oEUlXtZ>DLHzqRK!#JHzPJNTp^yYl1=kt&L-sNBYU+23wt>xa=RkAXPEbb#w z!c2V?ujVb2#pQBYAs8j4CN8Avd1Zu_j>M~%ynU1=|<=is%chT$k5l~7^6?pTE9=!yE--?_;F4jGpgUzj(vFwd!S^#^loiYL zwBLiL%`o_cXb*WxjNWu+PfO@+hqx;5;HaNow4drzgY`X@Z*T;Gg?O@~4~R2X(V1}} z!NFKNIwK7+bRbF#YN7~_12QGZgHWU~O}lL>gu2bM1xa|PPcW$49;9N8lsG^PMi2!V zyExUA$rwS-%n1*waWX8SgU1u+H&@eVX7>2;U*~o4rmm(#HI>nFNsbIjtWK0-j)@GH z%#dm;LUWSJa6*$|Ij^cJmWFB`?p{O#T0~rV8h!Yr>)RGXf>cv3no)^5C`;uZtG9p+4f{}K1V}W#lmAc&x0FFR$zX}&4&#)qnC#GO2gl*;x{`$C^h7sGCl$Pt1s6`vaWmK!#+H@mB!>wPTsaR8nY6*Q5^HA?BGcL~ zUv8NS)j;q>IA&;uhB&3ugn6c(k->v-6v{3CZu$JfXMgLP)6bRnUs-EfEihk zjCaZg_Vj5uH9>Gr5X3?)(D(iz1n(rL3S*FjS?B|eKl1faD(EhD+u)*r3&|OIr?O59 zqf!Qz_F$u|`v@(qOqn&2HLlB@>eo6gN4rYpY(?np<>B=8xEgYXqXq7d8p-tTT4u@R zJR&uuGW%VTvXH!&S=Y6$2a4bgKk#@aO~d!vkl!vQ%FRpx!U6+wp_&3#d9EK zNT^yG;TMSWAXlUh7e?wq5pfrySeiWUOlZ%C6xqwM zrI$V3q!#_f6@Pf-N82e`o-U*7Z;clC_v~N%&v$?Sy?y;~^`o(LIgD#~pXe)D_c#_4n&3btW;jzT*uFhv zWlV&j$}^)CklpU;8w5c1JiRTiB5+PkkU8fhCAik{)d{M|3&`dr!W4xoTl6B z{`>cj+&guHdM$jMStr`ca^?m~M;gUZX9!-AiJ`~^e|j`#ClzX@HAaT>YdcJ8J$Sip z`lmvLv6%t$F1;>^0&IIzM}zd#4&+KWeRB&Is*@cNJnv*tPjA8SFGPMHn>UqI%?v0G zvC~1bBMB0b(5L29m72+pJCaF34nnxnhpY04{PIvvaeKpZkof58Vqf3PU!Iz>e5W#n z+=)E*crk_?(t0rsyGs&R(=lCt{&6XDTAroDH=o(V+YJ*jj*K&UfZMa{XtRGS*Y+s$ zcX6H8_Stxx?0r47>-nn4?G{_MVNp$RhzWNj7>XNmKo(?T7$kbo44!am3DOo28~bMP z*Zx7rE4^XD`O^gJQ+;Z%zSkc!SO-;zg#yEOI*c?t2-$-;#OZ?OUq^cdU|3=d%1Q`x zqB*hdBYziK+d)2}cqTQ5GIe^U2eMGu(N=jwB`dul2~Tt-ubAUbo*0dK!O9Nb8bLYI z7t&0$RY!d|pS_jPK$m8zNY<+ElMlkr?iQ9+htefxpHFygX@H7G)RE8#3k`;MC7-M( zRSj`cigK(-J%6+%R!J%6qg%k(PCvpehUmuK5%7p6DVYS8pm$$VqF9R1)Q2A$M@UJ1 zPihUd>+6&CI(iGsq2{dV#<*H*vg5oyOX!KupK;Tlj>|1x@cblu|BdUF{QK9>zD?i# zSo$gZe35?2(`_Eha{TV_<_CQC{WbZp!ug3%lqcS--~fwaZC`Xwuqu{VGg?1i^$*^pZ}f%_ zNz(b{Q69gO>El=bFwf&B#vgq5XMFcl7Q;Jt7w%8A3m*atieeWsOcT`^0u?j5;Tc)T zGc`ZOQm?3>hE2z~T%yd@kc4zrp(?hm1qYt?;31u`L9Zn63UG8*qbb&RTwZo$%aO<= z@C{GZ9xOd(d+qUK8LV{&!4E|;QqWC68YJD(oBb(*dj!QPW$98ywB~QKe^D~m$DiNo z#jlRO=Htcc;bV*g(ogf-$B*?&o_owT+@oYqvc7JHsAXd4?Q!sVhP!{fl>c7d3ln-Ylk2ujI`2XdH?&hHR-+2tsR|hf$sE$2#F{_?31UJUEumYiMWYW?ki2g|HsuzD zFy=-PmOu*QLQ<@tAVG83GIVcP&iFSk?4#QyHf$&$>5xR5H|YsHQA7kuk%Oa#o>J11 z+Ms&wq6?aHlvPZIBS};w&F%r_Xw8exmfX#|grw9r9lc9(lJs-roHZ#i+{HaqMZ{!{ zkxRITl1zvpo^%=Bb68H&>jP|-?|(g+GS!gyJ@`i;yu-o2Q_h(xdP_@ zBu(riJpXMx1O|zwKX{g9KAzs+i@m6m+^v1OgYO zogXI7I(GNHZ_lbRKxZ^zC!%nOJrGls6znUk&g#rF$yhg{dPCT=4M;eC1E?*-!PU!TLTc z*?#Qgg;kIO54(m}GHx6P%QjzkMxs7ajlM;xLfi7!j0~5F9K;bWj6f0;(uv-YiDc*? zRZOX8*m6P=UiQxr9iTenL;wfIpfr}m2HoB`TgCS+;VASV-O=7F&yTX4LUN{yq+ZQJ zyqm}noveC_EJK*hBWb938&j6lvZt;&v`0+yH2ksZXoRM;#t^|%87XdA9zin<)}O*(5Xo~(4;ls@E$VHreqWIu?nfzb(X8d*}!`gry1qtWnKN~aakOXOzetuCpmTQciXZHZRK zT8gzOvpl9}k0(*LWNp?;WRLMoG)ejpLp8fYnXqMdm|cV#ZAjT&`IAvDq`K4FLt$WbpK?lJDfOZLQ@i|I89eZ!`B$-w)XjpWjuK1N zH#d)O0glQjT!Qz(3}<+u7?dlnGWC|VF*gs!Wa{afFwdk7?mudcLCcn_z(_{x9O|Itpv9-6YUR@rU>~n}| z5_*s5ayXtNL@*PZE;48b9)ok^a(dQ3{?)TGuieL;zabm#h&3!TUop<8a=u#hN=FPS zBguu=+_dR99{4b_29Xe@7kvK7=bnNV12rMdD_MG)%H(fwo6HTg+|I8g0$d+G02zwbr?Y;j*i_; z`R27hj>VFt-nFO%8fup;VI|_a`<45(k8A0qswUozl-*stA6$l}R3ekRGE9ee{s<2#>U= zhlG1*c;~BE{-YoC&tJ9IU-d7)>|ed$)k|JfUd$Xaws5y#4^Q%TgAec@Vfl>@N&{!` zvS`oGpZT+^Y5FeudzOzWVzJwQZNK%CI6VzFpF@SmZFnNQ16HnbmLW5TgX&2}}9WX$2c=Qyekjr+5csz*=?NiY}z zoz!us7(FP1ly|XEam03%lIi}N!*-FD3~XCr3F5$>S9l1fK(|ccnIXHlND|XZgW{M1 zq}b;OQ{Wr#X%I#zb0!S!gtErILkE^KKsdwqc=b^j7aV#g2GN)jxucEl4E@6o$1mRZ zvgWID{4uA8x;)zHe#j*}O)*}c9_NP#0!S>4OUB`S!r;AQ@cIkB{GkD{2 zfiH|RW6Ki_BJcyNAOq_}$2XKzW>nmDxE{=WG1s!DG6>lzK3~ ziwSNxHeURk>kC(ROc$OF^1GUb9ghz(iT$vD_#KwdIn<7y@fF+d*oinIkEBF8?10c3 zltRr+$|1NFj)kIV+0pI4%5so_Btk%$;h^vo37?)~FF)0%`ZU4%{%TWSZST6ssL~&q zCR)La-1Y-%7S=!nQ!LPiWl4mt95YO~?Rj5wXE-AxN?MRD>%Fpe#Brr=F9t`P11yEK zFcd&1ddCwvFe5=^Ub~VyLBUWTuWuk|epf9}d0V+ zv~m>gPdrXsGPh?AJmp~clQ;WsTrXi<|MT`=eOv#@lD(v>UcU6lQT3a}pWD^vKkL6; zt;Rh(gIf@IfCi7Cn+z-S*J-(x`NO%|<(`8@9|O7;%VR9*UAF`t@#xI=%vbg?rPSA> zT)!t-N`1lexbn0{MD%A%{kD{kYfLqkEWTz;IRtA*H#`5pl(_(u!f1HVFX&9^NJV|8 zIHk0^{LWa>c1rnV*}+?Zr1W+h0C*dA{sD`V_Ct4$s64ay#!uF#DX zY>0z2fs?lXG6@=%arRK>AtYraiXvwyNZ@%NB(LRfo%DC7{NKL1;fou4``70W_Y6{` zt3zs~jjOv~w1;lSbq&^mbcQmF6*4RrPWPyM_vZZ7?Sbd7_}FNL)G5k1GYXN3E&2Go z``V_>*A`6M9iXB@cJxRU zriE-t@(DVlKu4E-D<*<&M~qNb$n;DBPv4cP-lShNO;yWWyNs6)=a2Qh9_ORCbdbAq z|DsKgH!?R}e7u>Z#QOOtIvj~9VhqjL*aW2c!twc^9nxW~dY{J4OTT#=Fx_!o@J?BY z!BFb+cF5?L)W`ExL7ltIAKE=KKjketeai=!$xa=(pzh4;G!6|<(~fd}KDaYZ`)vvZ%?$bUqWx5#CRpENH3LT+*_1Gw{o7H~(kU(Hh0U$8r9uGs6y>@BROv5*zrHTV*@kB z_8A(g$}@)yS|nARhqA6(UHfhnq?B5ETuIa&V_^Oh`EVOrr5~oA?dyH zQPYFQ2PqHgcQUNxo^3UUPVH$i(wTm`8;g}WndW;H@$F+KYZ2K!%K(;CC03WAsLGVZ zJwjq&p-_w*0jOpzxMxhmhpG*)wzlO|dgZDRMsB-}{W^G2d1ZVtc;>`~pPlK#c0MGBO>Pyz1HE+7CuXyFXh9Z{5BuDXHO>b2ROr9P&mQ^ z+#AnE`uL6;Jx#NnA38%G)mO*wO&|LUS!ImJStk=YM$39Je8@_a+^2`}0`S}K!c6Mh z+v(4*>{NRy`fd~-fesGBXt;28Z0rN1mc|xwxOxAtmkrUF%f)08RMr==`QVQiYda87->y1&0UeRsdc zUv6%`-Wkw*Z`?RImv<;t3Zd(~Kdwu?>?t zv~A}KUKt7vHGD!dWuQ*cFY+bJc<~}lMy9@(^yrC`J&$qYyy)>cOYXIbOs~pS(e>Gv zr{_2CU(}RjNoYw~q9(*+ab$qSp;VIWaFfP>fO0$|C z$!D>scykq8oK@s#kvksLipfqJ+C|I|iA4$Hkm-gCs!W?`)kp@7;gnxlVKP}r3nOSE zvldeGNAVJtkP|YYnvbvS2Ni;Lve;!bV=03ZNKL_t(k$IvtCCB*Vj zRY~2}&^(Gy;pVt|%4;_7U|BUHTI-=AYT_f`i1m`Au6CC5>LY79wqi<%dQg&tp~+o! zY&s~7jH>v$CQ`8wl_pul@z5<`*7{jYB_X02klf;u@9(E~8aJO04N8Shf#VE~K6rM` zgLB>}ZM0cWx4q6>TAy3 z{2X~rbBla$W~B^|sr!4fw|tW%1Xid{Yk1-@qZc&o_kp2!a1fGVK|QeAo>sE?FyvQp znIILOuvIkyJIX|4A3dm|Y*g@lt9YF07aYz;38W)RQ$p!mK<}Whq{ei$OR2VRxD=Z0 z*?PVJq)atT5GNKY3$^l0Qk@pMGMxb9D*FE6R4aVLo z7Da<7c%Tz;0VQ=J?XZTJC}+l?PspGrY9q=X02!bSpBRF#IH;kSWTeePDF9=$E+!bX zL|qXlreANKiu4Nr%}E$S;zC_=gd46Zu)uneEN9&ZhrUKkJ@#*I=sDq%gqD*71v`w7)LM{ zCWyeUco*D1f~>pchg~JMt5dD*Y2@t&y&!R%?3V}o4_~yO|A6x~fBSfuzP$%Zy=Xtxrv~eLtPwk<(1wWW|4BFW zo7Ho;zzmnC)n$7SR?X~5n(#_QS$WAhebK3`oNef z_e*uLI~&!rkNMlwm-V*g5bVRoiz>k=yLH_g3aKDYC|R(=aCFNk3=Rn!L?E^Yu57`? zbHO_!?^t$gltOF}O%Q0ZOROX_j3|`E#fXG#al>1XN$g38$yo|1m>_1%$W9gpWMQm( z;-MK@$%3z26wR&-(_lbKu+WUj(8T3J>KtaQyz2Ac&JX`bIlWEKv3Jz7zaQCsxm_II&vnA>4^7#63t=DU6dOfsvVIJ#YOee~_a`U^7@9YPBxI2Au+h5)JN(m%0DXq{G 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 literal 0 HcmV?d00001 diff --git a/projects/VS2022/examples/shaders_color_correction.vcxproj b/projects/VS2022/examples/shaders_color_correction.vcxproj new file mode 100644 index 000000000..520c041cb --- /dev/null +++ b/projects/VS2022/examples/shaders_color_correction.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65} + Win32Proj + shaders_color_correction + 10.0 + shaders_color_correction + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index 81f700bec..44ef64c34 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -397,6 +397,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shaders_mandelbrot_set", "e EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_math_angle_rotation", "examples\shapes_math_angle_rotation.vcxproj", "{84DE22BB-C25F-425C-A7FE-0120CF107B83}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shaders_color_correction", "examples\shaders_color_correction.vcxproj", "{98152EDD-7E28-4FA3-89D8-B636ED5D5F65}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug.DLL|ARM64 = Debug.DLL|ARM64 @@ -4923,6 +4925,30 @@ Global {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Release|x64.Build.0 = Release|x64 {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Release|x86.ActiveCfg = Release|Win32 {84DE22BB-C25F-425C-A7FE-0120CF107B83}.Release|x86.Build.0 = Release|Win32 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Debug|ARM64.Build.0 = Debug|ARM64 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Debug|x64.ActiveCfg = Debug|x64 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Debug|x64.Build.0 = Debug|x64 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Debug|x86.ActiveCfg = Debug|Win32 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Debug|x86.Build.0 = Debug|Win32 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Release|ARM64.ActiveCfg = Release|ARM64 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Release|ARM64.Build.0 = Release|ARM64 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Release|x64.ActiveCfg = Release|x64 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Release|x64.Build.0 = Release|x64 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Release|x86.ActiveCfg = Release|Win32 + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5123,6 +5149,7 @@ Global {2F578155-D51F-4C03-AB7F-5C5122CA46CC} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {1C829D1A-892C-451C-AF0B-AC65C85F5CC6} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {84DE22BB-C25F-425C-A7FE-0120CF107B83} = {278D8859-20B1-428F-8448-064F46E1F021} + {98152EDD-7E28-4FA3-89D8-B636ED5D5F65} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} From 40594f3ec09ac1c045c112422ee4315371d99015 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 26 Oct 2025 10:24:56 -0700 Subject: [PATCH 008/430] Fix typo in font loading documentation (#5308) --- examples/text/text_font_loading.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/text/text_font_loading.c b/examples/text/text_font_loading.c index f1582348b..151b63d45 100644 --- a/examples/text/text_font_loading.c +++ b/examples/text/text_font_loading.c @@ -9,7 +9,7 @@ * - TTF/OTF > Sprite font atlas is generated on loading, user can configure * some of the generation parameters (size, characters to include) * - BMFonts > Angel code font fileformat, sprite font image must be provided -* together with the .fnt file, font generation cna not be configured +* together with the .fnt file, font generation can not be configured * - XNA Spritefont > Sprite font image, following XNA Spritefont conventions, * Characters in image must follow some spacing and order rules * From 6a5b7fb3f8a7e718c91a66aa66ee2f038cb0dba6 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 26 Oct 2025 18:40:46 +0100 Subject: [PATCH 009/430] Update models_decals.c --- examples/models/models_decals.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/examples/models/models_decals.c b/examples/models/models_decals.c index 6fa2bd75b..d445e69cd 100644 --- a/examples/models/models_decals.c +++ b/examples/models/models_decals.c @@ -46,7 +46,7 @@ static Mesh BuildMesh(MeshBuilder *mb); static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, float decalOffset); static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s); #define FreeDecalMeshData() GenMeshDecal((Model){ .meshCount = -1.0f }, (Matrix){ 0 }, 0.0f, 0.0f) -static bool Button(Rectangle rec, char *label); +static bool GuiButton(Rectangle rec, const char *label); //------------------------------------------------------------------------------------ // Program main entry point @@ -248,14 +248,12 @@ int main(void) DrawText("Hold RMB to move camera", 10, 430, 10, GRAY); DrawText("(c) Character model and texture from kenney.nl", screenWidth - 260, screenHeight - 20, 10, GRAY); - Rectangle rect = (Rectangle){ 10, screenHeight - 100, 100, 60 }; + // UI elements + if (GuiButton((Rectangle){ 10, screenHeight - 100, 100, 60 }, showModel ? "Hide Model" : "Show Model")) showModel = !showModel; - if (Button(rect, showModel ? "Hide Model" : "Show Model")) showModel = !showModel; - - rect.x += rect.width + 10; - - if (Button(rect, "Clear Decals")) + if (GuiButton((Rectangle){ 10 + 110, screenHeight - 100, 100, 60 }, "Clear Decals")) { + // Clear decals, unload all decal models for (int i = 0; i < decalCount; i++) UnloadModel(decalModels[i]); decalCount = 0; } @@ -362,14 +360,14 @@ static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s) } // Generate mesh decals for provided model -static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, float decalOffset) +static Mesh GenMeshDecal(Model target, Matrix projection, float decalSize, float decalOffset) { // We're going to use these to build up our decal meshes // They'll resize automatically as we go, we'll free them at the end static MeshBuilder meshBuilders[2] = { 0 }; // Ugly way of telling us to free the static MeshBuilder data - if (inputModel.meshCount == -1) + if (target.meshCount == -1) { FreeMeshBuilder(&meshBuilders[0]); FreeMeshBuilder(&meshBuilders[1]); @@ -388,9 +386,9 @@ static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, f int mbIndex = 0; // First pass, just get any triangle inside the bounding box (for each mesh of the model) - for (int meshIndex = 0; meshIndex < inputModel.meshCount; meshIndex++) + for (int meshIndex = 0; meshIndex < target.meshCount; meshIndex++) { - Mesh mesh = inputModel.meshes[meshIndex]; + Mesh mesh = target.meshes[meshIndex]; for (int tri = 0; tri < mesh.triangleCount; tri++) { Vector3 vertices[3] = { 0 }; @@ -584,7 +582,7 @@ static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, f } // Button UI element -static bool Button(Rectangle rec, const char *label) +static bool GuiButton(Rectangle rec, const char *label) { Color bgColor = GRAY; bool pressed = false; From a6dd08fb71f31be5e68a5fd3ab3f729c51f7a208 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 26 Oct 2025 20:10:47 +0100 Subject: [PATCH 010/430] Update shaders_color_correction.c --- examples/shaders/shaders_color_correction.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/shaders/shaders_color_correction.c b/examples/shaders/shaders_color_correction.c index 8aee1d0e4..c96f606d8 100644 --- a/examples/shaders/shaders_color_correction.c +++ b/examples/shaders/shaders_color_correction.c @@ -29,7 +29,7 @@ #define GLSL_VERSION 100 #endif -#define NUMBER_TEXTURES 4 +#define MAX_TEXTURES 4 //------------------------------------------------------------------------------------ // Program main entry point @@ -43,7 +43,7 @@ int main(void) InitWindow(screenWidth, screenHeight, "raylib [shaders] example - basic color correction"); - Texture2D texture[NUMBER_TEXTURES] = { + Texture2D texture[MAX_TEXTURES] = { LoadTexture("resources/parrots.png"), LoadTexture("resources/cat.png"), LoadTexture("resources/mandrill.png"), @@ -53,7 +53,6 @@ int main(void) Shader shdrColorCorrection = LoadShader(0, TextFormat("resources/shaders/glsl%i/color_correction.fs", GLSL_VERSION)); int imageIndex = 0; - int resetButtonClicked = 0; float contrast = 0.0f; @@ -113,8 +112,8 @@ int main(void) DrawLine(580, 0, 580, GetScreenHeight(), (Color){ 218, 218, 218, 255 }); DrawRectangle(580, 0, GetScreenWidth(), GetScreenHeight(), (Color){ 232, 232, 232, 255 }); - // Draw some text - DrawText("Basic Color Correction", 585, 40, 19, GRAY); + // Draw UI info text + DrawText("Color Correction", 585, 40, 20, GRAY); DrawText("Picture", 602, 75, 10, GRAY); DrawText("Press [1] - [4] to Change Picture", 600, 230, 8, GRAY); @@ -129,6 +128,7 @@ int main(void) GuiSliderBar((Rectangle){ 645, 160, 120, 20 }, "Brightness", TextFormat("%.0f", brightness), &brightness, -100.0f, 100.0f); resetButtonClicked = GuiButton((Rectangle){ 645, 190, 40, 20 }, "Reset"); + //------------------------------------------------------------------------------ DrawFPS(710, 10); @@ -138,8 +138,7 @@ int main(void) // De-Initialization //-------------------------------------------------------------------------------------- - for (int i = 0; i < NUMBER_TEXTURES; ++i) - UnloadTexture(texture[i]); + for (int i = 0; i < MAX_TEXTURES; ++i) UnloadTexture(texture[i]); UnloadShader(shdrColorCorrection); CloseWindow(); // Close window and OpenGL context From cf9a0619ca5c6bf97fc6d8a1c9d3dc63618b0ad0 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 26 Oct 2025 21:21:05 +0100 Subject: [PATCH 011/430] REXM: Validate and update examples --- examples/Makefile | 3 + examples/Makefile.Web | 23 +- examples/README.md | 14 +- examples/examples_list.txt | 3 + examples/models/models_decals.c | 2 +- examples/shaders/shaders_color_correction.c | 6 +- examples/shaders/shaders_palette_switch.c | 4 +- examples/shapes/shapes_lines_drawing.c | 2 +- examples/shapes/shapes_math_angle_rotation.c | 11 +- examples/shapes/shapes_math_sine_cosine.c | 4 +- .../VS2022/examples/models_decals.vcxproj | 569 ++++++++++++++++++ .../examples/shapes_lines_drawing.vcxproj | 569 ++++++++++++++++++ .../examples/shapes_math_sine_cosine.vcxproj | 569 ++++++++++++++++++ projects/VS2022/raylib.sln | 85 ++- tools/rexm/examples_report.md | 11 +- tools/rexm/examples_report_issues.md | 2 +- 16 files changed, 1849 insertions(+), 28 deletions(-) create mode 100644 projects/VS2022/examples/models_decals.vcxproj create mode 100644 projects/VS2022/examples/shapes_lines_drawing.vcxproj create mode 100644 projects/VS2022/examples/shapes_math_sine_cosine.vcxproj diff --git a/examples/Makefile b/examples/Makefile index c601c72b2..c8500b665 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -562,8 +562,10 @@ SHAPES = \ shapes/shapes_following_eyes \ shapes/shapes_kaleidoscope \ shapes/shapes_lines_bezier \ + shapes/shapes_lines_drawing \ shapes/shapes_logo_raylib \ shapes/shapes_logo_raylib_anim \ + shapes/shapes_math_angle_rotation \ shapes/shapes_math_sine_cosine \ shapes/shapes_mouse_trail \ shapes/shapes_pie_chart \ @@ -632,6 +634,7 @@ MODELS = \ models/models_bone_socket \ models/models_box_collisions \ models/models_cubicmap_rendering \ + models/models_decals \ models/models_first_person_maze \ models/models_geometric_shapes \ models/models_heightmap_rendering \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 99be781ac..07f8d8fb8 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -562,8 +562,10 @@ SHAPES = \ shapes/shapes_following_eyes \ shapes/shapes_kaleidoscope \ shapes/shapes_lines_bezier \ + shapes/shapes_lines_drawing \ shapes/shapes_logo_raylib \ shapes/shapes_logo_raylib_anim \ + shapes/shapes_math_angle_rotation \ shapes/shapes_math_sine_cosine \ shapes/shapes_mouse_trail \ shapes/shapes_pie_chart \ @@ -632,6 +634,7 @@ MODELS = \ models/models_bone_socket \ models/models_box_collisions \ models/models_cubicmap_rendering \ + models/models_decals \ models/models_first_person_maze \ models/models_geometric_shapes \ models/models_heightmap_rendering \ @@ -895,12 +898,18 @@ shapes/shapes_kaleidoscope: shapes/shapes_kaleidoscope.c shapes/shapes_lines_bezier: shapes/shapes_lines_bezier.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) +shapes/shapes_lines_drawing: shapes/shapes_lines_drawing.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + shapes/shapes_logo_raylib: shapes/shapes_logo_raylib.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) shapes/shapes_logo_raylib_anim: shapes/shapes_logo_raylib_anim.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) +shapes/shapes_math_angle_rotation: shapes/shapes_math_angle_rotation.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + shapes/shapes_math_sine_cosine: shapes/shapes_math_sine_cosine.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) @@ -1157,6 +1166,12 @@ models/models_cubicmap_rendering: models/models_cubicmap_rendering.c --preload-file models/resources/cubicmap.png@resources/cubicmap.png \ --preload-file models/resources/cubicmap_atlas.png@resources/cubicmap_atlas.png +models/models_decals: models/models_decals.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ + --preload-file models/resources/models/obj/character.obj@resources/models/obj/character.obj \ + --preload-file models/resources/models/obj/character_diffuse.png@resources/models/obj/character_diffuse.png \ + --preload-file models/resources/raylib_logo.png@resources/raylib_logo.png + models/models_first_person_maze: models/models_first_person_maze.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file models/resources/cubicmap.png@resources/cubicmap.png \ @@ -1262,13 +1277,13 @@ shaders/shaders_basic_pbr: shaders/shaders_basic_pbr.c --preload-file shaders/resources/road_mra.png@resources/road_mra.png \ --preload-file shaders/resources/road_n.png@resources/road_n.png -shapes/shapes_recursive_tree: shaders/shaders_color_correction.c - $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ - --preload-file shaders/resources/shaders/glsl100/color_correction.fs@resources/shaders/glsl100/color_correction.fs \ +shaders/shaders_color_correction: shaders/shaders_color_correction.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file shaders/resources/parrots.png@resources/parrots.png \ --preload-file shaders/resources/cat.png@resources/cat.png \ --preload-file shaders/resources/mandrill.png@resources/mandrill.png \ - --preload-file shaders/resources/fudesumi.png@resources/fudesumi.png + --preload-file shaders/resources/fudesumi.png@resources/fudesumi.png \ + --preload-file shaders/resources/shaders/glsl100/color_correction.fs@resources/shaders/glsl100/color_correction.fs shaders/shaders_custom_uniform: shaders/shaders_custom_uniform.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ diff --git a/examples/README.md b/examples/README.md index de28c931f..4d0b25d56 100644 --- a/examples/README.md +++ b/examples/README.md @@ -17,7 +17,7 @@ You may find it easier to use than other toolchains, especially when it comes to - `zig build [module]` to compile all examples for a module (e.g. `zig build core`) - `zig build [example]` to compile _and run_ a particular example (e.g. `zig build core_basic_window`) -## EXAMPLES COLLECTION [TOTAL: 188] +## EXAMPLES COLLECTION [TOTAL: 192] ### category: core [45] @@ -71,7 +71,7 @@ Examples using raylib[core](../src/rcore.c) platform functionality like window c | [core_clipboard_text](core/core_clipboard_text.c) | core_clipboard_text | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Robin](https://github.com/RobinsAviary) | | [core_text_file_loading](core/core_text_file_loading.c) | core_text_file_loading | ⭐☆☆☆ | 5.5 | 5.6 | [Aanjishnu Bhattacharyya](https://github.com/NimComPoo-04) | -### category: shapes [32] +### category: shapes [34] Examples using raylib shapes drawing functionality, provided by raylib [shapes](../src/rshapes.c) module. @@ -105,9 +105,12 @@ Examples using raylib shapes drawing functionality, provided by raylib [shapes]( | [shapes_pie_chart](shapes/shapes_pie_chart.c) | shapes_pie_chart | ⭐⭐⭐☆ | 5.5 | 5.6 | [Gideon Serfontein](https://github.com/GideonSerf) | | [shapes_kaleidoscope](shapes/shapes_kaleidoscope.c) | shapes_kaleidoscope | ⭐⭐☆☆ | 5.5 | 5.6 | [Hugo ARNAL](https://github.com/hugoarnal) | | [shapes_clock_of_clocks](shapes/shapes_clock_of_clocks.c) | shapes_clock_of_clocks | ⭐⭐☆☆ | 5.5 | 5.6-dev | [JP Mortiboys](https://github.com/themushroompirates) | +| [shapes_math_sine_cosine](shapes/shapes_math_sine_cosine.c) | shapes_math_sine_cosine | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Jopestpe](https://github.com/jopestpe) | | [shapes_mouse_trail](shapes/shapes_mouse_trail.c) | shapes_mouse_trail | ⭐☆☆☆ | 5.6 | 5.6-dev | [Balamurugan R](https://github.com/Bala050814) | | [shapes_simple_particles](shapes/shapes_simple_particles.c) | shapes_simple_particles | ⭐⭐☆☆ | 5.6 | 5.6 | [Jordi Santonja](https://github.com/JordSant) | | [shapes_starfield_effect](shapes/shapes_starfield_effect.c) | shapes_starfield_effect | ⭐⭐☆☆ | 5.5 | 5.6-dev | [JP Mortiboys](https://github.com/themushroompirates) | +| [shapes_lines_drawing](shapes/shapes_lines_drawing.c) | shapes_lines_drawing | ⭐☆☆☆ | 5.6-dev | 5.6 | [Robin](https://github.com/RobinsAviary) | +| [shapes_math_angle_rotation](shapes/shapes_math_angle_rotation.c) | shapes_math_angle_rotation | ⭐☆☆☆ | 5.6-dev | 5.6 | [Kris](https://github.com/krispy-snacc) | ### category: textures [26] @@ -164,7 +167,7 @@ Examples using raylib text functionality, including sprite fonts loading/generat | [text_inline_styling](text/text_inline_styling.c) | text_inline_styling | ⭐⭐⭐☆ | 5.6-dev | 5.6-dev | [Wagner Barongello](https://github.com/SultansOfCode) | | [text_words_alignment](text/text_words_alignment.c) | text_words_alignment | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [JP Mortiboys](https://github.com/themushroompirates) | -### category: models [25] +### category: models [26] Examples using raylib models functionality, including models loading/generation and drawing, provided by raylib [models](../src/rmodels.c) module. @@ -195,6 +198,7 @@ Examples using raylib models functionality, including models loading/generation | [models_tesseract_view](models/models_tesseract_view.c) | models_tesseract_view | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Timothy van der Valk](https://github.com/arceryz) | | [models_basic_voxel](models/models_basic_voxel.c) | models_basic_voxel | ⭐⭐☆☆ | 5.5 | 5.5 | [Tim Little](https://github.com/timlittle) | | [models_rotating_cube](models/models_rotating_cube.c) | models_rotating_cube | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Jopestpe](https://github.com/jopestpe) | +| [models_decals](models/models_decals.c) | models_decals | ⭐⭐⭐⭐️ | 5.6-dev | 5.6-dev | [JP Mortiboys](https://github.com/themushroompirates) | ### category: shaders [32] @@ -214,6 +218,8 @@ Examples using raylib shaders functionality, including shaders loading, paramete | [shaders_texture_outline](shaders/shaders_texture_outline.c) | shaders_texture_outline | ⭐⭐⭐☆ | 4.0 | 4.0 | [Serenity Skiff](https://github.com/GoldenThumbs) | | [shaders_texture_waves](shaders/shaders_texture_waves.c) | shaders_texture_waves | ⭐⭐☆☆ | 2.5 | 3.7 | [Anata](https://github.com/anatagawa) | | [shaders_julia_set](shaders/shaders_julia_set.c) | shaders_julia_set | ⭐⭐⭐☆ | 2.5 | 4.0 | [Josh Colclough](https://github.com/joshcol9232) | +| [shaders_mandelbrot_set](shaders/shaders_mandelbrot_set.c) | shaders_mandelbrot_set | ⭐⭐⭐☆ | 5.6 | 5.6 | [Jordi Santonja](https://github.com/JordSant) | +| [shaders_color_correction](shaders/shaders_color_correction.c) | shaders_color_correction | ⭐⭐☆☆ | 5.6 | 5.6 | [Jordi Santonja](https://github.com/JordSant) | | [shaders_eratosthenes_sieve](shaders/shaders_eratosthenes_sieve.c) | shaders_eratosthenes_sieve | ⭐⭐⭐☆ | 2.5 | 4.0 | [ProfJski](https://github.com/ProfJski) | | [shaders_fog_rendering](shaders/shaders_fog_rendering.c) | shaders_fog_rendering | ⭐⭐⭐☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/chriscamacho) | | [shaders_simple_mask](shaders/shaders_simple_mask.c) | shaders_simple_mask | ⭐⭐☆☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/chriscamacho) | @@ -232,8 +238,6 @@ Examples using raylib shaders functionality, including shaders loading, paramete | [shaders_lightmap_rendering](shaders/shaders_lightmap_rendering.c) | shaders_lightmap_rendering | ⭐⭐⭐☆ | 4.5 | 4.5 | [Jussi Viitala](https://github.com/nullstare) | | [shaders_rounded_rectangle](shaders/shaders_rounded_rectangle.c) | shaders_rounded_rectangle | ⭐⭐⭐☆ | 5.5 | 5.5 | [Anstro Pleuton](https://github.com/anstropleuton) | | [shaders_depth_rendering](shaders/shaders_depth_rendering.c) | shaders_depth_rendering | ⭐⭐⭐☆ | 5.6-dev | 5.6-dev | [Luís Almeida](https://github.com/luis605) | -| [shaders_mandelbrot_set](shaders/shaders_mandelbrot_set.c) | shaders_mandelbrot_set | ⭐⭐⭐☆ | 5.6 | 5.6 | [Jordi Santonja](https://github.com/JordSant) | -| [shaders_color_correction](shaders/shaders_color_correction.c) | shaders_color_correction | ⭐⭐☆☆ | 5.6 | 5.6 | [Jordi Santonja](https://github.com/JordSant) | ### category: audio [8] diff --git a/examples/examples_list.txt b/examples/examples_list.txt index f24cb9905..7c8709785 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -84,6 +84,8 @@ shapes;shapes_math_sine_cosine;★★☆☆;5.6-dev;5.6-dev;2025;2025;"Jopestpe" shapes;shapes_mouse_trail;★☆☆☆;5.6;5.6-dev;2025;2025;"Balamurugan R";@Bala050814 shapes;shapes_simple_particles;★★☆☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant shapes;shapes_starfield_effect;★★☆☆;5.5;5.6-dev;2025;2025;"JP Mortiboys";@themushroompirates +shapes;shapes_lines_drawing;★☆☆☆;5.6-dev;5.6;2025;2025;"Robin";@RobinsAviary +shapes;shapes_math_angle_rotation;★☆☆☆;5.6-dev;5.6;2025;2025;"Kris";@krispy-snacc textures;textures_logo_raylib;★☆☆☆;1.0;1.0;2014;2025;"Ramon Santamaria";@raysan5 textures;textures_srcrec_dstrec;★★★☆;1.3;1.3;2015;2025;"Ramon Santamaria";@raysan5 textures;textures_image_drawing;★★☆☆;1.4;1.4;2016;2025;"Ramon Santamaria";@raysan5 @@ -150,6 +152,7 @@ models;models_bone_socket;★★★★;4.5;4.5;2024;2025;"iP";@ipzaur models;models_tesseract_view;★★☆☆;5.6-dev;5.6-dev;2024;2025;"Timothy van der Valk";@arceryz models;models_basic_voxel;★★☆☆;5.5;5.5;2025;2025;"Tim Little";@timlittle models;models_rotating_cube;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Jopestpe";@jopestpe +models;models_decals;★★★★;5.6-dev;5.6-dev;2025;2025;"JP Mortiboys";@themushroompirates shaders;shaders_ascii_rendering;★★☆☆;5.5;5.6;2025;2025;"Maicon Santana";@maiconpintoabreu shaders;shaders_basic_lighting;★★★★;3.0;4.2;2019;2025;"Chris Camacho";@chriscamacho shaders;shaders_model_shader;★★☆☆;1.3;3.7;2014;2025;"Ramon Santamaria";@raysan5 diff --git a/examples/models/models_decals.c b/examples/models/models_decals.c index d445e69cd..f556139e1 100644 --- a/examples/models/models_decals.c +++ b/examples/models/models_decals.c @@ -4,7 +4,7 @@ * * Example complexity rating: [★★★★] 4/4 * -* Example originally created with raylib 5.6-dev +* Example originally created with raylib 5.6-dev, last time updated with raylib 5.6-dev * * Example contributed by JP Mortiboys (@themushroompirates) and reviewed by Ramon Santamaria (@raysan5) * Based on previous work by @mrdoob diff --git a/examples/shaders/shaders_color_correction.c b/examples/shaders/shaders_color_correction.c index c96f606d8..3abf29840 100644 --- a/examples/shaders/shaders_color_correction.c +++ b/examples/shaders/shaders_color_correction.c @@ -1,6 +1,6 @@ /******************************************************************************************* * -* raylib [shaders] example - basic color correction +* raylib [shaders] example - color correction * * Example complexity rating: [★★☆☆] 2/4 * @@ -9,7 +9,7 @@ * * Example originally created with raylib 5.6, last time updated with raylib 5.6 * -* Example contributed by Jordi Santonja (@JordSant) +* Example contributed by Jordi Santonja (@JordSant) 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 @@ -41,7 +41,7 @@ int main(void) const int screenWidth = 800; const int screenHeight = 450; - InitWindow(screenWidth, screenHeight, "raylib [shaders] example - basic color correction"); + InitWindow(screenWidth, screenHeight, "raylib [shaders] example - color correction"); Texture2D texture[MAX_TEXTURES] = { LoadTexture("resources/parrots.png"), diff --git a/examples/shaders/shaders_palette_switch.c b/examples/shaders/shaders_palette_switch.c index e23cad1d3..048fe7fbc 100644 --- a/examples/shaders/shaders_palette_switch.c +++ b/examples/shaders/shaders_palette_switch.c @@ -1,6 +1,6 @@ /******************************************************************************************* * -* raylib [shaders] example - palette switch +* raylib [shaders] example - palette switch * * Example complexity rating: [★★★☆] 3/4 * @@ -11,7 +11,7 @@ * on OpenGL ES 2.0 platforms (Android, Raspberry Pi, HTML5), use #version 100 shaders * raylib comes with shaders ready for both versions, check raylib/shaders install folder * -* Example originally created with raylib 2.5, last time updated with raylib 3.7 +* Example originally created with raylib 2.5, last time updated with raylib 3.7 * * Example contributed by Marco Lizza (@MarcoLizza) and reviewed by Ramon Santamaria (@raysan5) * diff --git a/examples/shapes/shapes_lines_drawing.c b/examples/shapes/shapes_lines_drawing.c index 9347acaf3..aeaae3d45 100644 --- a/examples/shapes/shapes_lines_drawing.c +++ b/examples/shapes/shapes_lines_drawing.c @@ -11,7 +11,7 @@ * 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) 2025-2025 Robin (@RobinsAviary) +* Copyright (c) 2025 Robin (@RobinsAviary) * ********************************************************************************************/ diff --git a/examples/shapes/shapes_math_angle_rotation.c b/examples/shapes/shapes_math_angle_rotation.c index 0e4802064..f895026f5 100644 --- a/examples/shapes/shapes_math_angle_rotation.c +++ b/examples/shapes/shapes_math_angle_rotation.c @@ -1,14 +1,17 @@ /******************************************************************************************* * -* raylib [shapes] example - Math angle rotation lines +* raylib [shapes] example - math angle rotation * * Example complexity rating: [★☆☆☆] 1/4 * -* Example originally created with raylib 5.5, last time updated with raylib 5.6 +* Example originally created with raylib 5.6-dev, last time updated with raylib 5.6 * * Example contributed by Kris (@krispy-snacc) and reviewed by Ramon Santamaria (@raysan5) * -* Example licensed under an unmodified zlib/libpng license +* 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) 2025 Kris (@krispy-snacc) * ********************************************************************************************/ @@ -25,7 +28,7 @@ int main(void) const int screenWidth = 720; const int screenHeight = 400; - InitWindow(screenWidth, screenHeight, "raylib [shapes] example - angle rotation lines"); + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - math angle rotation"); SetTargetFPS(60); Vector2 center = { screenWidth / 2.0f, screenHeight / 2.0f }; diff --git a/examples/shapes/shapes_math_sine_cosine.c b/examples/shapes/shapes_math_sine_cosine.c index f61d4f548..d8d13920e 100644 --- a/examples/shapes/shapes_math_sine_cosine.c +++ b/examples/shapes/shapes_math_sine_cosine.c @@ -6,12 +6,12 @@ * * Example originally created with raylib 5.6-dev, last time updated with raylib 5.6-dev * -* Example contributed by Jopestpe (@jopestpe) +* Example contributed by Jopestpe (@jopestpe) 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) 2025-2025 Jopestpe (@jopestpe) +* Copyright (c) 2025 Jopestpe (@jopestpe) * ********************************************************************************************/ diff --git a/projects/VS2022/examples/models_decals.vcxproj b/projects/VS2022/examples/models_decals.vcxproj new file mode 100644 index 000000000..a7a5e2f2b --- /dev/null +++ b/projects/VS2022/examples/models_decals.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {028F0967-B253-45DA-B1C4-FACCE45D0D8D} + Win32Proj + models_decals + 10.0 + models_decals + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;shcore.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/examples/shapes_lines_drawing.vcxproj b/projects/VS2022/examples/shapes_lines_drawing.vcxproj new file mode 100644 index 000000000..49159676b --- /dev/null +++ b/projects/VS2022/examples/shapes_lines_drawing.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {666346D7-C84B-498D-AE17-53B20C62DB1A} + Win32Proj + shapes_lines_drawing + 10.0 + shapes_lines_drawing + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;shcore.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/examples/shapes_math_sine_cosine.vcxproj b/projects/VS2022/examples/shapes_math_sine_cosine.vcxproj new file mode 100644 index 000000000..8b2457b83 --- /dev/null +++ b/projects/VS2022/examples/shapes_math_sine_cosine.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {B7FDD40F-DDA4-468E-9C40-EEB175964A26} + Win32Proj + shapes_math_sine_cosine + 10.0 + shapes_math_sine_cosine + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;shcore.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index 44ef64c34..c15632182 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -399,6 +399,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_math_angle_rotation" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shaders_color_correction", "examples\shaders_color_correction.vcxproj", "{98152EDD-7E28-4FA3-89D8-B636ED5D5F65}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_math_sine_cosine", "examples\shapes_math_sine_cosine.vcxproj", "{B7FDD40F-DDA4-468E-9C40-EEB175964A26}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "models_decals", "examples\models_decals.vcxproj", "{028F0967-B253-45DA-B1C4-FACCE45D0D8D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_lines_drawing", "examples\shapes_lines_drawing.vcxproj", "{666346D7-C84B-498D-AE17-53B20C62DB1A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug.DLL|ARM64 = Debug.DLL|ARM64 @@ -4949,6 +4955,78 @@ Global {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Release|x64.Build.0 = Release|x64 {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Release|x86.ActiveCfg = Release|Win32 {98152EDD-7E28-4FA3-89D8-B636ED5D5F65}.Release|x86.Build.0 = Release|Win32 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Debug|ARM64.Build.0 = Debug|ARM64 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Debug|x64.ActiveCfg = Debug|x64 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Debug|x64.Build.0 = Debug|x64 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Debug|x86.ActiveCfg = Debug|Win32 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Debug|x86.Build.0 = Debug|Win32 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Release|ARM64.ActiveCfg = Release|ARM64 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Release|ARM64.Build.0 = Release|ARM64 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Release|x64.ActiveCfg = Release|x64 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Release|x64.Build.0 = Release|x64 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Release|x86.ActiveCfg = Release|Win32 + {B7FDD40F-DDA4-468E-9C40-EEB175964A26}.Release|x86.Build.0 = Release|Win32 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Debug|ARM64.Build.0 = Debug|ARM64 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Debug|x64.ActiveCfg = Debug|x64 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Debug|x64.Build.0 = Debug|x64 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Debug|x86.ActiveCfg = Debug|Win32 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Debug|x86.Build.0 = Debug|Win32 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Release|ARM64.ActiveCfg = Release|ARM64 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Release|ARM64.Build.0 = Release|ARM64 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Release|x64.ActiveCfg = Release|x64 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Release|x64.Build.0 = Release|x64 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Release|x86.ActiveCfg = Release|Win32 + {028F0967-B253-45DA-B1C4-FACCE45D0D8D}.Release|x86.Build.0 = Release|Win32 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Debug|ARM64.Build.0 = Debug|ARM64 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Debug|x64.ActiveCfg = Debug|x64 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Debug|x64.Build.0 = Debug|x64 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Debug|x86.ActiveCfg = Debug|Win32 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Debug|x86.Build.0 = Debug|Win32 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Release|ARM64.ActiveCfg = Release|ARM64 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Release|ARM64.Build.0 = Release|ARM64 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Release|x64.ActiveCfg = Release|x64 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Release|x64.Build.0 = Release|x64 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Release|x86.ActiveCfg = Release|Win32 + {666346D7-C84B-498D-AE17-53B20C62DB1A}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5116,7 +5194,7 @@ Global {C54703BF-D68A-480D-BE27-49B62E45D582} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {9CD8BCAD-F212-4BCC-BA98-899743CE3279} = {CC132A4D-D081-4C26-BFB9-AB11984054F8} {0981CA28-E4A5-4DF1-987F-A41D09131EFC} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} - {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} + {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {278D8859-20B1-428F-8448-064F46E1F021} {6BFF72EA-7362-4A3B-B6E5-9A3655BBBDA3} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {6777EC3C-077C-42FC-B4AD-B799CE55CCE4} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A} {A61DAD9C-271C-4E95-81AA-DB4CD58564D4} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} @@ -5125,7 +5203,7 @@ Global {3B27F358-2679-4F38-B297-17B536F580BB} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {718FCBD0-591D-448C-B7D5-9F1CA8544E7B} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {19CA0070-B4B2-4394-90B7-D0C259AA35BA} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E} = {AF5BEC5C-1F2B-4DA8-B12D-D09FE569237C} {9DB1F875-6E65-4195-B23F-ED8095C0B99C} = {278D8859-20B1-428F-8448-064F46E1F021} {52BA9067-A5FC-4CE8-82AD-7204ECFDEF9F} = {278D8859-20B1-428F-8448-064F46E1F021} {8E132D5A-2C00-48D0-8747-97E41356F26F} = {278D8859-20B1-428F-8448-064F46E1F021} @@ -5150,6 +5228,9 @@ Global {1C829D1A-892C-451C-AF0B-AC65C85F5CC6} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {84DE22BB-C25F-425C-A7FE-0120CF107B83} = {278D8859-20B1-428F-8448-064F46E1F021} {98152EDD-7E28-4FA3-89D8-B636ED5D5F65} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} + {B7FDD40F-DDA4-468E-9C40-EEB175964A26} = {278D8859-20B1-428F-8448-064F46E1F021} + {028F0967-B253-45DA-B1C4-FACCE45D0D8D} = {AF5BEC5C-1F2B-4DA8-B12D-D09FE569237C} + {666346D7-C84B-498D-AE17-53B20C62DB1A} = {278D8859-20B1-428F-8448-064F46E1F021} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} diff --git a/tools/rexm/examples_report.md b/tools/rexm/examples_report.md index fcc1f8839..68efbe1ef 100644 --- a/tools/rexm/examples_report.md +++ b/tools/rexm/examples_report.md @@ -64,6 +64,7 @@ Example elements validated: | core_highdpi_testbed | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_screen_recording | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_clipboard_text | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_text_file_loading | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_basic_shapes | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_bouncing_ball | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_bullet_hell | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -92,6 +93,7 @@ Example elements validated: | shapes_pie_chart | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_kaleidoscope | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_clock_of_clocks | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_math_sine_cosine | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_mouse_trail | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_simple_particles | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_starfield_effect | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -173,6 +175,8 @@ Example elements validated: | shaders_texture_outline | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_texture_waves | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_julia_set | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shaders_mandelbrot_set | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shaders_color_correction | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_eratosthenes_sieve | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_fog_rendering | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_simple_mask | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -204,6 +208,7 @@ Example elements validated: | easings_testbed | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | | embedded_files_loading | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| web_basic_window | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| core_text_file_loading | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shaders_mandelbrot_set | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| web_basic_window | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| models_decals | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_lines_drawing | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_math_angle_rotation | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | diff --git a/tools/rexm/examples_report_issues.md b/tools/rexm/examples_report_issues.md index 5de76a17a..8d24a251d 100644 --- a/tools/rexm/examples_report_issues.md +++ b/tools/rexm/examples_report_issues.md @@ -27,4 +27,4 @@ Example elements validated: | easings_testbed | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | | embedded_files_loading | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| web_basic_window | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| web_basic_window | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | From fdc500756dad1f404356a2841b3e1cd8a14871ce Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 26 Oct 2025 21:21:28 +0100 Subject: [PATCH 012/430] REXM: ADDED: More detailed log info --- tools/rexm/rexm.c | 95 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 17 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index d668655c8..41fb0cae6 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -464,12 +464,14 @@ int main(int argc, char *argv[]) // Create: raylib/examples//_example_name.png if (FileExists(TextFormat("%s/%s.png", GetDirectoryPath(inFileName), exName))) { + LOG("INFO: [%s] Copying file screenshot...\n", GetFileName(inFileName)); FileCopy(TextFormat("%s/%s.png", GetDirectoryPath(inFileName), exName), TextFormat("%s/%s/%s.png", exBasePath, exCategory, exName)); } else // No screenshot available next to source file { // Copy screenshot template + LOG("WARNING: [%s] No screenshot found, using placeholder screenshot\n", GetFileName(inFileName)); FileCopy(exTemplateScreenshot, TextFormat("%s/%s/%s.png", exBasePath, exCategory, exName)); } @@ -478,10 +480,13 @@ int main(int argc, char *argv[]) // Scan resources used in example to copy // NOTE: resources path will be relative to example source file directory int resPathCount = 0; + LOG("INFO: [%s] Scanning file for resources...\n", GetFileName(inFileName)); char **resPaths = ScanExampleResources(TextFormat("%s/%s.c", GetDirectoryPath(inFileName), exName), &resPathCount); if (resPathCount > 0) { + LOG("INFO: [%s] Required resources found: %i\n", GetFileName(inFileName), resPathCount); + for (int r = 0; r < resPathCount; r++) { // WARNING: Special case to consider: shaders, resource paths could use conditions: "glsl%i" @@ -494,7 +499,7 @@ int main(int argc, char *argv[]) { char *resPathUpdated = TextReplace(resPaths[r], "glsl%i", TextFormat("glsl%i", glslVer[v])); - LOG("INFO: Example resource required: %s\n", resPathUpdated); + LOG("INFO: [%s] Resource required [%i/%i]: %s\n", GetFileName(inFileName), r, resPathCount, resPathUpdated); if (FileExists(TextFormat("%s/%s", GetDirectoryPath(inFileName), resPathUpdated))) { @@ -515,7 +520,7 @@ int main(int argc, char *argv[]) } else { - LOG("INFO: Example resource required: %s\n", resPaths[r]); + LOG("INFO: [%s] Resource required [%i/%i]: %s\n", GetFileName(inFileName), r, resPathCount, resPaths[r]); if (FileExists(TextFormat("%s/%s", GetDirectoryPath(inFileName), resPaths[r]))) { @@ -543,6 +548,8 @@ int main(int argc, char *argv[]) char *exCollectionList = LoadFileText(exCollectionFilePath); if (TextFindIndex(exCollectionList, exName) == -1) // Example not found { + LOG("INFO: [%s] Adding example to collection list (%s)\n", GetFileName(inFileName), exCategory); + char *exCollectionListUpdated = (char *)RL_CALLOC(REXM_MAX_BUFFER_SIZE, 1); // Updated list copy, 2MB // Add example to the main list, by category @@ -560,9 +567,15 @@ int main(int argc, char *argv[]) // Get required example info from example file header (if provided) - // NOTE: If no example info is provided (other than category/name), just using some default values + // NOTE: Load example info from provided example header rlExampleInfo *exInfo = LoadExampleInfo(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); + LOG("INFO: [%s] Example info: \n", GetFileName(inFileName)); + LOG(" > Author: %s (@%s)\n", exInfo->author, exInfo->authorGitHub); + LOG(" > Stars: %i\n", exInfo->stars); + LOG(" > Version-Update: %s-%s\n", exInfo->verCreated, exInfo->verUpdated); + LOG(" > Created-Reviewed: %i-%i\n", exInfo->yearCreated, exInfo->yearReviewed); + // Get example difficulty stars text char starsText[16] = { 0 }; for (int i = 0; i < 4; i++) @@ -599,13 +612,14 @@ int main(int argc, char *argv[]) UnloadFileText(exCollectionList); //------------------------------------------------------------------------------------------------ - // Update: Makefile, Makefile.Web, README.md, examples.js + // Update: Metadata, Makefile, Makefile.Web, README.md, examples.js //------------------------------------------------------------------------------------------------ UpdateRequiredFiles(); //------------------------------------------------------------------------------------------------ // Create: raylib/projects/VS2022/examples/_example_name.vcxproj //------------------------------------------------------------------------------------------------ + LOG("INFO: [%s] Creating example project\n", TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exName)); // WARNING: When adding new project a unique UUID should be assigned! FileCopy(TextFormat("%s/../projects/VS2022/examples/core_basic_window.vcxproj", exBasePath), TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exName)); @@ -619,6 +633,7 @@ int main(int argc, char *argv[]) // we must store provided file paths because pointers will be overwriten // TODO: It seems projects are added to solution BUT not to required solution folder, // that process still requires to be done manually + LOG("INFO: [%s] Adding project to raylib solution (.sln)\n", TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exName)); AddVSProjectToSolution(exVSProjectSolutionFile, TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exName), exCategory); //------------------------------------------------------------------------------------------------ @@ -633,17 +648,21 @@ int main(int argc, char *argv[]) // WARNING 1: EMSDK_PATH must be set to proper location when calling from GitHub Actions // WARNING 2: raylib.a and raylib.web.a must be available when compiling locally #if defined(_WIN32) + LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: Win32)\n", GetFileNameWithoutExt(inFileName)); //putenv("RAYLIB_DIR=C:\\GitHub\\raylib"); - putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); + _putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exCategory, exName)); #else + LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: POSIX)\n", GetFileNameWithoutExt(inFileName)); system(TextFormat("make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exCategory, exName)); #endif // Update generated .html metadata + LOG("INFO: [%s] Updating HTML Metadata...\n", TextFormat("%s.html", exName)); UpdateWebMetadata(TextFormat("%s/%s/%s.html", exBasePath, exCategory, exName), TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); // Copy results to web side + LOG("INFO: [%s] Copy example build to raylib.com\n", exName); FileCopy(TextFormat("%s/%s/%s.html", exBasePath, exCategory, exName), TextFormat("%s/%s/%s.html", exWebPath, exCategory, exName)); FileCopy(TextFormat("%s/%s/%s.data", exBasePath, exCategory, exName), @@ -732,7 +751,7 @@ int main(int argc, char *argv[]) // Recompile example (on raylib side) // WARNING: EMSDK_PATH must be set to proper location when calling from GitHub Actions #if defined(_WIN32) - putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); + _putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exRecategory, exRename)); #else system(TextFormat("make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exRecategory, exRename)); @@ -776,6 +795,7 @@ int main(int argc, char *argv[]) // Remove example from collection for files update //------------------------------------------------------------------------------------------------ + LOG("INFO: [%s] Removing example from collection\n", exName); char *exCollectionList = LoadFileText(exCollectionFilePath); int exIndex = TextFindIndex(exCollectionList, TextFormat("%s;%s", exCategory, exName)); if (exIndex > 0) // Example found @@ -831,22 +851,33 @@ int main(int argc, char *argv[]) // Remove: raylib/examples//_example_name.c // Remove: raylib/examples//_example_name.png + LOG("INFO: [%s] Removing example code file\n", TextFormat("%s.c", exName)); FileRemove(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); + LOG("INFO: [%s] Removing example screenshot file\n", TextFormat("%s.png", exName)); FileRemove(TextFormat("%s/%s/%s.png", exBasePath, exCategory, exName)); // Edit: Update required files: Makefile, Makefile.Web, README.md, examples.js UpdateRequiredFiles(); // Remove: raylib/projects/VS2022/examples/_example_name.vcxproj + LOG("INFO: [%s] Removing example project file\n", TextFormat("%s.vcxproj", exName)); FileRemove(TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exName)); // Edit: raylib/projects/VS2022/raylib.sln --> Remove example project + LOG("INFO: [%s] Removing example from raylib solution (.sln)\n", exName); RemoveVSProjectFromSolution(TextFormat("%s/../projects/VS2022/raylib.sln", exBasePath), exName); + // Remove: Delete example build from local copy (if exists) + FileRemove(TextFormat("%s/%s/%s.html", exBasePath, exCategory, exName)); + FileRemove(TextFormat("%s/%s/%s.data", exBasePath, exCategory, exName)); + FileRemove(TextFormat("%s/%s/%s.wasm", exBasePath, exCategory, exName)); + FileRemove(TextFormat("%s/%s/%s.js", exBasePath, exCategory, exName)); + // Remove: raylib.com/examples//_example_name.html // Remove: raylib.com/examples//_example_name.data // Remove: raylib.com/examples//_example_name.wasm // Remove: raylib.com/examples//_example_name.js + LOG("INFO: [%s] Deleting example from raylib.com\n", exName); FileRemove(TextFormat("%s/%s/%s.html", exWebPath, exCategory, exName)); FileRemove(TextFormat("%s/%s/%s.data", exWebPath, exCategory, exName)); FileRemove(TextFormat("%s/%s/%s.wasm", exWebPath, exCategory, exName)); @@ -868,11 +899,10 @@ int main(int argc, char *argv[]) // Build: raylib.com/examples//_example_name.data // Build: raylib.com/examples//_example_name.wasm // Build: raylib.com/examples//_example_name.js - #if defined(_WIN32) // Set required environment variables //putenv(TextFormat("RAYLIB_DIR=%s\\..", exBasePath)); - putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); + _putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); //putenv("MAKE=mingw32-make"); //ChangeDirectory(exBasePath); #endif @@ -880,22 +910,28 @@ int main(int argc, char *argv[]) { // Build example for PLATFORM_DESKTOP #if defined(_WIN32) + LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: Win32)\n", exName); system(TextFormat("mingw32-make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exRebuildList[i].category, exRebuildList[i].name)); #else + LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: POSIX)\n", exName); system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exRebuildList[i].category, exRebuildList[i].name)); #endif // Build example for PLATFORM_WEB #if defined(_WIN32) + LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: Win32)\n", exName); system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exRebuildList[i].category, exRebuildList[i].name)); #else + LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: POSIX)\n", exName); system(TextFormat("make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exRebuildList[i].category, exRebuildList[i].name)); #endif // Update generated .html metadata + LOG("INFO: [%s] Updating HTML Metadata...\n", TextFormat("%s.html", exRebuildList[i].name)), UpdateWebMetadata(TextFormat("%s/%s/%s.html", exBasePath, exRebuildList[i].category, exRebuildList[i].name), TextFormat("%s/%s/%s.c", exBasePath, exRebuildList[i].category, exRebuildList[i].name)); // Copy results to web side + LOG("INFO: [%s] Copy example build to raylib.com\n", exRebuildList[i].name), FileCopy(TextFormat("%s/%s/%s.html", exBasePath, exRebuildList[i].category, exRebuildList[i].name), TextFormat("%s/%s/%s.html", exWebPath, exRebuildList[i].category, exRebuildList[i].name)); FileCopy(TextFormat("%s/%s/%s.data", exBasePath, exRebuildList[i].category, exRebuildList[i].name), @@ -933,10 +969,9 @@ int main(int argc, char *argv[]) VALID_INVALID_CATEGORY */ - // TODO: Log more details about the validation process - // Scan available example .c files and add to collection missing ones // NOTE: Source of truth is what we have in the examples directories (on validation/update) + LOG("INFO: Scanning available example (.c) files to be added to collection...\n"); FilePathList clist = LoadDirectoryFilesEx(exBasePath, ".c", true); char *exList = LoadFileText(exCollectionFilePath); @@ -1007,6 +1042,7 @@ int main(int argc, char *argv[]) UnloadDirectoryFiles(clist); // Check all examples in collection [examples_list.txt] -> Source of truth! + LOG("INFO: Validating examples in collection...\n"); int exCollectionCount = 0; rlExampleInfo *exCollection = LoadExamplesData(exCollectionFilePath, "ALL", false, &exCollectionCount); @@ -1016,6 +1052,8 @@ int main(int argc, char *argv[]) rlExampleInfo *exInfo = &exCollection[i]; exInfo->status = 0; + LOG("INFO: [%s] Validating example...\n", exInfo->name); + // Validate: raylib/examples//_example_name.c -> File exists? if (!FileExists(TextFormat("%s/%s/%s.c", exBasePath, exInfo->category, exInfo->name))) exInfo->status |= VALID_MISSING_C; @@ -1138,11 +1176,16 @@ int main(int argc, char *argv[]) exInfo->status |= VALID_INCONSISTENT_INFO; } + if (exInfo->status == 0) LOG("INFO: [%s] Validation result: OK", exInfo->name); + else LOG("WARNING: [%s] Validation result: ISSUES FOUND", exInfo->name); + UnloadExampleInfo(exInfoHeader); } if (opCode == OP_UPDATE) { + LOG("INFO: Updating examples with issues in collection...\n"); + // Actions to fix/review anything possible from validation results //------------------------------------------------------------------------------------------------ // Check examples "status" information @@ -1162,16 +1205,16 @@ int main(int argc, char *argv[]) // NOTE: Some examples should be excluded from VS2022 solution because // they have specific platform/linkage requirements: - if ((strcmp(exInfo->name, "core_basic_window_web") == 0) || - (strcmp(exInfo->name, "core_input_gestures_web") == 0) || - (strcmp(exInfo->name, "raylib_opengl_interop") == 0) || - (strcmp(exInfo->name, "raymath_vector_angle") == 0)) continue; + if ((strcmp(exInfo->name, "web_basic_window") == 0) || + (strcmp(exInfo->name, "raylib_opengl_interop") == 0)) continue; // Review: Add: raylib/projects/VS2022/examples/_example_name.vcxproj // Review: Add: raylib/projects/VS2022/raylib.sln // Solves: VALID_MISSING_VCXPROJ, VALID_NOT_IN_VCXSOL if (exInfo->status & VALID_MISSING_VCXPROJ) { + LOG("WARNING: [%s] Missing VS2022 project file\n", exInfo->name); + LOG("INFO: [%s.vcxproj] Creating VS2022 project file\n", exInfo->name); FileCopy(TextFormat("%s/../projects/VS2022/examples/core_basic_window.vcxproj", exBasePath), TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exInfo->name)); FileTextReplace(TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exInfo->name), @@ -1185,6 +1228,8 @@ int main(int argc, char *argv[]) // Add project (.vcxproj) to raylib solution (.sln) if (exInfo->status & VALID_NOT_IN_VCXSOL) { + LOG("WARNING: [%s.vcxproj] Project not included in raylib solution (.sln)\n", exInfo->name); + LOG("INFO: [%s.vcxproj] Adding project to raylib solution (.sln)\n", exInfo->name); AddVSProjectToSolution(exVSProjectSolutionFile, TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exInfo->name), exInfo->category); @@ -1199,19 +1244,25 @@ int main(int argc, char *argv[]) if ((strcmp(exInfo->category, "others") != 0) && // Skipping "others" category ((exInfo->status & VALID_MISSING_WEB_OUTPUT) || (exInfo->status & VALID_MISSING_WEB_METADATA))) { + LOG("WARNING: [%s] Example not available on raylib web\n", exInfo->name); + // Build example for PLATFORM_WEB #if defined(_WIN32) - putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); + LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: Win32)\n", exInfo->name); + _putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exInfo->category, exInfo->name)); #else + LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: POSIX)\n", exInfo->name); system(TextFormat("make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exInfo->category, exInfo->name)); #endif // Update generated .html metadata + LOG("INFO: [%s.html] Updating HTML Metadata...\n", exInfo->name); UpdateWebMetadata(TextFormat("%s/%s/%s.html", exBasePath, exInfo->category, exInfo->name), TextFormat("%s/%s/%s.c", exBasePath, exInfo->category, exInfo->name)); // Copy results to web side + LOG("INFO: [%s] Copy example build to raylib.com\n", exInfo->name); FileCopy(TextFormat("%s/%s/%s.html", exBasePath, exInfo->category, exInfo->name), TextFormat("%s/%s/%s.html", exWebPath, exInfo->category, exInfo->name)); FileCopy(TextFormat("%s/%s/%s.data", exBasePath, exInfo->category, exInfo->name), @@ -1228,6 +1279,8 @@ int main(int argc, char *argv[]) if (exInfo->status & VALID_INCONSISTENT_INFO) { // Update source code header info + LOG("WARNING: [%s.c] Inconsistent source code metadata\n", exInfo->name); + LOG("INFO: [%s.c] Updating source code metadata...\n", exInfo->name); UpdateSourceMetadata(TextFormat("%s/%s/%s.c", exBasePath, exInfo->category, exInfo->name), exInfo); exInfo->status &= ~VALID_INCONSISTENT_INFO; @@ -1273,6 +1326,7 @@ int main(int argc, char *argv[]) | shapes_colors_palette | ✘ | ✔ | ✘ | ✔ | ✘ | ✔ | ✔ | ✘ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_format_text | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | ✔ | ✘ | ✔ | ✔ | ✔ | ✔ | */ + LOG("INFO: [examples_report.md] Generating examples validation report...\n"); char *report = (char *)RL_CALLOC(REXM_MAX_BUFFER_SIZE, 1); @@ -1324,6 +1378,8 @@ int main(int argc, char *argv[]) // Generate a report with only the examples missing some elements //----------------------------------------------------------------------------------------------------- + LOG("INFO: [examples_report_issues.md] Generating examples issues report...\n"); + char *reportIssues = (char *)RL_CALLOC(REXM_MAX_BUFFER_SIZE, 1); repIndex = 0; @@ -1437,6 +1493,7 @@ static int UpdateRequiredFiles(void) // Edit: Example source code metadata for consistency //------------------------------------------------------------------------------------------------ + LOG("INFO: Updating all examples metadata...\n"); int exListCount = 0; rlExampleInfo *exList = LoadExamplesData(exCollectionFilePath, "ALL", true, &exListCount); for (int i = 0; i < exListCount; i++) @@ -1449,6 +1506,7 @@ static int UpdateRequiredFiles(void) // Edit: raylib/examples/Makefile --> Update from collection //------------------------------------------------------------------------------------------------ + LOG("INFO: Updating raylib/examples/Makefile\n"); char *mkText = LoadFileText(TextFormat("%s/Makefile", exBasePath)); char *mkTextUpdated = (char *)RL_CALLOC(REXM_MAX_BUFFER_SIZE, 1); // Updated Makefile copy, 2MB @@ -1484,6 +1542,7 @@ static int UpdateRequiredFiles(void) // Edit: raylib/examples/Makefile.Web --> Update from collection // NOTE: We avoid the "others" category on web building //------------------------------------------------------------------------------------------------ + LOG("INFO: Updating raylib/examples/Makefile.Web\n"); char *mkwText = LoadFileText(TextFormat("%s/Makefile.Web", exBasePath)); char *mkwTextUpdated = (char *)RL_CALLOC(REXM_MAX_BUFFER_SIZE, 1); // Updated Makefile copy, 2MB @@ -1602,6 +1661,7 @@ static int UpdateRequiredFiles(void) // Edit: raylib/examples/README.md --> Update from collection //------------------------------------------------------------------------------------------------ + LOG("INFO: Updating raylib/examples/README.md\n"); // NOTE: Using [examples_list.txt] to update/regen README.md // Lines format: | 01 | [core_basic_window](core/core_basic_window.c) | core_basic_window | ⭐️☆☆☆ | 1.0 | 1.0 | [Ray](https://github.com/raysan5) | char *mdText = LoadFileText(TextFormat("%s/README.md", exBasePath)); @@ -1710,6 +1770,7 @@ static int UpdateRequiredFiles(void) // Edit: raylib.com/common/examples.js --> Update from collection // NOTE: Entries format: exampleEntry('⭐️☆☆☆' , 'core' , 'basic_window'), //------------------------------------------------------------------------------------------------ + LOG("INFO: Updating raylib.com/common/examples.js\n"); char *jsText = LoadFileText(TextFormat("%s/../common/examples.js", exWebPath)); if (!jsText) { @@ -2233,7 +2294,7 @@ static int RemoveVSProjectFromSolution(const char *slnFile, const char *exName) char uuid[38] = { 0 }; strcpy(uuid, "ABCDEF00-0123-4567-89AB-000000000012"); // Temp value int textUpdatedOfsset = 0; - int exNameLen = strlen(exName); + int exNameLen = (int)strlen(exName); for (int i = 0, index = 0; i < lineCount; i++) { @@ -2304,7 +2365,7 @@ static void UpdateSourceMetadata(const char *exSrcPath, const rlExampleInfo *inf char exNameFormated[256] = { 0 }; // Example name without category and using spaces int exNameIndex = TextFindIndex(info->name, "_"); strcpy(exNameFormated, info->name + exNameIndex + 1); - int exNameLen = strlen(exNameFormated); + int exNameLen = (int)strlen(exNameFormated); for (int i = 0; i < exNameLen; i++) { if (exNameFormated[i] == '_') exNameFormated[i] = ' '; } // Update example header title (line #3 - ALWAYS) From a0f3f07bdc01ae3a548428883fbb9a93ad28dc25 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 26 Oct 2025 21:23:08 +0100 Subject: [PATCH 013/430] Update examples_report.md --- tools/rexm/examples_report.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/rexm/examples_report.md b/tools/rexm/examples_report.md index 68efbe1ef..7734a234c 100644 --- a/tools/rexm/examples_report.md +++ b/tools/rexm/examples_report.md @@ -97,6 +97,8 @@ Example elements validated: | shapes_mouse_trail | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_simple_particles | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_starfield_effect | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_lines_drawing | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_math_angle_rotation | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_logo_raylib | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_srcrec_dstrec | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_image_drawing | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -163,6 +165,7 @@ Example elements validated: | models_tesseract_view | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | models_basic_voxel | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | models_rotating_cube | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| models_decals | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_ascii_rendering | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_basic_lighting | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_model_shader | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -209,6 +212,3 @@ Example elements validated: | raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | | embedded_files_loading | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | web_basic_window | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| models_decals | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_lines_drawing | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_math_angle_rotation | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | From 5338e3912463d31053c69bf6fb78852cff8ade3f Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 26 Oct 2025 21:23:11 +0100 Subject: [PATCH 014/430] Update rexm.c --- tools/rexm/rexm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 41fb0cae6..98134ee96 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1176,8 +1176,8 @@ int main(int argc, char *argv[]) exInfo->status |= VALID_INCONSISTENT_INFO; } - if (exInfo->status == 0) LOG("INFO: [%s] Validation result: OK", exInfo->name); - else LOG("WARNING: [%s] Validation result: ISSUES FOUND", exInfo->name); + if (exInfo->status == 0) LOG("INFO: [%s] Validation result: OK\n", exInfo->name); + else LOG("WARNING: [%s] Validation result: ISSUES FOUND\n", exInfo->name); UnloadExampleInfo(exInfoHeader); } From aae2c4b355099968e3d8aa50a2335a5764c33b72 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 26 Oct 2025 21:25:25 +0100 Subject: [PATCH 015/430] Update rexm.c --- tools/rexm/rexm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 98134ee96..ffd4d0457 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1003,6 +1003,9 @@ int main(int argc, char *argv[]) if (!TextIsEqual(GetFileNameWithoutExt(clist.paths[i]), "examples_template") && (TextFindIndex(exList, GetFileNameWithoutExt(clist.paths[i])) == -1)) { + // TODO: Examples to be added in the list should be added at the end of their categories, + // not at the end of the file... + // Add example to the examples collection list // WARNING: Added to the end of the list, order must be set by users and // defines placement on raylib webpage From 9aaf120bbe624d1e873181764fa3cc3e6d821716 Mon Sep 17 00:00:00 2001 From: JordSant <77529699+JordSant@users.noreply.github.com> Date: Mon, 27 Oct 2025 00:15:59 +0100 Subject: [PATCH 016/430] [examples] Fixed spaces `shaders_mandelbrot_set` (#5310) --- examples/shaders/shaders_mandelbrot_set.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/shaders/shaders_mandelbrot_set.c b/examples/shaders/shaders_mandelbrot_set.c index a373c518a..dc592b6e4 100644 --- a/examples/shaders/shaders_mandelbrot_set.c +++ b/examples/shaders/shaders_mandelbrot_set.c @@ -69,7 +69,7 @@ int main(void) float offset[2] = { startingOffset[0], startingOffset[1] }; float zoom = startingZoom; // Depending on the zoom the mximum number of iterations must be adapted to get more detail as we zzoom in - // The solution is not perfect, so a control has been added to increase/decrease the number of iterations with UP/DOWN keys + // The solution is not perfect, so a control has been added to increase/decrease the number of iterations with UP/DOWN keys #if defined(PLATFORM_DESKTOP) int maxIterations = 333; float maxIterationsMultiplier = 166.5f; @@ -134,8 +134,8 @@ int main(void) if (IsKeyPressed(KEY_F1)) showControls = !showControls; // Toggle whether or not to show controls - // Change number of max iterations with UP and DOWN keys - // WARNING: Increasing the number of max iterations greatly impacts performance + // Change number of max iterations with UP and DOWN keys + // WARNING: Increasing the number of max iterations greatly impacts performance if (IsKeyPressed(KEY_UP)) { maxIterationsMultiplier *= 1.4f; @@ -167,10 +167,10 @@ int main(void) updateShader = true; } - // In case a parameter has been changed, update the shader values + // In case a parameter has been changed, update the shader values if (updateShader) - { - // As we zoom in, increase the number of max iterations to get more detail + { + // As we zoom in, increase the number of max iterations to get more detail // Aproximate formula, but it works-ish maxIterations = (int)(sqrtf(2.0f*sqrtf(fabsf(1.0f - sqrtf(37.5f*zoom))))*maxIterationsMultiplier); From 5db5c9b4a16ad82be08b1f100e22fd7550ae1ac5 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 27 Oct 2025 00:20:48 +0100 Subject: [PATCH 017/430] Update rexm.c --- tools/rexm/rexm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index ffd4d0457..f45ce9be3 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -926,12 +926,12 @@ int main(int argc, char *argv[]) system(TextFormat("make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exRebuildList[i].category, exRebuildList[i].name)); #endif // Update generated .html metadata - LOG("INFO: [%s] Updating HTML Metadata...\n", TextFormat("%s.html", exRebuildList[i].name)), + LOG("INFO: [%s] Updating HTML Metadata...\n", TextFormat("%s.html", exRebuildList[i].name)); UpdateWebMetadata(TextFormat("%s/%s/%s.html", exBasePath, exRebuildList[i].category, exRebuildList[i].name), TextFormat("%s/%s/%s.c", exBasePath, exRebuildList[i].category, exRebuildList[i].name)); // Copy results to web side - LOG("INFO: [%s] Copy example build to raylib.com\n", exRebuildList[i].name), + LOG("INFO: [%s] Copy example build to raylib.com\n", exRebuildList[i].name); FileCopy(TextFormat("%s/%s/%s.html", exBasePath, exRebuildList[i].category, exRebuildList[i].name), TextFormat("%s/%s/%s.html", exWebPath, exRebuildList[i].category, exRebuildList[i].name)); FileCopy(TextFormat("%s/%s/%s.data", exBasePath, exRebuildList[i].category, exRebuildList[i].name), From 3b6a68ba69078b021a0ff87118820f1bff8180f8 Mon Sep 17 00:00:00 2001 From: Uneven Prankster <33995085+GithubPrankster@users.noreply.github.com> Date: Mon, 27 Oct 2025 08:12:42 -0300 Subject: [PATCH 018/430] Improve support for `PLATFORM_DESKTOP_WIN32` in src/Makefile (#5311) Co-authored-by: Uneven Prankster --- src/Makefile | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Makefile b/src/Makefile index 4369d8084..48ff50b30 100644 --- a/src/Makefile +++ b/src/Makefile @@ -130,7 +130,7 @@ HOST_PLATFORM_OS ?= WINDOWS PLATFORM_OS ?= WINDOWS # Determine PLATFORM_OS when required -ifeq ($(TARGET_PLATFORM),$(filter $(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW PLATFORM_DESKTOP_SDL PLATFORM_DESKTOP_RGFW PLATFORM_WEB PLATFORM_WEB_RGFW PLATFORM_ANDROID)) +ifeq ($(TARGET_PLATFORM),$(filter $(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW PLATFORM_DESKTOP_SDL PLATFORM_DESKTOP_RGFW PLATFORM_DESKTOP_WIN32 PLATFORM_WEB PLATFORM_WEB_RGFW PLATFORM_ANDROID)) # No uname.exe on MinGW!, but OS=Windows_NT on Windows! # ifeq ($(UNAME),Msys) -> Windows ifeq ($(OS),Windows_NT) @@ -252,6 +252,14 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_RGFW) #GRAPHICS = GRAPHICS_API_OPENGL_43 # Uncomment to use OpenGL 4.3 #GRAPHICS = GRAPHICS_API_OPENGL_ES2 # Uncomment to use OpenGL ES 2.0 (ANGLE) endif +ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_WIN32) + GRAPHICS ?= GRAPHICS_API_OPENGL_33 + #GRAPHICS = GRAPHICS_API_OPENGL_11_SOFTWARE # Uncomment to use software rendering + #GRAPHICS = GRAPHICS_API_OPENGL_11 # Uncomment to use OpenGL 1.1 + #GRAPHICS = GRAPHICS_API_OPENGL_21 # Uncomment to use OpenGL 2.1 + #GRAPHICS = GRAPHICS_API_OPENGL_43 # Uncomment to use OpenGL 4.3 + #GRAPHICS = GRAPHICS_API_OPENGL_ES2 # Uncomment to use OpenGL ES 2.0 (ANGLE) +endif ifeq ($(TARGET_PLATFORM),PLATFORM_DRM) # On DRM OpenGL ES 2.0 must be used GRAPHICS = GRAPHICS_API_OPENGL_ES2 @@ -519,7 +527,7 @@ endif #------------------------------------------------------------------------------------------------ LDFLAGS = $(CUSTOM_LDFLAGS) -L. -L$(RAYLIB_RELEASE_PATH) -ifeq ($(TARGET_PLATFORM),$(filter $(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW PLATFORM_DESKTOP_SDL PLATFORM_DESKTOP_RGFW)) +ifeq ($(TARGET_PLATFORM),$(filter $(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW PLATFORM_DESKTOP_SDL PLATFORM_DESKTOP_RGFW PLATFORM_DESKTOP_WIN32)) ifeq ($(PLATFORM_OS),WINDOWS) ifneq ($(CC), tcc) LDFLAGS += -Wl,--out-implib,$(RAYLIB_RELEASE_PATH)/lib$(RAYLIB_LIB_NAME)dll.a From f4ed8fc50e9b7534cc904c4a9ea5a5fc3ac25c44 Mon Sep 17 00:00:00 2001 From: Le Juez Victor <90587919+Bigfoot71@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:59:59 +0100 Subject: [PATCH 019/430] [rlsw] Simplify framebuffer logic and add blit/copy fast path (#5312) * consistency tweak * unified color and depth buffer * tweaks * review the storage of clear values + complete get/set depth value * copy/blit fast path * better simd read/write * framebuffer alignment * fix 'typo' my french slipped out --- src/external/rlsw.h | 636 ++++++++++++++++++-------------------------- 1 file changed, 257 insertions(+), 379 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index e049e707e..ba2716790 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -198,6 +198,7 @@ typedef double GLclampd; //#define GL_ATTRIB_STACK_DEPTH 0x0BB0 //#define GL_CLIENT_ATTRIB_STACK_DEPTH 0x0BB1 #define GL_COLOR_CLEAR_VALUE 0x0C22 +#define GL_DEPTH_CLEAR_VALUE 0x0B73 //#define GL_COLOR_WRITEMASK 0x0C23 //#define GL_CURRENT_INDEX 0x0B01 #define GL_CURRENT_COLOR 0x0B00 @@ -332,6 +333,7 @@ typedef double GLclampd; #define glViewport(x, y, w, h) swViewport((x), (y), (w), (h)) #define glScissor(x, y, w, h) swScissor((x), (y), (w), (h)) #define glClearColor(r, g, b, a) swClearColor((r), (g), (b), (a)) +#define glClearDepth(d) swClearDepth((d)) #define glClear(bitmask) swClear((bitmask)) #define glBlendFunc(sfactor, dfactor) swBlendFunc((sfactor), (dfactor)) #define glPolygonMode(face, mode) swPolygonMode((mode)) @@ -384,7 +386,6 @@ typedef double GLclampd; #define glBindTexture(tr, id) swBindTexture((id)) // OpenGL functions NOT IMPLEMENTED by rlsw -#define glClearDepth(X) ((void)(X)) #define glDepthMask(X) ((void)(X)) #define glColorMask(X,Y,Z,W) ((void)(X),(void)(Y),(void)(Z),(void)(W)) #define glPixelStorei(X,Y) ((void)(X),(void)(Y)) @@ -415,6 +416,7 @@ typedef enum { SW_VERSION = GL_VERSION, SW_EXTENSIONS = GL_EXTENSIONS, SW_COLOR_CLEAR_VALUE = GL_COLOR_CLEAR_VALUE, + SW_DEPTH_CLEAR_VALUE = GL_DEPTH_CLEAR_VALUE, SW_CURRENT_COLOR = GL_CURRENT_COLOR, SW_CURRENT_TEXTURE_COORDS = GL_CURRENT_TEXTURE_COORDS, SW_POINT_SIZE = GL_POINT_SIZE, @@ -529,7 +531,6 @@ SWAPI void swClose(void); SWAPI bool swResizeFramebuffer(int w, int h); SWAPI void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, void *pixels); SWAPI void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySrc, int wSrc, int hSrc, SWformat format, SWtype type, void *pixels); -SWAPI void *swGetColorBuffer(int *w, int *h); SWAPI void swEnable(SWstate state); SWAPI void swDisable(SWstate state); @@ -542,6 +543,7 @@ SWAPI void swViewport(int x, int y, int width, int height); SWAPI void swScissor(int x, int y, int width, int height); SWAPI void swClearColor(float r, float g, float b, float a); +SWAPI void swClearDepth(float depth); SWAPI void swClear(uint32_t bitmask); SWAPI void swBlendFunc(SWfactor sfactor, SWfactor dfactor); @@ -608,6 +610,7 @@ SWAPI void swBindTexture(uint32_t id); #define RLSW_IMPLEMENTATION #if defined(RLSW_IMPLEMENTATION) +#include #include #include #include // Required for: floorf(), fabsf() @@ -683,66 +686,67 @@ SWAPI void swBindTexture(uint32_t id); #define SW_DEPTH_PIXEL_SIZE (SW_DEPTH_BUFFER_BITS/8) #if (SW_COLOR_BUFFER_BITS == 8) - #define COLOR_TYPE uint8_t - #define COLOR_IS_PACKED 1 - #define PACK_COLOR(r,g,b) ((((uint8_t)((r)*7+0.5f))&0x07)<<5 | (((uint8_t)((g)*7+0.5f))&0x07)<<2 | ((uint8_t)((b)*3+0.5f))&0x03) - #define UNPACK_R(p) (((p)>>5)&0x07) - #define UNPACK_G(p) (((p)>>2)&0x07) - #define UNPACK_B(p) ((p)&0x03) - #define SCALE_R(v) ((v)*255+3)/7 - #define SCALE_G(v) ((v)*255+3)/7 - #define SCALE_B(v) ((v)*255+1)/3 - #define TO_FLOAT_R(v) ((v)*(1.0f/7.0f)) - #define TO_FLOAT_G(v) ((v)*(1.0f/7.0f)) - #define TO_FLOAT_B(v) ((v)*(1.0f/3.0f)) + #define SW_COLOR_TYPE uint8_t + #define SW_COLOR_IS_PACKED 1 + #define SW_COLOR_PACK_COMP 1 + #define SW_PACK_COLOR(r,g,b) ((((uint8_t)((r)*7+0.5f))&0x07)<<5 | (((uint8_t)((g)*7+0.5f))&0x07)<<2 | ((uint8_t)((b)*3+0.5f))&0x03) + #define SW_UNPACK_R(p) (((p)>>5)&0x07) + #define SW_UNPACK_G(p) (((p)>>2)&0x07) + #define SW_UNPACK_B(p) ((p)&0x03) + #define SW_SCALE_R(v) ((v)*255+3)/7 + #define SW_SCALE_G(v) ((v)*255+3)/7 + #define SW_SCALE_B(v) ((v)*255+1)/3 + #define SW_TO_FLOAT_R(v) ((v)*(1.0f/7.0f)) + #define SW_TO_FLOAT_G(v) ((v)*(1.0f/7.0f)) + #define SW_TO_FLOAT_B(v) ((v)*(1.0f/3.0f)) #elif (SW_COLOR_BUFFER_BITS == 16) - #define COLOR_TYPE uint16_t - #define COLOR_IS_PACKED 1 - #define PACK_COLOR(r,g,b) ((((uint16_t)((r)*31+0.5f))&0x1F)<<11 | (((uint16_t)((g)*63+0.5f))&0x3F)<<5 | ((uint16_t)((b)*31+0.5f))&0x1F) - #define UNPACK_R(p) (((p)>>11)&0x1F) - #define UNPACK_G(p) (((p)>>5)&0x3F) - #define UNPACK_B(p) ((p)&0x1F) - #define SCALE_R(v) ((v)*255+15)/31 - #define SCALE_G(v) ((v)*255+31)/63 - #define SCALE_B(v) ((v)*255+15)/31 - #define TO_FLOAT_R(v) ((v)*(1.0f/31.0f)) - #define TO_FLOAT_G(v) ((v)*(1.0f/63.0f)) - #define TO_FLOAT_B(v) ((v)*(1.0f/31.0f)) + #define SW_COLOR_TYPE uint16_t + #define SW_COLOR_IS_PACKED 1 + #define SW_COLOR_PACK_COMP 1 + #define SW_PACK_COLOR(r,g,b) ((((uint16_t)((r)*31+0.5f))&0x1F)<<11 | (((uint16_t)((g)*63+0.5f))&0x3F)<<5 | ((uint16_t)((b)*31+0.5f))&0x1F) + #define SW_UNPACK_R(p) (((p)>>11)&0x1F) + #define SW_UNPACK_G(p) (((p)>>5)&0x3F) + #define SW_UNPACK_B(p) ((p)&0x1F) + #define SW_SCALE_R(v) ((v)*255+15)/31 + #define SW_SCALE_G(v) ((v)*255+31)/63 + #define SW_SCALE_B(v) ((v)*255+15)/31 + #define SW_TO_FLOAT_R(v) ((v)*(1.0f/31.0f)) + #define SW_TO_FLOAT_G(v) ((v)*(1.0f/63.0f)) + #define SW_TO_FLOAT_B(v) ((v)*(1.0f/31.0f)) #else // 32 bits - #define COLOR_TYPE uint8_t - #define COLOR_IS_PACKED 0 + #define SW_COLOR_TYPE uint8_t + #define SW_COLOR_IS_PACKED 0 + #define SW_COLOR_PACK_COMP 4 #endif #if (SW_DEPTH_BUFFER_BITS == 8) - #define DEPTH_TYPE uint8_t - #define DEPTH_IS_PACKED 1 - #define DEPTH_MAX UINT8_MAX - #define DEPTH_SCALE (1.0f/UINT8_MAX) - #define PACK_DEPTH(d) ((DEPTH_TYPE)((d)*DEPTH_MAX)) - #define UNPACK_DEPTH(p) (p) + #define SW_DEPTH_TYPE uint8_t + #define SW_DEPTH_IS_PACKED 1 + #define SW_DEPTH_PACK_COMP 1 + #define SW_DEPTH_MAX UINT8_MAX + #define SW_DEPTH_SCALE (1.0f/UINT8_MAX) + #define SW_PACK_DEPTH(d) ((SW_DEPTH_TYPE)((d)*SW_DEPTH_MAX)) + #define SW_UNPACK_DEPTH(p) (p) #elif (SW_DEPTH_BUFFER_BITS == 16) - #define DEPTH_TYPE uint16_t - #define DEPTH_IS_PACKED 1 - #define DEPTH_MAX UINT16_MAX - #define DEPTH_SCALE (1.0f/UINT16_MAX) - #define PACK_DEPTH(d) ((DEPTH_TYPE)((d)*DEPTH_MAX)) - #define UNPACK_DEPTH(p) (p) + #define SW_DEPTH_TYPE uint16_t + #define SW_DEPTH_IS_PACKED 1 + #define SW_DEPTH_PACK_COMP 1 + #define SW_DEPTH_MAX UINT16_MAX + #define SW_DEPTH_SCALE (1.0f/UINT16_MAX) + #define SW_PACK_DEPTH(d) ((SW_DEPTH_TYPE)((d)*SW_DEPTH_MAX)) + #define SW_UNPACK_DEPTH(p) (p) #else // 24 bits - #define DEPTH_TYPE uint8_t - #define DEPTH_IS_PACKED 0 - #define DEPTH_MAX 0xFFFFFF - #define DEPTH_SCALE (1.0f/0xFFFFFF) - #define PACK_DEPTH_0(d) (((uint32_t)((d)*DEPTH_MAX)>>16)&0xFF) - #define PACK_DEPTH_1(d) (((uint32_t)((d)*DEPTH_MAX)>>8)&0xFF) - #define PACK_DEPTH_2(d) ((uint32_t)((d)*DEPTH_MAX)&0xFF) - #define UNPACK_DEPTH(p) (((p)[0]<<16)|((p)[1]<<8)|(p)[2]) + #define SW_DEPTH_TYPE uint8_t + #define SW_DEPTH_IS_PACKED 0 + #define SW_DEPTH_PACK_COMP 3 + #define SW_DEPTH_MAX 0xFFFFFF + #define SW_DEPTH_SCALE (1.0f/0xFFFFFF) + #define SW_PACK_DEPTH_0(d) (((uint32_t)((d)*SW_DEPTH_MAX)>>16)&0xFF) + #define SW_PACK_DEPTH_1(d) (((uint32_t)((d)*SW_DEPTH_MAX)>>8)&0xFF) + #define SW_PACK_DEPTH_2(d) ((uint32_t)((d)*SW_DEPTH_MAX)&0xFF) + #define SW_UNPACK_DEPTH(p) (((p)[0]<<16)|((p)[1]<<8)|(p)[2]) #endif -#define GET_COLOR_PTR(ptr, offset) ((void*)((uint8_t*)(ptr) + (offset)*SW_COLOR_PIXEL_SIZE)) -#define GET_DEPTH_PTR(ptr, offset) ((void*)((uint8_t*)(ptr) + (offset)*SW_DEPTH_PIXEL_SIZE)) -#define INC_COLOR_PTR(ptr) ((ptr) = (void*)((uint8_t*)(ptr) + SW_COLOR_PIXEL_SIZE)) -#define INC_DEPTH_PTR(ptr) ((ptr) = (void*)((uint8_t*)(ptr) + SW_DEPTH_PIXEL_SIZE)) - #define SW_STATE_CHECK(flags) (SW_STATE_CHECK_EX(RLSW.stateFlags, (flags))) #define SW_STATE_CHECK_EX(state, flags) (((state) & (flags)) == (flags)) @@ -809,8 +813,13 @@ typedef struct { } sw_texture_t; typedef struct { - void *color; - void *depth; + alignas(SW_COLOR_PIXEL_SIZE) + SW_COLOR_TYPE color[SW_COLOR_PACK_COMP]; + SW_DEPTH_TYPE depth[SW_DEPTH_PACK_COMP]; +} sw_pixel_t; + +typedef struct { + sw_pixel_t* pixels; int width; int height; int allocSz; @@ -818,8 +827,7 @@ typedef struct { typedef struct { sw_framebuffer_t framebuffer; // Main framebuffer - float clearColor[4]; // Color used to clear the screen - float clearDepth; // Depth value used to clear the screen + sw_pixel_t clearValue; // Clear value of the framebuffer float vpCenter[2]; // Viewport center float vpHalf[2]; // Viewport half dimensions @@ -1075,30 +1083,24 @@ static inline void sw_float_to_unorm8_simd(uint8_t dst[4], const float src[4]) #if defined(SW_HAS_NEON) float32x4_t values = vld1q_f32(src); float32x4_t scaled = vmulq_n_f32(values, 255.0f); - scaled = vminq_f32(vmaxq_f32(scaled, vdupq_n_f32(0.0f)), vdupq_n_f32(255.0f)); - uint32x4_t clamped = vcvtq_u32_f32(scaled); - - uint16x4_t narrow16 = vmovn_u32(clamped); - uint8x8_t narrow8 = vmovn_u16(vcombine_u16(narrow16, narrow16)); - - vst1_lane_u32((uint32_t*)dst, vreinterpret_u32_u8(narrow8), 0); + int32x4_t clamped_s32 = vcvtq_s32_f32(scaled); // f32 -> s32 (truncated) + int16x4_t narrow16_s = vqmovn_s32(clamped_s32); + int16x8_t combined16_s = vcombine_s16(narrow16_s, narrow16_s); + uint8x8_t narrow8_u = vqmovun_s16(combined16_s); + vst1_lane_u32((uint32_t*)dst, vreinterpret_u32_u8(narrow8_u), 0); #elif defined(SW_HAS_SSE41) __m128 values = _mm_loadu_ps(src); __m128 scaled = _mm_mul_ps(values, _mm_set1_ps(255.0f)); - scaled = _mm_max_ps(_mm_min_ps(scaled, _mm_set1_ps(255.0f)), _mm_setzero_ps()); - __m128i clamped = _mm_cvtps_epi32(scaled); - - clamped = _mm_packus_epi32(clamped, clamped); - clamped = _mm_packus_epi16(clamped, clamped); + __m128i clamped = _mm_cvtps_epi32(scaled); // f32 -> s32 (truncated) + clamped = _mm_packus_epi32(clamped, clamped); // s32 -> u16 (saturated < 0 to 0) + clamped = _mm_packus_epi16(clamped, clamped); // u16 -> u8 (saturated > 255 to 255) *(uint32_t*)dst = _mm_cvtsi128_si32(clamped); #elif defined(SW_HAS_SSE2) __m128 values = _mm_loadu_ps(src); __m128 scaled = _mm_mul_ps(values, _mm_set1_ps(255.0f)); - scaled = _mm_max_ps(_mm_min_ps(scaled, _mm_set1_ps(255.0f)), _mm_setzero_ps()); - __m128i clamped = _mm_cvtps_epi32(scaled); - - clamped = _mm_packs_epi32(clamped, clamped); - clamped = _mm_packus_epi16(clamped, clamped); + __m128i clamped = _mm_cvtps_epi32(scaled); // f32 -> s32 (truncated) + clamped = _mm_packs_epi32(clamped, clamped); // s32 -> s16 (saturated) + clamped = _mm_packus_epi16(clamped, clamped); // s16 -> u8 (saturated < 0 to 0) *(uint32_t*)dst = _mm_cvtsi128_si32(clamped); #else for (int i = 0; i < 4; i++) @@ -1106,7 +1108,7 @@ static inline void sw_float_to_unorm8_simd(uint8_t dst[4], const float src[4]) float val = src[i]*255.0f; val = (val > 255.0f)? 255.0f : val; val = (val < 0.0f)? 0.0f : val; - dst[i] = (uint8_t)(val + 0.5f); + dst[i] = (uint8_t)val; } #endif } @@ -1114,13 +1116,9 @@ static inline void sw_float_to_unorm8_simd(uint8_t dst[4], const float src[4]) static inline void sw_float_from_unorm8_simd(float dst[4], const uint8_t src[4]) { #if defined(SW_HAS_NEON) - uint32x4_t bytes = vdupq_n_u32(0); - bytes = vld1q_lane_u32((const uint32_t*)src, bytes, 0); - - uint8x8_t bytes8 = vreinterpret_u8_u32(vget_low_u32(bytes)); + uint8x8_t bytes8 = vld1_u8(src); //< Read 8 bytes, faster, but let's hope we're not at the end of the page (unlikely)... uint16x8_t bytes16 = vmovl_u8(bytes8); uint32x4_t ints = vmovl_u16(vget_low_u16(bytes16)); - float32x4_t floats = vcvtq_f32_u32(ints); floats = vmulq_n_f32(floats, SW_INV_255); vst1q_f32(dst, floats); @@ -1204,15 +1202,8 @@ static inline bool sw_framebuffer_load(int w, int h) { int size = w*h; - RLSW.framebuffer.color = SW_MALLOC(SW_COLOR_PIXEL_SIZE*size); - if (RLSW.framebuffer.color == NULL) return false; - - RLSW.framebuffer.depth = SW_MALLOC(SW_DEPTH_PIXEL_SIZE*size); - if (RLSW.framebuffer.depth == NULL) - { - SW_FREE(RLSW.framebuffer.color); - return false; - } + RLSW.framebuffer.pixels = SW_MALLOC(sizeof(sw_pixel_t)*size); + if (RLSW.framebuffer.pixels == NULL) return false; RLSW.framebuffer.width = w; RLSW.framebuffer.height = h; @@ -1232,18 +1223,10 @@ static inline bool sw_framebuffer_resize(int w, int h) return true; } - void *newColor = SW_REALLOC(RLSW.framebuffer.color, SW_COLOR_PIXEL_SIZE*newSize); - if (newColor == NULL) return false; + void *newPixels = SW_REALLOC(RLSW.framebuffer.pixels, sizeof(sw_pixel_t)*newSize); + if (newPixels == NULL) return false; - void *newDepth = SW_REALLOC(RLSW.framebuffer.depth, SW_DEPTH_PIXEL_SIZE*newSize); - if (newDepth == NULL) - { - SW_FREE(newColor); - return false; - } - - RLSW.framebuffer.color = newColor; - RLSW.framebuffer.depth = newDepth; + RLSW.framebuffer.pixels = newPixels; RLSW.framebuffer.width = w; RLSW.framebuffer.height = h; @@ -1252,29 +1235,29 @@ static inline bool sw_framebuffer_resize(int w, int h) return true; } -static inline void sw_framebuffer_read_color(float dst[4], const void *src) +static inline void sw_framebuffer_read_color(float dst[4], const sw_pixel_t *src) { -#if COLOR_IS_PACKED - COLOR_TYPE pixel = ((COLOR_TYPE*)src)[0]; - dst[0] = TO_FLOAT_R(UNPACK_R(pixel)); - dst[1] = TO_FLOAT_G(UNPACK_G(pixel)); - dst[2] = TO_FLOAT_B(UNPACK_B(pixel)); +#if SW_COLOR_IS_PACKED + SW_COLOR_TYPE pixel = src->color[0]; + dst[0] = SW_TO_FLOAT_R(SW_UNPACK_R(pixel)); + dst[1] = SW_TO_FLOAT_G(SW_UNPACK_G(pixel)); + dst[2] = SW_TO_FLOAT_B(SW_UNPACK_B(pixel)); dst[3] = 1.0f; #else - sw_float_from_unorm8_simd(dst, src); + sw_float_from_unorm8_simd(dst, src->color); #endif } -static inline void sw_framebuffer_read_color8(uint8_t dst[4], const void *src) +static inline void sw_framebuffer_read_color8(uint8_t dst[4], const sw_pixel_t *src) { -#if COLOR_IS_PACKED - COLOR_TYPE pixel = ((COLOR_TYPE*)src)[0]; - dst[0] = SCALE_R(UNPACK_R(pixel)); - dst[1] = SCALE_G(UNPACK_G(pixel)); - dst[2] = SCALE_B(UNPACK_B(pixel)); +#if SW_COLOR_IS_PACKED + SW_COLOR_TYPE pixel = src->color[0]; + dst[0] = SW_SCALE_R(SW_UNPACK_R(pixel)); + dst[1] = SW_SCALE_G(SW_UNPACK_G(pixel)); + dst[2] = SW_SCALE_B(SW_UNPACK_B(pixel)); dst[3] = 255; #else - const COLOR_TYPE *p = (const COLOR_TYPE*)src; + const SW_COLOR_TYPE *p = src->color; dst[0] = p[0]; dst[1] = p[1]; dst[2] = p[2]; @@ -1282,231 +1265,139 @@ static inline void sw_framebuffer_read_color8(uint8_t dst[4], const void *src) #endif } -static inline void sw_framebuffer_write_color(void *dst, const float src[4]) +static inline float sw_framebuffer_read_depth(const sw_pixel_t *src) { -#if COLOR_IS_PACKED - ((COLOR_TYPE*)dst)[0] = PACK_COLOR(src[0], src[1], src[2]); +#if SW_DEPTH_IS_PACKED + return src->depth[0]*SW_DEPTH_SCALE; #else - sw_float_to_unorm8_simd(dst, src); + return SW_UNPACK_DEPTH(src->depth)*SW_DEPTH_SCALE; #endif } -static inline void sw_framebuffer_fill_color(void *ptr, int size, const float color[4]) +static inline void sw_framebuffer_write_color(sw_pixel_t *dst, const float src[4]) { -#if COLOR_IS_PACKED - COLOR_TYPE packed = PACK_COLOR(color[0], color[1], color[2]); - COLOR_TYPE *p = (COLOR_TYPE*)ptr; +#if SW_COLOR_IS_PACKED + dst->color[0] = SW_PACK_COLOR(src[0], src[1], src[2]); #else - COLOR_TYPE r = sw_clampi(color[0]*255, 0, 255); - COLOR_TYPE g = sw_clampi(color[1]*255, 0, 255); - COLOR_TYPE b = sw_clampi(color[2]*255, 0, 255); - COLOR_TYPE a = sw_clampi(color[3]*255, 0, 255); - COLOR_TYPE *p = (COLOR_TYPE*)ptr; + sw_float_to_unorm8_simd(dst->color, src); #endif - +} + +static inline void sw_framebuffer_write_depth(sw_pixel_t *dst, float depth) +{ +#if SW_DEPTH_IS_PACKED + dst->depth[0] = SW_PACK_DEPTH(depth); +#else + dst->depth[0] = SW_PACK_DEPTH_0(depth); + dst->depth[1] = SW_PACK_DEPTH_1(depth); + dst->depth[2] = SW_PACK_DEPTH_2(depth); +#endif +} + +static inline void sw_framebuffer_fill_color(sw_pixel_t *ptr, int size, const SW_COLOR_TYPE color[SW_COLOR_PACK_COMP]) +{ if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { int w = RLSW.scMax[0] - RLSW.scMin[0] + 1; for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) { -#if COLOR_IS_PACKED - COLOR_TYPE *row = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; - for (int x = 0; x < w; x++) *row++ = packed; -#else - COLOR_TYPE *row = p + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); - for (int x = 0; x < w; x++) + sw_pixel_t *row = ptr + y*RLSW.framebuffer.width + RLSW.scMin[0]; + for (int x = 0; x < w; x++, row++) { - *row++ = r; - *row++ = g; - *row++ = b; - *row++ = a; + for (int i = 0; i < SW_COLOR_PACK_COMP; i++) row->color[i] = color[i]; } -#endif } } else { -#if COLOR_IS_PACKED - for (int i = 0; i < size; i++) *p++ = packed; -#else - for (int i = 0; i < size; i++) + for (int i = 0; i < size; i++, ptr++) { - *p++ = r; - *p++ = g; - *p++ = b; - *p++ = a; + for (int j = 0; j < SW_COLOR_PACK_COMP; j++) ptr->color[j] = color[j]; } -#endif } } -static inline float sw_framebuffer_read_depth(const void *src) +static inline void sw_framebuffer_fill_depth(sw_pixel_t *ptr, int size, const SW_DEPTH_TYPE depth[SW_DEPTH_PACK_COMP]) { -#if DEPTH_IS_PACKED - return ((DEPTH_TYPE*)src)[0]*DEPTH_SCALE; -#else - const DEPTH_TYPE *p = (const DEPTH_TYPE*)src; - uint32_t d = UNPACK_DEPTH(p); - return d*DEPTH_SCALE; -#endif -} - -static inline void sw_framebuffer_write_depth(void *dst, float depth) -{ -#if DEPTH_IS_PACKED - ((DEPTH_TYPE*)dst)[0] = PACK_DEPTH(depth); -#else - DEPTH_TYPE *p = (DEPTH_TYPE*)dst; - p[0] = PACK_DEPTH_0(depth); - p[1] = PACK_DEPTH_1(depth); - p[2] = PACK_DEPTH_2(depth); -#endif -} - -static inline void sw_framebuffer_fill_depth(void *ptr, int size, float value) -{ -#if DEPTH_IS_PACKED - DEPTH_TYPE d = PACK_DEPTH(value); - DEPTH_TYPE *p = (DEPTH_TYPE*)ptr; -#else - DEPTH_TYPE d0 = PACK_DEPTH_0(value); - DEPTH_TYPE d1 = PACK_DEPTH_1(value); - DEPTH_TYPE d2 = PACK_DEPTH_2(value); - DEPTH_TYPE *p = (DEPTH_TYPE*)ptr; -#endif - if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { int w = RLSW.scMax[0] - RLSW.scMin[0] + 1; for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) { -#if DEPTH_IS_PACKED - DEPTH_TYPE *row = p + y*RLSW.framebuffer.width + RLSW.scMin[0]; - for (int x = 0; x < w; x++) *row++ = d; -#else - DEPTH_TYPE *row = p + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); - for (int x = 0; x < w; x++) + sw_pixel_t *row = ptr + y*RLSW.framebuffer.width + RLSW.scMin[0]; + for (int x = 0; x < w; x++, row++) { - *row++ = d0; - *row++ = d1; - *row++ = d2; + for (int i = 0; i < SW_DEPTH_PACK_COMP; i++) row->depth[i] = depth[i]; } -#endif } } else { -#if DEPTH_IS_PACKED - for (int i = 0; i < size; i++) *p++ = d; -#else - for (int i = 0; i < size; i++) + for (int i = 0; i < size; i++, ptr++) { - *p++ = d0; - *p++ = d1; - *p++ = d2; + for (int j = 0; j < SW_DEPTH_PACK_COMP; j++) ptr->depth[j] = depth[j]; } -#endif } } -static inline void sw_framebuffer_fill(void *colorPtr, void *depthPtr, int size, float color[4], float depth) +static inline void sw_framebuffer_fill(sw_pixel_t *ptr, int size, sw_pixel_t value) { -#if COLOR_IS_PACKED - COLOR_TYPE packedColor = PACK_COLOR(color[0], color[1], color[2]); - COLOR_TYPE *pColor = (COLOR_TYPE*)colorPtr; -#else - COLOR_TYPE r = sw_clampi(color[0]*255, 0, 255); - COLOR_TYPE g = sw_clampi(color[1]*255, 0, 255); - COLOR_TYPE b = sw_clampi(color[2]*255, 0, 255); - COLOR_TYPE a = sw_clampi(color[3]*255, 0, 255); - COLOR_TYPE *pColor = (COLOR_TYPE*)colorPtr; -#endif - -#if DEPTH_IS_PACKED - DEPTH_TYPE d = PACK_DEPTH(depth); - DEPTH_TYPE *pDepth = (DEPTH_TYPE*)depthPtr; -#else - DEPTH_TYPE d0 = PACK_DEPTH_0(depth); - DEPTH_TYPE d1 = PACK_DEPTH_1(depth); - DEPTH_TYPE d2 = PACK_DEPTH_2(depth); - DEPTH_TYPE *pDepth = (DEPTH_TYPE*)depthPtr; -#endif - if (RLSW.stateFlags & SW_STATE_SCISSOR_TEST) { int w = RLSW.scMax[0] - RLSW.scMin[0] + 1; for (int y = RLSW.scMin[1]; y <= RLSW.scMax[1]; y++) { -#if COLOR_IS_PACKED - COLOR_TYPE *rowColor = pColor + y*RLSW.framebuffer.width + RLSW.scMin[0]; -#else - COLOR_TYPE *rowColor = pColor + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); -#endif - -#if DEPTH_IS_PACKED - DEPTH_TYPE *rowDepth = pDepth + y*RLSW.framebuffer.width + RLSW.scMin[0]; -#else - DEPTH_TYPE *rowDepth = pDepth + 3*(y*RLSW.framebuffer.width + RLSW.scMin[0]); -#endif - - for (int x = 0; x < w; x++) - { -#if COLOR_IS_PACKED - *rowColor++ = packedColor; -#else - *rowColor++ = r; - *rowColor++ = g; - *rowColor++ = b; - *rowColor++ = a; -#endif - -#if DEPTH_IS_PACKED - *rowDepth++ = d; -#else - *rowDepth++ = d0; - *rowDepth++ = d1; - *rowDepth++ = d2; -#endif - } + sw_pixel_t *row = ptr + y*RLSW.framebuffer.width + RLSW.scMin[0]; + for (int x = 0; x < w; x++, row++) *row = value; } } else { + for (int i = 0; i < size; i++, ptr++) *ptr = value; + } +} + +static inline void sw_framebuffer_copy_fast(void* dst) +{ + int size = RLSW.framebuffer.width*RLSW.framebuffer.height; + const sw_pixel_t *pixels = RLSW.framebuffer.pixels; + +#if SW_COLOR_BUFFER_BITS == 8 + uint8_t *dst8 = (uint8_t*)dst; + for (int i = 0; i < size; i++) dst8[i] = pixels[i].color[0]; +#elif SW_COLOR_BUFFER_BITS == 16 + uint16_t *dst16 = (uint16_t*)dst; + for (int i = 0; i < size; i++) dst16[i] = *(uint16_t*)pixels[i].color; +#else // 32 bits + uint32_t *dst32 = (uint32_t*)dst; + #if SW_GL_FRAMEBUFFER_COPY_BGRA for (int i = 0; i < size; i++) { -#if COLOR_IS_PACKED - *pColor++ = packedColor; -#else - *pColor++ = r; - *pColor++ = g; - *pColor++ = b; - *pColor++ = a; -#endif - -#if DEPTH_IS_PACKED - *pDepth++ = d; -#else - *pDepth++ = d0; - *pDepth++ = d1; - *pDepth++ = d2; -#endif + const uint8_t *c = pixels[i].color; + dst32[i] = (uint32_t)c[2] | ((uint32_t)c[1] << 8) | ((uint32_t)c[0] << 16) | ((uint32_t)c[3] << 24); } - } + #else // RGBA + for (int i = 0; i < size; i++) dst32[i] = *(uint32_t*)pixels[i].color; + #endif +#endif } #define DEFINE_FRAMEBUFFER_COPY_BEGIN(name, DST_PTR_T) \ -static inline void sw_framebuffer_copy_to_##name(int x, int y, int w, int h, DST_PTR_T *dst) \ +static inline void sw_framebuffer_copy_to_##name(int x, int y, int w, int h, DST_PTR_T *dst) \ { \ - const void *src = RLSW.framebuffer.color; \ + const int stride = RLSW.framebuffer.width; \ + const sw_pixel_t *src = RLSW.framebuffer.pixels + (y*stride + x); \ \ - for (int iy = y; iy < h; iy++) { \ - for (int ix = x; ix < w; ix++) { \ + for (int iy = 0; iy < h; iy++) { \ + const sw_pixel_t *line = src; \ + for (int ix = 0; ix < w; ix++) { \ uint8_t color[4]; \ - sw_framebuffer_read_color8(color, src); \ + sw_framebuffer_read_color8(color, line); \ #define DEFINE_FRAMEBUFFER_COPY_END() \ - INC_COLOR_PTR(src); \ + ++line; \ } \ + src += stride; \ } \ } @@ -1620,22 +1511,24 @@ static inline void sw_framebuffer_blit_to_##name( int xSrc, int ySrc, int wSrc, int hSrc, \ DST_PTR_T *dst) \ { \ - const uint8_t *srcBase = (uint8_t*)RLSW.framebuffer.color; \ - int fbWidth = RLSW.framebuffer.width; \ + const sw_pixel_t *srcBase = RLSW.framebuffer.pixels; \ + const int fbWidth = RLSW.framebuffer.width; \ \ - uint32_t xScale = ((uint32_t)wSrc << 16)/(uint32_t)wDst; \ - uint32_t yScale = ((uint32_t)hSrc << 16)/(uint32_t)hDst; \ + const uint32_t xScale = ((uint32_t)wSrc << 16)/(uint32_t)wDst; \ + const uint32_t yScale = ((uint32_t)hSrc << 16)/(uint32_t)hDst; \ \ for (int dy = 0; dy < hDst; dy++) { \ uint32_t yFix = ((uint32_t)ySrc << 16) + dy*yScale; \ int sy = yFix >> 16; \ + const sw_pixel_t *srcLine = srcBase + sy*fbWidth + xSrc; \ \ + const sw_pixel_t *srcPtr = srcLine; \ for (int dx = 0; dx < wDst; dx++) { \ uint32_t xFix = dx*xScale; \ int sx = xFix >> 16; \ - const void *srcPtr = GET_COLOR_PTR(srcBase, sy*fbWidth + sx); \ + const sw_pixel_t *pixel = srcPtr + sx; \ uint8_t color[4]; \ - sw_framebuffer_read_color8(color, srcPtr); \ + sw_framebuffer_read_color8(color, pixel); #define DEFINE_FRAMEBUFFER_BLIT_END() \ } \ @@ -2378,8 +2271,7 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, \ /* Pre-calculate the starting pointers for the framebuffer row */ \ int y = (int)start->screen[1]; \ - void *cptr = GET_COLOR_PTR(RLSW.framebuffer.color, y*RLSW.framebuffer.width + xStart); \ - void *dptr = GET_DEPTH_PTR(RLSW.framebuffer.depth, y*RLSW.framebuffer.width + xStart); \ + sw_pixel_t *ptr = RLSW.framebuffer.pixels + y*RLSW.framebuffer.width + xStart; \ \ /* Scanline rasterization */ \ for (int x = xStart; x < xEnd; x++) \ @@ -2395,12 +2287,12 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, if (ENABLE_DEPTH_TEST) \ { \ /* TODO: Implement different depth funcs? */ \ - float depth = sw_framebuffer_read_depth(dptr); \ + float depth = sw_framebuffer_read_depth(ptr); \ if (z > depth) goto discard; \ } \ \ /* TODO: Implement depth mask */ \ - sw_framebuffer_write_depth(dptr, z); \ + sw_framebuffer_write_depth(ptr, z); \ \ if (ENABLE_TEXTURE) \ { \ @@ -2417,13 +2309,13 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, if (ENABLE_COLOR_BLEND) \ { \ float dstColor[4]; \ - sw_framebuffer_read_color(dstColor, cptr); \ + sw_framebuffer_read_color(dstColor, ptr); \ sw_blend_colors(dstColor, srcColor); \ - sw_framebuffer_write_color(cptr, dstColor); \ + sw_framebuffer_write_color(ptr, dstColor); \ } \ else \ { \ - sw_framebuffer_write_color(cptr, srcColor); \ + sw_framebuffer_write_color(ptr, srcColor); \ } \ \ /* Increment the interpolation parameter, UVs, and pointers */ \ @@ -2439,9 +2331,7 @@ static inline void FUNC_NAME(const sw_texture_t *tex, const sw_vertex_t *start, u += dUdx; \ v += dVdx; \ } \ - \ - INC_COLOR_PTR(cptr); \ - INC_DEPTH_PTR(dptr); \ + ++ptr; \ } \ } @@ -2809,8 +2699,7 @@ static inline void FUNC_NAME(void) const sw_texture_t *tex; \ if (ENABLE_TEXTURE) tex = &RLSW.loadedTextures[RLSW.currentTexture]; \ \ - void *cDstBase = RLSW.framebuffer.color; \ - void *dDstBase = RLSW.framebuffer.depth; \ + sw_pixel_t *pixels = RLSW.framebuffer.pixels; \ int wDst = RLSW.framebuffer.width; \ \ float zScanline = v0->homogeneous[2] + dZdx*xSubstep + dZdy*ySubstep; \ @@ -2826,8 +2715,7 @@ static inline void FUNC_NAME(void) \ for (int y = yMin; y < yMax; y++) \ { \ - void *cptr = GET_COLOR_PTR(cDstBase, y*wDst + xMin); \ - void *dptr = GET_DEPTH_PTR(dDstBase, y*wDst + xMin); \ + sw_pixel_t *ptr = pixels + y*wDst + xMin; \ \ float z = zScanline; \ float u = uScanline; \ @@ -2855,12 +2743,12 @@ static inline void FUNC_NAME(void) if (ENABLE_DEPTH_TEST) \ { \ /* TODO: Implement different depth funcs? */ \ - float depth = sw_framebuffer_read_depth(dptr); \ + float depth = sw_framebuffer_read_depth(ptr); \ if (z > depth) goto discard; \ } \ \ /* TODO: Implement depth mask */ \ - sw_framebuffer_write_depth(dptr, z); \ + sw_framebuffer_write_depth(ptr, z); \ \ if (ENABLE_TEXTURE) \ { \ @@ -2875,11 +2763,11 @@ static inline void FUNC_NAME(void) if (ENABLE_COLOR_BLEND) \ { \ float dstColor[4]; \ - sw_framebuffer_read_color(dstColor, cptr); \ + sw_framebuffer_read_color(dstColor, ptr); \ sw_blend_colors(dstColor, srcColor); \ - sw_framebuffer_write_color(cptr, dstColor); \ + sw_framebuffer_write_color(ptr, dstColor); \ } \ - else sw_framebuffer_write_color(cptr, srcColor); \ + else sw_framebuffer_write_color(ptr, srcColor); \ \ discard: \ z += dZdx; \ @@ -2887,15 +2775,12 @@ static inline void FUNC_NAME(void) color[1] += dCdx[1]; \ color[2] += dCdx[2]; \ color[3] += dCdx[3]; \ - \ if (ENABLE_TEXTURE) \ { \ u += dUdx; \ v += dVdx; \ } \ - \ - INC_COLOR_PTR(cptr); \ - INC_DEPTH_PTR(dptr); \ + ++ptr; \ } \ \ zScanline += dZdy; \ @@ -3119,8 +3004,7 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ float a = v0->color[3] + aInc*substep; \ \ const int fbWidth = RLSW.framebuffer.width; \ - void *cBuffer = RLSW.framebuffer.color; \ - void *dBuffer = RLSW.framebuffer.depth; \ + sw_pixel_t *pixels = RLSW.framebuffer.pixels; \ \ int numPixels = (int)(steps - substep) + 1; \ \ @@ -3130,28 +3014,26 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ int px = (int)(x - 0.5f); \ int py = (int)(y - 0.5f); \ \ - int offset = py*fbWidth + px; \ - void *dptr = GET_DEPTH_PTR(dBuffer, offset); \ + sw_pixel_t *ptr = pixels + py*fbWidth + px; \ \ if (ENABLE_DEPTH_TEST) \ { \ - float depth = sw_framebuffer_read_depth(dptr); \ + float depth = sw_framebuffer_read_depth(ptr); \ if (z > depth) goto discard; \ } \ \ - sw_framebuffer_write_depth(dptr, z); \ + sw_framebuffer_write_depth(ptr, z); \ \ - void *cptr = GET_COLOR_PTR(cBuffer, offset); \ float color[4] = {r, g, b, a}; \ \ if (ENABLE_COLOR_BLEND) \ { \ float dstColor[4]; \ - sw_framebuffer_read_color(dstColor, cptr); \ + sw_framebuffer_read_color(dstColor, ptr); \ sw_blend_colors(dstColor, color); \ - sw_framebuffer_write_color(cptr, dstColor); \ + sw_framebuffer_write_color(ptr, dstColor); \ } \ - else sw_framebuffer_write_color(cptr, color); \ + else sw_framebuffer_write_color(ptr, color); \ \ discard: \ x += xInc; y += yInc; z += zInc; \ @@ -3292,27 +3174,24 @@ static inline void FUNC_NAME(int x, int y, float z, const float color[4]) \ } \ \ int offset = y*RLSW.framebuffer.width + x; \ - \ - void *dptr = GET_DEPTH_PTR(RLSW.framebuffer.depth, offset); \ + sw_pixel_t *ptr = RLSW.framebuffer.pixels + offset; \ \ if (ENABLE_DEPTH_TEST) \ { \ - float depth = sw_framebuffer_read_depth(dptr); \ + float depth = sw_framebuffer_read_depth(ptr); \ if (z > depth) return; \ } \ \ - sw_framebuffer_write_depth(dptr, z); \ - \ - void *cptr = GET_COLOR_PTR(RLSW.framebuffer.color, offset); \ + sw_framebuffer_write_depth(ptr, z); \ \ if (ENABLE_COLOR_BLEND) \ { \ float dstColor[4]; \ - sw_framebuffer_read_color(dstColor, cptr); \ + sw_framebuffer_read_color(dstColor, ptr); \ sw_blend_colors(dstColor, color); \ - sw_framebuffer_write_color(cptr, dstColor); \ + sw_framebuffer_write_color(ptr, dstColor); \ } \ - else sw_framebuffer_write_color(cptr, color); \ + else sw_framebuffer_write_color(ptr, color); \ } #define DEFINE_POINT_THICK_RASTER(FUNC_NAME, RASTER_FUNC) \ @@ -3598,11 +3477,9 @@ bool swInit(int w, int h) RLSW.freeTextureIds = (uint32_t *)SW_MALLOC(SW_MAX_TEXTURES*sizeof(uint32_t)); if (RLSW.loadedTextures == NULL) { swClose(); return false; } - RLSW.clearColor[0] = 0.0f; - RLSW.clearColor[1] = 0.0f; - RLSW.clearColor[2] = 0.0f; - RLSW.clearColor[3] = 1.0f; - RLSW.clearDepth = 1.0f; + const float clearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + sw_framebuffer_write_color(&RLSW.clearValue, clearColor); + sw_framebuffer_write_depth(&RLSW.clearValue, 1.0f); RLSW.currentMatrixMode = SW_MODELVIEW; RLSW.currentMatrix = &RLSW.stackModelview[0]; @@ -3669,8 +3546,7 @@ void swClose(void) } } - SW_FREE(RLSW.framebuffer.color); - SW_FREE(RLSW.framebuffer.depth); + SW_FREE(RLSW.framebuffer.pixels); SW_FREE(RLSW.loadedTextures); SW_FREE(RLSW.freeTextureIds); @@ -3686,17 +3562,8 @@ void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, { sw_pixelformat_t pFormat = (sw_pixelformat_t)sw_get_pixel_format(format, type); - if (w <= 0) - { - RLSW.errCode = SW_INVALID_VALUE; - return; - } - - if (h <= 0) - { - RLSW.errCode = SW_INVALID_VALUE; - return; - } + if (w <= 0) { RLSW.errCode = SW_INVALID_VALUE; return; } + if (h <= 0) { RLSW.errCode = SW_INVALID_VALUE; return; } if (w > RLSW.framebuffer.width) w = RLSW.framebuffer.width; if (h > RLSW.framebuffer.height) h = RLSW.framebuffer.height; @@ -3704,6 +3571,25 @@ void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, x = sw_clampi(x, 0, w); y = sw_clampi(y, 0, h); + if (x >= w || y >= h) return; + + if (x == 0 && y == 0 && w == RLSW.framebuffer.width && h == RLSW.framebuffer.height) + { + #if SW_COLOR_BUFFER_BITS == 32 + if (pFormat == SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) + { + sw_framebuffer_copy_fast(pixels); + return; + } + #elif SW_COLOR_BUFFER_BITS == 16 + if (pFormat == SW_PIXELFORMAT_UNCOMPRESSED_R5G6B5) + { + sw_framebuffer_copy_fast(pixels); + return; + } + #endif + } + switch (pFormat) { case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: sw_framebuffer_copy_to_GRAYALPHA(x, y, w, h, (uint8_t *)pixels); break; @@ -3730,17 +3616,13 @@ void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySr { sw_pixelformat_t pFormat = (sw_pixelformat_t)sw_get_pixel_format(format, type); - if (wSrc <= 0) + if (xDst == xSrc && yDst == ySrc && wDst == wSrc && hDst == hSrc) { - RLSW.errCode = SW_INVALID_VALUE; - return; + swCopyFramebuffer(xSrc, ySrc, wSrc, hSrc, format, type, pixels); } - if (hSrc <= 0) - { - RLSW.errCode = SW_INVALID_VALUE; - return; - } + if (wSrc <= 0) { RLSW.errCode = SW_INVALID_VALUE; return; } + if (hSrc <= 0) { RLSW.errCode = SW_INVALID_VALUE; return; } if (wSrc > RLSW.framebuffer.width) wSrc = RLSW.framebuffer.width; if (hSrc > RLSW.framebuffer.height) hSrc = RLSW.framebuffer.height; @@ -3770,14 +3652,6 @@ void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySr } } -void *swGetColorBuffer(int *w, int *h) -{ - if (w) *w = RLSW.framebuffer.width; - if (h) *h = RLSW.framebuffer.height; - - return RLSW.framebuffer.color; -} - void swEnable(SWstate state) { switch (state) @@ -3821,10 +3695,11 @@ void swGetFloatv(SWget name, float *v) { case SW_COLOR_CLEAR_VALUE: { - v[0] = RLSW.clearColor[0]; - v[1] = RLSW.clearColor[1]; - v[2] = RLSW.clearColor[2]; - v[3] = RLSW.clearColor[3]; + sw_framebuffer_read_color(v, &RLSW.clearValue); + } break; + case SW_DEPTH_CLEAR_VALUE: + { + v[0] = sw_framebuffer_read_depth(&RLSW.clearValue); } break; case SW_CURRENT_COLOR: { @@ -3932,10 +3807,13 @@ void swScissor(int x, int y, int width, int height) void swClearColor(float r, float g, float b, float a) { - RLSW.clearColor[0] = r; - RLSW.clearColor[1] = g; - RLSW.clearColor[2] = b; - RLSW.clearColor[3] = a; + float v[4] = { r, g, b, a }; + sw_framebuffer_write_color(&RLSW.clearValue, v); +} + +void swClearDepth(float depth) +{ + sw_framebuffer_write_depth(&RLSW.clearValue, depth); } void swClear(uint32_t bitmask) @@ -3944,15 +3822,15 @@ void swClear(uint32_t bitmask) if ((bitmask & (SW_COLOR_BUFFER_BIT | SW_DEPTH_BUFFER_BIT)) == (SW_COLOR_BUFFER_BIT | SW_DEPTH_BUFFER_BIT)) { - sw_framebuffer_fill(RLSW.framebuffer.color, RLSW.framebuffer.depth,size, RLSW.clearColor, RLSW.clearDepth); + sw_framebuffer_fill(RLSW.framebuffer.pixels, size, RLSW.clearValue); } else if (bitmask & (SW_COLOR_BUFFER_BIT)) { - sw_framebuffer_fill_color(RLSW.framebuffer.color, size, RLSW.clearColor); + sw_framebuffer_fill_color(RLSW.framebuffer.pixels, size, RLSW.clearValue.color); } else if (bitmask & SW_DEPTH_BUFFER_BIT) { - sw_framebuffer_fill_depth(RLSW.framebuffer.depth, size, RLSW.clearDepth); + sw_framebuffer_fill_depth(RLSW.framebuffer.pixels, size, RLSW.clearValue.depth); } } @@ -4525,7 +4403,7 @@ void swDrawArrays(SWdraw mode, int offset, int count) float u, v; if (texcoords) { - int idx = 2 * i; + int idx = 2*i; u = texcoords[idx]; v = texcoords[idx + 1]; } @@ -4536,8 +4414,8 @@ void swDrawArrays(SWdraw mode, int offset, int count) } float texcoord[2]; - texcoord[0] = texMatrix[0] * u + texMatrix[4] * v + texMatrix[12]; - texcoord[1] = texMatrix[1] * u + texMatrix[5] * v + texMatrix[13]; + texcoord[0] = texMatrix[0]*u + texMatrix[4]*v + texMatrix[12]; + texcoord[1] = texMatrix[1]*u + texMatrix[5]*v + texMatrix[13]; float color[4] = { defaultColor[0], @@ -4548,14 +4426,14 @@ void swDrawArrays(SWdraw mode, int offset, int count) if (colors) { - int idx = 4 * i; + int idx = 4*i; color[0] *= (float)colors[idx]*SW_INV_255; color[1] *= (float)colors[idx + 1]*SW_INV_255; color[2] *= (float)colors[idx + 2]*SW_INV_255; color[3] *= (float)colors[idx + 3]*SW_INV_255; } - int idx = 3 * i; + int idx = 3*i; float position[4] = { positions[idx], positions[idx + 1], @@ -4621,7 +4499,7 @@ void swDrawElements(SWdraw mode, int count, int type, const void *indices) float u, v; if (texcoords) { - int idx = 2 * index; + int idx = 2*index; u = texcoords[idx]; v = texcoords[idx + 1]; } @@ -4632,8 +4510,8 @@ void swDrawElements(SWdraw mode, int count, int type, const void *indices) } float texcoord[2]; - texcoord[0] = texMatrix[0] * u + texMatrix[4] * v + texMatrix[12]; - texcoord[1] = texMatrix[1] * u + texMatrix[5] * v + texMatrix[13]; + texcoord[0] = texMatrix[0]*u + texMatrix[4]*v + texMatrix[12]; + texcoord[1] = texMatrix[1]*u + texMatrix[5]*v + texMatrix[13]; float color[4] = { defaultColor[0], @@ -4644,14 +4522,14 @@ void swDrawElements(SWdraw mode, int count, int type, const void *indices) if (colors) { - int idx = 4 * index; + int idx = 4*index; color[0] *= (float)colors[idx]*SW_INV_255; color[1] *= (float)colors[idx + 1]*SW_INV_255; color[2] *= (float)colors[idx + 2]*SW_INV_255; color[3] *= (float)colors[idx + 3]*SW_INV_255; } - int idx = 3 * index; + int idx = 3*index; float position[4] = { positions[idx], positions[idx + 1], From 93a21c7e1300c073acc0a9c5b69d8cdfb6127255 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 28 Oct 2025 19:55:28 +0100 Subject: [PATCH 020/430] Support other graphic backends on some platforms --- src/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Makefile b/src/Makefile index 48ff50b30..0895d8f95 100644 --- a/src/Makefile +++ b/src/Makefile @@ -262,16 +262,16 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_WIN32) endif ifeq ($(TARGET_PLATFORM),PLATFORM_DRM) # On DRM OpenGL ES 2.0 must be used - GRAPHICS = GRAPHICS_API_OPENGL_ES2 + GRAPHICS ?= GRAPHICS_API_OPENGL_ES2 endif ifeq ($(TARGET_PLATFORM),$(filter $(TARGET_PLATFORM),PLATFORM_WEB PLATFORM_WEB_RGFW)) # On HTML5 OpenGL ES 2.0 is used, emscripten translates it to WebGL 1.0 - GRAPHICS = GRAPHICS_API_OPENGL_ES2 + GRAPHICS ?= GRAPHICS_API_OPENGL_ES2 #GRAPHICS = GRAPHICS_API_OPENGL_ES3 endif ifeq ($(TARGET_PLATFORM),PLATFORM_ANDROID) # By default use OpenGL ES 2.0 on Android - GRAPHICS = GRAPHICS_API_OPENGL_ES2 + GRAPHICS ?= GRAPHICS_API_OPENGL_ES2 endif # Define default C compiler and archiver to pack library: CC, AR From 78870335e6e791055c38c007b28bca9eea37947d Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 28 Oct 2025 19:55:35 +0100 Subject: [PATCH 021/430] Update rlgl.h --- src/rlgl.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rlgl.h b/src/rlgl.h index e6a1c9432..99e9037d5 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -21,6 +21,7 @@ * Internal buffer (and resources) must be manually unloaded calling rlglClose() * * CONFIGURATION: +* #define GRAPHICS_API_OPENGL_11_SOFTWARE * #define GRAPHICS_API_OPENGL_11 * #define GRAPHICS_API_OPENGL_21 * #define GRAPHICS_API_OPENGL_33 From f106301d4678a392d67c564666ac77a4d5d1946c Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 28 Oct 2025 20:11:29 +0100 Subject: [PATCH 022/430] ADDED: Some code sample for RISC-V RVV vector instructions -WIP- --- src/external/rlsw.h | 96 ++++++++++++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 31 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index ba2716790..cfb2d236f 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -616,69 +616,74 @@ SWAPI void swBindTexture(uint32_t id); #include // Required for: floorf(), fabsf() #if defined(__FMA__) && defined(__AVX2__) -# define SW_HAS_FMA_AVX2 -# include + #define SW_HAS_FMA_AVX2 + #include #endif #if defined(__FMA__) && defined(__AVX__) -# define SW_HAS_FMA_AVX -# include + #define SW_HAS_FMA_AVX + #include #endif #if defined(__AVX2__) -# define SW_HAS_AVX2 -# include + #define SW_HAS_AVX2 + #include #endif #if defined(__AVX__) -# define SW_HAS_AVX -# include + #define SW_HAS_AVX + #include #endif #if defined(__SSE4_2__) -# define SW_HAS_SSE42 -# include + #define SW_HAS_SSE42 + #include #endif #if defined(__SSE4_1__) -# define SW_HAS_SSE41 -# include + #define SW_HAS_SSE41 + #include #endif #if defined(__SSSE3__) -# define SW_HAS_SSSE3 -# include + #define SW_HAS_SSSE3 + #include #endif #if defined(__SSE3__) -# define SW_HAS_SSE3 -# include + #define SW_HAS_SSE3 + #include #endif #if defined(__SSE2__) -# define SW_HAS_SSE2 -# include + #define SW_HAS_SSE2 + #include #endif #if defined(__SSE__) -# define SW_HAS_SSE -# include + #define SW_HAS_SSE + #include #endif #if defined(__ARM_NEON) || defined(__aarch64__) -# if defined(__ARM_FEATURE_FMA) -# define SW_HAS_NEON_FMA -# else -# define SW_HAS_NEON -# endif -# include + #if defined(__ARM_FEATURE_FMA) + #define SW_HAS_NEON_FMA + #else + #define SW_HAS_NEON + #endif + #include +#endif + +#ifdef __riscv_vector + #define SW_HAS_RVV + #include #endif //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- #define SW_PI 3.14159265358979323846f -#define SW_INV_255 0.00392156862745098f +#define SW_INV_255 0.00392156862745098f // 1.0f/255.0f #define SW_DEG2RAD (SW_PI/180.0f) #define SW_RAD2DEG (180.0f/SW_PI) @@ -1102,6 +1107,27 @@ static inline void sw_float_to_unorm8_simd(uint8_t dst[4], const float src[4]) clamped = _mm_packs_epi32(clamped, clamped); // s32 -> s16 (saturated) clamped = _mm_packus_epi16(clamped, clamped); // s16 -> u8 (saturated < 0 to 0) *(uint32_t*)dst = _mm_cvtsi128_si32(clamped); +#elif defined(SW_HAS_RVV) + // TODO: Sample code generated by AI, needs testing and review + size_t vl = vsetvl_e32m1(4); // Load up to 4 floats into a vector register + vfloat32m1_t vsrc = vle32_v_f32m1(src, vl); // Load float32 values + + // Clamp to [0.0f, 1.0f] + vfloat32m1_t vzero = vfmv_v_f_f32m1(0.0f, vl); + vfloat32m1_t vone = vfmv_v_f_f32m1(1.0f, vl); + vsrc = vfmin_vv_f32m1(vsrc, vone, vl); + vsrc = vfmax_vv_f32m1(vsrc, vzero, vl); + + // Multiply by 255.0f and add 0.5f for rounding + vfloat32m1_t vscaled = vfmul_vf_f32m1(vsrc, 255.0f, vl); + vscaled = vfadd_vf_f32m1(vscaled, 0.5f, vl); + + // Convert to unsigned integer (truncate toward zero) + vuint32m1_t vu32 = vfcvt_xu_f_v_u32m1(vscaled, vl); + + // Narrow from u32 -> u8 + vuint8m1_t vu8 = vnclipu_wx_u8m1(vu32, 0, vl); // Round toward zero + vse8_v_u8m1(dst, vu8, vl); // Store result #else for (int i = 0; i < 4; i++) { @@ -1123,18 +1149,26 @@ static inline void sw_float_from_unorm8_simd(float dst[4], const uint8_t src[4]) floats = vmulq_n_f32(floats, SW_INV_255); vst1q_f32(dst, floats); #elif defined(SW_HAS_SSE41) - __m128i bytes = _mm_cvtsi32_si128(*(const uint32_t*)src); + __m128i bytes = _mm_cvtsi32_si128(*(const uint32_t *)src); __m128i ints = _mm_cvtepu8_epi32(bytes); __m128 floats = _mm_cvtepi32_ps(ints); floats = _mm_mul_ps(floats, _mm_set1_ps(SW_INV_255)); _mm_storeu_ps(dst, floats); #elif defined(SW_HAS_SSE2) - __m128i bytes = _mm_cvtsi32_si128(*(const uint32_t*)src); + __m128i bytes = _mm_cvtsi32_si128(*(const uint32_t *)src); bytes = _mm_unpacklo_epi8(bytes, _mm_setzero_si128()); __m128i ints = _mm_unpacklo_epi16(bytes, _mm_setzero_si128()); __m128 floats = _mm_cvtepi32_ps(ints); floats = _mm_mul_ps(floats, _mm_set1_ps(SW_INV_255)); _mm_storeu_ps(dst, floats); +#elif defined(SW_HAS_RVV) + // TODO: Sample code generated by AI, needs testing and review + size_t vl = vsetvl_e8m1(4); // Set vector length for 8-bit input elements + vuint8m1_t vsrc_u8 = vle8_v_u8m1(src, vl); // Load 4 unsigned 8-bit integers + vuint32m1_t vsrc_u32 = vwcvt_xu_u_v_u32m1(vsrc_u8, vl); // Widen to 32-bit unsigned integers + vfloat32m1_t vsrc_f32 = vfcvt_f_xu_v_f32m1(vsrc_u32, vl); // Convert to float32 + vfloat32m1_t vnorm = vfmul_vf_f32m1(vsrc_f32, SW_INV_255, vl); // Multiply by 1/255.0 to normalize + vse32_v_f32m1(dst, vnorm, vl); // Store result #else dst[0] = (float)src[0]*SW_INV_255; dst[1] = (float)src[1]*SW_INV_255; @@ -2672,8 +2706,8 @@ static inline void FUNC_NAME(void) float ySubstep = 1.0f - sw_fract(v0->screen[1]); \ \ /* Calculation of vertex gradients in X and Y */ \ - float dUdx, dVdx; \ - float dUdy, dVdy; \ + float dUdx = 0.0f, dVdx = 0.0f; \ + float dUdy = 0.0f, dVdy = 0.0f; \ if (ENABLE_TEXTURE) { \ dUdx = (v1->texcoord[0] - v0->texcoord[0])*wRcp; \ dVdx = (v1->texcoord[1] - v0->texcoord[1])*wRcp; \ From a844a943b5c89fc7c1bef550bd24cd5109649fa7 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 28 Oct 2025 20:45:21 +0100 Subject: [PATCH 023/430] It seems alignas() is C11 and raylib is C99, so not fully supported #5312 Added a workaround but it has other probably undesired implications --- src/external/rlsw.h | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index cfb2d236f..6f59bba95 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -610,11 +610,19 @@ SWAPI void swBindTexture(uint32_t id); #define RLSW_IMPLEMENTATION #if defined(RLSW_IMPLEMENTATION) -#include #include #include #include // Required for: floorf(), fabsf() +#if defined(_MSC_VER) + #define ALIGNAS(x) __declspec(align(x)) +#elif defined(__GNUC__) || defined(__clang__) + #define ALIGNAS(x) __attribute__((aligned(x))) +#else + #include + #define ALIGNAS(x) alignas(x) +#endif + #if defined(__FMA__) && defined(__AVX2__) #define SW_HAS_FMA_AVX2 #include @@ -687,8 +695,8 @@ SWAPI void swBindTexture(uint32_t id); #define SW_DEG2RAD (SW_PI/180.0f) #define SW_RAD2DEG (180.0f/SW_PI) -#define SW_COLOR_PIXEL_SIZE (SW_COLOR_BUFFER_BITS/8) -#define SW_DEPTH_PIXEL_SIZE (SW_DEPTH_BUFFER_BITS/8) +#define SW_COLOR_PIXEL_SIZE 4 //(SW_COLOR_BUFFER_BITS >> 3) +#define SW_DEPTH_PIXEL_SIZE (SW_DEPTH_BUFFER_BITS >> 3) #if (SW_COLOR_BUFFER_BITS == 8) #define SW_COLOR_TYPE uint8_t @@ -817,14 +825,15 @@ typedef struct { float ty; // Texel height } sw_texture_t; -typedef struct { - alignas(SW_COLOR_PIXEL_SIZE) +// Pixel data type +// WARNING: ALIGNAS() macro requires a constant value (not operand) +typedef ALIGNAS(SW_COLOR_PIXEL_SIZE) struct { SW_COLOR_TYPE color[SW_COLOR_PACK_COMP]; SW_DEPTH_TYPE depth[SW_DEPTH_PACK_COMP]; } sw_pixel_t; typedef struct { - sw_pixel_t* pixels; + sw_pixel_t *pixels; int width; int height; int allocSz; From bf5c00f7e063ee0d236a48a9ba2be799d84c8b4c Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 28 Oct 2025 20:57:31 +0100 Subject: [PATCH 024/430] RE-ADDED: `swGetColorBuffer()` for convenience #5312 `PLATFORM_DRM` depends on it but if there is a better approach to get the buffer, it can just be removed again and replaced by alternative. --- src/external/rlsw.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index 6f59bba95..f6c15ee1d 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -531,6 +531,7 @@ SWAPI void swClose(void); SWAPI bool swResizeFramebuffer(int w, int h); SWAPI void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, void *pixels); SWAPI void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySrc, int wSrc, int hSrc, SWformat format, SWtype type, void *pixels); +SWAPI void *swGetColorBuffer(int *w, int *h); SWAPI void swEnable(SWstate state); SWAPI void swDisable(SWstate state); @@ -3695,6 +3696,14 @@ void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySr } } +void *swGetColorBuffer(int *w, int *h) +{ + if (w) *w = RLSW.framebuffer.width; + if (h) *h = RLSW.framebuffer.height; + + return (void *)RLSW.framebuffer.pixels->color; +} + void swEnable(SWstate state) { switch (state) From cbff0fa22c9b9eb9662971731c4cee2c6898661a Mon Sep 17 00:00:00 2001 From: Le Juez Victor <90587919+Bigfoot71@users.noreply.github.com> Date: Tue, 28 Oct 2025 23:50:04 +0100 Subject: [PATCH 025/430] [rlsw] Fix axis aligned quad detection (#5314) * fix `sw_quad_is_axis_aligned` * align fix * remove swGetColorBuffer and tweak DRM * review alignment --- src/external/rlsw.h | 96 +++++++++++++++++++++++++-------------- src/platforms/rcore_drm.c | 21 +-------- 2 files changed, 63 insertions(+), 54 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index f6c15ee1d..78ff3fa59 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -531,7 +531,6 @@ SWAPI void swClose(void); SWAPI bool swResizeFramebuffer(int w, int h); SWAPI void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, void *pixels); SWAPI void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySrc, int wSrc, int hSrc, SWformat format, SWtype type, void *pixels); -SWAPI void *swGetColorBuffer(int *w, int *h); SWAPI void swEnable(SWstate state); SWAPI void swDisable(SWstate state); @@ -616,12 +615,23 @@ SWAPI void swBindTexture(uint32_t id); #include // Required for: floorf(), fabsf() #if defined(_MSC_VER) - #define ALIGNAS(x) __declspec(align(x)) + #define SW_ALIGN(x) __declspec(align(x)) #elif defined(__GNUC__) || defined(__clang__) - #define ALIGNAS(x) __attribute__((aligned(x))) + #define SW_ALIGN(x) __attribute__((aligned(x))) #else - #include - #define ALIGNAS(x) alignas(x) + #define SW_ALIGN(x) // Do nothing if not available +#endif + +#if defined(_M_X64) || defined(__x86_64__) + #define SW_ARCH_X86_64 +#elif defined(_M_IX86) || defined(__i386__) + #define SW_ARCH_X86 +#elif defined(_M_ARM) || defined(__arm__) + #define SW_ARCH_ARM32 +#elif defined(_M_ARM64) || defined(__aarch64__) + #define SW_ARCH_ARM64 +#elif defined(__riscv) + #define SW_ARCH_RISCV #endif #if defined(__FMA__) && defined(__AVX2__) @@ -696,8 +706,15 @@ SWAPI void swBindTexture(uint32_t id); #define SW_DEG2RAD (SW_PI/180.0f) #define SW_RAD2DEG (180.0f/SW_PI) -#define SW_COLOR_PIXEL_SIZE 4 //(SW_COLOR_BUFFER_BITS >> 3) +#define SW_COLOR_PIXEL_SIZE (SW_COLOR_BUFFER_BITS >> 3) #define SW_DEPTH_PIXEL_SIZE (SW_DEPTH_BUFFER_BITS >> 3) +#define SW_PIXEL_SIZE (SW_COLOR_PIXEL_SIZE + SW_DEPTH_PIXEL_SIZE) + +#if (SW_PIXEL_SIZE <= 4) + #define SW_PIXEL_ALIGNMENT 4 +#else // if (SW_PIXEL_SIZE <= 8) + #define SW_PIXEL_ALIGNMENT 8 +#endif #if (SW_COLOR_BUFFER_BITS == 8) #define SW_COLOR_TYPE uint8_t @@ -827,10 +844,12 @@ typedef struct { } sw_texture_t; // Pixel data type -// WARNING: ALIGNAS() macro requires a constant value (not operand) -typedef ALIGNAS(SW_COLOR_PIXEL_SIZE) struct { +typedef SW_ALIGN(SW_PIXEL_ALIGNMENT) struct { SW_COLOR_TYPE color[SW_COLOR_PACK_COMP]; SW_DEPTH_TYPE depth[SW_DEPTH_PACK_COMP]; +#if (SW_PIXEL_SIZE % SW_PIXEL_ALIGNMENT != 0) + uint8_t padding[SW_PIXEL_ALIGNMENT - SW_PIXEL_SIZE % SW_PIXEL_ALIGNMENT]; +#endif } sw_pixel_t; typedef struct { @@ -2624,25 +2643,38 @@ static inline void sw_quad_clip_and_project(void) static inline bool sw_quad_is_axis_aligned(void) { - int horizontal = 0; - int vertical = 0; - + // Reject quads with perspective projection + // The fast path assumes affine (non-perspective) quads, + // so we require all vertices to have homogeneous w = 1.0 for (int i = 0; i < 4; i++) { if (RLSW.vertexBuffer[i].homogeneous[3] != 1.0f) return false; - - const float *v0 = RLSW.vertexBuffer[i].position; - const float *v1 = RLSW.vertexBuffer[(i + 1)%4].position; - - float dx = v1[0] - v0[0]; - float dy = v1[1] - v0[1]; - - if ((fabsf(dx) > 1e-6f) && (fabsf(dy) < 1e-6f)) horizontal++; - else if ((fabsf(dy) > 1e-6f) && (fabsf(dx) < 1e-6f)) vertical++; - else return false; // Diagonal edge -> not axis-aligned } - return ((horizontal == 2) && (vertical == 2)); + // Epsilon tolerance in screen space (pixels) + const float epsilon = 0.5f; + + // Fetch screen-space positions for the four quad vertices + const float *p0 = RLSW.vertexBuffer[0].screen; + const float *p1 = RLSW.vertexBuffer[1].screen; + const float *p2 = RLSW.vertexBuffer[2].screen; + const float *p3 = RLSW.vertexBuffer[3].screen; + + // Compute edge vectors between consecutive vertices + // These define the four sides of the quad in screen space + float dx01 = p1[0] - p0[0], dy01 = p1[1] - p0[1]; + float dx12 = p2[0] - p1[0], dy12 = p2[1] - p1[1]; + float dx23 = p3[0] - p2[0], dy23 = p3[1] - p2[1]; + float dx30 = p0[0] - p3[0], dy30 = p0[1] - p3[1]; + + // Each edge must be either horizontal or vertical within epsilon tolerance + // If any edge deviates significantly from either axis, the quad is not axis-aligned + if (!((fabsf(dy01) < epsilon) || (fabsf(dx01) < epsilon))) return false; + if (!((fabsf(dy12) < epsilon) || (fabsf(dx12) < epsilon))) return false; + if (!((fabsf(dy23) < epsilon) || (fabsf(dx23) < epsilon))) return false; + if (!((fabsf(dy30) < epsilon) || (fabsf(dx30) < epsilon))) return false; + + return true; } static inline void sw_quad_sort_cw(const sw_vertex_t* *output) @@ -3660,11 +3692,6 @@ void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySr { sw_pixelformat_t pFormat = (sw_pixelformat_t)sw_get_pixel_format(format, type); - if (xDst == xSrc && yDst == ySrc && wDst == wSrc && hDst == hSrc) - { - swCopyFramebuffer(xSrc, ySrc, wSrc, hSrc, format, type, pixels); - } - if (wSrc <= 0) { RLSW.errCode = SW_INVALID_VALUE; return; } if (hSrc <= 0) { RLSW.errCode = SW_INVALID_VALUE; return; } @@ -3674,6 +3701,13 @@ void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySr xSrc = sw_clampi(xSrc, 0, wSrc); ySrc = sw_clampi(ySrc, 0, hSrc); + // Check if the sizes are identical after clamping the source to avoid unexpected issues + // REVIEW: This repeats the operations if true, so we could make a copy function without these checks + if (xDst == xSrc && yDst == ySrc && wDst == wSrc && hDst == hSrc) + { + swCopyFramebuffer(xSrc, ySrc, wSrc, hSrc, format, type, pixels); + } + switch (pFormat) { case SW_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: sw_framebuffer_blit_to_GRAYALPHA(xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, (uint8_t *)pixels); break; @@ -3696,14 +3730,6 @@ void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySr } } -void *swGetColorBuffer(int *w, int *h) -{ - if (w) *w = RLSW.framebuffer.width; - if (h) *h = RLSW.framebuffer.height; - - return (void *)RLSW.framebuffer.pixels->color; -} - void swEnable(SWstate state) { switch (state) diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index a08a76bce..da0d08aca 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -824,15 +824,6 @@ void SwapScreenBuffer(void) return; } - // Get the software rendered color buffer - int bufferWidth = 0, bufferHeight = 0; - void *colorBuffer = swGetColorBuffer(&bufferWidth, &bufferHeight); - if (!colorBuffer) - { - TRACELOG(LOG_ERROR, "DISPLAY: Failed to get software color buffer"); - return; - } - // Retrieving the dimensions of the display mode used drmModeModeInfo *mode = &platform.connector->modes[platform.modeIndex]; uint32_t width = mode->hdisplay; @@ -900,16 +891,8 @@ void SwapScreenBuffer(void) } // Copy the software rendered buffer to the dumb buffer with scaling if needed - if (bufferWidth == width && bufferHeight == height) - { - // Direct copy if sizes match - swCopyFramebuffer(0, 0, bufferWidth, bufferHeight, SW_RGBA, SW_UNSIGNED_BYTE, dumbBuffer); - } - else - { - // Scale the software buffer to match the display mode - swBlitFramebuffer(0, 0, width, height, 0, 0, bufferWidth, bufferHeight, SW_RGBA, SW_UNSIGNED_BYTE, dumbBuffer); - } + // NOTE: RLSW will make a simple copy if the dimensions match + swBlitFramebuffer(0, 0, width, height, 0, 0, width, height, SW_RGBA, SW_UNSIGNED_BYTE, dumbBuffer); // Unmap the buffer munmap(dumbBuffer, creq.size); From f16d5ce1ddb0b0a447a30ef02d6a5a76d6fea4e2 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 29 Oct 2025 11:12:04 +0100 Subject: [PATCH 026/430] REVIEWED: Make sure SSE is being used when compiling with MSVC Added log info and some formatting for visibility --- src/external/rlsw.h | 167 ++++++++++++++++++++++++-------------------- 1 file changed, 91 insertions(+), 76 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index 78ff3fa59..ab8756e56 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -94,11 +94,9 @@ #ifndef SW_MALLOC #define SW_MALLOC(sz) malloc(sz) #endif - #ifndef SW_REALLOC #define SW_REALLOC(ptr, newSz) realloc(ptr, newSz) #endif - #ifndef SW_FREE #define SW_FREE(ptr) free(ptr) #endif @@ -152,12 +150,6 @@ #define SW_CLIP_EPSILON 1e-4f #endif -#ifdef __cplusplus - #define CURLY_INIT(name) name -#else - #define CURLY_INIT(name) (name) -#endif - //---------------------------------------------------------------------------------- // OpenGL Compatibility Types //---------------------------------------------------------------------------------- @@ -610,9 +602,19 @@ SWAPI void swBindTexture(uint32_t id); #define RLSW_IMPLEMENTATION #if defined(RLSW_IMPLEMENTATION) -#include -#include -#include // Required for: floorf(), fabsf() +#include // Required for: malloc(), free() +#include // Required for: NULL, size_t, uint8_t, uint16_t, uint32_t... +#include // Required for: sinf(), cosf(), floorf(), fabsf(), sqrtf(), roundf() + +// Simple log system to avoid printf() calls if required +// NOTE: Avoiding those calls, also avoids const strings memory usage +#define SW_SUPPORT_LOG_INFO +#if defined(SW_SUPPORT_LOG_INFO) //&& defined(_DEBUG) // WARNING: LOG() output required for this tool + #include + #define SW_LOG(...) printf(__VA_ARGS__) +#else + #define SW_LOG(...) +#endif #if defined(_MSC_VER) #define SW_ALIGN(x) __declspec(align(x)) @@ -634,56 +636,47 @@ SWAPI void swBindTexture(uint32_t id); #define SW_ARCH_RISCV #endif +// Check for SIMD vector instructions #if defined(__FMA__) && defined(__AVX2__) #define SW_HAS_FMA_AVX2 #include #endif - #if defined(__FMA__) && defined(__AVX__) #define SW_HAS_FMA_AVX #include #endif - #if defined(__AVX2__) #define SW_HAS_AVX2 #include #endif - #if defined(__AVX__) #define SW_HAS_AVX #include #endif - #if defined(__SSE4_2__) #define SW_HAS_SSE42 #include #endif - #if defined(__SSE4_1__) #define SW_HAS_SSE41 #include #endif - #if defined(__SSSE3__) #define SW_HAS_SSSE3 #include #endif - #if defined(__SSE3__) #define SW_HAS_SSE3 #include #endif - -#if defined(__SSE2__) +#if defined(__SSE2__) || (defined(_M_AMD64) || defined(_M_X64)) // SSE2 x64 #define SW_HAS_SSE2 #include #endif - #if defined(__SSE__) #define SW_HAS_SSE #include #endif - #if defined(__ARM_NEON) || defined(__aarch64__) #if defined(__ARM_FEATURE_FMA) #define SW_HAS_NEON_FMA @@ -692,12 +685,17 @@ SWAPI void swBindTexture(uint32_t id); #endif #include #endif - -#ifdef __riscv_vector +#if defined(__riscv_vector) #define SW_HAS_RVV #include #endif +#ifdef __cplusplus + #define SW_CURLY_INIT(name) name +#else + #define SW_CURLY_INIT(name) (name) +#endif + //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -717,68 +715,68 @@ SWAPI void swBindTexture(uint32_t id); #endif #if (SW_COLOR_BUFFER_BITS == 8) - #define SW_COLOR_TYPE uint8_t - #define SW_COLOR_IS_PACKED 1 - #define SW_COLOR_PACK_COMP 1 + #define SW_COLOR_TYPE uint8_t + #define SW_COLOR_IS_PACKED 1 + #define SW_COLOR_PACK_COMP 1 #define SW_PACK_COLOR(r,g,b) ((((uint8_t)((r)*7+0.5f))&0x07)<<5 | (((uint8_t)((g)*7+0.5f))&0x07)<<2 | ((uint8_t)((b)*3+0.5f))&0x03) - #define SW_UNPACK_R(p) (((p)>>5)&0x07) - #define SW_UNPACK_G(p) (((p)>>2)&0x07) - #define SW_UNPACK_B(p) ((p)&0x03) - #define SW_SCALE_R(v) ((v)*255+3)/7 - #define SW_SCALE_G(v) ((v)*255+3)/7 - #define SW_SCALE_B(v) ((v)*255+1)/3 - #define SW_TO_FLOAT_R(v) ((v)*(1.0f/7.0f)) - #define SW_TO_FLOAT_G(v) ((v)*(1.0f/7.0f)) - #define SW_TO_FLOAT_B(v) ((v)*(1.0f/3.0f)) + #define SW_UNPACK_R(p) (((p)>>5)&0x07) + #define SW_UNPACK_G(p) (((p)>>2)&0x07) + #define SW_UNPACK_B(p) ((p)&0x03) + #define SW_SCALE_R(v) ((v)*255+3)/7 + #define SW_SCALE_G(v) ((v)*255+3)/7 + #define SW_SCALE_B(v) ((v)*255+1)/3 + #define SW_TO_FLOAT_R(v) ((v)*(1.0f/7.0f)) + #define SW_TO_FLOAT_G(v) ((v)*(1.0f/7.0f)) + #define SW_TO_FLOAT_B(v) ((v)*(1.0f/3.0f)) #elif (SW_COLOR_BUFFER_BITS == 16) - #define SW_COLOR_TYPE uint16_t - #define SW_COLOR_IS_PACKED 1 - #define SW_COLOR_PACK_COMP 1 + #define SW_COLOR_TYPE uint16_t + #define SW_COLOR_IS_PACKED 1 + #define SW_COLOR_PACK_COMP 1 #define SW_PACK_COLOR(r,g,b) ((((uint16_t)((r)*31+0.5f))&0x1F)<<11 | (((uint16_t)((g)*63+0.5f))&0x3F)<<5 | ((uint16_t)((b)*31+0.5f))&0x1F) - #define SW_UNPACK_R(p) (((p)>>11)&0x1F) - #define SW_UNPACK_G(p) (((p)>>5)&0x3F) - #define SW_UNPACK_B(p) ((p)&0x1F) - #define SW_SCALE_R(v) ((v)*255+15)/31 - #define SW_SCALE_G(v) ((v)*255+31)/63 - #define SW_SCALE_B(v) ((v)*255+15)/31 - #define SW_TO_FLOAT_R(v) ((v)*(1.0f/31.0f)) - #define SW_TO_FLOAT_G(v) ((v)*(1.0f/63.0f)) - #define SW_TO_FLOAT_B(v) ((v)*(1.0f/31.0f)) + #define SW_UNPACK_R(p) (((p)>>11)&0x1F) + #define SW_UNPACK_G(p) (((p)>>5)&0x3F) + #define SW_UNPACK_B(p) ((p)&0x1F) + #define SW_SCALE_R(v) ((v)*255+15)/31 + #define SW_SCALE_G(v) ((v)*255+31)/63 + #define SW_SCALE_B(v) ((v)*255+15)/31 + #define SW_TO_FLOAT_R(v) ((v)*(1.0f/31.0f)) + #define SW_TO_FLOAT_G(v) ((v)*(1.0f/63.0f)) + #define SW_TO_FLOAT_B(v) ((v)*(1.0f/31.0f)) #else // 32 bits - #define SW_COLOR_TYPE uint8_t - #define SW_COLOR_IS_PACKED 0 - #define SW_COLOR_PACK_COMP 4 + #define SW_COLOR_TYPE uint8_t + #define SW_COLOR_IS_PACKED 0 + #define SW_COLOR_PACK_COMP 4 #endif #if (SW_DEPTH_BUFFER_BITS == 8) - #define SW_DEPTH_TYPE uint8_t - #define SW_DEPTH_IS_PACKED 1 - #define SW_DEPTH_PACK_COMP 1 - #define SW_DEPTH_MAX UINT8_MAX - #define SW_DEPTH_SCALE (1.0f/UINT8_MAX) - #define SW_PACK_DEPTH(d) ((SW_DEPTH_TYPE)((d)*SW_DEPTH_MAX)) - #define SW_UNPACK_DEPTH(p) (p) + #define SW_DEPTH_TYPE uint8_t + #define SW_DEPTH_IS_PACKED 1 + #define SW_DEPTH_PACK_COMP 1 + #define SW_DEPTH_MAX UINT8_MAX + #define SW_DEPTH_SCALE (1.0f/UINT8_MAX) + #define SW_PACK_DEPTH(d) ((SW_DEPTH_TYPE)((d)*SW_DEPTH_MAX)) + #define SW_UNPACK_DEPTH(p) (p) #elif (SW_DEPTH_BUFFER_BITS == 16) - #define SW_DEPTH_TYPE uint16_t - #define SW_DEPTH_IS_PACKED 1 - #define SW_DEPTH_PACK_COMP 1 - #define SW_DEPTH_MAX UINT16_MAX - #define SW_DEPTH_SCALE (1.0f/UINT16_MAX) - #define SW_PACK_DEPTH(d) ((SW_DEPTH_TYPE)((d)*SW_DEPTH_MAX)) - #define SW_UNPACK_DEPTH(p) (p) + #define SW_DEPTH_TYPE uint16_t + #define SW_DEPTH_IS_PACKED 1 + #define SW_DEPTH_PACK_COMP 1 + #define SW_DEPTH_MAX UINT16_MAX + #define SW_DEPTH_SCALE (1.0f/UINT16_MAX) + #define SW_PACK_DEPTH(d) ((SW_DEPTH_TYPE)((d)*SW_DEPTH_MAX)) + #define SW_UNPACK_DEPTH(p) (p) #else // 24 bits - #define SW_DEPTH_TYPE uint8_t - #define SW_DEPTH_IS_PACKED 0 - #define SW_DEPTH_PACK_COMP 3 - #define SW_DEPTH_MAX 0xFFFFFF - #define SW_DEPTH_SCALE (1.0f/0xFFFFFF) - #define SW_PACK_DEPTH_0(d) (((uint32_t)((d)*SW_DEPTH_MAX)>>16)&0xFF) - #define SW_PACK_DEPTH_1(d) (((uint32_t)((d)*SW_DEPTH_MAX)>>8)&0xFF) - #define SW_PACK_DEPTH_2(d) ((uint32_t)((d)*SW_DEPTH_MAX)&0xFF) - #define SW_UNPACK_DEPTH(p) (((p)[0]<<16)|((p)[1]<<8)|(p)[2]) + #define SW_DEPTH_TYPE uint8_t + #define SW_DEPTH_IS_PACKED 0 + #define SW_DEPTH_PACK_COMP 3 + #define SW_DEPTH_MAX 0xFFFFFF + #define SW_DEPTH_SCALE (1.0f/0xFFFFFF) + #define SW_PACK_DEPTH_0(d) (((uint32_t)((d)*SW_DEPTH_MAX)>>16)&0xFF) + #define SW_PACK_DEPTH_1(d) (((uint32_t)((d)*SW_DEPTH_MAX)>>8)&0xFF) + #define SW_PACK_DEPTH_2(d) ((uint32_t)((d)*SW_DEPTH_MAX)&0xFF) + #define SW_UNPACK_DEPTH(p) (((p)[0]<<16)|((p)[1]<<8)|(p)[2]) #endif -#define SW_STATE_CHECK(flags) (SW_STATE_CHECK_EX(RLSW.stateFlags, (flags))) +#define SW_STATE_CHECK(flags) (SW_STATE_CHECK_EX(RLSW.stateFlags, (flags))) #define SW_STATE_CHECK_EX(state, flags) (((state) & (flags)) == (flags)) #define SW_STATE_SCISSOR_TEST (1 << 0) @@ -3607,6 +3605,23 @@ bool swInit(int w, int h) RLSW.loadedTextures[0].ty = 0.5f; RLSW.loadedTextureCount = 1; + + SW_LOG("INFO: RLSW: Software renderer initialized successfully\n"); +#if defined(SW_HAS_FMA_AVX) && defined(SW_HAS_FMA_AVX2) + SW_LOG("INFO: RLSW: Using SIMD instructions: FMA AVX\n"); +#endif +#if defined(SW_HAS_AVX) || defined(SW_HAS_AVX2) + SW_LOG("INFO: RLSW: Using SIMD instructions: AVX\n"); +#endif +#if defined(SW_HAS_SSE) || defined(SW_HAS_SSE2) || defined(SW_HAS_SSE3) || defined(SW_HAS_SSE41) || defined(SW_HAS_SSE42) + SW_LOG("INFO: RLSW: Using SIMD instructions: SSE\n"); +#endif +#if defined(SW_HAS_NEON_FMA) || defined(SW_HAS_NEON) + SW_LOG("INFO: RLSW: Using SIMD instructions: NEON\n"); +#endif +#if defined(SW_HAS_RVV) + SW_LOG("INFO: RLSW: Using SIMD instructions: RVV\n"); +#endif return true; } @@ -3626,7 +3641,7 @@ void swClose(void) SW_FREE(RLSW.loadedTextures); SW_FREE(RLSW.freeTextureIds); - RLSW = CURLY_INIT(sw_context_t) { 0 }; + RLSW = SW_CURLY_INIT(sw_context_t) { 0 }; } bool swResizeFramebuffer(int w, int h) From 3389c80f498307a7546d868f7a9fcf18d23c65ae Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 29 Oct 2025 11:33:08 +0100 Subject: [PATCH 027/430] Update rlsw.h --- src/external/rlsw.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index ab8756e56..f102c3efd 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -3613,7 +3613,7 @@ bool swInit(int w, int h) #if defined(SW_HAS_AVX) || defined(SW_HAS_AVX2) SW_LOG("INFO: RLSW: Using SIMD instructions: AVX\n"); #endif -#if defined(SW_HAS_SSE) || defined(SW_HAS_SSE2) || defined(SW_HAS_SSE3) || defined(SW_HAS_SSE41) || defined(SW_HAS_SSE42) +#if defined(SW_HAS_SSE) || defined(SW_HAS_SSE2) || defined(SW_HAS_SSE3) || defined(SW_HAS_SSSE3) || defined(SW_HAS_SSE41) || defined(SW_HAS_SSE42) SW_LOG("INFO: RLSW: Using SIMD instructions: SSE\n"); #endif #if defined(SW_HAS_NEON_FMA) || defined(SW_HAS_NEON) From 127cc1c79ee93f957907f7824eee75ac00dde095 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 29 Oct 2025 22:45:37 +0100 Subject: [PATCH 028/430] REVIEWED: Makefile to support software renderer --- examples/Makefile | 14 +++++++++++++- src/Makefile | 13 +++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/examples/Makefile b/examples/Makefile index c8500b665..b5e968079 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -486,7 +486,19 @@ endif ifeq ($(TARGET_PLATFORM),PLATFORM_DRM) # Libraries for DRM compiling # NOTE: Required packages: libasound2-dev (ALSA) - LDLIBS = -lraylib -lGLESv2 -lEGL -lpthread -lrt -lm -lgbm -ldrm -ldl -latomic + LDLIBS = -lraylib -lGLESv2 -lEGL -ldrm -lgbm -lpthread -lrt -lm -ldl -latomic + # TODO: Examples compilation does not define GRAPHICS, is it required? + #ifeq ($(GRAPHICS),GRAPHICS_API_OPENGL_ES2) + # LDLIBS += -lGLESv2 -lEGL + #endif +endif +ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_WIN32) + # Libraries for Windows desktop compilation + LDFLAGS += -L..\src + LDLIBS = -lraylib -lgdi32 -lwinmm -lshcore + ifneq ($(GRAPHICS),GRAPHICS_API_OPENGL_11_SOFTWARE) + LDLIBS += -lopengl32 + endif endif ifeq ($(TARGET_PLATFORM),$(filter $(TARGET_PLATFORM),PLATFORM_WEB PLATFORM_WEB_RGFW)) # Libraries for web (HTML5) compiling diff --git a/src/Makefile b/src/Makefile index 0895d8f95..41867da1c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -228,7 +228,6 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_ANDROID) ifeq ($(ANDROID_ARCH),x86_64) ANDROID_COMPILER_ARCH = x86_64 endif - endif # Define raylib graphics api depending on selected platform @@ -261,8 +260,8 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_WIN32) #GRAPHICS = GRAPHICS_API_OPENGL_ES2 # Uncomment to use OpenGL ES 2.0 (ANGLE) endif ifeq ($(TARGET_PLATFORM),PLATFORM_DRM) - # On DRM OpenGL ES 2.0 must be used GRAPHICS ?= GRAPHICS_API_OPENGL_ES2 + #GRAPHICS = GRAPHICS_API_OPENGL_11_SOFTWARE # Uncomment to use software rendering endif ifeq ($(TARGET_PLATFORM),$(filter $(TARGET_PLATFORM),PLATFORM_WEB PLATFORM_WEB_RGFW)) # On HTML5 OpenGL ES 2.0 is used, emscripten translates it to WebGL 1.0 @@ -636,13 +635,19 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_RGFW) endif endif ifeq ($(TARGET_PLATFORM),PLATFORM_DRM) - LDLIBS = -lGLESv2 -lEGL -ldrm -lgbm -lpthread -lrt -lm -ldl + LDLIBS = -ldrm -lgbm -lpthread -lrt -lm -ldl + ifeq ($(GRAPHICS),GRAPHICS_API_OPENGL_ES2) + LDLIBS += -lGLESv2 -lEGL + endif ifeq ($(RAYLIB_MODULE_AUDIO),TRUE) LDLIBS += -latomic endif endif ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_WIN32) - LDLIBS = -lgdi32 -lwinmm -lopengl32 -lshcore + LDLIBS = -lgdi32 -lwinmm -lshcore + ifneq ($(GRAPHICS),GRAPHICS_API_OPENGL_11_SOFTWARE) + LDLIBS += -lopengl32 + endif endif ifeq ($(TARGET_PLATFORM),PLATFORM_ANDROID) LDLIBS = -llog -landroid -lEGL -lGLESv2 -lOpenSLES -lc -lm From b2d455400c3946461943902e2b071eaf49407111 Mon Sep 17 00:00:00 2001 From: Dave Goehrig Date: Thu, 30 Oct 2025 19:15:14 +0100 Subject: [PATCH 029/430] Adding SwiftForth language binding (#5319) --- BINDINGS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BINDINGS.md b/BINDINGS.md index 7770f41c2..f2ffaea9e 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -31,6 +31,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [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 | | [raylib-freebasic](https://github.com/WIITD/raylib-freebasic) | **5.0** | [FreeBASIC](https://www.freebasic.net) | MIT | +| [raylib.f](https://github.com/cthulhuology/raylib.f) | **5.5** | [Forth](https://forth.com) | Zlib | | [fortran-raylib](https://github.com/interkosmos/fortran-raylib) | **5.5** | [Fortran](https://fortran-lang.org) | ISC | | [raylib-go](https://github.com/gen2brain/raylib-go) | **5.5** | [Go](https://golang.org) | Zlib | | [raylib-guile](https://github.com/petelliott/raylib-guile) | **auto** | [Guile](https://www.gnu.org/software/guile) | Zlib | From bca54047f9bb735e9fbc3173674a9c1e6503a708 Mon Sep 17 00:00:00 2001 From: Le Juez Victor <90587919+Bigfoot71@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:17:18 +0100 Subject: [PATCH 030/430] [rlsw] Review depth formats and fix depth writing (#5317) * review depth format/writing * adding a note --- src/external/rlsw.h | 58 +++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index f102c3efd..9c510e945 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -114,11 +114,11 @@ #endif #ifndef SW_COLOR_BUFFER_BITS - #define SW_COLOR_BUFFER_BITS 32 //< 32 (rgba), 16 (rgb packed) or 8 (rgb packed) + #define SW_COLOR_BUFFER_BITS 32 //< 32 (rgba), 16 (rgb packed) or 8 (rgb packed) #endif #ifndef SW_DEPTH_BUFFER_BITS - #define SW_DEPTH_BUFFER_BITS 16 //< 24, 16 or 8 + #define SW_DEPTH_BUFFER_BITS 16 //< 32, 24 or 16 #endif #ifndef SW_MAX_PROJECTION_STACK_SIZE @@ -748,32 +748,32 @@ SWAPI void swBindTexture(uint32_t id); #define SW_COLOR_PACK_COMP 4 #endif -#if (SW_DEPTH_BUFFER_BITS == 8) - #define SW_DEPTH_TYPE uint8_t - #define SW_DEPTH_IS_PACKED 1 - #define SW_DEPTH_PACK_COMP 1 - #define SW_DEPTH_MAX UINT8_MAX - #define SW_DEPTH_SCALE (1.0f/UINT8_MAX) - #define SW_PACK_DEPTH(d) ((SW_DEPTH_TYPE)((d)*SW_DEPTH_MAX)) - #define SW_UNPACK_DEPTH(p) (p) -#elif (SW_DEPTH_BUFFER_BITS == 16) - #define SW_DEPTH_TYPE uint16_t - #define SW_DEPTH_IS_PACKED 1 - #define SW_DEPTH_PACK_COMP 1 - #define SW_DEPTH_MAX UINT16_MAX - #define SW_DEPTH_SCALE (1.0f/UINT16_MAX) - #define SW_PACK_DEPTH(d) ((SW_DEPTH_TYPE)((d)*SW_DEPTH_MAX)) - #define SW_UNPACK_DEPTH(p) (p) -#else // 24 bits - #define SW_DEPTH_TYPE uint8_t - #define SW_DEPTH_IS_PACKED 0 - #define SW_DEPTH_PACK_COMP 3 - #define SW_DEPTH_MAX 0xFFFFFF - #define SW_DEPTH_SCALE (1.0f/0xFFFFFF) - #define SW_PACK_DEPTH_0(d) (((uint32_t)((d)*SW_DEPTH_MAX)>>16)&0xFF) - #define SW_PACK_DEPTH_1(d) (((uint32_t)((d)*SW_DEPTH_MAX)>>8)&0xFF) - #define SW_PACK_DEPTH_2(d) ((uint32_t)((d)*SW_DEPTH_MAX)&0xFF) - #define SW_UNPACK_DEPTH(p) (((p)[0]<<16)|((p)[1]<<8)|(p)[2]) +#if (SW_DEPTH_BUFFER_BITS == 16) + #define SW_DEPTH_TYPE uint16_t + #define SW_DEPTH_IS_PACKED 1 + #define SW_DEPTH_PACK_COMP 1 + #define SW_DEPTH_MAX UINT16_MAX + #define SW_DEPTH_SCALE (1.0f/UINT16_MAX) + #define SW_PACK_DEPTH(d) ((SW_DEPTH_TYPE)((d)*SW_DEPTH_MAX)) + #define SW_UNPACK_DEPTH(p) (p) +#elif (SW_DEPTH_BUFFER_BITS == 24) + #define SW_DEPTH_TYPE uint8_t + #define SW_DEPTH_IS_PACKED 0 + #define SW_DEPTH_PACK_COMP 3 + #define SW_DEPTH_MAX 0xFFFFFFU + #define SW_DEPTH_SCALE (1.0f/0xFFFFFFU) + #define SW_PACK_DEPTH_0(d) ((uint8_t)(((uint32_t)((d)*SW_DEPTH_MAX)>>16)&0xFFU)) + #define SW_PACK_DEPTH_1(d) ((uint8_t)(((uint32_t)((d)*SW_DEPTH_MAX)>>8)&0xFFU)) + #define SW_PACK_DEPTH_2(d) ((uint8_t)((uint32_t)((d)*SW_DEPTH_MAX)&0xFFU)) + #define SW_UNPACK_DEPTH(p) ((((uint32_t)(p)[0]<<16)|((uint32_t)(p)[1]<<8)|(uint32_t)(p)[2])) +#else // 32 bits + #define SW_DEPTH_TYPE float + #define SW_DEPTH_IS_PACKED 1 + #define SW_DEPTH_PACK_COMP 1 + #define SW_DEPTH_MAX 1.0f + #define SW_DEPTH_SCALE 1.0f + #define SW_PACK_DEPTH(d) ((SW_DEPTH_TYPE)(d)) + #define SW_UNPACK_DEPTH(p) (p) #endif #define SW_STATE_CHECK(flags) (SW_STATE_CHECK_EX(RLSW.stateFlags, (flags))) @@ -1346,6 +1346,8 @@ static inline void sw_framebuffer_write_color(sw_pixel_t *dst, const float src[4 static inline void sw_framebuffer_write_depth(sw_pixel_t *dst, float depth) { + depth = sw_saturate(depth); // REVIEW: An overflow can occur in certain circumstances with clipping, and needs to be reviewed... + #if SW_DEPTH_IS_PACKED dst->depth[0] = SW_PACK_DEPTH(depth); #else From dfc94f64d1e1db5231a68e8ea968378df16f2292 Mon Sep 17 00:00:00 2001 From: Arrangemonk <34814431+Arrangemonk@users.noreply.github.com> Date: Fri, 31 Oct 2025 20:43:27 +0100 Subject: [PATCH 031/430] =?UTF-8?q?Revert=20"UpdateModelAnimation=20does?= =?UTF-8?q?=20matrixtranspose(matrixinvert)=20only=20once=20per=E2=80=A6"?= =?UTF-8?q?=20(#5322)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit e3a562ab57a9fa4a9dbaa3d5ffb67f31c25a4a36. --- src/rmodels.c | 79 ++++++++++++++++++++++++--------------------------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index 3c904c396..ed86fb19a 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -2352,8 +2352,6 @@ void UpdateModelAnimation(Model model, ModelAnimation anim, int frame) Mesh mesh = model.meshes[m]; Vector3 animVertex = { 0 }; Vector3 animNormal = { 0 }; - Matrix boneMatrix = { 0 }; - Matrix InverseBoneMatrix = { 0 }; int boneId = 0; int boneCounter = 0; float boneWeight = 0.0; @@ -2361,50 +2359,47 @@ void UpdateModelAnimation(Model model, ModelAnimation anim, int frame) const int vValues = mesh.vertexCount*3; // Skip if missing bone data, causes segfault without on some models - if ((mesh.boneWeights == NULL) || (mesh.boneIds == NULL)) continue; + if ((mesh.boneWeights == NULL) || (mesh.boneIds == NULL)) continue; - // Iterates over 4 bones per vertex - for (int j = 0; j < 4; j++, boneCounter++) - { - boneWeight = mesh.boneWeights[boneCounter]; - boneId = mesh.boneIds[boneCounter]; + for (int vCounter = 0; vCounter < vValues; vCounter += 3) + { + mesh.animVertices[vCounter] = 0; + mesh.animVertices[vCounter + 1] = 0; + mesh.animVertices[vCounter + 2] = 0; + if (mesh.animNormals != NULL) + { + mesh.animNormals[vCounter] = 0; + mesh.animNormals[vCounter + 1] = 0; + mesh.animNormals[vCounter + 2] = 0; + } - // Early stop when no transformation will be applied - if (boneWeight == 0.0f) continue; + // Iterates over 4 bones per vertex + for (int j = 0; j < 4; j++, boneCounter++) + { + boneWeight = mesh.boneWeights[boneCounter]; + boneId = mesh.boneIds[boneCounter]; - boneMatrix = model.meshes[m].boneMatrices[boneId]; - InverseBoneMatrix = MatrixTranspose(MatrixInvert(boneMatrix)); + // Early stop when no transformation will be applied + if (boneWeight == 0.0f) continue; + animVertex = (Vector3){ mesh.vertices[vCounter], mesh.vertices[vCounter + 1], mesh.vertices[vCounter + 2] }; + animVertex = Vector3Transform(animVertex,model.meshes[m].boneMatrices[boneId]); + mesh.animVertices[vCounter] += animVertex.x*boneWeight; + mesh.animVertices[vCounter+1] += animVertex.y*boneWeight; + mesh.animVertices[vCounter+2] += animVertex.z*boneWeight; + updated = true; - for (int vCounter = 0; vCounter < vValues; vCounter += 3) - { - mesh.animVertices[vCounter] = 0; - mesh.animVertices[vCounter + 1] = 0; - mesh.animVertices[vCounter + 2] = 0; - if (mesh.animNormals != NULL) - { - mesh.animNormals[vCounter] = 0; - mesh.animNormals[vCounter + 1] = 0; - mesh.animNormals[vCounter + 2] = 0; - } - animVertex = (Vector3){ mesh.vertices[vCounter], mesh.vertices[vCounter + 1], mesh.vertices[vCounter + 2] }; - animVertex = Vector3Transform(animVertex, boneMatrix); - mesh.animVertices[vCounter] += animVertex.x*boneWeight; - mesh.animVertices[vCounter+1] += animVertex.y*boneWeight; - mesh.animVertices[vCounter+2] += animVertex.z*boneWeight; - updated = true; - - // Normals processing - // NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals) - if ((mesh.normals != NULL) && (mesh.animNormals != NULL)) - { - animNormal = (Vector3){ mesh.normals[vCounter], mesh.normals[vCounter + 1], mesh.normals[vCounter + 2] }; - animNormal = Vector3Transform(animNormal, InverseBoneMatrix); - mesh.animNormals[vCounter] += animNormal.x*boneWeight; - mesh.animNormals[vCounter + 1] += animNormal.y*boneWeight; - mesh.animNormals[vCounter + 2] += animNormal.z*boneWeight; - } - } - } + // Normals processing + // NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals) + if ((mesh.normals != NULL) && (mesh.animNormals != NULL )) + { + animNormal = (Vector3){ mesh.normals[vCounter], mesh.normals[vCounter + 1], mesh.normals[vCounter + 2] }; + animNormal = Vector3Transform(animNormal, MatrixTranspose(MatrixInvert(model.meshes[m].boneMatrices[boneId]))); + mesh.animNormals[vCounter] += animNormal.x*boneWeight; + mesh.animNormals[vCounter + 1] += animNormal.y*boneWeight; + mesh.animNormals[vCounter + 2] += animNormal.z*boneWeight; + } + } + } if (updated) { From 02466212100344b00bb3c9d438d2236144070aac Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 1 Nov 2025 21:06:18 +0100 Subject: [PATCH 032/430] REVIEWED: SIMD intrinsics checks and usage --- src/external/rlsw.h | 192 +++++++++++++++++++++++--------------------- 1 file changed, 100 insertions(+), 92 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index 9c510e945..15ab89d4e 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -40,6 +40,14 @@ * If not defined, the library is in header only mode and can be included in other headers * or source files without problems. But only ONE file should hold the implementation * +* #define RLSW_USE_SIMD_INTRINSICS +* Detect and use SIMD intrinsics on the host compilation platform +* SIMD could improve rendering considerable vectorizing some raster operations +* but the target platforms running the compiled program with SIMD enabled +* must support the SIMD the program has been built for, making them only +* recommended under specific situations and only if the developers know +* what are they doing; this flag is not defined by default +* * rlsw capabilities could be customized just defining some internal * values before library inclusion (default values listed): * @@ -636,59 +644,58 @@ SWAPI void swBindTexture(uint32_t id); #define SW_ARCH_RISCV #endif -// Check for SIMD vector instructions -#if defined(__FMA__) && defined(__AVX2__) - #define SW_HAS_FMA_AVX2 - #include -#endif -#if defined(__FMA__) && defined(__AVX__) - #define SW_HAS_FMA_AVX - #include -#endif -#if defined(__AVX2__) - #define SW_HAS_AVX2 - #include -#endif -#if defined(__AVX__) - #define SW_HAS_AVX - #include -#endif -#if defined(__SSE4_2__) - #define SW_HAS_SSE42 - #include -#endif -#if defined(__SSE4_1__) - #define SW_HAS_SSE41 - #include -#endif -#if defined(__SSSE3__) - #define SW_HAS_SSSE3 - #include -#endif -#if defined(__SSE3__) - #define SW_HAS_SSE3 - #include -#endif -#if defined(__SSE2__) || (defined(_M_AMD64) || defined(_M_X64)) // SSE2 x64 - #define SW_HAS_SSE2 - #include -#endif -#if defined(__SSE__) - #define SW_HAS_SSE - #include -#endif -#if defined(__ARM_NEON) || defined(__aarch64__) - #if defined(__ARM_FEATURE_FMA) - #define SW_HAS_NEON_FMA - #else - #define SW_HAS_NEON +#if defined(RLSW_USE_SIMD_INTRINSICS) + // Check for SIMD vector instructions + // NOTE: Compiler is responsible to enable required flags for host device, + // supported features are detected at compiler init but varies depending on compiler + // TODO: This logic must be reviewed to avoid the inclusion of multiple headers + // and enable the higher level of SIMD available + #if defined(__FMA__) && defined(__AVX2__) + #define SW_HAS_FMA_AVX2 + #include + #elif defined(__FMA__) && defined(__AVX__) + #define SW_HAS_FMA_AVX + #include + #elif defined(__AVX2__) + #define SW_HAS_AVX2 + #include + #elif defined(__AVX__) + #define SW_HAS_AVX + #include #endif - #include -#endif -#if defined(__riscv_vector) - #define SW_HAS_RVV - #include -#endif + #if defined(__SSE4_2__) + #define SW_HAS_SSE42 + #include + #elif defined(__SSE4_1__) + #define SW_HAS_SSE41 + #include + #elif defined(__SSSE3__) + #define SW_HAS_SSSE3 + #include + #elif defined(__SSE3__) + #define SW_HAS_SSE3 + #include + #elif defined(__SSE2__) || (defined(_M_AMD64) || defined(_M_X64)) // SSE2 x64 + #define SW_HAS_SSE2 + #include + #elif defined(__SSE__) + #define SW_HAS_SSE + #include + #endif + #if defined(__ARM_NEON) || defined(__aarch64__) + #if defined(__ARM_FEATURE_FMA) + #define SW_HAS_NEON_FMA + #else + #define SW_HAS_NEON + #endif + #include + #endif + #if defined(__riscv_vector) + // NOTE: Requires compilation flags: -march=rv64gcv -mabi=lp64d + #define SW_HAS_RVV + #include + #endif +#endif // RLSW_USE_SIMD_INTRINSICS #ifdef __cplusplus #define SW_CURLY_INIT(name) name @@ -749,31 +756,31 @@ SWAPI void swBindTexture(uint32_t id); #endif #if (SW_DEPTH_BUFFER_BITS == 16) - #define SW_DEPTH_TYPE uint16_t - #define SW_DEPTH_IS_PACKED 1 - #define SW_DEPTH_PACK_COMP 1 - #define SW_DEPTH_MAX UINT16_MAX - #define SW_DEPTH_SCALE (1.0f/UINT16_MAX) - #define SW_PACK_DEPTH(d) ((SW_DEPTH_TYPE)((d)*SW_DEPTH_MAX)) - #define SW_UNPACK_DEPTH(p) (p) + #define SW_DEPTH_TYPE uint16_t + #define SW_DEPTH_IS_PACKED 1 + #define SW_DEPTH_PACK_COMP 1 + #define SW_DEPTH_MAX UINT16_MAX + #define SW_DEPTH_SCALE (1.0f/UINT16_MAX) + #define SW_PACK_DEPTH(d) ((SW_DEPTH_TYPE)((d)*SW_DEPTH_MAX)) + #define SW_UNPACK_DEPTH(p) (p) #elif (SW_DEPTH_BUFFER_BITS == 24) - #define SW_DEPTH_TYPE uint8_t - #define SW_DEPTH_IS_PACKED 0 - #define SW_DEPTH_PACK_COMP 3 - #define SW_DEPTH_MAX 0xFFFFFFU - #define SW_DEPTH_SCALE (1.0f/0xFFFFFFU) - #define SW_PACK_DEPTH_0(d) ((uint8_t)(((uint32_t)((d)*SW_DEPTH_MAX)>>16)&0xFFU)) - #define SW_PACK_DEPTH_1(d) ((uint8_t)(((uint32_t)((d)*SW_DEPTH_MAX)>>8)&0xFFU)) - #define SW_PACK_DEPTH_2(d) ((uint8_t)((uint32_t)((d)*SW_DEPTH_MAX)&0xFFU)) - #define SW_UNPACK_DEPTH(p) ((((uint32_t)(p)[0]<<16)|((uint32_t)(p)[1]<<8)|(uint32_t)(p)[2])) + #define SW_DEPTH_TYPE uint8_t + #define SW_DEPTH_IS_PACKED 0 + #define SW_DEPTH_PACK_COMP 3 + #define SW_DEPTH_MAX 0xFFFFFFU + #define SW_DEPTH_SCALE (1.0f/0xFFFFFFU) + #define SW_PACK_DEPTH_0(d) ((uint8_t)(((uint32_t)((d)*SW_DEPTH_MAX)>>16)&0xFFU)) + #define SW_PACK_DEPTH_1(d) ((uint8_t)(((uint32_t)((d)*SW_DEPTH_MAX)>>8)&0xFFU)) + #define SW_PACK_DEPTH_2(d) ((uint8_t)((uint32_t)((d)*SW_DEPTH_MAX)&0xFFU)) + #define SW_UNPACK_DEPTH(p) ((((uint32_t)(p)[0]<<16)|((uint32_t)(p)[1]<<8)|(uint32_t)(p)[2])) #else // 32 bits - #define SW_DEPTH_TYPE float - #define SW_DEPTH_IS_PACKED 1 - #define SW_DEPTH_PACK_COMP 1 - #define SW_DEPTH_MAX 1.0f - #define SW_DEPTH_SCALE 1.0f - #define SW_PACK_DEPTH(d) ((SW_DEPTH_TYPE)(d)) - #define SW_UNPACK_DEPTH(p) (p) + #define SW_DEPTH_TYPE float + #define SW_DEPTH_IS_PACKED 1 + #define SW_DEPTH_PACK_COMP 1 + #define SW_DEPTH_MAX 1.0f + #define SW_DEPTH_SCALE 1.0f + #define SW_PACK_DEPTH(d) ((SW_DEPTH_TYPE)(d)) + #define SW_UNPACK_DEPTH(p) (p) #endif #define SW_STATE_CHECK(flags) (SW_STATE_CHECK_EX(RLSW.stateFlags, (flags))) @@ -1136,25 +1143,26 @@ static inline void sw_float_to_unorm8_simd(uint8_t dst[4], const float src[4]) *(uint32_t*)dst = _mm_cvtsi128_si32(clamped); #elif defined(SW_HAS_RVV) // TODO: Sample code generated by AI, needs testing and review - size_t vl = vsetvl_e32m1(4); // Load up to 4 floats into a vector register - vfloat32m1_t vsrc = vle32_v_f32m1(src, vl); // Load float32 values + // NOTE: RVV 1.0 specs define the use of __riscv_ prefix for instrinsic functions + size_t vl = __riscv_vsetvl_e32m1(4); // Load up to 4 floats into a vector register + vfloat32m1_t vsrc = __riscv_vle32_v_f32m1(src, vl); // Load float32 values // Clamp to [0.0f, 1.0f] - vfloat32m1_t vzero = vfmv_v_f_f32m1(0.0f, vl); - vfloat32m1_t vone = vfmv_v_f_f32m1(1.0f, vl); - vsrc = vfmin_vv_f32m1(vsrc, vone, vl); - vsrc = vfmax_vv_f32m1(vsrc, vzero, vl); + vfloat32m1_t vzero = __riscv_vfmv_v_f_f32m1(0.0f, vl); + vfloat32m1_t vone = __riscv_vfmv_v_f_f32m1(1.0f, vl); + vsrc = __riscv_vfmin_vv_f32m1(vsrc, vone, vl); + vsrc = __riscv_vfmax_vv_f32m1(vsrc, vzero, vl); // Multiply by 255.0f and add 0.5f for rounding - vfloat32m1_t vscaled = vfmul_vf_f32m1(vsrc, 255.0f, vl); - vscaled = vfadd_vf_f32m1(vscaled, 0.5f, vl); + vfloat32m1_t vscaled = __riscv_vfmul_vf_f32m1(vsrc, 255.0f, vl); + vscaled = __riscv_vfadd_vf_f32m1(vscaled, 0.5f, vl); // Convert to unsigned integer (truncate toward zero) - vuint32m1_t vu32 = vfcvt_xu_f_v_u32m1(vscaled, vl); + vuint32m1_t vu32 = __riscv_vfcvt_xu_f_v_u32m1(vscaled, vl); // Narrow from u32 -> u8 - vuint8m1_t vu8 = vnclipu_wx_u8m1(vu32, 0, vl); // Round toward zero - vse8_v_u8m1(dst, vu8, vl); // Store result + vuint8m1_t vu8 = __riscv_vnclipu_wx_u8m1(vu32, 0, vl); // Round toward zero + __riscv_vse8_v_u8m1(dst, vu8, vl); // Store result #else for (int i = 0; i < 4; i++) { @@ -1190,12 +1198,12 @@ static inline void sw_float_from_unorm8_simd(float dst[4], const uint8_t src[4]) _mm_storeu_ps(dst, floats); #elif defined(SW_HAS_RVV) // TODO: Sample code generated by AI, needs testing and review - size_t vl = vsetvl_e8m1(4); // Set vector length for 8-bit input elements - vuint8m1_t vsrc_u8 = vle8_v_u8m1(src, vl); // Load 4 unsigned 8-bit integers - vuint32m1_t vsrc_u32 = vwcvt_xu_u_v_u32m1(vsrc_u8, vl); // Widen to 32-bit unsigned integers - vfloat32m1_t vsrc_f32 = vfcvt_f_xu_v_f32m1(vsrc_u32, vl); // Convert to float32 - vfloat32m1_t vnorm = vfmul_vf_f32m1(vsrc_f32, SW_INV_255, vl); // Multiply by 1/255.0 to normalize - vse32_v_f32m1(dst, vnorm, vl); // Store result + size_t vl = __riscv_vsetvl_e8m1(4); // Set vector length for 8-bit input elements + vuint8m1_t vsrc_u8 = __riscv_vle8_v_u8m1(src, vl); // Load 4 unsigned 8-bit integers + vuint32m1_t vsrc_u32 = __riscv_vwcvt_xu_u_v_u32m1(vsrc_u8, vl); // Widen to 32-bit unsigned integers + vfloat32m1_t vsrc_f32 = __riscv_vfcvt_f_xu_v_f32m1(vsrc_u32, vl); // Convert to float32 + vfloat32m1_t vnorm = __riscv_vfmul_vf_f32m1(vsrc_f32, SW_INV_255, vl); // Multiply by 1/255.0 to normalize + __riscv_vse32_v_f32m1(dst, vnorm, vl); // Store result #else dst[0] = (float)src[0]*SW_INV_255; dst[1] = (float)src[1]*SW_INV_255; From 5fbf67a6307f685b0126815a529720bf92886526 Mon Sep 17 00:00:00 2001 From: JohnnyCena123 Date: Sun, 2 Nov 2025 20:24:47 +0200 Subject: [PATCH 033/430] [rcore] Use `FLAG_*` macros where possible (#5169) * use FLAG_* macros where possible * rename `FLAG_CHECK()` to `FLAG_IS_SET()` * remove unnecessary equality checks * fix issues --------- Co-authored-by: Ray --- src/platforms/rcore_android.c | 28 ++--- src/platforms/rcore_desktop_glfw.c | 166 ++++++++++++++--------------- src/platforms/rcore_desktop_rgfw.c | 131 +++++++++++------------ src/platforms/rcore_desktop_sdl.c | 161 +++++++++++++--------------- src/platforms/rcore_drm.c | 32 +++--- src/platforms/rcore_template.c | 4 +- src/platforms/rcore_web.c | 164 ++++++++++++++-------------- src/rcore.c | 18 ++-- 8 files changed, 341 insertions(+), 363 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 47dcce32b..4f106ee3b 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -796,10 +796,10 @@ int InitPlatform(void) //AConfiguration_getScreenLong(platform.app->config); // Set some default window flags - CORE.Window.flags &= ~FLAG_WINDOW_HIDDEN; // false - CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; // false - CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; // true - CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; // false + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // false + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // false + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // true + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // false //---------------------------------------------------------------------------- // Initialize App command system @@ -883,11 +883,11 @@ void ClosePlatform(void) static int InitGraphicsDevice(void) { CORE.Window.fullscreen = true; - CORE.Window.flags |= FLAG_FULLSCREEN_MODE; + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); EGLint samples = 0; EGLint sampleBuffer = 0; - if (CORE.Window.flags & FLAG_MSAA_4X_HINT) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) { samples = 4; sampleBuffer = 1; @@ -992,7 +992,7 @@ static int InitGraphicsDevice(void) CORE.Window.ready = true; - if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) MinimizeWindow(); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) MinimizeWindow(); return 0; } @@ -1059,7 +1059,7 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd) // Set font white rectangle for shapes drawing, so shapes and text can be batched together // WARNING: rshapes module is required, if not available, default internal white rectangle is used Rectangle rec = GetFontDefault().recs[95]; - if (CORE.Window.flags & FLAG_MSAA_4X_HINT) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) { // NOTE: We try to maxime rec padding to avoid pixel bleeding on MSAA filtering SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 2, rec.y + 2, 1, 1 }); @@ -1102,14 +1102,14 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd) case APP_CMD_GAINED_FOCUS: { platform.appEnabled = true; - CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); //ResumeMusicStream(); } break; case APP_CMD_PAUSE: break; case APP_CMD_LOST_FOCUS: { platform.appEnabled = false; - CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); //PauseMusicStream(); } break; case APP_CMD_TERM_WINDOW: @@ -1187,8 +1187,8 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) if (type == AINPUT_EVENT_TYPE_MOTION) { - if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) || - ((source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)) + if (FLAG_IS_SET(source, AINPUT_SOURCE_JOYSTICK) || + FLAG_IS_SET(source, AINPUT_SOURCE_GAMEPAD)) { // For now we'll assume a single gamepad which we "detect" on its input event CORE.Input.Gamepad.ready[0] = true; @@ -1251,8 +1251,8 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) //int32_t AKeyEvent_getMetaState(event); // Handle gamepad button presses and releases - if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) || - ((source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)) + if (FLAG_IS_SET(source, AINPUT_SOURCE_JOYSTICK) || + FLAG_IS_SET(source, AINPUT_SOURCE_GAMEPAD)) { // For now we'll assume a single gamepad which we "detect" on its input event CORE.Input.Gamepad.ready[0] = true; diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 5dde1df67..78b513b40 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -195,14 +195,14 @@ void ToggleFullscreen(void) TRACELOG(LOG_WARNING, "GLFW: Failed to get monitor"); CORE.Window.fullscreen = false; - CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); glfwSetWindowMonitor(platform.handle, NULL, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); } else { CORE.Window.fullscreen = true; - CORE.Window.flags |= FLAG_FULLSCREEN_MODE; + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); glfwSetWindowMonitor(platform.handle, monitor, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); } @@ -211,7 +211,7 @@ void ToggleFullscreen(void) else { CORE.Window.fullscreen = false; - CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.previousPosition.x, CORE.Window.previousPosition.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); @@ -222,7 +222,7 @@ void ToggleFullscreen(void) // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) // NOTE: V-Sync can be enabled by graphic driver configuration - if (CORE.Window.flags & FLAG_VSYNC_HINT) glfwSwapInterval(1); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_VSYNC_HINT)) glfwSwapInterval(1); } // Toggle borderless windowed mode @@ -256,7 +256,7 @@ void ToggleBorderlessWindowed(void) // Set undecorated flag glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_FALSE); - CORE.Window.flags |= FLAG_WINDOW_UNDECORATED; + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); // Get monitor position and size int monitorPosX = 0; @@ -272,13 +272,13 @@ void ToggleBorderlessWindowed(void) // Refocus window glfwFocusWindow(platform.handle); - CORE.Window.flags |= FLAG_BORDERLESS_WINDOWED_MODE; + FLAG_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); } else { // Remove undecorated flag glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_TRUE); - CORE.Window.flags &= ~FLAG_WINDOW_UNDECORATED; + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); // Return previous screen size and position // NOTE: The order matters here, it must set size first, then set position, otherwise the screen will be positioned incorrectly @@ -288,7 +288,7 @@ void ToggleBorderlessWindowed(void) // Refocus window glfwFocusWindow(platform.handle); - CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE; + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); CORE.Window.position.x = CORE.Window.previousPosition.x; CORE.Window.position.y = CORE.Window.previousPosition.y; @@ -305,7 +305,7 @@ void MaximizeWindow(void) if (glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE) { glfwMaximizeWindow(platform.handle); - CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); } } @@ -323,8 +323,8 @@ void RestoreWindow(void) { // Restores the specified window if it was previously iconified (minimized) or maximized glfwRestoreWindow(platform.handle); - CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; - CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); } } @@ -337,109 +337,109 @@ void SetWindowState(unsigned int flags) // NOTE: In most cases the functions already change the flags internally // State change: FLAG_VSYNC_HINT - if (((CORE.Window.flags & FLAG_VSYNC_HINT) != (flags & FLAG_VSYNC_HINT)) && ((flags & FLAG_VSYNC_HINT) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_VSYNC_HINT) != FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) && FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) { glfwSwapInterval(1); - CORE.Window.flags |= FLAG_VSYNC_HINT; + FLAG_SET(CORE.Window.flags, FLAG_VSYNC_HINT); } // State change: FLAG_BORDERLESS_WINDOWED_MODE // NOTE: This must be handled before FLAG_FULLSCREEN_MODE because ToggleBorderlessWindowed() needs to get some fullscreen values if fullscreen is running - if (((CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) != (flags & FLAG_BORDERLESS_WINDOWED_MODE)) && ((flags & FLAG_BORDERLESS_WINDOWED_MODE) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE) != FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) && FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) { ToggleBorderlessWindowed(); // NOTE: Window state flag updated inside function } // State change: FLAG_FULLSCREEN_MODE - if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) != (flags & FLAG_FULLSCREEN_MODE) && ((flags & FLAG_FULLSCREEN_MODE) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE) != FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) && FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) { ToggleFullscreen(); // NOTE: Window state flag updated inside function } // State change: FLAG_WINDOW_RESIZABLE - if (((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) != (flags & FLAG_WINDOW_RESIZABLE)) && ((flags & FLAG_WINDOW_RESIZABLE) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) && FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) { glfwSetWindowAttrib(platform.handle, GLFW_RESIZABLE, GLFW_TRUE); - CORE.Window.flags |= FLAG_WINDOW_RESIZABLE; + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE); } // State change: FLAG_WINDOW_UNDECORATED - if (((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) != (flags & FLAG_WINDOW_UNDECORATED)) && (flags & FLAG_WINDOW_UNDECORATED)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED) != FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) && FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) { glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_FALSE); - CORE.Window.flags |= FLAG_WINDOW_UNDECORATED; + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); } // State change: FLAG_WINDOW_HIDDEN - if (((CORE.Window.flags & FLAG_WINDOW_HIDDEN) != (flags & FLAG_WINDOW_HIDDEN)) && ((flags & FLAG_WINDOW_HIDDEN) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN) != FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) && FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) { glfwHideWindow(platform.handle); - CORE.Window.flags |= FLAG_WINDOW_HIDDEN; + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN); } // State change: FLAG_WINDOW_MINIMIZED - if (((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) != (flags & FLAG_WINDOW_MINIMIZED)) && ((flags & FLAG_WINDOW_MINIMIZED) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED) != FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) && FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) { //GLFW_ICONIFIED MinimizeWindow(); // NOTE: Window state flag updated inside function } // State change: FLAG_WINDOW_MAXIMIZED - if (((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) != (flags & FLAG_WINDOW_MAXIMIZED)) && ((flags & FLAG_WINDOW_MAXIMIZED) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED) != FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) && FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) { //GLFW_MAXIMIZED MaximizeWindow(); // NOTE: Window state flag updated inside function } // State change: FLAG_WINDOW_UNFOCUSED - if (((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) != (flags & FLAG_WINDOW_UNFOCUSED)) && ((flags & FLAG_WINDOW_UNFOCUSED) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED) != FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) && FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) { glfwSetWindowAttrib(platform.handle, GLFW_FOCUS_ON_SHOW, GLFW_FALSE); - CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); } // State change: FLAG_WINDOW_TOPMOST - if (((CORE.Window.flags & FLAG_WINDOW_TOPMOST) != (flags & FLAG_WINDOW_TOPMOST)) && ((flags & FLAG_WINDOW_TOPMOST) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST) != FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) && FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) { glfwSetWindowAttrib(platform.handle, GLFW_FLOATING, GLFW_TRUE); - CORE.Window.flags |= FLAG_WINDOW_TOPMOST; + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST); } // State change: FLAG_WINDOW_ALWAYS_RUN - if (((CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) != (flags & FLAG_WINDOW_ALWAYS_RUN)) && ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN) != FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) && FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) { - CORE.Window.flags |= FLAG_WINDOW_ALWAYS_RUN; + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); } // The following states can not be changed after window creation // State change: FLAG_WINDOW_TRANSPARENT - if (((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) != (flags & FLAG_WINDOW_TRANSPARENT)) && ((flags & FLAG_WINDOW_TRANSPARENT) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TRANSPARENT) != FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) && FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) { TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only be configured before window initialization"); } // State change: FLAG_WINDOW_HIGHDPI - if (((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) != (flags & FLAG_WINDOW_HIGHDPI)) && ((flags & FLAG_WINDOW_HIGHDPI) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI) != FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) && FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) { TRACELOG(LOG_WARNING, "WINDOW: High DPI can only be configured before window initialization"); } // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH - if (((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) != (flags & FLAG_WINDOW_MOUSE_PASSTHROUGH)) && ((flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MOUSE_PASSTHROUGH) != FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) && FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) { glfwSetWindowAttrib(platform.handle, GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE); - CORE.Window.flags |= FLAG_WINDOW_MOUSE_PASSTHROUGH; + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MOUSE_PASSTHROUGH); } // State change: FLAG_MSAA_4X_HINT - if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) != (flags & FLAG_MSAA_4X_HINT)) && ((flags & FLAG_MSAA_4X_HINT) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT) != FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) && FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) { TRACELOG(LOG_WARNING, "WINDOW: MSAA can only be configured before window initialization"); } // State change: FLAG_INTERLACED_HINT - if (((CORE.Window.flags & FLAG_INTERLACED_HINT) != (flags & FLAG_INTERLACED_HINT)) && ((flags & FLAG_INTERLACED_HINT) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_INTERLACED_HINT) != FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) && FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) { TRACELOG(LOG_WARNING, "WINDOW: Interlaced mode can only be configured before window initialization"); } @@ -452,107 +452,107 @@ void ClearWindowState(unsigned int flags) // NOTE: In most cases the functions already change the flags internally // State change: FLAG_VSYNC_HINT - if (((CORE.Window.flags & FLAG_VSYNC_HINT) > 0) && ((flags & FLAG_VSYNC_HINT) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_VSYNC_HINT)) && (FLAG_IS_SET(flags, FLAG_VSYNC_HINT))) { glfwSwapInterval(0); - CORE.Window.flags &= ~FLAG_VSYNC_HINT; + FLAG_CLEAR(CORE.Window.flags, FLAG_VSYNC_HINT); } // State change: FLAG_BORDERLESS_WINDOWED_MODE // NOTE: This must be handled before FLAG_FULLSCREEN_MODE because ToggleBorderlessWindowed() needs to get some fullscreen values if fullscreen is running - if (((CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) > 0) && ((flags & FLAG_BORDERLESS_WINDOWED_MODE) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) && (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE))) { ToggleBorderlessWindowed(); // NOTE: Window state flag updated inside function } // State change: FLAG_FULLSCREEN_MODE - if (((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) && ((flags & FLAG_FULLSCREEN_MODE) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) && (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE))) { ToggleFullscreen(); // NOTE: Window state flag updated inside function } // State change: FLAG_WINDOW_RESIZABLE - if (((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) && ((flags & FLAG_WINDOW_RESIZABLE) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) && (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE))) { glfwSetWindowAttrib(platform.handle, GLFW_RESIZABLE, GLFW_FALSE); - CORE.Window.flags &= ~FLAG_WINDOW_RESIZABLE; + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_RESIZABLE); } // State change: FLAG_WINDOW_HIDDEN - if (((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) && ((flags & FLAG_WINDOW_HIDDEN) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) && (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN))) { glfwShowWindow(platform.handle); - CORE.Window.flags &= ~FLAG_WINDOW_HIDDEN; + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_HIDDEN); } // State change: FLAG_WINDOW_MINIMIZED - if (((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) && ((flags & FLAG_WINDOW_MINIMIZED) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) && (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED))) { RestoreWindow(); // NOTE: Window state flag updated inside function } // State change: FLAG_WINDOW_MAXIMIZED - if (((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) && ((flags & FLAG_WINDOW_MAXIMIZED) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) && (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED))) { RestoreWindow(); // NOTE: Window state flag updated inside function } // State change: FLAG_WINDOW_UNDECORATED - if (((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) && ((flags & FLAG_WINDOW_UNDECORATED) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED)) && (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED))) { glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_TRUE); - CORE.Window.flags &= ~FLAG_WINDOW_UNDECORATED; + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); } // State change: FLAG_WINDOW_UNFOCUSED - if (((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) && ((flags & FLAG_WINDOW_UNFOCUSED) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) && (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED))) { glfwSetWindowAttrib(platform.handle, GLFW_FOCUS_ON_SHOW, GLFW_TRUE); - CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); } // State change: FLAG_WINDOW_TOPMOST - if (((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) && ((flags & FLAG_WINDOW_TOPMOST) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST)) && (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST))) { glfwSetWindowAttrib(platform.handle, GLFW_FLOATING, GLFW_FALSE); - CORE.Window.flags &= ~FLAG_WINDOW_TOPMOST; + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_TOPMOST); } // State change: FLAG_WINDOW_ALWAYS_RUN - if (((CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) > 0) && ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN)) && (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN))) { - CORE.Window.flags &= ~FLAG_WINDOW_ALWAYS_RUN; + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); } // The following states can not be changed after window creation // State change: FLAG_WINDOW_TRANSPARENT - if (((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) && ((flags & FLAG_WINDOW_TRANSPARENT) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TRANSPARENT)) && (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT))) { TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only be configured before window initialization"); } // State change: FLAG_WINDOW_HIGHDPI - if (((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) && ((flags & FLAG_WINDOW_HIGHDPI) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) && (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI))) { TRACELOG(LOG_WARNING, "WINDOW: High DPI can only be configured before window initialization"); } // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH - if (((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0) && ((flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) && (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH))) { glfwSetWindowAttrib(platform.handle, GLFW_MOUSE_PASSTHROUGH, GLFW_FALSE); - CORE.Window.flags &= ~FLAG_WINDOW_MOUSE_PASSTHROUGH; + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MOUSE_PASSTHROUGH); } // State change: FLAG_MSAA_4X_HINT - if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) > 0) && ((flags & FLAG_MSAA_4X_HINT) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) && (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT))) { TRACELOG(LOG_WARNING, "WINDOW: MSAA can only be configured before window initialization"); } // State change: FLAG_INTERLACED_HINT - if (((CORE.Window.flags & FLAG_INTERLACED_HINT) > 0) && ((flags & FLAG_INTERLACED_HINT) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_INTERLACED_HINT)) && (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT))) { TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only be configured before window initialization"); } @@ -1391,31 +1391,31 @@ int InitPlatform(void) unsigned int requestedWindowFlags = CORE.Window.flags; // Check window creation flags - if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) CORE.Window.fullscreen = true; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) CORE.Window.fullscreen = true; - if ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window else glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); // Window initially hidden - if ((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // Border and buttons on Window + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED)) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // Border and buttons on Window else glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); // Decorated window - if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // Resizable window + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // Resizable window else glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // Avoid window being resizable // Disable FLAG_WINDOW_MINIMIZED, not supported on initialization - if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // Disable FLAG_WINDOW_MAXIMIZED, not supported on initialization - if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); - if ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE); else glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE); - if ((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST)) glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); else glfwWindowHint(GLFW_FLOATING, GLFW_FALSE); // NOTE: Some GLFW flags are not supported on HTML5 - if ((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); // Transparent framebuffer + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TRANSPARENT)) glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); // Transparent framebuffer else glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_FALSE); // Opaque framebuffer // HACK: Most of this was written before GLFW_SCALE_FRAMEBUFFER existed and @@ -1428,7 +1428,7 @@ int InitPlatform(void) glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE); #endif - if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { // since we skipped it before, now make sure to set this on macOS #if defined(__APPLE__) @@ -1445,10 +1445,10 @@ int InitPlatform(void) else glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); // Mouse passthrough - if ((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0) glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE); else glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_FALSE); - if (CORE.Window.flags & FLAG_MSAA_4X_HINT) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) { // NOTE: MSAA is only enabled for main framebuffer, not user-created FBOs TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); @@ -1640,7 +1640,7 @@ int InitPlatform(void) // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) // NOTE: V-Sync can be enabled by graphic driver configuration, it doesn't need // to be activated on web platforms since VSync is enforced there - if (CORE.Window.flags & FLAG_VSYNC_HINT) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_VSYNC_HINT)) { // WARNING: It seems to hit a critical render path in Intel HD Graphics glfwSwapInterval(1); @@ -1650,7 +1650,7 @@ int InitPlatform(void) int fbWidth = CORE.Window.screen.width; int fbHeight = CORE.Window.screen.height; - if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { // NOTE: On APPLE platforms system should manage window/input scaling and also framebuffer scaling // Framebuffer scaling should be activated with: glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); @@ -1682,7 +1682,7 @@ int InitPlatform(void) return -1; } - if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) MinimizeWindow(); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) MinimizeWindow(); // If graphic device is no properly initialized, we end program if (!CORE.Window.ready) { TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphic device"); return -1; } @@ -1726,7 +1726,7 @@ int InitPlatform(void) glfwSetWindowFocusCallback(platform.handle, WindowFocusCallback); glfwSetDropCallback(platform.handle, WindowDropCallback); - if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { glfwSetWindowContentScaleCallback(platform.handle, WindowContentScaleCallback); } @@ -1847,22 +1847,22 @@ static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float s // GLFW3 WindowIconify Callback, runs when window is minimized/restored static void WindowIconifyCallback(GLFWwindow *window, int iconified) { - if (iconified) CORE.Window.flags |= FLAG_WINDOW_MINIMIZED; // The window was iconified - else CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; // The window was restored + if (iconified) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // The window was iconified + else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // The window was restored } // GLFW3 WindowMaximize Callback, runs when window is maximized/restored static void WindowMaximizeCallback(GLFWwindow *window, int maximized) { - if (maximized) CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; // The window was maximized - else CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; // The window was restored + if (maximized) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // The window was maximized + else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // The window was restored } // GLFW3 WindowFocus Callback, runs when window get/lose focus static void WindowFocusCallback(GLFWwindow *window, int focused) { - if (focused) CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; // The window was focused - else CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; // The window lost focus + if (focused) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window was maximized + else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window was restored } // GLFW3 Window Drop Callback, runs when drop files into window @@ -1905,8 +1905,8 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i else if (action == GLFW_REPEAT) CORE.Input.Keyboard.keyRepeatInFrame[key] = 1; // WARNING: Check if CAPS/NUM key modifiers are enabled and force down state for those keys - if (((key == KEY_CAPS_LOCK) && ((mods & GLFW_MOD_CAPS_LOCK) > 0)) || - ((key == KEY_NUM_LOCK) && ((mods & GLFW_MOD_NUM_LOCK) > 0))) CORE.Input.Keyboard.currentKeyState[key] = 1; + if (((key == KEY_CAPS_LOCK) && (FLAG_IS_SET(mods, GLFW_MOD_CAPS_LOCK))) || + ((key == KEY_NUM_LOCK) && (FLAG_IS_SET(mods, GLFW_MOD_NUM_LOCK)))) CORE.Input.Keyboard.currentKeyState[key] = 1; // Check if there is space available in the key queue if ((CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE) && (action == GLFW_PRESS)) diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 86842140d..863e57a99 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -303,7 +303,7 @@ void ToggleFullscreen(void) platform.mon = RGFW_window_getMonitor(platform.window); CORE.Window.fullscreen = true; - CORE.Window.flags |= FLAG_FULLSCREEN_MODE; + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); RGFW_monitor_scaleToWindow(platform.mon, platform.window); RGFW_window_setFullscreen(platform.window, 1); @@ -311,7 +311,7 @@ void ToggleFullscreen(void) else { CORE.Window.fullscreen = false; - CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); if (platform.mon.mode.area.w) { @@ -330,7 +330,7 @@ void ToggleFullscreen(void) // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) // NOTE: V-Sync can be enabled by graphic driver configuration - if (CORE.Window.flags & FLAG_VSYNC_HINT) RGFW_window_swapInterval(platform.window, 1); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_VSYNC_HINT)) RGFW_window_swapInterval(platform.window, 1); } // Toggle borderless windowed mode @@ -372,7 +372,7 @@ void MinimizeWindow(void) // Restore window from being minimized/maximized void RestoreWindow(void) { - if (!(CORE.Window.flags & FLAG_WINDOW_UNFOCUSED)) RGFW_window_focus(platform.window); + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) RGFW_window_focus(platform.window); RGFW_window_restore(platform.window); } @@ -382,72 +382,68 @@ void SetWindowState(unsigned int flags) { if (!CORE.Window.ready) TRACELOG(LOG_WARNING, "WINDOW: SetWindowState does nothing before window initialization, Use \"SetConfigFlags\" instead"); - CORE.Window.flags |= flags; + FLAG_SET(CORE.Window.flags, flags); - if (flags & FLAG_VSYNC_HINT) + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) { RGFW_window_swapInterval(platform.window, 1); } - if (flags & FLAG_FULLSCREEN_MODE) + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) { if (!CORE.Window.fullscreen) ToggleFullscreen(); } - if (flags & FLAG_WINDOW_RESIZABLE) + if (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) { RGFW_window_setMaxSize(platform.window, RGFW_AREA(0, 0)); RGFW_window_setMinSize(platform.window, RGFW_AREA(0, 0)); } - if (flags & FLAG_WINDOW_UNDECORATED) + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) { RGFW_window_setBorder(platform.window, 0); } - if (flags & FLAG_WINDOW_HIDDEN) + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) { RGFW_window_hide(platform.window); } - if (flags & FLAG_WINDOW_MINIMIZED) + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) { RGFW_window_minimize(platform.window); } - if (flags & FLAG_WINDOW_MAXIMIZED) + if (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) { RGFW_window_maximize(platform.window); } - if (flags & FLAG_WINDOW_UNFOCUSED) + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) { - CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; - platform.window->_flags &= ~RGFW_windowFocusOnShow; + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); + FLAG_CLEAR(platform.window->_flags, RGFW_windowFocusOnShow); RGFW_window_setFlags(platform.window, platform.window->_flags); } - if (flags & FLAG_WINDOW_TOPMOST) + if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) { RGFW_window_setFloating(platform.window, RGFW_TRUE); } - if (flags & FLAG_WINDOW_ALWAYS_RUN) - { - CORE.Window.flags |= FLAG_WINDOW_ALWAYS_RUN; - } - if (flags & FLAG_WINDOW_TRANSPARENT) + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) { TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only be configured before window initialization"); } - if (flags & FLAG_WINDOW_HIGHDPI) + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) { TRACELOG(LOG_WARNING, "WINDOW: High DPI can only be configured before window initialization"); } - if (flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) { RGFW_window_setMousePassthrough(platform.window, 1); } - if (flags & FLAG_BORDERLESS_WINDOWED_MODE) + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) { ToggleBorderlessWindowed(); } - if (flags & FLAG_MSAA_4X_HINT) + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) { RGFW_setGLHint(RGFW_glSamples, 4); } - if (flags & FLAG_INTERLACED_HINT) + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) { TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only be configured before window initialization"); } @@ -456,77 +452,72 @@ void SetWindowState(unsigned int flags) // Clear window configuration state flags void ClearWindowState(unsigned int flags) { - CORE.Window.flags &= ~flags; + FLAG_CLEAR(CORE.Window.flags, flags); - if (flags & FLAG_VSYNC_HINT) + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) { RGFW_window_swapInterval(platform.window, 0); } - if (flags & FLAG_FULLSCREEN_MODE) + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) { if (CORE.Window.fullscreen) ToggleFullscreen(); } - if (flags & FLAG_WINDOW_RESIZABLE) + if (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) { 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)); } - if (flags & FLAG_WINDOW_UNDECORATED) + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) { RGFW_window_setBorder(platform.window, 1); } - if (flags & FLAG_WINDOW_HIDDEN) + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) { - if (!(CORE.Window.flags & FLAG_WINDOW_UNFOCUSED)) RGFW_window_focus(platform.window); + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) RGFW_window_focus(platform.window); RGFW_window_show(platform.window); } - if (flags & FLAG_WINDOW_MINIMIZED) + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) { - if (!(CORE.Window.flags & FLAG_WINDOW_UNFOCUSED)) RGFW_window_focus(platform.window); + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) RGFW_window_focus(platform.window); RGFW_window_restore(platform.window); } - if (flags & FLAG_WINDOW_MAXIMIZED) + if (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) { - if (!(CORE.Window.flags & FLAG_WINDOW_UNFOCUSED)) RGFW_window_focus(platform.window); + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) RGFW_window_focus(platform.window); RGFW_window_restore(platform.window); } - if (flags & FLAG_WINDOW_UNFOCUSED) + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) { RGFW_window_setFlags(platform.window, platform.window->_flags | RGFW_windowFocusOnShow); - CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; } - if (flags & FLAG_WINDOW_TOPMOST) + if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) { RGFW_window_setFloating(platform.window, RGFW_FALSE); } - if (flags & FLAG_WINDOW_ALWAYS_RUN) - { - CORE.Window.flags &= ~FLAG_WINDOW_ALWAYS_RUN; - } - if (flags & FLAG_WINDOW_TRANSPARENT) + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) { TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only be configured before window initialization"); } - if (flags & FLAG_WINDOW_HIGHDPI) + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) { TRACELOG(LOG_WARNING, "WINDOW: High DPI can only be configured before window initialization"); } - if (flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) { RGFW_window_setMousePassthrough(platform.window, 0); } - if (flags & FLAG_BORDERLESS_WINDOWED_MODE) + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) { if (CORE.Window.fullscreen) ToggleBorderlessWindowed(); } - if (flags & FLAG_MSAA_4X_HINT) + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) { RGFW_setGLHint(RGFW_glSamples, 0); } - if (flags & FLAG_INTERLACED_HINT) + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) { TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only be configured before window initialization"); } @@ -983,7 +974,7 @@ void PollInputEvents(void) CORE.Window.resizedLastFrame = false; CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; - if (platform.window->_flags & RGFW_HOLD_MOUSE) + if (FLAG_IS_SET(platform.window->_flags, RGFW_HOLD_MOUSE)) { CORE.Input.Mouse.previousPosition = (Vector2){ 0.0f, 0.0f }; CORE.Input.Mouse.currentPosition = (Vector2){ 0.0f, 0.0f }; @@ -1062,18 +1053,18 @@ void PollInputEvents(void) } break; case RGFW_windowMaximized: { - CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; // The window was maximized + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // The window was maximized } break; case RGFW_windowMinimized: { - CORE.Window.flags |= FLAG_WINDOW_MINIMIZED; // The window was iconified + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // The window was iconified } break; case RGFW_windowRestored: { if (RGFW_window_isMaximized(platform.window)) - CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; // The window was restored + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // The window was restored if (RGFW_window_isMinimized(platform.window)) - CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; // The window was restored + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // The window was restored } break; case RGFW_windowMoved: { @@ -1159,7 +1150,7 @@ void PollInputEvents(void) } break; case RGFW_mousePosChanged: { - if (platform.window->_flags & RGFW_HOLD_MOUSE) + if (FLAG_IS_SET(platform.window->_flags, RGFW_HOLD_MOUSE)) { CORE.Input.Mouse.currentPosition.x += (float)event->vector.x; CORE.Input.Mouse.currentPosition.y += (float)event->vector.y; @@ -1283,24 +1274,24 @@ int InitPlatform(void) unsigned int flags = RGFW_windowCenter | RGFW_windowAllowDND; // Check window creation flags - if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) { CORE.Window.fullscreen = true; - flags |= RGFW_windowFullscreen; + FLAG_SET(flags, RGFW_windowFullscreen); } - if ((CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) > 0) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) { CORE.Window.fullscreen = true; - flags |= RGFW_windowedFullscreen; + FLAG_SET(flags, RGFW_windowedFullscreen); } - if ((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) flags |= RGFW_windowNoBorder; - if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) == 0) flags |= RGFW_windowNoResize; - if ((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) flags |= RGFW_windowTransparent; - if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) flags |= RGFW_windowFullscreen; - if ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) flags |= RGFW_windowHide; - if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) flags |= RGFW_windowMaximize; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED)) FLAG_SET(flags, RGFW_windowNoBorder); + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) FLAG_SET(flags, RGFW_windowNoResize); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TRANSPARENT)) FLAG_SET(flags, RGFW_windowTransparent); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) FLAG_SET(flags, RGFW_windowFullscreen); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) FLAG_SET(flags, RGFW_windowHide); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) FLAG_SET(flags, RGFW_windowMaximize); // NOTE: Some OpenGL context attributes must be set before window creation // Check selection OpenGL version @@ -1320,9 +1311,9 @@ int InitPlatform(void) RGFW_setGLHint(RGFW_glMinor, 3); } - if (CORE.Window.flags & FLAG_MSAA_4X_HINT) RGFW_setGLHint(RGFW_glSamples, 4); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) RGFW_setGLHint(RGFW_glSamples, 4); - if (!(CORE.Window.flags & FLAG_WINDOW_UNFOCUSED)) flags |= RGFW_windowFocusOnShow | RGFW_windowFocus; + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) FLAG_SET(flags, RGFW_windowFocusOnShow | RGFW_windowFocus); platform.window = RGFW_createWindow(CORE.Window.title, RGFW_RECT(0, 0, CORE.Window.screen.width, CORE.Window.screen.height), flags); platform.mon.mode.area.w = 0; @@ -1345,8 +1336,8 @@ int InitPlatform(void) // TODO: Is this needed by raylib now? // If so, rcore_desktop_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); + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_VSYNC_HINT)) RGFW_window_swapInterval(platform.window, 1); RGFW_window_makeCurrent(platform.window); // Check surface and context activation diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 03bad80f9..0b6376a50 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -468,16 +468,16 @@ void ToggleFullscreen(void) if ((monitor >= 0) && (monitor < monitorCount)) #endif { - if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) { SDL_SetWindowFullscreen(platform.window, 0); - CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); CORE.Window.fullscreen = false; } else { SDL_SetWindowFullscreen(platform.window, SDL_WINDOW_FULLSCREEN); - CORE.Window.flags |= FLAG_FULLSCREEN_MODE; + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); CORE.Window.fullscreen = true; } } @@ -496,15 +496,15 @@ void ToggleBorderlessWindowed(void) if ((monitor >= 0) && (monitor < monitorCount)) #endif { - if ((CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) > 0) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) { SDL_SetWindowFullscreen(platform.window, 0); - CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE; + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); } else { SDL_SetWindowFullscreen(platform.window, SDL_WINDOW_FULLSCREEN_DESKTOP); - CORE.Window.flags |= FLAG_BORDERLESS_WINDOWED_MODE; + FLAG_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); } } else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); @@ -514,14 +514,14 @@ void ToggleBorderlessWindowed(void) void MaximizeWindow(void) { SDL_MaximizeWindow(platform.window); - if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) == 0) CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); } // Set window state: minimized void MinimizeWindow(void) { SDL_MinimizeWindow(platform.window); - if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) == 0) CORE.Window.flags |= FLAG_WINDOW_MINIMIZED; + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); } // Restore window from being minimized/maximized @@ -536,13 +536,13 @@ void SetWindowState(unsigned int flags) { if (!CORE.Window.ready) TRACELOG(LOG_WARNING, "WINDOW: SetWindowState does nothing before window initialization, Use \"SetConfigFlags\" instead"); - CORE.Window.flags |= flags; + FLAG_SET(CORE.Window.flags, flags); - if (flags & FLAG_VSYNC_HINT) + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) { SDL_GL_SetSwapInterval(1); } - if (flags & FLAG_FULLSCREEN_MODE) + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) { const int monitor = SDL_GetWindowDisplayIndex(platform.window); const int monitorCount = SDL_GetNumVideoDisplays(); @@ -558,55 +558,51 @@ void SetWindowState(unsigned int flags) } else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); } - if (flags & FLAG_WINDOW_RESIZABLE) + if (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) { SDL_SetWindowResizable(platform.window, SDL_TRUE); } - if (flags & FLAG_WINDOW_UNDECORATED) + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) { SDL_SetWindowBordered(platform.window, SDL_FALSE); } - if (flags & FLAG_WINDOW_HIDDEN) + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) { SDL_HideWindow(platform.window); } - if (flags & FLAG_WINDOW_MINIMIZED) + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) { SDL_MinimizeWindow(platform.window); } - if (flags & FLAG_WINDOW_MAXIMIZED) + if (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) { SDL_MaximizeWindow(platform.window); } - if (flags & FLAG_WINDOW_UNFOCUSED) + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) { // NOTE: To be able to implement this part it seems that we should // do it ourselves, via 'windows.h', 'X11/Xlib.h' or even 'Cocoa.h' TRACELOG(LOG_WARNING, "SetWindowState() - FLAG_WINDOW_UNFOCUSED is not supported on PLATFORM_DESKTOP_SDL"); } - if (flags & FLAG_WINDOW_TOPMOST) + if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) { SDL_SetWindowAlwaysOnTop(platform.window, SDL_FALSE); } - if (flags & FLAG_WINDOW_ALWAYS_RUN) - { - CORE.Window.flags |= FLAG_WINDOW_ALWAYS_RUN; - } - if (flags & FLAG_WINDOW_TRANSPARENT) + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) { TRACELOG(LOG_WARNING, "SetWindowState() - FLAG_WINDOW_TRANSPARENT is not supported on PLATFORM_DESKTOP_SDL"); } - if (flags & FLAG_WINDOW_HIGHDPI) + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) { // NOTE: Such a function does not seem to exist TRACELOG(LOG_WARNING, "SetWindowState() - FLAG_WINDOW_HIGHDPI is not supported on PLATFORM_DESKTOP_SDL"); } - if (flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) { //SDL_SetWindowGrab(platform.window, SDL_FALSE); TRACELOG(LOG_WARNING, "SetWindowState() - FLAG_WINDOW_MOUSE_PASSTHROUGH is not supported on PLATFORM_DESKTOP_SDL"); } - if (flags & FLAG_BORDERLESS_WINDOWED_MODE) + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) { const int monitor = SDL_GetWindowDisplayIndex(platform.window); const int monitorCount = SDL_GetNumVideoDisplays(); @@ -621,12 +617,12 @@ void SetWindowState(unsigned int flags) } else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); } - if (flags & FLAG_MSAA_4X_HINT) + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); // Enable multisampling buffers SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); // Enable multisampling } - if (flags & FLAG_INTERLACED_HINT) + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) { TRACELOG(LOG_WARNING, "SetWindowState() - FLAG_INTERLACED_HINT is not supported on PLATFORM_DESKTOP_SDL"); } @@ -635,74 +631,69 @@ void SetWindowState(unsigned int flags) // Clear window configuration state flags void ClearWindowState(unsigned int flags) { - CORE.Window.flags &= ~flags; + FLAG_CLEAR(CORE.Window.flags, flags); - if (flags & FLAG_VSYNC_HINT) + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) { SDL_GL_SetSwapInterval(0); } - if (flags & FLAG_FULLSCREEN_MODE) + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) { SDL_SetWindowFullscreen(platform.window, 0); CORE.Window.fullscreen = false; } - if (flags & FLAG_WINDOW_RESIZABLE) + if (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) { SDL_SetWindowResizable(platform.window, SDL_FALSE); } - if (flags & FLAG_WINDOW_UNDECORATED) + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) { SDL_SetWindowBordered(platform.window, SDL_TRUE); } - if (flags & FLAG_WINDOW_HIDDEN) + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) { SDL_ShowWindow(platform.window); } - if (flags & FLAG_WINDOW_MINIMIZED) + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) { SDL_RestoreWindow(platform.window); } - if (flags & FLAG_WINDOW_MAXIMIZED) + if (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) { SDL_RestoreWindow(platform.window); } - if (flags & FLAG_WINDOW_UNFOCUSED) + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) { //SDL_RaiseWindow(platform.window); TRACELOG(LOG_WARNING, "ClearWindowState() - FLAG_WINDOW_UNFOCUSED is not supported on PLATFORM_DESKTOP_SDL"); } - if (flags & FLAG_WINDOW_TOPMOST) + if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) { SDL_SetWindowAlwaysOnTop(platform.window, SDL_FALSE); } - if (flags & FLAG_WINDOW_ALWAYS_RUN) - { - CORE.Window.flags &= ~FLAG_WINDOW_ALWAYS_RUN; - } - if (flags & FLAG_WINDOW_TRANSPARENT) + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) { TRACELOG(LOG_WARNING, "ClearWindowState() - FLAG_WINDOW_TRANSPARENT is not supported on PLATFORM_DESKTOP_SDL"); } - if (flags & FLAG_WINDOW_HIGHDPI) { // NOTE: There also doesn't seem to be a feature to disable high DPI once enabled TRACELOG(LOG_WARNING, "ClearWindowState() - FLAG_WINDOW_HIGHDPI is not supported on PLATFORM_DESKTOP_SDL"); } - if (flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) { //SDL_SetWindowGrab(platform.window, SDL_TRUE); TRACELOG(LOG_WARNING, "ClearWindowState() - FLAG_WINDOW_MOUSE_PASSTHROUGH is not supported on PLATFORM_DESKTOP_SDL"); } - if (flags & FLAG_BORDERLESS_WINDOWED_MODE) + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) { SDL_SetWindowFullscreen(platform.window, 0); } - if (flags & FLAG_MSAA_4X_HINT) + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); // Disable multisampling buffers SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); // Disable multisampling } - if (flags & FLAG_INTERLACED_HINT) + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) { TRACELOG(LOG_WARNING, "ClearWindowState() - FLAG_INTERLACED_HINT is not supported on PLATFORM_DESKTOP_SDL"); } @@ -847,7 +838,7 @@ void SetWindowMonitor(int monitor) // 1. SDL started supporting moving exclusive fullscreen windows between displays on SDL3, // see commit https://github.com/libsdl-org/SDL/commit/3f5ef7dd422057edbcf3e736107e34be4b75d9ba // 2. A workaround for SDL2 is leaving fullscreen, moving the window, then entering full screen again - const bool wasFullscreen = ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0)? true : false; + const bool wasFullscreen = (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE))? true : false; const int screenWidth = CORE.Window.screen.width; const int screenHeight = CORE.Window.screen.height; @@ -1410,7 +1401,7 @@ void PollInputEvents(void) CORE.Window.resizedLastFrame = false; - if ((CORE.Window.eventWaiting) || (((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) && ((CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) == 0))) + if ((CORE.Window.eventWaiting) || (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED) && !FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN))) { SDL_WaitEvent(NULL); CORE.Time.previous = GetTime(); @@ -1498,7 +1489,7 @@ void PollInputEvents(void) #ifndef USING_VERSION_SDL3 // Manually detect if the window was maximized (due to SDL2 restore being unreliable on some platforms) to remove the FLAG_WINDOW_MAXIMIZED accordingly - if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) { int borderTop = 0; int borderLeft = 0; @@ -1508,7 +1499,7 @@ void PollInputEvents(void) SDL_Rect usableBounds; SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(platform.window), &usableBounds); - if ((width + borderLeft + borderRight != usableBounds.w) && (height + borderTop + borderBottom != usableBounds.h)) CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; + if ((width + borderLeft + borderRight != usableBounds.w) && (height + borderTop + borderBottom != usableBounds.h)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); } #endif } break; @@ -1524,43 +1515,43 @@ void PollInputEvents(void) case SDL_WINDOWEVENT_MINIMIZED: { - if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) == 0) CORE.Window.flags |= FLAG_WINDOW_MINIMIZED; + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); } break; case SDL_WINDOWEVENT_MAXIMIZED: { - if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) == 0) CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); } break; case SDL_WINDOWEVENT_RESTORED: { - if ((SDL_GetWindowFlags(platform.window) & SDL_WINDOW_MINIMIZED) == 0) + if (!FLAG_IS_SET(SDL_GetWindowFlags(platform.window), SDL_WINDOW_MINIMIZED)) { - if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); } #ifdef USING_VERSION_SDL3 - if ((SDL_GetWindowFlags(platform.window) & SDL_WINDOW_MAXIMIZED) == 0) + if (!FLAG_IS_SET(SDL_GetWindowFlags(platform.window), SDL_WINDOW_MAXIMIZED)) { - if ((CORE.Window.flags & SDL_WINDOW_MAXIMIZED) > 0) CORE.Window.flags &= ~SDL_WINDOW_MAXIMIZED; + if (FLAG_IS_SET(CORE.Window.flags, SDL_WINDOW_MAXIMIZED)) FLAG_CLEAR(CORE.Window.flags, SDL_WINDOW_MAXIMIZED); } #endif } break; case SDL_WINDOWEVENT_HIDDEN: { - if ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) == 0) CORE.Window.flags |= FLAG_WINDOW_HIDDEN; + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN); } break; case SDL_WINDOWEVENT_SHOWN: { - if ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) CORE.Window.flags &= ~FLAG_WINDOW_HIDDEN; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_HIDDEN); } break; case SDL_WINDOWEVENT_FOCUS_GAINED: { - if ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); } break; case SDL_WINDOWEVENT_FOCUS_LOST: { - if ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) == 0) CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); } break; #ifndef USING_VERSION_SDL3 @@ -1930,38 +1921,34 @@ int InitPlatform(void) // Initialize graphic device: display/window and graphic context //---------------------------------------------------------------------------- unsigned int flags = 0; - flags |= SDL_WINDOW_SHOWN; - flags |= SDL_WINDOW_INPUT_FOCUS; - flags |= SDL_WINDOW_MOUSE_FOCUS; - flags |= SDL_WINDOW_MOUSE_CAPTURE; // Window has mouse captured + FLAG_SET(flags, SDL_WINDOW_SHOWN); + FLAG_SET(flags, SDL_WINDOW_INPUT_FOCUS); + FLAG_SET(flags, SDL_WINDOW_MOUSE_FOCUS); + FLAG_SET(flags, SDL_WINDOW_MOUSE_CAPTURE); // Window has mouse captured // Check window creation flags - if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) { CORE.Window.fullscreen = true; - flags |= SDL_WINDOW_FULLSCREEN; + FLAG_SET(flags, SDL_WINDOW_FULLSCREEN); } - //if ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) == 0) flags |= SDL_WINDOW_HIDDEN; - if ((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) flags |= SDL_WINDOW_BORDERLESS; - if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) flags |= SDL_WINDOW_RESIZABLE; - if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) flags |= SDL_WINDOW_MINIMIZED; - if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) flags |= SDL_WINDOW_MAXIMIZED; - - if ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) + //if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) FLAG_SET(flags, SDL_WINDOW_HIDDEN); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED)) FLAG_SET(flags, SDL_WINDOW_BORDERLESS); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) FLAG_SET(flags, SDL_WINDOW_RESIZABLE); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_SET(flags, SDL_WINDOW_MINIMIZED); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) FLAG_SET(flags, SDL_WINDOW_MAXIMIZED); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) { - flags &= ~SDL_WINDOW_INPUT_FOCUS; - flags &= ~SDL_WINDOW_MOUSE_FOCUS; + FLAG_CLEAR(flags, SDL_WINDOW_INPUT_FOCUS); + FLAG_CLEAR(flags, SDL_WINDOW_MOUSE_FOCUS); } + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST)) FLAG_SET(flags, SDL_WINDOW_ALWAYS_ON_TOP); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) FLAG_CLEAR(flags, SDL_WINDOW_MOUSE_CAPTURE); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) FLAG_SET(flags, SDL_WINDOW_ALLOW_HIGHDPI); - if ((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) flags |= SDL_WINDOW_ALWAYS_ON_TOP; - if ((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0) flags &= ~SDL_WINDOW_MOUSE_CAPTURE; - - if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) flags |= SDL_WINDOW_ALLOW_HIGHDPI; - - //if ((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) flags |= SDL_WINDOW_TRANSPARENT; // Alternative: SDL_GL_ALPHA_SIZE = 8 - - //if ((CORE.Window.flags & FLAG_FULLSCREEN_DESKTOP) > 0) flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + //if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TRANSPARENT)) FLAG_SET(flags, SDL_WINDOW_TRANSPARENT); // Alternative: SDL_GL_ALPHA_SIZE = 8 + //if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_DESKTOP)) FLAG_SET(flags, SDL_WINDOW_FULLSCREEN_DESKTOP); // NOTE: Some OpenGL context attributes must be set before window creation @@ -2004,7 +1991,7 @@ int InitPlatform(void) SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); } - if (CORE.Window.flags & FLAG_MSAA_4X_HINT) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); @@ -2047,7 +2034,7 @@ int InitPlatform(void) if (platform.glContext != NULL) { - SDL_GL_SetSwapInterval((CORE.Window.flags & FLAG_VSYNC_HINT)? 1 : 0); + SDL_GL_SetSwapInterval((FLAG_IS_SET(CORE.Window.flags, FLAG_VSYNC_HINT))? 1: 0); // Load OpenGL extensions // NOTE: GL procedures address loader is required to load extensions diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index da0d08aca..c0fa5a5f3 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -1148,7 +1148,7 @@ int InitPlatform(void) // Initialize graphic device: display/window and graphic context //---------------------------------------------------------------------------- CORE.Window.fullscreen = true; - CORE.Window.flags |= FLAG_FULLSCREEN_MODE; + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); #if defined(DEFAULT_GRAPHIC_DEVICE_DRM) platform.fd = open(DEFAULT_GRAPHIC_DEVICE_DRM, O_RDWR); @@ -1297,7 +1297,7 @@ int InitPlatform(void) CORE.Window.screen.height = CORE.Window.display.height; } - const bool allowInterlaced = CORE.Window.flags & FLAG_INTERLACED_HINT; + const bool allowInterlaced = FLAG_IS_SET(CORE.Window.flags, FLAG_INTERLACED_HINT); const int fps = (CORE.Time.target > 0) ? (1.0/CORE.Time.target) : 60; // Try to find an exact matching mode @@ -1328,7 +1328,7 @@ int InitPlatform(void) TRACELOG(LOG_INFO, "DISPLAY: Selected DRM connector mode %s (%ux%u%c@%u)", platform.connector->modes[platform.modeIndex].name, platform.connector->modes[platform.modeIndex].hdisplay, platform.connector->modes[platform.modeIndex].vdisplay, - (platform.connector->modes[platform.modeIndex].flags & DRM_MODE_FLAG_INTERLACE) ? 'i' : 'p', + FLAG_IS_SET(platform.connector->modes[platform.modeIndex].flags, DRM_MODE_FLAG_INTERLACE)? 'i' : 'p', platform.connector->modes[platform.modeIndex].vrefresh); drmModeFreeEncoder(enc); @@ -1384,7 +1384,7 @@ int InitPlatform(void) EGLint samples = 0; EGLint sampleBuffer = 0; - if (CORE.Window.flags & FLAG_MSAA_4X_HINT) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) { samples = 4; sampleBuffer = 1; @@ -1561,17 +1561,17 @@ int InitPlatform(void) TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); #endif - if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) MinimizeWindow(); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) MinimizeWindow(); // If graphic device is no properly initialized, we end program if (!CORE.Window.ready) { TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphic device"); return -1; } else SetWindowPosition(GetMonitorWidth(GetCurrentMonitor())/2 - CORE.Window.screen.width/2, GetMonitorHeight(GetCurrentMonitor())/2 - CORE.Window.screen.height/2); // Set some default window flags - CORE.Window.flags &= ~FLAG_WINDOW_HIDDEN; // false - CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; // false - CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; // true - CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; // false + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // false + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // false + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // true + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // false //---------------------------------------------------------------------------- // Initialize timing system @@ -1720,8 +1720,8 @@ static void InitKeyboard(void) // New terminal settings for keyboard: turn off buffering (non-canonical mode), echo and key processing // NOTE: ISIG controls if ^C and ^Z generate break signals or not - keyboardNewSettings.c_lflag &= ~(ICANON | ECHO | ISIG); - //keyboardNewSettings.c_iflag &= ~(ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF); + FLAG_CLEAR(keyboardNewSettings.c_lflag, ICANON | ECHO | ISIG); + //FLAG_CLEAR(keyboardNewSettings.c_iflag, ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF); keyboardNewSettings.c_cc[VMIN] = 1; keyboardNewSettings.c_cc[VTIME] = 0; @@ -2402,7 +2402,7 @@ static int FindMatchingConnectorMode(const drmModeConnector *connector, const dr for (size_t i = 0; i < connector->count_modes; i++) { TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, connector->modes[i].hdisplay, connector->modes[i].vdisplay, - connector->modes[i].vrefresh, (connector->modes[i].flags & DRM_MODE_FLAG_INTERLACE)? "interlaced" : "progressive"); + connector->modes[i].vrefresh, (FLAG_IS_SET(connector->modes[i].flags, DRM_MODE_FLAG_INTERLACE) > 0)? "interlaced" : "progressive"); if (0 == BINCMP(&platform.crtc->mode, &platform.connector->modes[i])) return i; } @@ -2423,9 +2423,9 @@ static int FindExactConnectorMode(const drmModeConnector *connector, uint width, { const drmModeModeInfo *const mode = &platform.connector->modes[i]; - TRACELOG(LOG_TRACE, "DISPLAY: DRM Mode %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, (mode->flags & DRM_MODE_FLAG_INTERLACE)? "interlaced" : "progressive"); + TRACELOG(LOG_TRACE, "DISPLAY: DRM Mode %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, (FLAG_IS_SET(mode->flags, DRM_MODE_FLAG_INTERLACE) > 0)? "interlaced" : "progressive"); - if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && !allowInterlaced) continue; + if ((FLAG_IS_SET(mode->flags, DRM_MODE_FLAG_INTERLACE) > 0) && !allowInterlaced) continue; if ((mode->hdisplay == width) && (mode->vdisplay == height) && (mode->vrefresh == fps)) return i; } @@ -2449,7 +2449,7 @@ static int FindNearestConnectorMode(const drmModeConnector *connector, uint widt const drmModeModeInfo *const mode = &platform.connector->modes[i]; TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, - (mode->flags & DRM_MODE_FLAG_INTERLACE)? "interlaced" : "progressive"); + (FLAG_IS_SET(mode->flags, DRM_MODE_FLAG_INTERLACE) > 0)? "interlaced" : "progressive"); if ((mode->hdisplay < width) || (mode->vdisplay < height)) { @@ -2457,7 +2457,7 @@ static int FindNearestConnectorMode(const drmModeConnector *connector, uint widt continue; } - if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && !allowInterlaced) + if ((FLAG_IS_SET(mode->flags, DRM_MODE_FLAG_INTERLACE) > 0) && !allowInterlaced) { TRACELOG(LOG_TRACE, "DISPLAY: DRM shouldn't choose an interlaced mode"); continue; diff --git a/src/platforms/rcore_template.c b/src/platforms/rcore_template.c index 36629fc69..bc03a3cdb 100644 --- a/src/platforms/rcore_template.c +++ b/src/platforms/rcore_template.c @@ -460,11 +460,11 @@ int InitPlatform(void) // Below example illustrates that process using EGL library //---------------------------------------------------------------------------- CORE.Window.fullscreen = true; - CORE.Window.flags |= FLAG_FULLSCREEN_MODE; + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); EGLint samples = 0; EGLint sampleBuffer = 0; - if (CORE.Window.flags & FLAG_MSAA_4X_HINT) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) { samples = 4; sampleBuffer = 1; diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index c8fe0cfe7..5f8afd7e4 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -181,8 +181,8 @@ void ToggleFullscreen(void) const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); if (wasFullscreen) { - if (CORE.Window.flags & FLAG_FULLSCREEN_MODE) enterFullscreen = false; - else if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) enterFullscreen = true; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) enterFullscreen = false; + else if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) enterFullscreen = true; else { const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); @@ -194,8 +194,8 @@ void ToggleFullscreen(void) EM_ASM(document.exitFullscreen();); CORE.Window.fullscreen = false; - CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; - CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE; + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); } else enterFullscreen = true; @@ -210,7 +210,7 @@ void ToggleFullscreen(void) }, 100); ); CORE.Window.fullscreen = true; - CORE.Window.flags |= FLAG_FULLSCREEN_MODE; + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); } // NOTE: Old notes below: @@ -263,7 +263,7 @@ void ToggleFullscreen(void) TRACELOG(LOG_WARNING, "Emscripten: Enter fullscreen: Canvas size: %i x %i", width, height); CORE.Window.fullscreen = true; // Toggle fullscreen flag - CORE.Window.flags |= FLAG_FULLSCREEN_MODE; + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); } else { @@ -275,7 +275,7 @@ void ToggleFullscreen(void) TRACELOG(LOG_WARNING, "Emscripten: Exit fullscreen: Canvas size: %i x %i", width, height); CORE.Window.fullscreen = false; // Toggle fullscreen flag - CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); } */ } @@ -289,8 +289,8 @@ void ToggleBorderlessWindowed(void) const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); if (wasFullscreen) { - if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) enterBorderless = false; - else if (CORE.Window.flags & FLAG_FULLSCREEN_MODE) enterBorderless = true; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) enterBorderless = false; + else if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) enterBorderless = true; else { const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); @@ -302,8 +302,8 @@ void ToggleBorderlessWindowed(void) EM_ASM(document.exitFullscreen();); CORE.Window.fullscreen = false; - CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; - CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE; + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); } else enterBorderless = true; @@ -322,14 +322,14 @@ void ToggleBorderlessWindowed(void) }, 100); }, 100); ); - CORE.Window.flags |= FLAG_BORDERLESS_WINDOWED_MODE; + FLAG_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); } } // Set window state: maximized, if resizable void MaximizeWindow(void) { - if ((glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE) && !(CORE.Window.flags & FLAG_WINDOW_MAXIMIZED)) + if ((glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE) && !(FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED))) { platform.unmaximizedWidth = CORE.Window.screen.width; platform.unmaximizedHeight = CORE.Window.screen.height; @@ -339,7 +339,7 @@ void MaximizeWindow(void) if (tabWidth && tabHeight) glfwSetWindowSize(platform.handle, tabWidth, tabHeight); - CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); } } @@ -352,11 +352,11 @@ void MinimizeWindow(void) // Restore window from being minimized/maximized void RestoreWindow(void) { - if ((glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE) && (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED)) + if ((glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE) && (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED))) { if (platform.unmaximizedWidth && platform.unmaximizedHeight) glfwSetWindowSize(platform.handle, platform.unmaximizedWidth, platform.unmaximizedHeight); - CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); } } @@ -369,13 +369,13 @@ void SetWindowState(unsigned int flags) // NOTE: In most cases the functions already change the flags internally // State change: FLAG_VSYNC_HINT - if ((flags & FLAG_VSYNC_HINT) > 0) + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) { TRACELOG(LOG_WARNING, "SetWindowState(FLAG_VSYNC_HINT) not available on target platform"); } // State change: FLAG_BORDERLESS_WINDOWED_MODE - if ((flags & FLAG_BORDERLESS_WINDOWED_MODE) > 0) + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) { // NOTE: Window state flag updated inside ToggleBorderlessWindowed() function const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); @@ -383,13 +383,13 @@ void SetWindowState(unsigned int flags) { const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); const int canvasStyleWidth = EM_ASM_INT( { return parseInt(Module.canvas.style.width); }, 0); - if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) || canvasStyleWidth > canvasWidth) ToggleBorderlessWindowed(); + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) || canvasStyleWidth > canvasWidth) ToggleBorderlessWindowed(); } else ToggleBorderlessWindowed(); } // State change: FLAG_FULLSCREEN_MODE - if ((flags & FLAG_FULLSCREEN_MODE) > 0) + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) { // NOTE: Window state flag updated inside ToggleFullscreen() function const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); @@ -397,38 +397,38 @@ void SetWindowState(unsigned int flags) { const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); const int screenWidth = EM_ASM_INT( { return screen.width; }, 0); - if ((CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) || screenWidth == canvasWidth ) ToggleFullscreen(); + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) || screenWidth == canvasWidth ) ToggleFullscreen(); } else ToggleFullscreen(); } // State change: FLAG_WINDOW_RESIZABLE - if (((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) != (flags & FLAG_WINDOW_RESIZABLE)) && ((flags & FLAG_WINDOW_RESIZABLE) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) && (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE))) { glfwSetWindowAttrib(platform.handle, GLFW_RESIZABLE, GLFW_TRUE); - CORE.Window.flags |= FLAG_WINDOW_RESIZABLE; + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE); } // State change: FLAG_WINDOW_UNDECORATED - if ((flags & FLAG_WINDOW_UNDECORATED) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) { TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_UNDECORATED) not available on target platform"); } // State change: FLAG_WINDOW_HIDDEN - if ((flags & FLAG_WINDOW_HIDDEN) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) { TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_HIDDEN) not available on target platform"); } // State change: FLAG_WINDOW_MINIMIZED - if ((flags & FLAG_WINDOW_MINIMIZED) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) { TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_MINIMIZED) not available on target platform"); } // State change: FLAG_WINDOW_MAXIMIZED - if (((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) != (flags & FLAG_WINDOW_MAXIMIZED)) && ((flags & FLAG_WINDOW_MAXIMIZED) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED) != FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) && (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED))) { if (glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE) { @@ -440,24 +440,24 @@ void SetWindowState(unsigned int flags) if (tabWidth && tabHeight) glfwSetWindowSize(platform.handle, tabWidth, tabHeight); - CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); } } // State change: FLAG_WINDOW_UNFOCUSED - if ((flags & FLAG_WINDOW_UNFOCUSED) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) { TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_UNFOCUSED) not available on target platform"); } // State change: FLAG_WINDOW_TOPMOST - if ((flags & FLAG_WINDOW_TOPMOST) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) { TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_TOPMOST) not available on target platform"); } // State change: FLAG_WINDOW_ALWAYS_RUN - if ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) { TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_ALWAYS_RUN) not available on target platform"); } @@ -466,31 +466,31 @@ void SetWindowState(unsigned int flags) // NOTE: Review for PLATFORM_WEB // State change: FLAG_WINDOW_TRANSPARENT - if ((flags & FLAG_WINDOW_TRANSPARENT) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) { TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_TRANSPARENT) not available on target platform"); } // State change: FLAG_WINDOW_HIGHDPI - if ((flags & FLAG_WINDOW_HIGHDPI) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) { TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_HIGHDPI) not available on target platform"); } // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH - if ((flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) { TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_MOUSE_PASSTHROUGH) not available on target platform"); } // State change: FLAG_MSAA_4X_HINT - if ((flags & FLAG_MSAA_4X_HINT) > 0) + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) { TRACELOG(LOG_WARNING, "SetWindowState(FLAG_MSAA_4X_HINT) not available on target platform"); } // State change: FLAG_INTERLACED_HINT - if ((flags & FLAG_INTERLACED_HINT) > 0) + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) { TRACELOG(LOG_WARNING, "SetWindowState(FLAG_INTERLACED_HINT) not available on target platform"); } @@ -503,90 +503,90 @@ void ClearWindowState(unsigned int flags) // NOTE: In most cases the functions already change the flags internally // State change: FLAG_VSYNC_HINT - if ((flags & FLAG_VSYNC_HINT) > 0) + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) { TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_VSYNC_HINT) not available on target platform"); } // State change: FLAG_BORDERLESS_WINDOWED_MODE - if ((flags & FLAG_BORDERLESS_WINDOWED_MODE) > 0) + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) { const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); if (wasFullscreen) { const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); const int screenWidth = EM_ASM_INT( { return screen.width; }, 0); - if ((CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) || (screenWidth == canvasWidth)) EM_ASM(document.exitFullscreen();); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE) || (screenWidth == canvasWidth)) EM_ASM(document.exitFullscreen();); } - CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE; + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); } // State change: FLAG_FULLSCREEN_MODE - if ((flags & FLAG_FULLSCREEN_MODE) > 0) + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) { const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); if (wasFullscreen) { const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); const int canvasStyleWidth = EM_ASM_INT( { return parseInt(Module.canvas.style.width); }, 0); - if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) || (canvasStyleWidth > canvasWidth)) EM_ASM(document.exitFullscreen();); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE) || (canvasStyleWidth > canvasWidth)) EM_ASM(document.exitFullscreen();); } CORE.Window.fullscreen = false; - CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); } // State change: FLAG_WINDOW_RESIZABLE - if (((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) && ((flags & FLAG_WINDOW_RESIZABLE) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) && (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE))) { glfwSetWindowAttrib(platform.handle, GLFW_RESIZABLE, GLFW_FALSE); - CORE.Window.flags &= ~FLAG_WINDOW_RESIZABLE; + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_RESIZABLE); } // State change: FLAG_WINDOW_HIDDEN - if ((flags & FLAG_WINDOW_HIDDEN) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) { TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_HIDDEN) not available on target platform"); } // State change: FLAG_WINDOW_MINIMIZED - if ((flags & FLAG_WINDOW_MINIMIZED) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) { TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_MINIMIZED) not available on target platform"); } // State change: FLAG_WINDOW_MAXIMIZED - if (((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) && ((flags & FLAG_WINDOW_MAXIMIZED) > 0)) + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) && (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED))) { if (glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE) { if (platform.unmaximizedWidth && platform.unmaximizedHeight) glfwSetWindowSize(platform.handle, platform.unmaximizedWidth, platform.unmaximizedHeight); - CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); } } // State change: FLAG_WINDOW_UNDECORATED - if ((flags & FLAG_WINDOW_UNDECORATED) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) { TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_UNDECORATED) not available on target platform"); } // State change: FLAG_WINDOW_UNFOCUSED - if ((flags & FLAG_WINDOW_UNFOCUSED) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) { TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_UNFOCUSED) not available on target platform"); } // State change: FLAG_WINDOW_TOPMOST - if ((flags & FLAG_WINDOW_TOPMOST) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) { TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_TOPMOST) not available on target platform"); } // State change: FLAG_WINDOW_ALWAYS_RUN - if ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) { TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_ALWAYS_RUN) not available on target platform"); } @@ -595,31 +595,31 @@ void ClearWindowState(unsigned int flags) // NOTE: Review for PLATFORM_WEB // State change: FLAG_WINDOW_TRANSPARENT - if ((flags & FLAG_WINDOW_TRANSPARENT) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) { TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_TRANSPARENT) not available on target platform"); } // State change: FLAG_WINDOW_HIGHDPI - if ((flags & FLAG_WINDOW_HIGHDPI) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) { TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_HIGHDPI) not available on target platform"); } // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH - if ((flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0) + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) { TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_MOUSE_PASSTHROUGH) not available on target platform"); } // State change: FLAG_MSAA_4X_HINT - if ((flags & FLAG_MSAA_4X_HINT) > 0) + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) { TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_MSAA_4X_HINT) not available on target platform"); } // State change: FLAG_INTERLACED_HINT - if ((flags & FLAG_INTERLACED_HINT) > 0) + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) { TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_INTERLACED_HINT) not available on target platform"); } @@ -663,7 +663,7 @@ void SetWindowMinSize(int width, int height) CORE.Window.screenMin.height = height; // Trigger the resize event once to update the window minimum width and height - if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) != 0) EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != 0) EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); } // Set window maximum dimensions (FLAG_WINDOW_RESIZABLE) @@ -673,7 +673,7 @@ void SetWindowMaxSize(int width, int height) CORE.Window.screenMax.height = height; // Trigger the resize event once to update the window maximum width and height - if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) != 0) EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != 0) EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); } // Set window dimensions @@ -1122,27 +1122,27 @@ int InitPlatform(void) // glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers // Check window creation flags - if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) CORE.Window.fullscreen = true; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) CORE.Window.fullscreen = true; - if ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window else glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); // Window initially hidden - if ((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // Border and buttons on Window + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED)) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // Border and buttons on Window else glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); // Decorated window - if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // Resizable window + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // Resizable window else glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // Avoid window being resizable // Disable FLAG_WINDOW_MINIMIZED, not supported on initialization - if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // Disable FLAG_WINDOW_MAXIMIZED, not supported on initialization - if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); - if ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE); else glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE); - if ((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST)) glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); else glfwWindowHint(GLFW_FLOATING, GLFW_FALSE); // NOTE: Some GLFW flags are not supported on HTML5 @@ -1150,10 +1150,10 @@ int InitPlatform(void) // Scale content area based on the monitor content scale where window is placed on // NOTE: This feature requires emscripten 3.1.51 - //if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); + //if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); //else glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); - if (CORE.Window.flags & FLAG_MSAA_4X_HINT) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) { // NOTE: MSAA is only enabled for main framebuffer, not user-created FBOs TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); @@ -1296,7 +1296,7 @@ int InitPlatform(void) glfwSetWindowFocusCallback(platform.handle, WindowFocusCallback); glfwSetDropCallback(platform.handle, WindowDropCallback); - if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { // Window content (framebuffer) scale callback glfwSetWindowContentScaleCallback(platform.handle, WindowContentScaleCallback); @@ -1338,7 +1338,7 @@ int InitPlatform(void) return -1; } - if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) MinimizeWindow(); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) MinimizeWindow(); // If graphic device is no properly initialized, we end program if (!CORE.Window.ready) { TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphic device"); return -1; } @@ -1423,7 +1423,7 @@ static void WindowSizeCallback(GLFWwindow *window, int width, int height) if (IsWindowFullscreen()) return; // Set current screen size - if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { Vector2 windowScaleDPI = GetWindowScaleDPI(); @@ -1448,15 +1448,15 @@ static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float s // GLFW3: Called on windows minimized/restored static void WindowIconifyCallback(GLFWwindow *window, int iconified) { - if (iconified) CORE.Window.flags |= FLAG_WINDOW_MINIMIZED; // The window was iconified - else CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; // The window was restored + if (iconified) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // The window was iconified + else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // The window was restored } // GLFW3: Called on windows get/lose focus static void WindowFocusCallback(GLFWwindow *window, int focused) { - if (focused) CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; // The window was focused - else CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; // The window lost focus + if (focused) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window was focused + else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window lost focus } // GLFW3: Called on file-drop over the window @@ -1782,8 +1782,8 @@ static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const Emscripte if (!wasFullscreen) { CORE.Window.fullscreen = false; - CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; - CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE; + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); } } @@ -1794,7 +1794,7 @@ static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const Emscripte static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData) { // Don't resize non-resizeable windows - if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) == 0) return 1; + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) return 1; // This event is called whenever the window changes sizes, // so the size of the canvas object is explicitly retrieved below @@ -1844,8 +1844,8 @@ static EM_BOOL EmscriptenFocusCallback(int eventType, const EmscriptenFocusEvent // Emscripten: Called on visibility change events static EM_BOOL EmscriptenVisibilityChangeCallback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData) { - if (visibilityChangeEvent->hidden) CORE.Window.flags |= FLAG_WINDOW_HIDDEN; // The window was hidden - else CORE.Window.flags &= ~FLAG_WINDOW_HIDDEN; // The window was restored + if (visibilityChangeEvent->hidden) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // The window was hidden + else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // The window was restored return 1; // The event was consumed by the callback handler } //------------------------------------------------------------------------------------------------------- diff --git a/src/rcore.c b/src/rcore.c index d06e3089b..cb6c89881 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -280,7 +280,7 @@ __declspec(dllimport) unsigned int __stdcall timeEndPeriod(unsigned int uPeriod) #define FLAG_SET(n, f) ((n) |= (f)) #define FLAG_CLEAR(n, f) ((n) &= ~(f)) #define FLAG_TOGGLE(n, f) ((n) ^= (f)) -#define FLAG_CHECK(n, f) ((n) & (f)) +#define FLAG_IS_SET(n, f) (((n) & (f)) > 0) //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -725,7 +725,7 @@ void InitWindow(int width, int height, const char *title) // Set font white rectangle for shapes drawing, so shapes and text can be batched together // WARNING: rshapes module is required, if not available, default internal white rectangle is used Rectangle rec = GetFontDefault().recs[95]; - if (CORE.Window.flags & FLAG_MSAA_4X_HINT) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) { // NOTE: We try to maxime rec padding to avoid pixel bleeding on MSAA filtering SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 2, rec.y + 2, 1, 1 }); @@ -797,25 +797,25 @@ bool IsWindowFullscreen(void) // Check if window is currently hidden bool IsWindowHidden(void) { - return ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0); + return (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)); } // Check if window has been minimized bool IsWindowMinimized(void) { - return ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0); + return (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)); } // Check if window has been maximized bool IsWindowMaximized(void) { - return ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0); + return (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)); } // Check if window has the focus bool IsWindowFocused(void) { - return ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) == 0); + return (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)); } // Check if window has been resizedLastFrame @@ -827,7 +827,7 @@ bool IsWindowResized(void) // Check if one specific window flag is enabled bool IsWindowState(unsigned int flag) { - return ((CORE.Window.flags & flag) > 0); + return (FLAG_IS_SET(CORE.Window.flags, flag)); } // Get current screen width @@ -1208,7 +1208,7 @@ void BeginScissorMode(int x, int y, int width, int height) rlScissor((int)(x*scale.x), (int)(GetScreenHeight()*scale.y - (((y + height)*scale.y))), (int)(width*scale.x), (int)(height*scale.y)); } #else - if (!CORE.Window.usingFbo && ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0)) + if (!CORE.Window.usingFbo && (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI))) { Vector2 scale = GetWindowScaleDPI(); rlScissor((int)(x*scale.x), (int)(CORE.Window.currentFbo.height - (y + height)*scale.y), (int)(width*scale.x), (int)(height*scale.y)); @@ -1931,7 +1931,7 @@ void SetConfigFlags(unsigned int flags) // Selected flags are set but not evaluated at this point, // flag evaluation happens at InitWindow() or SetWindowState() - CORE.Window.flags |= flags; + FLAG_SET(CORE.Window.flags, flags); } //---------------------------------------------------------------------------------- From 3cf3b309c6caff366e13ed0e8db897ca7b375bf3 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 2 Nov 2025 19:40:45 +0100 Subject: [PATCH 034/430] REVIEWED: Flags set/clear #5169 --- src/platforms/rcore_desktop_glfw.c | 8 ++++---- src/platforms/rcore_desktop_rgfw.c | 4 ++++ src/platforms/rcore_desktop_sdl.c | 10 +++++++--- src/platforms/rcore_desktop_win32.c | 2 +- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 78b513b40..dbe2062a0 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1854,15 +1854,15 @@ static void WindowIconifyCallback(GLFWwindow *window, int iconified) // GLFW3 WindowMaximize Callback, runs when window is maximized/restored static void WindowMaximizeCallback(GLFWwindow *window, int maximized) { - if (maximized) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // The window was maximized - else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // The window was restored + if (maximized) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // The window was maximized + else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // The window was restored } // GLFW3 WindowFocus Callback, runs when window get/lose focus static void WindowFocusCallback(GLFWwindow *window, int focused) { - if (focused) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window was maximized - else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window was restored + if (focused) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window was focused + else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window lost focus } // GLFW3 Window Drop Callback, runs when drop files into window diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 863e57a99..177c88fc7 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -423,6 +423,10 @@ void SetWindowState(unsigned int flags) { RGFW_window_setFloating(platform.window, RGFW_TRUE); } + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) + { + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); + } if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) { TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only be configured before window initialization"); diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 0b6376a50..841fc4479 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -588,6 +588,10 @@ void SetWindowState(unsigned int flags) { SDL_SetWindowAlwaysOnTop(platform.window, SDL_FALSE); } + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN) + { + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); + } if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) { TRACELOG(LOG_WARNING, "SetWindowState() - FLAG_WINDOW_TRANSPARENT is not supported on PLATFORM_DESKTOP_SDL"); @@ -1933,7 +1937,7 @@ int InitPlatform(void) FLAG_SET(flags, SDL_WINDOW_FULLSCREEN); } - //if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) FLAG_SET(flags, SDL_WINDOW_HIDDEN); + //if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) FLAG_SET(flags, SDL_WINDOW_HIDDEN); if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED)) FLAG_SET(flags, SDL_WINDOW_BORDERLESS); if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) FLAG_SET(flags, SDL_WINDOW_RESIZABLE); if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_SET(flags, SDL_WINDOW_MINIMIZED); @@ -1943,9 +1947,9 @@ int InitPlatform(void) FLAG_CLEAR(flags, SDL_WINDOW_INPUT_FOCUS); FLAG_CLEAR(flags, SDL_WINDOW_MOUSE_FOCUS); } - if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST)) FLAG_SET(flags, SDL_WINDOW_ALWAYS_ON_TOP); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST)) FLAG_SET(flags, SDL_WINDOW_ALWAYS_ON_TOP); if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) FLAG_CLEAR(flags, SDL_WINDOW_MOUSE_CAPTURE); - if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) FLAG_SET(flags, SDL_WINDOW_ALLOW_HIGHDPI); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) FLAG_SET(flags, SDL_WINDOW_ALLOW_HIGHDPI); //if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TRANSPARENT)) FLAG_SET(flags, SDL_WINDOW_TRANSPARENT); // Alternative: SDL_GL_ALPHA_SIZE = 8 //if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_DESKTOP)) FLAG_SET(flags, SDL_WINDOW_FULLSCREEN_DESKTOP); diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 1dadd5586..ce80eb41b 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -271,7 +271,7 @@ static DWORD MakeWindowStyle(unsigned flags) // Minimized takes precedence over maximized int mized = MIZED_NONE; - if (FLAG_CHECK(flags, FLAG_WINDOW_MINIMIZED)) mized = MIZED_MIN; + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) mized = MIZED_MIN; if (flags & FLAG_WINDOW_MAXIMIZED) mized = MIZED_MAX; switch (mized) From 46e8343a3085ce2212dde39e9f10f98f77d516df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agnis=20Aldi=C5=86=C5=A1=20=22NeZv=C4=93rs?= Date: Sun, 2 Nov 2025 20:45:51 +0200 Subject: [PATCH 035/430] [examples] Added `core_viewport_scaling` (#5313) * example - core_viewport_scaling * Code convention update --- examples/Makefile | 1 + examples/Makefile.Web | 1 + examples/README.md | 1 + examples/core/core_viewport_scaling.c | 351 +++++++++++ examples/core/core_viewport_scaling.png | Bin 0 -> 8699 bytes examples/examples_list.txt | 1 + .../examples/core_viewport_scaling.vcxproj | 569 ++++++++++++++++++ projects/VS2022/raylib.sln | 27 + 8 files changed, 951 insertions(+) create mode 100644 examples/core/core_viewport_scaling.c create mode 100644 examples/core/core_viewport_scaling.png create mode 100644 projects/VS2022/examples/core_viewport_scaling.vcxproj diff --git a/examples/Makefile b/examples/Makefile index b5e968079..6fcf218ee 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -551,6 +551,7 @@ CORE = \ core/core_storage_values \ core/core_text_file_loading \ core/core_undo_redo \ + core/core_viewport_scaling \ core/core_vr_simulator \ core/core_window_flags \ core/core_window_letterbox \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 07f8d8fb8..3ac776435 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -539,6 +539,7 @@ CORE = \ core/core_storage_values \ core/core_text_file_loading \ core/core_undo_redo \ + core/core_viewport_scaling \ core/core_vr_simulator \ core/core_window_flags \ core/core_window_letterbox \ diff --git a/examples/README.md b/examples/README.md index 4d0b25d56..28a2107e4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -64,6 +64,7 @@ Examples using raylib[core](../src/rcore.c) platform functionality like window c | [core_high_dpi](core/core_high_dpi.c) | core_high_dpi | ⭐⭐☆☆ | 5.0 | 5.5 | [Jonathan Marler](https://github.com/marler8997) | | [core_render_texture](core/core_render_texture.c) | core_render_texture | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | | [core_undo_redo](core/core_undo_redo.c) | core_undo_redo | ⭐⭐⭐☆ | 5.5 | 5.6 | [Ramon Santamaria](https://github.com/raysan5) | +| [core_viewport_scaling](core/core_viewport_scaling.c) | core_viewport_scaling | ⭐⭐☆☆ | 5.5 | 5.5 | [Agnis Aldins](https://github.com/nezvers) | | [core_input_actions](core/core_input_actions.c) | core_input_actions | ⭐⭐☆☆ | 5.5 | 5.6 | [Jett](https://github.com/JettMonstersGoBoom) | | [core_directory_files](core/core_directory_files.c) | core_directory_files | ⭐☆☆☆ | 5.5 | 5.6 | [Hugo ARNAL](https://github.com/hugoarnal) | | [core_highdpi_testbed](core/core_highdpi_testbed.c) | core_highdpi_testbed | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | diff --git a/examples/core/core_viewport_scaling.c b/examples/core/core_viewport_scaling.c new file mode 100644 index 000000000..e70b89829 --- /dev/null +++ b/examples/core/core_viewport_scaling.c @@ -0,0 +1,351 @@ +/******************************************************************************************* +* +* raylib [core] example - viewport scaling +* +* Example complexity rating: [★★☆☆] 2/4 +* +* Example originally created with raylib 5.5, last time updated with raylib 5.5 +* +* Example contributed by Agnis Aldins (@nezvers) 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) 2025 Agnis Aldins (@nezvers) +* +********************************************************************************************/ + +#include "raylib.h" + +// For itteration purposes and teaching example +#define RESOLUTION_COUNT 4 + +enum ViewportType +{ + // Only upscale, useful for pixel art + KEEP_ASPECT_INTEGER, + KEEP_HEIGHT_INTEGER, + KEEP_WIDTH_INTEGER, + // Can also downscale + KEEP_ASPECT, + KEEP_HEIGHT, + KEEP_WIDTH, + // For itteration purposes and as a teaching example + VIEWPORT_TYPE_COUNT, +}; + +//-------------------------------------------------------------------------------------- +// Module Functions Declaration +//-------------------------------------------------------------------------------------- +static void KeepAspectCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect); + +static void KeepHeightCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect); + +static void KeepWidthCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect); + +static void KeepAspectCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect); + +static void KeepHeightCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect); + +static void KeepWidthCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect); + +static void ResizeRenderSize(enum ViewportType viewportType, int *screenWidth, int *screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect, RenderTexture2D *target); + +// Example how to calculate position on RenderTexture +static Vector2 Screen2RenderTexturePosition(Vector2 point, Rectangle *textureRect, Rectangle *scaledRect); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //--------------------------------------------------------- + // Preset resolutions that could be created by subdividing screen resolution + Vector2 resolutionList[RESOLUTION_COUNT] = { + (Vector2){64, 64}, + (Vector2){256, 240}, + (Vector2){320, 180}, + // 4K doesn't work with integer scaling but included for example purposes with non-integer scaling + (Vector2){3840, 2160}, + }; + int resolutionIndex = 0; + + int screenWidth = 800; + int screenHeight = 450; + int gameWidth = 64; + int gameHeight = 64; + + RenderTexture2D target = (RenderTexture2D){0}; + Rectangle sourceRect = (Rectangle){0}; + Rectangle destRect = (Rectangle){0}; + + // For displaying on GUI + const char *ViewportTypeNames[VIEWPORT_TYPE_COUNT] = { + "KEEP_ASPECT_INTEGER", + "KEEP_HEIGHT_INTEGER", + "KEEP_WIDTH_INTEGER", + "KEEP_ASPECT", + "KEEP_HEIGHT", + "KEEP_WIDTH", + }; + enum ViewportType viewportType = KEEP_ASPECT_INTEGER; + + SetConfigFlags(FLAG_WINDOW_RESIZABLE); + InitWindow(screenWidth, screenHeight, "raylib [core] example - Viewport Scaling"); + ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target); + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //---------------------------------------------------------- + + // Button rectangles + Rectangle decreaseResolutionButton = (Rectangle){200, 30, 10, 10}; + Rectangle increaseResolutionButton = (Rectangle){215, 30, 10, 10}; + Rectangle decreaseTypeButton = (Rectangle){200, 45, 10, 10}; + Rectangle increaseTypeButton = (Rectangle){215, 45, 10, 10}; + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //----------------------------------------------------- + if (IsWindowResized()){ + ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target); + } + Vector2 mousePosition = GetMousePosition(); + bool mousePressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); + + // Check buttons and rescale + if (CheckCollisionPointRec(mousePosition, decreaseResolutionButton) && mousePressed){ + resolutionIndex = (resolutionIndex + RESOLUTION_COUNT - 1) % RESOLUTION_COUNT; + gameWidth = resolutionList[resolutionIndex].x; + gameHeight = resolutionList[resolutionIndex].y; + ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target); + } + if (CheckCollisionPointRec(mousePosition, increaseResolutionButton) && mousePressed){ + resolutionIndex = (resolutionIndex + 1) % RESOLUTION_COUNT; + gameWidth = resolutionList[resolutionIndex].x; + gameHeight = resolutionList[resolutionIndex].y; + ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target); + } + if (CheckCollisionPointRec(mousePosition, decreaseTypeButton) && mousePressed){ + viewportType = (viewportType + VIEWPORT_TYPE_COUNT - 1) % VIEWPORT_TYPE_COUNT; + ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target); + } + if (CheckCollisionPointRec(mousePosition, increaseTypeButton) && mousePressed){ + viewportType = (viewportType + 1) % VIEWPORT_TYPE_COUNT; + ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target); + } + + Vector2 textureMousePosition = Screen2RenderTexturePosition(mousePosition, &sourceRect, &destRect); + + // Draw + //----------------------------------------------------- + // Draw our scene to the render texture + BeginTextureMode(target); + ClearBackground(WHITE); + DrawCircle(textureMousePosition.x, textureMousePosition.y, 20.f, LIME); + + + EndTextureMode(); + + // Draw render texture to main framebuffer + BeginDrawing(); + ClearBackground(BLACK); + + // Draw our render texture with rotation applied + const Vector2 ORIGIN_POSITION = (Vector2){ 0.0f, 0.0f }; + const float ROTATION = 0.f; + DrawTexturePro(target.texture, sourceRect, destRect, ORIGIN_POSITION, ROTATION, WHITE); + + // Draw Native resolution (GUI or anything) + // Draw info box + Rectangle infoRect = (Rectangle){5, 5, 330, 105}; + DrawRectangleRec(infoRect, Fade(LIGHTGRAY, 0.7f)); + DrawRectangleLines(infoRect.x, infoRect.y, infoRect.width, infoRect.height, BLUE); + + DrawText(TextFormat("Window Resolution: %d x %d", screenWidth, screenHeight), 15, 15, 10, BLACK); + DrawText(TextFormat("Game Resolution: %d x %d", gameWidth, gameHeight), 15, 30, 10, BLACK); + + DrawText(TextFormat("Type: %s", ViewportTypeNames[viewportType]), 15, 45, 10, BLACK); + Vector2 scaleRatio = (Vector2){destRect.width / sourceRect.width, destRect.height / -sourceRect.height}; + if (scaleRatio.x < 0.001f || scaleRatio.y < 0.001f) + { + DrawText(TextFormat("Scale ratio: INVALID"), 15, 60, 10, BLACK); + } + else + { + DrawText(TextFormat("Scale ratio: %.2f x %.2f", scaleRatio.x, scaleRatio.y), 15, 60, 10, BLACK); + } + DrawText(TextFormat("Source size: %.2f x %.2f", sourceRect.width, -sourceRect.height), 15, 75, 10, BLACK); + DrawText(TextFormat("Destination size: %.2f x %.2f", destRect.width, destRect.height), 15, 90, 10, BLACK); + + // Draw buttons + DrawRectangleRec(decreaseTypeButton, SKYBLUE); + DrawRectangleRec(increaseTypeButton, SKYBLUE); + DrawRectangleRec(decreaseResolutionButton, SKYBLUE); + DrawRectangleRec(increaseResolutionButton, SKYBLUE); + DrawText("<", decreaseTypeButton.x + 3, decreaseTypeButton.y + 1, 10, BLACK); + DrawText(">", increaseTypeButton.x + 3, increaseTypeButton.y + 1, 10, BLACK); + DrawText("<", decreaseResolutionButton.x + 3, decreaseResolutionButton.y + 1, 10, BLACK); + DrawText(">", increaseResolutionButton.x + 3, increaseResolutionButton.y + 1, 10, BLACK); + + EndDrawing(); + //----------------------------------------------------- + } + + // De-Initialization + //--------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //---------------------------------------------------------- + + return 0; +} + +//-------------------------------------------------------------------------------------- +// Module Functions Definition +//-------------------------------------------------------------------------------------- +static void KeepAspectCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect) +{ + sourceRect->x = 0.f; + sourceRect->y = (float)gameHeight; + sourceRect->width = (float)gameWidth; + sourceRect->height = (float)-gameHeight; + + const int ratio_x = (screenWidth/gameWidth); + const int ratio_y = (screenHeight/gameHeight); + const float resizeRatio = (float)(ratio_x < ratio_y ? ratio_x : ratio_y); + + destRect->x = (float)(int)((screenWidth - (gameWidth * resizeRatio)) * 0.5); + destRect->y = (float)(int)((screenHeight - (gameHeight * resizeRatio)) * 0.5); + destRect->width = (float)(int)(gameWidth * resizeRatio); + destRect->height = (float)(int)(gameHeight * resizeRatio); +} + +static void KeepHeightCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect) +{ + const float resizeRatio = (float)(screenHeight/gameHeight); + sourceRect->x = 0.f; + sourceRect->y = 0.f; + sourceRect->width = (float)(int)(screenWidth / resizeRatio); + sourceRect->height = (float)-gameHeight; + + destRect->x = (float)(int)((screenWidth - (sourceRect->width * resizeRatio)) * 0.5); + destRect->y = (float)(int)((screenHeight - (gameHeight * resizeRatio)) * 0.5); + destRect->width = (float)(int)(sourceRect->width * resizeRatio); + destRect->height = (float)(int)(gameHeight * resizeRatio); +} + +static void KeepWidthCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect) +{ + const float resizeRatio = (float)(screenWidth/gameWidth); + sourceRect->x = 0.f; + sourceRect->y = 0.f; + sourceRect->width = (float)gameWidth; + sourceRect->height = (float)(int)(screenHeight / resizeRatio); + + destRect->x = (float)(int)((screenWidth - (gameWidth * resizeRatio)) * 0.5); + destRect->y = (float)(int)((screenHeight - (sourceRect->height * resizeRatio)) * 0.5); + destRect->width = (float)(int)(gameWidth * resizeRatio); + destRect->height = (float)(int)(sourceRect->height * resizeRatio); + + sourceRect->height *= -1.f; +} + +static void KeepAspectCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect) +{ + sourceRect->x = 0.f; + sourceRect->y = (float)gameHeight; + sourceRect->width = (float)gameWidth; + sourceRect->height = (float)-gameHeight; + + const float ratio_x = ((float)screenWidth/(float)gameWidth); + const float ratio_y = ((float)screenHeight/(float)gameHeight); + const float resizeRatio = (ratio_x < ratio_y ? ratio_x : ratio_y); + + destRect->x = (float)(int)((screenWidth - (gameWidth * resizeRatio)) * 0.5); + destRect->y = (float)(int)((screenHeight - (gameHeight * resizeRatio)) * 0.5); + destRect->width = (float)(int)(gameWidth * resizeRatio); + destRect->height = (float)(int)(gameHeight * resizeRatio); +} + +static void KeepHeightCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect) +{ + const float resizeRatio = ((float)screenHeight/(float)gameHeight); + sourceRect->x = 0.f; + sourceRect->y = 0.f; + sourceRect->width = (float)(int)((float)screenWidth / resizeRatio); + sourceRect->height = (float)-gameHeight; + + destRect->x = (float)(int)((screenWidth - (sourceRect->width * resizeRatio)) * 0.5); + destRect->y = (float)(int)((screenHeight - (gameHeight * resizeRatio)) * 0.5); + destRect->width = (float)(int)(sourceRect->width * resizeRatio); + destRect->height = (float)(int)(gameHeight * resizeRatio); +} + +static void KeepWidthCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect) +{ + const float resizeRatio = ((float)screenWidth/(float)gameWidth); + sourceRect->x = 0.f; + sourceRect->y = 0.f; + sourceRect->width = (float)gameWidth; + sourceRect->height = (float)(int)((float)screenHeight / resizeRatio); + + destRect->x = (float)(int)((screenWidth - (gameWidth * resizeRatio)) * 0.5); + destRect->y = (float)(int)((screenHeight - (sourceRect->height * resizeRatio)) * 0.5); + destRect->width = (float)(int)(gameWidth * resizeRatio); + destRect->height = (float)(int)(sourceRect->height * resizeRatio); + + sourceRect->height *= -1.f; +} + +static void ResizeRenderSize(enum ViewportType viewportType, int *screenWidth, int *screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect, RenderTexture2D *target) +{ + *screenWidth = GetScreenWidth(); + *screenHeight = GetScreenHeight(); + + switch(viewportType) + { + case KEEP_ASPECT_INTEGER: + { + KeepAspectCenteredInteger(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); + break; + } + case KEEP_HEIGHT_INTEGER: + { + KeepHeightCenteredInteger(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); + break; + } + case KEEP_WIDTH_INTEGER: + { + KeepWidthCenteredInteger(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); + break; + } + case KEEP_ASPECT: + { + KeepAspectCentered(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); + break; + } + case KEEP_HEIGHT: + { + KeepHeightCentered(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); + break; + } + case KEEP_WIDTH: + { + KeepWidthCentered(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); + break; + } + default: {} + } + UnloadRenderTexture(*target); + *target = LoadRenderTexture(sourceRect->width, -sourceRect->height); +} + +// Example how to calculate position on RenderTexture +static Vector2 Screen2RenderTexturePosition(Vector2 point, Rectangle *textureRect, Rectangle *scaledRect) +{ + Vector2 relativePosition = {point.x - scaledRect->x, point.y - scaledRect->y}; + Vector2 ratio = {textureRect->width / scaledRect->width, -textureRect->height / scaledRect->height}; + + return (Vector2){relativePosition.x * ratio.x, relativePosition.y * ratio.x}; +} \ No newline at end of file diff --git a/examples/core/core_viewport_scaling.png b/examples/core/core_viewport_scaling.png new file mode 100644 index 0000000000000000000000000000000000000000..68fad6209adf6ec0967b0ddb737a749b512c880c GIT binary patch literal 8699 zcmeHNYgCh0)(+B+tyQU`BVG`N3TT5+2^Rr@Dkz{_rl^El2q*+G1e9iNDYuBuP{lPlxykxl7J)#ZCGKu6J|K|x1`tT=&PNMWzg*I?Bd7)~w2$jf5b3_& z3)SR|wY3 zjSqf2TxYj#cSdgL0b5(&v)Lg!c}K52c%_xIVR_gW(t=c<$6O0}^5itTd2-JLL zPB%T{+HS2IuRj1SEd7@;l*>?lX6S6}4BF<}x+!W#ik&luk6~9`)LvP%<ZAfap z$Dvch=^sXPt~!ZY>c`u{y7XjQ$PaxMyF>BX?8Ht>#I~h=M=58&dL&4f3|gF9ef~FJ z3f1vrNtzliK}VD0yGxEFLqF3!-#9rWc$kar=r0CK14RV5GNl!c2TR|$6K59HC`vy7 zABa=luM@zsQrpHlFa+77R{2!{M@k$J$;+7~vf262@bZ;Y4sp!^bkZQhBDJ^*-i^x} z*@BdkJ>VR3E_R-dl8Qa#w*d27ImowRH<=ylQq#aY!N8FKk_WPwSKWQ>-7V%Ng3f z;)52nU~ZtSZ^pGYcH|+^Y@%wvw5gJ`A0tG@cPj`ClX>jR=qFQY`qjpr!Uj}T+J4~W zp;`-jIY}biHAtz5z8=#gGdTAX4+rf+4v6KG@f0F)kXpkgcJbz-W`z$rm`rg!hFeCi zl?dwyG_2|wCAb305maIw4~C;h(&k;vzkd>?O2cCF-z)pWN0zc`~C;5 zSN%9@_0{U}Q7tgWH};9GjF=a+ZK4B%-|IcM87b~aiL6OW%_4M9BZCXgJ=tXV2G(>W z)1boL8fFQ_hb`|u*8EMaV*j==YJ77?<$7kl&P+=dZE}bwk2H~7WrJOn-}4l=Ov2YV zRoQN|YA?$a9LbG(YOCgU1u2x5t<8=~<2}8fSBD6nn~E5Q|0_921Q3UJBE3lZiTh?C-;ys zY-j;T+EhwECLWI$Uo%tDsuIHW($WHy`O~Nh(c);eFY-JQPpUKiQNlq_jI^;xWc}?u4=# zlVDAP7jMCo=?WD>POgx5Uq85r&a_MmK)c}_lYS2RzHfh^^Xx;IJg+I=8_2$(-qZb88sG4(WJAlq;-9c0QdwSVQbKUn#6a7Pfi^5-yZw!Las5 zcr$iEbhKcr`uGt3deR|6znU%Bdrl&sOh$_pnFngeJ6s2q5=Ru$(_4$Yhe7%cI{^<~L_y>s@t)3qO0udLdEt>q{Gx zn>$MKf^Lt@728;?kAEKF*Ez~g4u*AcCoW2Z!X)*|i{h|(OZ|Ej)x9y&yF-U@Q=XR< zfOSiyB}%SyW;ZjyZ>}QajMRHNe&U&x7_bw@#;ht_@>Jf@Z9myA?l|brtEC6PMX+KR zqk&S7PD@ojYw{@r9QE{EI&)rWaz`dCL3AghQ-?2S;d)zy>n*rk?6dM-6n@IFoURbK z(mZ~;U^Z)@e7uuWN6?WYgZmn9QK&WQs}&J{v#&ZbMyViB>gg1ztyoB_6y?psw=efD z9j*%FK;J$a@;do-f7rkP`}$0TgoY>Vt4Hm?TRfLdGGs>-3$G7`+cuSsn{ zeOX3FH%1TDrogyC{(M5>BD&m-;11o{T!RC6CBeKuNF9 zNFA%_Lyhvr`EXE25RVdP)2$dWTh+RyQ?0U){7(HOq>+v8*Tjwm=? zy%I$thD!`9b=9}}!=~WQQKu@7u!L4A779hhgSElZF<3DijupkU&#TXh!cbnIj$cyz zUU4CFtw_d~bSR+DI2@jR1$QNf*O(Zema3pJjNXtIaVk|uVRENVRWwELLh~)wPvR$gHQo(9L977^93yfuRS4d%|kVWdc z(Z3ux%~=92c!Jk=eEQrof2EsPi19^8MNRh{2)Y?tYsdn_#R1mia|O^BSOldp$}N zrq@yGFF|#g9kb&a!E6QQ)O*4sgq&Ie49f(o)Hrr1<-j0>Jlixw77CH9j{fA*U>Fx8 zdL2J5mj|?3Ts^8P#ib6R++OXKci;3R@3TdEhsI1fmvwq^;bN`FdaJL34kRx~|JLWf zVsso}%*`+y4Q0nx?>w^N`#s*x8OZxv2aTP~oZZeX*#F|=>7z{BGA{pK_(v6ArGM_A zwVq^qFAhI@d$EDlqQ94#Jicr7>{4{@`T_%>{Y|+o&|{${hb>O@Z2B^XT1Et>u#@c!@-V?x^}Irx}3VE@~=c9Y3(_=2@CjXz(I5R+$`Hc(}| z0j$qf7N-0s%TFNM&cx*nlxe$bygBut3zALB;l^zMtbK}0GJbd<7Qb|(28~raA>A$w zd!kui15i0178JTc+fjBGG+=~$#N598#phJdg*lp77GExmFoYS^zxWcs!oX7OajlYX zz0M8r{RE=q{TWZeA>z)Y9FBFymI0Y!S?=4zcg|fahXBYD$_jl9*W(5r5H))SL$pe0gNq#kLG%o7WLQsvJEbr7n(^)XPr$04_x0HXBV|ZeOA(1mZhyrR^Rz z&1Z{(HP0X1L`eHjXLh$K;({=%Xhit^9^1Azi^ob4MO z08Yyvc&u3FpObOT1b}@#?u?<~-pLEE0Zu=6hJ57`=B}Sp0&qI@u<>_)5aYq5)%u2$ zUruhL0m!_JWfpy!y)ftA3r_&K0;J%?>FpP`d&L0AHU&XUT=-w}k0k@hKOVu^8W`$q z%wpjIaLhSfA1_PVkW@4TfOnjV@H*Y5-9G%<7&tSXR_L8B`fmE=HURj+q{8KYy6`6o zY8CY-3ThPO4;JhE|13wpZ6S^$u7>)$D8G`Ia|jw`gu1XVO7d& z&s&w3^WohE|E;p|9X0E1oWXA?&;O0OhnK0Eg|{>lDTRpL-)%1%@PW@uTnpyecj;yp z6gB)GdZ=ctz0pQCi*z*#L2IesueEE<%a{XszhB?zjcldFU7EcyZ3H;xjYLr2=Ul|r z`!Eb5>%5`^*b}jfHonif>LbPJclb#01c&yhZ_n!dR3jA^I0m85zp(z*FC?I)K#%n2 k_#5B=0x?8zrCjMe;LAU|bTd))4;vu&U0yDvogv@+1N0Y87XSbN literal 0 HcmV?d00001 diff --git a/examples/examples_list.txt b/examples/examples_list.txt index 7c8709785..f22a0892f 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -46,6 +46,7 @@ core;core_automation_events;★★★☆;5.0;5.0;2023;2025;"Ramon Santamaria";@r core;core_high_dpi;★★☆☆;5.0;5.5;2025;2025;"Jonathan Marler";@marler8997 core;core_render_texture;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 core;core_undo_redo;★★★☆;5.5;5.6;2025;2025;"Ramon Santamaria";@raysan5 +core;core_viewport_scaling;★★☆☆;5.5;5.5;2025;2025;"Agnis Aldins";@nezvers core;core_input_actions;★★☆☆;5.5;5.6;2025;2025;"Jett";@JettMonstersGoBoom core;core_directory_files;★☆☆☆;5.5;5.6;2025;2025;"Hugo ARNAL";@hugoarnal core;core_highdpi_testbed;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 diff --git a/projects/VS2022/examples/core_viewport_scaling.vcxproj b/projects/VS2022/examples/core_viewport_scaling.vcxproj new file mode 100644 index 000000000..712cfe2a4 --- /dev/null +++ b/projects/VS2022/examples/core_viewport_scaling.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91} + Win32Proj + core_viewport_scaling + 10.0 + core_viewport_scaling + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index c15632182..8ace665a9 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -405,6 +405,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "models_decals", "examples\m EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_lines_drawing", "examples\shapes_lines_drawing.vcxproj", "{666346D7-C84B-498D-AE17-53B20C62DB1A}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_viewport_scaling", "examples\core_viewport_scaling.vcxproj", "{AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug.DLL|ARM64 = Debug.DLL|ARM64 @@ -5027,6 +5029,30 @@ Global {666346D7-C84B-498D-AE17-53B20C62DB1A}.Release|x64.Build.0 = Release|x64 {666346D7-C84B-498D-AE17-53B20C62DB1A}.Release|x86.ActiveCfg = Release|Win32 {666346D7-C84B-498D-AE17-53B20C62DB1A}.Release|x86.Build.0 = Release|Win32 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Debug|ARM64.Build.0 = Debug|ARM64 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Debug|x64.ActiveCfg = Debug|x64 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Debug|x64.Build.0 = Debug|x64 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Debug|x86.ActiveCfg = Debug|Win32 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Debug|x86.Build.0 = Debug|Win32 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Release|ARM64.ActiveCfg = Release|ARM64 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Release|ARM64.Build.0 = Release|ARM64 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Release|x64.ActiveCfg = Release|x64 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Release|x64.Build.0 = Release|x64 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Release|x86.ActiveCfg = Release|Win32 + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5231,6 +5257,7 @@ Global {B7FDD40F-DDA4-468E-9C40-EEB175964A26} = {278D8859-20B1-428F-8448-064F46E1F021} {028F0967-B253-45DA-B1C4-FACCE45D0D8D} = {AF5BEC5C-1F2B-4DA8-B12D-D09FE569237C} {666346D7-C84B-498D-AE17-53B20C62DB1A} = {278D8859-20B1-428F-8448-064F46E1F021} + {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} From cc83b2bd8e272c889ad2f5e6ee88f075be5a6642 Mon Sep 17 00:00:00 2001 From: Tiago Ferreira Date: Sun, 2 Nov 2025 18:46:57 +0000 Subject: [PATCH 036/430] fix: cursor lock/unlock inconsistent behaviour on glfw, rgfw, sl (#5323) --- src/platforms/rcore_desktop_glfw.c | 2 ++ src/platforms/rcore_desktop_rgfw.c | 7 +++++-- src/platforms/rcore_desktop_sdl.c | 11 ++++------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index dbe2062a0..c67b91845 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1070,6 +1070,7 @@ void EnableCursor(void) if (glfwRawMouseMotionSupported()) glfwSetInputMode(platform.handle, GLFW_RAW_MOUSE_MOTION, GLFW_FALSE); + CORE.Input.Mouse.cursorHidden = false; CORE.Input.Mouse.cursorLocked = false; } @@ -1083,6 +1084,7 @@ void DisableCursor(void) if (glfwRawMouseMotionSupported()) glfwSetInputMode(platform.handle, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE); + CORE.Input.Mouse.cursorHidden = true; CORE.Input.Mouse.cursorLocked = true; } diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 177c88fc7..47160af54 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -838,8 +838,9 @@ void EnableCursor(void) // Set cursor position in the middle SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); - RGFW_window_showMouse(platform.window, true); - CORE.Input.Mouse.cursorHidden = false; + ShowCursor(); + + CORE.Input.Mouse.cursorLocked = true; } // Disables cursor (lock cursor) @@ -848,6 +849,8 @@ void DisableCursor(void) RGFW_disableCursor = true; RGFW_window_mouseHold(platform.window, RGFW_AREA(0, 0)); HideCursor(); + + CORE.Input.Mouse.cursorLocked = true; } // Swap back buffer with front buffer (screen drawing) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 841fc4479..99ef6338c 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -1237,13 +1237,7 @@ void EnableCursor(void) { SDL_SetRelativeMouseMode(SDL_FALSE); -#if defined(USING_VERSION_SDL3) - // NOTE: SDL_ShowCursor() has been split into three functions: - // SDL_ShowCursor(), SDL_HideCursor(), and SDL_CursorVisible() - SDL_ShowCursor(); -#else - SDL_ShowCursor(SDL_ENABLE); -#endif + ShowCursor(); CORE.Input.Mouse.cursorLocked = false; } @@ -1253,6 +1247,9 @@ void DisableCursor(void) { SDL_SetRelativeMouseMode(SDL_TRUE); + HideCursor(); + + platform.cursorRelative = true; CORE.Input.Mouse.cursorLocked = true; } From 81004135a46b68e17a401d8ff203d5cc52a5a397 Mon Sep 17 00:00:00 2001 From: EDBC_REPO <109326461+EDBCREPO@users.noreply.github.com> Date: Sun, 2 Nov 2025 14:48:11 -0400 Subject: [PATCH 037/430] adding Matrix MatrixCompose( translate, rotation, scale ) to raymath.h (#5324) --- src/raymath.h | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/raymath.h b/src/raymath.h index 65a20de59..9a9aa8be2 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -2552,6 +2552,38 @@ RMAPI int QuaternionEquals(Quaternion p, Quaternion q) return result; } +// Compose a transformation matrix from rotational, translational and scaling components +RMAPI Matrix MatrixCompose( Vector3 translation, Quaternion rotation, Vector3 scale ) +{ + + //Initialize Vectors + Vector3 right = { 1, 0, 0 }; + Vector3 up = { 0, 1, 0 }; + Vector3 forward = { 0, 0, 1 }; + + //Scale Vectors + right = Vector3Scale( right , scale.x ); + up = Vector3Scale( up , scale.y ); + forward = Vector3Scale( forward , scale.z ); + + //Rotate Vectors + right = Vector3RotateByQuaternion( right , rotation ); + up = Vector3RotateByQuaternion( up , rotation ); + forward = Vector3RotateByQuaternion( forward, rotation ); + + // Set matrix output + Matrix result = { + right.x, up.x, forward.x, position.x, + right.y, up.y, forward.y, position.y, + right.z, up.z, forward.z, position.z, + 0, 0, 0, 1 + }; + + // Return matrix output + return result; + +} + // Decompose a transformation matrix into its rotational, translational and scaling components and remove shear RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotation, Vector3 *scale) { From 87d49262f828c957059b766bc7aba9a2124310ee Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 2 Nov 2025 19:53:45 +0100 Subject: [PATCH 038/430] REVIEWED: raymath: `MatrixCompose()` --- src/raymath.h | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/src/raymath.h b/src/raymath.h index 9a9aa8be2..1e1565426 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -2553,38 +2553,37 @@ RMAPI int QuaternionEquals(Quaternion p, Quaternion q) } // Compose a transformation matrix from rotational, translational and scaling components -RMAPI Matrix MatrixCompose( Vector3 translation, Quaternion rotation, Vector3 scale ) +// TODO: This function is not following raymath conventions defined in header: NOT self-contained +RMAPI Matrix MatrixCompose(Vector3 translation, Quaternion rotation, Vector3 scale) { + // Initialize vectors + Vector3 right = { 1.0f, 0.0f, 0.0f }; + Vector3 up = { 0.0f, 1.0f, 0.0f }; + Vector3 forward = { 0.0f, 0.0f, 1.0f }; - //Initialize Vectors - Vector3 right = { 1, 0, 0 }; - Vector3 up = { 0, 1, 0 }; - Vector3 forward = { 0, 0, 1 }; + // Scale vectors + right = Vector3Scale(right, scale.x); + up = Vector3Scale(up, scale.y); + forward = Vector3Scale(forward , scale.z); - //Scale Vectors - right = Vector3Scale( right , scale.x ); - up = Vector3Scale( up , scale.y ); - forward = Vector3Scale( forward , scale.z ); - - //Rotate Vectors - right = Vector3RotateByQuaternion( right , rotation ); - up = Vector3RotateByQuaternion( up , rotation ); - forward = Vector3RotateByQuaternion( forward, rotation ); + // Rotate vectors + right = Vector3RotateByQuaternion(right, rotation); + up = Vector3RotateByQuaternion(up, rotation); + forward = Vector3RotateByQuaternion(forward, rotation); - // Set matrix output + // Set result matrix output Matrix result = { right.x, up.x, forward.x, position.x, right.y, up.y, forward.y, position.y, right.z, up.z, forward.z, position.z, - 0, 0, 0, 1 + 0.0f, 0.0f, 0.0f, 1.0f }; - // Return matrix output return result; - } // Decompose a transformation matrix into its rotational, translational and scaling components and remove shear +// TODO: This function is not following raymath conventions defined in header: NOT self-contained RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotation, Vector3 *scale) { float eps = (float)1e-9; @@ -2619,10 +2618,7 @@ RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotatio // X Scale scl.x = Vector3Length(matColumns[0]); - if (scl.x > eps) - { - matColumns[0] = Vector3Scale(matColumns[0], 1.0f / scl.x); - } + if (scl.x > eps) matColumns[0] = Vector3Scale(matColumns[0], 1.0f / scl.x); // Compute XY shear and make col2 orthogonal shear[0] = Vector3DotProduct(matColumns[0], matColumns[1]); From ee3be5799a1858be98d6afe7e0aedfcea975eaa3 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 2 Nov 2025 19:59:46 +0100 Subject: [PATCH 039/430] Update raymath.h --- src/raymath.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/raymath.h b/src/raymath.h index 1e1565426..32dfd2b0a 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -2573,9 +2573,9 @@ RMAPI Matrix MatrixCompose(Vector3 translation, Quaternion rotation, Vector3 sca // Set result matrix output Matrix result = { - right.x, up.x, forward.x, position.x, - right.y, up.y, forward.y, position.y, - right.z, up.z, forward.z, position.z, + right.x, up.x, forward.x, translation.x, + right.y, up.y, forward.y, translation.y, + right.z, up.z, forward.z, translation.z, 0.0f, 0.0f, 0.0f, 1.0f }; From 91addeb889d10d0df12e09bd2780802bf2417622 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 2 Nov 2025 20:04:43 +0100 Subject: [PATCH 040/430] Update rexm.c --- tools/rexm/rexm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index f45ce9be3..4e417e987 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -50,12 +50,12 @@ #include "raylib.h" -#include +#include // Required for: NULL, calloc(), free() #include // Required for: rename(), remove() #include // Required for: strcmp(), strcpy() #define SUPPORT_LOG_INFO -#if defined(SUPPORT_LOG_INFO) && defined(_DEBUG) +#if defined(SUPPORT_LOG_INFO) //&& defined(_DEBUG) #define LOG(...) printf("REXM: "__VA_ARGS__) #else #define LOG(...) From ed68a4fccef55fe7feaa12d3dd27347aa9969bd3 Mon Sep 17 00:00:00 2001 From: komunre <49118681+komunre@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:43:20 +0700 Subject: [PATCH 041/430] Fixed bugs in SDL backend (#5325) --- src/platforms/rcore_desktop_sdl.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 99ef6338c..b1ca43a60 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -588,7 +588,7 @@ void SetWindowState(unsigned int flags) { SDL_SetWindowAlwaysOnTop(platform.window, SDL_FALSE); } - if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN) + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) { FLAG_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); } @@ -1249,7 +1249,9 @@ void DisableCursor(void) HideCursor(); - platform.cursorRelative = true; + // ???? + //platform.cursorRelative = true; + CORE.Input.Mouse.cursorHidden = true; CORE.Input.Mouse.cursorLocked = true; } From cfb43fa9991f6e44be178f61f407749cc613250d Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 3 Nov 2025 09:45:18 +0100 Subject: [PATCH 042/430] Update rcore_desktop_sdl.c --- src/platforms/rcore_desktop_sdl.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index b1ca43a60..f7ee57ee4 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -1238,7 +1238,6 @@ void EnableCursor(void) SDL_SetRelativeMouseMode(SDL_FALSE); ShowCursor(); - CORE.Input.Mouse.cursorLocked = false; } @@ -1248,10 +1247,6 @@ void DisableCursor(void) SDL_SetRelativeMouseMode(SDL_TRUE); HideCursor(); - - // ???? - //platform.cursorRelative = true; - CORE.Input.Mouse.cursorHidden = true; CORE.Input.Mouse.cursorLocked = true; } From e92832fc6d7046946d1bfb0623a3141a351b864c Mon Sep 17 00:00:00 2001 From: NoNameAuthenticated Date: Tue, 4 Nov 2025 11:50:54 -0500 Subject: [PATCH 043/430] Update rcore_desktop_sdl.c (#5332) --- src/platforms/rcore_desktop_sdl.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index f7ee57ee4..cf11037cb 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -463,7 +463,7 @@ void ToggleFullscreen(void) const int monitorCount = SDL_GetNumVideoDisplays(); #if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure - if ((monitor > 0) && (monitor <= monitorCount)) + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid #else if ((monitor >= 0) && (monitor < monitorCount)) #endif @@ -491,7 +491,7 @@ void ToggleBorderlessWindowed(void) const int monitorCount = SDL_GetNumVideoDisplays(); #if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure - if ((monitor > 0) && (monitor <= monitorCount)) + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid #else if ((monitor >= 0) && (monitor < monitorCount)) #endif @@ -548,7 +548,7 @@ void SetWindowState(unsigned int flags) const int monitorCount = SDL_GetNumVideoDisplays(); #if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure - if ((monitor > 0) && (monitor <= monitorCount)) + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid #else if ((monitor >= 0) && (monitor < monitorCount)) #endif @@ -612,7 +612,7 @@ void SetWindowState(unsigned int flags) const int monitorCount = SDL_GetNumVideoDisplays(); #if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure - if ((monitor > 0) && (monitor <= monitorCount)) + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid #else if ((monitor >= 0) && (monitor < monitorCount)) #endif @@ -833,7 +833,7 @@ void SetWindowMonitor(int monitor) { const int monitorCount = SDL_GetNumVideoDisplays(); #if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure - if ((monitor > 0) && (monitor <= monitorCount)) + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid #else if ((monitor >= 0) && (monitor < monitorCount)) #endif @@ -961,7 +961,7 @@ Vector2 GetMonitorPosition(int monitor) { const int monitorCount = SDL_GetNumVideoDisplays(); #if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure - if ((monitor > 0) && (monitor <= monitorCount)) + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid #else if ((monitor >= 0) && (monitor < monitorCount)) #endif @@ -989,7 +989,7 @@ int GetMonitorWidth(int monitor) const int monitorCount = SDL_GetNumVideoDisplays(); #if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure - if ((monitor > 0) && (monitor <= monitorCount)) + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid #else if ((monitor >= 0) && (monitor < monitorCount)) #endif @@ -1010,7 +1010,7 @@ int GetMonitorHeight(int monitor) const int monitorCount = SDL_GetNumVideoDisplays(); #if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure - if ((monitor > 0) && (monitor <= monitorCount)) + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid #else if ((monitor >= 0) && (monitor < monitorCount)) #endif @@ -1031,7 +1031,7 @@ int GetMonitorPhysicalWidth(int monitor) const int monitorCount = SDL_GetNumVideoDisplays(); #if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure - if ((monitor > 0) && (monitor <= monitorCount)) + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid #else if ((monitor >= 0) && (monitor < monitorCount)) #endif @@ -1055,7 +1055,7 @@ int GetMonitorPhysicalHeight(int monitor) const int monitorCount = SDL_GetNumVideoDisplays(); #if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure - if ((monitor > 0) && (monitor <= monitorCount)) + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid #else if ((monitor >= 0) && (monitor < monitorCount)) #endif @@ -1079,7 +1079,7 @@ int GetMonitorRefreshRate(int monitor) const int monitorCount = SDL_GetNumVideoDisplays(); #if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure - if ((monitor > 0) && (monitor <= monitorCount)) + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid #else if ((monitor >= 0) && (monitor < monitorCount)) #endif @@ -1099,7 +1099,7 @@ const char *GetMonitorName(int monitor) const int monitorCount = SDL_GetNumVideoDisplays(); #if defined(USING_VERSION_SDL3) // SDL3 Migration: Monitor is an id instead of index now, returns 0 on failure - if ((monitor > 0) && (monitor <= monitorCount)) + if (SDL_GetDisplayProperties(monitor) != 0) // Returns 0 on failure, so a value other than zero indicates that the monitor id is valid #else if ((monitor >= 0) && (monitor < monitorCount)) #endif From 48c1619d208c07a01ff913d9c5ab86403ab2db27 Mon Sep 17 00:00:00 2001 From: iann Date: Tue, 4 Nov 2025 10:52:53 -0600 Subject: [PATCH 044/430] added consistent behavior for texture in opengl11 draw states and fixed loadobj texcoord behavior for opengl11 context (#5328) --- src/rmodels.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index ed86fb19a..c09a94652 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -1435,7 +1435,7 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) #define GL_COLOR_ARRAY 0x8076 #define GL_TEXTURE_COORD_ARRAY 0x8078 - rlEnableTexture(material.maps[MATERIAL_MAP_DIFFUSE].texture.id); + if (mesh.texcoords && material.maps[MATERIAL_MAP_DIFFUSE].texture.id > 0) rlEnableTexture(material.maps[MATERIAL_MAP_DIFFUSE].texture.id); if (mesh.animVertices) rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.animVertices); else rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.vertices); @@ -4429,10 +4429,12 @@ static Model LoadOBJ(const char *fileName) model.meshes[i].vertices = (float *)MemAlloc(sizeof(float)*vertexCount*3); model.meshes[i].normals = (float *)MemAlloc(sizeof(float)*vertexCount*3); - model.meshes[i].texcoords = (float *)MemAlloc(sizeof(float)*vertexCount*2); #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + model.meshes[i].texcoords = (float *)MemAlloc(sizeof(float)*vertexCount*2); model.meshes[i].colors = (unsigned char *)MemAlloc(sizeof(unsigned char)*vertexCount*4); #else + if (objAttributes.texcoords != NULL && objAttributes.num_texcoords > 0) model.meshes[i].texcoords = (float *)MemAlloc(sizeof(float)*vertexCount*2); + else model.meshes[i].texcoords = NULL; model.meshes[i].colors = NULL; #endif } @@ -4488,16 +4490,11 @@ static Model LoadOBJ(const char *fileName) for (int i = 0; i < 3; i++) model.meshes[meshIndex].vertices[localMeshVertexCount*3 + i] = objAttributes.vertices[vertIndex*3 + i]; - if ((objAttributes.texcoords != NULL) && (texcordIndex != TINYOBJ_INVALID_INDEX) && (texcordIndex >= 0)) + if ((objAttributes.texcoords != NULL) && (texcordIndex != TINYOBJ_INVALID_INDEX) && (texcordIndex >= 0) && (model.meshes[meshIndex].texcoords)) { for (int i = 0; i < 2; i++) model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + i] = objAttributes.texcoords[texcordIndex*2 + i]; model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1] = 1.0f - model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1]; } - else - { - model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 0] = 0.0f; - model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1] = 0.0f; - } if ((objAttributes.normals != NULL) && (normalIndex != TINYOBJ_INVALID_INDEX) && (normalIndex >= 0)) { From 12ce106661bc9c8f74ea3f3069291e5fc4e7d50a Mon Sep 17 00:00:00 2001 From: JohnnyCena123 Date: Tue, 4 Nov 2025 19:17:37 +0200 Subject: [PATCH 045/430] [rcore][glfw] fix `IsWindowFocused()` inverted logic (#5333) --- src/platforms/rcore_desktop_glfw.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index c67b91845..bf383031b 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1863,8 +1863,8 @@ static void WindowMaximizeCallback(GLFWwindow *window, int maximized) // GLFW3 WindowFocus Callback, runs when window get/lose focus static void WindowFocusCallback(GLFWwindow *window, int focused) { - if (focused) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window was focused - else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window lost focus + if (focused) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window was focused + else FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window lost focus } // GLFW3 Window Drop Callback, runs when drop files into window From 3843f771fbac00e1c028ce2ec552b36ba34c1de5 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 5 Nov 2025 20:51:18 +0100 Subject: [PATCH 046/430] Update core_clipboard_text.c --- examples/core/core_clipboard_text.c | 30 ++++++++--------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/examples/core/core_clipboard_text.c b/examples/core/core_clipboard_text.c index 2be64154b..c9d22c54a 100644 --- a/examples/core/core_clipboard_text.c +++ b/examples/core/core_clipboard_text.c @@ -31,14 +31,14 @@ int main(void) InitWindow(screenWidth, screenHeight, "raylib [core] example - clipboard text"); - const char* clipboardText = NULL; + const char *clipboardText = NULL; // List of text the user can switch through and copy - const char* copyableText[] = {"raylib is fun", "hello, clipboard!", "potato chips"}; + const char *copyableText[] = { "raylib is fun", "hello, clipboard!", "potato chips" }; unsigned int textIndex = 0; - const char* popupText = NULL; + const char *popupText = NULL; // Initialize timers // The amount of time the pop-up text is on screen, before fading @@ -53,6 +53,8 @@ int main(void) float textAlpha = 0.0f; // Offset amount for animations const int offsetAmount = -4; + + SetTargetFPS(60); //-------------------------------------------------------------------------------------- // Main game loop @@ -70,7 +72,6 @@ int main(void) if (copyAnim > 0) copyAnim -= GetFrameTime(); if (textAnim > 0) textAnim -= GetFrameTime(); - // React to the user pressing paste if (pastePressed) { // Most operating systems hide this information until the user presses Ctrl-V on the window. @@ -81,17 +82,13 @@ int main(void) if (IsImageValid(image)) { - // Unload the image UnloadImage(image); - // Update visuals popupText = "clipboard contains image"; } else { - // Get text from the user's clipboard clipboardText = GetClipboardText(); - // Update visuals popupText = "text pasted"; pasteAnim = animMaxTime; } @@ -114,7 +111,6 @@ int main(void) copyAnim = animMaxTime; copyAnimMult = 1; textAlpha = 1; - // Update the text that pops up at the bottom of the screen popupText = "text copied"; } @@ -141,15 +137,8 @@ int main(void) copyAnim = animMaxTime; copyAnimMult = -1; - if (textIndex == 0) - { - // Loop back to the other end - textIndex = (sizeof(copyableText) / sizeof(const char*)) - 1; // Length of array minus one - } - else - { - textIndex -= 1; - } + if (textIndex == 0) textIndex = (sizeof(copyableText)/sizeof(const char*)) - 1; + else textIndex -= 1; } //---------------------------------------------------------------------------------- @@ -189,10 +178,7 @@ int main(void) DrawText(popupText, 10, 425 + offset, 20, ColorAlpha(DARKGREEN, textAlpha)); // Fade-out animation - if (textTimer < 0) - { - textAlpha -= GetFrameTime(); - } + if (textTimer < 0) textAlpha -= GetFrameTime(); } EndDrawing(); From 9ff87b38b830324a5d085cc02eb0127320d7f06e Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 5 Nov 2025 20:51:23 +0100 Subject: [PATCH 047/430] Update raygui.h --- examples/core/raygui.h | 870 ++++++++++++++++++++++++++--------------- 1 file changed, 556 insertions(+), 314 deletions(-) diff --git a/examples/core/raygui.h b/examples/core/raygui.h index a3fc51f0f..17ced6ef5 100644 --- a/examples/core/raygui.h +++ b/examples/core/raygui.h @@ -4,7 +4,7 @@ * * DESCRIPTION: * raygui is a tools-dev-focused immediate-mode-gui library based on raylib but also -* available as a standalone library, as long as input and drawing functions are provided. +* available as a standalone library, as long as input and drawing functions are provided * * FEATURES: * - Immediate-mode gui, minimal retained data @@ -27,7 +27,7 @@ * - WARNING: GuiLoadStyle() and GuiLoadStyle{Custom}() functions, allocate memory for * font atlas recs and glyphs, freeing that memory is (usually) up to the user, * no unload function is explicitly provided... but note that GuiLoadStyleDefault() unloads -* by default any previously loaded font (texture, recs, glyphs). +* by default any previously loaded font (texture, recs, glyphs) * - Global UI alpha (guiAlpha) is applied inside GuiDrawRectangle() and GuiDrawText() functions * * CONTROLS PROVIDED: @@ -65,7 +65,7 @@ * - MessageBox --> Window, Label, Button * - TextInputBox --> Window, Label, TextBox, Button * -* It also provides a set of functions for styling the controls based on its properties (size, color). +* It also provides a set of functions for styling the controls based on its properties (size, color) * * * RAYGUI STYLE (guiStyle): @@ -81,7 +81,7 @@ * * Note that the first set of BASE properties (by default guiStyle[0..15]) belong to the generic style * used for all controls, when any of those base values is set, it is automatically populated to all -* controls, so, specific control values overwriting generic style should be set after base values. +* controls, so, specific control values overwriting generic style should be set after base values * * After the first BASE set we have the EXTENDED properties (by default guiStyle[16..23]), those * properties are actually common to all controls and can not be overwritten individually (like BASE ones) @@ -100,7 +100,7 @@ * Every icon is codified in binary form, using 1 bit per pixel, so, every 16x16 icon * requires 8 integers (16*16/32) to be stored in memory. * -* When the icon is draw, actually one quad per pixel is drawn if the bit for that pixel is set. +* When the icon is draw, actually one quad per pixel is drawn if the bit for that pixel is set * * The global icons array size is fixed and depends on the number of icons and size: * @@ -112,20 +112,20 @@ * * RAYGUI LAYOUT: * raygui currently does not provide an auto-layout mechanism like other libraries, -* layouts must be defined manually on controls drawing, providing the right bounds Rectangle for it. +* layouts must be defined manually on controls drawing, providing the right bounds Rectangle for it * * TOOL: rGuiLayout is a visual tool to create raygui layouts: github.com/raysan5/rguilayout * * CONFIGURATION: * #define RAYGUI_IMPLEMENTATION -* Generates the implementation of the library into the included file. +* Generates the implementation of the library into the included file * If not defined, the library is in header only mode and can be included in other headers -* or source files without problems. But only ONE file should hold the implementation. +* or source files without problems. But only ONE file should hold the implementation * * #define RAYGUI_STANDALONE * Avoid raylib.h header inclusion in this file. Data types defined on raylib are defined * internally in the library and input management and drawing functions must be provided by -* the user (check library implementation for further details). +* the user (check library implementation for further details) * * #define RAYGUI_NO_ICONS * Avoid including embedded ricons data (256 icons, 16x16 pixels, 1-bit per pixel, 2KB) @@ -141,12 +141,17 @@ * Draw text bounds rectangles for debug * * VERSIONS HISTORY: -* 4.5-dev (Sep-2024) Current dev version... +* 5.0-dev (2025) Current dev version... * ADDED: guiControlExclusiveMode and guiControlExclusiveRec for exclusive modes * ADDED: GuiValueBoxFloat() * ADDED: GuiDropdonwBox() properties: DROPDOWN_ARROW_HIDDEN, DROPDOWN_ROLL_UP * ADDED: GuiListView() property: LIST_ITEMS_BORDER_WIDTH +* ADDED: GuiLoadIconsFromMemory() * ADDED: Multiple new icons +* REMOVED: GuiSpinner() from controls list, using BUTTON + VALUEBOX properties +* REMOVED: GuiSliderPro(), functionality was redundant +* REVIEWED: Controls using text labels to use LABEL properties +* REVIEWED: Replaced sprintf() by snprintf() for more safety * REVIEWED: GuiTabBar(), close tab with mouse middle button * REVIEWED: GuiScrollPanel(), scroll speed proportional to content * REVIEWED: GuiDropdownBox(), support roll up and hidden arrow @@ -156,6 +161,8 @@ * REVIEWED: GuiIconText(), increase buffer size and reviewed padding * REVIEWED: GuiDrawText(), improved wrap mode drawing * REVIEWED: GuiScrollBar(), minor tweaks +* REVIEWED: GuiProgressBar(), improved borders computing +* REVIEWED: GuiTextBox(), multiple improvements: autocursor and more * REVIEWED: Functions descriptions, removed wrong return value reference * REDESIGNED: GuiColorPanel(), improved HSV <-> RGBA convertion * @@ -259,16 +266,16 @@ * 1.4 (15-Jun-2017) Rewritten all GUI functions (removed useless ones) * 1.3 (12-Jun-2017) Complete redesign of style system * 1.1 (01-Jun-2017) Complete review of the library -* 1.0 (07-Jun-2016) Converted to header-only by Ramon Santamaria. -* 0.9 (07-Mar-2016) Reviewed and tested by Albert Martos, Ian Eito, Sergio Martinez and Ramon Santamaria. -* 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria. +* 1.0 (07-Jun-2016) Converted to header-only by Ramon Santamaria +* 0.9 (07-Mar-2016) Reviewed and tested by Albert Martos, Ian Eito, Sergio Martinez and Ramon Santamaria +* 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria * * DEPENDENCIES: * raylib 5.0 - Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing * * STANDALONE MODE: * By default raygui depends on raylib mostly for the inputs and the drawing functionality but that dependency can be disabled -* with the config flag RAYGUI_STANDALONE. In that case is up to the user to provide another backend to cover library needs. +* with the config flag RAYGUI_STANDALONE. In that case is up to the user to provide another backend to cover library needs * * The following functions should be redefined for a custom backend: * @@ -309,7 +316,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2024 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2025 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -334,7 +341,7 @@ #define RAYGUI_VERSION_MAJOR 4 #define RAYGUI_VERSION_MINOR 5 #define RAYGUI_VERSION_PATCH 0 -#define RAYGUI_VERSION "4.5-dev" +#define RAYGUI_VERSION "5.0-dev" #if !defined(RAYGUI_STANDALONE) #include "raylib.h" @@ -358,17 +365,6 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -// Allow custom memory allocators -#ifndef RAYGUI_MALLOC - #define RAYGUI_MALLOC(sz) malloc(sz) -#endif -#ifndef RAYGUI_CALLOC - #define RAYGUI_CALLOC(n,sz) calloc(n,sz) -#endif -#ifndef RAYGUI_FREE - #define RAYGUI_FREE(p) free(p) -#endif - // Simple log system to avoid printf() calls if required // NOTE: Avoiding those calls, also avoids const strings memory usage #define RAYGUI_SUPPORT_LOG_INFO @@ -421,13 +417,16 @@ // TODO: Texture2D type is very coupled to raylib, required by Font type // It should be redesigned to be provided by user - typedef struct Texture2D { + typedef struct Texture { unsigned int id; // OpenGL texture id int width; // Texture base width int height; // Texture base height int mipmaps; // Mipmap levels, 1 by default int format; // Data format (PixelFormat type) - } Texture2D; + } Texture; + + // Texture2D, same as Texture + typedef Texture Texture2D; // Image, pixel data stored in CPU memory (RAM) typedef struct Image { @@ -527,7 +526,7 @@ typedef enum { DROPDOWNBOX, TEXTBOX, // Used also for: TEXTBOXMULTI VALUEBOX, - SPINNER, // Uses: BUTTON, VALUEBOX + CONTROL11, LISTVIEW, COLORPICKER, SCROLLBAR, @@ -549,12 +548,12 @@ typedef enum { BORDER_COLOR_DISABLED, // Control border color in STATE_DISABLED BASE_COLOR_DISABLED, // Control base color in STATE_DISABLED TEXT_COLOR_DISABLED, // Control text color in STATE_DISABLED - BORDER_WIDTH, // Control border size, 0 for no border + BORDER_WIDTH = 12, // Control border size, 0 for no border //TEXT_SIZE, // Control text size (glyphs max height) -> GLOBAL for all controls //TEXT_SPACING, // Control text spacing between glyphs -> GLOBAL for all controls - //TEXT_LINE_SPACING // Control text spacing between lines -> GLOBAL for all controls - TEXT_PADDING, // Control text padding, not considering border - TEXT_ALIGNMENT, // Control text horizontal alignment inside control text bound (after border and padding) + //TEXT_LINE_SPACING, // Control text spacing between lines -> GLOBAL for all controls + TEXT_PADDING = 13, // Control text padding, not considering border + TEXT_ALIGNMENT = 14, // Control text horizontal alignment inside control text bound (after border and padding) //TEXT_WRAP_MODE // Control text wrap-mode inside text bounds -> GLOBAL for all controls } GuiControlProperty; @@ -641,11 +640,14 @@ typedef enum { TEXT_READONLY = 16, // TextBox in read-only mode: 0-text editable, 1-text no-editable } GuiTextBoxProperty; -// Spinner +// ValueBox/Spinner typedef enum { - SPIN_BUTTON_WIDTH = 16, // Spinner left/right buttons width - SPIN_BUTTON_SPACING, // Spinner buttons separation -} GuiSpinnerProperty; + SPINNER_BUTTON_WIDTH = 16, // Spinner left/right buttons width + SPINNER_BUTTON_SPACING, // Spinner buttons separation +} GuiValueBoxProperty; + +// Control11 +//typedef enum { } GuiControl11Property; // ListView typedef enum { @@ -653,6 +655,7 @@ typedef enum { LIST_ITEMS_SPACING, // ListView items separation SCROLLBAR_WIDTH, // ListView scrollbar size (usually width) SCROLLBAR_SIDE, // ListView scrollbar side (0-SCROLLBAR_LEFT_SIDE, 1-SCROLLBAR_RIGHT_SIDE) + LIST_ITEMS_BORDER_NORMAL, // ListView items border enabled in normal state LIST_ITEMS_BORDER_WIDTH // ListView items border width } GuiListViewProperty; @@ -717,6 +720,9 @@ RAYGUIAPI char **GuiLoadIcons(const char *fileName, bool loadIconsName); // Load RAYGUIAPI void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color); // Draw icon using pixel size at specified position #endif +// Utility functions +RAYGUIAPI int GuiGetTextWidth(const char *text); // Get text width considering gui style and icon size (if required) + // Controls //---------------------------------------------------------------------------------------------------------- // Container/separator controls, useful for controls organization @@ -999,11 +1005,11 @@ typedef enum { ICON_MLAYERS = 226, ICON_MAPS = 227, ICON_HOT = 228, - ICON_229 = 229, - ICON_230 = 230, - ICON_231 = 231, - ICON_232 = 232, - ICON_233 = 233, + ICON_LABEL = 229, + ICON_NAME_ID = 230, + ICON_SLICING = 231, + ICON_MANUAL_CONTROL = 232, + ICON_COLLISION = 233, ICON_234 = 234, ICON_235 = 235, ICON_236 = 236, @@ -1046,12 +1052,24 @@ typedef enum { #if defined(RAYGUI_IMPLEMENTATION) #include // required for: isspace() [GuiTextBox()] -#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf(), vsprintf() [GuiLoadStyle(), GuiLoadIcons()] -#include // Required for: malloc(), calloc(), free() [GuiLoadStyle(), GuiLoadIcons()] +#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf(), snprintf(), vsprintf() [GuiLoadStyle(), GuiLoadIcons()] #include // Required for: strlen() [GuiTextBox(), GuiValueBox()], memset(), memcpy() #include // Required for: va_list, va_start(), vfprintf(), va_end() [TextFormat()] #include // Required for: roundf() [GuiColorPicker()] +// Allow custom memory allocators +#if defined(RAYGUI_MALLOC) || defined(RAYGUI_CALLOC) || defined(RAYGUI_FREE) + #if !defined(RAYGUI_MALLOC) || !defined(RAYGUI_CALLOC) || !defined(RAYGUI_FREE) + #error "RAYGUI: if RAYGUI_MALLOC, RAYGUI_CALLOC, or RAYGUI_FREE is customized, all three must be customized" + #endif +#else + #include // Required for: malloc(), calloc(), free() [GuiLoadStyle(), GuiLoadIcons()] + + #define RAYGUI_MALLOC(sz) malloc(sz) + #define RAYGUI_CALLOC(n,sz) calloc(n,sz) + #define RAYGUI_FREE(p) free(p) +#endif + #ifdef __cplusplus #define RAYGUI_CLITERAL(name) name #else @@ -1318,11 +1336,11 @@ static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x422a422a, 0x422e422a, 0x40384e28, 0x00007fe0, // ICON_MLAYERS 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x5b2a512a, 0x512e552a, 0x40385128, 0x00007fe0, // ICON_MAPS 0x04200000, 0x1cf00c60, 0x11f019f0, 0x0f3807b8, 0x1e3c0f3c, 0x1c1c1e1c, 0x1e3c1c1c, 0x00000f70, // ICON_HOT - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_229 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_230 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_231 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_232 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_233 + 0x00000000, 0x20803f00, 0x2a202e40, 0x20082e10, 0x08021004, 0x02040402, 0x00900108, 0x00000060, // ICON_LABEL + 0x00000000, 0x042007e0, 0x47e27c3e, 0x4ffa4002, 0x47fa4002, 0x4ffa4002, 0x7ffe4002, 0x00000000, // ICON_NAME_ID + 0x7fe00000, 0x402e4020, 0x43ce5e0a, 0x40504078, 0x438e4078, 0x402e5e0a, 0x7fe04020, 0x00000000, // ICON_SLICING + 0x00000000, 0x40027ffe, 0x47c24002, 0x55425d42, 0x55725542, 0x50125552, 0x10105016, 0x00001ff0, // ICON_MANUAL_CONTROL + 0x7ffe0000, 0x43c24002, 0x48124422, 0x500a500a, 0x500a500a, 0x44224812, 0x400243c2, 0x00007ffe, // ICON_COLLISION 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_234 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_235 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_236 @@ -1363,7 +1381,7 @@ static unsigned int *guiIconsPtr = guiIcons; #define RAYGUI_MAX_PROPS_EXTENDED 8 // Maximum number of extended properties //---------------------------------------------------------------------------------- -// Types and Structures Definition +// Module Types and Structures Definition //---------------------------------------------------------------------------------- // Gui control property style color element typedef enum { BORDER = 0, BASE, TEXT, OTHER } GuiPropertyElement; @@ -1387,8 +1405,7 @@ static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*() //static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking -static int autoCursorCooldownCounter = 0; // Cooldown frame counter for automatic cursor movement on key-down -static int autoCursorDelayCounter = 0; // Delay frame counter for automatic cursor movement +static int autoCursorCounter = 0; // Frame counter for automatic repeated cursor movement on key-down (cooldown and delay) //---------------------------------------------------------------------------------- // Style data array for all gui style properties (allocated on data segment by default) @@ -1484,7 +1501,6 @@ static void DrawRectangleGradientV(int posX, int posY, int width, int height, Co //---------------------------------------------------------------------------------- static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize); // Load style from memory (binary only) -static int GetTextWidth(const char *text); // Gui get text width using gui font and style static Rectangle GetTextBounds(int control, Rectangle bounds); // Get text bounds considering control bounds static const char *GetTextIcon(const char *text, int *iconId); // Get text icon if provided and move text cursor @@ -1589,6 +1605,10 @@ int GuiWindowBox(Rectangle bounds, const char *title) #define RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT 24 #endif + #if !defined(RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT) + #define RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT 18 + #endif + int result = 0; //GuiState state = guiState; @@ -1597,9 +1617,10 @@ int GuiWindowBox(Rectangle bounds, const char *title) Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)statusBarHeight }; if (bounds.height < statusBarHeight*2.0f) bounds.height = statusBarHeight*2.0f; + const float vPadding = statusBarHeight/2.0f - RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT/2.0f; Rectangle windowPanel = { bounds.x, bounds.y + (float)statusBarHeight - 1, bounds.width, bounds.height - (float)statusBarHeight + 1 }; - Rectangle closeButtonRec = { statusBar.x + statusBar.width - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - 20, - statusBar.y + statusBarHeight/2.0f - 18.0f/2.0f, 18, 18 }; + Rectangle closeButtonRec = { statusBar.x + statusBar.width - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT - vPadding, + statusBar.y + vPadding, RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT, RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT }; // Update control //-------------------------------------------------------------------- @@ -1653,7 +1674,7 @@ int GuiGroupBox(Rectangle bounds, const char *text) // Line control int GuiLine(Rectangle bounds, const char *text) { - #if !defined(RAYGUI_LINE_ORIGIN_SIZE) + #if !defined(RAYGUI_LINE_MARGIN_TEXT) #define RAYGUI_LINE_MARGIN_TEXT 12 #endif #if !defined(RAYGUI_LINE_TEXT_PADDING) @@ -1671,7 +1692,7 @@ int GuiLine(Rectangle bounds, const char *text) else { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.width = (float)GuiGetTextWidth(text) + 2; textBounds.height = bounds.height; textBounds.x = bounds.x + RAYGUI_LINE_MARGIN_TEXT; textBounds.y = bounds.y; @@ -1711,8 +1732,8 @@ int GuiPanel(Rectangle bounds, const char *text) //-------------------------------------------------------------------- if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar - GuiDrawRectangle(bounds, RAYGUI_PANEL_BORDER_WIDTH, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED: (int)LINE_COLOR)), - GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? BASE_COLOR_DISABLED : BACKGROUND_COLOR))); + GuiDrawRectangle(bounds, RAYGUI_PANEL_BORDER_WIDTH, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR)), + GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BASE_COLOR_DISABLED : (int)BACKGROUND_COLOR))); //-------------------------------------------------------------------- return result; @@ -2011,7 +2032,7 @@ int GuiLabelButton(Rectangle bounds, const char *text) bool pressed = false; // NOTE: We force bounds.width to be all text - float textWidth = (float)GetTextWidth(text); + float textWidth = (float)GuiGetTextWidth(text); if ((bounds.width - 2*GuiGetStyle(LABEL, BORDER_WIDTH) - 2*GuiGetStyle(LABEL, TEXT_PADDING)) < textWidth) bounds.width = textWidth + 2*GuiGetStyle(LABEL, BORDER_WIDTH) + 2*GuiGetStyle(LABEL, TEXT_PADDING) + 2; // Update control @@ -2149,7 +2170,9 @@ int GuiToggleSlider(Rectangle bounds, const char *text, int *active) // Get substrings items from text (items pointers) int itemCount = 0; - const char **items = GuiTextSplit(text, ';', &itemCount, NULL); + const char **items = NULL; + + if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); Rectangle slider = { 0, // Calculated later depending on the active toggle @@ -2196,7 +2219,7 @@ int GuiToggleSlider(Rectangle bounds, const char *text, int *active) if (text != NULL) { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(text); + textBounds.width = (float)GuiGetTextWidth(text); textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = slider.x + slider.width/2 - textBounds.width/2; textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; @@ -2221,7 +2244,7 @@ int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) if (text != NULL) { - textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.width = (float)GuiGetTextWidth(text) + 2; textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x + bounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; @@ -2474,7 +2497,7 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) { #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) - #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 40 // Frames to wait for autocursor movement + #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 20 // Frames to wait for autocursor movement #endif #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement @@ -2487,10 +2510,10 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); - int textLength = (int)strlen(text); // Get current text length + int textLength = (text != NULL)? (int)strlen(text) : 0; // Get current text length int thisCursorIndex = textBoxCursorIndex; if (thisCursorIndex > textLength) thisCursorIndex = textLength; - int textWidth = GetTextWidth(text) - GetTextWidth(text + thisCursorIndex); + int textWidth = GuiGetTextWidth(text) - GuiGetTextWidth(text + thisCursorIndex); int textIndexOffset = 0; // Text index offset to start drawing in the box // Cursor rectangle @@ -2511,15 +2534,6 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) mouseCursor.x = -1; mouseCursor.width = 1; - // Auto-cursor movement logic - // NOTE: Cursor moves automatically when key down after some time - if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCooldownCounter++; - else - { - autoCursorCooldownCounter = 0; // GLOBAL: Cursor cooldown counter - autoCursorDelayCounter = 0; // GLOBAL: Cursor delay counter - } - // Blink-cursor frame counter //if (!autoCursorMode) blinkCursorFrameCounter++; //else blinkCursorFrameCounter = 0; @@ -2537,6 +2551,13 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) if (editMode) { + // GLOBAL: Auto-cursor movement logic + // NOTE: Keystrokes are handled repeatedly when button is held down for some time + if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCounter++; + else autoCursorCounter = 0; + + bool autoCursorShouldTrigger = (autoCursorCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) && ((autoCursorCounter % RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0); + state = STATE_PRESSED; if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; @@ -2550,7 +2571,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) textIndexOffset += nextCodepointSize; - textWidth = GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex); + textWidth = GuiGetTextWidth(text + textIndexOffset) - GuiGetTextWidth(text + textBoxCursorIndex); } int codepoint = GetCharPressed(); // Get Unicode codepoint @@ -2560,10 +2581,43 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) int codepointSize = 0; const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); - // Add codepoint to text, at current cursor position - // NOTE: Make sure we do not overflow buffer size - if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) + // Handle text paste action + if (IsKeyPressed(KEY_V) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) { + const char *pasteText = GetClipboardText(); + if (pasteText != NULL) + { + int pasteLength = 0; + int pasteCodepoint; + int pasteCodepointSize; + + // Count how many codepoints to copy, stopping at the first unwanted control character + while (true) + { + pasteCodepoint = GetCodepointNext(pasteText + pasteLength, &pasteCodepointSize); + if (textLength + pasteLength + pasteCodepointSize >= textSize) break; + if (!(multiline && (pasteCodepoint == (int)'\n')) && !(pasteCodepoint >= 32)) break; + pasteLength += pasteCodepointSize; + } + + if (pasteLength > 0) + { + // Move forward data from cursor position + for (int i = textLength + pasteLength; i > textBoxCursorIndex; i--) text[i] = text[i - pasteLength]; + + // Paste data in at cursor + for (int i = 0; i < pasteLength; i++) text[textBoxCursorIndex + i] = pasteText[i]; + + textBoxCursorIndex += pasteLength; + textLength += pasteLength; + text[textLength] = '\0'; + } + } + } + else if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) + { + // Adding codepoint to text, at current cursor position + // Move forward data from cursor position for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; @@ -2583,113 +2637,185 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) // Move cursor to end if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength; - // Delete codepoint from text, after current cursor position - if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + // Delete related codepoints from text, after current cursor position + if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_DELETE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) { - autoCursorDelayCounter++; + int offset = textBoxCursorIndex; + int accCodepointSize = 0; + int nextCodepointSize; + int nextCodepoint; - if (IsKeyPressed(KEY_DELETE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + bool puctuation = ispunct(nextCodepoint & 0xff); + while (offset < textLength) { - int nextCodepointSize = 0; - GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); - - // Move backward text from cursor position - for (int i = textBoxCursorIndex; i < textLength; i++) text[i] = text[i + nextCodepointSize]; - - textLength -= codepointSize; - if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; - - // Make sure text last character is EOL - text[textLength] = '\0'; + if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) + break; + offset += nextCodepointSize; + accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); } + + // Check whitespace to delete (ASCII only) + while (offset < textLength) + { + if (!isspace(nextCodepoint & 0xff)) break; + + offset += nextCodepointSize; + accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + } + + // Move text after cursor forward (including final null terminator) + for (int i = offset; i <= textLength; i++) text[i - accCodepointSize] = text[i]; + + textLength -= accCodepointSize; + } + + else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && autoCursorShouldTrigger))) + { + // Delete single codepoint from text, after current cursor position + + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex + nextCodepointSize; i <= textLength; i++) text[i - nextCodepointSize] = text[i]; + + textLength -= nextCodepointSize; } // Delete related codepoints from text, before current cursor position - if ((textLength > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) { - int i = textBoxCursorIndex - 1; + int offset = textBoxCursorIndex; int accCodepointSize = 0; + int prevCodepointSize; + int prevCodepoint; - // Move cursor to the end of word if on space already - while ((i > 0) && isspace(text[i])) + // Check whitespace to delete (ASCII only) + while (offset > 0) { - int prevCodepointSize = 0; - GetCodepointPrevious(text + i, &prevCodepointSize); - i -= prevCodepointSize; + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if (!isspace(prevCodepoint & 0xff)) break; + + offset -= prevCodepointSize; + accCodepointSize += prevCodepointSize; + } + + // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + bool puctuation = ispunct(prevCodepoint & 0xff); + while (offset > 0) + { + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break; + + offset -= prevCodepointSize; accCodepointSize += prevCodepointSize; } - // Move cursor to the start of the word - while ((i > 0) && !isspace(text[i])) - { - int prevCodepointSize = 0; - GetCodepointPrevious(text + i, &prevCodepointSize); - i -= prevCodepointSize; - accCodepointSize += prevCodepointSize; - } + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - accCodepointSize] = text[i]; - // Move forward text from cursor position - for (int j = (textBoxCursorIndex - accCodepointSize); j < textLength; j++) text[j] = text[j + accCodepointSize]; - - // Prevent cursor index from decrementing past 0 - if (textBoxCursorIndex > 0) - { - textBoxCursorIndex -= accCodepointSize; - textLength -= accCodepointSize; - } - - // Make sure text last character is EOL - text[textLength] = '\0'; - } - else if ((textLength > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + textLength -= accCodepointSize; + textBoxCursorIndex -= accCodepointSize; + } + + else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && autoCursorShouldTrigger))) { - autoCursorDelayCounter++; + // Delete single codepoint from text, before current cursor position + + int prevCodepointSize = 0; - if (IsKeyPressed(KEY_BACKSPACE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames - { - int prevCodepointSize = 0; + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); - // Prevent cursor index from decrementing past 0 - if (textBoxCursorIndex > 0) - { - GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - prevCodepointSize] = text[i]; - // Move backward text from cursor position - for (int i = (textBoxCursorIndex - prevCodepointSize); i < textLength; i++) text[i] = text[i + prevCodepointSize]; - - textBoxCursorIndex -= codepointSize; - textLength -= codepointSize; - } - - // Make sure text last character is EOL - text[textLength] = '\0'; - } + textLength -= prevCodepointSize; + textBoxCursorIndex -= prevCodepointSize; } // Move cursor position with keys - if (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) + if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_LEFT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) { - autoCursorDelayCounter++; + int offset = textBoxCursorIndex; + //int accCodepointSize = 0; + int prevCodepointSize; + int prevCodepoint; - if (IsKeyPressed(KEY_LEFT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + // Check whitespace to skip (ASCII only) + while (offset > 0) { - int prevCodepointSize = 0; - if (textBoxCursorIndex > 0) GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if (!isspace(prevCodepoint & 0xff)) break; - if (textBoxCursorIndex >= prevCodepointSize) textBoxCursorIndex -= prevCodepointSize; + offset -= prevCodepointSize; + //accCodepointSize += prevCodepointSize; } + + // Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + bool puctuation = ispunct(prevCodepoint & 0xff); + while (offset > 0) + { + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break; + + offset -= prevCodepointSize; + //accCodepointSize += prevCodepointSize; + } + + textBoxCursorIndex = offset; } - else if (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) + else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && autoCursorShouldTrigger))) { - autoCursorDelayCounter++; + int prevCodepointSize = 0; + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); - if (IsKeyPressed(KEY_RIGHT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + textBoxCursorIndex -= prevCodepointSize; + } + else if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_RIGHT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + int offset = textBoxCursorIndex; + //int accCodepointSize = 0; + int nextCodepointSize; + int nextCodepoint; + + // Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + bool puctuation = ispunct(nextCodepoint & 0xff); + while (offset < textLength) { - int nextCodepointSize = 0; - GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) break; - if ((textBoxCursorIndex + nextCodepointSize) <= textLength) textBoxCursorIndex += nextCodepointSize; + offset += nextCodepointSize; + //accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); } + + // Check whitespace to skip (ASCII only) + while (offset < textLength) + { + if (!isspace(nextCodepoint & 0xff)) break; + + offset += nextCodepointSize; + //accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + } + + textBoxCursorIndex = offset; + } + else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && autoCursorShouldTrigger))) + { + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + textBoxCursorIndex += nextCodepointSize; } // Move cursor position with mouse @@ -2701,7 +2827,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) float widthToMouseX = 0; int mouseCursorIndex = 0; - for (int i = textIndexOffset; i < textLength; i++) + for (int i = textIndexOffset; i < textLength; i += codepointSize) { codepoint = GetCodepointNext(&text[i], &codepointSize); codepointIndex = GetGlyphIndex(guiFont, codepoint); @@ -2720,7 +2846,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) } // Check if mouse cursor is at the last position - int textEndWidth = GetTextWidth(text + textIndexOffset); + int textEndWidth = GuiGetTextWidth(text + textIndexOffset); if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2)) { mouseCursor.x = textBounds.x + textEndWidth; @@ -2737,7 +2863,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) else mouseCursor.x = -1; // Recalculate cursor position.y depending on textBoxCursorIndex - cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); + cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GuiGetTextWidth(text + textIndexOffset) - GuiGetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); //if (multiline) cursor.y = GetTextLines() // Finish text editing on ENTER or mouse click outside bounds @@ -2745,6 +2871,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) (!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) { textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index + autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes result = 1; } } @@ -2757,6 +2884,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text + autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes result = 1; } } @@ -2825,19 +2953,22 @@ int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int int tempValue = *value; - Rectangle spinner = { bounds.x + GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING), bounds.y, - bounds.width - 2*(GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING)), bounds.height }; - Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; - Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; + Rectangle valueBoxBounds = { + bounds.x + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH) + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_SPACING), + bounds.y, + bounds.width - 2*(GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH) + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_SPACING)), bounds.height }; + Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.height }; + Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.y, + (float)GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.height }; Rectangle textBounds = { 0 }; if (text != NULL) { - textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.width = (float)GuiGetTextWidth(text) + 2; textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(SPINNER, TEXT_PADDING); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - if (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SPINNER, TEXT_PADDING); + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); } // Update control @@ -2871,20 +3002,20 @@ int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int // Draw control //-------------------------------------------------------------------- - result = GuiValueBox(spinner, NULL, &tempValue, minValue, maxValue, editMode); + result = GuiValueBox(valueBoxBounds, NULL, &tempValue, minValue, maxValue, editMode); // Draw value selector custom buttons // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(SPINNER, BORDER_WIDTH)); + GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(VALUEBOX, BORDER_WIDTH)); GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); // Draw text label if provided - GuiDrawText(text, textBounds, (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); //-------------------------------------------------------------------- *value = tempValue; @@ -2903,12 +3034,12 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in GuiState state = guiState; char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; - sprintf(textValue, "%i", *value); + snprintf(textValue, RAYGUI_VALUEBOX_MAX_CHARS + 1, "%i", *value); Rectangle textBounds = { 0 }; if (text != NULL) { - textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.width = (float)GuiGetTextWidth(text) + 2; textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; @@ -2929,10 +3060,37 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in int keyCount = (int)strlen(textValue); + // Add or remove minus symbol + if (IsKeyPressed(KEY_MINUS)) + { + if (textValue[0] == '-') + { + for (int i = 0 ; i < keyCount; i++) textValue[i] = textValue[i + 1]; + + keyCount--; + valueHasChanged = true; + } + else if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS -1) + { + if (keyCount == 0) + { + textValue[0] = '0'; + textValue[1] = '\0'; + keyCount++; + } + + for (int i = keyCount ; i > -1; i--) textValue[i + 1] = textValue[i]; + + textValue[0] = '-'; + keyCount++; + valueHasChanged = true; + } + } + // Only allow keys in range [48..57] if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) { - if (GetTextWidth(textValue) < bounds.width) + if (GuiGetTextWidth(textValue) < bounds.width) { int key = GetCharPressed(); if ((key >= 48) && (key <= 57)) @@ -2992,11 +3150,14 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); - // Draw cursor + // Draw cursor rectangle if (editMode) { // NOTE: ValueBox internal text is always centered - Rectangle cursor = { bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH) }; + Rectangle cursor = { bounds.x + GuiGetTextWidth(textValue)/2 + bounds.width/2 + 1, + bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH) + 2, + 2, bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2 - 4 }; + if (cursor.height > bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); } @@ -3019,12 +3180,12 @@ int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float GuiState state = guiState; //char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; - //sprintf(textValue, "%2.2f", *value); + //snprintf(textValue, sizeof(textValue), "%2.2f", *value); - Rectangle textBounds = {0}; + Rectangle textBounds = { 0 }; if (text != NULL) { - textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.width = (float)GuiGetTextWidth(text) + 2; textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; @@ -3045,10 +3206,37 @@ int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float int keyCount = (int)strlen(textValue); + // Add or remove minus symbol + if (IsKeyPressed(KEY_MINUS)) + { + if (textValue[0] == '-') + { + for (int i = 0; i < keyCount; i++) textValue[i] = textValue[i + 1]; + + keyCount--; + valueHasChanged = true; + } + else if (keyCount < (RAYGUI_VALUEBOX_MAX_CHARS - 1)) + { + if (keyCount == 0) + { + textValue[0] = '0'; + textValue[1] = '\0'; + keyCount++; + } + + for (int i = keyCount; i > -1; i--) textValue[i + 1] = textValue[i]; + + textValue[0] = '-'; + keyCount++; + valueHasChanged = true; + } + } + // Only allow keys in range [48..57] if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) { - if (GetTextWidth(textValue) < bounds.width) + if (GuiGetTextWidth(textValue) < bounds.width) { int key = GetCharPressed(); if (((key >= 48) && (key <= 57)) || @@ -3103,7 +3291,7 @@ int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float if (editMode) { // NOTE: ValueBox internal text is always centered - Rectangle cursor = {bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, + Rectangle cursor = {bounds.x + GuiGetTextWidth(textValue)/2 + bounds.width/2 + 1, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH)}; GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); @@ -3120,7 +3308,7 @@ int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float // Slider control with pro parameters // NOTE: Other GuiSlider*() controls use this one -int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue, int sliderWidth) +int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) { int result = 0; GuiState state = guiState; @@ -3129,6 +3317,8 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, if (value == NULL) value = &temp; float oldValue = *value; + int sliderWidth = GuiGetStyle(SLIDER, SLIDER_WIDTH); + Rectangle slider = { bounds.x, bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), 0, bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; @@ -3146,7 +3336,7 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, { state = STATE_PRESSED; // Get equivalent value and slider position from mousePosition.x - *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width - sliderWidth)) + minValue; } } else @@ -3166,7 +3356,7 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, if (!CheckCollisionPointRec(mousePoint, slider)) { // Get equivalent value and slider position from mousePosition.x - *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width - sliderWidth)) + minValue; } } else state = STATE_FOCUSED; @@ -3205,44 +3395,45 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_FOCUSED))); else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_PRESSED))); + else if (state == STATE_DISABLED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_DISABLED))); // Draw left/right text if provided if (textLeft != NULL) { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textLeft); + textBounds.width = (float)GuiGetTextWidth(textLeft); textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SLIDER, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); } if (textRight != NULL) { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textRight); + textBounds.width = (float)GuiGetTextWidth(textRight); textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x + bounds.width + GuiGetStyle(SLIDER, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); } //-------------------------------------------------------------------- return result; } -// Slider control extended, returns selected value and has text -int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) -{ - return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, GuiGetStyle(SLIDER, SLIDER_WIDTH)); -} - // Slider Bar control extended, returns selected value int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) { - return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, 0); + int result = 0; + int preSliderWidth = GuiGetStyle(SLIDER, SLIDER_WIDTH); + GuiSetStyle(SLIDER, SLIDER_WIDTH, 0); + result = GuiSlider(bounds, textLeft, textRight, value, minValue, maxValue); + GuiSetStyle(SLIDER, SLIDER_WIDTH, preSliderWidth); + + return result; } // Progress Bar control extended, shows current progress value @@ -3257,14 +3448,14 @@ int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight // Progress bar Rectangle progress = { bounds.x + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) + GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING), 0, - bounds.height - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - 2*GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING) }; + bounds.height - GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - 2*GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING) -1 }; // Update control //-------------------------------------------------------------------- if (*value > maxValue) *value = maxValue; // WARNING: Working with floats could lead to rounding issues - if ((state != STATE_DISABLED)) progress.width = (float)(*value/(maxValue - minValue))*bounds.width - ((*value >= maxValue)? (float)(2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)) : 0.0f); + if ((state != STATE_DISABLED)) progress.width = ((float)*value/(maxValue - minValue))*(bounds.width - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)); //-------------------------------------------------------------------- // Draw control @@ -3282,15 +3473,15 @@ int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); } - else GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + else GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - if (*value >= maxValue) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + progress.width + 1, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + if (*value >= maxValue) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1}, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); else { // Draw borders not yet reached by value - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y + bounds.height - 1, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y + bounds.height - 1, bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); } // Draw slider internal progress bar (depends on state) @@ -3301,23 +3492,23 @@ int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight if (textLeft != NULL) { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textLeft); + textBounds.width = (float)GuiGetTextWidth(textLeft); textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x - textBounds.width - GuiGetStyle(PROGRESSBAR, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); } if (textRight != NULL) { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textRight); + textBounds.width = (float)GuiGetTextWidth(textRight); textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x + bounds.width + GuiGetStyle(PROGRESSBAR, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); } //-------------------------------------------------------------------- @@ -3467,11 +3658,11 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd // Draw visible items for (int i = 0; ((i < visibleItems) && (text != NULL)); i++) { - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_NORMAL)), BLANK); + if (GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_NORMAL)) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_NORMAL)), BLANK); if (state == STATE_DISABLED) { - if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); + if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED))); } @@ -3480,18 +3671,18 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd if (((startIndex + i) == itemSelected) && (active != NULL)) { // Draw item selected - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED))); + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED))); GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED))); } else if (((startIndex + i) == itemFocused)) // && (focus != NULL)) // NOTE: We want items focused, despite not returned! { // Draw item focused - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED))); + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED))); GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED))); } else { - // Draw item normal + // Draw item normal (no rectangle) GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL))); } } @@ -3531,22 +3722,22 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd return result; } -// Color Panel control - Color (RGBA) variant. +// Color Panel control - Color (RGBA) variant int GuiColorPanel(Rectangle bounds, const char *text, Color *color) { int result = 0; Vector3 vcolor = { (float)color->r/255.0f, (float)color->g/255.0f, (float)color->b/255.0f }; Vector3 hsv = ConvertRGBtoHSV(vcolor); - Vector3 prevHsv = hsv; // workaround to see if GuiColorPanelHSV modifies the hsv. + Vector3 prevHsv = hsv; // workaround to see if GuiColorPanelHSV modifies the hsv GuiColorPanelHSV(bounds, text, &hsv); - // Check if the hsv was changed, only then change the color. - // This is required, because the Color->HSV->Color conversion has precision errors. - // Thus the assignment from HSV to Color should only be made, if the HSV has a new user-entered value. - // Otherwise GuiColorPanel would often modify it's color without user input. - // TODO: GuiColorPanelHSV could return 1 if the slider was dragged, to simplify this check. + // Check if the hsv was changed, only then change the color + // This is required, because the Color->HSV->Color conversion has precision errors + // Thus the assignment from HSV to Color should only be made, if the HSV has a new user-entered value + // Otherwise GuiColorPanel would often modify it's color without user input + // TODO: GuiColorPanelHSV could return 1 if the slider was dragged, to simplify this check if (hsv.x != prevHsv.x || hsv.y != prevHsv.y || hsv.z != prevHsv.z) { Vector3 rgb = ConvertHSVtoRGB(hsv); @@ -3570,7 +3761,10 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) int result = 0; GuiState state = guiState; - Rectangle selector = { (float)bounds.x + (*alpha)*bounds.width - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.y - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT), (float)bounds.height + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2 }; + Rectangle selector = { (float)bounds.x + (*alpha)*bounds.width - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, + (float)bounds.y - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), + (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT), + (float)bounds.height + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2 }; // Update control //-------------------------------------------------------------------- @@ -3617,7 +3811,6 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) // Draw control //-------------------------------------------------------------------- - // Draw alpha bar: checked background if (state != STATE_DISABLED) { @@ -3755,7 +3948,7 @@ int GuiColorPicker(Rectangle bounds, const char *text, Color *color) Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; //Rectangle boundsAlpha = { bounds.x, bounds.y + bounds.height + GuiGetStyle(COLORPICKER, BARS_PADDING), bounds.width, GuiGetStyle(COLORPICKER, BARS_THICK) }; - // NOTE: this conversion can cause low hue-resolution, if the r, g and b value are very similar, which causes the hue bar to shift around when only the GuiColorPanel is used. + // NOTE: this conversion can cause low hue-resolution, if the r, g and b value are very similar, which causes the hue bar to shift around when only the GuiColorPanel is used Vector3 hsv = ConvertRGBtoHSV(RAYGUI_CLITERAL(Vector3){ (*color).r/255.0f, (*color).g/255.0f, (*color).b/255.0f }); GuiColorBarHue(boundsHue, NULL, &hsv.x); @@ -3768,8 +3961,8 @@ int GuiColorPicker(Rectangle bounds, const char *text, Color *color) return result; } -// Color Picker control that avoids conversion to RGB and back to HSV on each call, thus avoiding jittering. -// The user can call ConvertHSVtoRGB() to convert *colorHsv value to RGB. +// Color Picker control that avoids conversion to RGB and back to HSV on each call, thus avoiding jittering +// The user can call ConvertHSVtoRGB() to convert *colorHsv value to RGB // NOTE: It's divided in multiple controls: // int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) // int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) @@ -3917,7 +4110,7 @@ int GuiMessageBox(Rectangle bounds, const char *title, const char *message, cons buttonBounds.width = (bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; buttonBounds.height = RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; - //int textWidth = GetTextWidth(message) + 2; + //int textWidth = GuiGetTextWidth(message) + 2; Rectangle textBounds = { 0 }; textBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; @@ -3981,7 +4174,7 @@ int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, co Rectangle textBounds = { 0 }; if (message != NULL) { - int textSize = GetTextWidth(message) + 2; + int textSize = GuiGetTextWidth(message) + 2; textBounds.x = bounds.x + bounds.width/2 - textSize/2; textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + messageInputHeight/4 - (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/2; @@ -4221,7 +4414,7 @@ void GuiLoadStyle(const char *fileName) if (fileDataSize > 0) { - unsigned char *fileData = (unsigned char *)RAYGUI_MALLOC(fileDataSize*sizeof(unsigned char)); + unsigned char *fileData = (unsigned char *)RAYGUI_CALLOC(fileDataSize, sizeof(unsigned char)); fread(fileData, sizeof(unsigned char), fileDataSize, rgsFile); GuiLoadStyleFromMemory(fileData, fileDataSize); @@ -4283,8 +4476,6 @@ void GuiLoadStyleDefault(void) GuiSetStyle(TEXTBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); GuiSetStyle(VALUEBOX, TEXT_PADDING, 0); GuiSetStyle(VALUEBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - GuiSetStyle(SPINNER, TEXT_PADDING, 0); - GuiSetStyle(SPINNER, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); GuiSetStyle(STATUSBAR, TEXT_PADDING, 8); GuiSetStyle(STATUSBAR, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); @@ -4299,8 +4490,8 @@ void GuiLoadStyleDefault(void) GuiSetStyle(COMBOBOX, COMBO_BUTTON_SPACING, 2); GuiSetStyle(DROPDOWNBOX, ARROW_PADDING, 16); GuiSetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING, 2); - GuiSetStyle(SPINNER, SPIN_BUTTON_WIDTH, 24); - GuiSetStyle(SPINNER, SPIN_BUTTON_SPACING, 2); + GuiSetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH, 24); + GuiSetStyle(VALUEBOX, SPINNER_BUTTON_SPACING, 2); GuiSetStyle(SCROLLBAR, BORDER_WIDTH, 0); GuiSetStyle(SCROLLBAR, ARROWS_VISIBLE, 0); GuiSetStyle(SCROLLBAR, ARROWS_SIZE, 6); @@ -4310,6 +4501,7 @@ void GuiLoadStyleDefault(void) GuiSetStyle(SCROLLBAR, SCROLL_SPEED, 12); GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 28); GuiSetStyle(LISTVIEW, LIST_ITEMS_SPACING, 2); + GuiSetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH, 1); GuiSetStyle(LISTVIEW, SCROLLBAR_WIDTH, 12); GuiSetStyle(LISTVIEW, SCROLLBAR_SIDE, SCROLLBAR_RIGHT_SIDE); GuiSetStyle(COLORPICKER, COLOR_SELECTOR_SIZE, 8); @@ -4322,8 +4514,8 @@ void GuiLoadStyleDefault(void) { // Unload previous font texture UnloadTexture(guiFont.texture); - RL_FREE(guiFont.recs); - RL_FREE(guiFont.glyphs); + RAYGUI_FREE(guiFont.recs); + RAYGUI_FREE(guiFont.glyphs); guiFont.recs = NULL; guiFont.glyphs = NULL; @@ -4352,7 +4544,7 @@ const char *GuiIconText(int iconId, const char *text) if (text != NULL) { memset(buffer, 0, 1024); - sprintf(buffer, "#%03i#", iconId); + snprintf(buffer, 1024, "#%03i#", iconId); for (int i = 5; i < 1024; i++) { @@ -4364,7 +4556,7 @@ const char *GuiIconText(int iconId, const char *text) } else { - sprintf(iconBuffer, "#%03i#", iconId); + snprintf(iconBuffer, 16, "#%03i#", iconId); return iconBuffer; } @@ -4430,17 +4622,17 @@ char **GuiLoadIcons(const char *fileName, bool loadIconsName) { if (loadIconsName) { - guiIconsName = (char **)RAYGUI_MALLOC(iconCount*sizeof(char **)); + guiIconsName = (char **)RAYGUI_CALLOC(iconCount, sizeof(char *)); for (int i = 0; i < iconCount; i++) { - guiIconsName[i] = (char *)RAYGUI_MALLOC(RAYGUI_ICON_MAX_NAME_LENGTH); + guiIconsName[i] = (char *)RAYGUI_CALLOC(RAYGUI_ICON_MAX_NAME_LENGTH, sizeof(char)); fread(guiIconsName[i], 1, RAYGUI_ICON_MAX_NAME_LENGTH, rgiFile); } } else fseek(rgiFile, iconCount*RAYGUI_ICON_MAX_NAME_LENGTH, SEEK_CUR); // Read icons data directly over internal icons array - fread(guiIconsPtr, sizeof(unsigned int), iconCount*(iconSize*iconSize/32), rgiFile); + fread(guiIconsPtr, sizeof(unsigned int), (int)iconCount*((int)iconSize*(int)iconSize/32), rgiFile); } fclose(rgiFile); @@ -4449,6 +4641,56 @@ char **GuiLoadIcons(const char *fileName, bool loadIconsName) return guiIconsName; } +// Load icons from memory +// WARNING: Binary files only +char **GuiLoadIconsFromMemory(const unsigned char *fileData, int dataSize, bool loadIconsName) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + char **guiIconsName = NULL; + + char signature[5] = { 0 }; + short version = 0; + short reserved = 0; + short iconCount = 0; + short iconSize = 0; + + memcpy(signature, fileDataPtr, 4); + memcpy(&version, fileDataPtr + 4, sizeof(short)); + memcpy(&reserved, fileDataPtr + 4 + 2, sizeof(short)); + memcpy(&iconCount, fileDataPtr + 4 + 2 + 2, sizeof(short)); + memcpy(&iconSize, fileDataPtr + 4 + 2 + 2 + 2, sizeof(short)); + fileDataPtr += 12; + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'I') && + (signature[3] == ' ')) + { + if (loadIconsName) + { + guiIconsName = (char **)RAYGUI_CALLOC(iconCount, sizeof(char *)); + for (int i = 0; i < iconCount; i++) + { + guiIconsName[i] = (char *)RAYGUI_CALLOC(RAYGUI_ICON_MAX_NAME_LENGTH, sizeof(char)); + memcpy(guiIconsName[i], fileDataPtr, RAYGUI_ICON_MAX_NAME_LENGTH); + fileDataPtr += RAYGUI_ICON_MAX_NAME_LENGTH; + } + } + else + { + // Skip icon name data if not required + fileDataPtr += iconCount*RAYGUI_ICON_MAX_NAME_LENGTH; + } + + int iconDataSize = iconCount*((int)iconSize*(int)iconSize/32)*(int)sizeof(unsigned int); + guiIconsPtr = (unsigned int *)RAYGUI_CALLOC(iconDataSize, 1); + + memcpy(guiIconsPtr, fileDataPtr, iconDataSize); + } + + return guiIconsName; +} + // Draw selected icon using rectangles pixel-by-pixel void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color) { @@ -4476,12 +4718,73 @@ void GuiSetIconScale(int scale) if (scale >= 1) guiIconScale = scale; } +// Get text width considering gui style and icon size (if required) +int GuiGetTextWidth(const char *text) +{ + #if !defined(ICON_TEXT_PADDING) + #define ICON_TEXT_PADDING 4 + #endif + + Vector2 textSize = { 0 }; + int textIconOffset = 0; + + if ((text != NULL) && (text[0] != '\0')) + { + if (text[0] == '#') + { + for (int i = 1; (i < 5) && (text[i] != '\0'); i++) + { + if (text[i] == '#') + { + textIconOffset = i; + break; + } + } + } + + text += textIconOffset; + + // Make sure guiFont is set, GuiGetStyle() initializes it lazynessly + float fontSize = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + + // Custom MeasureText() implementation + if ((guiFont.texture.id > 0) && (text != NULL)) + { + // Get size in bytes of text, considering end of line and line break + int size = 0; + for (int i = 0; i < MAX_LINE_BUFFER_SIZE; i++) + { + if ((text[i] != '\0') && (text[i] != '\n')) size++; + else break; + } + + float scaleFactor = fontSize/(float)guiFont.baseSize; + textSize.y = (float)guiFont.baseSize*scaleFactor; + float glyphWidth = 0.0f; + + for (int i = 0, codepointSize = 0; i < size; i += codepointSize) + { + int codepoint = GetCodepointNext(&text[i], &codepointSize); + int codepointIndex = GetGlyphIndex(guiFont, codepoint); + + if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); + else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); + + textSize.x += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + } + + if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE + ICON_TEXT_PADDING); + } + + return (int)textSize.x; +} + #endif // !RAYGUI_NO_ICONS //---------------------------------------------------------------------------------- // Module Internal Functions Definition //---------------------------------------------------------------------------------- - // Load style from memory // WARNING: Binary files only static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) @@ -4567,7 +4870,7 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) { // Compressed font atlas image data (DEFLATE), it requires DecompressData() int dataUncompSize = 0; - unsigned char *compData = (unsigned char *)RAYGUI_MALLOC(fontImageCompSize); + unsigned char *compData = (unsigned char *)RAYGUI_CALLOC(fontImageCompSize, sizeof(unsigned char)); memcpy(compData, fileDataPtr, fontImageCompSize); fileDataPtr += fontImageCompSize; @@ -4581,7 +4884,7 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) else { // Font atlas image data is not compressed - imFont.data = (unsigned char *)RAYGUI_MALLOC(fontImageUncompSize); + imFont.data = (unsigned char *)RAYGUI_CALLOC(fontImageUncompSize, sizeof(unsigned char)); memcpy(imFont.data, fileDataPtr, fontImageUncompSize); fileDataPtr += fontImageUncompSize; } @@ -4609,7 +4912,7 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) if ((recsDataCompressedSize > 0) && (recsDataCompressedSize != recsDataSize)) { // Recs data is compressed, uncompress it - unsigned char *recsDataCompressed = (unsigned char *)RAYGUI_MALLOC(recsDataCompressedSize); + unsigned char *recsDataCompressed = (unsigned char *)RAYGUI_CALLOC(recsDataCompressedSize, sizeof(unsigned char)); memcpy(recsDataCompressed, fileDataPtr, recsDataCompressedSize); fileDataPtr += recsDataCompressedSize; @@ -4651,7 +4954,7 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) if ((glyphsDataCompressedSize > 0) && (glyphsDataCompressedSize != glyphsDataSize)) { // Glyphs data is compressed, uncompress it - unsigned char *glypsDataCompressed = (unsigned char *)RAYGUI_MALLOC(glyphsDataCompressedSize); + unsigned char *glypsDataCompressed = (unsigned char *)RAYGUI_CALLOC(glyphsDataCompressedSize, sizeof(unsigned char)); memcpy(glypsDataCompressed, fileDataPtr, glyphsDataCompressedSize); fileDataPtr += glyphsDataCompressedSize; @@ -4704,68 +5007,6 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) } } -// Gui get text width considering icon -static int GetTextWidth(const char *text) -{ - #if !defined(ICON_TEXT_PADDING) - #define ICON_TEXT_PADDING 4 - #endif - - Vector2 textSize = { 0 }; - int textIconOffset = 0; - - if ((text != NULL) && (text[0] != '\0')) - { - if (text[0] == '#') - { - for (int i = 1; (i < 5) && (text[i] != '\0'); i++) - { - if (text[i] == '#') - { - textIconOffset = i; - break; - } - } - } - - text += textIconOffset; - - // Make sure guiFont is set, GuiGetStyle() initializes it lazynessly - float fontSize = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - - // Custom MeasureText() implementation - if ((guiFont.texture.id > 0) && (text != NULL)) - { - // Get size in bytes of text, considering end of line and line break - int size = 0; - for (int i = 0; i < MAX_LINE_BUFFER_SIZE; i++) - { - if ((text[i] != '\0') && (text[i] != '\n')) size++; - else break; - } - - float scaleFactor = fontSize/(float)guiFont.baseSize; - textSize.y = (float)guiFont.baseSize*scaleFactor; - float glyphWidth = 0.0f; - - for (int i = 0, codepointSize = 0; i < size; i += codepointSize) - { - int codepoint = GetCodepointNext(&text[i], &codepointSize); - int codepointIndex = GetGlyphIndex(guiFont, codepoint); - - if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); - else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); - - textSize.x += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - } - } - - if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE + ICON_TEXT_PADDING); - } - - return (int)textSize.x; -} - // Get text bounds considering control bounds static Rectangle GetTextBounds(int control, Rectangle bounds) { @@ -4786,7 +5027,7 @@ static Rectangle GetTextBounds(int control, Rectangle bounds) case SLIDER: case CHECKBOX: case VALUEBOX: - case SPINNER: + case CONTROL11: // TODO: More special cases (label on side): SLIDER, CHECKBOX, VALUEBOX, SPINNER default: { @@ -4832,7 +5073,8 @@ static const char *GetTextIcon(const char *text, int *iconId) } // Get text divided into lines (by line-breaks '\n') -const char **GetTextLines(const char *text, int *count) +// WARNING: It returns pointers to new lines but it does not add NULL ('\0') terminator! +static const char **GetTextLines(const char *text, int *count) { #define RAYGUI_MAX_TEXT_LINES 128 @@ -4936,8 +5178,8 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C float textBoundsWidthOffset = 0.0f; // NOTE: We get text size after icon has been processed - // WARNING: GetTextWidth() also processes text icon to get width! -> Really needed? - int textSizeX = GetTextWidth(lines[i]); + // WARNING: GuiGetTextWidth() also processes text icon to get width! -> Really needed? + int textSizeX = GuiGetTextWidth(lines[i]); // If text requires an icon, add size to measure if (iconId >= 0) @@ -5000,7 +5242,7 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C float textOffsetX = 0.0f; float glyphWidth = 0; - int ellipsisWidth = GetTextWidth("..."); + int ellipsisWidth = GuiGetTextWidth("..."); bool textOverflow = false; for (int c = 0, codepointSize = 0; c < lineSize; c += codepointSize) { @@ -5144,13 +5386,13 @@ static void GuiTooltip(Rectangle controlRec) if ((controlRec.x + textSize.x + 16) > GetScreenWidth()) controlRec.x -= (textSize.x + 16 - controlRec.width); - GuiPanel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, NULL); + GuiPanel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.0f }, NULL); int textPadding = GuiGetStyle(LABEL, TEXT_PADDING); int textAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); GuiSetStyle(LABEL, TEXT_PADDING, 0); GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - GuiLabel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, guiTooltipPtr); + GuiLabel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.0f }, guiTooltipPtr); GuiSetStyle(LABEL, TEXT_ALIGNMENT, textAlignment); GuiSetStyle(LABEL, TEXT_PADDING, textPadding); } @@ -5204,7 +5446,7 @@ static const char **GuiTextSplit(const char *text, char delimiter, int *count, i buffer[i] = '\0'; // Set an end of string at this point counter++; - if (counter > RAYGUI_TEXTSPLIT_MAX_ITEMS) break; + if (counter >= RAYGUI_TEXTSPLIT_MAX_ITEMS) break; } } @@ -5526,10 +5768,10 @@ static Color GetColor(int hexValue) { Color color; - color.r = (unsigned char)(hexValue >> 24) & 0xFF; - color.g = (unsigned char)(hexValue >> 16) & 0xFF; - color.b = (unsigned char)(hexValue >> 8) & 0xFF; - color.a = (unsigned char)hexValue & 0xFF; + color.r = (unsigned char)(hexValue >> 24) & 0xff; + color.g = (unsigned char)(hexValue >> 16) & 0xff; + color.b = (unsigned char)(hexValue >> 8) & 0xff; + color.a = (unsigned char)hexValue & 0xff; return color; } @@ -5562,7 +5804,7 @@ static const char *TextFormat(const char *text, ...) va_list args; va_start(args, text); - vsprintf(buffer, text, args); + vsnprintf(buffer, RAYGUI_TEXTFORMAT_MAX_SIZE, text, args); va_end(args); return buffer; @@ -5731,7 +5973,7 @@ static int GetCodepointNext(const char *text, int *codepointSize) } else if (0xe0 == (0xf0 & ptr[0])) { - // 3 byte UTF-8 codepoint */ + // 3 byte UTF-8 codepoint if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]); *codepointSize = 3; From 6adb1c2704a4e72a14326f78dec34dc6318b6bc3 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 5 Nov 2025 20:51:40 +0100 Subject: [PATCH 048/430] Update core_viewport_scaling.c --- examples/core/core_viewport_scaling.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/core/core_viewport_scaling.c b/examples/core/core_viewport_scaling.c index e70b89829..59e0bd026 100644 --- a/examples/core/core_viewport_scaling.c +++ b/examples/core/core_viewport_scaling.c @@ -92,7 +92,7 @@ int main(void) enum ViewportType viewportType = KEEP_ASPECT_INTEGER; SetConfigFlags(FLAG_WINDOW_RESIZABLE); - InitWindow(screenWidth, screenHeight, "raylib [core] example - Viewport Scaling"); + InitWindow(screenWidth, screenHeight, "raylib [core] example - viewport scaling"); ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target); SetTargetFPS(60); // Set our game to run at 60 frames-per-second From c24f5ac4123356d401b85a9904477c7967215319 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 5 Nov 2025 20:52:14 +0100 Subject: [PATCH 049/430] REXM: Reviewed rebuild to support full categories -WIP- --- tools/rexm/rexm.c | 61 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 4e417e987..450e95819 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -887,11 +887,14 @@ int main(int argc, char *argv[]) case OP_BUILD: { LOG("INFO: Command requested: BUILD\n"); - LOG("INFO: Example to be built: %s\n", exRebuildRequested); + LOG("INFO: Example to be built: %s\n", exName); - if ((strcmp(exRebuildRequested, "others") != 0) && + if ((exRebuildRequested[0] != '\0') && + (strcmp(exRebuildRequested, "others") != 0) && (strcmp(exCategory, "others") != 0)) // Skipping "others" category for rebuild: Special needs { + // TODO: Support building full categories: exRebuildRequested + int exRebuildCount = 0; rlExampleInfo *exRebuildList = LoadExamplesData(exCollectionFilePath, exRebuildRequested, false, &exRebuildCount); @@ -944,7 +947,54 @@ int main(int argc, char *argv[]) UnloadExamplesData(exRebuildList); } - else LOG("WARNING: [others] category examples should be build manually, they could have specific build requirements\n"); + else // Build a single example + { + // Build: raylib.com/examples//_example_name.html + // Build: raylib.com/examples//_example_name.data + // Build: raylib.com/examples//_example_name.wasm + // Build: raylib.com/examples//_example_name.js +#if defined(_WIN32) + // Set required environment variables + //putenv(TextFormat("RAYLIB_DIR=%s\\..", exBasePath)); + _putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); + //putenv("MAKE=mingw32-make"); + //ChangeDirectory(exBasePath); +#endif + + // Build example for PLATFORM_DESKTOP +#if defined(_WIN32) + LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: Win32)\n", exName); + system(TextFormat("mingw32-make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName)); +#else + LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: POSIX)\n", exName); + system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName)); +#endif + + // Build example for PLATFORM_WEB +#if defined(_WIN32) + LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: Win32)\n", exName); + system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exCategory, exName)); +#else + LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: POSIX)\n", exName); + system(TextFormat("make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exCategory, exName)); +#endif + // Update generated .html metadata + LOG("INFO: [%s] Updating HTML Metadata...\n", TextFormat("%s.html", exName)); + UpdateWebMetadata(TextFormat("%s/%s/%s.html", exBasePath, exCategory, exName), + TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); + + // Copy results to web side + LOG("INFO: [%s] Copy example build to raylib.com\n", exName); + FileCopy(TextFormat("%s/%s/%s.html", exBasePath, exCategory, exName), + TextFormat("%s/%s/%s.html", exWebPath, exCategory, exName)); + FileCopy(TextFormat("%s/%s/%s.data", exBasePath, exCategory, exName), + TextFormat("%s/%s/%s.data", exWebPath, exCategory, exName)); + FileCopy(TextFormat("%s/%s/%s.wasm", exBasePath, exCategory, exName), + TextFormat("%s/%s/%s.wasm", exWebPath, exCategory, exName)); + FileCopy(TextFormat("%s/%s/%s.js", exBasePath, exCategory, exName), + TextFormat("%s/%s/%s.js", exWebPath, exCategory, exName)); + } + //LOG("WARNING: [others] category examples should be build manually, they could have specific build requirements\n"); } break; case OP_VALIDATE: // Validate: report and actions @@ -1909,10 +1959,13 @@ static void UnloadExamplesData(rlExampleInfo *exInfo) // WARNING: Expecting the example to follow raylib_example_template.c static rlExampleInfo *LoadExampleInfo(const char *exFileName) { - rlExampleInfo *exInfo = (rlExampleInfo *)RL_CALLOC(1, sizeof(rlExampleInfo)); + rlExampleInfo *exInfo = NULL; if (FileExists(exFileName) && IsFileExtension(exFileName, ".c")) { + // Example found in collection + exInfo = (rlExampleInfo *)RL_CALLOC(1, sizeof(rlExampleInfo)); + strcpy(exInfo->name, GetFileNameWithoutExt(exFileName)); strncpy(exInfo->category, exInfo->name, TextFindIndex(exInfo->name, "_")); From 4883813bbb640e5350346c546b840f36e561eb97 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 5 Nov 2025 20:52:18 +0100 Subject: [PATCH 050/430] Update raygui.h --- examples/shapes/raygui.h | 870 +++++++++++++++++++++++++-------------- 1 file changed, 556 insertions(+), 314 deletions(-) diff --git a/examples/shapes/raygui.h b/examples/shapes/raygui.h index a3fc51f0f..17ced6ef5 100644 --- a/examples/shapes/raygui.h +++ b/examples/shapes/raygui.h @@ -4,7 +4,7 @@ * * DESCRIPTION: * raygui is a tools-dev-focused immediate-mode-gui library based on raylib but also -* available as a standalone library, as long as input and drawing functions are provided. +* available as a standalone library, as long as input and drawing functions are provided * * FEATURES: * - Immediate-mode gui, minimal retained data @@ -27,7 +27,7 @@ * - WARNING: GuiLoadStyle() and GuiLoadStyle{Custom}() functions, allocate memory for * font atlas recs and glyphs, freeing that memory is (usually) up to the user, * no unload function is explicitly provided... but note that GuiLoadStyleDefault() unloads -* by default any previously loaded font (texture, recs, glyphs). +* by default any previously loaded font (texture, recs, glyphs) * - Global UI alpha (guiAlpha) is applied inside GuiDrawRectangle() and GuiDrawText() functions * * CONTROLS PROVIDED: @@ -65,7 +65,7 @@ * - MessageBox --> Window, Label, Button * - TextInputBox --> Window, Label, TextBox, Button * -* It also provides a set of functions for styling the controls based on its properties (size, color). +* It also provides a set of functions for styling the controls based on its properties (size, color) * * * RAYGUI STYLE (guiStyle): @@ -81,7 +81,7 @@ * * Note that the first set of BASE properties (by default guiStyle[0..15]) belong to the generic style * used for all controls, when any of those base values is set, it is automatically populated to all -* controls, so, specific control values overwriting generic style should be set after base values. +* controls, so, specific control values overwriting generic style should be set after base values * * After the first BASE set we have the EXTENDED properties (by default guiStyle[16..23]), those * properties are actually common to all controls and can not be overwritten individually (like BASE ones) @@ -100,7 +100,7 @@ * Every icon is codified in binary form, using 1 bit per pixel, so, every 16x16 icon * requires 8 integers (16*16/32) to be stored in memory. * -* When the icon is draw, actually one quad per pixel is drawn if the bit for that pixel is set. +* When the icon is draw, actually one quad per pixel is drawn if the bit for that pixel is set * * The global icons array size is fixed and depends on the number of icons and size: * @@ -112,20 +112,20 @@ * * RAYGUI LAYOUT: * raygui currently does not provide an auto-layout mechanism like other libraries, -* layouts must be defined manually on controls drawing, providing the right bounds Rectangle for it. +* layouts must be defined manually on controls drawing, providing the right bounds Rectangle for it * * TOOL: rGuiLayout is a visual tool to create raygui layouts: github.com/raysan5/rguilayout * * CONFIGURATION: * #define RAYGUI_IMPLEMENTATION -* Generates the implementation of the library into the included file. +* Generates the implementation of the library into the included file * If not defined, the library is in header only mode and can be included in other headers -* or source files without problems. But only ONE file should hold the implementation. +* or source files without problems. But only ONE file should hold the implementation * * #define RAYGUI_STANDALONE * Avoid raylib.h header inclusion in this file. Data types defined on raylib are defined * internally in the library and input management and drawing functions must be provided by -* the user (check library implementation for further details). +* the user (check library implementation for further details) * * #define RAYGUI_NO_ICONS * Avoid including embedded ricons data (256 icons, 16x16 pixels, 1-bit per pixel, 2KB) @@ -141,12 +141,17 @@ * Draw text bounds rectangles for debug * * VERSIONS HISTORY: -* 4.5-dev (Sep-2024) Current dev version... +* 5.0-dev (2025) Current dev version... * ADDED: guiControlExclusiveMode and guiControlExclusiveRec for exclusive modes * ADDED: GuiValueBoxFloat() * ADDED: GuiDropdonwBox() properties: DROPDOWN_ARROW_HIDDEN, DROPDOWN_ROLL_UP * ADDED: GuiListView() property: LIST_ITEMS_BORDER_WIDTH +* ADDED: GuiLoadIconsFromMemory() * ADDED: Multiple new icons +* REMOVED: GuiSpinner() from controls list, using BUTTON + VALUEBOX properties +* REMOVED: GuiSliderPro(), functionality was redundant +* REVIEWED: Controls using text labels to use LABEL properties +* REVIEWED: Replaced sprintf() by snprintf() for more safety * REVIEWED: GuiTabBar(), close tab with mouse middle button * REVIEWED: GuiScrollPanel(), scroll speed proportional to content * REVIEWED: GuiDropdownBox(), support roll up and hidden arrow @@ -156,6 +161,8 @@ * REVIEWED: GuiIconText(), increase buffer size and reviewed padding * REVIEWED: GuiDrawText(), improved wrap mode drawing * REVIEWED: GuiScrollBar(), minor tweaks +* REVIEWED: GuiProgressBar(), improved borders computing +* REVIEWED: GuiTextBox(), multiple improvements: autocursor and more * REVIEWED: Functions descriptions, removed wrong return value reference * REDESIGNED: GuiColorPanel(), improved HSV <-> RGBA convertion * @@ -259,16 +266,16 @@ * 1.4 (15-Jun-2017) Rewritten all GUI functions (removed useless ones) * 1.3 (12-Jun-2017) Complete redesign of style system * 1.1 (01-Jun-2017) Complete review of the library -* 1.0 (07-Jun-2016) Converted to header-only by Ramon Santamaria. -* 0.9 (07-Mar-2016) Reviewed and tested by Albert Martos, Ian Eito, Sergio Martinez and Ramon Santamaria. -* 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria. +* 1.0 (07-Jun-2016) Converted to header-only by Ramon Santamaria +* 0.9 (07-Mar-2016) Reviewed and tested by Albert Martos, Ian Eito, Sergio Martinez and Ramon Santamaria +* 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria * * DEPENDENCIES: * raylib 5.0 - Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing * * STANDALONE MODE: * By default raygui depends on raylib mostly for the inputs and the drawing functionality but that dependency can be disabled -* with the config flag RAYGUI_STANDALONE. In that case is up to the user to provide another backend to cover library needs. +* with the config flag RAYGUI_STANDALONE. In that case is up to the user to provide another backend to cover library needs * * The following functions should be redefined for a custom backend: * @@ -309,7 +316,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2024 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2025 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -334,7 +341,7 @@ #define RAYGUI_VERSION_MAJOR 4 #define RAYGUI_VERSION_MINOR 5 #define RAYGUI_VERSION_PATCH 0 -#define RAYGUI_VERSION "4.5-dev" +#define RAYGUI_VERSION "5.0-dev" #if !defined(RAYGUI_STANDALONE) #include "raylib.h" @@ -358,17 +365,6 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -// Allow custom memory allocators -#ifndef RAYGUI_MALLOC - #define RAYGUI_MALLOC(sz) malloc(sz) -#endif -#ifndef RAYGUI_CALLOC - #define RAYGUI_CALLOC(n,sz) calloc(n,sz) -#endif -#ifndef RAYGUI_FREE - #define RAYGUI_FREE(p) free(p) -#endif - // Simple log system to avoid printf() calls if required // NOTE: Avoiding those calls, also avoids const strings memory usage #define RAYGUI_SUPPORT_LOG_INFO @@ -421,13 +417,16 @@ // TODO: Texture2D type is very coupled to raylib, required by Font type // It should be redesigned to be provided by user - typedef struct Texture2D { + typedef struct Texture { unsigned int id; // OpenGL texture id int width; // Texture base width int height; // Texture base height int mipmaps; // Mipmap levels, 1 by default int format; // Data format (PixelFormat type) - } Texture2D; + } Texture; + + // Texture2D, same as Texture + typedef Texture Texture2D; // Image, pixel data stored in CPU memory (RAM) typedef struct Image { @@ -527,7 +526,7 @@ typedef enum { DROPDOWNBOX, TEXTBOX, // Used also for: TEXTBOXMULTI VALUEBOX, - SPINNER, // Uses: BUTTON, VALUEBOX + CONTROL11, LISTVIEW, COLORPICKER, SCROLLBAR, @@ -549,12 +548,12 @@ typedef enum { BORDER_COLOR_DISABLED, // Control border color in STATE_DISABLED BASE_COLOR_DISABLED, // Control base color in STATE_DISABLED TEXT_COLOR_DISABLED, // Control text color in STATE_DISABLED - BORDER_WIDTH, // Control border size, 0 for no border + BORDER_WIDTH = 12, // Control border size, 0 for no border //TEXT_SIZE, // Control text size (glyphs max height) -> GLOBAL for all controls //TEXT_SPACING, // Control text spacing between glyphs -> GLOBAL for all controls - //TEXT_LINE_SPACING // Control text spacing between lines -> GLOBAL for all controls - TEXT_PADDING, // Control text padding, not considering border - TEXT_ALIGNMENT, // Control text horizontal alignment inside control text bound (after border and padding) + //TEXT_LINE_SPACING, // Control text spacing between lines -> GLOBAL for all controls + TEXT_PADDING = 13, // Control text padding, not considering border + TEXT_ALIGNMENT = 14, // Control text horizontal alignment inside control text bound (after border and padding) //TEXT_WRAP_MODE // Control text wrap-mode inside text bounds -> GLOBAL for all controls } GuiControlProperty; @@ -641,11 +640,14 @@ typedef enum { TEXT_READONLY = 16, // TextBox in read-only mode: 0-text editable, 1-text no-editable } GuiTextBoxProperty; -// Spinner +// ValueBox/Spinner typedef enum { - SPIN_BUTTON_WIDTH = 16, // Spinner left/right buttons width - SPIN_BUTTON_SPACING, // Spinner buttons separation -} GuiSpinnerProperty; + SPINNER_BUTTON_WIDTH = 16, // Spinner left/right buttons width + SPINNER_BUTTON_SPACING, // Spinner buttons separation +} GuiValueBoxProperty; + +// Control11 +//typedef enum { } GuiControl11Property; // ListView typedef enum { @@ -653,6 +655,7 @@ typedef enum { LIST_ITEMS_SPACING, // ListView items separation SCROLLBAR_WIDTH, // ListView scrollbar size (usually width) SCROLLBAR_SIDE, // ListView scrollbar side (0-SCROLLBAR_LEFT_SIDE, 1-SCROLLBAR_RIGHT_SIDE) + LIST_ITEMS_BORDER_NORMAL, // ListView items border enabled in normal state LIST_ITEMS_BORDER_WIDTH // ListView items border width } GuiListViewProperty; @@ -717,6 +720,9 @@ RAYGUIAPI char **GuiLoadIcons(const char *fileName, bool loadIconsName); // Load RAYGUIAPI void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color); // Draw icon using pixel size at specified position #endif +// Utility functions +RAYGUIAPI int GuiGetTextWidth(const char *text); // Get text width considering gui style and icon size (if required) + // Controls //---------------------------------------------------------------------------------------------------------- // Container/separator controls, useful for controls organization @@ -999,11 +1005,11 @@ typedef enum { ICON_MLAYERS = 226, ICON_MAPS = 227, ICON_HOT = 228, - ICON_229 = 229, - ICON_230 = 230, - ICON_231 = 231, - ICON_232 = 232, - ICON_233 = 233, + ICON_LABEL = 229, + ICON_NAME_ID = 230, + ICON_SLICING = 231, + ICON_MANUAL_CONTROL = 232, + ICON_COLLISION = 233, ICON_234 = 234, ICON_235 = 235, ICON_236 = 236, @@ -1046,12 +1052,24 @@ typedef enum { #if defined(RAYGUI_IMPLEMENTATION) #include // required for: isspace() [GuiTextBox()] -#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf(), vsprintf() [GuiLoadStyle(), GuiLoadIcons()] -#include // Required for: malloc(), calloc(), free() [GuiLoadStyle(), GuiLoadIcons()] +#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf(), snprintf(), vsprintf() [GuiLoadStyle(), GuiLoadIcons()] #include // Required for: strlen() [GuiTextBox(), GuiValueBox()], memset(), memcpy() #include // Required for: va_list, va_start(), vfprintf(), va_end() [TextFormat()] #include // Required for: roundf() [GuiColorPicker()] +// Allow custom memory allocators +#if defined(RAYGUI_MALLOC) || defined(RAYGUI_CALLOC) || defined(RAYGUI_FREE) + #if !defined(RAYGUI_MALLOC) || !defined(RAYGUI_CALLOC) || !defined(RAYGUI_FREE) + #error "RAYGUI: if RAYGUI_MALLOC, RAYGUI_CALLOC, or RAYGUI_FREE is customized, all three must be customized" + #endif +#else + #include // Required for: malloc(), calloc(), free() [GuiLoadStyle(), GuiLoadIcons()] + + #define RAYGUI_MALLOC(sz) malloc(sz) + #define RAYGUI_CALLOC(n,sz) calloc(n,sz) + #define RAYGUI_FREE(p) free(p) +#endif + #ifdef __cplusplus #define RAYGUI_CLITERAL(name) name #else @@ -1318,11 +1336,11 @@ static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x422a422a, 0x422e422a, 0x40384e28, 0x00007fe0, // ICON_MLAYERS 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x5b2a512a, 0x512e552a, 0x40385128, 0x00007fe0, // ICON_MAPS 0x04200000, 0x1cf00c60, 0x11f019f0, 0x0f3807b8, 0x1e3c0f3c, 0x1c1c1e1c, 0x1e3c1c1c, 0x00000f70, // ICON_HOT - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_229 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_230 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_231 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_232 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_233 + 0x00000000, 0x20803f00, 0x2a202e40, 0x20082e10, 0x08021004, 0x02040402, 0x00900108, 0x00000060, // ICON_LABEL + 0x00000000, 0x042007e0, 0x47e27c3e, 0x4ffa4002, 0x47fa4002, 0x4ffa4002, 0x7ffe4002, 0x00000000, // ICON_NAME_ID + 0x7fe00000, 0x402e4020, 0x43ce5e0a, 0x40504078, 0x438e4078, 0x402e5e0a, 0x7fe04020, 0x00000000, // ICON_SLICING + 0x00000000, 0x40027ffe, 0x47c24002, 0x55425d42, 0x55725542, 0x50125552, 0x10105016, 0x00001ff0, // ICON_MANUAL_CONTROL + 0x7ffe0000, 0x43c24002, 0x48124422, 0x500a500a, 0x500a500a, 0x44224812, 0x400243c2, 0x00007ffe, // ICON_COLLISION 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_234 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_235 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_236 @@ -1363,7 +1381,7 @@ static unsigned int *guiIconsPtr = guiIcons; #define RAYGUI_MAX_PROPS_EXTENDED 8 // Maximum number of extended properties //---------------------------------------------------------------------------------- -// Types and Structures Definition +// Module Types and Structures Definition //---------------------------------------------------------------------------------- // Gui control property style color element typedef enum { BORDER = 0, BASE, TEXT, OTHER } GuiPropertyElement; @@ -1387,8 +1405,7 @@ static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*() //static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking -static int autoCursorCooldownCounter = 0; // Cooldown frame counter for automatic cursor movement on key-down -static int autoCursorDelayCounter = 0; // Delay frame counter for automatic cursor movement +static int autoCursorCounter = 0; // Frame counter for automatic repeated cursor movement on key-down (cooldown and delay) //---------------------------------------------------------------------------------- // Style data array for all gui style properties (allocated on data segment by default) @@ -1484,7 +1501,6 @@ static void DrawRectangleGradientV(int posX, int posY, int width, int height, Co //---------------------------------------------------------------------------------- static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize); // Load style from memory (binary only) -static int GetTextWidth(const char *text); // Gui get text width using gui font and style static Rectangle GetTextBounds(int control, Rectangle bounds); // Get text bounds considering control bounds static const char *GetTextIcon(const char *text, int *iconId); // Get text icon if provided and move text cursor @@ -1589,6 +1605,10 @@ int GuiWindowBox(Rectangle bounds, const char *title) #define RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT 24 #endif + #if !defined(RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT) + #define RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT 18 + #endif + int result = 0; //GuiState state = guiState; @@ -1597,9 +1617,10 @@ int GuiWindowBox(Rectangle bounds, const char *title) Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)statusBarHeight }; if (bounds.height < statusBarHeight*2.0f) bounds.height = statusBarHeight*2.0f; + const float vPadding = statusBarHeight/2.0f - RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT/2.0f; Rectangle windowPanel = { bounds.x, bounds.y + (float)statusBarHeight - 1, bounds.width, bounds.height - (float)statusBarHeight + 1 }; - Rectangle closeButtonRec = { statusBar.x + statusBar.width - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - 20, - statusBar.y + statusBarHeight/2.0f - 18.0f/2.0f, 18, 18 }; + Rectangle closeButtonRec = { statusBar.x + statusBar.width - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT - vPadding, + statusBar.y + vPadding, RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT, RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT }; // Update control //-------------------------------------------------------------------- @@ -1653,7 +1674,7 @@ int GuiGroupBox(Rectangle bounds, const char *text) // Line control int GuiLine(Rectangle bounds, const char *text) { - #if !defined(RAYGUI_LINE_ORIGIN_SIZE) + #if !defined(RAYGUI_LINE_MARGIN_TEXT) #define RAYGUI_LINE_MARGIN_TEXT 12 #endif #if !defined(RAYGUI_LINE_TEXT_PADDING) @@ -1671,7 +1692,7 @@ int GuiLine(Rectangle bounds, const char *text) else { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.width = (float)GuiGetTextWidth(text) + 2; textBounds.height = bounds.height; textBounds.x = bounds.x + RAYGUI_LINE_MARGIN_TEXT; textBounds.y = bounds.y; @@ -1711,8 +1732,8 @@ int GuiPanel(Rectangle bounds, const char *text) //-------------------------------------------------------------------- if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar - GuiDrawRectangle(bounds, RAYGUI_PANEL_BORDER_WIDTH, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED: (int)LINE_COLOR)), - GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? BASE_COLOR_DISABLED : BACKGROUND_COLOR))); + GuiDrawRectangle(bounds, RAYGUI_PANEL_BORDER_WIDTH, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR)), + GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BASE_COLOR_DISABLED : (int)BACKGROUND_COLOR))); //-------------------------------------------------------------------- return result; @@ -2011,7 +2032,7 @@ int GuiLabelButton(Rectangle bounds, const char *text) bool pressed = false; // NOTE: We force bounds.width to be all text - float textWidth = (float)GetTextWidth(text); + float textWidth = (float)GuiGetTextWidth(text); if ((bounds.width - 2*GuiGetStyle(LABEL, BORDER_WIDTH) - 2*GuiGetStyle(LABEL, TEXT_PADDING)) < textWidth) bounds.width = textWidth + 2*GuiGetStyle(LABEL, BORDER_WIDTH) + 2*GuiGetStyle(LABEL, TEXT_PADDING) + 2; // Update control @@ -2149,7 +2170,9 @@ int GuiToggleSlider(Rectangle bounds, const char *text, int *active) // Get substrings items from text (items pointers) int itemCount = 0; - const char **items = GuiTextSplit(text, ';', &itemCount, NULL); + const char **items = NULL; + + if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); Rectangle slider = { 0, // Calculated later depending on the active toggle @@ -2196,7 +2219,7 @@ int GuiToggleSlider(Rectangle bounds, const char *text, int *active) if (text != NULL) { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(text); + textBounds.width = (float)GuiGetTextWidth(text); textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = slider.x + slider.width/2 - textBounds.width/2; textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; @@ -2221,7 +2244,7 @@ int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) if (text != NULL) { - textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.width = (float)GuiGetTextWidth(text) + 2; textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x + bounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; @@ -2474,7 +2497,7 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) { #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) - #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 40 // Frames to wait for autocursor movement + #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 20 // Frames to wait for autocursor movement #endif #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement @@ -2487,10 +2510,10 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); - int textLength = (int)strlen(text); // Get current text length + int textLength = (text != NULL)? (int)strlen(text) : 0; // Get current text length int thisCursorIndex = textBoxCursorIndex; if (thisCursorIndex > textLength) thisCursorIndex = textLength; - int textWidth = GetTextWidth(text) - GetTextWidth(text + thisCursorIndex); + int textWidth = GuiGetTextWidth(text) - GuiGetTextWidth(text + thisCursorIndex); int textIndexOffset = 0; // Text index offset to start drawing in the box // Cursor rectangle @@ -2511,15 +2534,6 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) mouseCursor.x = -1; mouseCursor.width = 1; - // Auto-cursor movement logic - // NOTE: Cursor moves automatically when key down after some time - if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCooldownCounter++; - else - { - autoCursorCooldownCounter = 0; // GLOBAL: Cursor cooldown counter - autoCursorDelayCounter = 0; // GLOBAL: Cursor delay counter - } - // Blink-cursor frame counter //if (!autoCursorMode) blinkCursorFrameCounter++; //else blinkCursorFrameCounter = 0; @@ -2537,6 +2551,13 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) if (editMode) { + // GLOBAL: Auto-cursor movement logic + // NOTE: Keystrokes are handled repeatedly when button is held down for some time + if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCounter++; + else autoCursorCounter = 0; + + bool autoCursorShouldTrigger = (autoCursorCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) && ((autoCursorCounter % RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0); + state = STATE_PRESSED; if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; @@ -2550,7 +2571,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) textIndexOffset += nextCodepointSize; - textWidth = GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex); + textWidth = GuiGetTextWidth(text + textIndexOffset) - GuiGetTextWidth(text + textBoxCursorIndex); } int codepoint = GetCharPressed(); // Get Unicode codepoint @@ -2560,10 +2581,43 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) int codepointSize = 0; const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); - // Add codepoint to text, at current cursor position - // NOTE: Make sure we do not overflow buffer size - if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) + // Handle text paste action + if (IsKeyPressed(KEY_V) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) { + const char *pasteText = GetClipboardText(); + if (pasteText != NULL) + { + int pasteLength = 0; + int pasteCodepoint; + int pasteCodepointSize; + + // Count how many codepoints to copy, stopping at the first unwanted control character + while (true) + { + pasteCodepoint = GetCodepointNext(pasteText + pasteLength, &pasteCodepointSize); + if (textLength + pasteLength + pasteCodepointSize >= textSize) break; + if (!(multiline && (pasteCodepoint == (int)'\n')) && !(pasteCodepoint >= 32)) break; + pasteLength += pasteCodepointSize; + } + + if (pasteLength > 0) + { + // Move forward data from cursor position + for (int i = textLength + pasteLength; i > textBoxCursorIndex; i--) text[i] = text[i - pasteLength]; + + // Paste data in at cursor + for (int i = 0; i < pasteLength; i++) text[textBoxCursorIndex + i] = pasteText[i]; + + textBoxCursorIndex += pasteLength; + textLength += pasteLength; + text[textLength] = '\0'; + } + } + } + else if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) + { + // Adding codepoint to text, at current cursor position + // Move forward data from cursor position for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; @@ -2583,113 +2637,185 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) // Move cursor to end if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength; - // Delete codepoint from text, after current cursor position - if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + // Delete related codepoints from text, after current cursor position + if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_DELETE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) { - autoCursorDelayCounter++; + int offset = textBoxCursorIndex; + int accCodepointSize = 0; + int nextCodepointSize; + int nextCodepoint; - if (IsKeyPressed(KEY_DELETE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + bool puctuation = ispunct(nextCodepoint & 0xff); + while (offset < textLength) { - int nextCodepointSize = 0; - GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); - - // Move backward text from cursor position - for (int i = textBoxCursorIndex; i < textLength; i++) text[i] = text[i + nextCodepointSize]; - - textLength -= codepointSize; - if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; - - // Make sure text last character is EOL - text[textLength] = '\0'; + if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) + break; + offset += nextCodepointSize; + accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); } + + // Check whitespace to delete (ASCII only) + while (offset < textLength) + { + if (!isspace(nextCodepoint & 0xff)) break; + + offset += nextCodepointSize; + accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + } + + // Move text after cursor forward (including final null terminator) + for (int i = offset; i <= textLength; i++) text[i - accCodepointSize] = text[i]; + + textLength -= accCodepointSize; + } + + else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && autoCursorShouldTrigger))) + { + // Delete single codepoint from text, after current cursor position + + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex + nextCodepointSize; i <= textLength; i++) text[i - nextCodepointSize] = text[i]; + + textLength -= nextCodepointSize; } // Delete related codepoints from text, before current cursor position - if ((textLength > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) { - int i = textBoxCursorIndex - 1; + int offset = textBoxCursorIndex; int accCodepointSize = 0; + int prevCodepointSize; + int prevCodepoint; - // Move cursor to the end of word if on space already - while ((i > 0) && isspace(text[i])) + // Check whitespace to delete (ASCII only) + while (offset > 0) { - int prevCodepointSize = 0; - GetCodepointPrevious(text + i, &prevCodepointSize); - i -= prevCodepointSize; + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if (!isspace(prevCodepoint & 0xff)) break; + + offset -= prevCodepointSize; + accCodepointSize += prevCodepointSize; + } + + // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + bool puctuation = ispunct(prevCodepoint & 0xff); + while (offset > 0) + { + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break; + + offset -= prevCodepointSize; accCodepointSize += prevCodepointSize; } - // Move cursor to the start of the word - while ((i > 0) && !isspace(text[i])) - { - int prevCodepointSize = 0; - GetCodepointPrevious(text + i, &prevCodepointSize); - i -= prevCodepointSize; - accCodepointSize += prevCodepointSize; - } + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - accCodepointSize] = text[i]; - // Move forward text from cursor position - for (int j = (textBoxCursorIndex - accCodepointSize); j < textLength; j++) text[j] = text[j + accCodepointSize]; - - // Prevent cursor index from decrementing past 0 - if (textBoxCursorIndex > 0) - { - textBoxCursorIndex -= accCodepointSize; - textLength -= accCodepointSize; - } - - // Make sure text last character is EOL - text[textLength] = '\0'; - } - else if ((textLength > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + textLength -= accCodepointSize; + textBoxCursorIndex -= accCodepointSize; + } + + else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && autoCursorShouldTrigger))) { - autoCursorDelayCounter++; + // Delete single codepoint from text, before current cursor position + + int prevCodepointSize = 0; - if (IsKeyPressed(KEY_BACKSPACE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames - { - int prevCodepointSize = 0; + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); - // Prevent cursor index from decrementing past 0 - if (textBoxCursorIndex > 0) - { - GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - prevCodepointSize] = text[i]; - // Move backward text from cursor position - for (int i = (textBoxCursorIndex - prevCodepointSize); i < textLength; i++) text[i] = text[i + prevCodepointSize]; - - textBoxCursorIndex -= codepointSize; - textLength -= codepointSize; - } - - // Make sure text last character is EOL - text[textLength] = '\0'; - } + textLength -= prevCodepointSize; + textBoxCursorIndex -= prevCodepointSize; } // Move cursor position with keys - if (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) + if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_LEFT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) { - autoCursorDelayCounter++; + int offset = textBoxCursorIndex; + //int accCodepointSize = 0; + int prevCodepointSize; + int prevCodepoint; - if (IsKeyPressed(KEY_LEFT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + // Check whitespace to skip (ASCII only) + while (offset > 0) { - int prevCodepointSize = 0; - if (textBoxCursorIndex > 0) GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if (!isspace(prevCodepoint & 0xff)) break; - if (textBoxCursorIndex >= prevCodepointSize) textBoxCursorIndex -= prevCodepointSize; + offset -= prevCodepointSize; + //accCodepointSize += prevCodepointSize; } + + // Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + bool puctuation = ispunct(prevCodepoint & 0xff); + while (offset > 0) + { + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break; + + offset -= prevCodepointSize; + //accCodepointSize += prevCodepointSize; + } + + textBoxCursorIndex = offset; } - else if (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) + else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && autoCursorShouldTrigger))) { - autoCursorDelayCounter++; + int prevCodepointSize = 0; + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); - if (IsKeyPressed(KEY_RIGHT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + textBoxCursorIndex -= prevCodepointSize; + } + else if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_RIGHT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + int offset = textBoxCursorIndex; + //int accCodepointSize = 0; + int nextCodepointSize; + int nextCodepoint; + + // Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + bool puctuation = ispunct(nextCodepoint & 0xff); + while (offset < textLength) { - int nextCodepointSize = 0; - GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) break; - if ((textBoxCursorIndex + nextCodepointSize) <= textLength) textBoxCursorIndex += nextCodepointSize; + offset += nextCodepointSize; + //accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); } + + // Check whitespace to skip (ASCII only) + while (offset < textLength) + { + if (!isspace(nextCodepoint & 0xff)) break; + + offset += nextCodepointSize; + //accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + } + + textBoxCursorIndex = offset; + } + else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && autoCursorShouldTrigger))) + { + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + textBoxCursorIndex += nextCodepointSize; } // Move cursor position with mouse @@ -2701,7 +2827,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) float widthToMouseX = 0; int mouseCursorIndex = 0; - for (int i = textIndexOffset; i < textLength; i++) + for (int i = textIndexOffset; i < textLength; i += codepointSize) { codepoint = GetCodepointNext(&text[i], &codepointSize); codepointIndex = GetGlyphIndex(guiFont, codepoint); @@ -2720,7 +2846,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) } // Check if mouse cursor is at the last position - int textEndWidth = GetTextWidth(text + textIndexOffset); + int textEndWidth = GuiGetTextWidth(text + textIndexOffset); if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2)) { mouseCursor.x = textBounds.x + textEndWidth; @@ -2737,7 +2863,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) else mouseCursor.x = -1; // Recalculate cursor position.y depending on textBoxCursorIndex - cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); + cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GuiGetTextWidth(text + textIndexOffset) - GuiGetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); //if (multiline) cursor.y = GetTextLines() // Finish text editing on ENTER or mouse click outside bounds @@ -2745,6 +2871,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) (!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) { textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index + autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes result = 1; } } @@ -2757,6 +2884,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text + autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes result = 1; } } @@ -2825,19 +2953,22 @@ int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int int tempValue = *value; - Rectangle spinner = { bounds.x + GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING), bounds.y, - bounds.width - 2*(GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING)), bounds.height }; - Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; - Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; + Rectangle valueBoxBounds = { + bounds.x + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH) + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_SPACING), + bounds.y, + bounds.width - 2*(GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH) + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_SPACING)), bounds.height }; + Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.height }; + Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.y, + (float)GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.height }; Rectangle textBounds = { 0 }; if (text != NULL) { - textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.width = (float)GuiGetTextWidth(text) + 2; textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(SPINNER, TEXT_PADDING); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - if (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SPINNER, TEXT_PADDING); + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); } // Update control @@ -2871,20 +3002,20 @@ int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int // Draw control //-------------------------------------------------------------------- - result = GuiValueBox(spinner, NULL, &tempValue, minValue, maxValue, editMode); + result = GuiValueBox(valueBoxBounds, NULL, &tempValue, minValue, maxValue, editMode); // Draw value selector custom buttons // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(SPINNER, BORDER_WIDTH)); + GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(VALUEBOX, BORDER_WIDTH)); GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); // Draw text label if provided - GuiDrawText(text, textBounds, (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); //-------------------------------------------------------------------- *value = tempValue; @@ -2903,12 +3034,12 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in GuiState state = guiState; char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; - sprintf(textValue, "%i", *value); + snprintf(textValue, RAYGUI_VALUEBOX_MAX_CHARS + 1, "%i", *value); Rectangle textBounds = { 0 }; if (text != NULL) { - textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.width = (float)GuiGetTextWidth(text) + 2; textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; @@ -2929,10 +3060,37 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in int keyCount = (int)strlen(textValue); + // Add or remove minus symbol + if (IsKeyPressed(KEY_MINUS)) + { + if (textValue[0] == '-') + { + for (int i = 0 ; i < keyCount; i++) textValue[i] = textValue[i + 1]; + + keyCount--; + valueHasChanged = true; + } + else if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS -1) + { + if (keyCount == 0) + { + textValue[0] = '0'; + textValue[1] = '\0'; + keyCount++; + } + + for (int i = keyCount ; i > -1; i--) textValue[i + 1] = textValue[i]; + + textValue[0] = '-'; + keyCount++; + valueHasChanged = true; + } + } + // Only allow keys in range [48..57] if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) { - if (GetTextWidth(textValue) < bounds.width) + if (GuiGetTextWidth(textValue) < bounds.width) { int key = GetCharPressed(); if ((key >= 48) && (key <= 57)) @@ -2992,11 +3150,14 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); - // Draw cursor + // Draw cursor rectangle if (editMode) { // NOTE: ValueBox internal text is always centered - Rectangle cursor = { bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH) }; + Rectangle cursor = { bounds.x + GuiGetTextWidth(textValue)/2 + bounds.width/2 + 1, + bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH) + 2, + 2, bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2 - 4 }; + if (cursor.height > bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); } @@ -3019,12 +3180,12 @@ int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float GuiState state = guiState; //char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; - //sprintf(textValue, "%2.2f", *value); + //snprintf(textValue, sizeof(textValue), "%2.2f", *value); - Rectangle textBounds = {0}; + Rectangle textBounds = { 0 }; if (text != NULL) { - textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.width = (float)GuiGetTextWidth(text) + 2; textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; @@ -3045,10 +3206,37 @@ int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float int keyCount = (int)strlen(textValue); + // Add or remove minus symbol + if (IsKeyPressed(KEY_MINUS)) + { + if (textValue[0] == '-') + { + for (int i = 0; i < keyCount; i++) textValue[i] = textValue[i + 1]; + + keyCount--; + valueHasChanged = true; + } + else if (keyCount < (RAYGUI_VALUEBOX_MAX_CHARS - 1)) + { + if (keyCount == 0) + { + textValue[0] = '0'; + textValue[1] = '\0'; + keyCount++; + } + + for (int i = keyCount; i > -1; i--) textValue[i + 1] = textValue[i]; + + textValue[0] = '-'; + keyCount++; + valueHasChanged = true; + } + } + // Only allow keys in range [48..57] if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) { - if (GetTextWidth(textValue) < bounds.width) + if (GuiGetTextWidth(textValue) < bounds.width) { int key = GetCharPressed(); if (((key >= 48) && (key <= 57)) || @@ -3103,7 +3291,7 @@ int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float if (editMode) { // NOTE: ValueBox internal text is always centered - Rectangle cursor = {bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, + Rectangle cursor = {bounds.x + GuiGetTextWidth(textValue)/2 + bounds.width/2 + 1, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH)}; GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); @@ -3120,7 +3308,7 @@ int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float // Slider control with pro parameters // NOTE: Other GuiSlider*() controls use this one -int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue, int sliderWidth) +int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) { int result = 0; GuiState state = guiState; @@ -3129,6 +3317,8 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, if (value == NULL) value = &temp; float oldValue = *value; + int sliderWidth = GuiGetStyle(SLIDER, SLIDER_WIDTH); + Rectangle slider = { bounds.x, bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), 0, bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; @@ -3146,7 +3336,7 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, { state = STATE_PRESSED; // Get equivalent value and slider position from mousePosition.x - *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width - sliderWidth)) + minValue; } } else @@ -3166,7 +3356,7 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, if (!CheckCollisionPointRec(mousePoint, slider)) { // Get equivalent value and slider position from mousePosition.x - *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width - sliderWidth)) + minValue; } } else state = STATE_FOCUSED; @@ -3205,44 +3395,45 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_FOCUSED))); else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_PRESSED))); + else if (state == STATE_DISABLED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_DISABLED))); // Draw left/right text if provided if (textLeft != NULL) { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textLeft); + textBounds.width = (float)GuiGetTextWidth(textLeft); textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SLIDER, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); } if (textRight != NULL) { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textRight); + textBounds.width = (float)GuiGetTextWidth(textRight); textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x + bounds.width + GuiGetStyle(SLIDER, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); } //-------------------------------------------------------------------- return result; } -// Slider control extended, returns selected value and has text -int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) -{ - return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, GuiGetStyle(SLIDER, SLIDER_WIDTH)); -} - // Slider Bar control extended, returns selected value int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) { - return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, 0); + int result = 0; + int preSliderWidth = GuiGetStyle(SLIDER, SLIDER_WIDTH); + GuiSetStyle(SLIDER, SLIDER_WIDTH, 0); + result = GuiSlider(bounds, textLeft, textRight, value, minValue, maxValue); + GuiSetStyle(SLIDER, SLIDER_WIDTH, preSliderWidth); + + return result; } // Progress Bar control extended, shows current progress value @@ -3257,14 +3448,14 @@ int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight // Progress bar Rectangle progress = { bounds.x + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) + GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING), 0, - bounds.height - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - 2*GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING) }; + bounds.height - GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - 2*GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING) -1 }; // Update control //-------------------------------------------------------------------- if (*value > maxValue) *value = maxValue; // WARNING: Working with floats could lead to rounding issues - if ((state != STATE_DISABLED)) progress.width = (float)(*value/(maxValue - minValue))*bounds.width - ((*value >= maxValue)? (float)(2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)) : 0.0f); + if ((state != STATE_DISABLED)) progress.width = ((float)*value/(maxValue - minValue))*(bounds.width - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)); //-------------------------------------------------------------------- // Draw control @@ -3282,15 +3473,15 @@ int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); } - else GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + else GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - if (*value >= maxValue) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + progress.width + 1, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + if (*value >= maxValue) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1}, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); else { // Draw borders not yet reached by value - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y + bounds.height - 1, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y + bounds.height - 1, bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); } // Draw slider internal progress bar (depends on state) @@ -3301,23 +3492,23 @@ int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight if (textLeft != NULL) { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textLeft); + textBounds.width = (float)GuiGetTextWidth(textLeft); textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x - textBounds.width - GuiGetStyle(PROGRESSBAR, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); } if (textRight != NULL) { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textRight); + textBounds.width = (float)GuiGetTextWidth(textRight); textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x + bounds.width + GuiGetStyle(PROGRESSBAR, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); } //-------------------------------------------------------------------- @@ -3467,11 +3658,11 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd // Draw visible items for (int i = 0; ((i < visibleItems) && (text != NULL)); i++) { - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_NORMAL)), BLANK); + if (GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_NORMAL)) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_NORMAL)), BLANK); if (state == STATE_DISABLED) { - if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); + if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED))); } @@ -3480,18 +3671,18 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd if (((startIndex + i) == itemSelected) && (active != NULL)) { // Draw item selected - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED))); + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED))); GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED))); } else if (((startIndex + i) == itemFocused)) // && (focus != NULL)) // NOTE: We want items focused, despite not returned! { // Draw item focused - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED))); + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED))); GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED))); } else { - // Draw item normal + // Draw item normal (no rectangle) GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL))); } } @@ -3531,22 +3722,22 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd return result; } -// Color Panel control - Color (RGBA) variant. +// Color Panel control - Color (RGBA) variant int GuiColorPanel(Rectangle bounds, const char *text, Color *color) { int result = 0; Vector3 vcolor = { (float)color->r/255.0f, (float)color->g/255.0f, (float)color->b/255.0f }; Vector3 hsv = ConvertRGBtoHSV(vcolor); - Vector3 prevHsv = hsv; // workaround to see if GuiColorPanelHSV modifies the hsv. + Vector3 prevHsv = hsv; // workaround to see if GuiColorPanelHSV modifies the hsv GuiColorPanelHSV(bounds, text, &hsv); - // Check if the hsv was changed, only then change the color. - // This is required, because the Color->HSV->Color conversion has precision errors. - // Thus the assignment from HSV to Color should only be made, if the HSV has a new user-entered value. - // Otherwise GuiColorPanel would often modify it's color without user input. - // TODO: GuiColorPanelHSV could return 1 if the slider was dragged, to simplify this check. + // Check if the hsv was changed, only then change the color + // This is required, because the Color->HSV->Color conversion has precision errors + // Thus the assignment from HSV to Color should only be made, if the HSV has a new user-entered value + // Otherwise GuiColorPanel would often modify it's color without user input + // TODO: GuiColorPanelHSV could return 1 if the slider was dragged, to simplify this check if (hsv.x != prevHsv.x || hsv.y != prevHsv.y || hsv.z != prevHsv.z) { Vector3 rgb = ConvertHSVtoRGB(hsv); @@ -3570,7 +3761,10 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) int result = 0; GuiState state = guiState; - Rectangle selector = { (float)bounds.x + (*alpha)*bounds.width - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.y - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT), (float)bounds.height + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2 }; + Rectangle selector = { (float)bounds.x + (*alpha)*bounds.width - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, + (float)bounds.y - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), + (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT), + (float)bounds.height + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2 }; // Update control //-------------------------------------------------------------------- @@ -3617,7 +3811,6 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) // Draw control //-------------------------------------------------------------------- - // Draw alpha bar: checked background if (state != STATE_DISABLED) { @@ -3755,7 +3948,7 @@ int GuiColorPicker(Rectangle bounds, const char *text, Color *color) Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; //Rectangle boundsAlpha = { bounds.x, bounds.y + bounds.height + GuiGetStyle(COLORPICKER, BARS_PADDING), bounds.width, GuiGetStyle(COLORPICKER, BARS_THICK) }; - // NOTE: this conversion can cause low hue-resolution, if the r, g and b value are very similar, which causes the hue bar to shift around when only the GuiColorPanel is used. + // NOTE: this conversion can cause low hue-resolution, if the r, g and b value are very similar, which causes the hue bar to shift around when only the GuiColorPanel is used Vector3 hsv = ConvertRGBtoHSV(RAYGUI_CLITERAL(Vector3){ (*color).r/255.0f, (*color).g/255.0f, (*color).b/255.0f }); GuiColorBarHue(boundsHue, NULL, &hsv.x); @@ -3768,8 +3961,8 @@ int GuiColorPicker(Rectangle bounds, const char *text, Color *color) return result; } -// Color Picker control that avoids conversion to RGB and back to HSV on each call, thus avoiding jittering. -// The user can call ConvertHSVtoRGB() to convert *colorHsv value to RGB. +// Color Picker control that avoids conversion to RGB and back to HSV on each call, thus avoiding jittering +// The user can call ConvertHSVtoRGB() to convert *colorHsv value to RGB // NOTE: It's divided in multiple controls: // int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) // int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) @@ -3917,7 +4110,7 @@ int GuiMessageBox(Rectangle bounds, const char *title, const char *message, cons buttonBounds.width = (bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; buttonBounds.height = RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; - //int textWidth = GetTextWidth(message) + 2; + //int textWidth = GuiGetTextWidth(message) + 2; Rectangle textBounds = { 0 }; textBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; @@ -3981,7 +4174,7 @@ int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, co Rectangle textBounds = { 0 }; if (message != NULL) { - int textSize = GetTextWidth(message) + 2; + int textSize = GuiGetTextWidth(message) + 2; textBounds.x = bounds.x + bounds.width/2 - textSize/2; textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + messageInputHeight/4 - (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/2; @@ -4221,7 +4414,7 @@ void GuiLoadStyle(const char *fileName) if (fileDataSize > 0) { - unsigned char *fileData = (unsigned char *)RAYGUI_MALLOC(fileDataSize*sizeof(unsigned char)); + unsigned char *fileData = (unsigned char *)RAYGUI_CALLOC(fileDataSize, sizeof(unsigned char)); fread(fileData, sizeof(unsigned char), fileDataSize, rgsFile); GuiLoadStyleFromMemory(fileData, fileDataSize); @@ -4283,8 +4476,6 @@ void GuiLoadStyleDefault(void) GuiSetStyle(TEXTBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); GuiSetStyle(VALUEBOX, TEXT_PADDING, 0); GuiSetStyle(VALUEBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - GuiSetStyle(SPINNER, TEXT_PADDING, 0); - GuiSetStyle(SPINNER, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); GuiSetStyle(STATUSBAR, TEXT_PADDING, 8); GuiSetStyle(STATUSBAR, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); @@ -4299,8 +4490,8 @@ void GuiLoadStyleDefault(void) GuiSetStyle(COMBOBOX, COMBO_BUTTON_SPACING, 2); GuiSetStyle(DROPDOWNBOX, ARROW_PADDING, 16); GuiSetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING, 2); - GuiSetStyle(SPINNER, SPIN_BUTTON_WIDTH, 24); - GuiSetStyle(SPINNER, SPIN_BUTTON_SPACING, 2); + GuiSetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH, 24); + GuiSetStyle(VALUEBOX, SPINNER_BUTTON_SPACING, 2); GuiSetStyle(SCROLLBAR, BORDER_WIDTH, 0); GuiSetStyle(SCROLLBAR, ARROWS_VISIBLE, 0); GuiSetStyle(SCROLLBAR, ARROWS_SIZE, 6); @@ -4310,6 +4501,7 @@ void GuiLoadStyleDefault(void) GuiSetStyle(SCROLLBAR, SCROLL_SPEED, 12); GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 28); GuiSetStyle(LISTVIEW, LIST_ITEMS_SPACING, 2); + GuiSetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH, 1); GuiSetStyle(LISTVIEW, SCROLLBAR_WIDTH, 12); GuiSetStyle(LISTVIEW, SCROLLBAR_SIDE, SCROLLBAR_RIGHT_SIDE); GuiSetStyle(COLORPICKER, COLOR_SELECTOR_SIZE, 8); @@ -4322,8 +4514,8 @@ void GuiLoadStyleDefault(void) { // Unload previous font texture UnloadTexture(guiFont.texture); - RL_FREE(guiFont.recs); - RL_FREE(guiFont.glyphs); + RAYGUI_FREE(guiFont.recs); + RAYGUI_FREE(guiFont.glyphs); guiFont.recs = NULL; guiFont.glyphs = NULL; @@ -4352,7 +4544,7 @@ const char *GuiIconText(int iconId, const char *text) if (text != NULL) { memset(buffer, 0, 1024); - sprintf(buffer, "#%03i#", iconId); + snprintf(buffer, 1024, "#%03i#", iconId); for (int i = 5; i < 1024; i++) { @@ -4364,7 +4556,7 @@ const char *GuiIconText(int iconId, const char *text) } else { - sprintf(iconBuffer, "#%03i#", iconId); + snprintf(iconBuffer, 16, "#%03i#", iconId); return iconBuffer; } @@ -4430,17 +4622,17 @@ char **GuiLoadIcons(const char *fileName, bool loadIconsName) { if (loadIconsName) { - guiIconsName = (char **)RAYGUI_MALLOC(iconCount*sizeof(char **)); + guiIconsName = (char **)RAYGUI_CALLOC(iconCount, sizeof(char *)); for (int i = 0; i < iconCount; i++) { - guiIconsName[i] = (char *)RAYGUI_MALLOC(RAYGUI_ICON_MAX_NAME_LENGTH); + guiIconsName[i] = (char *)RAYGUI_CALLOC(RAYGUI_ICON_MAX_NAME_LENGTH, sizeof(char)); fread(guiIconsName[i], 1, RAYGUI_ICON_MAX_NAME_LENGTH, rgiFile); } } else fseek(rgiFile, iconCount*RAYGUI_ICON_MAX_NAME_LENGTH, SEEK_CUR); // Read icons data directly over internal icons array - fread(guiIconsPtr, sizeof(unsigned int), iconCount*(iconSize*iconSize/32), rgiFile); + fread(guiIconsPtr, sizeof(unsigned int), (int)iconCount*((int)iconSize*(int)iconSize/32), rgiFile); } fclose(rgiFile); @@ -4449,6 +4641,56 @@ char **GuiLoadIcons(const char *fileName, bool loadIconsName) return guiIconsName; } +// Load icons from memory +// WARNING: Binary files only +char **GuiLoadIconsFromMemory(const unsigned char *fileData, int dataSize, bool loadIconsName) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + char **guiIconsName = NULL; + + char signature[5] = { 0 }; + short version = 0; + short reserved = 0; + short iconCount = 0; + short iconSize = 0; + + memcpy(signature, fileDataPtr, 4); + memcpy(&version, fileDataPtr + 4, sizeof(short)); + memcpy(&reserved, fileDataPtr + 4 + 2, sizeof(short)); + memcpy(&iconCount, fileDataPtr + 4 + 2 + 2, sizeof(short)); + memcpy(&iconSize, fileDataPtr + 4 + 2 + 2 + 2, sizeof(short)); + fileDataPtr += 12; + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'I') && + (signature[3] == ' ')) + { + if (loadIconsName) + { + guiIconsName = (char **)RAYGUI_CALLOC(iconCount, sizeof(char *)); + for (int i = 0; i < iconCount; i++) + { + guiIconsName[i] = (char *)RAYGUI_CALLOC(RAYGUI_ICON_MAX_NAME_LENGTH, sizeof(char)); + memcpy(guiIconsName[i], fileDataPtr, RAYGUI_ICON_MAX_NAME_LENGTH); + fileDataPtr += RAYGUI_ICON_MAX_NAME_LENGTH; + } + } + else + { + // Skip icon name data if not required + fileDataPtr += iconCount*RAYGUI_ICON_MAX_NAME_LENGTH; + } + + int iconDataSize = iconCount*((int)iconSize*(int)iconSize/32)*(int)sizeof(unsigned int); + guiIconsPtr = (unsigned int *)RAYGUI_CALLOC(iconDataSize, 1); + + memcpy(guiIconsPtr, fileDataPtr, iconDataSize); + } + + return guiIconsName; +} + // Draw selected icon using rectangles pixel-by-pixel void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color) { @@ -4476,12 +4718,73 @@ void GuiSetIconScale(int scale) if (scale >= 1) guiIconScale = scale; } +// Get text width considering gui style and icon size (if required) +int GuiGetTextWidth(const char *text) +{ + #if !defined(ICON_TEXT_PADDING) + #define ICON_TEXT_PADDING 4 + #endif + + Vector2 textSize = { 0 }; + int textIconOffset = 0; + + if ((text != NULL) && (text[0] != '\0')) + { + if (text[0] == '#') + { + for (int i = 1; (i < 5) && (text[i] != '\0'); i++) + { + if (text[i] == '#') + { + textIconOffset = i; + break; + } + } + } + + text += textIconOffset; + + // Make sure guiFont is set, GuiGetStyle() initializes it lazynessly + float fontSize = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + + // Custom MeasureText() implementation + if ((guiFont.texture.id > 0) && (text != NULL)) + { + // Get size in bytes of text, considering end of line and line break + int size = 0; + for (int i = 0; i < MAX_LINE_BUFFER_SIZE; i++) + { + if ((text[i] != '\0') && (text[i] != '\n')) size++; + else break; + } + + float scaleFactor = fontSize/(float)guiFont.baseSize; + textSize.y = (float)guiFont.baseSize*scaleFactor; + float glyphWidth = 0.0f; + + for (int i = 0, codepointSize = 0; i < size; i += codepointSize) + { + int codepoint = GetCodepointNext(&text[i], &codepointSize); + int codepointIndex = GetGlyphIndex(guiFont, codepoint); + + if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); + else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); + + textSize.x += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + } + + if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE + ICON_TEXT_PADDING); + } + + return (int)textSize.x; +} + #endif // !RAYGUI_NO_ICONS //---------------------------------------------------------------------------------- // Module Internal Functions Definition //---------------------------------------------------------------------------------- - // Load style from memory // WARNING: Binary files only static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) @@ -4567,7 +4870,7 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) { // Compressed font atlas image data (DEFLATE), it requires DecompressData() int dataUncompSize = 0; - unsigned char *compData = (unsigned char *)RAYGUI_MALLOC(fontImageCompSize); + unsigned char *compData = (unsigned char *)RAYGUI_CALLOC(fontImageCompSize, sizeof(unsigned char)); memcpy(compData, fileDataPtr, fontImageCompSize); fileDataPtr += fontImageCompSize; @@ -4581,7 +4884,7 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) else { // Font atlas image data is not compressed - imFont.data = (unsigned char *)RAYGUI_MALLOC(fontImageUncompSize); + imFont.data = (unsigned char *)RAYGUI_CALLOC(fontImageUncompSize, sizeof(unsigned char)); memcpy(imFont.data, fileDataPtr, fontImageUncompSize); fileDataPtr += fontImageUncompSize; } @@ -4609,7 +4912,7 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) if ((recsDataCompressedSize > 0) && (recsDataCompressedSize != recsDataSize)) { // Recs data is compressed, uncompress it - unsigned char *recsDataCompressed = (unsigned char *)RAYGUI_MALLOC(recsDataCompressedSize); + unsigned char *recsDataCompressed = (unsigned char *)RAYGUI_CALLOC(recsDataCompressedSize, sizeof(unsigned char)); memcpy(recsDataCompressed, fileDataPtr, recsDataCompressedSize); fileDataPtr += recsDataCompressedSize; @@ -4651,7 +4954,7 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) if ((glyphsDataCompressedSize > 0) && (glyphsDataCompressedSize != glyphsDataSize)) { // Glyphs data is compressed, uncompress it - unsigned char *glypsDataCompressed = (unsigned char *)RAYGUI_MALLOC(glyphsDataCompressedSize); + unsigned char *glypsDataCompressed = (unsigned char *)RAYGUI_CALLOC(glyphsDataCompressedSize, sizeof(unsigned char)); memcpy(glypsDataCompressed, fileDataPtr, glyphsDataCompressedSize); fileDataPtr += glyphsDataCompressedSize; @@ -4704,68 +5007,6 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) } } -// Gui get text width considering icon -static int GetTextWidth(const char *text) -{ - #if !defined(ICON_TEXT_PADDING) - #define ICON_TEXT_PADDING 4 - #endif - - Vector2 textSize = { 0 }; - int textIconOffset = 0; - - if ((text != NULL) && (text[0] != '\0')) - { - if (text[0] == '#') - { - for (int i = 1; (i < 5) && (text[i] != '\0'); i++) - { - if (text[i] == '#') - { - textIconOffset = i; - break; - } - } - } - - text += textIconOffset; - - // Make sure guiFont is set, GuiGetStyle() initializes it lazynessly - float fontSize = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - - // Custom MeasureText() implementation - if ((guiFont.texture.id > 0) && (text != NULL)) - { - // Get size in bytes of text, considering end of line and line break - int size = 0; - for (int i = 0; i < MAX_LINE_BUFFER_SIZE; i++) - { - if ((text[i] != '\0') && (text[i] != '\n')) size++; - else break; - } - - float scaleFactor = fontSize/(float)guiFont.baseSize; - textSize.y = (float)guiFont.baseSize*scaleFactor; - float glyphWidth = 0.0f; - - for (int i = 0, codepointSize = 0; i < size; i += codepointSize) - { - int codepoint = GetCodepointNext(&text[i], &codepointSize); - int codepointIndex = GetGlyphIndex(guiFont, codepoint); - - if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); - else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); - - textSize.x += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - } - } - - if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE + ICON_TEXT_PADDING); - } - - return (int)textSize.x; -} - // Get text bounds considering control bounds static Rectangle GetTextBounds(int control, Rectangle bounds) { @@ -4786,7 +5027,7 @@ static Rectangle GetTextBounds(int control, Rectangle bounds) case SLIDER: case CHECKBOX: case VALUEBOX: - case SPINNER: + case CONTROL11: // TODO: More special cases (label on side): SLIDER, CHECKBOX, VALUEBOX, SPINNER default: { @@ -4832,7 +5073,8 @@ static const char *GetTextIcon(const char *text, int *iconId) } // Get text divided into lines (by line-breaks '\n') -const char **GetTextLines(const char *text, int *count) +// WARNING: It returns pointers to new lines but it does not add NULL ('\0') terminator! +static const char **GetTextLines(const char *text, int *count) { #define RAYGUI_MAX_TEXT_LINES 128 @@ -4936,8 +5178,8 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C float textBoundsWidthOffset = 0.0f; // NOTE: We get text size after icon has been processed - // WARNING: GetTextWidth() also processes text icon to get width! -> Really needed? - int textSizeX = GetTextWidth(lines[i]); + // WARNING: GuiGetTextWidth() also processes text icon to get width! -> Really needed? + int textSizeX = GuiGetTextWidth(lines[i]); // If text requires an icon, add size to measure if (iconId >= 0) @@ -5000,7 +5242,7 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C float textOffsetX = 0.0f; float glyphWidth = 0; - int ellipsisWidth = GetTextWidth("..."); + int ellipsisWidth = GuiGetTextWidth("..."); bool textOverflow = false; for (int c = 0, codepointSize = 0; c < lineSize; c += codepointSize) { @@ -5144,13 +5386,13 @@ static void GuiTooltip(Rectangle controlRec) if ((controlRec.x + textSize.x + 16) > GetScreenWidth()) controlRec.x -= (textSize.x + 16 - controlRec.width); - GuiPanel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, NULL); + GuiPanel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.0f }, NULL); int textPadding = GuiGetStyle(LABEL, TEXT_PADDING); int textAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); GuiSetStyle(LABEL, TEXT_PADDING, 0); GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - GuiLabel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, guiTooltipPtr); + GuiLabel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.0f }, guiTooltipPtr); GuiSetStyle(LABEL, TEXT_ALIGNMENT, textAlignment); GuiSetStyle(LABEL, TEXT_PADDING, textPadding); } @@ -5204,7 +5446,7 @@ static const char **GuiTextSplit(const char *text, char delimiter, int *count, i buffer[i] = '\0'; // Set an end of string at this point counter++; - if (counter > RAYGUI_TEXTSPLIT_MAX_ITEMS) break; + if (counter >= RAYGUI_TEXTSPLIT_MAX_ITEMS) break; } } @@ -5526,10 +5768,10 @@ static Color GetColor(int hexValue) { Color color; - color.r = (unsigned char)(hexValue >> 24) & 0xFF; - color.g = (unsigned char)(hexValue >> 16) & 0xFF; - color.b = (unsigned char)(hexValue >> 8) & 0xFF; - color.a = (unsigned char)hexValue & 0xFF; + color.r = (unsigned char)(hexValue >> 24) & 0xff; + color.g = (unsigned char)(hexValue >> 16) & 0xff; + color.b = (unsigned char)(hexValue >> 8) & 0xff; + color.a = (unsigned char)hexValue & 0xff; return color; } @@ -5562,7 +5804,7 @@ static const char *TextFormat(const char *text, ...) va_list args; va_start(args, text); - vsprintf(buffer, text, args); + vsnprintf(buffer, RAYGUI_TEXTFORMAT_MAX_SIZE, text, args); va_end(args); return buffer; @@ -5731,7 +5973,7 @@ static int GetCodepointNext(const char *text, int *codepointSize) } else if (0xe0 == (0xf0 & ptr[0])) { - // 3 byte UTF-8 codepoint */ + // 3 byte UTF-8 codepoint if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]); *codepointSize = 3; From a66b6c998a97a9603dd13e428197effae265000a Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 5 Nov 2025 20:52:23 +0100 Subject: [PATCH 051/430] Update README.md --- examples/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/README.md b/examples/README.md index 28a2107e4..f1f6c80b3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -17,9 +17,9 @@ You may find it easier to use than other toolchains, especially when it comes to - `zig build [module]` to compile all examples for a module (e.g. `zig build core`) - `zig build [example]` to compile _and run_ a particular example (e.g. `zig build core_basic_window`) -## EXAMPLES COLLECTION [TOTAL: 192] +## EXAMPLES COLLECTION [TOTAL: 194] -### category: core [45] +### category: core [47] Examples using raylib[core](../src/rcore.c) platform functionality like window creation, inputs, drawing modes and system functionality. @@ -71,6 +71,7 @@ Examples using raylib[core](../src/rcore.c) platform functionality like window c | [core_screen_recording](core/core_screen_recording.c) | core_screen_recording | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | | [core_clipboard_text](core/core_clipboard_text.c) | core_clipboard_text | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Robin](https://github.com/RobinsAviary) | | [core_text_file_loading](core/core_text_file_loading.c) | core_text_file_loading | ⭐☆☆☆ | 5.5 | 5.6 | [Aanjishnu Bhattacharyya](https://github.com/NimComPoo-04) | +| [core_compute_hash](core/core_compute_hash.c) | core_compute_hash | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | ### category: shapes [34] From eacbc8bd616169c558bfbcfa918ceaa19dbd4cee Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 5 Nov 2025 20:52:45 +0100 Subject: [PATCH 052/430] Update examples_report.md --- tools/rexm/examples_report.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/rexm/examples_report.md b/tools/rexm/examples_report.md index 7734a234c..24977e38e 100644 --- a/tools/rexm/examples_report.md +++ b/tools/rexm/examples_report.md @@ -59,12 +59,14 @@ Example elements validated: | core_high_dpi | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_render_texture | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_undo_redo | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_viewport_scaling | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_input_actions | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_directory_files | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_highdpi_testbed | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_screen_recording | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_clipboard_text | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_text_file_loading | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_compute_hash | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_basic_shapes | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_bouncing_ball | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_bullet_hell | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | From af57c8854f14511a85a4c7c30e877a84f8a174c3 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 5 Nov 2025 20:53:14 +0100 Subject: [PATCH 053/430] REXM: ADDED: `core_compute_hash` --- examples/Makefile | 1 + examples/Makefile.Web | 7 + examples/core/core_compute_hash.c | 143 +++++ examples/core/core_compute_hash.png | Bin 0 -> 19367 bytes examples/examples_list.txt | 1 + .../VS2022/examples/core_compute_hash.vcxproj | 569 ++++++++++++++++++ projects/VS2022/raylib.sln | 29 +- projects/VS2022/raylib/raylib.vcxproj | 9 +- 8 files changed, 754 insertions(+), 5 deletions(-) create mode 100644 examples/core/core_compute_hash.c create mode 100644 examples/core/core_compute_hash.png create mode 100644 projects/VS2022/examples/core_compute_hash.vcxproj diff --git a/examples/Makefile b/examples/Makefile index 6fcf218ee..729459de4 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -525,6 +525,7 @@ CORE = \ core/core_basic_screen_manager \ core/core_basic_window \ core/core_clipboard_text \ + core/core_compute_hash \ core/core_custom_frame_control \ core/core_custom_logging \ core/core_delta_time \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 3ac776435..d2336e7df 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -513,6 +513,7 @@ CORE = \ core/core_basic_screen_manager \ core/core_basic_window \ core/core_clipboard_text \ + core/core_compute_hash \ core/core_custom_frame_control \ core/core_custom_logging \ core/core_delta_time \ @@ -753,6 +754,9 @@ core/core_basic_window: core/core_basic_window.c core/core_clipboard_text: core/core_clipboard_text.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) +core/core_compute_hash: core/core_compute_hash.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + core/core_custom_frame_control: core/core_custom_frame_control.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) @@ -834,6 +838,9 @@ core/core_text_file_loading: core/core_text_file_loading.c core/core_undo_redo: core/core_undo_redo.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) +core/core_viewport_scaling: core/core_viewport_scaling.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + core/core_vr_simulator: core/core_vr_simulator.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file core/resources/shaders/glsl100/distortion.fs@resources/shaders/glsl100/distortion.fs diff --git a/examples/core/core_compute_hash.c b/examples/core/core_compute_hash.c new file mode 100644 index 000000000..376e2d65c --- /dev/null +++ b/examples/core/core_compute_hash.c @@ -0,0 +1,143 @@ +/******************************************************************************************* +* +* raylib [core] example - compute hash +* +* Example complexity rating: [★★☆☆] 2/4 +* +* Example originally created with raylib 5.6-dev, last time updated with raylib 5.6-dev +* +* 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) 2025 Ramon Santamaria (@raysan5) +* +********************************************************************************************/ + +#include "raylib.h" + +#define RAYGUI_IMPLEMENTATION +#include "raygui.h" + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +static char *GetDataAsHexText(const unsigned int *data, int dataSize); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [core] example - compute hash"); + + // UI controls variables + char textInput[96] = "The quick brown fox jumps over the lazy dog."; + bool textBoxEditMode = false; + bool btnComputeHashes = false; + + // Data hash values + unsigned int hashCRC32 = 0; + unsigned int *hashMD5 = NULL; + unsigned int *hashSHA1 = NULL; + unsigned int *hashSHA256 = NULL; + + // Base64 encoded data + char *base64Text = NULL; + int base64TextSize = 0; + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + if (btnComputeHashes) + { + int textInputLen = strlen(textInput); + + // Encode data to Base64 string (includes NULL terminator), memory must be MemFree() + base64Text = EncodeDataBase64((unsigned char *)textInput, textInputLen, &base64TextSize); + + hashCRC32 = ComputeCRC32((unsigned char *)textInput, textInputLen); // Compute CRC32 hash code (4 bytes) + hashMD5 = ComputeMD5((unsigned char *)textInput, textInputLen); // Compute MD5 hash code, returns static int[4] (16 bytes) + hashSHA1 = ComputeSHA1((unsigned char *)textInput, textInputLen); // Compute SHA1 hash code, returns static int[5] (20 bytes) + hashSHA256 = ComputeSHA256((unsigned char *)textInput, textInputLen); // Compute SHA256 hash code, returns static int[8] (32 bytes) + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + GuiSetStyle(DEFAULT, TEXT_SIZE, 20); + GuiSetStyle(DEFAULT, TEXT_SPACING, 2); + GuiLabel((Rectangle){ 40, 26, 720, 32 }, "INPUT DATA (TEXT):"); + GuiSetStyle(DEFAULT, TEXT_SPACING, 1); + GuiSetStyle(DEFAULT, TEXT_SIZE, 10); + + if (GuiTextBox((Rectangle){ 40, 64, 720, 32 }, textInput, 95, textBoxEditMode)) textBoxEditMode = !textBoxEditMode; + + btnComputeHashes = GuiButton((Rectangle){ 40, 64 + 40, 720, 32 }, "COMPUTE INPUT DATA HASHES"); + + GuiSetStyle(DEFAULT, TEXT_SIZE, 20); + GuiSetStyle(DEFAULT, TEXT_SPACING, 2); + GuiLabel((Rectangle){ 40, 160, 720, 32 }, "INPUT DATA HASH VALUES:"); + GuiSetStyle(DEFAULT, TEXT_SPACING, 1); + GuiSetStyle(DEFAULT, TEXT_SIZE, 10); + + GuiSetStyle(TEXTBOX, TEXT_READONLY, 1); + GuiLabel((Rectangle){ 40, 200, 120, 32 }, "CRC32 [32 bit]:"); + GuiTextBox((Rectangle){ 40 + 120, 200, 720 - 120, 32 }, GetDataAsHexText(&hashCRC32, 1), 120, false); + GuiLabel((Rectangle){ 40, 200 + 36, 120, 32 }, "MD5 [128 bit]:"); + GuiTextBox((Rectangle){ 40 + 120, 200 + 36, 720 - 120, 32 }, GetDataAsHexText(hashMD5, 4), 120, false); + GuiLabel((Rectangle){ 40, 200 + 36*2, 120, 32 }, "SHA1 [160 bit]:"); + GuiTextBox((Rectangle){ 40 + 120, 200 + 36*2, 720 - 120, 32 }, GetDataAsHexText(hashSHA1, 5), 120, false); + GuiLabel((Rectangle){ 40, 200 + 36*3, 120, 32 }, "SHA256 [256 bit]:"); + GuiTextBox((Rectangle){ 40 + 120, 200 + 36*3, 720 - 120, 32 }, GetDataAsHexText(hashSHA256, 8), 120, false); + + GuiSetState(STATE_FOCUSED); + GuiLabel((Rectangle){ 40, 200 + 36*5 - 30, 320, 32 }, "BONUS - BAS64 ENCODED STRING:"); + GuiSetState(STATE_NORMAL); + GuiLabel((Rectangle){ 40, 200 + 36*5, 120, 32 }, "BASE64 ENCODING:"); + GuiTextBox((Rectangle){ 40 + 120, 200 + 36*5, 720 - 120, 32 }, base64Text, 120, false); + GuiSetStyle(TEXTBOX, TEXT_READONLY, 0); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + MemFree(base64Text); // Free Base64 text data + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +static char *GetDataAsHexText(const unsigned int *data, int dataSize) +{ + static char text[128] = { 0 }; + memset(text, 0, 128); + + if ((data != NULL) && (dataSize > 0) && (dataSize < ((128/8) - 1))) + { + for (int i = 0; i < dataSize; i++) TextCopy(text + i*8, TextFormat("%08X", data[i])); + } + else TextCopy(text, "00000000"); + + return text; +} diff --git a/examples/core/core_compute_hash.png b/examples/core/core_compute_hash.png new file mode 100644 index 0000000000000000000000000000000000000000..19a5d0b1cff66831f997ce43d809b1256d550f9c GIT binary patch literal 19367 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 V{zjPu@CRI=ZH5+GFJSNy{|_IH5&r-H literal 0 HcmV?d00001 diff --git a/examples/examples_list.txt b/examples/examples_list.txt index f22a0892f..1804d7edc 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -53,6 +53,7 @@ core;core_highdpi_testbed;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamar core;core_screen_recording;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 core;core_clipboard_text;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Robin";@RobinsAviary core;core_text_file_loading;★☆☆☆;5.5;5.6;0;0;"Aanjishnu Bhattacharyya";@NimComPoo-04 +core;core_compute_hash;★★☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 shapes;shapes_basic_shapes;★☆☆☆;1.0;4.2;2014;2025;"Ramon Santamaria";@raysan5 shapes;shapes_bouncing_ball;★☆☆☆;2.5;2.5;2013;2025;"Ramon Santamaria";@raysan5 shapes;shapes_bullet_hell;★☆☆☆;5.6;5.6;2025;2025;"Zero";@zerohorsepower diff --git a/projects/VS2022/examples/core_compute_hash.vcxproj b/projects/VS2022/examples/core_compute_hash.vcxproj new file mode 100644 index 000000000..bbca36d86 --- /dev/null +++ b/projects/VS2022/examples/core_compute_hash.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {6C897101-BE52-4387-8AA2-062123A76BA1} + Win32Proj + core_compute_hash + 10.0 + core_compute_hash + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index 8ace665a9..34c696ffe 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -407,6 +407,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_lines_drawing", "exa EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_viewport_scaling", "examples\core_viewport_scaling.vcxproj", "{AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_compute_hash", "examples\core_compute_hash.vcxproj", "{6C897101-BE52-4387-8AA2-062123A76BA1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug.DLL|ARM64 = Debug.DLL|ARM64 @@ -5053,6 +5055,30 @@ Global {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Release|x64.Build.0 = Release|x64 {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Release|x86.ActiveCfg = Release|Win32 {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91}.Release|x86.Build.0 = Release|Win32 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Debug|ARM64.Build.0 = Debug|ARM64 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Debug|x64.ActiveCfg = Debug|x64 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Debug|x64.Build.0 = Debug|x64 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Debug|x86.ActiveCfg = Debug|Win32 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Debug|x86.Build.0 = Debug|Win32 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Release|ARM64.ActiveCfg = Release|ARM64 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Release|ARM64.Build.0 = Release|ARM64 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Release|x64.ActiveCfg = Release|x64 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Release|x64.Build.0 = Release|x64 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Release|x86.ActiveCfg = Release|Win32 + {6C897101-BE52-4387-8AA2-062123A76BA1}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5220,7 +5246,7 @@ Global {C54703BF-D68A-480D-BE27-49B62E45D582} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {9CD8BCAD-F212-4BCC-BA98-899743CE3279} = {CC132A4D-D081-4C26-BFB9-AB11984054F8} {0981CA28-E4A5-4DF1-987F-A41D09131EFC} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} - {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {278D8859-20B1-428F-8448-064F46E1F021} + {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {6BFF72EA-7362-4A3B-B6E5-9A3655BBBDA3} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {6777EC3C-077C-42FC-B4AD-B799CE55CCE4} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A} {A61DAD9C-271C-4E95-81AA-DB4CD58564D4} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} @@ -5258,6 +5284,7 @@ Global {028F0967-B253-45DA-B1C4-FACCE45D0D8D} = {AF5BEC5C-1F2B-4DA8-B12D-D09FE569237C} {666346D7-C84B-498D-AE17-53B20C62DB1A} = {278D8859-20B1-428F-8448-064F46E1F021} {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} + {6C897101-BE52-4387-8AA2-062123A76BA1} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} diff --git a/projects/VS2022/raylib/raylib.vcxproj b/projects/VS2022/raylib/raylib.vcxproj index 7721a669a..cf254761e 100644 --- a/projects/VS2022/raylib/raylib.vcxproj +++ b/projects/VS2022/raylib/raylib.vcxproj @@ -242,7 +242,7 @@ Level3 Disabled - _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions);GRAPHICS_API_OPENGL_33;PLATFORM_DESKTOP + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;WIN32;_DEBUG;_LIB;GRAPHICS_API_OPENGL_33;PLATFORM_DESKTOP;%(PreprocessorDefinitions) CompileAsC $(ProjectDir)..\..\..\src\external\glfw\include @@ -295,7 +295,7 @@ Level3 Disabled - _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions);GRAPHICS_API_OPENGL_33;PLATFORM_DESKTOP;BUILD_LIBTYPE_SHARED + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;WIN32;_DEBUG;_LIB;GRAPHICS_API_OPENGL_33;PLATFORM_DESKTOP;BUILD_LIBTYPE_SHARED;%(PreprocessorDefinitions) CompileAsC $(ProjectDir)..\..\..\src\external\glfw\include MultiThreadedDebug @@ -353,10 +353,11 @@ MaxSpeed true true - _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions);GRAPHICS_API_OPENGL_33;PLATFORM_DESKTOP + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;WIN32;NDEBUG;_LIB;GRAPHICS_API_OPENGL_33;PLATFORM_DESKTOP;%(PreprocessorDefinitions) $(ProjectDir)..\..\..\src\external\glfw\include CompileAsC + AdvancedVectorExtensions2 Windows @@ -412,7 +413,7 @@ MaxSpeed true true - _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions);GRAPHICS_API_OPENGL_33;PLATFORM_DESKTOP;BUILD_LIBTYPE_SHARED + _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;WIN32;NDEBUG;_LIB;GRAPHICS_API_OPENGL_33;PLATFORM_DESKTOP;BUILD_LIBTYPE_SHARED;%(PreprocessorDefinitions) $(ProjectDir)..\..\..\src\external\glfw\include CompileAsC MultiThreaded From ad17af57e928c61e8e29d2f6c74962c249adf36d Mon Sep 17 00:00:00 2001 From: JohnnyCena123 Date: Fri, 7 Nov 2025 11:13:27 +0200 Subject: [PATCH 054/430] [ignore][parser] properly ignore built rlparser executable (#5337) * [ignore][parser] properly ignore built rlparser executable * remove actual executable --- .gitignore | 3 ++- tools/rlparser/rlparser | Bin 43144 -> 0 bytes 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 100755 tools/rlparser/rlparser diff --git a/.gitignore b/.gitignore index fa4c4c49d..ddbc5c11d 100644 --- a/.gitignore +++ b/.gitignore @@ -115,7 +115,8 @@ build-*/ docgen_tmp/ # Tools stuff -tools/parser/raylib_parser +tools/parser/rlparser.exe +tools/parser/rlparser tools/rexm/rexm.exe tools/rexm/rexm diff --git a/tools/rlparser/rlparser b/tools/rlparser/rlparser deleted file mode 100755 index b68032c023debdd4b5e656b794864b81bd2e123c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43144 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 From 4ff296bf0ba9d1c2685392085f47016d05996696 Mon Sep 17 00:00:00 2001 From: Le Juez Victor <90587919+Bigfoot71@users.noreply.github.com> Date: Sat, 8 Nov 2025 11:28:15 +0100 Subject: [PATCH 055/430] fix clipping issue (#5342) --- src/external/rlsw.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index 15ab89d4e..db5a8189d 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -2118,12 +2118,12 @@ static inline int sw_clip_##name( // Frustum cliping functions //------------------------------------------------------------------------------------------- #define IS_INSIDE_PLANE_W(h) ((h)[3] >= SW_CLIP_EPSILON) -#define IS_INSIDE_PLANE_X_POS(h) ((h)[0] <= (h)[3]) -#define IS_INSIDE_PLANE_X_NEG(h) (-(h)[0] <= (h)[3]) -#define IS_INSIDE_PLANE_Y_POS(h) ((h)[1] <= (h)[3]) -#define IS_INSIDE_PLANE_Y_NEG(h) (-(h)[1] <= (h)[3]) -#define IS_INSIDE_PLANE_Z_POS(h) ((h)[2] <= (h)[3]) -#define IS_INSIDE_PLANE_Z_NEG(h) (-(h)[2] <= (h)[3]) +#define IS_INSIDE_PLANE_X_POS(h) ( (h)[0] < (h)[3]) // Exclusive for +X +#define IS_INSIDE_PLANE_X_NEG(h) (-(h)[0] < (h)[3]) // Exclusive for -X +#define IS_INSIDE_PLANE_Y_POS(h) ( (h)[1] < (h)[3]) // Exclusive for +Y +#define IS_INSIDE_PLANE_Y_NEG(h) (-(h)[1] < (h)[3]) // Exclusive for -Y +#define IS_INSIDE_PLANE_Z_POS(h) ( (h)[2] <= (h)[3]) // Inclusive for +Z +#define IS_INSIDE_PLANE_Z_NEG(h) (-(h)[2] <= (h)[3]) // Inclusive for -Z #define COMPUTE_T_PLANE_W(hPrev, hCurr) ((SW_CLIP_EPSILON - (hPrev)[3])/((hCurr)[3] - (hPrev)[3])) #define COMPUTE_T_PLANE_X_POS(hPrev, hCurr) (((hPrev)[3] - (hPrev)[0])/(((hPrev)[3] - (hPrev)[0]) - ((hCurr)[3] - (hCurr)[0]))) From d8da443604cfc0cdcd5dde6251549e298f3e3dc3 Mon Sep 17 00:00:00 2001 From: Chakradhar Date: Sat, 8 Nov 2025 15:59:45 +0530 Subject: [PATCH 056/430] Fixed core_text_file_loading example in raylib examples, to account for blank lines in text file and text wrapping properly for the case when the last word goes out the display (#5339) --- examples/core/core_text_file_loading.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/examples/core/core_text_file_loading.c b/examples/core/core_text_file_loading.c index ed48f7d36..852cc32c4 100644 --- a/examples/core/core_text_file_loading.c +++ b/examples/core/core_text_file_loading.c @@ -19,6 +19,8 @@ #include "raymath.h" // Required for: Lerp() +#include + //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ @@ -59,10 +61,11 @@ int main(void) int lastSpace = 0; // Keeping track of last valid space to insert '\n' int lastWrapStart = 0; // Keeping track of the start of this wrapped line. - while (lines[i][j] != '\0') + while (j <= strlen(lines[i])) { - if (lines[i][j] == ' ') + if (lines[i][j] == ' ' || lines[i][j] == '\0') { + char before = lines[i][j]; // Making a C Style string by adding a '\0' at the required location so that we can use the MeasureText function lines[i][j] = '\0'; @@ -75,7 +78,7 @@ int main(void) lastWrapStart = lastSpace + 1; } - lines[i][j] = ' '; // Resetting the space back + if(before != '\0') lines[i][j] = ' '; // Resetting the space back lastSpace = j; // Since we encountered a new space we update our last encountered space location } @@ -92,7 +95,7 @@ int main(void) textHeight += (int)size.y + 10; } - // A simple scrollbar on the side to show how far we have red into the file + // A simple scrollbar on the side to show how far we have read into the file Rectangle scrollBar = { .x = (float)screenWidth - 5, .y = 0, @@ -132,7 +135,13 @@ int main(void) for (int i = 0, t = textTop; i < lineCount; i++) { // Each time we go through and calculate the height of the text to move the cursor appropriately - Vector2 size = MeasureTextEx(GetFontDefault(), lines[i], (float)fontSize, 2); + Vector2 size; + if(strcmp(lines[i], "")){ + // Fix for empty line in the text file + size = MeasureTextEx( GetFontDefault(), lines[i], (float)fontSize, 2); + }else{ + size = MeasureTextEx( GetFontDefault(), " ", (float)fontSize, 2); + } DrawText(lines[i], 10, t, fontSize, RED); From 8b3ea995f91742fffae313e9b22aef6f080bec3f Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 8 Nov 2025 11:32:58 +0100 Subject: [PATCH 057/430] Update parse_api.yml --- .github/workflows/parse_api.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/parse_api.yml b/.github/workflows/parse_api.yml index e095e4a55..c6aa59bfe 100644 --- a/.github/workflows/parse_api.yml +++ b/.github/workflows/parse_api.yml @@ -32,6 +32,6 @@ jobs: set -x git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add tools/rlparser + git add tools/rlparser/output git commit -m "rlparser: update raylib_api.* by CI" git push From 2a324ace277ed2b510678457c1d6d837be1c9231 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 8 Nov 2025 11:35:34 +0100 Subject: [PATCH 058/430] Update parse_api.yml --- .github/workflows/parse_api.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/parse_api.yml b/.github/workflows/parse_api.yml index c6aa59bfe..e25cde18b 100644 --- a/.github/workflows/parse_api.yml +++ b/.github/workflows/parse_api.yml @@ -22,7 +22,7 @@ jobs: - name: Diff parse files id: diff run: | - git add -N tools/rlparser + git add -N tools/rlparser/output git diff --name-only --exit-code continue-on-error: true From d7a7eda959e6dff41274e06c3b2621f4e40e5963 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 8 Nov 2025 11:36:42 +0100 Subject: [PATCH 059/430] [examples] `core_directory_files` fixes (#5343) * [examples] reset on folder click `continue` after clicking a new folder * [examples] don't make non-directories clickable `IsPathFile` is not enough to check if it's a directory since it also takes in char devices. * rlparser: update raylib_api.* by CI * Delete tools/rlparser/rlparser --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Ray --- examples/core/core_directory_files.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/core/core_directory_files.c b/examples/core/core_directory_files.c index b7e11b686..83a4239d0 100644 --- a/examples/core/core_directory_files.c +++ b/examples/core/core_directory_files.c @@ -69,18 +69,19 @@ int main(void) DrawText(directory, 100, 40, 20, DARKGRAY); btnBackPressed = GuiButton((Rectangle){ 40.0f, 40.0f, 20, 20 }, "<"); - + for (int i = 0; i < (int)files.count; i++) { Color color = Fade(LIGHTGRAY, 0.3f); - if (!IsPathFile(files.paths[i])) + if (!IsPathFile(files.paths[i]) && DirectoryExists(files.paths[i])) { if (GuiButton((Rectangle){0.0f, 85.0f + 40.0f*(float)i, screenWidth, 40}, "")) { strcpy(directory, files.paths[i]); UnloadDirectoryFiles(files); files = LoadDirectoryFiles(directory); + continue; } } From 0b4815b8fe861f8fbeac35f46f7e1ff78891b7b5 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 9 Nov 2025 13:43:08 +0100 Subject: [PATCH 060/430] WARNING: REMOVED: GIT recording option, added example --- examples/README.md | 2 +- examples/core/core_screen_recording.c | 118 ++++++++++++++++++++-- examples/core/core_screen_recording.png | Bin 17323 -> 17353 bytes {src/external => examples/core}/msf_gif.h | 0 examples/examples_list.txt | 2 +- src/config.h | 2 - src/raylib.h | 1 - src/rcore.c | 102 +------------------ tools/rexm/examples_report.md | 2 +- tools/rexm/examples_report_issues.md | 1 - 10 files changed, 112 insertions(+), 118 deletions(-) rename {src/external => examples/core}/msf_gif.h (100%) diff --git a/examples/README.md b/examples/README.md index f1f6c80b3..9a208f599 100644 --- a/examples/README.md +++ b/examples/README.md @@ -68,7 +68,7 @@ Examples using raylib[core](../src/rcore.c) platform functionality like window c | [core_input_actions](core/core_input_actions.c) | core_input_actions | ⭐⭐☆☆ | 5.5 | 5.6 | [Jett](https://github.com/JettMonstersGoBoom) | | [core_directory_files](core/core_directory_files.c) | core_directory_files | ⭐☆☆☆ | 5.5 | 5.6 | [Hugo ARNAL](https://github.com/hugoarnal) | | [core_highdpi_testbed](core/core_highdpi_testbed.c) | core_highdpi_testbed | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | -| [core_screen_recording](core/core_screen_recording.c) | core_screen_recording | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | +| [core_screen_recording](core/core_screen_recording.c) | core_screen_recording | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | | [core_clipboard_text](core/core_clipboard_text.c) | core_clipboard_text | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Robin](https://github.com/RobinsAviary) | | [core_text_file_loading](core/core_text_file_loading.c) | core_text_file_loading | ⭐☆☆☆ | 5.5 | 5.6 | [Aanjishnu Bhattacharyya](https://github.com/NimComPoo-04) | | [core_compute_hash](core/core_compute_hash.c) | core_compute_hash | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | diff --git a/examples/core/core_screen_recording.c b/examples/core/core_screen_recording.c index 43c90eec5..633ddcf81 100644 --- a/examples/core/core_screen_recording.c +++ b/examples/core/core_screen_recording.c @@ -2,12 +2,10 @@ * * raylib [core] example - screen recording * -* Example complexity rating: [★☆☆☆] 1/4 +* Example complexity rating: [★★☆☆] 2/4 * * Example originally created with raylib 5.6-dev, last time updated with raylib 5.6-dev * -* Example contributed by Ramon Santamaria (@raysan5) 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 * @@ -17,6 +15,16 @@ #include "raylib.h" +// Using msf_gif library to record frames into GIF +#define MSF_GIF_IMPL +#include "msf_gif.h" // GIF recording functionality + +#include // Required for: sinf() + +#define GIF_RECORD_FRAMERATE 5 // Record framerate, we get a frame every N frames + +#define MAX_SINEWAVE_POINTS 256 + //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ @@ -29,7 +37,20 @@ int main(void) InitWindow(screenWidth, screenHeight, "raylib [core] example - screen recording"); - // TODO: Load resources / Initialize variables at this point + bool gifRecording = false; // GIF recording state + unsigned int gifFrameCounter = 0; // GIF frames counter + MsfGifState gifState = { 0 }; // MSGIF context state + + Vector2 circlePosition = { 0.0f, screenHeight/2.0f }; + float timeCounter = 0.0f; + + // Get sine wave points for line drawing + Vector2 sinePoints[MAX_SINEWAVE_POINTS] = { 0 }; + for (int i = 0; i < MAX_SINEWAVE_POINTS; i++) + { + sinePoints[i].x = i*GetScreenWidth()/180.0f; + sinePoints[i].y = screenHeight/2.0f + 150*sinf((2*PI/1.5f)*(1.0f/60.0f)*(float)i); // Calculate for 60 fps + } SetTargetFPS(60); //-------------------------------------------------------------------------------------- @@ -39,7 +60,59 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - // TODO: Update variables / Implement example logic at this point + // Update circle sinusoidal movement + timeCounter += GetFrameTime(); + circlePosition.x += GetScreenWidth()/180.0f; + circlePosition.y = screenHeight/2.0f + 150*sinf((2*PI/1.5f)*timeCounter); + if (circlePosition.x > screenWidth) + { + circlePosition.x = 0.0f; + circlePosition.y = screenHeight/2.0f; + timeCounter = 0.0f; + } + + // Start-Stop GIF recording on CTRL+R + if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_R)) + { + if (gifRecording) + { + // Stop current recording and save file + gifRecording = false; + MsfGifResult result = msf_gif_end(&gifState); + SaveFileData(TextFormat("%s/screenrecording.gif", GetApplicationDirectory()), result.data, (unsigned int)result.dataSize); + msf_gif_free(result); + + TraceLog(LOG_INFO, "Finish animated GIF recording"); + } + else + { + // Start a new recording + gifRecording = true; + gifFrameCounter = 0; + msf_gif_begin(&gifState, GetRenderWidth(), GetRenderHeight()); + + TraceLog(LOG_INFO, "Start animated GIF recording"); + } + } + + if (gifRecording) + { + gifFrameCounter++; + + // NOTE: We record one gif frame depending on the desired gif framerate + if (gifFrameCounter > GIF_RECORD_FRAMERATE) + { + // Get image data for the current frame (from backbuffer) + // WARNING: This process is quite slow, it can generate stuttering + Image imScreen = LoadImageFromScreen(); + + // Add the frame to the gif recording, providing and "estimated" time for display in centiseconds + msf_gif_frame(&gifState, imScreen.data, (int)((1.0f/60.0f)*GIF_RECORD_FRAMERATE)/10, 16, imScreen.width*4); + gifFrameCounter = 0; + + UnloadImage(imScreen); // Free image data + } + } //---------------------------------------------------------------------------------- // Draw @@ -48,20 +121,43 @@ int main(void) ClearBackground(RAYWHITE); - // TODO: Draw everything that requires to be drawn at this point + for (int i = 0; i < (MAX_SINEWAVE_POINTS - 1); i++) + { + DrawLineV(sinePoints[i], sinePoints[i + 1], MAROON); + DrawCircleV(sinePoints[i], 3, MAROON); + } - DrawLineEx((Vector2){ 0, 0 }, (Vector2){ screenWidth, screenHeight }, 2.0f, RED); - DrawLineEx((Vector2){ 0, screenHeight }, (Vector2){ screenWidth, 0 }, 2.0f, RED); - DrawText("example base code template", 260, 400, 20, LIGHTGRAY); + DrawCircleV(circlePosition, 30, RED); + DrawFPS(10, 10); + + /* + // Draw record indicator + // WARNING: If drawn here, it will appear in the recorded image, + // use a render texture instead for the recording and LoadImageFromTexture(rt.texture) + if (gifRecording) + { + // Display the recording indicator every half-second + if ((int)(GetTime()/0.5)%2 == 1) + { + DrawCircle(30, GetScreenHeight() - 20, 10, MAROON); + DrawText("GIF RECORDING", 50, GetScreenHeight() - 25, 10, RED); + } + } + */ EndDrawing(); //---------------------------------------------------------------------------------- } // De-Initialization //-------------------------------------------------------------------------------------- - - // TODO: Unload all loaded resources at this point + // If still recording a GIF on close window, just finish + if (gifRecording) + { + MsfGifResult result = msf_gif_end(&gifState); + msf_gif_free(result); + gifRecording = false; + } CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- diff --git a/examples/core/core_screen_recording.png b/examples/core/core_screen_recording.png index da99bbb0d97118eef3ca41c16b62182acced63f9..9b14af24d5f8472e403671d78ac8c629f3612bc2 100644 GIT binary patch literal 17353 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 aXx{Fh`%kLYY!czW3FF=h-xU_U{Qm(sHl%3) diff --git a/src/external/msf_gif.h b/examples/core/msf_gif.h similarity index 100% rename from src/external/msf_gif.h rename to examples/core/msf_gif.h diff --git a/examples/examples_list.txt b/examples/examples_list.txt index 1804d7edc..e5e82fbd4 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -50,7 +50,7 @@ core;core_viewport_scaling;★★☆☆;5.5;5.5;2025;2025;"Agnis Aldins";@nezver core;core_input_actions;★★☆☆;5.5;5.6;2025;2025;"Jett";@JettMonstersGoBoom core;core_directory_files;★☆☆☆;5.5;5.6;2025;2025;"Hugo ARNAL";@hugoarnal core;core_highdpi_testbed;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 -core;core_screen_recording;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 +core;core_screen_recording;★★☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 core;core_clipboard_text;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Robin";@RobinsAviary core;core_text_file_loading;★☆☆☆;5.5;5.6;0;0;"Aanjishnu Bhattacharyya";@NimComPoo-04 core;core_compute_hash;★★☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 diff --git a/src/config.h b/src/config.h index b87e199dc..8e6e6a5fa 100644 --- a/src/config.h +++ b/src/config.h @@ -60,8 +60,6 @@ #define SUPPORT_PARTIALBUSY_WAIT_LOOP 1 // Allow automatic screen capture of current screen pressing F12, defined in KeyCallback() #define SUPPORT_SCREEN_CAPTURE 1 -// Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback() -#define SUPPORT_GIF_RECORDING 1 // Support CompressData() and DecompressData() functions #define SUPPORT_COMPRESSION_API 1 // Support automatic generated events, loading and recording of those events when required diff --git a/src/raylib.h b/src/raylib.h index e4be11084..2f9ec2268 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -33,7 +33,6 @@ * [raudio] miniaudio (David Reid - github.com/mackron/miniaudio) for audio device/context management * * OPTIONAL DEPENDENCIES (included): -* [rcore] msf_gif (Miles Fogle) for GIF recording * [rcore] sinfl (Micha Mettke) for DEFLATE decompression algorithm * [rcore] sdefl (Micha Mettke) for DEFLATE compression algorithm * [rcore] rprand (Ramon Santamaria) for pseudo-random numbers generation diff --git a/src/rcore.c b/src/rcore.c index cb6c89881..706cb2028 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -52,9 +52,6 @@ * #define SUPPORT_SCREEN_CAPTURE * Allow automatic screen capture of current screen pressing F12, defined in KeyCallback() * -* #define SUPPORT_GIF_RECORDING -* Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback() -* * #define SUPPORT_COMPRESSION_API * Support CompressData() and DecompressData() functions, those functions use zlib implementation * provided by stb_image and stb_image_write libraries, so, those libraries must be enabled on textures module @@ -134,15 +131,6 @@ #include "rcamera.h" // Camera system functionality #endif -#if defined(SUPPORT_GIF_RECORDING) - #define MSF_GIF_MALLOC(contextPointer, newSize) RL_MALLOC(newSize) - #define MSF_GIF_REALLOC(contextPointer, oldMemory, oldSize, newSize) RL_REALLOC(oldMemory, newSize) - #define MSF_GIF_FREE(contextPointer, oldMemory, oldSize) RL_FREE(oldMemory) - - #define MSF_GIF_IMPL - #include "external/msf_gif.h" // GIF recording functionality -#endif - #if defined(SUPPORT_COMPRESSION_API) #define SINFL_IMPLEMENTATION #define SINFL_NO_SIMD @@ -402,12 +390,6 @@ bool isGpuReady = false; static int screenshotCounter = 0; // Screenshots counter #endif -#if defined(SUPPORT_GIF_RECORDING) -static unsigned int gifFrameCounter = 0; // GIF frames counter -static bool gifRecording = false; // GIF recording state -static MsfGifState gifState = { 0 }; // MSGIF context state -#endif - #if defined(SUPPORT_AUTOMATION_EVENTS) // Automation events type typedef enum AutomationEventType { @@ -758,15 +740,6 @@ void InitWindow(int width, int height, const char *title) // Close window and unload OpenGL context void CloseWindow(void) { -#if defined(SUPPORT_GIF_RECORDING) - if (gifRecording) - { - MsfGifResult result = msf_gif_end(&gifState); - msf_gif_free(result); - gifRecording = false; - } -#endif - #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) UnloadFontDefault(); // WARNING: Module required: rtext #endif @@ -929,47 +902,6 @@ void EndDrawing(void) { rlDrawRenderBatchActive(); // Update and draw internal render batch -#if defined(SUPPORT_GIF_RECORDING) - // Draw record indicator - if (gifRecording) - { - #ifndef GIF_RECORD_FRAMERATE - #define GIF_RECORD_FRAMERATE 10 - #endif - gifFrameCounter += (unsigned int)(GetFrameTime()*1000); - - // NOTE: We record one gif frame depending on the desired gif framerate - if (gifFrameCounter > 1000/GIF_RECORD_FRAMERATE) - { - // Get image data for the current frame (from backbuffer) - // NOTE: This process is quite slow... :( - Vector2 scale = GetWindowScaleDPI(); - unsigned char *screenData = rlReadScreenPixels((int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); - - #ifndef GIF_RECORD_BITRATE - #define GIF_RECORD_BITRATE 16 - #endif - - // Add the frame to the gif recording, given how many frames have passed in centiseconds - msf_gif_frame(&gifState, screenData, gifFrameCounter/10, GIF_RECORD_BITRATE, (int)((float)CORE.Window.render.width*scale.x)*4); - gifFrameCounter -= 1000/GIF_RECORD_FRAMERATE; - - RL_FREE(screenData); // Free image data - } - - #if defined(SUPPORT_MODULE_RSHAPES) && defined(SUPPORT_MODULE_RTEXT) - // Display the recording indicator every half-second - if ((int)(GetTime()/0.5)%2 == 1) - { - DrawCircle(30, CORE.Window.screen.height - 20, 10, MAROON); // WARNING: Module required: rshapes - DrawText("GIF RECORDING", 50, CORE.Window.screen.height - 25, 10, RED); // WARNING: Module required: rtext - } - #endif - - rlDrawRenderBatchActive(); // Update and draw internal render batch - } -#endif - #if defined(SUPPORT_AUTOMATION_EVENTS) if (automationEventRecording) RecordAutomationEvent(); // Event recording #endif @@ -1002,38 +934,8 @@ void EndDrawing(void) #if defined(SUPPORT_SCREEN_CAPTURE) if (IsKeyPressed(KEY_F12)) { -#if defined(SUPPORT_GIF_RECORDING) - if (IsKeyDown(KEY_LEFT_CONTROL)) - { - if (gifRecording) - { - gifRecording = false; - - MsfGifResult result = msf_gif_end(&gifState); - - SaveFileData(TextFormat("%s/screenrec%03i.gif", CORE.Storage.basePath, screenshotCounter), result.data, (unsigned int)result.dataSize); - msf_gif_free(result); - - TRACELOG(LOG_INFO, "SYSTEM: Finish animated GIF recording"); - } - else - { - gifRecording = true; - gifFrameCounter = 0; - - Vector2 scale = GetWindowScaleDPI(); - msf_gif_begin(&gifState, (int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); - screenshotCounter++; - - TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter)); - } - } - else -#endif // SUPPORT_GIF_RECORDING - { - TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); - screenshotCounter++; - } + TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); + screenshotCounter++; } #endif // SUPPORT_SCREEN_CAPTURE diff --git a/tools/rexm/examples_report.md b/tools/rexm/examples_report.md index 24977e38e..e3d64137b 100644 --- a/tools/rexm/examples_report.md +++ b/tools/rexm/examples_report.md @@ -63,7 +63,7 @@ Example elements validated: | core_input_actions | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_directory_files | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_highdpi_testbed | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| core_screen_recording | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_screen_recording | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_clipboard_text | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_text_file_loading | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_compute_hash | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | diff --git a/tools/rexm/examples_report_issues.md b/tools/rexm/examples_report_issues.md index 8d24a251d..081170806 100644 --- a/tools/rexm/examples_report_issues.md +++ b/tools/rexm/examples_report_issues.md @@ -21,7 +21,6 @@ Example elements validated: | **EXAMPLE NAME** | [C] | [CAT]| [INFO]|[PNG]|[WPNG]| [RES]| [MK] |[MKWEB]| [VCX]| [SOL]|[RDME]|[JS] | [WOUT]|[WMETA]| |:---------------------------------|:---:|:----:|:-----:|:---:|:----:|:----:|:----:|:-----:|:----:|:----:|:----:|:---:|:-----:|:-----:| | core_highdpi_testbed | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| core_screen_recording | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | rlgl_standalone | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | rlgl_compute_shader | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | easings_testbed | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | From f76e3714364fdd96756cb2fcaee3be5c48f8b35f Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 9 Nov 2025 19:14:46 +0100 Subject: [PATCH 061/430] REDESIGNED: `core_clipboard_text`, based on #5248 --- examples/README.md | 2 +- examples/core/core_clipboard_text.c | 216 +++++++++++--------------- examples/core/core_clipboard_text.png | Bin 15878 -> 17783 bytes examples/examples_list.txt | 2 +- 4 files changed, 94 insertions(+), 126 deletions(-) diff --git a/examples/README.md b/examples/README.md index 9a208f599..29cea47fb 100644 --- a/examples/README.md +++ b/examples/README.md @@ -69,7 +69,7 @@ Examples using raylib[core](../src/rcore.c) platform functionality like window c | [core_directory_files](core/core_directory_files.c) | core_directory_files | ⭐☆☆☆ | 5.5 | 5.6 | [Hugo ARNAL](https://github.com/hugoarnal) | | [core_highdpi_testbed](core/core_highdpi_testbed.c) | core_highdpi_testbed | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | | [core_screen_recording](core/core_screen_recording.c) | core_screen_recording | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | -| [core_clipboard_text](core/core_clipboard_text.c) | core_clipboard_text | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Robin](https://github.com/RobinsAviary) | +| [core_clipboard_text](core/core_clipboard_text.c) | core_clipboard_text | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ananth S](https://github.com/Ananth1839) | | [core_text_file_loading](core/core_text_file_loading.c) | core_text_file_loading | ⭐☆☆☆ | 5.5 | 5.6 | [Aanjishnu Bhattacharyya](https://github.com/NimComPoo-04) | | [core_compute_hash](core/core_compute_hash.c) | core_compute_hash | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | diff --git a/examples/core/core_clipboard_text.c b/examples/core/core_clipboard_text.c index c9d22c54a..895c235da 100644 --- a/examples/core/core_clipboard_text.c +++ b/examples/core/core_clipboard_text.c @@ -2,22 +2,23 @@ * * raylib [core] example - clipboard text * -* Example complexity rating: [★☆☆☆] 1/4 -* * Example originally created with raylib 5.6-dev, last time updated with raylib 5.6-dev * -* Example contributed by Robin (@RobinsAviary) and reviewed by Ramon Santamaria (@raysan5) +* Example contributed by Ananth S (@Ananth1839) 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) 2025 Robin (@RobinsAviary) +* Copyright (c) 2025 Ananth S (@Ananth1839) * ********************************************************************************************/ #include "raylib.h" -#include +#define RAYGUI_IMPLEMENTATION +#include "raygui.h" + +#define MAX_TEXT_SAMPLES 5 //------------------------------------------------------------------------------------ // Program main entry point @@ -31,30 +32,32 @@ int main(void) InitWindow(screenWidth, screenHeight, "raylib [core] example - clipboard text"); - const char *clipboardText = NULL; + // Define some sample texts + const char *sampleTexts[MAX_TEXT_SAMPLES] = { + "Hello from raylib!", + "The quick brown fox jumps over the lazy dog", + "Clipboard operations are useful!", + "raylib is a simple and easy-to-use library", + "Copy and paste me!" + }; - // List of text the user can switch through and copy - const char *copyableText[] = { "raylib is fun", "hello, clipboard!", "potato chips" }; + char *clipboardText = NULL; + char inputBuffer[256] = "Hello from raylib!"; // Random initial string - unsigned int textIndex = 0; + // UI required variables + bool textBoxEditMode = false; - const char *popupText = NULL; + bool btnCutPressed = false; + bool btnCopyPressed = false; + bool btnPastePressed = false; + bool btnClearPressed = false; + bool btnRandomPressed = false; - // Initialize timers - // The amount of time the pop-up text is on screen, before fading - const float maxTime = 3.0f; - float textTimer = 0.0f; - // The length of time text is offset - const float animMaxTime = 0.1f; - float pasteAnim = 0.0f; - float copyAnim = 0.0f; - int copyAnimMult = 1; - float textAnim = 0.0f; - float textAlpha = 0.0f; - // Offset amount for animations - const int offsetAmount = -4; + // Set UI style + GuiSetStyle(DEFAULT, TEXT_SIZE, 20); + GuiSetIconScale(2); - SetTargetFPS(60); + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- // Main game loop @@ -62,83 +65,56 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - // Check if the user has pressed the copy/paste key combinations - bool pastePressed = (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_V)); - bool copyPressed = (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C)); - - // Update animation timers - if (textTimer > 0) textTimer -= GetFrameTime(); - if (pasteAnim > 0) pasteAnim -= GetFrameTime(); - if (copyAnim > 0) copyAnim -= GetFrameTime(); - if (textAnim > 0) textAnim -= GetFrameTime(); - - if (pastePressed) + // Handle button interactions + if (btnCutPressed) { - // Most operating systems hide this information until the user presses Ctrl-V on the window. + SetClipboardText(inputBuffer); + clipboardText = GetClipboardText(); + inputBuffer[0] = '\0'; // Quick solution to clear text + //memset(inputBuffer, 0, 256); // Clear full buffer properly + } - // Check to see if the clipboard contains an image - // This function does nothing outside of Windows, as it directly calls the Windows API - Image image = GetClipboardImage(); - - if (IsImageValid(image)) + if (btnCopyPressed) + { + SetClipboardText(inputBuffer); // Copy text to clipboard + clipboardText = GetClipboardText(); // Get text from clipboard + } + + if (btnPastePressed) + { + // Paste text from clipboard + clipboardText = GetClipboardText(); + if (clipboardText != NULL) TextCopy(inputBuffer, clipboardText); + } + + if (btnClearPressed) + { + inputBuffer[0] = '\0'; // Quick solution to clear text + //memset(inputBuffer, 0, 256); // Clear full buffer properly + } + + if (btnRandomPressed) + { + // Get random text from sample list + TextCopy(inputBuffer, sampleTexts[GetRandomValue(0, MAX_TEXT_SAMPLES - 1)]); + } + + // Quick cut/copy/paste with keyboard shortcuts + if (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)) + { + if (IsKeyPressed(KEY_X)) { - UnloadImage(image); - popupText = "clipboard contains image"; + SetClipboardText(inputBuffer); + inputBuffer[0] = '\0'; // Quick solution to clear text } - else + + if (IsKeyPressed(KEY_C)) SetClipboardText(inputBuffer); + + if (IsKeyPressed(KEY_V)) { clipboardText = GetClipboardText(); - - popupText = "text pasted"; - pasteAnim = animMaxTime; + if (clipboardText != NULL) TextCopy(inputBuffer, clipboardText); } - - // Reset animation values - textTimer = maxTime; - textAnim = animMaxTime; - textAlpha = 1; - } - - // React to the user pressing copy - if (copyPressed) - { - // Set the text on the user's clipboard - SetClipboardText(copyableText[textIndex]); - - // Reset values - textTimer = maxTime; - textAnim = animMaxTime; - copyAnim = animMaxTime; - copyAnimMult = 1; - textAlpha = 1; - popupText = "text copied"; - } - - // Switch to the next item in the list when the user presses up - if (IsKeyPressed(KEY_UP)) - { - // Reset animation - copyAnim = animMaxTime; - copyAnimMult = 1; - - textIndex += 1; - - if (textIndex >= sizeof(copyableText) / sizeof(const char*)) // Length of array - { - // Loop back to the other end - textIndex = 0; - } - } - - // Switch to the previous item in the list when the user presses down - if (IsKeyPressed(KEY_DOWN)) - { - // Reset animation - copyAnim = animMaxTime; - copyAnimMult = -1; - - if (textIndex == 0) textIndex = (sizeof(copyableText)/sizeof(const char*)) - 1; - else textIndex -= 1; } //---------------------------------------------------------------------------------- @@ -146,40 +122,32 @@ int main(void) //---------------------------------------------------------------------------------- BeginDrawing(); - ClearBackground(RAYWHITE); + ClearBackground(RAYWHITE); - // Draw the user's pasted text, if there is any yet - if (clipboardText) - { - // Offset animation - int offset = 0; - if (pasteAnim > 0) offset = offsetAmount; + // Draw instructions + GuiLabel((Rectangle){ 50, 20, 700, 36 }, "Use the BUTTONS or KEY SHORTCUTS:"); + DrawText("[CTRL+X] - CUT | [CTRL+C] COPY | [CTRL+V] | PASTE", 50, 60, 20, MAROON); - // Draw the pasted text - DrawText("pasted clipboard:", 10, 10 + offset, 20, DARKGREEN); - DrawText(clipboardText, 10, 30 + offset, 20, DARKGRAY); - } + // Draw text box + if (GuiTextBox((Rectangle){ 50, 120, 652, 40 }, inputBuffer, 256, textBoxEditMode)) textBoxEditMode = !textBoxEditMode; - // Offset animation - int textOffset = 0; - if (copyAnim > 0) textOffset = offsetAmount; + // Random text button + btnRandomPressed = GuiButton((Rectangle){ 50 + 652 + 8, 120, 40, 40 }, "#77#"); - // Draw copyable text and controls - DrawText(copyableText[textIndex], 10, 330 + (textOffset * copyAnimMult), 20, MAROON); - DrawText("up/down to change string, ctrl-c to copy, ctrl-v to paste", 10, 355, 20, DARKGRAY); + // Draw buttons + btnCutPressed = GuiButton((Rectangle){ 50, 180, 158, 40 }, "#17#CUT"); + btnCopyPressed = GuiButton((Rectangle){ 50 + 165, 180, 158, 40 }, "#16#COPY"); + btnPastePressed = GuiButton((Rectangle){ 50 + 165*2, 180, 158, 40 }, "#18#PASTE"); + btnClearPressed = GuiButton((Rectangle){ 50 + 165*3, 180, 158, 40 }, "#143#CLEAR"); - // Alpha / Offset animation - if (textAlpha > 0) - { - // Offset animation - int offset = 0; - if (textAnim > 0) offset = offsetAmount; - // Draw pop up text - DrawText(popupText, 10, 425 + offset, 20, ColorAlpha(DARKGREEN, textAlpha)); - - // Fade-out animation - if (textTimer < 0) textAlpha -= GetFrameTime(); - } + // Draw clipboard status + GuiSetState(STATE_DISABLED); + GuiLabel((Rectangle){ 50, 260, 700, 40 }, "Clipboard current text data:"); + GuiSetStyle(TEXTBOX, TEXT_READONLY, 1); + GuiTextBox((Rectangle){ 50, 300, 700, 40 }, clipboardText, 256, false); + GuiSetStyle(TEXTBOX, TEXT_READONLY, 0); + GuiLabel((Rectangle){ 50, 360, 700, 40 }, "Try copying text from other applications and pasting here!"); + GuiSetState(STATE_NORMAL); EndDrawing(); //---------------------------------------------------------------------------------- @@ -191,4 +159,4 @@ int main(void) //-------------------------------------------------------------------------------------- return 0; -} \ No newline at end of file +} diff --git a/examples/core/core_clipboard_text.png b/examples/core/core_clipboard_text.png index caa9b314a694bdb9316f9f8d07161887d53887e6..96a7379c7d956fad97ce1a17eeec7df011d5f422 100644 GIT binary patch literal 17783 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 literal 15878 zcmeHOX;c&E8cu*hRDuX7B&-q9%4G=<6~Radn6QI@%SEIpTTB!YNnKb3ia~1-tQHky zYgj}?!2)6w3j{4iu%w`PKvWbd3Ix0oSp@AQ<8qHorakALd+ND&{>%w8Wajyv?|q-| zdo#~_*vo^CosFN3LZPtEPLAFvl)M27g|5da0%tUXN*hrqa*MO0z3<+Dps7jmzyZ!$ zB3_P1ka38@pbLdwde{V^sutt{z5ie>Ln)Y4n!>4NV7L$mBKKISE2s zn$VFf3kKz~s@70EcX5?W415qnxl9Za2yHU(0T4z&2)=(W1cI%6eRtq(RQ>2tHNUPS zw_;p|bkC0buwhDh&)bv**JsX=KQ%CF)?J35%<0M9gel{4#I2Zwi`XS}9+RX%a|oPq z;KX3Kw*h`1!|;m|S{XEmF2|>|xA~Dxl3IuCbC%|QZ+j?(;63znh*`5vq(F1S-n=gQ z#c>|{T5fU&u+*&KYBUH~#o=0_8`avO}An zq>6l~lZ{!<1|4MHwqw;mls^=nfPzkOld@EJ9FC=yVRW8YPj0INb5svInZJ}doJHuk z!z&6*N&jIcP-_P%86mJ_WTOe}QoHi!#Wi6Hv5T_XYmC|-v{5chCxwPH=IfVfII&(j zyFY8-heh3FRcPqWNYXkS$i;3LE^5cc108Q0?k(pR#zhn~3CFcejUXdsE670?3Iv~8IR%1>;)?H>HPwDgF9@;`ZQd zUolaiH{hWEOn-ad<8PNTuc&v&jE)$sxb_SCA>)A#Bmrm_@DbC}bb&g&PoZJ-g?Vgd zaq>Y-Ud`NmzC1bKnQfkTY9afgV!T+gQ6tW z_A56s_}Y+^1CIfelo&TXDYmnys-rd2J=fsR6z?X*(OgrYz~rfd(G&#}O~g(#&V4)d zNF|H0ZkmuJV^IdaG)0Z?CmZmN*~y|6`05ExZNJ)iAqkfH|A&QDvc345*LDQLXR^sc zCIXoV=?d_9?D>&!M8XjX#}B_7AW12q0$~AR;eBC;!~zlvNG$yOuZ2iPAQ^#V1db_1hi>ggubkWmDscZJ82TFZ;KfxrS*7;UI)gmc@@

Tcf=}BV3J))r^4wjd0XKJ%z3Gk!MeR zs8;_t!4En+*pkb(=Js_%lKW>Tnu3@>XjF39_sXX=RK=h zUwD*^XkU0?>0QqveT5aRuVdKg{Ij%5IAz`GqK`#4fzH_ZaL(9`9KVf%P_o3xn&BoL z^C2L(rYtjjKOsfY6-?zD(5dn{O5 z$Ky+XXSR|X`N|8*`Dn(D)r381+WPHB!(3i2scf5nAwt`vXU(=*ZY3l5!HJxJ3EYMn z`?F^X*G&_>o?4YZom(kji5lXV8q1FG_7Q$<0ejWlyCa4x$d8)BMw`s@(;2SL_n)lUcHQ7^ zY!Yo;*j;|2Df$j))0-ug*K+rC1y<)fFq69iU6YeSgIlLPnKe}Ag!A^5?t|POM&@1s zT@Xmw01Ud?T=Ex3{8;)fW_87{?sGl1tJkURLiU(mN@% zjCdz$XV)Fg>nJfS$Tq49$YZZ0+XOAkI7xRvy-VU7Fo7H2i&ORPuv9C__A|7sh}1Ws zwYC(Eb$a-}%1bR%t|Tj0sL+{3bGFhe8pWFBFWiPNeRn@%5nCZGjoeVU$Azx9)bym2 zSSfE@$9N*Aa{^2Dr?nBbt<+DS`=$G@)z9rujZ-=tdJEP4PPVn1Dbczbfi(Lo5~>9V z)nv|ssQBBWqB`&5(;*a{OK=EEeMpMD9a3$7l6ekJ z0!?^Z0L2xoRA}7XWt(B_F!=a)p5dN(pwWIG?7{(>Lml5EkCIlp(S3zY_~UlTuGOF$~p{ppsbk zScRD^piHo;IlnWuEQ4CTB&}>fD`FFL&l1=`Bkcq2lC`;z_Kjg>)^f@}sRk16xc0xM zTwi|1(IW9)>_TEJQQY5Gt9--Avr#u`YRv`PS*MSP%(vd*z1F;0w0`_(=mQy>Fl7Rf zufQVd!gXmmWuxns`6P(8Qmybradjva)5299P@xi>&)M!eoy?*g^xfla8zU6px~ISC z3I0(y1zAEzg7se|T>ZC^5u#pfIM5)Pg*#heFLFZ@Vta4*a&P-{)77BL3;NIFsM)p&ixHjItIWuXAULcT3Apw1^TOgRn^J@s;iyQmx1y&Y7Y9_ zVRXWk6YZ~!F+&pui#$txik!M#?a%LL4&w+bVz#`n;)exS_(JyX0jx_ZP8GQA7Fcnr z38KqH7C86UGLW1Bi=0?EQvbBe@J|}BqUI#by9zWmr^v3P-qo7jFw+qDU#~S$6bs&s VXD-Jt1O447=QSRV7pQ@We*is``VRmA diff --git a/examples/examples_list.txt b/examples/examples_list.txt index e5e82fbd4..7ce5cf2dd 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -51,7 +51,7 @@ core;core_input_actions;★★☆☆;5.5;5.6;2025;2025;"Jett";@JettMonstersGoBoo core;core_directory_files;★☆☆☆;5.5;5.6;2025;2025;"Hugo ARNAL";@hugoarnal core;core_highdpi_testbed;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 core;core_screen_recording;★★☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 -core;core_clipboard_text;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Robin";@RobinsAviary +core;core_clipboard_text;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ananth S";@Ananth1839 core;core_text_file_loading;★☆☆☆;5.5;5.6;0;0;"Aanjishnu Bhattacharyya";@NimComPoo-04 core;core_compute_hash;★★☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 shapes;shapes_basic_shapes;★☆☆☆;1.0;4.2;2014;2025;"Ramon Santamaria";@raysan5 From ee2999b3e0e6b1394db51de19b98467f8c643b22 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 9 Nov 2025 19:22:12 +0100 Subject: [PATCH 062/430] Update rexm.c --- tools/rexm/rexm.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 450e95819..a7cf88ba0 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -2154,11 +2154,12 @@ static char **ScanExampleResources(const char *filePath, int *resPathCount) int functionIndex01 = TextFindIndex(ptr - 40, "ExportImage"); // Check ExportImage() int functionIndex02 = TextFindIndex(ptr - 10, "TraceLog"); // Check TraceLog() int functionIndex03 = TextFindIndex(ptr - 40, "TakeScreenshot"); // Check TakeScreenshot() - + int functionIndex04 = TextFindIndex(ptr - 40, "SaveFileData"); // Check SaveFileData() if (!((functionIndex01 != -1) && (functionIndex01 < 40)) && // Not found ExportImage() before "" !((functionIndex02 != -1) && (functionIndex02 < 10)) && // Not found TraceLog() before "" - !((functionIndex03 != -1) && (functionIndex03 < 40))) // Not found TakeScreenshot() before "" + !((functionIndex03 != -1) && (functionIndex03 < 40)) && // Not found TakeScreenshot() before "" + !((functionIndex04 != -1) && (functionIndex04 < 40))) // Not found SaveFileData() before "" { int len = (int)(end - start); if ((len > 0) && (len < REXM_MAX_RESOURCE_PATH_LEN)) From d8601121da44b993f2893e9afd51b85105c6e698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20=E2=9D=A4=EF=B8=8F?= Date: Sun, 9 Nov 2025 17:08:26 -0500 Subject: [PATCH 063/430] [examples] Added: `textures_sprite_stacking` example (#5345) * Added sprite stacking example * added link * formatting --- examples/textures/resources/booth.png | Bin 0 -> 63521 bytes examples/textures/textures_sprite_stacking.c | 115 ++++++++++++++++++ .../textures/textures_sprite_stacking.png | Bin 0 -> 52440 bytes 3 files changed, 115 insertions(+) create mode 100644 examples/textures/resources/booth.png create mode 100644 examples/textures/textures_sprite_stacking.c create mode 100644 examples/textures/textures_sprite_stacking.png diff --git a/examples/textures/resources/booth.png b/examples/textures/resources/booth.png new file mode 100644 index 0000000000000000000000000000000000000000..b4147f77b9818ad3a70ffc66dce16ef3e0f56e8a GIT binary patch literal 63521 zcmcG01z40@+wLd|79he#B?T2k36Ty#r9?vM4y9X4y0!v?lz=n?D&5_wAV~Mn-Q79F zS?_>u_x}9c|M|~#=JJ9UW*pYLp1Pm=xz~6+6BoihM|2JbgJBCl=9dKDU&3It4QDXH zRrH2MD)@H7TvF%}EUk%T1_rwZ6Xt&?Z5=T`Q0_^fbijKcSMNNub><#9f0gj!SIv=n z>RV}-sR)^SPWSK*U&n`gfPbG)ky_k!sr6=+Qp$*EN8jrQ+n}OEpg|&ciQJMQvbSxCR7E#HF?2@ zQ)ja8HKHE;_CB2bzTd`uoB#1`z~SIV;8#i;$s8MRjpc1r=1w$EEK!9HHZ2ZXweuJc z-J9L0wDd*N{Bpj*GZ$QlV-Q+@Wf*u_QmyJo&4SMD-@2LQqf>}pz-oj$4$vDo0nTn!*z30mb0V# zx#4(tAJg_Z$G+)^JBQY5D>+FHG%c#~^QX`<+#G1iPQZXU&ujpP5c-IB=s^4sl{8{m zJm6uzB1opK6()8I`qS+%?{R5}YxDI*G(f@lUmw?QBCxEMu2{2axKT7mz)rwmtDSQj z`ccv|5dwS(=|rmeeSsoAaS9mV85qoLtR3#P!~6ngoB6jl==|OSc)iB(fEU7`_(X&c zcwhC8V8y*lwOl%_7iW{*x)xqhH+@At2np0<{fJ&&;fkqbwSB}Xqczh@|8=?Gi3@X3 zIGc4hgPj9TFcwKJq>^o^fnkphE@hkjlBV?z7G)iRxrVcM&1)AitX^$VQohi#p?URc z3+I3#m6m~2NzS2BA&;q(6XskEBC8kcHX>X1%!W+m+^b@<+E!+RYv>hJjN6liFh*fD zorwCaK`SzvklxAH-S_+LSJLqD7#DX;J6M$1SSz-=j0O3tpj;#RRHYJRnSB;0>`8mx zEY2?q@K@EXO3wA|X3Oji_yl*5NM4o|md=!m)#Od^(zd1MjMrYV=JYc|6q%Qihp$dv5oUE*iYBuT%!0yYU^V;flO3oi{ zG2tJrhkSruuygGW_gxJh+z1RF+*3!lpzdgtvV}=Jw_|^AQC8_cZwm%{=YuweiE+`+ zs0uaZi~hB-1cN+dL+7RR0WUcGPI^>VXk?MwTz@8;`N;<5REJ>@TOhJXZJ@jQJO*+^ zX5aj(<=8B_i4(h|+q+T}JY=0sW@Sl6BrNG;yIPk5 zIRw{nFRrT*&y_xuAi&}sva{y$EUW$G zr=zFgur8A+Afbu(DP7v@nF_vC@ZKfldu6tx4DgRTYY1$5U2G*dm>I-fpkXq3>I@8a zKDCSZ{@VuA3&wtMC<)8abA)Mw^a~zTqcM7v5e-e<%fg1#xlEF78^Ps!TF(xW4-RgW z$%4}TMGv7(KCMfQ=ko}?+0M*#do_eVFEW{@HYqbZFVk`Tbvw!H^}x3cTAA+=vHibW z-US;3qt$#PlsUg=IBc_DF7Bit0>knV7yg*I`cvB%c6xb@&lR0^>eX;gqRJ1q4#i70 zcNCvPAS1kmzoW-cmZMGBIrW>-EfJ=~4w&F>-j#}-3BI$u35 zMsu>2;S7U`A~C;2Nar0YyKJsiG$3YJ?R2adC@E!wf_*yfD;6so*!t%4T2N9hs+CJ8 zFV58a)`+JYc4b6tx>%~>!d_AV9JP=cPV)7}r@vPd-M8k(XRlbEYusrO_2N35Dbd9g zKd0w^(cumq2u$Eo)DPIb+sRyQ)BRveNA>55fkr9Y_D1j!CLZ38Eh%%)%80@;PFZp~ z*|M}1eSuLZddVW&+1ThHVxopkfz?))6>0bS(=AHMQLrg^ekSkEGjusz`^8pNNFda8!2po6mDe+GRD_g*VZj{Cv;vn zJ=~3b$X^woJ~TSIQ>kUr-~3d~kvn9tuNk!fGCi}I;zWseSoz2fIg3g3ER|(1lo_FN zIVQ{gR+t|GO@F2M7J{jM#aI0^OjG>$R&9mN!cE@0l2YH%=`kK$Vp5zuy2XlG!cEPY z>fHn3@lUr#2P^w}HedS&P>yz~Io5A}sdL567+T67I2D(hIY>NSvU!w;{=PSNt&YJ~ zk$AK?HW*k;fMm1VG%BbG`XvA1ztYtIv zZK*D*zDUR*7z+>YXm|Ff>T04Lj}y_shRAC;{HaUewDXBJQmYHr|JAOav^Gh;q9k|xW8}N@lWP4_;{I? zWBRFfLMCO_@$`O0P)fs;Q{_#Ux{R(zXDk=G=*H=Sr#xUxR-<`VAhe*s+DmKtE6i|a z$x>Cw_|bK?f7Z3K{BUz^@N@IT80Z>+Wwz4Ql5%~m!Cv{zU_Z^~OiePgtz46965-&j z6x^s!I0%n1?y#ttfWcal#;V_w*GPy`LWohNa9lTxBW9C5+}GI??B{Ixix04E)TY9G;<0xTqNMD|Rhx?Jfz~75zxf?TwS8WS>w&xtYD_>714pYtmhP8aPQDxj zHZ!HzX6Lxl z_sVy?&JQf-ZLYS1dy{z}AW`M3CVQQ76gk8@{aJ=K3{5 zY#JvB1n~07CJQ$Oy5DG*pH;Cw&RDo|)xQMKv}ot(Pi56OZ`HMHbNvAfCa+tnn!agg zHg@{vKa%J7&HKMvm534B2P3ffRCk*+2Rzn~JtmJX4=yP=U2q)Qtqjck{+@sP?g>=` zQmf>~?k@wmj)P}_zYr>dP}`5}w|?l600H@*5Zm#8)opyFTAhZ_=vQY(6CGbSkX5Z+vo}$bX zWoYkBD{)lSv7IGKGjCsma*q(}N3duBstNEm@_V!vUP2%=^SFS-TK^~?e(k9B4JCel zWp1a%;VI9l=C6=1cVL$rZ=1X?XP5&SA}#mn?LLlv6Fk!vtLTW1s7}YE1BAW`3pq^Q z!VMwJpKir|OD?|;!G77~`aS9gf88$(rrQQ`T}I|$vZaxI65--(jQ-2=gH@$X3VLN{ znSDoI)Wyr36*d{~g5`t5TaP09d=F@gfAP40zWvq6R)8feDJiS(^ZFEN&sAe?qw55k z2boDUC}w}k9f7+6jp@W^M>z2{_by3N_QNZXi2raIpqy>`5@+`Hw9R`$9|5DPv!opO5 zXz|jiVSYinpEm?xS|U}wwu`0S-;SUxTyM+er<=A(Lzxrp%Woa8O%^C5^8ZJTI#A^Y4{y)C_N>@@AEI*L*p>J>D~t zYX#!3)IC321@+VW|8*Ji@u+)YDfxZ(Lbw>&vnJ>qnw*RFel)eQq^idA7qHjJ-vEWS z+!1Qbicnn)q#X{syL9xCyqBZO96jOe*7hrVBW7Fc6E#b-ZU*&Ta^%5i?!&Eb6)G=d z&$YOG`+^td7ah8U0v0jcQP#Z<)Geo8tKRt3#YNJd*tZ}a;R(ZkhMmp~`WVBWgykmz zZ>Ej-k09)+ZmcF3J>Mg!eRO;5sjmCywt%BOBM{iz)?%Tggdm!h8tr(eFnVj(QTyAd z@`Kh_=HgoGsaGcDuc{{Adb@2^VwB7gpZCr{U8a1<*Rh-&|ou2BZ*ii4e~Tr3>A9J34GCPC-9Dp;b9DVMu{N5<9dc z#II_g_C`!MFI@clF8uBLha`yqEG#^+O=$+Dpo`7|S|2?5*KOS^PoP;OJoS@FBY{-d z63%qQf2aaH{v#-0s0xm|3leC4>F7Nla6-^p&1r|OnClhoo?HPN(pNPY*tZ|_eBq9@ zaUXS_vzJJMHO^0HjeV(a{DR3YE)K14`m>@ufdD_h?8vVWCkz$~0j0^&$sx}=C{sLu zT@w+cL5J*HQFIRbb;xEUWOXZ)DJXGmGS!pkiXu?eli(Azjibg+&w*1= zaaCGY-l0>33pdT@j06(WRE=w|Ll-xzVzkXJn6}mWM?EK>CS&jn-3&Z;O^o$EUS?+5VeaOqTR4teW5JzpctP%t zd7DGdvd)XTWlGB3eaCRI&^pOJ?8l7{;Q~-T2v9{)653;@q&ZYN?qv}{F1{!Bj*zp$ zGzs;k<7T;s?juVv+P9RRV$^>#=l36&kZ9UD7g_p|O|3*K__4~==K-$Q5ZjUHiKe06 z6m8D+uX6*m+b^2e1BwoQ=Y2F;Q{c4UW`!;<%G|uRHnsprT3ObV9uyP_zlSmcxl(?X zy+!}~yiEiw13rHWAF7>)?SIwI-uHVvE$pi`BL5c~T&1b~5;+n$dB5t0_F~GHJB{Y> z)aGslce${xi%N*ZIPe<{c#|vO+y99?Ar?Mug9%?e79oC#X+m>z-9MC(oLY=JF?7wv z*rB%ZL%MWFz_{qHWc#?kJ+WyROq_V|7|OHka!lymD2-v=_&Vsl0Z8ASxu^pYJiL5X zC-EX*e`O32%afs6)_8bMhh4mJJLcX>oI5uc^v#_%mkw?EkTKLj>qOmoQhN3jybVm$~Pem7a}_k3FWwvb=SAOsg2UlyD_G0B zmE7cH2n1FssJaSR%M^KUmq^Z1P;Ci15~7k7T!UYapY&o^foF583p*@$K7NU>*x7M9 zvT*T`@)B-_aOXwyv$)@b>F4jxS7*@!B*i&o^>64A;lbRrpl`9diyNInyTr>@!q5jm zk@X^b{D9TTyMZ_IKgN@OHjKowCkg78n5K1$GO&9;ueV{lAtWiHFKlEE`<91hvGMb{ zLa&NGKZDEzJi;}F98 zl!BG#x)BJ%;ETiv4`~pDd~ol^2l^v{X1I)nQhFV~Gult~<7sJ(NSD@xTI?wU*FT8o z+`Fg&a00z5*`PS5q1ya@y1uoHfW!^b7s)~&#f73S7-4HPBts*_$JD)9Cco6d_-+{soSwUqn~ zW5P*(w%?Cyj_-EFO#Y1>Q*pT}X3pF-$2YPAa|55fJ!@1YFwRsmWJF_#g%|*hMYZ_S z5eR6Vm<5EF&@7L`J|Q07<>n}^)!mU^3CqqfiS{HUV8^(v(YSyFCtU&&)mFE9FeZV@ z$}*|z@-hP~Pn>@Pt3`)8xh2+e$k(B7VwZ$I>Tvj|_eHxRTjx5-1yVO+vlDT|9mG;V zpC!&?9Dv7--!`{_Y5B$TuAA|u*9!y(7!swmhCaSo(#HL!yZb^6p(@SR#QB)~3|OP20T!c?#kgs^{c6gfmP(>-X=|d&@XR&sb7PMpzOt;g z?;#>?tne(;a#=@K9`VFKY;$zc)^=ROjC_An!~6>-?zKaGUlF=r$XooY-4j~^#?X;G z?hpDuAEKvumbkmy-VvA7 zmCSoe)j&i`i8O3BOddPE4`F{JS5`Z2A(vv};@q3dAgJcAp5ie79vH2KEytNGx}`w@ z;~%(z<8m0s5(Y$%17a88Jnbeb=3~a`yG=}DwGLi=AqpvM4GyO-@ZPALBLK+_R2eWk zN^Oh^P*O^D;Y%73;QtPgWzEGqZNTk!T`ad@E!eAL1@I z>SjT;-cG;;gAT->`>nASdABy4TFj2U3bb(PPP=P8tg{4)D#U`NY}{$Dhf-3p>!y-@ zbyo7tJ9jS>*8Uop2~Pv=kGeM8hUqGXlO`C4KL}DJz7U=0!ZGOpdnfDjDE_KXZi-EI z9%}-R@rz!unGHc)C##6+;A&~kySXfxM5iR?~7*gj}3i71+04zHI^g3I60!>ca$7r9&PtR>N_Vy0D zFD!^FDDgzbq%G!{jZ*!(U%rUf3TD6J{upL=uU6t5Zzy?TY@!Q$Z-_QN5$`#K_O)+W zli&?M>@MXvF|d>lZt)EvVp?x9=K6@}p;Ln+@1MQwZt7yRRh%ONXYMA#;zvY9CyCT{ zB^B_^)z+T;I{c?JUrk1M)JCw}HQ)R`hR0$IHNO~eysv*pz;w9-&eBFHOKQmg;PdlC z5WhB`6j_0H3~3?n1dWQn^&w|UCd=JGw&mEY`-SBn^D!Y5XMWGlOBv^dB5&yZTAu0S z?P*HrT`kNGF~;d1|3@x3`cYkw6KEBDO0ZY|g-q$%g+h>N0Ge(cQ-Gl5?d(PJI_xke zL;Mf!tZdLzGFbeK12v3V-*P-C4r@51<})u5DQuNlgW5Y8!3r04pVyO2?;^G+9#AyW zH^h`A-cEY|1YSBg@*%+T(K^pvJS|;hMVO=lMttY)vbj<1{Q`e6-G5N;kVfRl{R)vh zz9OHwBgC`OI{5ZgHj0<%2fx*LG=L~1r&_q{zU!_ z@dp>cyAbugi8DKwDxF8YC8ZCU_T?MuW*^n);MIqs$wMBA**jrhZzAJE9h3fLTk#kF zWm}IpD*PpAhW#!IU8E9gtht zy~#%+E2LwxFl8Y^A3wjAC>Se+wGB689VY%+i>G;X&l8VY${Hpo1wGU0g}rwoS`g*{BQG>@1NWTT z3JyHR{XowEaS)FO3cAE4+0x2-gGP!*^t|-VEp0*P<~tB9gVH$~DU{7MuWogAVRy-* zvKPznVUsG+l}$0MFj@V|MKFYanBSM$No8^!5TgseL9z!?F>D3~uL+P}qw!`7% zhdI59cGa0@Q%8ml*H{yC86G+J;HzUp?AuWO!QWjQ^c zT9sd5|6}Wqe#FB=fXSZFpn%od*J3~q7fjVibQunG!-mv~uj1iBBN?Xqme^f|1`Z%h z3$KT_09S{@fgUR8(e|{Oo&s+`>kKVq@{&mxIYxnd;qJ=&tzPaottMml0{I9}^l+H=b zXTU9xRnTag&4ZP6Mcc^e*+F|yD;PzT`aS)|Y!C^iqTgY||N1h&;}wP^7-wzEYyppl zW};u$jPJJA1ORwg zmei$XyY{)pZF=5G;nL`o@~tRvV&9JhQuvSwHE5F;WEWd$62ZoAy1MX6>Q<&O+Y%_w z6nu6%6qq=?vOAE_MY6SL>NHgxM!+|}VVm0r;1nMz(xdXZ6DMDJIuGw&*jOa*;*wXA z@a?L5C3=qxC7*bUB+}veIa=fCSt>p;s&y0B(54lYYSCkw)MV|+VhQ;Z{9^LO^DHnQ zXg|^PRkHlssc#MPA#oqtwGI7nU!Y&x(4b5!Iw<&#?tD9e1rZa|(3Iw<*9}a5hy{bfKo}fh^WZS0x{KZO4PDgwPp*%OTf@YdoyZQ_BX+-tWHile9?^+H`EX&>4 zya!CQ8x3BrdgnNes_m(%%n<=<_wq3rKku2OkUI{5)B??EB^{9KL?LZdIkMKVP;(h; zqsv_p6Es)^7`Q&M?Iy}}7n)oxea{lixQ?Ji7FPUDPa3E0LF3%D*_@jjuR{b&6_kjj zIZb-&qv!T`R|`MBdM0#^H24wmFSqHjD8#ufs)jT2EkQsI7V!9ai#I{<-8o_VAr(i38_rR znWntCbLBBYLks0YAdpP{m=5^>{T%9n{0(r4-dt7<&pn=(^n`J-dBpFEK=YAad>IWt z7D={tw~C(QJSuC`n`nhQmKkBGHnTccY(`VdPWkb7+pm>%LDdiVNDUC8X3>ok_qIN>v$ zAwF@~1Ro;ZI$RzKnKYH82pSQ?r}X&tPn(=HI?e>NqO_6kgLdEdp7g9CVn6a>=Z0=W zMkc~I;MlrSPYl$b7`bT6&xRhkwKR)^>(mEVJs&0sB~~OZI`sdVl2qB#uClD=e({7) zgIW5P)Bf1JBC~5O@Odd=+T3^vkS1(Hs(UYIb`Ld(C(=n7a8_sr^@n89LCA4v_j>3u z@m$NXjfgZ6c`=R`+Y_J)|71$c!DhXsIbhFe+=_4EgAV~Gy({Z4-Fq$mSj<5D!T2{~ zhSdsk^M}r%T(A0yvx_f762^UQ{qRzdke7?{qqcyOW9#)7?jOwa!+|4NjJXE66PQmO zY&IW~bjUb4DXZ{99`~uG0>{M7kz1Wb`n*9-TQQul z)puV$5Yyy5mnFJ?j+E%tQ~`xGriQ9{|1uZ#eICiE7x1G3d;VoYC8sI-vaHVZR_Bzv z8k2no%iiu7nUh?UBwWk7{Y^oF|6Bse7m-Ae7ETlTvb=<{OGE>eQXVFh>GQsYKRYiq zIZ0U&*HuwCog+YX97Irt`pLTJAd7EH5O8Li9J`Tq6P?PmM_u)RdZ}Cf>6%izr@iac zHSv(ck(R%8`qRY%j>HmN&c_XD(8JNYg3R=;^Foly_p2a)y- zGPqi??em%8sm}8v$DAn8`Wz8v5M8$Iv~A^tnsUOK1(M1#*g|VcH6Xs?(GqQn9d-{bU7#MsOq3E z1YER~2#9RR=W-m+LB7WjkG-h*#3&J{1=j_6C;(&l6I=PeycD4>*UU5m)kbNzHhaBn z)fk&wkOi;_pwJjWx}lpJmi8xn7)j96VQ6ki;|iLGBhLFgw{IaJF?rE5FsPFbjcyer z_E&@lC?mpm`_BAV`1D;K^W}H)n6wRa4}qpIf$Ht3Q3DZ#b*w9>%*;vRE#EIu+sb!3 z08tI;(>@=C;r>Ltz4AUUB~GLhpryYRB}wFV7R?&e)pY7fN8OpL+qu)n%k9)T1gWU! z>mglI3at}wS~pO9dF?t_lFM(dBy@QvV>kAQmcTehe5LtUkm=H^z`qWCJxm~@7i*w zf~!%J!@Z|Ki36hC%ixe@_!_D*#Pl^G`9)=Sil^pASNo4FB2W=yMz9(n+xiSvxaDe_C~sr?w9HM zrylVA;UwNefE|Fs51QAkZCDwoWA8hbFHqSFIcs?JX>I~WjfL?v&<>8Bh~ApcTR042C8W3aGDU@EdiUvHm8TESA&h#%hFXP6quT{i^nCMo;k`$`T`8ga z$hLGh`b}OQW?t@ie4Een&ShxaWu&i#ZJ6CIdDc+=sv=^UXajUx@7f&i=5C7C^L$qY z{=e)8kXv1|ga{qlpG70oD}Pv@0Bu%U2k=7j{cSLT>=z6WyreFMR%Yp?IN)u%T=!N) zNp@O0(g|OqNHF%bk)y7X9mLA((MvqVnsOm^k*GW2J?lrB+?&LSmWuid!m4jc zgI(_KxplAK*IP{AR6&qu5jATl-H)>J*w!N`%D%UEdn#CYMp zImfHVK1MuDg|7tD79tBHxG1Ocnnf28@9a=djmS9F$tIucuiaZ)yGNee*zVQbULtBZ zsBJqqImN%mUb)8pRHJdOp=r+EK_^njB64b>qNI7eqMl6RiK(>*8j zX+ox`JqIW)YJhSshX*%S7Fm(UkjW#quXeaTrNE*^rTpT4b;9v9ctLa12@f0Vis_XX zeZlfi@9mF(NtMX6rN(_Kg!VOBByebrI5TY~ezSR6J%tUZ99uX{~=^WHGqRET!}zuC?xBik48` zAEVr-y57F$q>s&u5Y#YukXtF)v(->}!dptuZ>zVU<%Dcki87z^H0=5+uIUJd*EcTy z69;l>K;S}IbMZ9$jePLskvPSeDId!cqV#~8kJLta{|Dfe;oU=(@2~OZ&cz{A za~BWymJV6uOJ7=axIy@N+v-j|iWhfFr^kI_t&%rbJPG3(c_*W17)fm`XJd!pI}U8V zCA3CsHokA)u_MHki0l1ToOBX+QldBs%dl)fJMW#;5?n(K+J|G8n;tv~Z+YKqM&wQB z-H9WjMR%+3{S&(cJyql*Scq$XQ%BCKAkRj0Sw|ljs1wA!Tgb}M?Bp>F}1gCz!p}5058gut)dnWaMWBcC1r(1+pEmVwL529eyvPB!LmQJ)37D)ilRGZ`W*cArLCa#cR`*(Lpzr5?!W%vE>#Sv3v( z{)CpBIylO~WTlB;1bP=dJkY%e<&{n>LDOGPfMAF2JN{5)zAi@{=@9$_vkpw;sD&cZPTJLtliSLDP$oz*^4{T z@I6z#EyLUsfwAxw zNn(T1gyc8WsS8^uPlt>6ra6_p-IvAh?Nc`wfg3gLSo)Q%VQxo9qNe(PPqpiuK|*Le zcMCe$CZxKxxu%+;D$lun_#=z@e3wiXl}I~5O3CY%6XuoZn1x?auu>M=G2huPmKKCp_DFb+HNhRce-SU!_BoAgP&s)}CP=(3r!*+gF`1tT#>rl9t8rf>X%Mn-*os(& z(Rd9>%V3*yG}q`^V2h@XNnR0ND9E5vz*oBZmGedU+8`LrI(82(?ya{!)ghWsmP^Np zlQ9z7Db9}AR4|~eNE*pJrB-TdIf;-*XzMHe*{*AkA69oMN>WMCNRayeJ3T|pBoT+( zq;h#QuA8EE1Vt^AdQ#_L@qweEVPjBuyGECd#r&JeY2Oz1Uq&FScUw)K@Y)0|UN#Ca zl2>x8^o0LysG8JF;wtNP4$UD8@$Rm4@$MJSP$3dB%|BMk-I$2G^>!Rt2FJuO982mS z>-F#3;;qq}Xom-U;TEfjTxXl2$VsYZ`xwj0Y1Se0r8BCA)aV$L8?Rk$T_%!W$Y9-c z4G?vbh9|oHa-bR$n+=ZcedC$}97UH0{L0oI`)pV$G4U|^toJB%#5OKIZG@9A1Dex? z>qAsA+a@m04?@DDZ}*7iN7c80cy2C9o6SKBsg7j?Bsg!ffgbKzMBEla^UK0IB!~0q z>j3f$#jx*kSa0$w=^9#nv-O}^W+@^^9ck;DeMk9dK>3hfZasLcAMnPaEv&n4+XWAW z*aEO~8Bn!T@llSP?dp&v@22W=)eRvga+(!2PRL^W-rSz;OlDfD0&muLDMAoLhIG)j zbgx;zwuL`NXtnmWuN3n2thob`?If;Lt$s$rT6A6^2jKiq!xMcSeet$B;vH!ml4{{n z2M{k61d{9;W8pngPbj!ykA>S&g5=bV?=ZqSC?3lbm=`i|rh0EgtK~SX(he-?QT1iy zK^+qynEoQ8q@y^_rC#~txUXFw15yI>b}>!~jj2cO>5uEgfI=kI0S_CqyIQ8OCiiCk zl9Nw+T|6GOt4)~C{TUN+R6c6o5lEwj%v?mDpy}Md1gEcq{FwSDy%IlEX5CJitK}irS8dNfs#2ag-&@6whfIv`^i#|mdStVPI>(#wf z0`X%&M9Rgxzuq2-i2^owttZnbaj-}m?|>vapP_F}`&lks6=6dy8z0&mG|Wsi^d>H= zD!wB~BD_@?SCc5*g}_~%uJ8RLySV%3RCL#JXiht~KUooNG*txRg)b6ArjjPmF_^oq z1Wn!B+MDks&)~u&+t$lkYlNA`E40PWT#|nYPRo3*-W``ao2vztRSjv~W1KU8N9bdy zQn#m)Act{4=&WLQckMiDTHXCLxRxZ&(UQM2NlC{s6La?{lO1^VFdS%Zl6M3i;;X=tdKepg?;rZcGkNVE*R=Og6z; z?|^=X51|nh@<&o#Z(L0YpU73<)$z5TGwQ&3o1rQT>Q2x=lyolmke}3*ABzrV;V2D= zx%7giq%DRb@1CQ2RV&N#j~M9Oahe~v@A*gl!QuYWrH2-;w}tpga10q4*@IhXvAj-; z5maHlEX3}oB7#05;*R|1Q&jkR_qa^W$(%13;-4OKF;&4v?VTKX#J#M+&+l_a2~QKr z8=_)h+gnzixGTGXIz8IIF{`+*Afqy%G7y;N5+|zo$gob)Sy($d^iU^TVTqWrP~3>_ zA@OSjOj1gLpSv4NKjKdr19!|nTsz*MgtY{jWGiuTyz3gAYgdKJB{*K??8e0E)nNRX z&l()5cwsm6Z6XGzHS_GLhbONT6tl&z6|*^&%o0Q>o5=fgdA9KL zV84HCLT!iQ`BX#$E6X#!%Y9z^9nXUDB9y&vozHG`qqA%L)aAH-1C>Xgn)|fsh4V~Y z``38hIr`^)TGL^jL?Qj^^sK6Z8}SAs(bNHc$lhNj$G@!iAlMK7ZT72FZe(Qf>MChH zje@bo64HC8mW^SSmU^jK`4S#pka;MBx)A-k~bURc~< zsQ8w)c(`cR)XTb2&4UMi1Kv7u(Zs$rrI=jdjf8Vgg7{Dt)-v+v@75YJuN@k)&N*DC z>Pk$#x$G8)9iK>RByYgOEdE))E+&>+?B#8-y)^IFICmA+?6FN-PQ2YFA05UZSmEM7 zxfh;JaY_A8U-0Fv3HX8;_2@l4YPv!kKN#2WJ^qvP@MIT>Q{5k<*w}>fY7m#yaSf4zC)Xna zK5q05lonb=*G5x!nlPKX5QX?(c*?KoV5Cu2mN&BZUV-eS=wo+k%1bqwRi?Cxd^v>b zyFNE8DixjS7whP5i`=rB<(#R~95B@5gKuG)d0IX}I^KOt31T>`E^f&7qyN@;VTy|Y z>O~aJ^}p=*%d2U|OLz*U)=9|Ma&oF=8XPz4y%z_H7KsG+A|XJA>+*09)tEGkDqn!Z zX@eNmrs#sVcC{6tmgQl<7;;EEKZqc80%r6vDr4sulMc%)}4$kou7Jgim}n zl=9niP+v943IWFt9UnI-Xqu1uj&tTVUTepcTDrxESZpL@3YCtctL`0_n^|sPX6H3A z_Ng8-P5<1aoh^ZUqco}GkXCFWuFW9Qe3L!A(Tr_$shl2s=z!ynk%9DjHs@Ri@{%=Q zyIZ?-5Niqxc`bHaY){g?fU5-OV+lc&o*ruIVE>;+DIeZ=Swd#~X#1Q?hpNF(8oNop>M&~`Peno7h-BAZ;}mL9J*_Eq+! zdA_SXwyvki81EW;A&~^3b~v`uW*EhrAkEUVEXs!8Qi|UM4C!pJN8j=Xm}qm~;C8rU zGRxh<&&u=XlT&y}M;B0Cf{K3=N;>l{4n{O(WA=;+asNUuwQYEVGRt;FI+a*h_BYla zTf6~T->h%&sajcm=5?&gYSj1p_wHW8D>OgYeo@#w^lE(iBL=GM0QdmE=E_=4I?tjK zy+P=Dmq2|Womx&=(nN!z9Ma%su-(g%DtlD=Qr3F=frqJoU%Ne>qyxFG+1>iEALiZ+ z8t3E-bF7&af7a*L&Kl5b!HIZEwLuk;vx%8CNw?eVdVURxsj-!UE_I#ko);=wYt)7J z5R3O^W5wa(CsT@A9S<#ShN=%H@>3;{&4%y4x>2#T+!hl{hy%w-XJR~~)DYkHP$f}p zG*W$CF4x~+!79jxI9ajk4`YZGeM_@+8IF#c>OQEvN-U_&76Wh(j5)K^WZX>BeY*S9 zpVwSIo-tGF5VW1`5_(F2YkB!(KefT&ATY~%*G)XU!jh~MW4qS#ba)_wFV$~k%PC*J z8IWuKk%~F$3ls2+eD{2n%hv;PVJiKBO45Ww)lQS9W-Om*8(4|+=2<*dxzZ~y)vUbW zP4M2J>Ug;_V`64yOdz1Yd`^X=gfOGe$?P$>sOXXDAwl1q~3?0lB9)w;W^IRh+Y zX3mSPZ~o(8!TePZc#0~wjKLLDUA+};yk=o*sS?{H^-xJdc0>uUxiCM!eAB*JKVqEY zMY(DZgK@!&Nm!}qOq`;~f_@i^?1Z7bN4hwQ1~Ws`tpFGF=OZBRP1;a>3%Ge_1v zU%=LER9cTZe|hrf(EeH!))1vxX;HNG%$<{h^K3?)AZovBg8UJXxur@BHlt`g>BRIu z$XJHAG^dL`}qJZ{bQ=iWuaavqx#$eMY@rAL1?l*l)=FBSUyha1vPe1basKTnpbH$Udr+!#$%#C@P*v*R=Io|di1 zYOZ>3Jp8g{P{5WBy$AeEeW999aR9fFb7b;8LTx0jSJcQyl&Nfl?YG^9jIaSF{jv7mlx~{B!?Nxy`&l zk^bCxgG|cG>e4L518^Y!c9Q5`YnN^N9Ii{JU30DxyAe~OSbY1ArsR?h3_17!St6jF zHN0ZoZ;+AQW0;wiwG*HWkQG_1BsD+v3E3Xq^1~u!jt(`)zN8JF9C44q7)%@xIbvGE zNyL$lo$(*2-lsNSOP^j6HBT}sSMGYKbSG;=?{KWyw)nJBE;vdlXFu01KUbxDf3xmW zsXcnhA5LKF7+wh-c;~hZpl;P>nP0u|S?#dmxB#QV>F$L7cX?WkPp_asZK~s<w|$$unbNu|S` zJ|B|%77sutRUqHAmomNNJ#Hq0?CLbbrH@D`=?8!;bqOTjUFp#6==6=aF7lIe*}*-^ zYFE1gUdA3L0a-KZ>|l?1V{RtW0c-9>mhK+IJbAgEJ>fLbc)NW6uCo#g>*hb_0A?}Q z;Eotf#i&xzN=yKiV~R#Rd74gqW1E`v8Ob9Jig5o zNds8DpK_ID64P8*Z}6>UQsMACz$@pwQBl4_M0tX0`wHcWxNSnp6WYr({3jbmL_ix= z;SSoUH=E$g$wrWF1r))niJ09UJG1DW_k$6)5Kx|Ajk(Ex((oZ49DIz(9C#rHJs-EO zxfW4u{H6zRAAaMZa#;gw0c402G3I@$I9*jh8|u63hCHll6pwR8tE+S z{L3=Gc1?EdU`OU5qwib_Ta3mu_#xcz^SCrEs$k=^<@=g)e^y`POV%Hw6 zja1w>TBgnpuut(GbQtYTy5#?q#gAy3eB0dsTNm*LrP$J17)qtKQj{je#~N|bOPM_H{e1ZC#_PDI~8y*>QS#|u~kzxKA* z+W+Eh@tcEZr1?PA&%xbWpFouj869q@1UYgRZ1c|iXz84+2abV#D6ie#>=+v7^Yfb> zvhHxsOf)pNtA%&fE2^pXw9OIX?R1qNa$Ul+0q}lwU(3jSq5M^TPuo+hiEh8wg{Z9Y zy!)@lYl`>VI5hXMzqk71{>t%rS2cUpXGsN)K|(+4RDjxkNg)T(kN*C4_+DM$jKvb` zDknt};_L3u*r57qu0UAl0|Ts8Z9ctj^{_gBX*O=Z2l~T@TTxFv)4L)M4?VleY{e&? z_CgQI#6ojUE4b%Y?otuY1P|p23Oo!OqUIC1eJ)+Bu%(6f(DZ=}10gKK*F;A@#qvcC znVnYauV|W_&WZWEd0XE4SS&s7{!kYW;erul) zOHXuBym+2+;|{}8FEPcYhwLvreFGDy8d~PZ%)=!(J9Xg#>;ucj!r^XjHMfrj-gBow z0ZWDs_izzqDPnb5YaU#eCC^@%$xu1#+cA03H4nqtEt*=z8t~aEe}8-=N>$|!j)2@8es+sEznVT z4w)QoIAjh3=eo?Rv^z6AdgvZbE=Wmf)kGfU3Aab%)dffKtTNU^UdIha^TJOP0{jOX zONaD9ca-E-=RR$CJl5lAe8t#TW%znLkI}!+9Kd87Gp=}t-BpL7=5Pqe?(R)}akW9{ zy)05TvE8T0u&qhX!;UxF44Sdvj1rw4aGlrOya!QHROiWlxM~}xQ=3JR)g2qe#ZGbF zEmV4aARpA62)4V*(Lv40@HJ}Zj-OL$)FJ!b!zloAR1c3zbm$yYV`AREnTo8zhaEY~ z(5$%3`7NhCnrPkw+CeehWLHZYscp^1-KfKXmAHP`-D_o*hACoPEt}o-lRPb3K1U}R zzWs!IyD)9qSYw2*mC{)g3z*-{#csc#X1GRVX>HC?8w9fA&P+NWdCY9I3RhkVD5acE z;K+&joA(+azdR%pe9sa9NP4vM4gu$Vcf#!r?EchO$3zA#rauMII6$h@I|V1Ti*+tE^VFx=kN z{3n-t&mWy9(m-twusVx3>MQ*!N-SUpl=~{vy~-tr#Z4z z@Q;HcA3Ah_>olwc-C-E==I38^jB=ltrw;~`BPNLXfm$tOjag8zl!M8sI>4Y1{^iXB z)V?u&s%v%{M`Xxnhape+-rUe3DZ2f#r>9&6RUZzA>ON?@Kq?Ql$ve2Y#; zlP9qq&Cn^LB2oj=Idpdk zNDBxIUDDkx@bAHR^f~W&&-Z&>Dj?`MtiASHca0KCPPb;7j*rQY5tClJ0s6fT>->9* z*AeKW+LJ3_+WZftEY^3_65m{P&7vfqk?DGTt|A4+`A*$q%?~8kRGwmzDBlCCC@rF* zH6o2&R}G-u9%nQ4)f=br8zdL{dH!-#+xn4~#=b7av(mgi)by`}Uu`Na^4WVfA%s2+ zgY^i=e6IxKR!Js+_zxG`^k-cyI&O{;(aEtni{J@^IzKrpQ5tP>X!*RHK-D!k@{KEk z>rJYp?ee!83>fOG57Ih=!ji=*H_(c(vSGn@`n=5rO0MRxvVzLP?W8UUN;;hev*{01 zZ`p!vQPWsc-jtg~q!&+@NRaoeQNyt{_ba}|dQ&bwhAv*A}2ed&3=U=FC5~+rT;67h&fI)4!VWs_SXi0;Me^ z?a@G~R&IwW2HQD|Q6A2wWWs%ZSmeZ+n1c_WlbjA?U-39|Y>BZ!srNjD7ZmIle=2R! zp@dUjMzXf2rEoT!nHHpVhgVE)+bRFN!r55c|wud5)}nrC5eF9 zb$+fCtR?n}*S3y_A5auNHWJ>rvMjSHX}nl{TwJ24gj;1S*0tHpktZ84@KkanucXX9*U}~*^p%<6M>G*4RovT_(+qVP5&;~sdNO^c{_LE;lRBjSwzG%5=`VvvYG8~ z#JR=+fmsc!{TN3b&y&N3JA@oKZOFHsJ0X>4~V@NMQfHh=9I*uCOp%Enieiz4=L`r7bHa-aXa*1{bCRCsyePjR*Z>uQ6*;m1=sj1`<$qpMax-=5#P`M>BZ!3$M!X;d7fQS>704$?q76U2?pq$e zj3g^{8XcAP7WvL~Q}9^PMAjappGo+>^|AO9l}AF_>|313AsREAdN_dM`XB2qNA<6| zJBj_@>kjhn|FQ1u*gYl(Y~NS#_QjfxG7TD(ph7(qBWcYDv)Dd*d{?QC=~23YjAYe} zz`>1D|ABudh$Ji@^tULXZc(-S^4JC+SSCpH%C7^~={KJhIZCnURbwEJR}4uh0u1(3 zv=_}_qMBWsUK{B!xr$gF^vkFWC0XQB)=Ro-!KxkW;eD)R1dF42RUPw+{gBZ3M5bZC zwijH&%7Y{3ev98~RH{fbbOI(h5$lpJ(F(eIGL_;m82|&DvwV)H4!rE69?e0%by4St zw?U)OyUIvuE+y>G9OA5cxcXf>^lANTyc4PND36 zEP42FM1iC=>*%(Oz9-r?;zpFkV^|MnKB>z#RB&~oK)KFrdD~BCUCzbTX*4VG)&yR0 zNTFIM=<*Ob_gt#6b4`bEcYkC56O;nlI;VVJb)Knpz)1Wr~|BMzoFj=k}El{gc4=U(Bg?Yq1fFAR4c-*K^rl*;CB zTq+WN6qeRkGrF+so8fR=G3lX^OT5ffXEESHrkWmaCik!D6Lrz_toDy9TYN9G7%WZ~ z)8<^&wRk~=Dy(8*vkI7;28xA<|Gb_gv70A_dw__i#}44~B{m;|U$z(k95p2$csZ&u z5&%rUfPc(y5)uS`n8r`^aNRIEU71(MWBQy2MDGgk!kYjdeY3?nhZcj)|F)f{B58;E zLBsNh06KR)-4Bk>j3Jq}sR7NH>Q-hEA6_pmLTg~cdo9Hu+XplMOQHaAO9{BIPr!RU z_>ttOWB>;U3k{*Yu1+$BMwTFnV804cu?|d_k(Dz2((4#S33l*CvO^lW?`bvbzR$^f z+0ol{SIRK`es_rY;ZO2#XD(U;qT_ zo4w+{Ha7T45BI5h7@_eR|AaXW29bok%KK%1YTbwY1SAAFq5m8k%x42s#ZvMy&wvzZ zbt^|tZ!L50HyLg}2I%I1L+$D*N4QF&->zH9)Phv|``F#~gLHD#C)vz-ZEQu6es>p< z0=)+2{<*kx+8P={1fQYrtxt4_~Vd~)Ff4a zFhr&gR6np%Qz1VljB7;#lnYr>_cS?@&yrsfis~0KfBkptGV&@THa1jgt5T^P>?MFU zs4m5tG0vVOGcGrlS{eHogDzzo_+t(+u7qVz2B=2P+Mi68Buy2+(U(`DA%OLBhmhMm z61CfyX~)(UCNzr&uq1cCM~V6>cmq=2BaRE564dr_0CUXx3ep6R&tX$)cX1R% z<0|_bsn-kD=a=zmAGh536vzfl|ROVh{^yf`mI{$O^$ns3eU9Wdl6(;r5sP*$O9(53IV{bjJX2K)BB zPdBcQ2@PQ9#n(_!tYNN$r7*2y(7;|MEX&NIRO(}elO^b@&txT63$K-^+*oSbtvAHw z&LE8XkR3v<@exToY$Qf4;a$W|0Qnt#uy{r6k_W)wya{Uz{53dbZ;z<71gjc}6(Po_ zq(=jPPU?#AyppuJtux%A)WS`+b+kv}`<#=eaZeEx+E&?q5Vp1f;eiK*oi09N6;ZiY zT7p%Q@8FYeh?r-#6)7WXkdkR`;>eQ3T}58k6JRY42tY>^En?JO$Wqg^Ghb{<0UAdO z{b%XO`~?lGDSZcgzF(i-Y2xi6p7FCY%Ef6)XO`}zEoAiXHN!T?*gg7gNaDlu?ro9? z_i-f9wRa3(Z2=E z^xLm9iuvLY&(B~xy86&3^09~3%i2Gg-de(+Dw`J*|4n5x@mfrf18if4qbExp2hD@v z@VM9P_B|awTdATywUhZR$8ak^C@2AZwc9gc^lhi?BV-a_+tvl@$Bx1skqkz)OsZPF zlWBt=HFM21mTY*-t#sqYUW&H7`F{AqyA;INd6K=~SseKRqEJm2R^s-TP8Unouzr+U z2otS2;fd|kq$;f1>eO%eM^&Y|APto|csZ1bn>4bt03=lvHW_u_OBGij#?b3bnW7lQ zyspvy7->Ir83nf!g5Puu*s|2QNU;nW{a^>PpXBs3r=Py%>u|&wF(pSz2&L+_pRth> zyGjzH8HJGynL$XcBvGx=@wR=uyf>Qk_m%}*tjw9?o9H{oz7-VCWTxlE57UX|d1K_i zx=>9emOMqfeF<=ZjZf_uJz`6CF;I&^}){ke5<_^g%{cjs#g@w zu1-~bUtX^n+V>#uKV-;SQ(daQmQQOLZ%mpt(cIK(c&Ix%nAtuObvsA!sB$(*nQ+#h z{{4jv$4=EIro^h}gun6Nbn5QIyt@hsFr&?-OUyMg-5_2p;$22fO{am18?<(;O-Bd; zonQubXv#OE5jTn*YRomG_HM?iG? z`Bd%J<*2C{$`1IKCSzqh&B4f+N+HiHmDO^S1SMB&$sv8g^HZKyPqfCNd|Kn%Ny=hO zct|pz(@#}NR#Bc1>{`EhRChkVvn+qEnKRqAl#8}*%2F#Knmo!WsX}7C%*Lqc=T90lGbpaq+J~PCJ9M zW;vV-a5}@ctJggO*|X^OEXUYB&Z{tj^;h|0)H}@js&5174ggBj#QTPAmZ;1wqrWk2 zH#Dc=37VV{<|h)}wH?y8*Z-Qmc8thu633*dgOdT1Len_ z%vBTh`<`CyvGudqxFCS^f8gX3Yr9ySe6NLs)?@i$-%of2j;zJASAP*SD-#T%%jpKePU+ zxKNDdYx4snB%L?c0n1i78Liw?)@{g9bLH=bf0Pwxq|?Q+rXk|p9p z-FF|Oc7@*Z66t$VO#9xJkbkd~%9-wLGXq0aLlH#uV4y&b{jgbi$|P^VXH0rm&5?y3 zH*Tdv)+eTh9=!HXXkJ%@JxOe05;mj*76jSj@0!ZY)#B{dKJc<8RyNgLDb!#^lSK(D z6j<5rhprJCTaabg(`ZGi?~3`1y)!tj71OE0zcjryx6C!b(_Cg2==0?DRX=c{sMDCS zumhTCV(jMo1k0DSbmDOA=OOIbufrhPDp3{!jILcAc{v5jrU)TxztF$1aLCnfl6Gxn zaT%~r<@0Jz6vV9?VeBPY4nCxEvf=Yv$^G>oR*dq#Gt2LTEA@?-ywqHJdn&jsY(vL~ z3x)STWj!`U@G_nU2!y+fTtI~D-GTZhTKc@^A=evWp#zfwDsNr_7cgkO^uD18T1#<~ zxFGoefJ@`RQ<(_Rbbge6E|+Ykr>I<%4Q%&KYR6N)e&A$h?q{X7xZ+A|YzoZ`N*v&6 z%;F4p|D;Xa(ts)-u59TJvAB28*)HR=YX~w7+`7q0xG9}+rx2yc^Y+;Kpj#nY#{MRc7CKA)ySOrZeNXbWnfA+fv~O7xNAmjg zqalW)%R}D&;<62>LkI3Mf&gIQjUIPEWn~z|QN&BKTw-N~Nbwn=O7CrrTujlt{nVq$ zsG}#(1|q}UW|H)9XZ_8SIs!6A$FNXcZ%@Amx<0}~POUt0Ig|%qM||$r*O}z#7%cj^ zynM%5=1aL4qxP!(=BE$_iZ{PSeZ%u5><;?boQeLy(jGut#0%cJ=;Q@Vb~R_bm$#@Y z71(+U18Y^=t+g>m0GPdb>xlcf8fgq4=vz$QXkL=IkdX+1t##s{MhRdGY|&s8>L)q5 z=!pK}b4N9dsT-dLT%?LJ6DA~7kH5Zq)N)5I(5EsM?`t3JYi$(tX=GHz%g!t(>s=<( zihJ)atD)@p;;&9!LdQUMVT@Y0gJN~xSQC)3q{`{2dO0n!D4o8!j%)HcBkJjM`#2^N z3d_e2t%)}tQ(mozxV6Q2LG8$&OsU|SLc77;I(OF91C`q_O$gMwmwqz8md@9O5bTqnOH;q-$jQn zu`+Oz!jQ7ei~mA)sFTg*P|zMBr=%594q!-AZeF{a7Z)~WA3d)y>R!QU)-7hcg-{%B zYxtI`zKC4@q&$oTA3yh_YE8d&OyR6&I$<$mO-wN1+&OUd`B%qV)MU}Rq=~xjN_J>A zJ?!~y$)ytMY26ULFttw`2vvN$B?JE`P>T}LTHZ?mf<#K{XbE16^3|g>*kdHg#}9?$ ztr&`|9ro_>E?B0R8FYn3APMickIZk;Jl#MkS@~AM;j`lR`GSbqk%jUd_o_+rIV$ze@p`5txys7Jz>bSCjLCdR_#)t~^ts~W?4GpTd6hzIhKCG5CQ=%G zV2p}0`od_Fs6-;1N`5DrWZ&zS@dOj>RDW23{cZ>l8@=%P3^lelu7M}65}GL~`czlk zr+hj&CZ~X(^KbboU025EH281fsv3Sz!sCY&CxEbo=!EZ`aBwD8Qa$wniUDVrSt#5r z5Pg*)u?B=tywSHJG$R%BWpnwNamGIs8P+*o1_pp&ktpg^A2KBn?pf44NIoX_5r2&O zYilLV0}7kd)$q@bbEbIu7}c(}&^vLhnzn?m?xGOCZ@6;%t2|T)^|rRL_i%K&z5QU0 zLq9*lfKY8F)#-IO7d{tyhdsj4KQ*g`&}@F3NGy(2{D4LfJ>zwxCD#UhaOS>IhUzs%;yHRSma{|{SB|y(t`_H z^Jjzqf;}(6lKn;>UOLUzmBIl)oDaa@LZk>4C)`=AxL(<9B(-A=IC38j+bnwS%7%q*G6~j7so7FJR}eledMW zWG;Pukvkj4)qdu%SrmEyF+0aWaGUE}jia`skz@py)btHzxS2q6Gf+7lu>`C zO;eZfGVyf*xSIa0_qOgk|==Le%Vg+oU+f7P|8nf*rnUT?y)RKMHC`GY8f zVA++PF$&HDutrwL&Aod=NMZQW=fP0EuVPsV6R&02Dzn#4w?@!S$!O%h(@T+Exs&jD zD+Y3ng+GKdarb^JQUK*pT3Rp~!QtIjLar35{uSw&Z%7*Lu91kTXq4076OpNv|Dfbz zPb2OYPd$a;RcXo|q=1`ofh{Nep{HSjcLe{4MdBZN24GhM%my{*qnDHv?+;FR=#3mJ z#*ilCD*$GE;Y8t$D5_R}r>Bb$8I|Q-JKdeD0$P~IHq1q9>~+$k7wd7cYtcqy7Z!l4 z9xuE-?_u*eD=j)aUQ9fV{|Nj?j1j<)X0+v6B(c2_#wX5b@8#2er!~ATi&1W zYq0(O4jp&+LgmT)>{Gs_ip#*2ghjOVd;Ur#2RLrdePdt?P~F3wwD+`VPg2Rh(A4kz z7{aKIHHmWA6HECJTSAWgwM2Y@XzsVo#7%wI*;(ESh)QupyI>x<2`>x@#F*-w2!lZe45vhf7dD?At42k6;j$?O{-p7cpA?R|On+Io?H~)2J zDERB$xh5CIjaq{R9CA$MM<|6xNn-9YQtzd%^Y%5n5~*XDe+|u4!v)f`1gv}N1;f6W zxu~z$>b&y`^aL?>V=JXUNOJ54K#G*2^s&13jv9+|pA}bV=UkJV3{43tm07;JKOa$8 zc<3MjZ4sqT`)Rw{0ge`6zmZds&-pR&!8JZD)rn4IO`-g<+5K}gWx0jAP!bgI+e>z9 zt{G0{_(+*PDy^JhkDqs9K5u|%XK*XbT z%t?-FPxYcdf@omI2qs9nlu2Q zZuBJIZ`+@`(Ckhb3HT}(H5gfH2i`A_-}T7Hr}icf$qnA`aQ6Fg@6-q5 zp$#uYL#u5fo^-ICPL0e?azA>vVKdW@!336Z>SmARrOi(wyq)Anz#N4<@?EHGnu4bY zi~c=2!yQBBlX}=UE=@L34zfD+Avbph&C-lmpBpmzoI)5o&5jRi9_i`Qi#o5iVapAWZE8s4cgySh+?GW1fFJjg?oZANR)w`q0|pr zbGfl7j&o^~P;>8Jy6Q$2mENusz|<0@iB~ADq3P1DTctO1I!7s~ZfEK}_D&cYThgn3 z!-oquIRxR5dayY2$y)hpK1B{W=1Ve~uZ-vx0R-^t$t47;)SZmo$qvn`xX{{rqQTFl zsqR?-(TN!{lrUrZvK0a6i19R#$o;gpI@d8}`4PrQnIt+r3V*Cc26nyNP9 z7O10y5p_1#=C>as#KkPENzopETU?Ee5kj`T;yRhjkKr?5(guYI{6Z8-uG(@$PbZ0V zKCud92R%hCuPuEj9T4dzq$lEy&dij}^=4qgpkLt~GNvfN!IT=?FAqGpv9v|5=zMC} zndt3I?4k1felf7mIM3RIlNtt22RV^W0sX5FrZi=}l!R*Q#RmW);U_(T@q`DbJ4U8) z?1KrkkNomKM*6#M$$(2?3HSyGcg~wQZ6^@Yh@vX>m!21P`Oq*+&3+ZK4i(KWD-R#u z<=;@eP{={Hs(^gHP7`RGYr&oU4+y+fd10ys`1Q0s(4+vvm(_qIE+9^7v7T0QD#X*%Gi9#>%p84vi9~!a9nXK~jbV_`~B)e;LX2ADK(vnLwjH zc$TtoGq%gH#dK0M(u0+zAZ41%Q`Q$WyPpf z(u-HJi@@7+b0bEl^vPa;6ZNh`v#8DNxok?|e$We20_gV%Vad))ajv6Ngt8zL0=_sD zU};W@L^(e#iQ$8JHjRHp|0&2l`U<WH!GzRb-}>+uP$JLX;Z^&M&^K+f*ReUig=#5KuCN-pILP3XREdVbwAARJWqlscf$MKog`I)83qhL4!H@=7`K-_%e z1v+hcA$rdp>dI<1e5r4Hr#IPEa_)8O%R!h~km=rC+(Ua(65&C7$Ev_EK&bDhyt~8p z%ZA08sdz0jn~FQSSrs9)URwMWSI}Jfey)dyL{m{}J%pL4>pNdem&;@zIDcl>fM4zM zJJoi%{?|%NtxoL7vjetr&clUu?Cibv7M(XTO_}NR$kodYJ`dYB6 zgnL89a$+(uhV#S@-d*Br+71_XR;W%)YfrS0fu*z$I7PwsG9TQ*SwN3$CaC#ZS!1~5xgF|xsq^c>k*uULt4x@`S?<23m$j0lyK0Z8o z->08A0f5#BG6yMmC6Jc8>i*4r|tZX0)~7hT;QzRHGL;UTqlj zNq6jP+<(nk7r)PWlMU&8c8)_RVYpiyN~Z_`-(2ltSc+i*!`uZQ zOebaOo$X~Pv)*?Yy^^%cI7E23YkxQ$ba^O91<25h#&u?o@0dWY&0{b?yIw;HOaZ~Q zhSE2!7;RYZ_;mH+#33k`2^}IE@JE-U?#~*s(nfLKKE6Zn$HUUTkRqB=m+)6;qp@RL zLB53K|EeKR4443mf~BYi(%XtAf<(V)8(wH)HM$cbBG8#uPAGoHu@D+s(q7LJlPq>{ zRY5q4xhwy16z_hQBor`jY&-S(&v`h7&|VnHojfYfYGBBspa6T0`Z~5WWUzjW_Rn4{(T1c=6h+%z<#g3Yxke_` zaQeQmtis`4-&!oD_j4P%F1C55+M}EAf$d87R1{xj&QcB2GOoYhz)2QQC0>&!>$}DW zAM#uHc1P3V_xPVzE;j*TA|L~^W-7ALzE$XwV!{0pfA9PW?%>$nYACwo z=DKD|Ix7yP!AMmA+jF=8-(yq~M1f@Js!Ks*#djC04#IDpjl`x6BkmJFm7pe(Ekdw>cl#80ST>fSS6{888$OLFpx zh{Be(A$&}Rxn4u0WSm9cmL_lOiEu%ey(lv=1Q)})nHi>Z)3%q}@%jt3l`m>Wvf>F( zxEf6{BwjKTbY)!FZ~iD1w=(bu2i-jciJoHRh@&lZyy)hxcGff|nLqO{D_pCjshQbl z>As?Z0sx^bXW{M^%%{xAjL@#eY0enYhRadZ{ZOOJ2lH+fd3g^S2I_kboK1HBd~shS zcV-Ys@GSoT107jU7dStFWlbC&5D#+3IwcrOsiv4$#BwJ;yJP_pn#wdz0@iJ74B2Oi zS$U9rze(Te*#P#(PF;q=I@CA+W7c1nV)+lv6xvSl)s4(pQdvG9nAuZ1l#gQcqeniV zVd9X+_6@>0><;G7a-9NgKO1eGNv8RT-*_UjF`=%aZPHdToyAikuo4aB(UcU_vlKcl$vPbDBIe2hFx!xQMw9-QY2AfYruCG6OXz+~-KWLTjGR=c> z#yEqV*LmU6qq z2N-+oT8bA&lopA`$E!c0ji z2qMsm!Crxj5a^j#SH$Yehj&P+fWmyF|L~BiSW-IA#MX4D5t#2Ox&(Mm?c0KF(|oSV zq__l1LZu?BhnehYH=1mqSOK@-rrOGaD^>3li6mdtvVHulZTYe-EcWx40K&zA{7={& z2s_y8VNd5SlNdt~c*4#$tx*U6V;1r5jVn^&Q06scYe{-16C)m8r}(4TMW0PhJR z(PU+*0@$z$V&Bg_J9s%B6_4-|+H7BJwmOdvIxr09BV+)GEOsGlJ>M+)?HbG*Fa)Et z?A|Zoo@yQ&^Y4QC&OL;=fdL_HCdBNiw#r^he9Q|@j93(H%+ zq7KddlWdlwvDe%t0;4oH(!{JxcsFjKK^WQYK=UMFj*`Z4yZzr37eWZOT%|OaP&%P# zywU@AAsD?`RJ-6ptCn)2pa5E$@UsbERE|MfL5(a%w3wSIZ^>bn0;88Bf5eC7Y)|_I zo`KIG9J&?BUeLyv(5Zdtby5a=(vd`$rJPZaSi)1%?v}2Sw&G)DgMPFZ3r@K(Frg## zIbU4UHGDIG9L8NBJJ}*oW$x`vxkZ6cQ=@I7J>`L`qD#E+`^dyfZb!nF0>sf6{m>P2 z0n?hsdpUo#xO059fMKu2OJyYXU{zqSr5G`e0<ePu};A4@4U?7 zHg!?8J)`?6Wfnf@*ceAFUpuIQg9VB(EoR}UaH@@9f2;AjFnb5!fU912rb3hNf7aoj zAMwlwc0eVjIM^QL_C9i%8c zJOlG;@Z2kqtVPr|w|(|mOiJ4q@ z_>Zm>l^}+-;vZdU(2|{{RAXDpv2Qq>s!lAxh$`v$k9d}lLaMRCM=I7@Kp|wv0X_2I zh}o;f3Hvict2*`~U;_9Tw-(N}7f!3QC2Bd7eQt>{T%LLrY2jf*dPwm$PJu6OJN%gQ z1$#F5b|P#grZvw&=8_cVY}U(Ij_HzVaVLsbX4=ZrAXEZ?-9dq^bKwRh8)cbOd95wh z^wL^S|xE_TY$|L+t+e)P*k@|UxONF+#+%WnnD#RCUCUZIJb9qy(i95hPSK^us zQPUb-q!d!Lq!S-)yvjflm3`1FeAK^zA}N_oq#vfoKML%BFLVJvAvQB8bBfy6F3|5j ze?Jeu^e7?Iz0vtAQIe^ZWMqf@!7KL(w0z0H<(M9NMGm)csxrwk&5l%>lA0cl<#nbQ zyAKF7c;Lw#{5dX}x~JqPX0#LD>x}hO-?-LS%d;g0OL{t_Btkh_Led^j&PVm`%LxBL zQmfvm;yx@>x#2U0#xdM&Hym0p{OPg^N*E0OS>JO+y5YXFd=CeBXm($#81t@=Dos>~ z;E>m|2rl}eO8@Dz!MxZ0vf!n>e1|4$u~NON=aO_~Fh;a}Wd0h9UDuCWo~V28_zx}4 zpC$Y2ZC{I@nPBM)x&Or_KrlNdDk$)mF500^>v$=F=U)in1wOcmpvhWmH@XJjYC+WL zBQ@tiURba}NcLPIJhE(^uH(Lkp?)~)3gmvBG$Ug3a(0P{2A_?Pfv&obJ4{|fGH!Aq zGBuPw0=21c3zI9#ojtb5E`x#UHLV8Ki=Ki21)YO`fr5yBygyj~Ydrn%U*Z~n1A$xG zWJ7x?!M|FvWvhQ*VAR)$?V9Z9wD6tqD{B-y_kJcxTm;&<)mc~xPuNOHr4HlkcClu7#mS$v zt-^4VEzXT5l$8S>(M>4Cl?LEYB+=E+o-D!KULK4}k)_n^&}bdZ7F>8Hg|^Wy;w zcgCmijil0Z@Uo>9Zdymy+Jin&HW=$?LoM5pZ~4^a7{i8qLjw(UU1qNU?=8pIR5@)A z;VlZe`}iT`<tEs$t$BlnKswI5c6K>J1aK{rCbK56?I1GxWkpG`9V0_$Wuv~ zz{J&YB7F>Ck}r+j73f0k%nJQ&8Nb1ND(YB3UbKF*RKq`42&C~qd%S+a`%fT-_~=jj%XoL&h2AVWp4tP1vwYF5nC(Uc44mcH&Th|d zglVM@MwUrL?Ak0HgqBsStb=L77AN$labWb}QOR)WGaIs~i8J9&8zl9_>j)68E1Ls^ z8s6%1p`Pa>VC>Y8@CgH>!jj|rX#lPLz!YqE7t$l)o4Ll^b&&Hf!dAQsU$uip%@ zX1S6zY&?HHYz{wpCXp+zCf?(}N2Sm~ttqeabN`}7>|gseTYP8P2ksKlY?p_N3Al~P z*;A3!WyaxY3y|()9bQM}{@N|rbWos>>~3Q>urR?FYfP4h6cP&oyj#s9i^qOlwr^Va z7Tt{zu-B{7v#Y=FQx;GSY!L=v1l529IX3M^h}Wn1$)7TJr*Ceqz@lCkxihuHVZ zE1ggk;ayqo7zX{gC#`w&n`C+_rvNvPTKd#7>Fz>!mZan^OFf0;{>PtXYc6S~BU96p zMiNiefgoX*k3Wnak33tmxqM4oUWK~RB6#Kn5a7dVh>_dZKbvhYooj5LMzOVtG8O+MoT{!fsLZHyBr0Le z_BpnUs5(w?k8&&!VygTjGWr`*_|>ET`ThTK_`Slp_oWAHyL4rU+SYPA{jJU~8CeBv z=~@2IgjTQS^S1ctymL^^^Nf*amF5r?M2Umu{msa^LlNR}zHz?eF)z+HCwBv<#nkX0O3m>_P+k8}=mjE#_hg^&<}Hv>}!gS$H-_<|c)>4kJKvgJ<-5kdu_kt>T~Q@O2t zKmluylWk)|mm>{k7PVmq4RkZ_EHA-i#`@9#Gz2AI$coEtk6fz5%`BKynyysFNhlm@ zXMel5jIo?4xr%NE{CYz=y@Fo z=T6xGZp}_o?T@PRdo}hdxIWykjjPByPj&}MNMsf^DUL>9W(f|6|Kl)kK8(AjPybL7 zbKVE$MbTxdv(2VH;Z+QO$laiU#YR-POv@Fz{<*Jw%Az60*wwvwQE(cDis@nu(IAYG z&3e$!@}VZD`mV-h<2^3sqdM`Cx1uXnHn;ywIQ`)Y22gc*+loGWxGLlAgWMnKU5Nt% z|CIZGe2X6qvG7eTm5}Nof=cfqX=nI9RPjRt4#J}-s<7p-z!w`$zp}CNgjuSyH-dld za8aT;G}(0Qy(8#xDm(c9LT0b(aPv^h=l@lWfY?BHK;+`JnLf*M&_{i8C5tx9f17R1 z4^ve{l9ng67FPO^o5#+S)BiJV9lKlwPWvZZoS-D*tvxXFa=Vg2xj7U;yzGYVOp|r> zp$j0d+=0@g7ud6#xe;nG%>bt9AewA1MN_Sai#*ZP<^zjMyQ0j>vtjHuBC)YzD;)rV z|G16?!Ir=vrVEdUr=2taP@lfmkZdt}R;T%0273PbQx~B6UOPcFznD@s(d9`ycxEwO z`e1RKcgOXdE;O0unC16f@>Y_$wDaNJt92{uRX*4AUxq2{w=uz*C8a9D!#6+{K?#aN z1y=F>H+lesY#qx`A$ojNgvegih_?Y|RF#*`+m~^*`!Ju%25RI!Pl-RZ0u>5EPkDe^ zom$$TUvBXWfBaj^Ei+N5hDFw#o|eO?KeicQn*Nj`Vc z!{IdPL-DB*-7nTJeKNw*&PdNU4RKs~T+xB1TokD;C^X z7I?xR{TFe=C5>?*?3p81aHkM>37Ci)E_3xuy{_ccNToK#Bzv9cPo2bEU!0cyl`Yxz-)OmA2l&5`}_29$W$C_Jz0T9Edo)n#<40WAJLvmoKT$yr|^?2l2E zMpnNXm)^wg<@)ZJnB-jPifEPrmCq+gF>&s=!{gc}29g(6+ZcO?t`nqD)9p+Gle31U zN>=tmq$0MprB_2_X*vx2wjaCF%`EH{A(p~q?|%2cWCvf+H0~ma3O!X&N*x4n>F|Jkjz?2|MJ$1;(P23B`apW*FzLY@=@ zW)gD8AJivSI2b=%f!!E<24AAvDWWpsJto#4nD19b)Eqs(awB&?hUwSr_Ja5|szFmG zwxhbYZ0lG#q-dbi>tDidlXG{T8xI?)HVxG+gr^730D$R(UQy z>_R!!aeVqCuN_PSijiiOwS@><-d3Xx@F%@YPK^hayx3tzm5v}F$=aS_q3HS#a>g&# z2_VN33UVyRV?1;LM0OzLFqn=8n(Qe^);dCb&wuS^1-bfA4IXoo>lg*xD*F0T6j`W0OYEmkhF^&b}j#lmouI){1a=jf5y6m zgtCz~G|u~hn$+kWB70;G#XYVYWEyDB^4bYL zzREftX&XT(YchhZ)9pk6>n{}#bDEce}U_a9Y$Rhj73jHNKb`|YuD3o zIYwfZDvak-mwOuvk2%YYPhnj?nAGJw`9bAv^sb7zSF^6_o?o&oVNdUlhwY@pYSSl~ zu#M7AzAx=8!Q>Se>;|{9uS~A-#RmqCRk#^~sITL3v`6;e`%Y4p4>kbvsjETglC($|CX2N&TJK6EYD{rD& z%Y_u_E@VGenB2&wpe%X>okfFrPm02@NV*SSB7Ge<;IdgeULz~6?8nK;`0TtqEW*`> zhK3aLPYgR{s~AFGN(`YHRa?|tOononH`j?c5uTw1d?UKvAMX&of_ z@WHUIA1_WsS25!)nfuwpeMZWsYX;-=9kU}fXE3Rp{s?}z6ps2EmV^41D_d@sE7mLv z-M1B#5>+CCs@IG(no&QL@*3W}&s^e@W}W?7BX~InGD}@SlEs~1kJejK{2`|? zJ>^!UbYfq;x@RqLxD-mfoUD>PhC(JUL)67rXTE1~^H1(5L>M5Fz$QY1#$9SkoejRG zQkKe=)_{ldX|$x7w<4Q5=$XWIIo|F8l{Ta*&92+9aOgq>&tvOFcCI=(r)7jry!|y4ah(d9Cl1RQTVfuirU2!QEl%=je#? zm*|s;z1GV!>E{fAd=OBEWywq~f3dprAh(_}bUpK;_1j0|C zNM1@JV>ziQ@BJN{D|pRR(VWhZdq+kaxS~sOQKA*vg4ak-rz|supdl;Q5ve0 z_KPtJKIj+JxV751Nkg|%Tb|2g{Gk8L<8`IO`Wm(dk#XCY(rvj&i0In3{0Cd@zUSFh z3PqADtEzrgzPQ`ER-1JFf!2)lCm*a9A&Y7~H;u{iZ=>bqt)(kmAE-mo6P;5R@g(W@ zyA2~3^dvEx*|Q zZL#r+)cb}tcU7h$m2P(+<^cS`_}LDCflot<0D96m$#(`>+tHKTI~iGM!S5Kaa7!yT zbw6DPFA*BceKs5#U+1xHQxJPpm2>!gaD(>)qY)o;E%+64bjYBvg!(~UEX6~lczRz;JHYy6$1t7`#6xmm-jePxr<>Fbkj zWV^yC9DC|?-H6zI^CX(-&(#K0Z!pASEW2cy+&L6}*C4>8;c!s=aclWr_fA8W z977VChqdpG0z->{SP7s@P0v=y3f*{WRSC6CCt=O^VMhdk2Rkn=#4Hzd9gyOJ$V zt|B7J+>`OxwB|TFeZw}XgQ(<9rBX(3>7){e_l)i4@ha2EifNT*336%Zg7chSfk5$^ z`NkJWz*S|sdMYeZDQa+vkCg9s6t))(F2D#}-a-CRP*!K;JI`<1S}o2+%zvzcCl&N?$X?5Qd@ zSNbm4e6gqXgQ3_xx;n4zNFrI`ymb6^5m>e69HWtx4-hf<@r}SKXHeLj8YjQ%YnnyAo2? zkv&V4Eo;`X481Ads$8{XK9m*ht{pO1JAGSM;sFAv$uUceYM>9>+FxWdLYQe zN?pDdimr3(x&AotHaPg1=u>m-1CrwW_oP{dB_H6<+xL z+SnGe3%3W=e5)H9rL4<9MT)8TP%3jzS2C7E(1T{4O7iASx}R4@$5e~2EwFk}CwE9l z6<~}4nsel;v&~5e<@mRv>c_a0E4L0V7MIqFM~r2aROeDxoXeJ!)7Ljr z>~0n${K#=D>e4VA7m#`~1OSQwrv{JJJ3oMNy6(piYvlZF){VP1q~#4x(VBL@@YHF? zW2<+E4L{GBm%kPd;Y#|(eeYtSHvI5*cI4S0ScR~Ven&oOk6ygxcU3ZOX1|R`;rm#{(Z$z2SrFx!Dnrvycgeo71d|cdq>gsrHw`C>AZeZKZ&3Vs8Yqh~z zPS6c2{6p9vHd(7w{@m&gq`Y7c(?zh9< zBP6M82Ld(1Vp_RA0qwo3*I{bt|?4!gx6jlGIV}-e_&H_$oOf&f+3zsJ^kvR8$QoxD!jj z@KlJ#DQ`_ZHCv1lUDmvt+~33P@6YIZPlAu{;r}xD)=z>@<-ZBOQOptGe$GojUhQ`8 zs+)*F^o-l!wF0n&x~9j2c498Zp`EEx2GWmjkGWir{d(~70)Gl(KY4Dk|IiyvbOYbb zBghW=FUxKNKTR{F=OEM*9F?aL!Oab-=wa;(P%9hM`i)ps>SH^1A* z+aUf!V2vHC)~y;ZT25hmW4X!CA~Ea}^{z0vAR>*F2PloI3zi(kg;v+eX0Z*7#VV7D zbM?6dG}bA{x|aU56D1!e24~)oRpn7Y&(b}Or^qH;@csmdtT3Cq)8x#i(6A|zCg0u65y(_ zX2)Z*)D{N!{jv8xw<%ZPOT3&voN)WiyTu$8xV7?&ZV}i^z@J+n2v#C!W+P!$r0#S; za9>ELR|QXumGdy%B|!07Gs{54mtiL+mx(GBho$0)xN|)NxW6R%bJ%)sYMyx(N0OuG zN9k<)$`*T{K6f^?UT_J%H=u(}NCG>Rxkuf9%RGsx6<8D)jss>d{ z6AR|?Fv;%Ox4G4A^AU~2T@|X|9GaHxiC>qB7Is2u2k=GW-j-bQBvsg&4x&>&pWB(D z0Vk@mA^teHBbnBsrdHS?Tn{LuUK((5=2A<)5W0rFX*a3ro~OLwYevlW12K3OQ2H(A z@65E)W7#)hg*(5ZjK(SU%}O4qYV?avhF0l%lpl9w1i$Dq-CXzbb+Nf~_ZSH`AX?B; zE{undqqzmY%w?5ab#u5y=ny!X{ybt+X=EBJ>#z%wV?9@5$-v42!$p>n^nrZLnl;Hl z7fbVV<4-QKSWTj`>*Z2IivG8HC13vOrT=@#ojhxP`)$mr@t*lL<*Jw#k~Hf`Jp2sd zMyi|ml_TGG9n@brnhvS@zlaO8di{*9o^q?Rt<48%^T-%&^u^a6K2<9Tq4JocIcRPr z`TUc~o2o=g4j6R5^$Fda-`%~wyu2I`& z>+VFc*jNQyd{XH71UZmgY8}9Mtp99z0ZAEef`8SS0)gK;r}E?g$*Xbr=vkMv zJ}u&bGl`Z4D=z<|09M?{(NR{&uvJWHmv zuw7Un)2XbZt<?ex_SB+IZPjhXkfq|H}^`>PFv2fc_b z0l*$jlhx92=d$*?fMQTC%;vn29m#z;+bQt*xU%o-TqhjuYotwt8TF_=O=!NTun~+R z#nD0R4;$tIFKsp+W$voQzFL@gi`p`idCs?y5LsMQCY){RIuwSwzZHILbBA#KE6uU4 z?kP-cFUk^Dv)ZAP)P0W$a-{3N^EO!h1F7LDK-;nzE;%1KysLc2RVUN?z?C$VE_7as z76JFxiEPPX)%9SFZJub^w`Rh}DsDZx{NJGX&u^WNacbHV4@u(qOlJB6#e{$Aww(|A zqf-wnXV=Hl?&+DG?dIn=-~EV)--B%~AtGWgs_$#RL(S{M;ib1Op2k-mR>Tws2D>%*G&6<5&QfGB`*b41CI?(x0AY^ZRX1RSAuJK(m;e5iNcyaa$sfEk{&;d?C z^VJs_wYUzZxV`)xaLu(bV!wwjs{;?Q_p#e|chLTFR(XFyK0X@p-V{)Ea6Jc@zOfX` z5pBoKdqzQ2nEQVRckJ>L2g{Q_$$H$w6VRZ~!x{zIT+OJB4w(Ytx^4<>fayY+Fx1<8 ziAdpUMKxiV`(;(j*Sxm=QUQII-40wz4h(E1^TVK1j~UKYzxsq~fyz8(!+6lZ*r|MU z)wz+BJ?T;raSoT+5MG^#k5+;z+e4nAeHl^vtS_|5Z)+{i@iT~^$a7SL8M28TjEK9I zHwz+k2tjCMYs#L3bPnE}Cej&3}K_EiHaaZ}vPBIOg(pAEPFMsJ|$Z z<(6UQi*4b{b}Lvim?k)y4FN2eoYrT{Rt-B%3QRfg5FF@xwGD$f^$k>GKTVkXs1UDwI(!VNNtBx~SydWXx`KN>+`@GB~gNaf}X;J#o=$tV8QgeDPg7CDl9vOst6D z=SM?3@rP>Qc^1LGPMkr!@V&0pI+Hp%Tken#vCWAq8P##%R-@O8Sro{7YI2k2Jqukj z*Bh;g_-*}V-7kz*8Lo;32T^1v+%}bpprbsdP$QZg^Qss67;lbTI%S`&hD688hSQiW zEY{Y28cVZ9qLee7b_~8j!{Q&HnNm2l3}u*I{?^W}pmS@`L2Lj&bm5j21D0{4 z9jP>{>%cCEZSm$NixD zI=Ep8B)y=h(zNN|3M(tyI|?-2F(;Ana?iW(xOH|3ZQ)Dvf~_+NPMuJlAlRw9 zYW3J#{$VW$650dLU%h*us1ftg(dO&qrc^156kN# zxPH`JQL#aiKoKBn?mIG^IJU(R_{H(@LHR)7{P)+2Up?7YPSXm?ZMmRg{C}JSN4wp> zmL=ZQByI5b>XBsh@s8jz^Iysb%dl7(ouMj@4E^4Dht^8Nj53v&+8b-{J&x%Ri}KCL zwf~f*e?av%-{j>oQ?&)La4oGC_0!HfyGai`cv!COuqK~6M`=z>%EVUYveTKz%Moo%X{ zIU+1TqQ&u706ng!AYAg7P!34Le6A-Yyx{0`@>drkcvWdNO9-*W>zmyt%$A$$6fQh) zX|NMDU03sPtD;TOKM5~-9=nQj(2!&erw#nr$$oG)}Tp}z{7 zGP5_RJN!;h0juUaI$CMT?DG_fn?f&0e4e9!cO&Wctjh4xO}GrHM6rGjwt7A@VKnucf8 z+qZ4&X6(+DxKD;4jXe$?)0LH+xl~U8!YF_AN{jq52OB>&n75P6EFmgoP;49e4191~ zfN09+z^WIw|nS#ya#UA@o82Nh{!+}ZZDWg|8P=uKp9eBKfjfTYzK`tB)&3%Ka%$N z;ZdpKCtc%&PHgCb^&q&b)nWGb4z-_1Q_5<}>Nrm2daow;oJnGAF^2irG5s{I!2PI8 zeR3%Jj)$;S@dmE*7!t&F9v}YEd3;H`%lXYMY%H=EBAM`B{*=0ehRDvnaEh+(^6OJe zvax%o<)ylEU48xhDsYMy@;Ak+jlw)OMEYMs?#{ffs5CkJE9#+2K{7k&$vzsPats()XdVi@OpY$Z+KfKb#+R7n`LS+@#U<+duHijiij~h6J z?0pEhpFBKQSVFE#`&NTJ&%VL%?FFTL0t`XFki7d)lyDktjCCLuidn&HbAE0|)j%23 z`{bUDsj)mM(C*@K-Npt@@h7JXCVgUMr4EUUEnp0!bo7$0f%sB%n?gwGRR(@p2Jd$d zFXGb#EE;}KOTD>`LRo-A^`RjcpkDDwm?oQbIp=+jp5J>h3wx(BWg9e#mx6ltt2H+n zyuyEH?Ll2}VZTnv(p|*vkC!o_>>cnC5O-mUh%jfIj0IybOaqa(%pjN0)}B+?*!#N| zpi5JuH3+)$_v9Ab zh5K8Q3C7OrTUgMEu;(@M*_`2f!WsS*bReqwxHd@^78c$cVZHC_nD99~4^DPVs{%+4 zIN71G@^Dc(*v`WNbL&l^23y-~Vud;pd3Eu$a9bt;?~@Zg7c_hXsy&WFdhuE97dIk? z;v6+d?gQasB5%8TweGnW2X7Uw>Pq1GxYB!Qc&o^Fpw_B6mrg?NHhlbC@jJvn1MRQR zznZhMR={^&4%N&%O6|wZQW-dYUlld&Zt~fW>304;)>%iJ*IoY(-hl=% zc?5>pzUz^^zEVFAXO8XC{eC{3{Db!i(o%YYNe!6OY&L8FC7JR?@8s{}n#W5&thBJA zl#dNRNe<~6m%JwRW?tRV;nn-1uvwivyXN#Vl{V7g^I}Z;kqMlvs*=MZKEV8HqW_4! zr+L(3|0;J9(T%^=wczBluU?N&V=W5Lch5e4(cDKiObB_1oSPqxd9=gQ0Zz?he$~iN z_!uX9QJlzL3`R)JYeyXapoC`lqwJ3P4mxUEx-TEFwKP7Xa&?4nZBY=G*gS8nNYVl{ zk)*zyjPj$1uu2JorLA+SpcE8+t4ZFCw7iq`BTCh_%QK9owuDes$9H^cV zr%zq0LQm!1FwaI2j3zVO@l5OdgkAgjc1)O3Y;?y1)VmkK>1lN}JHryli*GN4=$}G# zs{viFx{Le5kV1HS8EFizp}H9KC2Re5N?3U#EDA)aM2$&xh%cEdX9&zNI`11ACI$a| z`eFgOqLJv8?OM%QHMIiWOn%2`t(`S=ZP0=^-o&dYQ5AO0Kr(jS`Ijd#C<4Q~9Hv>{ zoR?X}Z2h zv`ti$l<{FrU-IYg8J;1?H?|o+SnBc?Lwp~ep3nLt%4T$|ntXTf$jKe;*Z46R;7kNS zNVd9rMEfEaHyy2Tt-aPdi$dEBTKc*flsJ0tihhc}OzWZ=hDnpiq$9d!CSr8N0JP-Q zgQ|bVRNC416Y&jy2o3d=mfGtr=YEsT2*5(zjlQoqN%X{0FbymP=0+Ch`8z)H&SfMW zgCO$~Lqc5yn84S_LoTQ_^V8JRVVOI{jxrI`7P9LqirI-z2jC*H(>kIo4+ZRctEo{( zmlh=AA}*P8%{Rxb5u%gAtW{&~75K5huEj9OMsXvA%}E49ie3FJwHOK>Q}Wz)G1HQ` zX_|)LUw#7{pvYb?>wE$xB#?TT(NAB8E#*aD zcOp$Ye@&}$w_9I5(^Md}U+P^Ou%2#ki6EN1^M;un^5@Ich&iO!bbqKQ+Fm!5^G-l>KAB_E=Lu|u55zTse!F+o*1j1j=h)&3-MRJ}RJKxE zD3)G_SWhqunhERHgs{IxRqe3EQL+oO-vBzGTX!)&+xPNi{%^IsZXX1*M9h7ZGhsV> z+swf{ft<(*VmQ%bD2Bk{swN8IawJfKFQ2|~ABE@{?*Sm77)Ez44PY}fD*CycQUxYL7aazvT*JsR7T0s*Km7$V6Y zS+Caqc&jjIE>h>`;=a+)!ScPXxez7;a$E(f_LpGALU8)dj66z22v= z1e+GweV>bQMwvfRE&2U7REsmWVD?;rCB9Zn`ncQ1AdJP9)(2a`GO-EKjvni#cQ%#L zmd4Lf@euKT`k+#sKR~CC_!elX_>W~A`s7y*3y5tj_n$PfYdkRSi!JnJad=x9!O|Yr z$nbfGH0smJ$=TCX{obVGs#=T};R=C_ridFPbMww8b zPw60ig9{Y5)%fb3n*Z;)9Q8?9POzqK(B@6{nc=+TEnapUiE)cEh`;11*;$d>WOHE^ zHgxT_3y{9Qfg>%{xZQHCx-a?|Y^fDnV&!;fs8d*36EegE>h*|a*TEju-J{p}QI?|MH zkn1)NO)T2i0!ITz`>9}oH2KJBJo#z&Z}>&xJ2iuLR%eIC$a=ukA@9bIl&5SnRZexq zcI+x<;Rt^A)sc{Qt@00Ccq>!_xN$Q>Py+q}Y)4Z+q5b+Ork`6+6v8#b0Q(9a#e#|! zgVWtjoqit~F0792Zs@>p>VsN`azSC7lg2YGlAf2lU<|;YF)j88urZ)v7H!uL>ry4e z=(etUi(Z3^UjKLWx{B^$Q%YkJL#S3a5Sbmo%3jqbntL7OW-p+X& z#tieUvdm#3FcJtDg6Et?azfR7>25hko#1$5;BbwK$3Q&?dmPp6z*{U@jzm{)AW zoNTiaZqs_EdGl3rulc!o{3A z4MwwZF-LGYOyDNVmHoLri+YdAcx-JmzIm?oJFl|FO^LwqF=*ppGX6Nb6x&nweN?bB zny0sImP(atn)SAU6daz1gJ8fJ>ypa2hGvZaxl)rdz#WK8wKbq0?MOx%G(3BZ4YNi| zw`#QxPA$tA$8E?_S+`U{^=3!09kCEIWVZtJ^^D{O--aR}v`{peuKK64epC?Su6>@_SFzh%(f zLSa>rpN0?>J?9?>@0&^yzN(F5ep0|B?y?uI2RlBD zNr>odIAIs}E*)<-Rwj5KuDyHl9^>eQ#Bp>OkNf_EtsH!Z?o^GZr4#DS_Grnt3D+eN zmTP~Aws>7DoUKX5GG#SMZ(Eq*Q}k*qXF-Sx?c5`F;n-mT6KaK(0(ZGKO7pT0rS@~T zvGnGB;DG*)K|1Je)YG_T78ya~ht=rcAWEzYAd3C=fm6sD#M}T`?s$W6%0ZA-Jogdr z!#G1~E$72#DHXGnulAO?Ws!%WcN~aTvQ~V6d<4ZrfOEy1}~l{Sol?Cv++0X zq;@M6x(B1=?#)Ku$!< znezB^JP^3RaBI`XVL$Yec|RTk^_j&m4iQ3ceu8b!=T1lvKwxgu>&}*^S~O@)b$DAM z5p&OTNfpU*fK(!|UKV@}0&xOcE#l6iVzK>ZMa10wn>N)U({PXqCoFJ7E;hcIwQIhs zM1k_ksgPTaiJ?;Otpfa{ZUj>J0iiL)uY5PRkOP{p_%l*}%C1rtEJ#l9stVE#sm&|G^b@;)` zn}^mzEKacI>xEM~pTLp%OI-(T)R&HqOa`T-ul-@Uoon~PsjfhCMgJJN;dgNKp|jO_ zt`>XTI?o9099db7cPRal*boUHe$j-!DMhhurr0XcJ8g%c*uNmk;pN_4@z`J+1l!Q zo3~G=D0(9`_n)x5=R~=KUFE7*W;1W7%FUs+gsA+QKSK8OH6pDrY@n zU1619vEk}9N;7S`lH5<%`*}JU-U(`v@q7702*>BmXmJL=Ey?xzMjhF3hOa;>vbOTx zZFd_jx!y`KP$L8Tl;UujTe%$0P)vZfc{ZF|R=V|&kuxyAgpp;M#~4D#X>6n1*dxw@LW(qGFO~IH?YZ#h=8=ZsFFLp`rV}_A zrlebt*yk)aBB*&i4}vNj6TVX6*^;e&0tfcb+kMZbKN$c6*ivx&VYsT0cq9<7tiCNjozA z0jU`Sy9*j_!|I8C)=g-L(chvQv65m91S{Hwl{d*jzOS)1EC-U=GakX+74a%F4t zsl++-s&gTd;9hna#TpW2bd|4KCXOhcp3#>y<5#}@I0cM9l_mO_+NEUell6u^uF%^x z?q(94?_qk~D6^IN{3p)uL1#`p|E5Ie9^N8jT34EdXy943A>DjTW))-#8e)*E8yIUW zT^30-tmgsJb*FD@P6>hA3(FISrIVXA1EVN6wb`!=3}EMuc2`kJLsRH+-3BgLz&}YJ zv!5=?;%|u~Df75oWpiE=>hHGvSTR{fbuQWwN}EYHitE_a-B~*;dR5E@@8ximU1b-iikuk zRMPp^Zg+h-${7Ssgc6R~Xg!=vOts(Gw_uUk=o35%y4edaj_N22G-xvv6Y(GM0?86l zt5bLaX*~Hhmx&c+OX^TXExMnVQsQ`?oQ!H5Z3RS$T7M#U^a&8Vxo#U(2~sbbi!~1X zTaS=CbZ`Hisp3V&e*lU_?qpWo-oj7_QgL7gst;Llj>A7X(d74V(#4oXZF6Tq>W|aR zK$*qkW(H&Y7$Dl=_c_@9gS{g1xtr-={G~_+5_GBYS>?@vgFivN@dv0U zkb9OE7LE@e5sAfw0VmJHsDfWIq09A=Uos*8#|bK)W^;{)tSA<;tfZ`gv~v~qZ;Sb# z(dDYyk{L1+mO4to4$8NVNnGp&6M-+Qn`&M#rn=!{fDCd}B^~*e=n6Okf?Kx`w)=`( zxVT3O)m1W;o6mK1$oH#qWM`tN_R3PfaFGDFaAK-u5^&u>;>^29Q@(1Kp%Jfk4G^Hq)geR6gmjVjxaF~~(upEt46wec>HWRA5%wK!Vg8|A}j6yS8 zNpLSq#J|(5hRh5ABV!CfpGdt(!0@OXxkipuM!!nhDzGG8b%iIln4u#z-Z>dw)jWlG z4LbjGU_;xV)sdw{#)DNE+{&)ovyvNLGsu=T{O+DbRW$nzFuv#N5W9iBTO{is`0VRp zbJUn!R>QXZn}omJ_VIT*@t5D}3Qw?X>WlebVp-)RJI`)^u!uVuIN9m___eE$+G#~| zLX+=ZD)lS%IsP_lF1CeRM?fNZd?7}`4aOtb(9WdEl=#BY^@>^J#1nlx4Ea*Z^P^2I zdj1~An_szjUhXBx#eaqE{pm>c6Pu4*%c|<=gbb6qb^J4cRR{-+AY#*aI+c%0YVTJ%vp)hu7S?={pP%;$W#_xQC0y=#^4LOL%Th;EJJKDaflqOoiKld63(bH57Va2;2h5`8I-5pYgw- zQJL-9?`Vuj{9mIHFIotv0_$SS4;0%fVip3Z)GdJM2l>aB;NuvAPCH0X`} z=kYR@X1`vBtYlE>cslFTO3UG>ECRIH@m`RXHFroh?-S27KHpC#i&#^}D4{qxYWA}8 zlN%(w?EJM<)~HSt3%{O2p+-tjG@a{v6q&*#qP}E`HkS!hKcOAaC;2iPa!!c_*QWb z1=M6rDx(m`C;?zX88MmD+=0M{u>+;Vq_dkL$JNHXH1>{l8FA>hl;&8lhxdq9ksTY%Xul5+j!Oyo)@TY zr0}YL5l+RZRo?y6;`*+IIsG>wlT-hfkhw*dH$g;W#?K+>;GQ2~jTS~zWUD^4Co@b4 z&=a<>8rUxSGfCXFRv33JglLyzYx&9n94fkuYoWQlzs-~bx|!*?z&642pC%%OaAN#W zjwM1jqOwk&f#1zJHHEeO@66B(j>8mRrEN(0l||p`jV<-PBuxL1*|#OTt9B6OL$tz> z#^=D!zlS&0v1N>NPdE)J8)d+S0&kAu$xET7#(Jrytxww<1&#_mOH^gcQ;{xRz>Tmj zV=GJg8AX(Uo$}qX0Vq&j{sqBSISinst$t<&>XX2HbIFRREHXN=LyX@GEJ$?VtHE9=~7b*QW<(@ z972a-d~piS#*a(E5SD$DmI1CLuKv5)aTu#dqamn~#bg5aFDcp}okE zL&{~1F%lLJ=^Nj$$<}-b%?F zly)}{m; zMFg0VxU}dW(vEV5I@rSo`Tlpusq&&owfcKh9>IRlbym;`;(EvG?sD0_;n$j!W+@wT za*n!CiW)pT+F;zd045j+PAB(Co1=&!FLMBgCjx742TtilE%oIIw|qNo&kdv+OQ9fu zOVCOI)PNnCay^Ps8CCIS$bc0s=q|v6QJqW?UY2Vkmxnsj;vyBA`J%1W0i zx;MgOWmU6?Bj6=2an5jtTg_7M7_+wN3xeptL!4kmZjh`^@>mC-K}wI+Czr;&U*Tg^ zZtJYF94)ZUDj1xSRm{DrK>Da%;qE!kXE#Tw66g1(ZCj$oLN0+AA>jn*Jk5k+5RRam zETnX3wQUD9s)bjMbiNZedF|OvRj4hm3BMGze3%Nwn1n2FJpfyPH^$;rCi%h03G_VQ z+0o=mI+;?&%Co3v*R#q9XyC<;)!NKgPAT8taJL7upgdgXyQq*D>GscFVf@_xx>xvZ zzB!o#x`qGyeDftH$>`BVd@enM-A1o16L44MbV;{wPOsE28}*L>w_Fl#yb0t-)sM%l ztS3uqV&4yNex4Hp54Tk5iq%$~!5W|6{<|-}_eR`Sn?}aCrRFM)H>-!6#y;Zu-%s*# zWPZmUBtufit|Y57HpBC`UA`2i8KIeevzlrzseuyf`A3N*?*Z+A-E_q8Sj|A7l~5p^ z>)KWCrPSfRjl{ha^EFv!9R1>PBNCFfBj;C^E?cLUgv||HG9TkSr+cca-wDCgT+zJJ zB&_y+^aR^i+j6UFC9en85~MJ2KmfS$F}lDf3Rp|Y5=G1kp<6N9=Mx;zZe;r(z@Bvz z)_Y|O01rUwnI<7E8W=qsABu-~XD4e1oC|lbpM~o&s7ghPWgU4Udyw|Pl?`&I04{-E zd^b)6Q1@`s;>4iEI^3F=e-W?lMsW`s+%CKWuMQOHbNsams?5BfMCKd03PkOr0dh-Z z`fge?at0^LQ8Q$v4?$4_HrsdyBu>a)91mN-$9~uyHDfFYq!Vt=Btv(`At74C{7DL` z=^&khW}+5+2d!l@9F8I;2-S|@R#PYO${I~X0J{9ZteW+d?mfdbG-@nQ&~< z9WsAgB6!+aBb?v@ju;e(0%Gt@{&!*^*{pMIT=6f-S(`}xYB6Gwb(jg$dYYz?VEEwk z?z32axkbV@N$b(m+{990a8trETY(1&Hu@GHccm`IvGKzN4eCBRr-tU84OQXRf%!x5 zfdq&m=dbn6D5$`IB$8Wb1KE?pUOxk{nup@QN{vrtQvL1p)p{=_LPd|RXflc}1$r~> z^RB82v|qUbuT$OUD89!$Uk}{t!_>mjoKJ-GN0nGXj&z~_csXVPU+X(2gyAd2RPiVy zs}~W|)4W1TScE{cWVhb67R|HPMKZ{2(nrw!0Nk%~q>l#n?PZZBr-;hI24*OQIjs_I zH|1n6OTFD7zW5_FB&S3{C~*7hlI_uglBnhSo71XO+IKitWKD4ZB7CF@4rD(H_pv2ZKOi)c`w{&=h$MImUB5f#?~nhh zWB!-wr@TA?G?LE6n1L8fqWHmIO^bIY&*Ly-xK`?S@E#Z%L6Qs5gRNJ50r`*Dp${(R z?f08#^1XdoFmkM0c&yU2#$fHJZ6tRUNPb1%UyG!k*cJ8fj{FTLU8K}kn|0i#MN005@il_g;A2txAsbT|QT_f;PYaJZ zPwa79Q+JHiC2y5R0^LB%gfi5;9`C{W)}FPp`FHj*o_y5C(q-?>X?drE{)9Z;ynS73 z1GoXLNuP^Jy~hjVC@4$8I$=E24>BcOoL$79?Fo7F?bDOSc}eDL_54aD#0XdHik3d& z+4bm$)T2->_OeLZ$`8ZJ_o?v73>;`0JT}dBSgrww5T#BWf8h0KMSs@W`9{c+VTMJt zE7%O)*P;<_7D?TPr~>E#VT0P1kY-x)M=PyqIG5xLBzq48A)9^>US54$o;9vH^R1l# zdP9!&jI|6SYds47oL}+@)iIANvCryab&_oYDN) zGub8)9?!+(Feik2EMBHozeT+&0yc; zT)NA&exdpc0+a?V%&@?MOkGaNA1hYA7{H62flfut#wf8WpAtebNJ9O%1@kuKE()6m z<4Q|%V;rWBmwajT-C@A~rf_C>F?XTQ9BXr1qQeFfSgsy|sC zUq3ooa(bblDtFS_%J?sV|A`T&*5Y7KH z5C4a1;GcG4G4WhUz3uH|QR+-^4zF0vd>v2jVdaRHth0EkxaEbb&88wE(T4zTExP>L%{00@r~E=h4*U(dy(& z24TPBgGrU+BkAKUX_e)Rl(it(UbXf1y;2V-+3of_a#{5{{_$RVuw=LCXt_yw$jHs7 z>Bn@~D178-3;dgQugIU%R3^|zXmcbjpUKPP<))*;8Hg_T+E2fq@BLgI?;P*?9q(!( zEuBL78h^-ueDL_2Swf)o^KNGFy15j9p$xyjzbOmzz`YSW_d)D*>v_t(?N4jmlJ8c0 zn5XF7kZvB9ghTa>c~?j;n43<~&)sBl@pRVqn3InP5qpibJD3jRd;W9$r#*969;(_Y z7hgB-=D}m55sAgczE_vrc}bzUIX`xL-q7dbW1i{hE_HkYH@qC+DuG&Y+=E)v54q>L z!AoBijv2#6;huflk%-Ye2ipk0%_s*ZY_yCP2^%2Egx5;izN|{hx{OML3d4Jr02dyh|0OMgq!c)z^_KZE_T;aapD;w^V0 zzUm_f;=40qJ+2cl_eZJ&Rs;B}`)47SyQkecK0Sff8)aHF5#KMg4GHB59=1B}d3Dvx zv`093A`mMNE2ec_Cd*MVgV4IBptbY1pE~v_0QZ!>8*~ERU@0@HrAd>e&bif)`?eZi z+vmb*WC4_VNgK{Eit3q$*)&GPUR_sqagefW;x1l{mwB;t@NA{2rk1;F)_4J-mpoE? z&8^ZKS=Srt9!Xkla=@18A4Cc;*HMwiYSRdYQLPZWJL;@?P~e*P5X zy=wuRDbrj*-LA{Wjh_$Y%XvZ0fQ(5S)^fEIQPCv0@`0{9x z99^5+q-9lz!Yna(I0(TatQidLb{2C`M;RmdtD!KqIzzi9 zf$hyb>GVPCSL)XzSc(MP%phz6FquN`$B?tN+z!a|7$~?1zTb|9yFjjnU5l}XU0n~+78Xs zZ8+x*^E=u~pI7S~DDFN=X_%OH&rQ1HwPhG4`Uo6|GR6ItE8?e}(6qcdrk9QdHyF8fnJo#&++EvV3F+{U@t6!a zx>RatFTiDkr1WM|)+*t%a0{n_JCDaR)NPOmC$#|&>&@~^sf9%^RqpO5*wl74iCJed z*Uv*xM4cSX@mhtS)PTOJsi--#d%A_bCt1<{6~B?Lo0igJojSax@U+d^v_W?z-XN%t z+MKY@W-D!RV?mFJ`iPercT>$6F$}`K=e&0mJ1swpu}sELT1M8Q3aNdhuTNcgJq|7G z^O}yFe#3oa?BfVqOu5j@O)zY{eJr;x8VcpHGW_+#M0(U3b|S(@AO^tzzc_b4g(8Zu<8OLR%yfDqpOX2Hp6 z_k4E`;!aewR9+Dca~Did55ammKJG7UFJzn4O`jJ?m^B_6*iOcrovph?c|4|5Fa8}a zG2=b?;Biu8V{oOtgvgL?wYr9-)1w3j+PFBObzSR`Pb7%%=$dg$v4l1iP3GKco)wX0 zUWuV<+rS`Vo;H!X*liZa2avH~U5m^);tE8Tqt_6$yi`GN{L4(;C(Mp6G^_-_cEu@0 zRDW!yC#R;ax1*|-F>J@T1!m(fqSE@=2LT=H6z7C0$(GbC3K&fu7!{eQtSeVGc^9oN zAV$cro5Ch)%MN2QMMkCT;;TWXDKi?ai}9g(e9!F|2&+aU3?#@3i8;Kq(rT09$n|T(xdI{DSIU(;Kgu0m~%= zA{}U8K}8&7G=#4&+Hp4Lt>YK(7v0{p(t{Je4#VtV?7JIYuY?R(9H4tbWFX$ANx3leKbU)z$g5c9ZduGR8GM-cy z5pFOrPVcK4M;YxP?AYd?gYi?S6nd(!XEBTOWu9SX0ftwq#<8y0X=O*a#S`+3&gmPM z z3;|_}zWo6h!uRUEmUR!W^wYtT?TCg-ZE>es>m|&90m1NmmjD)HUNj_HR&rnjPg|*& zzzk`4>lqHwzd^aVQV-fiG6zqX;`!Hn*iZiiPdjsAlYmkA-HrbYNBy1J{inI_|HIdP zl6Y;|Ph2#z;evG}m(G8fv;RMQ-~VYoekBhFh%SqDg3)YZ+?xD= 0; i--) + { + Rectangle source = { 0.0f, (float)i*frameHeight, frameWidth, frameHeight }; + // Center vertically + Rectangle dest = { screenWidth/2.0f, (screenHeight/2.0f) + (i*stackSpacing) - (stackSpacing*stackCount/2.0f), scaledWidth, scaledHeight }; + Vector2 origin = { scaledWidth/2.0f, scaledHeight/2.0f }; + + DrawTexturePro(booth, source, dest, origin, rotation, WHITE); + } + + DrawText("a/d to spin\nmouse wheel to change separation (aka 'angle')", 10, 10, 20, DARKGRAY); + const char *spacingText = TextFormat("current spacing: %.01f", stackSpacing); + DrawText(spacingText, 10, 50, 20, DARKGRAY); + const char *speedText = TextFormat("current speed: %.02f", rotationSpeed); + DrawText(speedText, 10, 70, 20, DARKGRAY); + DrawText("redbooth model (c) kluchek under cc 4.0", 10, 420, 20, DARKGRAY); + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadTexture(booth); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/textures/textures_sprite_stacking.png b/examples/textures/textures_sprite_stacking.png new file mode 100644 index 0000000000000000000000000000000000000000..53b9868235032b5021138f4a7eb4237c36d98985 GIT binary patch literal 52440 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 Nt)+v-Ju~;%{{wvR!chPK literal 0 HcmV?d00001 From 78faf6a12070797bc630457f8b507314b4e833dc Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 10 Nov 2025 11:47:50 +0100 Subject: [PATCH 064/430] Update README.md --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index 29cea47fb..82a91e60b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -21,7 +21,7 @@ You may find it easier to use than other toolchains, especially when it comes to ### category: core [47] -Examples using raylib[core](../src/rcore.c) platform functionality like window creation, inputs, drawing modes and system functionality. +Examples using raylib [core](../src/rcore.c) module platform functionality: window creation, inputs, drawing modes and system functionality. | example | image | difficulty
level | version
created | last version
updated | original
developer | |-----------|--------|:-------------------:|:------------------:|:-----------------------:|:----------------------| From 4e8b087ffe5d3b35513f90e94f70c224244a3e55 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 11 Nov 2025 17:15:40 +0100 Subject: [PATCH 065/430] REVIEWED: `ComputeSHA256()` --- src/rcore.c | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 706cb2028..84a948320 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2894,7 +2894,7 @@ unsigned int *ComputeMD5(unsigned char *data, int dataSize) // NOTE: Returns a static int[5] array (20 bytes) unsigned int *ComputeSHA1(unsigned char *data, int dataSize) { - #define ROTATE_LEFT(x, c) (((x) << (c)) | ((x) >> (32 - (c)))) + #define SHA1_ROTATE_LEFT(x, c) (((x) << (c)) | ((x) >> (32 - (c)))) static unsigned int hash[5] = { 0 }; // Hash to be returned @@ -2937,7 +2937,7 @@ unsigned int *ComputeSHA1(unsigned char *data, int dataSize) } // Message schedule: extend the sixteen 32-bit words into eighty 32-bit words: - for (int i = 16; i < 80; i++) w[i] = ROTATE_LEFT(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1); + for (int i = 16; i < 80; i++) w[i] = SHA1_ROTATE_LEFT(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1); // Initialize hash value for this chunk unsigned int a = hash[0]; @@ -2972,10 +2972,10 @@ unsigned int *ComputeSHA1(unsigned char *data, int dataSize) k = 0xCA62C1D6; } - unsigned int temp = ROTATE_LEFT(a, 5) + f + e + k + w[i]; + unsigned int temp = SHA1_ROTATE_LEFT(a, 5) + f + e + k + w[i]; e = d; d = c; - c = ROTATE_LEFT(b, 30); + c = SHA1_ROTATE_LEFT(b, 30); b = a; a = temp; } @@ -2997,9 +2997,9 @@ unsigned int *ComputeSHA1(unsigned char *data, int dataSize) // NOTE: Returns a static int[8] array (32 bytes) unsigned int *ComputeSHA256(unsigned char *data, int dataSize) { - #define ROTATE_RIGHT(x, c) ((x >> c) | (x << ((sizeof(unsigned int) * 8) - c))) - #define SHA256_A0(x) (ROTATE_RIGHT(x, 7) ^ ROTATE_RIGHT(x, 18) ^ (x >> 3)) - #define SHA256_A1(x) (ROTATE_RIGHT(x, 17) ^ ROTATE_RIGHT(x, 19) ^ (x >> 10)) + #define SHA256_ROTATE_RIGHT(x, c) ((x >> c) | (x << ((sizeof(unsigned int)*8) - c))) + #define SHA256_A0(x) (SHA256_ROTATE_RIGHT(x, 7) ^ SHA256_ROTATE_RIGHT(x, 18) ^ (x >> 3)) + #define SHA256_A1(x) (SHA256_ROTATE_RIGHT(x, 17) ^ SHA256_ROTATE_RIGHT(x, 19) ^ (x >> 10)) static const unsigned int k[64] = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, @@ -3020,7 +3020,7 @@ unsigned int *ComputeSHA256(unsigned char *data, int dataSize) 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 }; - static unsigned int hash[8]; + static unsigned int hash[8] = { 0 }; hash[0] = 0x6A09e667; hash[1] = 0xbb67ae85; hash[2] = 0x3c6ef372; @@ -3033,13 +3033,15 @@ unsigned int *ComputeSHA256(unsigned char *data, int dataSize) const unsigned long long int bitLen = ((unsigned long long int)dataSize)*8; unsigned long long int paddedSize = dataSize + sizeof(dataSize); paddedSize += (64 - (paddedSize%64)); - unsigned char *buffer = RL_CALLOC(paddedSize, sizeof(unsigned char)); + unsigned char *buffer = (unsigned char *)RL_CALLOC(paddedSize, sizeof(unsigned char)); memcpy(buffer, data, dataSize); buffer[dataSize] = 0x80; for (int i = 1; i <= sizeof(bitLen); i++) + { buffer[(paddedSize - sizeof(bitLen)) + (i - 1)] = (bitLen >> (8*(sizeof(bitLen) - i))) & 0xFF; - + } + for (unsigned long long int blockN = 0; blockN < paddedSize/64; blockN++) { unsigned int a = hash[0]; @@ -3052,23 +3054,22 @@ unsigned int *ComputeSHA256(unsigned char *data, int dataSize) unsigned int h = hash[7]; unsigned char *block = buffer + (blockN*64); - unsigned int w[64]; + unsigned int w[64] = { 0 }; for (int i = 0; i < 16; i++) { - w[i] = - ((unsigned int)block[i*4 + 0] << 24) | - ((unsigned int)block[i*4 + 1] << 16) | - ((unsigned int)block[i*4 + 2] << 8) | - ((unsigned int)block[i*4 + 3]); + w[i] = ((unsigned int)block[i*4 + 0] << 24) | + ((unsigned int)block[i*4 + 1] << 16) | + ((unsigned int)block[i*4 + 2] << 8) | + ((unsigned int)block[i*4 + 3]); } for (int t = 16; t < 64; t++) w[t] = SHA256_A1(w[t - 2]) + w[t - 7] + SHA256_A0(w[t - 15]) + w[t - 16]; for (unsigned long long int t = 0; t < 64; t++) { - unsigned int e1 = (ROTATE_RIGHT(e, 6) ^ ROTATE_RIGHT(e, 11) ^ ROTATE_RIGHT(e, 25)); + unsigned int e1 = (SHA256_ROTATE_RIGHT(e, 6) ^ SHA256_ROTATE_RIGHT(e, 11) ^ SHA256_ROTATE_RIGHT(e, 25)); unsigned int ch = ((e & f) ^ (~e & g)); unsigned int t1 = (h + e1 + ch + k[t] + w[t]); - unsigned int e0 = (ROTATE_RIGHT(a, 2) ^ ROTATE_RIGHT(a, 13) ^ ROTATE_RIGHT(a, 22)); + unsigned int e0 = (SHA256_ROTATE_RIGHT(a, 2) ^ SHA256_ROTATE_RIGHT(a, 13) ^ SHA256_ROTATE_RIGHT(a, 22)); unsigned int maj = ((a & b) ^ (a & c) ^ (b & c)); unsigned int t2 = e0 + maj; @@ -3091,7 +3092,9 @@ unsigned int *ComputeSHA256(unsigned char *data, int dataSize) hash[6] += g; hash[7] += h; } + RL_FREE(buffer); + return hash; } From fcaea5b1a11b0cd3e040328db037eb87864cec62 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 11 Nov 2025 17:39:53 +0100 Subject: [PATCH 066/430] Remove trailing spaces --- src/platforms/rcore_desktop_rgfw.c | 2 +- src/rcore.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 47160af54..09712a706 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -1343,7 +1343,7 @@ int InitPlatform(void) // TODO: Is this needed by raylib now? // If so, rcore_desktop_sdl should be updated too //SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); - + if (FLAG_IS_SET(CORE.Window.flags, FLAG_VSYNC_HINT)) RGFW_window_swapInterval(platform.window, 1); RGFW_window_makeCurrent(platform.window); diff --git a/src/rcore.c b/src/rcore.c index 84a948320..be1c7a34b 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -3041,7 +3041,7 @@ unsigned int *ComputeSHA256(unsigned char *data, int dataSize) { buffer[(paddedSize - sizeof(bitLen)) + (i - 1)] = (bitLen >> (8*(sizeof(bitLen) - i))) & 0xFF; } - + for (unsigned long long int blockN = 0; blockN < paddedSize/64; blockN++) { unsigned int a = hash[0]; @@ -3092,9 +3092,9 @@ unsigned int *ComputeSHA256(unsigned char *data, int dataSize) hash[6] += g; hash[7] += h; } - + RL_FREE(buffer); - + return hash; } From 7f82da0031b1b989f19db403e3071d31019adfbe Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 11 Nov 2025 17:40:21 +0100 Subject: [PATCH 067/430] Update rlsw.h --- src/external/rlsw.h | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index db5a8189d..318d77334 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -7,7 +7,7 @@ * functionality available on rlgl.h library used by raylib, becoming a direct software * rendering replacement for OpenGL 1.1 backend and allowing to run raylib on GPU-less * devices when required -* +* * FEATURES: * - Rendering to custom internal framebuffer with multiple color modes supported: * - Color buffer: RGB - 8-bit (3:3:2) | RGB - 16-bit (5:6:5) | RGB - 24-bit (8:8:8) @@ -50,7 +50,7 @@ * * rlsw capabilities could be customized just defining some internal * values before library inclusion (default values listed): -* +* * #define SW_GL_FRAMEBUFFER_COPY_BGRA true * #define SW_GL_BINDING_COPY_TEXTURE true * #define SW_COLOR_BUFFER_BITS 24 @@ -60,7 +60,7 @@ * #define SW_MAX_TEXTURE_STACK_SIZE 2 * #define SW_MAX_TEXTURES 128 * -* +* * LICENSE: MIT * * Copyright (c) 2025-2026 Le Juez Victor (@Bigfoot71), reviewed by Ramon Santamaria (@raysan5) @@ -71,10 +71,10 @@ * 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 @@ -648,7 +648,7 @@ SWAPI void swBindTexture(uint32_t id); // Check for SIMD vector instructions // NOTE: Compiler is responsible to enable required flags for host device, // supported features are detected at compiler init but varies depending on compiler - // TODO: This logic must be reviewed to avoid the inclusion of multiple headers + // TODO: This logic must be reviewed to avoid the inclusion of multiple headers // and enable the higher level of SIMD available #if defined(__FMA__) && defined(__AVX2__) #define SW_HAS_FMA_AVX2 @@ -896,7 +896,7 @@ typedef struct { int vertexCounter; // Number of vertices in 'ctx.vertexBuffer' SWdraw drawMode; // Current primitive mode (e.g., lines, triangles) - SWpoly polyMode; // Current polygon filling mode (e.g., lines, triangles) + SWpoly polyMode; // Current polygon filling mode (e.g., lines, triangles) int reqVertices; // Number of vertices required for the primitive being drawn float pointRadius; // Rasterized point radius float lineWidth; // Rasterized line width @@ -1123,9 +1123,9 @@ static inline void sw_float_to_unorm8_simd(uint8_t dst[4], const float src[4]) float32x4_t values = vld1q_f32(src); float32x4_t scaled = vmulq_n_f32(values, 255.0f); int32x4_t clamped_s32 = vcvtq_s32_f32(scaled); // f32 -> s32 (truncated) - int16x4_t narrow16_s = vqmovn_s32(clamped_s32); + int16x4_t narrow16_s = vqmovn_s32(clamped_s32); int16x8_t combined16_s = vcombine_s16(narrow16_s, narrow16_s); - uint8x8_t narrow8_u = vqmovun_s16(combined16_s); + uint8x8_t narrow8_u = vqmovun_s16(combined16_s); vst1_lane_u32((uint32_t*)dst, vreinterpret_u32_u8(narrow8_u), 0); #elif defined(SW_HAS_SSE41) __m128 values = _mm_loadu_ps(src); @@ -2690,9 +2690,9 @@ static inline void sw_quad_sort_cw(const sw_vertex_t* *output) const sw_vertex_t *input = RLSW.vertexBuffer; // Calculate the centroid of the quad - float cx = (input[0].screen[0] + input[1].screen[0] + + float cx = (input[0].screen[0] + input[1].screen[0] + input[2].screen[0] + input[3].screen[0])*0.25f; - float cy = (input[0].screen[1] + input[1].screen[1] + + float cy = (input[0].screen[1] + input[1].screen[1] + input[2].screen[1] + input[3].screen[1])*0.25f; // Calculate the angle of each vertex relative to the center @@ -3615,7 +3615,7 @@ bool swInit(int w, int h) RLSW.loadedTextures[0].ty = 0.5f; RLSW.loadedTextureCount = 1; - + SW_LOG("INFO: RLSW: Software renderer initialized successfully\n"); #if defined(SW_HAS_FMA_AVX) && defined(SW_HAS_FMA_AVX2) SW_LOG("INFO: RLSW: Using SIMD instructions: FMA AVX\n"); @@ -4494,13 +4494,13 @@ void swDrawArrays(SWdraw mode, int offset, int count) const float *texMatrix = RLSW.stackTexture[RLSW.stackTextureCounter - 1]; const float *defaultTexcoord = RLSW.current.texcoord; const float *defaultColor = RLSW.current.color; - + const float *positions = RLSW.array.positions; const float *texcoords = RLSW.array.texcoords; const uint8_t *colors = RLSW.array.colors; int end = offset + count; - + for (int i = offset; i < end; i++) { float u, v; @@ -4589,16 +4589,16 @@ void swDrawElements(SWdraw mode, int count, int type, const void *indices) const float *texMatrix = RLSW.stackTexture[RLSW.stackTextureCounter - 1]; const float *defaultTexcoord = RLSW.current.texcoord; const float *defaultColor = RLSW.current.color; - + const float *positions = RLSW.array.positions; const float *texcoords = RLSW.array.texcoords; const uint8_t *colors = RLSW.array.colors; - + for (int i = 0; i < count; i++) { - int index = indicesUb ? indicesUb[i] : + int index = indicesUb ? indicesUb[i] : (indicesUs ? indicesUs[i] : indicesUi[i]); - + float u, v; if (texcoords) { From 8ae2c9cf5f0f69bfca6ea4d794905a06e061509b Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 12 Nov 2025 10:22:56 +0100 Subject: [PATCH 068/430] FIX: `LoadFontDataBDF()` #5346 --- src/rtext.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/rtext.c b/src/rtext.c index b4cd560ba..1705e5b49 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -147,7 +147,7 @@ static int textLineSpacing = 2; // Text vertical line spacing in static Font LoadBMFont(const char *fileName); // Load a BMFont file (AngelCode font file) #endif #if defined(SUPPORT_FILEFORMAT_BDF) -static GlyphInfo *LoadFontDataBDF(const unsigned char *fileData, int dataSize, int *codepoints, int codepointCount, int *outFontSize); +static GlyphInfo *LoadFontDataBDF(const unsigned char *fileData, int dataSize, const int *codepoints, int codepointCount, int *outFontSize); #endif #if defined(SUPPORT_DEFAULT_FONT) @@ -647,7 +647,7 @@ GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSiz { bool genFontChars = false; stbtt_fontinfo fontInfo = { 0 }; - int *requiredCodepoints = (int *)codepoints; + int *requiredCodepoints = (int *)codepoints; // TODO: Should we create a shallow copy to avoid "dealing" with a const user array? if (stbtt_InitFont(&fontInfo, (unsigned char *)fileData, 0)) // Initialize font for data reading { @@ -2517,7 +2517,7 @@ static GlyphInfo *LoadFontDataBDF(const unsigned char *fileData, int dataSize, c char buffer[MAX_BUFFER_SIZE] = { 0 }; GlyphInfo *glyphs = NULL; - bool genFontChars = false; + bool internalCodepoints = false; int totalReadBytes = 0; // Data bytes read (total) int readBytes = 0; // Data bytes read (line) @@ -2545,21 +2545,23 @@ static GlyphInfo *LoadFontDataBDF(const unsigned char *fileData, int dataSize, c int charDWidthX = 0; // Character advance X int charDWidthY = 0; // Character advance Y (unused) - GlyphInfo *glyphs = NULL; // Pointer to output glyph info (NULL if not set) - int *requiredCodepoints = codepoints; + int *requiredCodepoints = (int *)RL_MALLOC(codepointCount*sizeof(int)); if (fileData == NULL) return glyphs; // In case no chars count provided, default to 95 codepointCount = (codepointCount > 0)? codepointCount : 95; - // Fill fontChars in case not provided externally - // NOTE: By default we fill glyphCount consecutively, starting at 32 (Space) - if (requiredCodepoints == NULL) + if (codepoints == NULL) { - requiredCodepoints = (int *)RL_MALLOC(codepointCount*sizeof(int)); + // Fill internal codepoints array in case not provided externally + // NOTE: By default we fill glyphCount consecutively, starting at 32 (Space) for (int i = 0; i < codepointCount; i++) requiredCodepoints[i] = i + 32; - genFontChars = true; + internalCodepoints = true; + } + else + { + for (int i = 0; i < codepointCount; i++) requiredCodepoints[i] = codepoints[i]; } glyphs = (GlyphInfo *)RL_CALLOC(codepointCount, sizeof(GlyphInfo)); @@ -2634,11 +2636,11 @@ static GlyphInfo *LoadFontDataBDF(const unsigned char *fileData, int dataSize, c // Search for glyph index in codepoints glyphs = NULL; - for (int codepointIndex = 0; codepointIndex < codepointCount; codepointIndex++) + for (int index = 0; index < codepointCount; index++) { - if (codepoints[codepointIndex] == charEncoding) + if (requiredCodepoints[index] == charEncoding) { - glyphs = &glyphs[codepointIndex]; + glyphs = &glyphs[index]; break; } } @@ -2738,7 +2740,7 @@ static GlyphInfo *LoadFontDataBDF(const unsigned char *fileData, int dataSize, c } } - if (genFontChars) RL_FREE(codepoints); + RL_FREE(requiredCodepoints); if (fontMalformed) { From 4dbe04b250d1e44c6e7f0f89d4b3763b20d852cb Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 13 Nov 2025 11:26:54 +0100 Subject: [PATCH 069/430] Update config.h --- src/config.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.h b/src/config.h index 8e6e6a5fa..9152acc8c 100644 --- a/src/config.h +++ b/src/config.h @@ -59,6 +59,7 @@ // Use a partial-busy wait loop, in this case frame sleeps for most of the time, but then runs a busy loop at the end for accuracy #define SUPPORT_PARTIALBUSY_WAIT_LOOP 1 // Allow automatic screen capture of current screen pressing F12, defined in KeyCallback() +// WARNING: It also requires SUPPORT_IMAGE_EXPORT and SUPPORT_FILEFORMAT_PNG flags #define SUPPORT_SCREEN_CAPTURE 1 // Support CompressData() and DecompressData() functions #define SUPPORT_COMPRESSION_API 1 From 9c73b0eb3715d7855c306f49a4543514c2315be7 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 13 Nov 2025 20:36:57 +0100 Subject: [PATCH 070/430] Update Makefile --- examples/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Makefile b/examples/Makefile index 729459de4..f70e6993d 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -77,7 +77,7 @@ RAYLIB_SRC_PATH ?= ../src # Locations of raylib.h and libraylib.a/libraylib.so # NOTE: Those variables are only used for PLATFORM_OS: LINUX, BSD -DESTDIR ?= /usr/local +DESTDIR ?= /usr/local RAYLIB_INCLUDE_PATH ?= $(DESTDIR)/include RAYLIB_LIB_PATH ?= $(DESTDIR)/lib From d172a24bb0fdc1f097b6ccfcc763f673eae6d2e7 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 13 Nov 2025 20:37:21 +0100 Subject: [PATCH 071/430] REVIEWED: `main(void)` --- examples/core/core_window_should_close.c | 2 +- examples/models/models_point_rendering.c | 2 +- examples/models/models_waving_cubes.c | 2 +- examples/others/web_basic_window.c | 2 +- examples/shaders/shaders_basic_pbr.c | 2 +- examples/textures/textures_textured_curve.c | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/core/core_window_should_close.c b/examples/core/core_window_should_close.c index bb7daf3ee..f53f9d48d 100644 --- a/examples/core/core_window_should_close.c +++ b/examples/core/core_window_should_close.c @@ -18,7 +18,7 @@ //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ -int main() +int main(void) { // Initialization //-------------------------------------------------------------------------------------- diff --git a/examples/models/models_point_rendering.c b/examples/models/models_point_rendering.c index 3a93fc8d9..ebfad5ac1 100644 --- a/examples/models/models_point_rendering.c +++ b/examples/models/models_point_rendering.c @@ -32,7 +32,7 @@ static Mesh GenMeshPoints(int numPoints); //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ -int main() +int main(void) { // Initialization //-------------------------------------------------------------------------------------- diff --git a/examples/models/models_waving_cubes.c b/examples/models/models_waving_cubes.c index 51febf39d..7996c1c8a 100644 --- a/examples/models/models_waving_cubes.c +++ b/examples/models/models_waving_cubes.c @@ -22,7 +22,7 @@ //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ -int main() +int main(void) { // Initialization //-------------------------------------------------------------------------------------- diff --git a/examples/others/web_basic_window.c b/examples/others/web_basic_window.c index f85c6e4f6..217c47fbc 100644 --- a/examples/others/web_basic_window.c +++ b/examples/others/web_basic_window.c @@ -36,7 +36,7 @@ void UpdateDrawFrame(void); // Update and Draw one frame //---------------------------------------------------------------------------------- // Program main entry point //---------------------------------------------------------------------------------- -int main() +int main(void) { // Initialization //-------------------------------------------------------------------------------------- diff --git a/examples/shaders/shaders_basic_pbr.c b/examples/shaders/shaders_basic_pbr.c index 6fb15a607..cc8583830 100644 --- a/examples/shaders/shaders_basic_pbr.c +++ b/examples/shaders/shaders_basic_pbr.c @@ -77,7 +77,7 @@ static void UpdateLight(Shader shader, Light light); //---------------------------------------------------------------------------------- // Program main entry point //---------------------------------------------------------------------------------- -int main() +int main(void) { // Initialization //-------------------------------------------------------------------------------------- diff --git a/examples/textures/textures_textured_curve.c b/examples/textures/textures_textured_curve.c index feeefc7b6..abf78c88a 100644 --- a/examples/textures/textures_textured_curve.c +++ b/examples/textures/textures_textured_curve.c @@ -49,7 +49,7 @@ static void DrawTexturedCurve(void); //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ -int main() +int main(void) { // Initialization //-------------------------------------------------------------------------------------- From 80b6b7fc2a6c49b3778252f2af5b459e6bfb2dc3 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 13 Nov 2025 20:37:35 +0100 Subject: [PATCH 072/430] Update core_input_gamepad.c --- examples/core/core_input_gamepad.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/core/core_input_gamepad.c b/examples/core/core_input_gamepad.c index 1da1c4ce0..abfdb11c1 100644 --- a/examples/core/core_input_gamepad.c +++ b/examples/core/core_input_gamepad.c @@ -196,7 +196,6 @@ int main(void) } else { - // Draw background: generic DrawRectangleRounded((Rectangle){ 175, 110, 460, 220}, 0.3f, 16, DARKGRAY); @@ -269,7 +268,6 @@ int main(void) else { DrawText(TextFormat("GP%d: NOT DETECTED", gamepad), 10, 10, 10, GRAY); - DrawTexture(texXboxPad, 0, 0, LIGHTGRAY); } From 6dcd4cd564ad3ec4811281cc7c397462fb3a6c37 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 13 Nov 2025 20:37:59 +0100 Subject: [PATCH 073/430] Update rexm.c --- tools/rexm/rexm.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index a7cf88ba0..c3ca04e47 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -404,7 +404,7 @@ int main(int argc, char *argv[]) char *exColInfo = LoadFileText(exCollectionFilePath); if (TextFindIndex(exColInfo, argv[2]) != -1) // Example in the collection { - strcpy(exName, argv[2]); // Register example name for removal + strcpy(exName, argv[2]); // Register example name strncpy(exCategory, exName, TextFindIndex(exName, "_")); opCode = OP_BUILD; } @@ -2155,11 +2155,13 @@ static char **ScanExampleResources(const char *filePath, int *resPathCount) int functionIndex02 = TextFindIndex(ptr - 10, "TraceLog"); // Check TraceLog() int functionIndex03 = TextFindIndex(ptr - 40, "TakeScreenshot"); // Check TakeScreenshot() int functionIndex04 = TextFindIndex(ptr - 40, "SaveFileData"); // Check SaveFileData() + int functionIndex05 = TextFindIndex(ptr - 40, "SaveFileText"); // Check SaveFileText() if (!((functionIndex01 != -1) && (functionIndex01 < 40)) && // Not found ExportImage() before "" !((functionIndex02 != -1) && (functionIndex02 < 10)) && // Not found TraceLog() before "" !((functionIndex03 != -1) && (functionIndex03 < 40)) && // Not found TakeScreenshot() before "" - !((functionIndex04 != -1) && (functionIndex04 < 40))) // Not found SaveFileData() before "" + !((functionIndex04 != -1) && (functionIndex04 < 40)) && // Not found TakeScreenshot() before "" + !((functionIndex05 != -1) && (functionIndex05 < 40))) // Not found SaveFileText() before "" { int len = (int)(end - start); if ((len > 0) && (len < REXM_MAX_RESOURCE_PATH_LEN)) From b5caef1ffb1d006845b2dc38b2d3c2bc18ec338a Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 13 Nov 2025 22:48:24 +0100 Subject: [PATCH 074/430] REXM: ADDED: Example automated-testing -WIP- --- tools/rexm/rexm.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index c3ca04e47..1e7865db1 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -121,6 +121,7 @@ typedef enum { OP_VALIDATE = 5, // Validate examples, using [examples_list.txt] as main source by default OP_UPDATE = 6, // Validate and update required examples (as far as possible) OP_BUILD = 7, // Build example for desktop and web, copy web output + OP_TEST = 8, // Test example: check output LOG WARNINGS } rlExampleOperation; static const char *exCategories[REXM_MAX_EXAMPLE_CATEGORIES] = { "core", "shapes", "textures", "text", "models", "shaders", "audio", "others" }; @@ -413,6 +414,36 @@ int main(int argc, char *argv[]) } } } + else if (strcmp(argv[1], "test") == 0) + { + // Build and test example for PLATFORM_DESKTOP + // NOTE: Build outputs to default directory, usually where the .c file is located, + // to avoid issues with copying resources (at least on Desktop) + if (argc == 2) LOG("WARNING: No example name provided to test\n"); + else if (argc > 3) LOG("WARNING: Too many arguments provided\n"); + else + { + // Support building not only individual examples but categories and "ALL" + if ((strcmp(argv[2], "ALL") == 0) || TextInList(argv[2], exCategories, REXM_MAX_EXAMPLE_CATEGORIES)) + { + // Category/ALL rebuilt requested + strcpy(exRebuildRequested, argv[2]); + } + else + { + // Verify example exists in collection to be removed + char *exColInfo = LoadFileText(exCollectionFilePath); + if (TextFindIndex(exColInfo, argv[2]) != -1) // Example in the collection + { + strcpy(exName, argv[2]); // Register example name + strncpy(exCategory, exName, TextFindIndex(exName, "_")); + opCode = OP_TEST; + } + else LOG("WARNING: TEST: Example requested not available in the collection\n"); + UnloadFileText(exColInfo); + } + } + } // Process command line options arguments for (int i = 1; i < argc; i++) @@ -1487,6 +1518,94 @@ int main(int argc, char *argv[]) UnloadExamplesData(exCollection); //------------------------------------------------------------------------------------------------ + } break; + case OP_TEST: + { + LOG("INFO: Command requested: TEST\n"); + LOG("INFO: Example to be built and tested: %s\n", exName); + + // Steps to follow + // STEP 1: Load example.c and replace required code to inject basic testing code: frames to run + // OPTION 1: Code injection required multiple changes for testing but it does not require raylib changes! + // OPTION 2: Support testing on raylib side: Args processing and events injection: SUPPORT_AUTOMATD_TESTING_SYSTEM, EVENTS_TESTING_MODE + // STEP 2: Build example (PLATFORM_DESKTOP) + // STEP 3: Run example with arguments: --frames 2 > .out.log + // STEP 4: Load .out.log and check "WARNING:" messages -> Some could maybe be ignored + // STEP 5: Generate report with results + + // STEP 1: Load example and inject required code + // PROBLEM: As we need to modify the example source code for building, we need to keep a copy or something + // WARNING: If we make a copy and something fails, it could not be restored at the end + // PROBLEM: Trying to build a copy won't work because Makefile is setup to look for specific example on specific path -> No output dir config + // IDEA: Create directory for testing data -> It implies moving files and set working dir... + // SOLUTION: Make a copy of original file -> Modify original -> Build -> Rename to .test.exe + FileCopy(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName), + TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName)); + char *srcText = LoadFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); + + static const char *mainReplaceText = + "#include \n" + "#include \n" + "int main(int argc, char *argv[])\n{\n" + " int requestedTestFrames = 0;\n" + " int testFramesCount = 0;\n" + " if ((argc > 1) && (argc == 3) && (strcmp(argv[1], \"--frames\") != 0)) requestedTestFrames = atoi(argv[2]);\n"; + + char *srcTextUpdated[3] = { 0 }; + srcTextUpdated[0] = TextReplace(srcText, "int main(void)\n{", mainReplaceText); + srcTextUpdated[1] = TextReplace(srcTextUpdated[0], "WindowShouldClose()", "WindowShouldClose() && (testFramesCount < requestedTestFrames)"); + srcTextUpdated[2] = TextReplace(srcTextUpdated[1], "EndDrawing();", "EndDrawing(); testFramesCount++;"); + UnloadFileText(srcText); + + SaveFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName), srcTextUpdated[2]); + for (int i = 0; i < 3; i++) { MemFree(srcTextUpdated[i]); srcTextUpdated[i] = NULL; } + + // STEP 2: Build example for DESKTOP platform +#if defined(_WIN32) + // Set required environment variables + //putenv(TextFormat("RAYLIB_DIR=%s\\..", exBasePath)); + _putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); + //putenv("MAKE=mingw32-make"); + //ChangeDirectory(exBasePath); +#endif + // Build example for PLATFORM_DESKTOP +#if defined(_WIN32) + LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: Win32)\n", exName); + system(TextFormat("mingw32-make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName)); +#else + LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: POSIX)\n", exName); + system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName)); +#endif + // Restore original source code before continue + FileCopy(TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName), + TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); + FileRemove(TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName)); + + // STEP 3: Run example with required arguments + ChangeDirectory(TextFormat("%s/%s", exBasePath, exCategory)); + system(TextFormat("%s --frames 2 > %s.log", exName, exName)); + + // STEP 4: Load and validate log -> WARNINGS + char *exTestLog = LoadFileText(TextFormat("%s/%s/%s.log", exBasePath, exCategory, exName)); + int exTestLogLinesCount = 0; + char **exTestLogLines = LoadTextLines(exTestLog, &exTestLogLinesCount); + UnloadFileText(exTestLog); + + int issueCounter = false; + for (int i = 0; i < exTestLogLinesCount; i++) + { + if (TextFindIndex(exTestLogLines[i], "WARNING") >= 0) + { + LOG("TEST: [%s] %s\n", exName, exTestLogLines[i]); + issueCounter++; + } + } + + UnloadTextLines(exTestLogLines, exTestLogLinesCount); + + // STEP 5: Generate auto-test report + //if (issueCounter > 0) + } break; default: // Help { From c059ece2a4292581d40e17257f8f9f2eec9f3b3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20=E2=9D=A4=EF=B8=8F?= Date: Thu, 13 Nov 2025 17:10:13 -0500 Subject: [PATCH 075/430] [examples] Added: directional billboard (#5351) * Added models directional billboard example * add killbot texture * various fixes and formatting tweaks * corrected stdlib --- .../models/models_directional_billboard.c | 116 ++++++++++++++++++ .../models/models_directional_billboard.png | Bin 0 -> 20612 bytes examples/models/resources/skillbot.png | Bin 0 -> 2241 bytes 3 files changed, 116 insertions(+) create mode 100644 examples/models/models_directional_billboard.c create mode 100644 examples/models/models_directional_billboard.png create mode 100644 examples/models/resources/skillbot.png diff --git a/examples/models/models_directional_billboard.c b/examples/models/models_directional_billboard.c new file mode 100644 index 000000000..fe1b33b2d --- /dev/null +++ b/examples/models/models_directional_billboard.c @@ -0,0 +1,116 @@ +/******************************************************************************************* +* +* raylib [models] example - directional billboard +* +* Example complexity rating: [★★☆☆] 2/4 +* +* Example originally created with raylib 5.6-dev, last time updated with raylib 5.6 +* +* Example contributed by Robin (@RobinsAviary) 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) 2025-2025 Robin (@RobinsAviary) +* Killbot art by patvanmackelberg https://opengameart.org/content/killbot-8-directional under CC0 +* +********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" +#include + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [models] example - directional billboard"); + + // Set up the camera + Camera camera = { 0 }; + camera.position = (Vector3){ 2.0f, 1.0f, 2.0f }; // Starting position + camera.target = (Vector3){ 0.0f, 0.5f, 0.0f }; // Target position + camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Up vector + camera.fovy = 45.0f; // FOV + camera.projection = CAMERA_PERSPECTIVE; // Projection type (Standard 3D perspective) + + // Load billboard texture + Texture skillbot = LoadTexture("resources/skillbot.png"); + + // Timer to update animation + float anim_timer = 0.0f; + // Animation frame + unsigned int anim = 0; + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + UpdateCamera(&camera, CAMERA_ORBITAL); + + // Update timer with delta time + anim_timer += GetFrameTime(); + + // Update frame index after a certain amount of time (half a second) + if (anim_timer > 0.5f) + { + anim_timer = 0.0f; + anim += 1; + } + + // Reset frame index to zero on overflow + if (anim >= 4) anim = 0; + + // Find the current direction frame based on the camera position to the billboard object + float dir = (float)floor(((Vector2Angle((Vector2){ 2.0f, 0.0f }, (Vector2){ camera.position.x, camera.position.z })/PI)*4.0f) + 0.25f); + + // Correct frame index if angle is negative + if (dir < 0.0f) + { + dir = 8.0f - (float)abs((int)dir); + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + BeginMode3D(camera); + + DrawGrid(10, 1.0f); + + // Draw billboard pointing straight up to the sky, rotated relative to the camera and offset from the bottom + DrawBillboardPro(camera, skillbot, (Rectangle){ 0.0f + (anim*24.0f), 0.0f + (dir*24.0f), 24.0f, 24.0f }, Vector3Zero(), (Vector3){ 0.0f, 1.0f, 0.0f }, Vector2One(), (Vector2){ 0.5f, 0.0f }, 0, WHITE); + + EndMode3D(); + + // Render various variables for reference + DrawText(TextFormat("animation: %d", anim), 10, 10, 20, DARKGRAY); + DrawText(TextFormat("direction frame: %.0f", dir), 10, 40, 20, DARKGRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + // Unload billboard texture + UnloadTexture(skillbot); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/models/models_directional_billboard.png b/examples/models/models_directional_billboard.png new file mode 100644 index 0000000000000000000000000000000000000000..cbbe11065eef52f7389e697660af68e119443e5d GIT binary patch literal 20612 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 X_q4(ZPw&Im1#zD9eC8f==WqKT!Md5^ literal 0 HcmV?d00001 diff --git a/examples/models/resources/skillbot.png b/examples/models/resources/skillbot.png new file mode 100644 index 0000000000000000000000000000000000000000..537f1338367ec575c714a6e858200eb07bc7ec1b GIT binary patch literal 2241 zcmZuy3piBk8vfUst6_{=Xe6VW=;As#xtk@_l+47Wh=vf+j>d$_WsH=ggc9w_F*(H3 zg{fnwBA4pm!PZTwV`E$@Lkdy0X3SYr?dR-sp7TG?x4!rL{_p?(^{?-H|Mgdn&qgwd zN&*0oS(`k3Q5}Lx6A^}2g_u~q{jYTy7B!T|_Hn^7_PfmKH{hcMs#Fp6y;7-*w95S4u=u;2XqQLGvv zsHoQeq5pSR0MO5qM*vtf#@d!)4T-hgeZOj?R zJ$(n>og8-*`uC4c#?BgQ;eP5RXR|#PE3w=RSJ&F!RLLKVj0FkoLYr82zMS zu=bHz>8UjMD)NdLE;A7B(Q2qn7H|2QSwR(iXbArL;-Km1t6oNf@!Blk)Sm;0ju#eY z4>@^1oOco%KAKMytGi*Uu%Tf`M`MM6apkw@W#}YPj)QD@f`nO=S4R@6%gn#SCUifCu8#Y1ChnkJ-XB6byVwUbb4T zP-sqcg?s5$CM-a?|9oMP@Y8 z)iL}fjJ@(;o5+qvgdNDo$>%}JV=CAmlGoo^HCoUJh~Da*q%Q5}loopJ26Ke)b93=;P+pU|w~jo<;7We58$aOV2qYX@ z6GRVnFmXsP@w%=VqMJI%7gbIl2|JVm&H1$*wH#_KdZ8%8|FELS#KriE8_vf4Fe(3wUM;8yE)z9h{cs3l!sbDpIw<>abiQPWv3a5n zs|HX7eQmLAQ@UXGwIR!*g20lTcYkC!G;f+e`xJFVTApW3TxL`J66D0FdtmZRL)CGf9Y_nMbrA0R}BuCHSG=%sd z+FbU96^0H}CI!wn3CAb$2~60^nl)J+eBLWT(H3$4ECFO!$DKR&CV2##O1Hp7UZBC;g+8Y^XGm=TDO22U~3w&Q){;i9?!~H&Jk|+7YVuz4sjcfueUApjYpL ztP-F8;q;Xjx7>E5nHwrc<3*<_#4Fj+TF$$YJo`$X1CGZ0gI?{f+~Qe^@TZw)e!g zVF4{}ln%tYWb*RrVb#s<9BCpxgkJyjL;q<cW}3DXB^K zrr1oo(S|<8f?6ehP~*%bY7&jo*DBDHiM(iQ8k5dmQ*~rCBEbwX5_ZvF%hr60@VCyWY0?immF$$@28^ JsB#Yx{TuKsjBNk_ literal 0 HcmV?d00001 From 4dca02daa50e390876e64c7fde0d74d8209977b0 Mon Sep 17 00:00:00 2001 From: iann Date: Thu, 13 Nov 2025 16:12:11 -0600 Subject: [PATCH 076/430] first draft of audio fft spectrum visualizer (#5348) --- examples/Makefile | 1 + .../audio/audio_fft_spectrum_visualizer.c | 279 ++++++++++++++++++ .../audio/audio_fft_spectrum_visualizer.png | Bin 0 -> 15580 bytes examples/audio/resources/fft.glsl | 32 ++ 4 files changed, 312 insertions(+) create mode 100644 examples/audio/audio_fft_spectrum_visualizer.c create mode 100644 examples/audio/audio_fft_spectrum_visualizer.png create mode 100644 examples/audio/resources/fft.glsl diff --git a/examples/Makefile b/examples/Makefile index f70e6993d..f36b89bc2 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -703,6 +703,7 @@ SHADERS = \ shaders/shaders_vertex_displacement AUDIO = \ + audio/audio_fft_spectrum_visualizer \ audio/audio_mixed_processor \ audio/audio_module_playing \ audio/audio_music_stream \ diff --git a/examples/audio/audio_fft_spectrum_visualizer.c b/examples/audio/audio_fft_spectrum_visualizer.c new file mode 100644 index 000000000..ad38020fd --- /dev/null +++ b/examples/audio/audio_fft_spectrum_visualizer.c @@ -0,0 +1,279 @@ +/******************************************************************************************* +* +* raylib [audio] example - fft spectrum visualizer +* +* Example complexity rating: [★★★☆] 3/4 +* +* Example originally created with raylib 6.0 +* +* Inspired by Inigo Quilez's https://www.shadertoy.com/ +* Resources/specification: https://gist.github.com/soulthreads/2efe50da4be1fb5f7ab60ff14ca434b8 +* +* Example created by created by IANN (@meisei4) 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) 2025 IANN (@meisei4) +* +********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" +#include +#include +#include + +#define MONO 1 +#define SAMPLE_RATE 44100 +#define SAMPLE_RATE_F 44100.0f +#define FFT_WINDOW_SIZE 1024 +#define BUFFER_SIZE 512 +#define PER_SAMPLE_BIT_DEPTH 16 +#define AUDIO_STREAM_RING_BUFFER_SIZE (FFT_WINDOW_SIZE*2) +#define EFFECTIVE_SAMPLE_RATE (SAMPLE_RATE_F*0.5f) +#define WINDOW_TIME ((double)FFT_WINDOW_SIZE/(double)EFFECTIVE_SAMPLE_RATE) +#define FFT_HISTORICAL_SMOOTHING_DUR 2.0f +#define MIN_DECIBELS (-100.0f) // https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/minDecibels +#define MAX_DECIBELS (-30.0f) // https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/maxDecibels +#define INVERSE_DECIBEL_RANGE (1.0f/(MAX_DECIBELS - MIN_DECIBELS)) +#define DB_TO_LINEAR_SCALE (20.0f/2.302585092994046f) +#define SMOOTHING_TIME_CONSTANT 0.8f // https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/smoothingTimeConstant +#define TEXTURE_HEIGHT 1 +#define FFT_ROW 0 +#define UNUSED_CHANNEL 0.0f + +typedef struct FFTComplex { float real, imaginary; } FFTComplex; + +typedef struct FFTData { + FFTComplex *spectrum; + FFTComplex *workBuffer; + float *prevMagnitudes; + float (*fftHistory)[BUFFER_SIZE]; + int fftHistoryLen; + int historyPos; + double lastFftTime; + float tapbackPos; +} FFTData; + +static void CaptureFrame(FFTData *fftData, const float *audioSamples); +static void RenderFrame(const FFTData *fftData, Image *fftImage); +static void CooleyTukeyFFTSlow(FFTComplex *spectrum, int n); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //----------------------------------------------------------------------------------- --- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [audio] example - fft spectrum visualizer"); + + Image fftImage = GenImageColor(BUFFER_SIZE, TEXTURE_HEIGHT, WHITE); + Texture2D fftTexture = LoadTextureFromImage(fftImage); + RenderTexture2D bufferA = LoadRenderTexture(screenWidth, screenHeight); + Vector2 iResolution = { (float)screenWidth, (float)screenHeight }; + + Shader shader = LoadShader(NULL, "resources/fft.glsl"); + int iResolutionLocation = GetShaderLocation(shader, "iResolution"); + int iChannel0Location = GetShaderLocation(shader, "iChannel0"); + SetShaderValue(shader, iResolutionLocation, &iResolution, SHADER_UNIFORM_VEC2); + SetShaderValueTexture(shader, iChannel0Location, fftTexture); + + InitAudioDevice(); + SetAudioStreamBufferSizeDefault(AUDIO_STREAM_RING_BUFFER_SIZE); + + Wave wav = LoadWave("resources/country.mp3"); + WaveFormat(&wav, SAMPLE_RATE, PER_SAMPLE_BIT_DEPTH, MONO); + + AudioStream audioStream = LoadAudioStream(SAMPLE_RATE, PER_SAMPLE_BIT_DEPTH, MONO); + PlayAudioStream(audioStream); + + int fftHistoryLen = (int)ceilf(FFT_HISTORICAL_SMOOTHING_DUR/WINDOW_TIME) + 1; + + FFTData fft = { + .spectrum = malloc(sizeof(FFTComplex)*FFT_WINDOW_SIZE), + .workBuffer = malloc(sizeof(FFTComplex)*FFT_WINDOW_SIZE), + .prevMagnitudes = calloc(BUFFER_SIZE, sizeof(float)), + .fftHistory = calloc(fftHistoryLen, sizeof(float[BUFFER_SIZE])), + .fftHistoryLen = fftHistoryLen, + .historyPos = 0, + .lastFftTime = 0.0, + .tapbackPos = 0.01f + }; + + size_t wavCursor = 0; + const short *wavPCM16 = wav.data; + + short chunkSamples[AUDIO_STREAM_RING_BUFFER_SIZE] = { 0 }; + float audioSamples[FFT_WINDOW_SIZE] = { 0 }; + + SetTargetFPS(60); + //---------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + while (IsAudioStreamProcessed(audioStream)) + { + for (int i = 0; i < AUDIO_STREAM_RING_BUFFER_SIZE; i++) + { + int left = (wav.channels == 2)? wavPCM16[wavCursor*2 + 0] : wavPCM16[wavCursor]; + int right = (wav.channels == 2)? wavPCM16[wavCursor*2 + 1] : left; + chunkSamples[i] = (short)((left + right)/2); + + if (++wavCursor >= wav.frameCount) + wavCursor = 0; + + } + + UpdateAudioStream(audioStream, chunkSamples, AUDIO_STREAM_RING_BUFFER_SIZE); + + for (int i = 0; i < FFT_WINDOW_SIZE; i++) + audioSamples[i] = (chunkSamples[i*2] + chunkSamples[i*2 + 1])*0.5f/32767.0f; + } + + CaptureFrame(&fft, audioSamples); + RenderFrame(&fft, &fftImage); + UpdateTexture(fftTexture, fftImage.data); + //------------------------------------------------------------------------------ + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + ClearBackground(BLACK); + BeginShaderMode(shader); + SetShaderValueTexture(shader, iChannel0Location, fftTexture); + DrawTextureRec(bufferA.texture, + (Rectangle){ 0, 0, (float)screenWidth, (float)-screenHeight }, + (Vector2){ 0, 0 }, + WHITE); + EndShaderMode(); + EndDrawing(); + //------------------------------------------------------------------------------ + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadShader(shader); + UnloadRenderTexture(bufferA); + UnloadTexture(fftTexture); + UnloadImage(fftImage); + UnloadAudioStream(audioStream); + UnloadWave(wav); + CloseAudioDevice(); + + free(fft.spectrum); + free(fft.workBuffer); + free(fft.prevMagnitudes); + free(fft.fftHistory); + + CloseWindow(); // Close window and OpenGL context + //---------------------------------------------------------------------------------- + + return 0; +} + +// Cooley–Tukey FFT https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm#Data_reordering,_bit_reversal,_and_in-place_algorithms +static void CooleyTukeyFFTSlow(FFTComplex *spectrum, int n) +{ + int j = 0; + for (int i = 1; i < n - 1; i++) + { + int bit = n >> 1; + while (j >= bit) + { + j -= bit; + bit >>= 1; + } + j += bit; + if (i < j) + { + FFTComplex temp = spectrum[i]; + spectrum[i] = spectrum[j]; + spectrum[j] = temp; + } + } + + for (int len = 2; len <= n; len <<= 1) + { + float angle = -2.0f*PI/len; + FFTComplex twiddleUnit = { cosf(angle), sinf(angle) }; + for (int i = 0; i < n; i += len) + { + FFTComplex twiddleCurrent = { 1.0f, 0.0f }; + for (int j = 0; j < len/2; j++) + { + FFTComplex even = spectrum[i + j]; + FFTComplex odd = spectrum[i + j + len/2]; + FFTComplex twiddledOdd = { + odd.real*twiddleCurrent.real - odd.imaginary*twiddleCurrent.imaginary, + odd.real*twiddleCurrent.imaginary + odd.imaginary*twiddleCurrent.real + }; + + spectrum[i + j].real = even.real + twiddledOdd.real; + spectrum[i + j].imaginary = even.imaginary + twiddledOdd.imaginary; + spectrum[i + j + len/2].real = even.real - twiddledOdd.real; + spectrum[i + j + len/2].imaginary = even.imaginary - twiddledOdd.imaginary; + + float twiddleRealNext = twiddleCurrent.real*twiddleUnit.real - twiddleCurrent.imaginary*twiddleUnit.imaginary; + twiddleCurrent.imaginary = twiddleCurrent.real*twiddleUnit.imaginary + twiddleCurrent.imaginary*twiddleUnit.real; + twiddleCurrent.real = twiddleRealNext; + } + } + } +} + +static void CaptureFrame(FFTData *fftData, const float *audioSamples) +{ + for (int i = 0; i < FFT_WINDOW_SIZE; i++) + { + float x = (2.0f*PI*i)/(FFT_WINDOW_SIZE - 1.0f); + float blackmanWeight = 0.42f - 0.5f*cosf(x) + 0.08f*cosf(2.0f*x); // https://en.wikipedia.org/wiki/Window_function#Blackman_window + fftData->workBuffer[i].real = audioSamples[i]*blackmanWeight; + fftData->workBuffer[i].imaginary = 0.0f; + } + + CooleyTukeyFFTSlow(fftData->workBuffer, FFT_WINDOW_SIZE); + memcpy(fftData->spectrum, fftData->workBuffer, sizeof(FFTComplex)*FFT_WINDOW_SIZE); + + float smoothedSpectrum[BUFFER_SIZE]; + + for (int bin = 0; bin < BUFFER_SIZE; bin++) + { + float re = fftData->workBuffer[bin].real; + float im = fftData->workBuffer[bin].imaginary; + float linearMagnitude = sqrtf(re*re + im*im)/FFT_WINDOW_SIZE; + + float smoothedMagnitude = SMOOTHING_TIME_CONSTANT*fftData->prevMagnitudes[bin] + (1.0f - SMOOTHING_TIME_CONSTANT)*linearMagnitude; + fftData->prevMagnitudes[bin] = smoothedMagnitude; + + float db = logf(fmaxf(smoothedMagnitude, 1e-40f))*DB_TO_LINEAR_SCALE; + float normalized = (db - MIN_DECIBELS)*INVERSE_DECIBEL_RANGE; + smoothedSpectrum[bin] = Clamp(normalized, 0.0f, 1.0f); + } + + fftData->lastFftTime = GetTime(); + memcpy(fftData->fftHistory[fftData->historyPos], smoothedSpectrum, sizeof(smoothedSpectrum)); + fftData->historyPos = (fftData->historyPos + 1) % fftData->fftHistoryLen; +} + +static void RenderFrame(const FFTData *fftData, Image *fftImage) +{ + double framesSinceTapback = floor(fftData->tapbackPos/WINDOW_TIME); + framesSinceTapback = Clamp(framesSinceTapback, 0.0, fftData->fftHistoryLen - 1); + + int historyPosition = (fftData->historyPos - 1 - (int)framesSinceTapback) % fftData->fftHistoryLen; + if (historyPosition < 0) + historyPosition += fftData->fftHistoryLen; + + const float *amplitude = fftData->fftHistory[historyPosition]; + for (int bin = 0; bin < BUFFER_SIZE; bin++) { + ImageDrawPixel(fftImage, bin, FFT_ROW, ColorFromNormalized((Vector4){ amplitude[bin], UNUSED_CHANNEL, UNUSED_CHANNEL, UNUSED_CHANNEL })); + } +} \ No newline at end of file diff --git a/examples/audio/audio_fft_spectrum_visualizer.png b/examples/audio/audio_fft_spectrum_visualizer.png new file mode 100644 index 0000000000000000000000000000000000000000..c3f1bc8b0acfbc066c241358f06922dcc8133e86 GIT binary patch literal 15580 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 XmXUW`GFsuSWx Date: Thu, 13 Nov 2025 23:19:37 +0100 Subject: [PATCH 077/430] REVIEWED: Bunnymark example new `raybunny`! #5344 As the license for previous bunny was not clear, it was replaced by a new custom bunny specifically created for this example with a CC0 license --- examples/textures/resources/LICENSE.md | 2 +- examples/textures/resources/raybunny.png | Bin 0 -> 466 bytes examples/textures/resources/wabbit_alpha.png | Bin 496 -> 0 bytes examples/textures/textures_bunnymark.c | 2 +- examples/textures/textures_bunnymark.png | Bin 436989 -> 433122 bytes 5 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 examples/textures/resources/raybunny.png delete mode 100644 examples/textures/resources/wabbit_alpha.png diff --git a/examples/textures/resources/LICENSE.md b/examples/textures/resources/LICENSE.md index e4ee45304..7d67572c7 100644 --- a/examples/textures/resources/LICENSE.md +++ b/examples/textures/resources/LICENSE.md @@ -8,7 +8,7 @@ | explosion.png | [Unity Labs Paris](https://blogs.unity3d.com/2016/11/28/free-vfx-image-sequences-flipbooks/) | [CC0](https://creativecommons.org/publicdomain/zero/1.0/) | | parrots.png | [Kodak set](http://r0k.us/graphics/kodak/) | ❔ | Original name: `kodim23.png` | cat.png | ❔ | ❔ | - | -| wabbit_alpha.png | ❔ | ❔ | - | +| raybunny.png | [VoidSrc*](https://x.com/voidsrc) | [CC0](https://creativecommons.org/publicdomain/zero/1.0/) | - | | custom_jupiter_crash.png | [Brian Kent (AEnigma)](https://www.dafont.com/es/aenigma.d188) | [Freeware](https://www.dafont.com/es/jupiter-crash.font) | Atlas created by [@raysan5](https://github.com/raysan5) | | KAISG.ttf | [Dieter Steffmann](http://www.steffmann.de/wordpress/) | [Freeware](https://www.1001fonts.com/users/steffmann/) | [Kaiserzeit Gotisch](https://www.dafont.com/es/kaiserzeit-gotisch.font) font | | fudesumi.png | [Eiden Marsal](https://www.artstation.com/marshall_z) | [CC-BY-NC](https://creativecommons.org/licenses/by-nc/4.0/) | - | diff --git a/examples/textures/resources/raybunny.png b/examples/textures/resources/raybunny.png new file mode 100644 index 0000000000000000000000000000000000000000..b608147bcb6b1a3d8413739d52106eea2ecd565a GIT binary patch literal 466 zcmV;@0WJQCP)o9Hn@nW zh@6s)?4$TUULq-_>a*8=nZTzoMSz*X%rDz76ZizaPM`~0(XT>8x}6Yu$(Ln$zTfZn z=kvbr)qh$(DJ87y+RXI_rLYjuv&Th5Tf)=xX=%dELulJJEX#rr9*Yo>s?WNv0033L z2)YR5qp*QP2p}Q=K#UQpda~Q&dx=0wX&5BN=t|O4##3SRDwdhCZJR6M$k!I+tu*9f zsRhV^TRW3dYARv=9%DQ$+Pwo7nLys!@_hum1GU~%G%7vAtOj_rEzsCCleC(%Cl1;p zF@qy{HP=X;PDhegIzpv8nt4V#V1H~Dr-dm(m-G&utc1?AVMbZ?Q>e{*y&JOTGnxyB zHIU`CY`GRnzvKRB<{1qnA%uVs!qlk6%*g+Z7U{W+a)8-6-*xt1AIbg+xXy`))M`NQ zHq2n=YZ_pa7Za`mIL&}Zuj_GtxHVv!K&2C^-`+}~ljtq}zb4fc4P#^g0{{R307*qo IM6N<$f;UIg`~Uy| literal 0 HcmV?d00001 diff --git a/examples/textures/resources/wabbit_alpha.png b/examples/textures/resources/wabbit_alpha.png deleted file mode 100644 index db4081fec5a801d4b6f2beeafca4a0dffbe5d333..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 496 zcmVkdg0005ENklL3}oV1UkKwj zR!p+}SzGQ1Lo9AdRLcO<$^;0*lnoX=&J$YA5LPS!VBaua0r+$qJpH~0V9wxJFD(PA z&1m1B(Y*nPt}2}R`nVc48-pGk+oY0f~#SXp#a2JUX%FH!|NU~u>Ux$NYShaVV# zxM9^@%P2RMzigf8kd2c+5PIidYcGl)n+CSN2R)N`?X1;_lBc?+5VD(*r^hK+fbnP$;y&koBO>l}`piuGtifTyxd7z=IjkKWV z<|mKp*eBaXAq@k&-1Y5Kafh=3yHrmDg>;#_$(i-TN-@4D-e)DRsn`coS6h^eTRRO$T00ld@}3ZH m<2RER$xNr(&-FgA{~G{&g0wAMhB_|*0000&N0u4sF1^vajbKUQe-rYtYb@xY?01!%o3-CNEC^Z4kEi_ zlp|Y`y;AmwGE<+&sn_TAeto`|=j;6se1ADVsB<3oaU0ihdn8zx9pm8=;2jvErsggUyqkw}}JSgxZ6BvrP5{vh*W7^m2`ZCTyC zdt0x9)gLjk%XguRS@jQxFUNEeV#L}U^(T%#k9hm9`;Lv^NbDdd%1-{nmHoL!a8(S5 zyG5k@V-#hxw72z%}ju*J}#o2B&n>B{3L+mpNqHSohJb&@0Oh$OAh^ zam;uQu)cITUBJozQ(exyffO+>rE=z$QBie)PSnX~VY#vJ%DKxorm5V+%rp$ zQm!_on|O!vhjR&6RZh)`DXJdi0@gKwZESJaFcGIgwIj~}o76m7F{yULSFtIS=}c@(65^I=KMk$1)pcgz)k%t(KHOs%KM!&q4VF) z9;hAPIsV*F;D4<>!2Pi5pKSSmdk%$F{zU(K`HTE}SkL}{UH%wea5q(+f3D7~h<)=n zVn6;b5epW6dQS|wd8T(F5jKj8VhE57V-^7U94@VRU8ss{wHSUI}TlOB}D!?h| z>FrgBwT={nHr6%dAbK!)q_Q!|I`$9ts|^UpH?}CZGy4y&Hs2ZQM2%kD=2WBVoDea6 z6S;Etk+sJCM!nB+K$HLaP_UNk8r~%sY%vcfyi@#&WB^Je&Ijk zCw|4H9$38&nMAY}Nw{g8-`mbOn8%h99_hxJmhvhA{9(*#1p0xwx-I2P>@7Kmn?Z?S zYp#W7PJE+gl8Vnc={&A^bm%$6DrD!I1omdf%VF$I;zx$y+rlA{B%JBud(X|V&%6fR zH7{(wyjF83m0cUWe$($;A;&e1ov+kpL;>7fnFEQM4BChbm_|J;vT3EuRe_5x4cu!) zw75$mw}ZuXxrHx0EQXkV@-QTy=d*LgI#m5SM7{6CW^_+tKyH5-x1gf0yU0^aV(P?YR2AdR$j({Xi?v+nU;;VC4wQ%??sk zXZ%D}Pf+3NfZiH%U1x-+Z(3&YefHt(dOG=nBK2;*_Hbclb01@1$qIwmNzQUxDShwC zfKLwQBK5@&F;4V}&yZ}+{6M_iKG`g*Oh5Tm-!dR*9i!o-^)ZQf`JDrHY@`EXG}2)p zH$ItusK2#xK133Wer})!k{!hdVW`PQD4P>@_4k}5TJf+aU2L(Pe9I~|y6nfpAA6J| zYF6JPHmsCD&l}}_FE(GJi$)rV!OT$Zu_75FQCos_-w0!2pfvJA%1e2nOcpO=P;4@!jZ*9rY z%*m%Dn9q`5sV~`I<4v*dHvB?mQ@XU%F+S(hnO#tH{X_Ms_pk&0olB04g7xk8Aw>0t zq??AKL=%Cx-)^c2`F$ zCK0zc?@)Wp$wA9uq>+r_bupuxcx+ML$ziB)>-!Mr3eH3WrKgAY9I3n4^rX9e@u|gk z&0r&3jYhbXf~<2PlOZSH;CG?%Jl08(ad+awXLXw|n(PQAgQ)1p!K za)g~Hw6BIv@SXTQffOZA%COhcQDmLq)bmx^2}HNTFtvL>B|E-^NBJh7q99So4q zhQ!&hPV_T$yTwn^;fgh|%FM4j&3B#!Cesl12E8`5KJ8<;(MZW2E|u zZ}<)S(p`plo$jGKkCwq-1ms*^y!X34aS%#PR)NypJ6C3A-D7h+t)h6;&U(Omq)@?< zdaWzJwBXc|8m`7_Yis1N;?4R=>=1GF=fdR!naQU=SvN>{=An{4KKgSEp27AWp`B3& zh8-NxJf{vG-w(4eJ8d*iMvL*zc`@)P>}A$z97_r5jthJ&?9>=I8r6CmA!glA-QV}R zh?GD4>H+aBN7=2&>0w@2i0yfLdS-H#{#4e3STWe`xFB8KTI!y<2>r+7yvKqE4C#3L z)zKgvvP%n--4RzBBQPAnFV)9sX-#vF*SMVI42MR7=ChlA*CMR);fb!(3HK4rw_YtS zIyGD5xHCcXUJNhD(MBz?>?C$m#eAI0-rJzBl6IwJuuPl+-yA(VL+lgyAh+<@g}3yc zbIp5)!Qdcq+Wvrr&&YW2Y2L&7mcg#?@vxiX#?s(EftHoxm>YXT_e`n=|2|@Tu=$Qf zbk9}=o!3%VjLlgotGEChMB=TKYt+ItZBDO_M#jq7CnByKTJrl+jr^p71>I2OKHJs- zSHlrA`DmfDH)ShSfhF0^K)y;XjWvR98!Sa=U*kGIV{*cTq9vYf#^FB=b%>lRKaNNz zaFk|mo@a;V-vNK7;-#vd`!Pkly<4v%fQn_(=_6j=H7G?6hU)d=!}>)c#tsx>F}$2q8jn( znJaSGw~(|MF)z?KzdKFGPaE9X(`d$HJT0SL$oKS4=R-U06s<6CRGOOl4Hwec7I3gG zItDI?z#M{E%2NM<2x14pKZpO_$Fk?d(ZFBr@2pY^vu5pp){Bd{wZLo zit<9JQC0A)aO@&Cn)+7Elh81tm%~vgtE@&QwsW64TyXw8_-%R44117nMa&TtuXzlD zhBR7eriN39Ec24-111fZ zBUJb!m0zmSdjI)7t^}89$z=}>^#D;D?I-R!X&yu?_Bk6eUO+6djM0!iAQ0G0?p@&K z198J2)W&{%(R@jVq1j4#VE~!)BO~;eXyfVoiSba}#*jCCH#;dN^?WL=8yC2I2=W6r_6UVWDdmCs7nt%;C~sTSCRi zGRe+YrE*;$?PPSu4^Tfd^ieE6K<2yP?Rp|kayiSKT@6XhNzab`;tq3HcaD{a6Iewb zVSc4}9t|Gz&}cfssh|*?m@tvFV(m2@MVT15U|fH2{~?4hG)RR%lkd#dYu}HkyGI11 zqkyxyeOGfB&o5Og6lKnnFXX?xeH`tO_9r|U zC_>Qk+J$7s6`8Ur-nl4_$%;x2BMxE$3uTi2e#tmGo0MT5)H7Z$(X{e&|Hs%P-io^j^4k z3!89)eg10rZ?$2Bvr!Y_{hj9s1l^V|0t}lACEbY&I!y3KtK|zt6(-TN{}in*ALHO~ zw84e5K1NJiOX?!+VHKl0%Y zv>4!LvQd2eN6YsBr}6poZFu_wZSBB#aIG}t13W_j{LR^$kSX}Wim9ON^$r?sxp0z? z96-$6!|%DDE*=rYmUGM@IG_Tg zv%GE$udUHjvG2;pkP#Q2Gpa}icWx>+Bj+w4k%UqyH0M+YtS!_=j_p$$hff|fBY<#) zIGJtBe_TlY!QseAmHYqB5R^jAqbmAmL2M+A0syMFuR@H1E#dcqC*r2nF22sAoRQor{%EZ zDcYA?o)#ooyMlvQ4I`rHLq(ohL8_m$cCK17NQjTgX;0Yxgzwk%;ZYjlCjBjv!rRKHF#(Oih@b&B7G==RX@#1?iz{ zb1sM>g15H;=EJKsrSGJmo7dSVo~UH+x1C9ME|oOz(1nKR(owv2*JFl@K|x2`c+k?H z<&?zTKrNV7X{E6S50By-JW210ME+nJo*%I64Z3S9bb_YBj7Ib1^bV}JiV5DP(PnAPuS+Ku_ z4*VVJLd9d4k;GI_66x5tL-}jqIm7QSV-r>1R-J6*;G(6!tuA3q#ylFLzq{xeqTSe} zJDJi*Mr``cBdKK3b$5TXCQURjY>Kz}NvY8I5=gN{KV(l~09X)SOw8nWI}$yGGG=@K z&=T?X8*LzGG)5jp$mE?$f_wErM~v+Oh=8Yw7YYHPelF>UHn%baHWyFEW6)vtIH?a( zD^X?gL~G3kJTJOFeA*m)0JSfrh@9e#ss7eg)`F-CD>~3or+r?XD*RH(W+CqVg;-n6 z^YPVh7$Kw?^h;-ukI}}V#?>-f5~tTX)nXc&i^}tO@6EhTdC&!d`G9)AkrSjlX^f`f zAN)BH2>tvoe1G=e#!CqljM@oyu>qedE$47KI+q@YxC@yYNEQl^Q(@rI*Z}9uWF;pI zkGK0VX1rd$5ae_~$nTS|G!0|KD+Kp|1Jk=D$Tu(eu>QRDnbRbb;ykOUquhFwR42+A zbch-WPs-%C$4LVP)R;#tq@l@oKGV4(zd8=wI}377I|n{{8HOA)Q@;bYmSYQ3qo%EZ zf^^kFE|F}?_)6M*HU?S{%a0LAnwB{Iixql}rX|Vz>UtWgVjyYrQ;+R%t^Oa_oc+WW zi*rP?Dq~Tvg4_7*UC?X&gA(BnfjfdIemFPa6g}zCWeTtQJ%*Eqdg}61ZvX(&YhbUb zM11lnXZ=&yFsEj1CNVvg;su(E01{Qy&J347NSKQ;e~G4^h;VTsDHmtOB8vJ&oUo*M z8pA_}rX|zgOA+T1g11M#8sUF88*DS}wrFK6W_fQ}#H#H2;|DAE`DhS}>BjoTOy|a> zMT8T@B(0)bba%WXPCYlNcAZV-M(+Nc4hs&!A+Gc0lzv~(|Hd`J{SlT(^t~wr{Ob)k30L;1UgSrVDOBAkwfm~p98F~_|I9#9L!Csr$ zvC<9?dL~@X$5we7GM5-?qo4OASbW)qm7cCcTKX?L;6?Q4MJRCzfIZUnnzVt$veV@_ zR*5NIlmNG1!O}n}z5{GlwIoG1e3>k$#Bg%(G}8;?H3oZR#+Lt?n0OCeN}3~t6KFpe zK%rCS*9vYFjb)JeD+Id)vA1Mz1W=JopxQ~)3C|O%VW5NIAi_09q#?D=NOK=4)k-6`r6m>J(j2S?| zX1D*`GxTd!z%4nx;sKXcsb32fFWF?nI|$V;lqT*Fic&t3?`_oBHi9yC(N@>;8a!cL zzxZfZfG%OGS11%!SHo4g_D!-%(vH94ok%aHQu{p4MyoI&>M-AJ1=0bRiXDWCV9?yA ze(WB3i5(yiqG!3R!mik=LydWDANJHrGtKf%@J(G)g-~JrvEM*6wfJ4; zc4I&)OTi!b=A#)YZjYYEr6S@f^a~;{$xq6@2O;TTpVbd9&5j<}3u=F?Yd8Rf-1)hZ zo(#5YhRP(L6jz(43}Z04V=Ec*jYhFLh)wdRDj!&Nw&|Yz^!vOp2uKYFISreO&<+L9 z`b6QS)X(a&vL>gEl{I56(Y>PYOA@1QF$|xuB zcak?j)vHq2n8)Y5uumV<BMZ`BugIDizuXrW|bg2z-M&MbkQjR;Hn{b{_W!JZ6gR)4bg4@j*f> zoiQR|(+rkbo9qI^rgS7{TIm43nuX8C%sQ1x_%lS(ejcL$@agJYmR+lvE*fnLPJo!tV68J*4%KC7F{z=<9dDt;<2#m5ZM+#e1 za2&6LFe784G~{D>J|rpE7!8L1UTL`qo4Sey)=Seu&3F?pfoHFNW9zB3JOxVP0>jmR zDMHok3YFDc#NHVy=U-cO;}<^T!C~+9mi$7ffdB&vy08K#+q}ZT3jMpBsmlb>pehYD zyT`3mwPc||GVfd{zpExw!G4Y))=l)cJhy(}1R=s?e3ZtCm;wWxOjWFebja;x%C#mU z45+3BjCAK;l7?xu5gY?rn8V3ebF?vGr|e%}#Lpez_m^{3@_PecjpV0QqU+YenF!m= zYz@cf+Rt+=rd}>a-vysrihoy;QX+1J^WUiRWb}Ol9egL7q-_8>1)aBN^hc%Vn{Z!l zkvAb8x(o!w-`mv`w4R2_@Bw^2pWH<7Ag7$8sAU0I!doLnje^IVeoaUjq%Q1)e7_#saHfOnSxah=<<0oP_=s^v3y{^aq*9|4OuF1@kom}UQDtc0*7|u^zb>s$ zla?g7Ehz6MPZWDkjOdr-@An8~;f%pKP1?jIi|K||`HYFii%q)KPvMtT(93WzKtHGh zoObCkzW4VUEBcgfC6>{6JJ!oukxK_5TE5p45grKI)x+veV+2l}{=Bu!Uo`h`8k&&W2(+eK*!SIi=$P!2 z)7UlVOo1+sKxnKSBD*V1vhMms$t0vgh>!_TD{^cOG*peP*O~E=lhWy-F(l+F17#{$ zG(&Tdz$MATfqIC5Ey6k1NAaWv3ZrYd$*%b-Wx>w_kT^ac1u z-MDX;TlDHQ<0d*Gy<;+zvy zNZU_0<0!f>0K`--3^;+w$lni@zx%0RD}Gse_Gg^qww}|jMzzVR&Vz@1mi#MVMb;P* zJpM7qBWDRXE$YI?1{S>xB|62%m@69V8NcjPV&cE-^*QUORiDX+X^PeEPq0R_18iuD z&d8H>a#f_hsyPUCo#%!fGZ@du&vXfp8g~>|`0p+{h@rGw(KnYD9g_VXqv zv&>`6Mm!3=b6C9Q9l6GnO$IheeVuc)^6knvk9xl?0ODhlgDmh;$-er1-0z>=h|-b~ z%LAtxfjn18nL?&pu87~h`}#~9@xm1zM+upuahlto*I3C5hmTi$(s^@I|Ki09 zu>OWdbuAGb123KZfzBoQz&(vqo0B_ud#|yy*J=FryFl8{Eo&=;Y#V=$vq>>&N0;!p z69p3{*}1UKv9#EJdzg^k%5iIO)mD9KWRX@kxs9)xUNyp9Q^rvY(V5S)LF*lF9q>?CY@I@%)BI#JxB6;8%chx6I9|q`(;$`Uh zVxpWQ1uu2`S2@E2c&?_U@u=aFhBm%54jd-Mv8|#I0aRMhY)odWNI2wj17hKf0!`Fr znyvR>*yd$XgI{J%B}ahjXlE1ULC7O`9_s=c;d4D@p^^|pIh?H=l^I95LlGP=77bnukNl|#~LSn++DHoY>6y{J~Hc}QGf!_(M+-u4Uu*x9aGY@|{uk4cX zLzQ8z$}pbeOn-a_nq=_NCVgVmx(O_Y-umg+YoJDwxZAvgF0Qydpbv9=goFiHosK>I zDLd{={Np0ID8;w>e_-M>pc|$QndJR}iQOAF7(byvy%#di7Ahglk#@8R5f+|o!H4=5 zf9)N=IfvciyPSbcyctzLNCHkrgMRuYLy&>u2f82&!G=(PVp<_oB9KV|hplPml%{pc?!D49>j5J_HrK6LueL-3(RuB~W z2V|QmUQV?j^bhRit_eqzy>GCl58fn9{Qi-J2w~Hs+=RAA@|R9=73J}%Jo;VCOrJRQ z6$49AVV)zfq>aq~s5W+>h9;?ZW6tBA9B9_<4Dz~RFHGn}`t34sYC$PRtOa1YK(u7&ktRRY)p^oY zsNYSOA5=7vm}DDa5WqaXQ(3U7#5Q{B7O;0R<<-)hbpuHNFwh`P zOAPD!pLFtq^JJ9&0;Bj;?`eFIW6abY@--jCGsel8Z0XQjT9_+h>8Qi(y^oRHS}7tj z9PF}8R24lD4B?fZR;GSPSpc6GmI z>;>!2aURXCynydFrf2(iDbrf~=ceznU?R>(bq=0BM<4tfjQ*Mh+s7`j|Cp&Km3d{{B4v zo={l;{g0=k0M<+JrfZ7HCe;W?^llMGBY0Vy$tAEIm0};8cwOpw%!733xu}>+ksXf4 z6;%8_7V#V4pRyI02#7%3cBI{c{l^~Rik~J!qryFS?c#Fr?hvR?ALOe@zbX9Dfn$0S z+`U_&)!)OTS_napUx;_? zCiNaQ8fCX8W|fVfj^1Mw!hK3BU!6tvBho(Cy{T#=aDgPMZ$wTVHh$AICPH$R(MIpQ z1GpWH;BXjcKtAF&AXt8e)#oPniO1kmpuiYws zN3B=+aHn3T{=W0)sj`)7JAQu_@21- zGnpa`3Lb;|@NxLy@I@VPCJ#{SAJk%=U;uLdFR3g<@V4A1^Xzba(iP%HjP_Olcyw!% zOGmQr<}x?+?*1t+;lJ^?WT8pZ#BK>qzd~(3p;vR+#j*XNuJw7mZu{)TZPbs7=r47r z;m*UL(@s}mT?l5#*Bc0rFxnFs9oI=%{l$k}{sOc#ntB#>lbK1=Ira>d_|^rvvaX95e&8Fxw@N8=I?<&1GcxOKWl&{#E7 zo_yDy%4ylg`m(k0zc+HlSqFBf@K*n5sdd2$%E~h@uz919YNzB;D*9^W=4B%`Sqa5!3PwJqgO>?UF$M8>ICl7jDSuJzhFm$IYfw;~Wyr zY(5B1{v5-6tkejR_`v-Eo{qZT$9Er0bc%Y?yzu}WxC}Q|1S(w~09@*i;G!a|IjXdr zL*k=%^n293X7?7of)4We+q}O)>ECZN^R+?+y7Aryk~(?rE4D(gc#7+6`{me?YlcT+YbN< z32^tiuhZ&pBuS(-r&D{$9-0K0Qms?;#|W1c`}$sO(7MEX-Wunbb9|L|V7*&usuNJ^ z9r#rL)ytYrv1JV0!SzL(Hf)fr+t%X83q1(*<+Ly5C3T3MZ4sPt`@W9q>}nm-Az}!b zy!4y=?N+LDahfcSp_W1Q`oHaF8UFCW-cXc)l0r5wwES~0k(V5#x)P*8tL0FcKcy^l zd+3ITvMJs_euDPfEw)T-qfrZu{lW5I!f4|D$Jk|HgM5?z{prrhic`1IK5`EAZLuR8 z;2|hvC^3m8lECL8(n1YHeSIH^xktPAA_+i8h8gqdhW%)6LvWjIU>8iQ`QGs$Y!R`w zZ3wXeWIl|vacvwp8ilA#+LQZU;4JX{jDaT5;BU$Ttu!3dnv7=e4dkum?Bn1=!d2yMQ!tCd7W#+-b>KV|z=wF)N&QM`KT~8>&#pQfUBR z<#7b+!>v8TbV#G#0z}`3+qkqtUIbGv9&HAMmsx5itt@z1ICkih zuX4WkGv!#`5Bgq+O-t6p**fahuLL0IR!-zOo16rp#p8)N=xjTai^u1$+i z%JTMi*U-#3{c`7)aWIQx(4n`{%#07WJ^~V}XkIX46EBH=4FL8*2gT6c*a(ELp^+x_ zwSr5d+RPGPng;-inqmV_m8qfeZJ4lYI9@v7~-HPEy5qeR+3QjQ#jX> zFCBcppSQ)l->fQXfW3Md`Um7o0Gv0MFIZpL%L*FkA?W1+?VV+bzyQ^sU3;ZK>esyn zX*}Cq%4j|FaYBSY3h1V~u_9m907MsjW_fiFQ&RbREm~<>FRC+w{GEaqW{PzZBYZ@J zCQm^D60{@)b}*g6HQ!^Y^J-#{9N$q&B82Lp7jNs&ZVlCRe=uP;>KWQY0lET&Wi2)k zpdg?`B*IvZ$n4?`)L{;TRSEw%@Iy6K-$*N;g5+9--jgWi11`#0DX2 zi#!{Xa1F%U$33I#O3+IZ$>ZHB;XEI~Oxg$8uNE;E%E_uu?9E=QuMWm2&vL!_s!6b}MnGP~oLqp+JEbX7-+)hFi- zs+#;RZ27}Z>UAafcJ1yP`=w$(rh1>aA5!viw&OadMOeH^v{5h8ad)uAkF;tT`|J6I zZfX)(6}peY5@^!3s(FCelAKuq-PX(9Bg1gnmW#xCwGhl0kc)V^SKq|T_i$Ibg69&W ziOU?e9|P|Gk*MpO*ajw83jowTeUkTkh#Xl9!^}(;=|q2%!#rVIqPaaaNWnF*OlrFi z!nn{*7dP}?24Gt?e~Y}c;z?NYV&o>Ue#xqm#IyM>mjpHfQ?F2a}RMta&~-1kbb}-BzPhuc-`Jm zlBUZLQHft`jRv3a{VDL?mjN^(#hiQ9$*Ft*1^(0$`A+ishNS*s%~QA48=iqOwCj@K zOn?nEI(x7|vmRp`7r)R;M4#Y-J%Sc}g^~EH1^umyyC9YMo>DsmE4z97IEhwq=91-8b1iiP zh-@>se>3!|9tYD@_bdszCWDjl8%=frj)-fx1=t9$LR8|NW}e z3@d(g2oi|?S*sp_`W3rw=hsI90fNyE$x*NYv$Cr%ls*kH?KfCPj#F6b4Wt4)yJRh9e2b z)*FDWsTT3fG1sYh;cPQQ&R0qDml!oW9Jw&H)^1$5irKX9x50K0;K<06+XK-zD2HXD zN8*zrI^lJ$p~P(<$xO1GGk+3j;Z7#i^?OR1FX=d`W(Ue)ntx!IVfEI>yffwP}V9 zNR5qYd(7CV#DG~eVWL_>LMCq5@^V~=E)uD$8!N8LPU%yAdcQbfVC?)8qtV*FP znjP({KmFmhhlN*dBkextgG+C2+LYq2ZU@HJ)j7htOyg3%IK^w<-jCk8&@8}n@cW12 zLACEVAII;LwgDAtF0I5Eoa~+F_m=3+VfzDP`)dr=9_ravE}G-Ae*CC!0PmC zx1)!XxPY$Ez|{l;sH%@e9OPBgp=zkVrge}%+fl!$-d7zl_Dx61ASFw}y~kUYnEIC7 zfMBzSm3;@Bc24by$!_IzzB7K1%~Zc`zl81Vho`j9FP}Uqeo1YT<(rETw`ac}v(nO3 z_XeVxQydm72L;-p+t|$yamc?8{C1=O4L^nAxAAO}mQAlexaV>pj*8c7)Xlb$Sy4T* zpC<@r$8HRrTcK(pfPoEU($YvXmOJ$4;cY=hi|L$CyedzYZiz#eBedVZXR}WnYN5Ve zKO8gc44Qwym@;3`=p=oGAD3gwIo2ml)XLRnvEX*q22{9jCM1f{^k+N;7Wxwe*iUvz zZ@CsC7)u+VOn;*`BUMD#5cf&>rHm2FwpQv#;+)<>IxPgsVBB54wEj9&QmuLg0<901(r(b!qn&^MwjO0|W zIsgi?g^EcM{ArF6uXCtVUA{W-Mfhd9zJfz_Ew{hhi)R^%P{sPK3$$4q14ZU3J%D=6 zEz&Zc7iR{t2XHUc@%@h*p$p`AHeerx6X{z5K*3!OB?B$2Am$Hzlk43g-_N=SM%8Dj zHC|`IjJ|HhrwKwZfEsUAR&x8Q)?Yf#ClrmizM!?mqp^m8eS*$K#7ke)hJPbw(yBKw zT2fp0(A1pln=_~0)9_NSz$5pVr(Y6w2n4bIAOrNp6TtZD6zDHy1!1=O+;PaUe$6I*u_zrm0&p;UI^K^_BlqEcTptP3kh+x3a@|UB}a;k+*wa)=6-1+eq z@>5iw3Wo}z&oc^-8AjX%|4kTQYc(r_v(h(FO^W0XFBvbSz`jDLODPI&SnD{S18f)Z z$HqdFB2<+QZ&j*4#TLG(Y6qq7qCHynnzm=!(FP<;FWU{XZzXRgj%A>d@_Pi+6v z(wk3-M^9Dm%|%MSFtjfU^V`skJ)-alDZ@pa6|%XXs^8#$@|t9$TeCO}@DAo5IDTJt zr5AI#bF@N_)GcA{oht85=si_}mbY>dO2GVG!BX z1On1c7ijK=jd&3(Yt(MR(Jid&pf#PB!;}ZmKk9ZG*)MO4{i{kg2PceTI<5VHx*i&4X6c=told@7_NAPJm2`qg@$W}Q)+H&WFQa2N#8 ziV$~d@>EPZ%r4ysm@_IL5br%t9`WZ@fefu4qG_;({dfnyVK4U|4eVNg8A}4{bOHOf zPFY!8^k9r~;gZu^&^(zjRV-0?W3fEoI~f|J9nWbxW!K|8XI&UG?9Y?F%P14jDdH&^ zNCYT0x)hj}pe2v&2FI#}LyNb>*|=J+acz9t$x1*rh3#2D%UK+@;m4Xu_GTosni5;#EQj4jKQJo zpk4t$*reSAa0kDN@NS@jK7bd(=mW{z2z6BC12)MQZ86^50KiS>85kahrO?ge5GN1H zN4k&|-{>zz69Vr9@8I*-8dg5*?5}_4w<9FhSb1k>JTuw%H74Y^U%|8bw}7MQH=V1_ zcj?nTAzD(ABy_Hg|IZH!9H-n3iW4oznQlQ)GL6AQn!pgq1rN4fPRdAZ(voFb#itD( z!x*ra5WZ-rQ+`~Lip{9wMgg-~+2J}Q z@9t$bN&;&(OB!iol}W5>5*t&lL6@=yU_`rOg95mfV)cI3An5I1(V|yBYc2#T&?4$uv-*6=f2WFmbPsW6;rWzfV@gpE#eK7M*@aIXIU=2Q%2|R-pDXp^7Uaz1 z4$_O94no^)`HJ-Oo#>+>$Cab)!rkZ_ z2(LT=FPFA@Y}*<)bHY|-MV^WEiVcSXuOggI!}{jNjNCrVBxr%E_e=CXqVt*Q7jsnJ z0awxZuih7q=NnAq?P1f}%MHXpX5x{HZ;$%PO|knOmjIeNPCT$@Zz4~+0N9yd%652g zfHRaf5prGIQPp#wfztzcKf>OGP7)@wFm{vvLFN^&H??Ud9RzPRO@pJ2pR)u)N`XMW zHC_h_wVbewy}?klKK+$|pDC{@04{lOSpeUhG@-x$>&-cMmVuhgkjcKovEkl_-umHt z`1jkFD~@q!2aEJ+D#DWgKvyx&F~je-+@?#yFOEM4L10 zKUJt6fW9c#g@_lRs#v-Hr$zy5XmY~t!SSbq!3BU&l`SJm@MvgTbS!|WozUk|A*9Mv zZ1^6X@8nvJh`7R|Ss!n{r{ovU(8cMQOMuA{e_3uJC9o^OW*l%p9eHiwY#yMxAz(&^ zDY2I(xe`eWHI$wlcVMPlj{2^ScfcFb5Wsp{a4Wo;c^cWf??8z7%+zzX4}vf)qw_Ge z@e%94GD^i7CVJ)yW$6cbARdqrg4xxMWGA(p97pf>mt%`en0Qc&@D0LDmr>WR0n*nA zi6}%X;sO%VDK_LUoI2b# z2C#b8I0DS=ojd7`W$GHw(J7XL+jIbEuA9Aj;kzl|S5KjTF%|F<3ySH@_rT2)`ytwt zB0aW7Sw=e2G=UwNwilG#L)dm%CS)v=f5r55vW{Qpk9M~M&HC94H)jAw%$2DRbka7_ zMTIW}w_=by#{LJH@M`UH<-${e{}hL+Thl+9P7o1%>|_<+HvRecO+g;kd8q|{-=rk? zUW_tr$r-!CJIHs+&2)2W@04qZb0)vv)2X(Tu_pFutp{I9#Q6|Dm8iZ=ccXo&-nd8t zDRyrglJ4vH92EXDjYt_0@Go+eXN~PS@;6(izX^MSL=0k%9Y~FpZ~-mOlQsABqQDnb z;hBLk3b50Bx0{2WvjhER-d?+wPgCFWoTc#aM1Q$>NgMy`pGRS`4u&IS))`c@N@)82 z`8+@?VwpjusOUw@XGWr3UNkl7#b9jv7rHsx;BA^)18CG|;C9|kF#GOBl}zSNpM%nr<~qO-357_Udc zx^C41MhE{%pth%_H#164rht$A$b25k`A!!QlGWVdjg6q3hZiNilY59>)t8(L~=6X;x|QG)!+NbtkeTnboCENU@VKCSlu@zS%Br z!B=CV(Wi+!#At0bsCjkNcf{Xr-Dvg(Qqa7g?_81l<@@)X%^8W(7+Wa~%FS!zR?)Bc zd1=2<82A+*TX~oSAZ^yJu}FUJpLZ6U{D7A)$~-oX!%Q?KE5y1}oiQOQf0K-3dYSF; zAkKN@X+ROch*o#L!SR9$AB*S)}dyr2MIt-1{&LEnm0Z1vk74GCz5&Ou#pR3IEkRbQPu8-R8 zp+C2k%{~)x=yTis%U<=YSwF%OjlYNQw#d#m!O#J;n-f>jew({8#$D{dc)@W5Q_evm zlS4D_+~&I=*pwOU|D&?_)cGY+s3X^9k&Yw~zf54T0q|_q zfLB*E@EC!^ZVsUPvlb=^ZWgXZ?iOkk1*j1q6R_|a^-r;>y;zGV5l+*k0I#690WjH_ zod?FRTkUp zsum`E{X8qm5u<>bpW~|?ml?_46K4|HsmoO0%g+G=Ba*_YO1f>Xq)^Hgm%imEYpBuq z6V!XI)r;bQUszVd^%t;Tk~0;LGhrFa*;X`vK(Ne+kuWze6rv*rrHFE;0AYMSogP6DFs*lB~8@Jy+@_)!W z^KdBJ_K(jPW(Kz{+f23@L`AY>8`}&OMbh#JNp>p9PLXB|Lz_k$LaBsGHJ0pYL>XD4 zBxFy>9^ZcIQ@8@Lh!8Yw0E;bZceP&BO0n>VnrO=;y zgm-hHmoVx03#M~2Z}-!HJZaZoQPp3dkQt)aRq35Sc&=}D-Qnrn#Asvn>iM0xDE%{^ zF{4SscUOed{Abm&!O&rAi)0v0pl;(WMxPJ!#*s9pNb4)!kE~Ptt-0)fJ{81|>$9DX z=%#8epHKQ$GQ*0hO^l#JsybDxf^5DbVN-hY<<=76Uo@R`(eM#*@cWOob*E5}X<`bqhUh6G3P3M-i&4o$NeDbhaUtq2=Nclx!gg**r zyY76i21zGJu?2DKC&||}qXu~XlCJ~@F}7$;M!=bZunlG%a=m@rujBZUCMH#>_B;*L zpkw5@6c>Xpe1o{Q({ikK=(j6(MF$fYPXB8zO`M5*cDLq?ri~PKI@>kK*jegL57L0Z znFI4hAj6xkalb{j6dk0BTkP5;S-vr<(dcxDvfah|xdygRfz-Emz58wnFwH7f zTOSQHXfRA-OSLB01{~_bl#JPa+<|{M6p(XejVaVKY3O1n{-u&x@)_wUQ1P)5 zR7A5t$GSnO%h%C0>)_%5Usjf*NFX%1I1}|xYd5RHJXAdJVe(le8C#8bXkwFv$`lNm ztWoQ|y#3iFW}XCyF6={%MiXwu^Eg~z*H0@I64ZG|;RwLv7GI#68B8!mhv4~H}b8ld9GIn}L1 z+MgheKDSaMJe>r(o)jD-sD=;kXt9&bj)ehlhPGwl-iD`6Uv!|!!ztho0^4%_W(


h^TaFr@1XYlOYpW$AY4QMmkd=1QXQLv8Nb6UXu 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

@A{ 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%0Diz{!|_2FswEADY7Nm8*S5a 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%0DQyEkTy-0Zp&5D!&_a>6 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 {mOk|>hu5hv@YEQ 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

4dRTgJcW|IwV`B_9W9P_nSqK~;h2JpFPkx@oU< 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 literal 0 HcmV?d00001 From 18e1ec504f4364daed463051c3ec02216264aa7f Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 29 Nov 2025 18:20:06 +0100 Subject: [PATCH 181/430] Updated example --- examples/text/text_inline_styling.c | 7 ++++--- src/platforms/rcore_desktop_glfw.c | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/text/text_inline_styling.c b/examples/text/text_inline_styling.c index 24e2704f7..8faef30eb 100644 --- a/examples/text/text_inline_styling.c +++ b/examples/text/text_inline_styling.c @@ -108,8 +108,9 @@ int main(void) //---------------------------------------------------------------------------------- // Module Functions Definition //---------------------------------------------------------------------------------- -// Draw text using inline styling, using input color as the base alpha multiplied to inline styles +// Draw text using inline styling // PARAM: color is the default text color, background color is BLANK by default +// NOTE: Using input color as the base alpha multiplied to inline styles static void DrawTextStyled(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color color) { // Text inline styling strategy used: [ ] delimiters for format @@ -179,12 +180,12 @@ static void DrawTextStyled(Font font, const char *text, Vector2 position, float if (text[i - 1] == 'c') { colFront = GetColor(colHexValue); - colFront.a *= (float)color.a / 255.0f; + colFront.a *= (float)color.a/255.0f; } else if (text[i - 1] == 'b') { colBack = GetColor(colHexValue); - colBack.a *= (float)color.a / 255.0f; + colBack.a *= (float)color.a/255.0f; } i += (colHexCount + 1); // Skip color value retrieved and ']' diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index a56e1c683..4f4e2c141 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -130,7 +130,7 @@ int InitPlatform(void); // Initialize platform (graphics, inputs and mo void ClosePlatform(void); // Close platform // Error callback event -static void ErrorCallback(int error, const char *description); // GLFW3 Error Callback, runs on GLFW3 error +static void ErrorCallback(int error, const char *description); // GLFW3 Error Callback, runs on GLFW3 error // Window callbacks events static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized From 78661a4ee035d7958a86da27c9c1d942ae7d41da Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 29 Nov 2025 18:23:17 +0100 Subject: [PATCH 182/430] REXM: ADDED: `shapes_ball_physics` --- examples/Makefile | 1 + examples/Makefile.Web | 4 + examples/README.md | 5 +- examples/examples_list.txt | 1 + examples/shapes/shapes_ball_physics.c | 99 ++- examples/shapes/shapes_ball_physics.png | Bin 43216 -> 23245 bytes .../examples/shapes_ball_physics.vcxproj | 569 ++++++++++++++++++ projects/VS2022/raylib.sln | 27 + tools/rexm/reports/examples_validation.md | 1 + 9 files changed, 655 insertions(+), 52 deletions(-) create mode 100644 projects/VS2022/examples/shapes_ball_physics.vcxproj diff --git a/examples/Makefile b/examples/Makefile index 72df8571a..48cfba97a 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -560,6 +560,7 @@ CORE = \ core/core_world_screen SHAPES = \ + shapes/shapes_ball_physics \ shapes/shapes_basic_shapes \ shapes/shapes_bouncing_ball \ shapes/shapes_bullet_hell \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 431b2cad9..522b50fe0 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -548,6 +548,7 @@ CORE = \ core/core_world_screen SHAPES = \ + shapes/shapes_ball_physics \ shapes/shapes_basic_shapes \ shapes/shapes_bouncing_ball \ shapes/shapes_bullet_hell \ @@ -864,6 +865,9 @@ core/core_world_screen: core/core_world_screen.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) # Compile SHAPES examples +shapes/shapes_ball_physics: shapes/shapes_ball_physics.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + shapes/shapes_basic_shapes: shapes/shapes_basic_shapes.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) diff --git a/examples/README.md b/examples/README.md index 77b6ff37e..148caffe0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -17,7 +17,7 @@ You may find it easier to use than other toolchains, especially when it comes to - `zig build [module]` to compile all examples for a module (e.g. `zig build core`) - `zig build [example]` to compile _and run_ a particular example (e.g. `zig build core_basic_window`) -## EXAMPLES COLLECTION [TOTAL: 200] +## EXAMPLES COLLECTION [TOTAL: 201] ### category: core [47] @@ -73,7 +73,7 @@ Examples using raylib [core](../src/rcore.c) module platform functionality: wind | [core_text_file_loading](core/core_text_file_loading.c) | core_text_file_loading | ⭐☆☆☆ | 5.5 | 5.6 | [Aanjishnu Bhattacharyya](https://github.com/NimComPoo-04) | | [core_compute_hash](core/core_compute_hash.c) | core_compute_hash | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | -### category: shapes [36] +### category: shapes [37] Examples using raylib shapes drawing functionality, provided by raylib [shapes](../src/rshapes.c) module. @@ -115,6 +115,7 @@ Examples using raylib shapes drawing functionality, provided by raylib [shapes]( | [shapes_math_angle_rotation](shapes/shapes_math_angle_rotation.c) | shapes_math_angle_rotation | ⭐☆☆☆ | 5.6-dev | 5.6 | [Kris](https://github.com/krispy-snacc) | | [shapes_rlgl_color_wheel](shapes/shapes_rlgl_color_wheel.c) | shapes_rlgl_color_wheel | ⭐⭐⭐☆ | 5.6-dev | 5.6-dev | [Robin](https://github.com/RobinsAviary) | | [shapes_rlgl_triangle](shapes/shapes_rlgl_triangle.c) | shapes_rlgl_triangle | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Robin](https://github.com/RobinsAviary) | +| [shapes_ball_physics](shapes/shapes_ball_physics.c) | shapes_ball_physics | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [David Buzatto](https://github.com/davidbuzatto) | ### category: textures [28] diff --git a/examples/examples_list.txt b/examples/examples_list.txt index 605ddf263..2373fcc9b 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -90,6 +90,7 @@ shapes;shapes_lines_drawing;★☆☆☆;5.6-dev;5.6;2025;2025;"Robin";@RobinsAv shapes;shapes_math_angle_rotation;★☆☆☆;5.6-dev;5.6;2025;2025;"Kris";@krispy-snacc shapes;shapes_rlgl_color_wheel;★★★☆;5.6-dev;5.6-dev;2025;2025;"Robin";@RobinsAviary shapes;shapes_rlgl_triangle;★★☆☆;5.6-dev;5.6-dev;2025;2025;"Robin";@RobinsAviary +shapes;shapes_ball_physics;★★☆☆;5.6-dev;5.6-dev;2025;2025;"David Buzatto";@davidbuzatto textures;textures_logo_raylib;★☆☆☆;1.0;1.0;2014;2025;"Ramon Santamaria";@raysan5 textures;textures_srcrec_dstrec;★★★☆;1.3;1.3;2015;2025;"Ramon Santamaria";@raysan5 textures;textures_image_drawing;★★☆☆;1.4;1.4;2016;2025;"Ramon Santamaria";@raysan5 diff --git a/examples/shapes/shapes_ball_physics.c b/examples/shapes/shapes_ball_physics.c index 1c41d5f1a..8ba6a14e7 100644 --- a/examples/shapes/shapes_ball_physics.c +++ b/examples/shapes/shapes_ball_physics.c @@ -1,10 +1,10 @@ /******************************************************************************************* * -* raylib [shapes] example - physics bouncing balls +* raylib [shapes] example - ball physics * * Example complexity rating: [★★☆☆] 2/4 * -* Example originally created with raylib 5.5 +* Example originally created with raylib 5.6-dev, last time updated with raylib 5.6-dev * * Example contributed by David Buzatto (@davidbuzatto) and reviewed by Ramon Santamaria (@raysan5) * @@ -15,9 +15,10 @@ * ********************************************************************************************/ +#include "raylib.h" + #include #include -#include "raylib.h" #define MAX_BALLS 5000 // Maximum quantity of balls @@ -42,12 +43,12 @@ int main(void) const int screenWidth = 800; const int screenHeight = 450; - InitWindow(screenWidth, screenHeight, "raylib [shapes] example - physics bouncing balls"); + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - ball physics"); Ball balls[MAX_BALLS] = {{ - .pos = {GetScreenWidth()/2, GetScreenHeight()/2}, - .vel = {200, 200}, - .ppos = {0}, + .pos = { GetScreenWidth()/2, GetScreenHeight()/2 }, + .vel = { 200, 200 }, + .ppos = { 0 }, .radius = 40, .friction = 0.99, .elasticity = 0.9, @@ -55,7 +56,7 @@ int main(void) .grabbed = false }}; - int ballQuantity = 1; + int ballCount = 1; Ball *grabbedBall = NULL; // A pointer to the current ball that is grabbed Vector2 pressOffset = {0}; // Mouse press offset relative to the ball that grabbedd @@ -75,8 +76,8 @@ int main(void) // Checks if a ball was grabbed if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { - for (int i = ballQuantity - 1; i >= 0; i--) { - + for (int i = ballCount - 1; i >= 0; i--) + { Ball *ball = &balls[i]; pressOffset.x = mousePos.x - ball->pos.x; pressOffset.y = mousePos.y - ball->pos.y; @@ -89,7 +90,6 @@ int main(void) grabbedBall = ball; break; } - } } @@ -104,37 +104,38 @@ int main(void) } // Creates a new ball - if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT) || (IsKeyDown(KEY_LEFT_CONTROL) && IsMouseButtonDown(MOUSE_BUTTON_RIGHT))) { - if (ballQuantity < MAX_BALLS) { - balls[ballQuantity++] = (Ball) { + if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT) || (IsKeyDown(KEY_LEFT_CONTROL) && IsMouseButtonDown(MOUSE_BUTTON_RIGHT))) + { + if (ballCount < MAX_BALLS) + { + balls[ballCount++] = (Ball){ .pos = mousePos, - .vel = {GetRandomValue(-300, 300), GetRandomValue(-300, 300)}, - .ppos = {0}, + .vel = { GetRandomValue(-300, 300), GetRandomValue(-300, 300) }, + .ppos = { 0 }, .radius = 20 + GetRandomValue(0, 30), .friction = 0.99, .elasticity = 0.9, - .color = {GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255}, + .color = { GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255 }, .grabbed = false }; } } // Shake balls - if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) { - for (int i = 0; i < ballQuantity; i++) { - Ball *ball = &balls[i]; - if (!ball->grabbed) { - ball->vel = (Vector2) {GetRandomValue(-2000, 2000), GetRandomValue(-2000, 2000)}; - } + if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) + { + for (int i = 0; i < ballCount; i++) + { + if (!balls[i].grabbed) balls[i].vel = (Vector2){ GetRandomValue(-2000, 2000), GetRandomValue(-2000, 2000) }; } } // Changes gravity - gravity += GetMouseWheelMove() * 5; + gravity += GetMouseWheelMove()*5; // Updates each ball state - for (int i = 0; i < ballQuantity; i++) { - + for (int i = 0; i < ballCount; i++) + { Ball *ball = &balls[i]; // The ball is not grabbed @@ -145,48 +146,47 @@ int main(void) ball->pos.y += ball->vel.y * delta; // Does the ball hit the screen right boundary? - if (ball->pos.x + ball->radius >= screenWidth) + if ((ball->pos.x + ball->radius) >= screenWidth) { ball->pos.x = screenWidth - ball->radius; // Ball repositioning - ball->vel.x = -ball->vel.x * ball->elasticity; // Elasticity makes the ball lose 10% of its velocity on hit + ball->vel.x = -ball->vel.x*ball->elasticity; // Elasticity makes the ball lose 10% of its velocity on hit } // Does the ball hit the screen left boundary? - else if (ball->pos.x - ball->radius <= 0) + else if ((ball->pos.x - ball->radius) <= 0) { ball->pos.x = ball->radius; - ball->vel.x = -ball->vel.x * ball->elasticity; + ball->vel.x = -ball->vel.x*ball->elasticity; } // The same for y axis - if (ball->pos.y + ball->radius >= screenHeight) + if ((ball->pos.y + ball->radius) >= screenHeight) { ball->pos.y = screenHeight - ball->radius; - ball->vel.y = -ball->vel.y * ball->elasticity; + ball->vel.y = -ball->vel.y*ball->elasticity; } - else if (ball->pos.y - ball->radius <= 0) + else if ((ball->pos.y - ball->radius) <= 0) { ball->pos.y = ball->radius; - ball->vel.y = -ball->vel.y * ball->elasticity; + ball->vel.y = -ball->vel.y*ball->elasticity; } // Friction makes the ball lose 1% of its velocity each frame - ball->vel.x = ball->vel.x * ball->friction; + ball->vel.x = ball->vel.x*ball->friction; // Gravity affects only the y axis - ball->vel.y = ball->vel.y * ball->friction + gravity; - + ball->vel.y = ball->vel.y*ball->friction + gravity; } else { // Ball repositioning using the mouse position ball->pos.x = mousePos.x - pressOffset.x; ball->pos.y = mousePos.y - pressOffset.y; + // While the ball is grabbed, recalculates its velocity - ball->vel.x = (ball->pos.x - ball->ppos.x) / delta; - ball->vel.y = (ball->pos.y - ball->ppos.y) / delta; + ball->vel.x = (ball->pos.x - ball->ppos.x)/delta; + ball->vel.y = (ball->pos.y - ball->ppos.y)/delta; ball->ppos = ball->pos; } } - //---------------------------------------------------------------------------------- // Draw @@ -195,19 +195,18 @@ int main(void) ClearBackground(RAYWHITE); - for (int i = 0; i < ballQuantity; i++) + for (int i = 0; i < ballCount; i++) { - Ball *ball = &balls[i]; - DrawCircleV(ball->pos, ball->radius, ball->color); - DrawCircleLinesV(ball->pos, ball->radius, BLACK); + DrawCircleV(balls[i].pos, balls[i].radius, balls[i].color); + DrawCircleLinesV(balls[i].pos, balls[i].radius, BLACK); } - DrawText("grab a ball by pressing with the mouse and throw it by releasing", 10, 10, 20, DARKGRAY); - DrawText("right click to create new balls (keep left control pressed to create a lot)", 10, 30, 20, DARKGRAY); - DrawText("use mouse wheel to change gravity", 10, 50, 20, DARKGRAY); - DrawText("middle click to shake", 10, 70, 20, DARKGRAY); - DrawText(TextFormat("ball quantity: %d", ballQuantity), 10, GetScreenHeight() - 55, 20, BLACK); - DrawText(TextFormat("gravity: %.2f", gravity), 10, GetScreenHeight() - 35, 20, BLACK); + DrawText("grab a ball by pressing with the mouse and throw it by releasing", 10, 10, 10, DARKGRAY); + DrawText("right click to create new balls (keep left control pressed to create a lot)", 10, 30, 10, DARKGRAY); + DrawText("use mouse wheel to change gravity", 10, 50, 10, DARKGRAY); + DrawText("middle click to shake", 10, 70, 10, DARKGRAY); + DrawText(TextFormat("BALL COUNT: %d", ballCount), 10, GetScreenHeight() - 70, 20, BLACK); + DrawText(TextFormat("GRAVITY: %.2f", gravity), 10, GetScreenHeight() - 40, 20, BLACK); EndDrawing(); //---------------------------------------------------------------------------------- diff --git a/examples/shapes/shapes_ball_physics.png b/examples/shapes/shapes_ball_physics.png index 1e4c86f14d077ebd65f47bb47b078f10f2425a0a..89493275c49616f7777ecec5a638be81160cd951 100644 GIT binary patch literal 23245 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 literal 43216 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

4dRTgJcW|IwV`B_9W9P_nSqK~;h2JpFPkx@oU< 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 diff --git a/projects/VS2022/examples/shapes_ball_physics.vcxproj b/projects/VS2022/examples/shapes_ball_physics.vcxproj new file mode 100644 index 000000000..47bec68f2 --- /dev/null +++ b/projects/VS2022/examples/shapes_ball_physics.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {6B1A933E-71B8-4C1F-9E79-02D98830E671} + Win32Proj + shapes_ball_physics + 10.0 + shapes_ball_physics + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index 07068d34c..b541b10fe 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -421,6 +421,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_rlgl_triangle", "exa EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "textures_sprite_stacking", "examples\textures_sprite_stacking.vcxproj", "{FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_ball_physics", "examples\shapes_ball_physics.vcxproj", "{6B1A933E-71B8-4C1F-9E79-02D98830E671}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug.DLL|ARM64 = Debug.DLL|ARM64 @@ -5235,6 +5237,30 @@ Global {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release|x64.Build.0 = Release|x64 {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release|x86.ActiveCfg = Release|Win32 {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release|x86.Build.0 = Release|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|ARM64.Build.0 = Debug|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x64.ActiveCfg = Debug|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x64.Build.0 = Debug|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x86.ActiveCfg = Debug|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x86.Build.0 = Debug|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|ARM64.ActiveCfg = Release|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|ARM64.Build.0 = Release|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x64.ActiveCfg = Release|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x64.Build.0 = Release|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x86.ActiveCfg = Release|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5447,6 +5473,7 @@ Global {32FE2658-1D70-442E-8672-0AC5C6F0BD7B} = {278D8859-20B1-428F-8448-064F46E1F021} {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F} = {278D8859-20B1-428F-8448-064F46E1F021} {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} + {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {278D8859-20B1-428F-8448-064F46E1F021} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} diff --git a/tools/rexm/reports/examples_validation.md b/tools/rexm/reports/examples_validation.md index af3befad9..831dbd978 100644 --- a/tools/rexm/reports/examples_validation.md +++ b/tools/rexm/reports/examples_validation.md @@ -103,6 +103,7 @@ Example elements validated: | shapes_math_angle_rotation | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_rlgl_color_wheel | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_rlgl_triangle | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_ball_physics | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_logo_raylib | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_srcrec_dstrec | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_image_drawing | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | From ebce9fa97ae643b2b1b17cfac3a8d45dba6de3a9 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 29 Nov 2025 20:01:44 +0100 Subject: [PATCH 183/430] Update rcore_memory.c --- src/platforms/rcore_memory.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platforms/rcore_memory.c b/src/platforms/rcore_memory.c index e49159a85..f78b72fed 100644 --- a/src/platforms/rcore_memory.c +++ b/src/platforms/rcore_memory.c @@ -371,7 +371,6 @@ double GetTime(void) QueryPerformanceCounter(&now); return (double)(now.QuadPart - CORE.Time.base)/(double)platform.timerFrequency.QuadPart; #elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) - double time = 0.0; struct timespec ts = { 0 }; clock_gettime(CLOCK_MONOTONIC, &ts); unsigned long long int nanoSeconds = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; From 6a048b7afeada62f9071969ba277ad14e0dce256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20=E2=9D=A4=EF=B8=8F?= Date: Sat, 29 Nov 2025 17:22:38 -0500 Subject: [PATCH 184/430] corrected visualstudio project (#5375) --- projects/VS2022/examples/shapes_triangle_strip.vcxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/VS2022/examples/shapes_triangle_strip.vcxproj b/projects/VS2022/examples/shapes_triangle_strip.vcxproj index b128c0ff6..eb4e200dd 100644 --- a/projects/VS2022/examples/shapes_triangle_strip.vcxproj +++ b/projects/VS2022/examples/shapes_triangle_strip.vcxproj @@ -553,7 +553,7 @@ - + From a568506265deb1e9e84a70e7a77925405a0c0eeb Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 30 Nov 2025 18:32:11 +0100 Subject: [PATCH 185/430] REVIEWED: External libraries `sdefl` and `sinfl` to address #5367 --- src/external/sdefl.h | 2 +- src/external/sinfl.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/external/sdefl.h b/src/external/sdefl.h index 36015b95b..bdc45b7eb 100644 --- a/src/external/sdefl.h +++ b/src/external/sdefl.h @@ -198,7 +198,7 @@ extern int zsdeflate(struct sdefl *s, void *o, const void *i, int n, int lvl); static int sdefl_ilog2(int n) { if (!n) return 0; -#ifdef _MSC_VER +#if defined(_MSC_VER) && !defined(__llvm__) && !defined(__INTEL_COMPILER) // @raysan5, address PR #5367 unsigned long msbp = 0; _BitScanReverse(&msbp, (unsigned long)n); return (int)msbp; diff --git a/src/external/sinfl.h b/src/external/sinfl.h index a749501ca..c8d0f96d0 100644 --- a/src/external/sinfl.h +++ b/src/external/sinfl.h @@ -171,7 +171,7 @@ extern int zsinflate(void *out, int cap, const void *in, int size); static int sinfl_bsr(unsigned n) { -#ifdef _MSC_VER +#if defined(_MSC_VER) && !defined(__llvm__) && !defined(__INTEL_COMPILER) // @raysan5, address PR #5367 unsigned long uln = 0; _BitScanReverse(&uln, n); return (int)(uln); From 4724f7cf1bc255bf23610326e564c24249ce3636 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 30 Nov 2025 19:02:38 +0100 Subject: [PATCH 186/430] REVIEWED: Comments for `UpdateSound()` specifying expected data format #5350 --- src/raudio.c | 4 ++-- src/raylib.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/raudio.c b/src/raudio.c index 2416f0849..d208bb6eb 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -1040,8 +1040,8 @@ void UnloadSoundAlias(Sound alias) } // Update sound buffer with new data -// NOTE 1: data format must match sound.stream.sampleSize -// NOTE 2: frameCount must not exceed sound.frameCount +// PARAMS: [data], format must match sound.stream.sampleSize, default 32 bit float - stereo +// PARAMS: [frameCount] must not exceed sound.frameCount void UpdateSound(Sound sound, const void *data, int frameCount) { if (sound.stream.buffer != NULL) diff --git a/src/raylib.h b/src/raylib.h index ece2e6aab..96dc316ae 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1657,7 +1657,7 @@ RLAPI Sound LoadSound(const char *fileName); // Load so RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data RLAPI Sound LoadSoundAlias(Sound source); // Create a new sound that shares the same sample data as the source sound, does not own the sound data RLAPI bool IsSoundValid(Sound sound); // Checks if a sound is valid (data loaded and buffers initialized) -RLAPI void UpdateSound(Sound sound, const void *data, int sampleCount); // Update sound buffer with new data (data and frame count should fit in sound) +RLAPI void UpdateSound(Sound sound, const void *data, int sampleCount); // Update sound buffer with new data (default data format: 32 bit float, stereo) RLAPI void UnloadWave(Wave wave); // Unload wave data RLAPI void UnloadSound(Sound sound); // Unload sound RLAPI void UnloadSoundAlias(Sound alias); // Unload a sound alias (does not deallocate sample data) From 4d9df337a770fc7afff2b8334b9e3308e0e09fce Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 30 Nov 2025 18:02:52 +0000 Subject: [PATCH 187/430] rlparser: update raylib_api.* by CI --- tools/rlparser/output/raylib_api.json | 2 +- tools/rlparser/output/raylib_api.lua | 2 +- tools/rlparser/output/raylib_api.txt | 2 +- tools/rlparser/output/raylib_api.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/rlparser/output/raylib_api.json b/tools/rlparser/output/raylib_api.json index b3d02a928..a1af3c9fc 100644 --- a/tools/rlparser/output/raylib_api.json +++ b/tools/rlparser/output/raylib_api.json @@ -11730,7 +11730,7 @@ }, { "name": "UpdateSound", - "description": "Update sound buffer with new data (data and frame count should fit in sound)", + "description": "Update sound buffer with new data (default data format: 32 bit float, stereo)", "returnType": "void", "params": [ { diff --git a/tools/rlparser/output/raylib_api.lua b/tools/rlparser/output/raylib_api.lua index e680a5acf..20043c12d 100644 --- a/tools/rlparser/output/raylib_api.lua +++ b/tools/rlparser/output/raylib_api.lua @@ -7995,7 +7995,7 @@ return { }, { name = "UpdateSound", - description = "Update sound buffer with new data (data and frame count should fit in sound)", + description = "Update sound buffer with new data (default data format: 32 bit float, stereo)", returnType = "void", params = { {type = "Sound", name = "sound"}, diff --git a/tools/rlparser/output/raylib_api.txt b/tools/rlparser/output/raylib_api.txt index e2edb8f3f..3578e41df 100644 --- a/tools/rlparser/output/raylib_api.txt +++ b/tools/rlparser/output/raylib_api.txt @@ -4483,7 +4483,7 @@ Function 543: IsSoundValid() (1 input parameters) Function 544: UpdateSound() (3 input parameters) Name: UpdateSound Return type: void - Description: Update sound buffer with new data (data and frame count should fit in sound) + Description: Update sound buffer with new data (default data format: 32 bit float, stereo) Param[1]: sound (type: Sound) Param[2]: data (type: const void *) Param[3]: sampleCount (type: int) diff --git a/tools/rlparser/output/raylib_api.xml b/tools/rlparser/output/raylib_api.xml index 3d1892c7c..ea7792612 100644 --- a/tools/rlparser/output/raylib_api.xml +++ b/tools/rlparser/output/raylib_api.xml @@ -2998,7 +2998,7 @@ - + From 3ba186f2c1d6f307740d313653772f0a312f5ec3 Mon Sep 17 00:00:00 2001 From: David Buzatto Date: Mon, 1 Dec 2025 08:57:45 -0300 Subject: [PATCH 188/430] [examples] Added: `shapes_penrose_tile` (#5376) * new shapes example - penrose tile * stack cleanup * proper use of strnlen, strncat and strncpy * typo correction * update screenshot of shapes_penrose_tile example --- examples/shapes/shapes_penrose_tile.c | 273 ++++++++++++++++++++++++ examples/shapes/shapes_penrose_tile.png | Bin 0 -> 25548 bytes 2 files changed, 273 insertions(+) create mode 100644 examples/shapes/shapes_penrose_tile.c create mode 100644 examples/shapes/shapes_penrose_tile.png diff --git a/examples/shapes/shapes_penrose_tile.c b/examples/shapes/shapes_penrose_tile.c new file mode 100644 index 000000000..dee62248d --- /dev/null +++ b/examples/shapes/shapes_penrose_tile.c @@ -0,0 +1,273 @@ +/******************************************************************************************* +* +* raylib [shapes] example - penrose tile +* +* Example complexity rating: [★★★★] 4/4 +* +* Example originally created with raylib 5.5 +* Based on: https://processing.org/examples/penrosetile.html +* +* Example contributed by David Buzatto (@davidbuzatto) 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) 2025 David Buzatto (@davidbuzatto) +* +********************************************************************************************/ + +#include +#include +#include +#include "raylib.h" + +#define STR_MAX_SIZE 10000 +#define TURTLE_STACK_MAX_SIZE 50 + +typedef struct TurtleState { + Vector2 origin; + double angle; +} TurtleState; + +typedef struct PenroseLSystem { + int steps; + char *production; + const char *ruleW; + const char *ruleX; + const char *ruleY; + const char *ruleZ; + float drawLength; + float theta; +} PenroseLSystem; + +static TurtleState turtleStack[TURTLE_STACK_MAX_SIZE]; +static int turtleTop = -1; + +void PushTurtleState(TurtleState state) +{ + if (turtleTop < TURTLE_STACK_MAX_SIZE - 1) + { + turtleStack[++turtleTop] = state; + } + else + { + TraceLog(LOG_WARNING, "TURTLE STACK OVERFLOW!"); + } +} + +TurtleState PopTurtleState(void) +{ + if (turtleTop >= 0) + { + return turtleStack[turtleTop--]; + } + else + { + TraceLog(LOG_WARNING, "TURTLE STACK UNDERFLOW!"); + } + return (TurtleState) {0}; +} + +PenroseLSystem CreatePenroseLSystem(float drawLength) +{ + PenroseLSystem ls = { + .steps = 0, + .ruleW = "YF++ZF4-XF[-YF4-WF]++", + .ruleX = "+YF--ZF[3-WF--XF]+", + .ruleY = "-WF++XF[+++YF++ZF]-", + .ruleZ = "--YF++++WF[+ZF++++XF]--XF", + .drawLength = drawLength, + .theta = 36.0f // in degrees + }; + ls.production = (char*) malloc(sizeof(char) * STR_MAX_SIZE); + ls.production[0] = '\0'; + strncpy(ls.production, "[X]++[X]++[X]++[X]++[X]", STR_MAX_SIZE); + return ls; +} + +void DrawPenroseLSystem(PenroseLSystem *ls) +{ + Vector2 screenCenter = {GetScreenWidth()/2, GetScreenHeight()/2}; + + TurtleState turtle = { + .origin = {0}, + .angle = -90.0f + }; + + int repeats = 1; + int productionLength = (int) strnlen(ls->production, STR_MAX_SIZE); + ls->steps += 12; + + if (ls->steps > productionLength) + { + ls->steps = productionLength; + } + + for (int i = 0; i < ls->steps; i++) + { + char step = ls->production[i]; + if ( step == 'F' ) + { + for ( int j = 0; j < repeats; j++ ) + { + Vector2 startPosWorld = turtle.origin; + float radAngle = DEG2RAD * turtle.angle; + turtle.origin.x += ls->drawLength * cosf(radAngle); + turtle.origin.y += ls->drawLength * sinf(radAngle); + Vector2 startPosScreen = {startPosWorld.x + screenCenter.x, startPosWorld.y + screenCenter.y}; + Vector2 endPosScreen = {turtle.origin.x + screenCenter.x, turtle.origin.y + screenCenter.y}; + DrawLineEx(startPosScreen, endPosScreen, 2, Fade(BLACK, 0.2)); + } + repeats = 1; + } + else if ( step == '+' ) + { + for ( int j = 0; j < repeats; j++ ) + { + turtle.angle += ls->theta; + } + repeats = 1; + } + else if ( step == '-' ) + { + for ( int j = 0; j < repeats; j++ ) + { + turtle.angle += -ls->theta; + } + repeats = 1; + } + else if ( step == '[' ) + { + PushTurtleState(turtle); + } + else if ( step == ']' ) + { + turtle = PopTurtleState(); + } + else if ( ( step >= 48 ) && ( step <= 57 ) ) + { + repeats = (int) step - 48; + } + } + + turtleTop = -1; + +} + +void BuildProductionStep(PenroseLSystem *ls) +{ + char *newProduction = (char*) malloc(sizeof(char) * STR_MAX_SIZE); + newProduction[0] = '\0'; + + int productionLength = strnlen(ls->production, STR_MAX_SIZE); + + for (int i = 0; i < productionLength; i++) + { + char step = ls->production[i]; + int remainingSpace = STR_MAX_SIZE - strnlen(newProduction, STR_MAX_SIZE) - 1; + switch (step) + { + case 'W': strncat(newProduction, ls->ruleW, remainingSpace); break; + case 'X': strncat(newProduction, ls->ruleX, remainingSpace); break; + case 'Y': strncat(newProduction, ls->ruleY, remainingSpace); break; + case 'Z': strncat(newProduction, ls->ruleZ, remainingSpace); break; + default: + { + if (step != 'F') + { + int t = strnlen(newProduction, STR_MAX_SIZE); + newProduction[t] = step; + newProduction[t+1] = '\0'; + } + } break; + } + } + + ls->drawLength *= 0.5f; + strncpy(ls->production, newProduction, STR_MAX_SIZE); + free( newProduction ); +} + +void BuildPenroseLSystem(PenroseLSystem *ls, float drawLength, int generations) +{ + *ls = CreatePenroseLSystem(drawLength); + for (int i = 0; i < generations; i++) + { + BuildProductionStep(ls); + } +} + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + SetConfigFlags( FLAG_MSAA_4X_HINT ); + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - penrose tile"); + + float drawLength = 460.0f; + int minGenerations = 0; + int maxGenerations = 4; + int generations = 0; + + PenroseLSystem ls = {0}; + BuildPenroseLSystem(&ls, drawLength * (generations / (float) maxGenerations), generations); + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //--------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + bool rebuild = false; + if (IsKeyPressed(KEY_UP)) + { + if (generations < maxGenerations) + { + generations++; + rebuild = true; + } + } + else if (IsKeyPressed(KEY_DOWN)) + { + if (generations > minGenerations) + { + generations--; + rebuild = generations > 0; + } + } + if (rebuild) + { + BuildPenroseLSystem(&ls, drawLength * (generations / (float) maxGenerations), generations); + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + ClearBackground( RAYWHITE ); + if (generations > 0) + { + DrawPenroseLSystem(&ls); + } + DrawText("penrose l-system", 10, 10, 20, DARKGRAY); + DrawText("press up or down to change generations", 10, 30, 20, DARKGRAY); + DrawText(TextFormat("generations: %d", generations), 10, 50, 20, DARKGRAY); + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/shapes/shapes_penrose_tile.png b/examples/shapes/shapes_penrose_tile.png new file mode 100644 index 0000000000000000000000000000000000000000..dffc35cc4832eaa89dec1aaff25031cde245bd99 GIT binary patch literal 25548 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 literal 0 HcmV?d00001 From d13314fe1c7c5014dcf961f80c9278d858753d73 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 2 Dec 2025 22:21:41 +0100 Subject: [PATCH 189/430] Update core_window_flags.c --- examples/core/core_window_flags.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/core/core_window_flags.c b/examples/core/core_window_flags.c index a8096eeb4..185e7f22c 100644 --- a/examples/core/core_window_flags.c +++ b/examples/core/core_window_flags.c @@ -43,7 +43,7 @@ int main(void) */ // Set configuration flags for window creation - //SetConfigFlags(FLAG_VSYNC_HINT | FLAG_MSAA_4X_HINT | FLAG_WINDOW_HIGHDPI); + //SetConfigFlags(FLAG_VSYNC_HINT | FLAG_MSAA_4X_HINT | FLAG_WINDOW_HIGHDPI);// | FLAG_WINDOW_TRANSPARENT); InitWindow(screenWidth, screenHeight, "raylib [core] example - window flags"); Vector2 ballPosition = { GetScreenWidth()/2.0f, GetScreenHeight()/2.0f }; From d3addad9a7ee1538552f8ca401eb6f84d2019c42 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 2 Dec 2025 22:34:48 +0100 Subject: [PATCH 190/430] REVIEWED: example: `shapes_penrose_tile` formating --- examples/shapes/shapes_penrose_tile.c | 338 +++++++++++++------------- 1 file changed, 170 insertions(+), 168 deletions(-) diff --git a/examples/shapes/shapes_penrose_tile.c b/examples/shapes/shapes_penrose_tile.c index dee62248d..948a29d12 100644 --- a/examples/shapes/shapes_penrose_tile.c +++ b/examples/shapes/shapes_penrose_tile.c @@ -16,14 +16,18 @@ * ********************************************************************************************/ +#include "raylib.h" + #include #include #include -#include "raylib.h" -#define STR_MAX_SIZE 10000 -#define TURTLE_STACK_MAX_SIZE 50 +#define STR_MAX_SIZE 10000 +#define TURTLE_STACK_MAX_SIZE 50 +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- typedef struct TurtleState { Vector2 origin; double angle; @@ -40,162 +44,21 @@ typedef struct PenroseLSystem { float theta; } PenroseLSystem; +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- static TurtleState turtleStack[TURTLE_STACK_MAX_SIZE]; static int turtleTop = -1; -void PushTurtleState(TurtleState state) -{ - if (turtleTop < TURTLE_STACK_MAX_SIZE - 1) - { - turtleStack[++turtleTop] = state; - } - else - { - TraceLog(LOG_WARNING, "TURTLE STACK OVERFLOW!"); - } -} - -TurtleState PopTurtleState(void) -{ - if (turtleTop >= 0) - { - return turtleStack[turtleTop--]; - } - else - { - TraceLog(LOG_WARNING, "TURTLE STACK UNDERFLOW!"); - } - return (TurtleState) {0}; -} - -PenroseLSystem CreatePenroseLSystem(float drawLength) -{ - PenroseLSystem ls = { - .steps = 0, - .ruleW = "YF++ZF4-XF[-YF4-WF]++", - .ruleX = "+YF--ZF[3-WF--XF]+", - .ruleY = "-WF++XF[+++YF++ZF]-", - .ruleZ = "--YF++++WF[+ZF++++XF]--XF", - .drawLength = drawLength, - .theta = 36.0f // in degrees - }; - ls.production = (char*) malloc(sizeof(char) * STR_MAX_SIZE); - ls.production[0] = '\0'; - strncpy(ls.production, "[X]++[X]++[X]++[X]++[X]", STR_MAX_SIZE); - return ls; -} - -void DrawPenroseLSystem(PenroseLSystem *ls) -{ - Vector2 screenCenter = {GetScreenWidth()/2, GetScreenHeight()/2}; - - TurtleState turtle = { - .origin = {0}, - .angle = -90.0f - }; - - int repeats = 1; - int productionLength = (int) strnlen(ls->production, STR_MAX_SIZE); - ls->steps += 12; - - if (ls->steps > productionLength) - { - ls->steps = productionLength; - } - - for (int i = 0; i < ls->steps; i++) - { - char step = ls->production[i]; - if ( step == 'F' ) - { - for ( int j = 0; j < repeats; j++ ) - { - Vector2 startPosWorld = turtle.origin; - float radAngle = DEG2RAD * turtle.angle; - turtle.origin.x += ls->drawLength * cosf(radAngle); - turtle.origin.y += ls->drawLength * sinf(radAngle); - Vector2 startPosScreen = {startPosWorld.x + screenCenter.x, startPosWorld.y + screenCenter.y}; - Vector2 endPosScreen = {turtle.origin.x + screenCenter.x, turtle.origin.y + screenCenter.y}; - DrawLineEx(startPosScreen, endPosScreen, 2, Fade(BLACK, 0.2)); - } - repeats = 1; - } - else if ( step == '+' ) - { - for ( int j = 0; j < repeats; j++ ) - { - turtle.angle += ls->theta; - } - repeats = 1; - } - else if ( step == '-' ) - { - for ( int j = 0; j < repeats; j++ ) - { - turtle.angle += -ls->theta; - } - repeats = 1; - } - else if ( step == '[' ) - { - PushTurtleState(turtle); - } - else if ( step == ']' ) - { - turtle = PopTurtleState(); - } - else if ( ( step >= 48 ) && ( step <= 57 ) ) - { - repeats = (int) step - 48; - } - } - - turtleTop = -1; - -} - -void BuildProductionStep(PenroseLSystem *ls) -{ - char *newProduction = (char*) malloc(sizeof(char) * STR_MAX_SIZE); - newProduction[0] = '\0'; - - int productionLength = strnlen(ls->production, STR_MAX_SIZE); - - for (int i = 0; i < productionLength; i++) - { - char step = ls->production[i]; - int remainingSpace = STR_MAX_SIZE - strnlen(newProduction, STR_MAX_SIZE) - 1; - switch (step) - { - case 'W': strncat(newProduction, ls->ruleW, remainingSpace); break; - case 'X': strncat(newProduction, ls->ruleX, remainingSpace); break; - case 'Y': strncat(newProduction, ls->ruleY, remainingSpace); break; - case 'Z': strncat(newProduction, ls->ruleZ, remainingSpace); break; - default: - { - if (step != 'F') - { - int t = strnlen(newProduction, STR_MAX_SIZE); - newProduction[t] = step; - newProduction[t+1] = '\0'; - } - } break; - } - } - - ls->drawLength *= 0.5f; - strncpy(ls->production, newProduction, STR_MAX_SIZE); - free( newProduction ); -} - -void BuildPenroseLSystem(PenroseLSystem *ls, float drawLength, int generations) -{ - *ls = CreatePenroseLSystem(drawLength); - for (int i = 0; i < generations; i++) - { - BuildProductionStep(ls); - } -} +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +static void PushTurtleState(TurtleState state); +static TurtleState PopTurtleState(void); +static PenroseLSystem CreatePenroseLSystem(float drawLength); +static void BuildProductionStep(PenroseLSystem *ls); +static void BuildPenroseLSystem(PenroseLSystem *ls, float drawLength, int generations); +static void DrawPenroseLSystem(PenroseLSystem *ls); //------------------------------------------------------------------------------------ // Program main entry point @@ -207,7 +70,7 @@ int main(void) const int screenWidth = 800; const int screenHeight = 450; - SetConfigFlags( FLAG_MSAA_4X_HINT ); + SetConfigFlags(FLAG_MSAA_4X_HINT); InitWindow(screenWidth, screenHeight, "raylib [shapes] example - penrose tile"); float drawLength = 460.0f; @@ -216,7 +79,7 @@ int main(void) int generations = 0; PenroseLSystem ls = {0}; - BuildPenroseLSystem(&ls, drawLength * (generations / (float) maxGenerations), generations); + BuildPenroseLSystem(&ls, drawLength*(generations/(float)maxGenerations), generations); SetTargetFPS(60); // Set our game to run at 60 frames-per-second //--------------------------------------------------------------------------------------- @@ -240,26 +103,25 @@ int main(void) if (generations > minGenerations) { generations--; - rebuild = generations > 0; + if (generations > 0) rebuild = true; } } - if (rebuild) - { - BuildPenroseLSystem(&ls, drawLength * (generations / (float) maxGenerations), generations); - } + + if (rebuild) BuildPenroseLSystem(&ls, drawLength*(generations/(float)maxGenerations), generations); //---------------------------------------------------------------------------------- // Draw //---------------------------------------------------------------------------------- BeginDrawing(); + ClearBackground( RAYWHITE ); - if (generations > 0) - { - DrawPenroseLSystem(&ls); - } + + if (generations > 0) DrawPenroseLSystem(&ls); + DrawText("penrose l-system", 10, 10, 20, DARKGRAY); DrawText("press up or down to change generations", 10, 30, 20, DARKGRAY); DrawText(TextFormat("generations: %d", generations), 10, 50, 20, DARKGRAY); + EndDrawing(); //---------------------------------------------------------------------------------- } @@ -270,4 +132,144 @@ int main(void) //-------------------------------------------------------------------------------------- return 0; -} \ No newline at end of file +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +void PushTurtleState(TurtleState state) +{ + if (turtleTop < (TURTLE_STACK_MAX_SIZE - 1)) turtleStack[++turtleTop] = state; + else TraceLog(LOG_WARNING, "TURTLE STACK OVERFLOW!"); +} + +TurtleState PopTurtleState(void) +{ + if (turtleTop >= 0) return turtleStack[turtleTop--]; + else TraceLog(LOG_WARNING, "TURTLE STACK UNDERFLOW!"); + + return (TurtleState){ 0 }; +} + +PenroseLSystem CreatePenroseLSystem(float drawLength) +{ + PenroseLSystem ls = { + .steps = 0, + .ruleW = "YF++ZF4-XF[-YF4-WF]++", + .ruleX = "+YF--ZF[3-WF--XF]+", + .ruleY = "-WF++XF[+++YF++ZF]-", + .ruleZ = "--YF++++WF[+ZF++++XF]--XF", + .drawLength = drawLength, + .theta = 36.0f // Degrees + }; + + ls.production = (char *)RL_MALLOC(sizeof(char)*STR_MAX_SIZE); + ls.production[0] = '\0'; + strncpy(ls.production, "[X]++[X]++[X]++[X]++[X]", STR_MAX_SIZE); + + return ls; +} + +void BuildProductionStep(PenroseLSystem *ls) +{ + char *newProduction = (char *)RL_MALLOC(sizeof(char)*STR_MAX_SIZE); + newProduction[0] = '\0'; + + int productionLength = strnlen(ls->production, STR_MAX_SIZE); + + for (int i = 0; i < productionLength; i++) + { + char step = ls->production[i]; + int remainingSpace = STR_MAX_SIZE - strnlen(newProduction, STR_MAX_SIZE) - 1; + switch (step) + { + case 'W': strncat(newProduction, ls->ruleW, remainingSpace); break; + case 'X': strncat(newProduction, ls->ruleX, remainingSpace); break; + case 'Y': strncat(newProduction, ls->ruleY, remainingSpace); break; + case 'Z': strncat(newProduction, ls->ruleZ, remainingSpace); break; + default: + { + if (step != 'F') + { + int t = strnlen(newProduction, STR_MAX_SIZE); + newProduction[t] = step; + newProduction[t + 1] = '\0'; + } + } break; + } + } + + ls->drawLength *= 0.5f; + strncpy(ls->production, newProduction, STR_MAX_SIZE); + + RL_FREE(newProduction); +} + +void BuildPenroseLSystem(PenroseLSystem *ls, float drawLength, int generations) +{ + *ls = CreatePenroseLSystem(drawLength); + for (int i = 0; i < generations; i++) BuildProductionStep(ls); +} + +void DrawPenroseLSystem(PenroseLSystem *ls) +{ + Vector2 screenCenter = { GetScreenWidth()/2, GetScreenHeight()/2 }; + + TurtleState turtle = { + .origin = {0}, + .angle = -90.0f + }; + + int repeats = 1; + int productionLength = (int)strnlen(ls->production, STR_MAX_SIZE); + ls->steps += 12; + + if (ls->steps > productionLength) ls->steps = productionLength; + + for (int i = 0; i < ls->steps; i++) + { + char step = ls->production[i]; + if (step == 'F') + { + for (int j = 0; j < repeats; j++) + { + Vector2 startPosWorld = turtle.origin; + float radAngle = DEG2RAD*turtle.angle; + turtle.origin.x += ls->drawLength*cosf(radAngle); + turtle.origin.y += ls->drawLength*sinf(radAngle); + Vector2 startPosScreen = { startPosWorld.x + screenCenter.x, startPosWorld.y + screenCenter.y }; + Vector2 endPosScreen = { turtle.origin.x + screenCenter.x, turtle.origin.y + screenCenter.y }; + + DrawLineEx(startPosScreen, endPosScreen, 2, Fade(BLACK, 0.2)); + } + + repeats = 1; + } + else if (step == '+') + { + for (int j = 0; j < repeats; j++) turtle.angle += ls->theta; + + repeats = 1; + } + else if (step == '-') + { + for (int j = 0; j < repeats; j++) turtle.angle += -ls->theta; + + repeats = 1; + } + else if (step == '[') + { + PushTurtleState(turtle); + } + else if (step == ']') + { + turtle = PopTurtleState(); + } + else if ((step >= 48) && (step <= 57)) + { + repeats = (int) step - 48; + } + } + + turtleTop = -1; +} From ed5da4520343b5952beb9ca491e2bdbc129fc791 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 2 Dec 2025 22:46:12 +0100 Subject: [PATCH 191/430] Update LICENSE.md #5380 --- examples/text/resources/LICENSE.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/text/resources/LICENSE.md b/examples/text/resources/LICENSE.md index 506a4aaf0..91fd618fd 100644 --- a/examples/text/resources/LICENSE.md +++ b/examples/text/resources/LICENSE.md @@ -8,13 +8,15 @@ | fonts/mecha.png | Captain Falcon | [Freeware](https://www.dafont.com/es/mecha-cf.font) | Atlas created by [@raysan5](https://github.com/raysan5) | | fonts/pixelplay.png | Aleksander Shevchuk | [Freeware](https://www.dafont.com/es/pixelplay.font) | Atlas created by [@raysan5](https://github.com/raysan5) | | fonts/pixantiqua.ttf | Gerhard Großmann | [Freeware](https://www.dafont.com/es/pixantiqua.font) | Atlas created by [@raysan5](https://github.com/raysan5) | -| anonymous_pro_bold.ttf | [Mark Simonson](https://fonts.google.com/specimen/Anonymous+Pro) | [Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL) | - | +| anonymous_pro_bold.ttf | [Mark Simonson](https://fonts.google.com/specimen/Anonymous+Pro) | [SIL Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL) | - | | custom_alagard.png | [Brian Kent (AEnigma)](https://www.dafont.com/es/aenigma.d188) | [Freeware](https://www.dafont.com/es/jupiter-crash.font) | Atlas created by [@raysan5](https://github.com/raysan5) | | custom_jupiter_crash.png | [Brian Kent (AEnigma)](https://www.dafont.com/es/aenigma.d188) | [Freeware](https://www.dafont.com/es/jupiter-crash.font) | Atlas created by [@raysan5](https://github.com/raysan5) | | custom_mecha.png | [Brian Kent (AEnigma)](https://www.dafont.com/es/aenigma.d188) | [Freeware](https://www.dafont.com/es/jupiter-crash.font) | Atlas created by [@raysan5](https://github.com/raysan5) | | dejavu.fnt, dejavu.png | [DejaVu Fonts](https://dejavu-fonts.github.io/) | [Free](https://dejavu-fonts.github.io/License.html) | Atlas made with [BMFont](https://www.angelcode.com/products/bmfont/) by [@raysan5](https://github.com/raysan5) | | KAISG.ttf | [Dieter Steffmann](http://www.steffmann.de/wordpress/) | [Freeware](https://www.1001fonts.com/users/steffmann/) | [Kaiserzeit Gotisch](https://www.dafont.com/es/kaiserzeit-gotisch.font) font | -| noto_cjk.fnt, noto_cjk.png | [Google Fonts](https://www.google.com/get/noto/help/cjk/) | [Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL) | Atlas made with [BMFont](https://www.angelcode.com/products/bmfont/) by [@raysan5](https://github.com/raysan5) | +| noto_cjk.fnt, noto_cjk.png | [Google Fonts](https://www.google.com/get/noto/help/cjk/) | [SIL Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL) | Atlas made with [BMFont](https://www.angelcode.com/products/bmfont/) by [@raysan5](https://github.com/raysan5) | | pixantiqua.fnt, pixantiqua.png | Gerhard Großmann | [Freeware](https://www.dafont.com/es/pixantiqua.font) | Atlas made with [BMFont](https://www.angelcode.com/products/bmfont/) by [@raysan5](https://github.com/raysan5) | | pixantiqua.ttf | Gerhard Großmann | [Freeware](https://www.dafont.com/es/pixantiqua.font) | - | | symbola.fnt, symbola.png | George Douros | [Freeware](https://fontlibrary.org/en/font/symbola) | Atlas made with [BMFont](https://www.angelcode.com/products/bmfont/) by [@raysan5](https://github.com/raysan5) | +| DotGothic16-Regular.ttf | [The DotGothic16 Project Authors](https://github.com/fontworks-fonts/DotGothic16) | [Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL) | - | +| NotoSansTC-Regular.ttf | [Adobe](http://www.adobe.com/) | [SIL Open Font License](https://openfontlicense.org/documents/OFL.txt) | - | From 1bbc8682f47b996b363d40f5692dc73765145f4f Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Tue, 2 Dec 2025 16:48:06 -0500 Subject: [PATCH 192/430] Fixed some typos and mispellings (#5381) Specifically "occured" -> "occurred" --- CHANGELOG | 2 +- examples/shapes/shapes_ball_physics.c | 2 +- examples/shapes/shapes_double_pendulum.c | 2 +- src/platforms/rcore_drm.c | 4 ++-- src/rcore.c | 2 +- src/rtext.c | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f792bca4d..29e4d8174 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -565,7 +565,7 @@ Detailed changes: [rtext] ADDED: SetTextLineSpacing() to define line breaks text drawing spacing by @raysan5 [rtext] RENAMED: LoadFont*() parameter names for consistency and coherence by @raysan5 [rtext] REVIEWED: GetCodepointCount(), ignore unused return value of GetCodepointNext by @ashn-dot-dev -[rtext] REVIEWED: TextFormat() warn user if buffer overflow occured (#3399) by @Murlocohol +[rtext] REVIEWED: TextFormat() warn user if buffer overflow occurred (#3399) by @Murlocohol [rtext] REVIEWED: TextFormat(), added "..." for truncation (#3366) by @raysan5 [rtext] REVIEWED: GetGlyphIndex() (#3000) by @raysan5 [rtext] REVIEWED: GetCodepointNext() to return default value by @chocolate42 diff --git a/examples/shapes/shapes_ball_physics.c b/examples/shapes/shapes_ball_physics.c index 8ba6a14e7..f9b620d28 100644 --- a/examples/shapes/shapes_ball_physics.c +++ b/examples/shapes/shapes_ball_physics.c @@ -83,7 +83,7 @@ int main(void) pressOffset.y = mousePos.y - ball->pos.y; // If the distance between the ball position and the mouse press position - // is less or equal the ball radius, the event occured inside the ball + // is less than or equal to the ball radius, the event occurred inside the ball if (hypot(pressOffset.x, pressOffset.y) <= ball->radius) { ball->grabbed = true; diff --git a/examples/shapes/shapes_double_pendulum.c b/examples/shapes/shapes_double_pendulum.c index cbf487f93..760d66203 100644 --- a/examples/shapes/shapes_double_pendulum.c +++ b/examples/shapes/shapes_double_pendulum.c @@ -42,7 +42,7 @@ int main(void) SetConfigFlags(FLAG_WINDOW_HIGHDPI); InitWindow(screenWidth, screenHeight, "raylib [shapes] example - double pendulum"); - // Simulation Paramters + // Simulation Parameters float l1 = 15.0f, m1 = 0.2f, theta1 = DEG2RAD*170, w1 = 0; float l2 = 15.0f, m2 = 0.1f, theta2 = DEG2RAD*0, w2 = 0; float lengthScaler = 0.1f; diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 881f96034..68d5b9685 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -1865,7 +1865,7 @@ static void ProcessKeyboard(void) } #endif // SUPPORT_SSH_KEYBOARD_RPI -// Initialise user input from evdev(/dev/input/event) +// Initialize user input from evdev(/dev/input/event) // this means mouse, keyboard or gamepad devices static void InitEvdevInput(void) { @@ -1873,7 +1873,7 @@ static void InitEvdevInput(void) DIR *directory = NULL; struct dirent *entity = NULL; - // Initialise keyboard file descriptor + // Initialize keyboard file descriptor platform.keyboardFd = -1; platform.mouseFd = -1; diff --git a/src/rcore.c b/src/rcore.c index 88448bdfe..58ff963e6 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -4308,7 +4308,7 @@ const char *TextFormat(const char *text, ...) int requiredByteCount = vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args); va_end(args); - // If requiredByteCount is larger than the MAX_TEXT_BUFFER_LENGTH, then overflow occured + // If requiredByteCount is larger than the MAX_TEXT_BUFFER_LENGTH, then overflow occurred if (requiredByteCount >= MAX_TEXT_BUFFER_LENGTH) { // Inserting "..." at the end of the string to mark as truncated diff --git a/src/rtext.c b/src/rtext.c index 8a3961a00..37f4eaafa 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1524,7 +1524,7 @@ const char *TextFormat(const char *text, ...) int requiredByteCount = vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args); va_end(args); - // If requiredByteCount is larger than the MAX_TEXT_BUFFER_LENGTH, then overflow occured + // If requiredByteCount is larger than the MAX_TEXT_BUFFER_LENGTH, then overflow occurred if (requiredByteCount >= MAX_TEXT_BUFFER_LENGTH) { // Inserting "..." at the end of the string to mark as truncated From 944567651ca7e3f43ff758a1326da481bc9d07a7 Mon Sep 17 00:00:00 2001 From: Connor O'Connor Date: Tue, 2 Dec 2025 16:49:55 -0500 Subject: [PATCH 193/430] replace sprintf with snprintf (#5382) --- src/rcore.c | 2 +- src/rtext.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 58ff963e6..3a9a47359 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -4313,7 +4313,7 @@ const char *TextFormat(const char *text, ...) { // Inserting "..." at the end of the string to mark as truncated char *truncBuffer = buffers[index] + MAX_TEXT_BUFFER_LENGTH - 4; // Adding 4 bytes = "...\0" - sprintf(truncBuffer, "..."); + snprintf(truncBuffer, 4, "..."); } index += 1; // Move to next buffer for next function call diff --git a/src/rtext.c b/src/rtext.c index 37f4eaafa..c17fbe9bf 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1529,7 +1529,7 @@ const char *TextFormat(const char *text, ...) { // Inserting "..." at the end of the string to mark as truncated char *truncBuffer = buffers[index] + MAX_TEXT_BUFFER_LENGTH - 4; // Adding 4 bytes = "...\0" - sprintf(truncBuffer, "..."); + snprintf(truncBuffer, 4, "..."); } index += 1; // Move to next buffer for next function call From 78a81bf407cf707980fc79f5869896548b8c6092 Mon Sep 17 00:00:00 2001 From: Aly Date: Tue, 2 Dec 2025 13:55:22 -0800 Subject: [PATCH 194/430] Fix ToggleBorderlessFullscreen() Not Hiding Taskbar (#5383) * Use glfwSetWindowMonitor instead of Pos and Size GLFW functions * Fix window not resetting properly when toggling out of fullscreen, formatting --- src/platforms/rcore_desktop_glfw.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 4f4e2c141..211e0f701 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -265,8 +265,15 @@ void ToggleBorderlessWindowed(void) const int monitorHeight = mode->height; // Set screen position and size - glfwSetWindowPos(platform.handle, monitorPosX, monitorPosY); - glfwSetWindowSize(platform.handle, monitorWidth, monitorHeight); + glfwSetWindowMonitor( + platform.handle, + monitors[monitor], + monitorPosX, + monitorPosY, + monitorWidth, + monitorHeight, + mode->refreshRate + ); // Refocus window glfwFocusWindow(platform.handle); @@ -281,8 +288,15 @@ void ToggleBorderlessWindowed(void) // Return previous screen size and position // NOTE: The order matters here, it must set size first, then set position, otherwise the screen will be positioned incorrectly - glfwSetWindowSize(platform.handle, CORE.Window.previousScreen.width, CORE.Window.previousScreen.height); - glfwSetWindowPos(platform.handle, CORE.Window.previousPosition.x, CORE.Window.previousPosition.y); + glfwSetWindowMonitor( + platform.handle, + NULL, + CORE.Window.previousPosition.x, + CORE.Window.previousPosition.y, + CORE.Window.previousScreen.width, + CORE.Window.previousScreen.height, + mode->refreshRate + ); // Refocus window glfwFocusWindow(platform.handle); From b1f8cde32992db160799ed1424951618c6f3ea47 Mon Sep 17 00:00:00 2001 From: David Buzatto Date: Wed, 3 Dec 2025 05:44:18 -0300 Subject: [PATCH 195/430] [examples] Added: `text_strings_management` (#5379) * new shapes example - penrose tile * stack cleanup * proper use of strnlen, strncat and strncpy * typo correction * update screenshot of shapes_penrose_tile example * new example for strings management * Improved structure for text_strings_management --- examples/text/text_strings_management.c | 400 ++++++++++++++++++++++ examples/text/text_strings_management.png | Bin 0 -> 18431 bytes 2 files changed, 400 insertions(+) create mode 100644 examples/text/text_strings_management.c create mode 100644 examples/text/text_strings_management.png diff --git a/examples/text/text_strings_management.c b/examples/text/text_strings_management.c new file mode 100644 index 000000000..d6b4aeb57 --- /dev/null +++ b/examples/text/text_strings_management.c @@ -0,0 +1,400 @@ +/******************************************************************************************* +* +* raylib [text] example - strings management +* +* Example complexity rating: [★★★☆] 3/4 +* +* Example originally created with raylib 5.6-dev, last time updated with raylib 5.6-dev +* +* Example contributed by David Buzatto (@davidbuzatto) 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) 2025 David Buzatto (@davidbuzatto) +* +********************************************************************************************/ + +#include "raylib.h" + +#include + +#define MAX_TEXT_LENGTH 100 +#define MAX_TEXT_PARTICLES 100 +#define FONT_SIZE 30 + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef struct TextParticle { + char text[MAX_TEXT_LENGTH]; + Rectangle rect; // Boundary + Vector2 vel; // Velocity + Vector2 ppos; // Previous position + float padding; + float borderWidth; + float friction; + float elasticity; + Color color; + bool grabbed; +} TextParticle; + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +void PrepareFirstTextParticle(const char* text, TextParticle *tps, int *particleCount); +TextParticle CreateTextParticle(const char *text, float x, float y, Color color); +void SliceTextParticle(TextParticle *tp, int particlePos, int sliceLength, TextParticle *tps, int *particleCount); +void SliceTextParticleByChar(TextParticle *tp, char charToSlice, TextParticle *tps, int *particleCount); +void ShatterTextParticle(TextParticle *tp, int particlePos, TextParticle *tps, int *particleCount); +void GlueTextParticles(TextParticle *grabbed, TextParticle *target, TextParticle *tps, int *particleCount); +void RealocateTextParticles(TextParticle *tps, int particlePos, int *particleCount); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - strings management"); + + TextParticle textParticles[MAX_TEXT_PARTICLES] = { 0 }; + int particleCount = 0; + TextParticle *grabbedTextParticle = NULL; + Vector2 pressOffset = {0}; + + PrepareFirstTextParticle("raylib => fun videogames programming!", textParticles, &particleCount); + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //--------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + float delta = GetFrameTime(); + Vector2 mousePos = GetMousePosition(); + + // Checks if a text particle was grabbed + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) + { + for (int i = particleCount - 1; i >= 0; i--) + { + TextParticle *tp = &textParticles[i]; + pressOffset.x = mousePos.x - tp->rect.x; + pressOffset.y = mousePos.y - tp->rect.y; + if (CheckCollisionPointRec(mousePos, tp->rect)) + { + tp->grabbed = true; + grabbedTextParticle = tp; + break; + } + } + } + + // Releases any text particle the was grabbed + if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) + { + if (grabbedTextParticle != NULL) + { + grabbedTextParticle->grabbed = false; + grabbedTextParticle = NULL; + } + } + + // Slice os shatter a text particle + if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) + { + for (int i = particleCount - 1; i >= 0; i--) + { + TextParticle *tp = &textParticles[i]; + if (CheckCollisionPointRec(mousePos, tp->rect)) + { + if (IsKeyDown(KEY_LEFT_SHIFT)) + { + ShatterTextParticle(tp, i, textParticles, &particleCount); + } + else + { + SliceTextParticle(tp, i, TextLength(tp->text)/2, textParticles, &particleCount); + } + break; + } + } + } + + // Shake text particles + if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) + { + for (int i = 0; i < particleCount; i++) + { + if (!textParticles[i].grabbed) textParticles[i].vel = (Vector2){ GetRandomValue(-2000, 2000), GetRandomValue(-2000, 2000) }; + } + } + + // Reset using TextTo* functions + if (IsKeyPressed(KEY_ONE)) PrepareFirstTextParticle("raylib => fun videogames programming!", textParticles, &particleCount); + if (IsKeyPressed(KEY_TWO)) PrepareFirstTextParticle(TextToUpper("raylib => fun videogames programming!"), textParticles, &particleCount); + if (IsKeyPressed(KEY_THREE)) PrepareFirstTextParticle(TextToLower("raylib => fun videogames programming!"), textParticles, &particleCount); + if (IsKeyPressed(KEY_FOUR)) PrepareFirstTextParticle(TextToPascal("raylib_fun_videogames_programming"), textParticles, &particleCount); + if (IsKeyPressed(KEY_FIVE)) PrepareFirstTextParticle(TextToSnake("RaylibFunVideogamesProgramming"), textParticles, &particleCount); + if (IsKeyPressed(KEY_SIX)) PrepareFirstTextParticle(TextToCamel("raylib_fun_videogames_programming"), textParticles, &particleCount); + + // Slice by char pressed only when we have one text particle + char charPressed = GetCharPressed(); + if ((charPressed >= 'A') && (charPressed <= 'z') && (particleCount == 1)) + { + SliceTextParticleByChar(&textParticles[0], charPressed, textParticles, &particleCount); + } + + // Updates each text particle state + for (int i = 0; i < particleCount; i++) + { + TextParticle *tp = &textParticles[i]; + + // The text particle is not grabbed + if (!tp->grabbed) + { + // text particle repositioning using the velocity + tp->rect.x += tp->vel.x * delta; + tp->rect.y += tp->vel.y * delta; + + // Does the text particle hit the screen right boundary? + if ((tp->rect.x + tp->rect.width) >= screenWidth) + { + tp->rect.x = screenWidth - tp->rect.width; // Text particle repositioning + tp->vel.x = -tp->vel.x*tp->elasticity; // Elasticity makes the text particle lose 10% of its velocity on hit + } + // Does the text particle hit the screen left boundary? + else if (tp->rect.x <= 0) + { + tp->rect.x = 0.0f; + tp->vel.x = -tp->vel.x*tp->elasticity; + } + + // The same for y axis + if ((tp->rect.y + tp->rect.height) >= screenHeight) + { + tp->rect.y = screenHeight - tp->rect.height; + tp->vel.y = -tp->vel.y*tp->elasticity; + } + else if (tp->rect.y <= 0) + { + tp->rect.y = 0.0f; + tp->vel.y = -tp->vel.y*tp->elasticity; + } + + // Friction makes the text particle lose 1% of its velocity each frame + tp->vel.x = tp->vel.x*tp->friction; + tp->vel.y = tp->vel.y*tp->friction; + } + else + { + // Text particle repositioning using the mouse position + tp->rect.x = mousePos.x - pressOffset.x; + tp->rect.y = mousePos.y - pressOffset.y; + + // While the text particle is grabbed, recalculates its velocity + tp->vel.x = (tp->rect.x - tp->ppos.x)/delta; + tp->vel.y = (tp->rect.y - tp->ppos.y)/delta; + tp->ppos.x = tp->rect.x; + tp->ppos.y = tp->rect.y; + + // Glue text particles when dragging and pressing left ctrl + if (IsKeyDown(KEY_LEFT_CONTROL)) + { + for (int i = 0; i < particleCount; i++) + { + if (&textParticles[i] != grabbedTextParticle && grabbedTextParticle->grabbed) + { + if (CheckCollisionRecs(grabbedTextParticle->rect, textParticles[i].rect)) + { + GlueTextParticles(grabbedTextParticle, &textParticles[i], textParticles, &particleCount); + grabbedTextParticle = &textParticles[particleCount-1]; + } + } + } + } + } + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + for (int i = 0; i < particleCount; i++) + { + TextParticle *tp = &textParticles[i]; + DrawRectangle(tp->rect.x-tp->borderWidth, tp->rect.y-tp->borderWidth, tp->rect.width+tp->borderWidth*2, tp->rect.height+tp->borderWidth*2, BLACK); + DrawRectangleRec(tp->rect, tp->color); + DrawText(tp->text, tp->rect.x+tp->padding, tp->rect.y+tp->padding, FONT_SIZE, BLACK); + } + + DrawText("grab a text particle by pressing with the mouse and throw it by releasing", 10, 10, 10, DARKGRAY); + DrawText("slice a text particle by pressing it with the mouse right button", 10, 30, 10, DARKGRAY); + DrawText("shatter a text particle keeping left shift pressed and pressing it with the mouse right button", 10, 50, 10, DARKGRAY); + DrawText("glue text particles by grabbing than and keeping left control pressed", 10, 70, 10, DARKGRAY); + DrawText("1 to 6 to reset", 10, 90, 10, DARKGRAY); + DrawText("when you have only one text particle, you can slice it by pressing a char", 10, 110, 10, DARKGRAY); + DrawText(TextFormat("TEXT PARTICLE COUNT: %d", particleCount), 10, GetScreenHeight() - 30, 20, BLACK); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +void PrepareFirstTextParticle(const char* text, TextParticle *tps, int *particleCount) +{ + tps[0] = CreateTextParticle( + text, + GetScreenWidth()/2, + GetScreenHeight()/2, + RAYWHITE + ); + *particleCount = 1; +} + +TextParticle CreateTextParticle(const char *text, float x, float y, Color color) +{ + TextParticle tp = { + .text = "", + .rect = { x, y, 30, 30 }, + .vel = { GetRandomValue(-200, 200), GetRandomValue(-200, 200) }, + .ppos = { 0 }, + .padding = 5.0f, + .borderWidth = 5.0f, + .friction = 0.99, + .elasticity = 0.9, + .color = color, + .grabbed = false + }; + + TextCopy(tp.text, text); + tp.rect.width = MeasureText(tp.text, FONT_SIZE)+tp.padding*2; + tp.rect.height = FONT_SIZE+tp.padding*2; + return tp; +} + +void SliceTextParticle(TextParticle *tp, int particlePos, int sliceLength, TextParticle *tps, int *particleCount) +{ + int length = TextLength(tp->text); + + if((length > 1) && ((*particleCount+length) < MAX_TEXT_PARTICLES)) + { + for (int i = 0; i < length; i += sliceLength) + { + const char *text = sliceLength == 1 ? TextFormat("%c", tp->text[i]) : TextSubtext(tp->text, i, sliceLength); + tps[(*particleCount)++] = CreateTextParticle( + text, + tp->rect.x + i * tp->rect.width/length, + tp->rect.y, + (Color) { GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255 } + ); + } + RealocateTextParticles(tps, particlePos, particleCount); + } +} + +void SliceTextParticleByChar(TextParticle *tp, char charToSlice, TextParticle *tps, int *particleCount) +{ + int tokenCount = 0; + const char **tokens = TextSplit(tp->text, charToSlice, &tokenCount); + + if (tokenCount > 1) + { + int textLength = TextLength(tp->text); + for (int i = 0; i < textLength; i++) + { + if (tp->text[i] == charToSlice) + { + tps[(*particleCount)++] = CreateTextParticle( + TextFormat("%c", charToSlice), + tp->rect.x, + tp->rect.y, + (Color) { GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255 } + ); + } + } + for (int i = 0; i < tokenCount; i++) + { + int tokenLength = TextLength(tokens[i]); + tps[(*particleCount)++] = CreateTextParticle( + TextFormat("%s", tokens[i]), + tp->rect.x + i * tp->rect.width/tokenLength, + tp->rect.y, + (Color) { GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255 } + ); + } + if (tokenCount) + { + RealocateTextParticles(tps, 0, particleCount); + } + } +} + +void ShatterTextParticle(TextParticle *tp, int particlePos, TextParticle *tps, int *particleCount) +{ + SliceTextParticle(tp, particlePos, 1, tps, particleCount); +} + +void GlueTextParticles(TextParticle *grabbed, TextParticle *target, TextParticle *tps, int *particleCount) +{ + int p1 = -1; + int p2 = -1; + + for (int i = 0; i < *particleCount; i++) + { + if (&tps[i] == grabbed) p1 = i; + if (&tps[i] == target) p2 = i; + } + + if ((p1 != -1) && (p2 != -1)) + { + TextParticle tp = CreateTextParticle( + TextFormat( "%s%s", grabbed->text, target->text), + grabbed->rect.x, + grabbed->rect.y, + RAYWHITE + ); + tp.grabbed = true; + tps[(*particleCount)++] = tp; + grabbed->grabbed = false; + if (p1 < p2) + { + RealocateTextParticles(tps, p2, particleCount); + RealocateTextParticles(tps, p1, particleCount); + } + else + { + RealocateTextParticles(tps, p1, particleCount); + RealocateTextParticles(tps, p2, particleCount); + } + } +} + +void RealocateTextParticles(TextParticle *tps, int particlePos, int *particleCount) +{ + for (int i = particlePos+1; i < *particleCount; i++) + { + tps[i-1] = tps[i]; + } + (*particleCount)--; +} \ No newline at end of file diff --git a/examples/text/text_strings_management.png b/examples/text/text_strings_management.png new file mode 100644 index 0000000000000000000000000000000000000000..d9b6cc4ed83dfd2eadef9b2b06725a4ccae864cc GIT binary patch literal 18431 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 Date: Wed, 3 Dec 2025 10:00:09 +0100 Subject: [PATCH 196/430] Update shapes_penrose_tile.c --- examples/shapes/shapes_penrose_tile.c | 54 +++++++++++++-------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/examples/shapes/shapes_penrose_tile.c b/examples/shapes/shapes_penrose_tile.c index 948a29d12..bf6b3baae 100644 --- a/examples/shapes/shapes_penrose_tile.c +++ b/examples/shapes/shapes_penrose_tile.c @@ -57,7 +57,6 @@ static void PushTurtleState(TurtleState state); static TurtleState PopTurtleState(void); static PenroseLSystem CreatePenroseLSystem(float drawLength); static void BuildProductionStep(PenroseLSystem *ls); -static void BuildPenroseLSystem(PenroseLSystem *ls, float drawLength, int generations); static void DrawPenroseLSystem(PenroseLSystem *ls); //------------------------------------------------------------------------------------ @@ -78,10 +77,11 @@ int main(void) int maxGenerations = 4; int generations = 0; - PenroseLSystem ls = {0}; - BuildPenroseLSystem(&ls, drawLength*(generations/(float)maxGenerations), generations); + // Initializee new penrose tile + PenroseLSystem ls = CreatePenroseLSystem(drawLength*(generations/(float)maxGenerations)); + for (int i = 0; i < generations; i++) BuildProductionStep(&ls); - SetTargetFPS(60); // Set our game to run at 60 frames-per-second + SetTargetFPS(120); // Set our game to run at 60 frames-per-second //--------------------------------------------------------------------------------------- // Main game loop @@ -107,7 +107,12 @@ int main(void) } } - if (rebuild) BuildPenroseLSystem(&ls, drawLength*(generations/(float)maxGenerations), generations); + if (rebuild) + { + RL_FREE(ls.production); // Free previous production for re-creation + ls = CreatePenroseLSystem(drawLength*(generations/(float)maxGenerations)); + for (int i = 0; i < generations; i++) BuildProductionStep(&ls); + } //---------------------------------------------------------------------------------- // Draw @@ -137,13 +142,15 @@ int main(void) //---------------------------------------------------------------------------------- // Module Functions Definition //---------------------------------------------------------------------------------- -void PushTurtleState(TurtleState state) +// Push turtle state for next step +static void PushTurtleState(TurtleState state) { if (turtleTop < (TURTLE_STACK_MAX_SIZE - 1)) turtleStack[++turtleTop] = state; else TraceLog(LOG_WARNING, "TURTLE STACK OVERFLOW!"); } -TurtleState PopTurtleState(void) +// Pop turtle state step +static TurtleState PopTurtleState(void) { if (turtleTop >= 0) return turtleStack[turtleTop--]; else TraceLog(LOG_WARNING, "TURTLE STACK UNDERFLOW!"); @@ -151,8 +158,10 @@ TurtleState PopTurtleState(void) return (TurtleState){ 0 }; } -PenroseLSystem CreatePenroseLSystem(float drawLength) +// Create a new penrose tile structure +static PenroseLSystem CreatePenroseLSystem(float drawLength) { + // TODO: Review constant values assignment on recreation? PenroseLSystem ls = { .steps = 0, .ruleW = "YF++ZF4-XF[-YF4-WF]++", @@ -170,7 +179,8 @@ PenroseLSystem CreatePenroseLSystem(float drawLength) return ls; } -void BuildProductionStep(PenroseLSystem *ls) +// Build next penrose step +static void BuildProductionStep(PenroseLSystem *ls) { char *newProduction = (char *)RL_MALLOC(sizeof(char)*STR_MAX_SIZE); newProduction[0] = '\0'; @@ -205,18 +215,13 @@ void BuildProductionStep(PenroseLSystem *ls) RL_FREE(newProduction); } -void BuildPenroseLSystem(PenroseLSystem *ls, float drawLength, int generations) -{ - *ls = CreatePenroseLSystem(drawLength); - for (int i = 0; i < generations; i++) BuildProductionStep(ls); -} - -void DrawPenroseLSystem(PenroseLSystem *ls) +// Draw penrose tile lines +static void DrawPenroseLSystem(PenroseLSystem *ls) { Vector2 screenCenter = { GetScreenWidth()/2, GetScreenHeight()/2 }; TurtleState turtle = { - .origin = {0}, + .origin = { 0 }, .angle = -90.0f }; @@ -257,18 +262,9 @@ void DrawPenroseLSystem(PenroseLSystem *ls) repeats = 1; } - else if (step == '[') - { - PushTurtleState(turtle); - } - else if (step == ']') - { - turtle = PopTurtleState(); - } - else if ((step >= 48) && (step <= 57)) - { - repeats = (int) step - 48; - } + else if (step == '[') PushTurtleState(turtle); + else if (step == ']') turtle = PopTurtleState(); + else if ((step >= 48) && (step <= 57)) repeats = (int) step - 48; } turtleTop = -1; From 95c4efd7a30d253f1c07ea98c3471d57ddfd2e03 Mon Sep 17 00:00:00 2001 From: Rayumie <241481078+rayumie@users.noreply.github.com> Date: Wed, 3 Dec 2025 12:56:41 -0300 Subject: [PATCH 197/430] Update comment on shapes_penrose_tile.c (#5384) --- examples/shapes/shapes_penrose_tile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shapes/shapes_penrose_tile.c b/examples/shapes/shapes_penrose_tile.c index bf6b3baae..354ebb457 100644 --- a/examples/shapes/shapes_penrose_tile.c +++ b/examples/shapes/shapes_penrose_tile.c @@ -81,7 +81,7 @@ int main(void) PenroseLSystem ls = CreatePenroseLSystem(drawLength*(generations/(float)maxGenerations)); for (int i = 0; i < generations; i++) BuildProductionStep(&ls); - SetTargetFPS(120); // Set our game to run at 60 frames-per-second + SetTargetFPS(120); // Set our game to run at 120 frames-per-second //--------------------------------------------------------------------------------------- // Main game loop From 983efae3e4565cdf1441cb0b3dff9498bb0147e8 Mon Sep 17 00:00:00 2001 From: BoneManSeth <72104908+Sethbones@users.noreply.github.com> Date: Wed, 3 Dec 2025 23:55:54 +0200 Subject: [PATCH 198/430] Expose RGFW to cmake (#5386) i was wondering why that was missing --- CMakeOptions.txt | 2 +- cmake/LibraryConfigurations.cmake | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeOptions.txt b/CMakeOptions.txt index 32cfb814e..cb1b95b0c 100644 --- a/CMakeOptions.txt +++ b/CMakeOptions.txt @@ -6,7 +6,7 @@ if(EMSCRIPTEN) # When configuring web builds with "emcmake cmake -B build -S .", set PLATFORM to Web by default SET(PLATFORM Web CACHE STRING "Platform to build for.") endif() -enum_option(PLATFORM "Desktop;Web;Android;Raspberry Pi;DRM;SDL" "Platform to build for.") +enum_option(PLATFORM "Desktop;Web;Android;Raspberry Pi;DRM;SDL;RGFW" "Platform to build for.") enum_option(OPENGL_VERSION "OFF;4.3;3.3;2.1;1.1;ES 2.0;ES 3.0;Software" "Force a specific OpenGL Version?") diff --git a/cmake/LibraryConfigurations.cmake b/cmake/LibraryConfigurations.cmake index 8f127fc23..96abeea93 100644 --- a/cmake/LibraryConfigurations.cmake +++ b/cmake/LibraryConfigurations.cmake @@ -141,6 +141,8 @@ elseif ("${PLATFORM}" MATCHES "SDL") add_compile_definitions(USING_SDL2_PACKAGE) endif() endif() +elseif ("${PLATFORM}" MATCHES "RGFW") + set(PLATFORM_CPP "PLATFORM_DESKTOP_RGFW") endif () if (NOT ${OPENGL_VERSION} MATCHES "OFF") From 561cc27403f2815b9dae23cade13d4087979ece4 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Sat, 6 Dec 2025 10:50:59 -0800 Subject: [PATCH 199/430] [rModels] Support 16 bit vec3 values in gltf reader (#5388) * Support 16 bit vec3 values coming from gltf * Add support for 8 bit normals --- src/rmodels.c | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/src/rmodels.c b/src/rmodels.c index fad6ae78b..a7db7690e 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -5578,6 +5578,56 @@ static Model LoadGLTF(const char *fileName) vertices[3*k+2] = vt.z; } } + else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_16u)) + { + // Init raylib mesh vertices to copy glTF attribute data + model.meshes[meshIndex].vertexCount = (int)attribute->count; + model.meshes[meshIndex].vertices = (float*)RL_MALLOC(attribute->count * 3 * sizeof(float)); + + // Load data into a temp buffer to be converted to raylib data type + unsigned short* temp = (unsigned short*)RL_MALLOC(attribute->count * 3 * sizeof(unsigned short)); + LOAD_ATTRIBUTE(attribute, 3, unsigned short, temp); + + // Convert data to raylib vertex data type (float) the matrix will scale it to the correct size as a float + for (unsigned int t = 0; t < attribute->count * 3; t++) model.meshes[meshIndex].vertices[t] = (float)temp[t]; + + RL_FREE(temp); + + // Transform the vertices + float* vertices = model.meshes[meshIndex].vertices; + 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; + vertices[3 * k + 1] = vt.y; + vertices[3 * k + 2] = vt.z; + } + } + else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_16)) + { + // Init raylib mesh vertices to copy glTF attribute data + model.meshes[meshIndex].vertexCount = (int)attribute->count; + model.meshes[meshIndex].vertices = (float*)RL_MALLOC(attribute->count * 3 * sizeof(float)); + + // Load data into a temp buffer to be converted to raylib data type + short* temp = (short*)RL_MALLOC(attribute->count * 3 * sizeof(short)); + LOAD_ATTRIBUTE(attribute, 3, short, temp); + + // Convert data to raylib vertex data type (float) the matrix will scale it to the correct size as a float + for (unsigned int t = 0; t < attribute->count * 3; t++) model.meshes[meshIndex].vertices[t] = (float)temp[t]; + + RL_FREE(temp); + + // Transform the vertices + float* vertices = model.meshes[meshIndex].vertices; + 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; + vertices[3 * k + 1] = vt.y; + vertices[3 * k + 2] = vt.z; + } + } else TRACELOG(LOG_WARNING, "MODEL: [%s] Vertices attribute data format not supported, use vec3 float", fileName); } } @@ -5606,6 +5656,78 @@ static Model LoadGLTF(const char *fileName) normals[3*k+2] = nt.z; } } + else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_16)) + { + // Init raylib mesh normals to copy glTF attribute data + model.meshes[meshIndex].normals = (float*)RL_MALLOC(attribute->count * 3 * sizeof(float)); + + // Load data into a temp buffer to be converted to raylib data type + short* temp = (short*)RL_MALLOC(attribute->count * 3 * sizeof(short)); + LOAD_ATTRIBUTE(attribute, 3, short, temp); + + // Convert data to raylib normal data type (float) + for (unsigned int t = 0; t < attribute->count * 3; t++) model.meshes[meshIndex].normals[t] = (float)temp[t]; + + RL_FREE(temp); + + // Transform the normals + float* normals = model.meshes[meshIndex].normals; + for (unsigned int k = 0; k < attribute->count; k++) + { + Vector3 nt = Vector3Normalize(Vector3Transform((Vector3) { normals[3 * k], normals[3 * k + 1], normals[3 * k + 2] }, worldMatrixNormals)); + normals[3 * k] = nt.x; + normals[3 * k + 1] = nt.y; + normals[3 * k + 2] = nt.z; + } + } + else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_8u)) + { + // Init raylib mesh normals to copy glTF attribute data + model.meshes[meshIndex].normals = (float*)RL_MALLOC(attribute->count * 3 * sizeof(float)); + + // Load data into a temp buffer to be converted to raylib data type + unsigned char* temp = (unsigned char*)RL_MALLOC(attribute->count * 3 * sizeof(unsigned char)); + LOAD_ATTRIBUTE(attribute, 3, unsigned char, temp); + + // Convert data to raylib normal data type (float) + for (unsigned int t = 0; t < attribute->count * 3; t++) model.meshes[meshIndex].normals[t] = (float)temp[t]; + + RL_FREE(temp); + + // Transform the normals + float* normals = model.meshes[meshIndex].normals; + for (unsigned int k = 0; k < attribute->count; k++) + { + Vector3 nt = Vector3Normalize(Vector3Transform((Vector3) { normals[3 * k], normals[3 * k + 1], normals[3 * k + 2] }, worldMatrixNormals)); + normals[3 * k] = nt.x; + normals[3 * k + 1] = nt.y; + normals[3 * k + 2] = nt.z; + } + } + else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_8)) + { + // Init raylib mesh normals to copy glTF attribute data + model.meshes[meshIndex].normals = (float*)RL_MALLOC(attribute->count * 3 * sizeof(float)); + + // Load data into a temp buffer to be converted to raylib data type + char* temp = (char*)RL_MALLOC(attribute->count * 3 * sizeof(char)); + LOAD_ATTRIBUTE(attribute, 3, char, temp); + + // Convert data to raylib normal data type (float) + for (unsigned int t = 0; t < attribute->count * 3; t++) model.meshes[meshIndex].normals[t] = (float)temp[t]; + + RL_FREE(temp); + + // Transform the normals + float* normals = model.meshes[meshIndex].normals; + for (unsigned int k = 0; k < attribute->count; k++) + { + Vector3 nt = Vector3Normalize(Vector3Transform((Vector3) { normals[3 * k], normals[3 * k + 1], normals[3 * k + 2] }, worldMatrixNormals)); + normals[3 * k] = nt.x; + normals[3 * k + 1] = nt.y; + normals[3 * k + 2] = nt.z; + } + } else TRACELOG(LOG_WARNING, "MODEL: [%s] Normals attribute data format not supported, use vec3 float", fileName); } } From f9899a71822193b741b154e684c25f0fb6fd0920 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 6 Dec 2025 20:00:19 +0100 Subject: [PATCH 200/430] Reviewed code formating --- src/rmodels.c | 80 +++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index a7db7690e..bea740b92 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -5582,50 +5582,50 @@ static Model LoadGLTF(const char *fileName) { // Init raylib mesh vertices to copy glTF attribute data model.meshes[meshIndex].vertexCount = (int)attribute->count; - model.meshes[meshIndex].vertices = (float*)RL_MALLOC(attribute->count * 3 * sizeof(float)); + model.meshes[meshIndex].vertices = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); // Load data into a temp buffer to be converted to raylib data type - unsigned short* temp = (unsigned short*)RL_MALLOC(attribute->count * 3 * sizeof(unsigned short)); + unsigned short *temp = (unsigned short *)RL_MALLOC(attribute->count*3*sizeof(unsigned short)); LOAD_ATTRIBUTE(attribute, 3, unsigned short, temp); // Convert data to raylib vertex data type (float) the matrix will scale it to the correct size as a float - for (unsigned int t = 0; t < attribute->count * 3; t++) model.meshes[meshIndex].vertices[t] = (float)temp[t]; + for (unsigned int t = 0; t < attribute->count 3; t++) model.meshes[meshIndex].vertices[t] = (float)temp[t]; RL_FREE(temp); // Transform the vertices - float* vertices = model.meshes[meshIndex].vertices; + float *vertices = model.meshes[meshIndex].vertices; 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; - vertices[3 * k + 1] = vt.y; - vertices[3 * k + 2] = vt.z; + Vector3 vt = Vector3Transform((Vector3){ vertices[3*k], vertices[3*k + 1], vertices[3*k + 2] }, worldMatrix); + vertices[3*k] = vt.x; + vertices[3*k + 1] = vt.y; + vertices[3*k + 2] = vt.z; } } else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_16)) { // Init raylib mesh vertices to copy glTF attribute data model.meshes[meshIndex].vertexCount = (int)attribute->count; - model.meshes[meshIndex].vertices = (float*)RL_MALLOC(attribute->count * 3 * sizeof(float)); + model.meshes[meshIndex].vertices = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); // Load data into a temp buffer to be converted to raylib data type - short* temp = (short*)RL_MALLOC(attribute->count * 3 * sizeof(short)); + short *temp = (short *)RL_MALLOC(attribute->count*3*sizeof(short)); LOAD_ATTRIBUTE(attribute, 3, short, temp); // Convert data to raylib vertex data type (float) the matrix will scale it to the correct size as a float - for (unsigned int t = 0; t < attribute->count * 3; t++) model.meshes[meshIndex].vertices[t] = (float)temp[t]; + for (unsigned int t = 0; t < attribute->count*3; t++) model.meshes[meshIndex].vertices[t] = (float)temp[t]; RL_FREE(temp); // Transform the vertices - float* vertices = model.meshes[meshIndex].vertices; + float *vertices = model.meshes[meshIndex].vertices; 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; - vertices[3 * k + 1] = vt.y; - vertices[3 * k + 2] = vt.z; + Vector3 vt = Vector3Transform((Vector3){ vertices[3*k], vertices[3*k + 1], vertices[3*k + 2] }, worldMatrix); + vertices[3*k] = vt.x; + vertices[3*k + 1] = vt.y; + vertices[3*k + 2] = vt.z; } } else TRACELOG(LOG_WARNING, "MODEL: [%s] Vertices attribute data format not supported, use vec3 float", fileName); @@ -5659,73 +5659,73 @@ static Model LoadGLTF(const char *fileName) else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_16)) { // Init raylib mesh normals to copy glTF attribute data - model.meshes[meshIndex].normals = (float*)RL_MALLOC(attribute->count * 3 * sizeof(float)); + model.meshes[meshIndex].normals = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); // Load data into a temp buffer to be converted to raylib data type - short* temp = (short*)RL_MALLOC(attribute->count * 3 * sizeof(short)); + short *temp = (short *)RL_MALLOC(attribute->count*3*sizeof(short)); LOAD_ATTRIBUTE(attribute, 3, short, temp); // Convert data to raylib normal data type (float) - for (unsigned int t = 0; t < attribute->count * 3; t++) model.meshes[meshIndex].normals[t] = (float)temp[t]; + for (unsigned int t = 0; t < attribute->count*3; t++) model.meshes[meshIndex].normals[t] = (float)temp[t]; RL_FREE(temp); // Transform the normals - float* normals = model.meshes[meshIndex].normals; + float *normals = model.meshes[meshIndex].normals; for (unsigned int k = 0; k < attribute->count; k++) { - Vector3 nt = Vector3Normalize(Vector3Transform((Vector3) { normals[3 * k], normals[3 * k + 1], normals[3 * k + 2] }, worldMatrixNormals)); - normals[3 * k] = nt.x; - normals[3 * k + 1] = nt.y; - normals[3 * k + 2] = nt.z; + Vector3 nt = Vector3Normalize(Vector3Transform((Vector3){ normals[3*k], normals[3*k + 1], normals[3*k + 2] }, worldMatrixNormals)); + normals[3*k] = nt.x; + normals[3*k + 1] = nt.y; + normals[3*k + 2] = nt.z; } } else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_8u)) { // Init raylib mesh normals to copy glTF attribute data - model.meshes[meshIndex].normals = (float*)RL_MALLOC(attribute->count * 3 * sizeof(float)); + model.meshes[meshIndex].normals = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); // Load data into a temp buffer to be converted to raylib data type - unsigned char* temp = (unsigned char*)RL_MALLOC(attribute->count * 3 * sizeof(unsigned char)); + unsigned char *temp = (unsigned char *)RL_MALLOC(attribute->count*3*sizeof(unsigned char)); LOAD_ATTRIBUTE(attribute, 3, unsigned char, temp); // Convert data to raylib normal data type (float) - for (unsigned int t = 0; t < attribute->count * 3; t++) model.meshes[meshIndex].normals[t] = (float)temp[t]; + for (unsigned int t = 0; t < attribute->count*3; t++) model.meshes[meshIndex].normals[t] = (float)temp[t]; RL_FREE(temp); // Transform the normals - float* normals = model.meshes[meshIndex].normals; + float *normals = model.meshes[meshIndex].normals; for (unsigned int k = 0; k < attribute->count; k++) { - Vector3 nt = Vector3Normalize(Vector3Transform((Vector3) { normals[3 * k], normals[3 * k + 1], normals[3 * k + 2] }, worldMatrixNormals)); - normals[3 * k] = nt.x; - normals[3 * k + 1] = nt.y; - normals[3 * k + 2] = nt.z; + Vector3 nt = Vector3Normalize(Vector3Transform((Vector3){ normals[3*k], normals[3*k + 1], normals[3*k + 2] }, worldMatrixNormals)); + normals[3*k] = nt.x; + normals[3*k + 1] = nt.y; + normals[3*k + 2] = nt.z; } } else if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_8)) { // Init raylib mesh normals to copy glTF attribute data - model.meshes[meshIndex].normals = (float*)RL_MALLOC(attribute->count * 3 * sizeof(float)); + model.meshes[meshIndex].normals = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); // Load data into a temp buffer to be converted to raylib data type - char* temp = (char*)RL_MALLOC(attribute->count * 3 * sizeof(char)); + char *temp = (char *)RL_MALLOC(attribute->count*3*sizeof(char)); LOAD_ATTRIBUTE(attribute, 3, char, temp); // Convert data to raylib normal data type (float) - for (unsigned int t = 0; t < attribute->count * 3; t++) model.meshes[meshIndex].normals[t] = (float)temp[t]; + for (unsigned int t = 0; t < attribute->count*3; t++) model.meshes[meshIndex].normals[t] = (float)temp[t]; RL_FREE(temp); // Transform the normals - float* normals = model.meshes[meshIndex].normals; + float *normals = model.meshes[meshIndex].normals; for (unsigned int k = 0; k < attribute->count; k++) { - Vector3 nt = Vector3Normalize(Vector3Transform((Vector3) { normals[3 * k], normals[3 * k + 1], normals[3 * k + 2] }, worldMatrixNormals)); - normals[3 * k] = nt.x; - normals[3 * k + 1] = nt.y; - normals[3 * k + 2] = nt.z; + Vector3 nt = Vector3Normalize(Vector3Transform((Vector3){ normals[3*k], normals[3*k + 1], normals[3*k + 2] }, worldMatrixNormals)); + normals[3*k] = nt.x; + normals[3*k + 1] = nt.y; + normals[3*k + 2] = nt.z; } } else TRACELOG(LOG_WARNING, "MODEL: [%s] Normals attribute data format not supported, use vec3 float", fileName); From fd8830948ecf8610c77f1d4a8abf5b6a868baf29 Mon Sep 17 00:00:00 2001 From: BoneManSeth <72104908+Sethbones@users.noreply.github.com> Date: Sat, 6 Dec 2025 21:01:47 +0200 Subject: [PATCH 201/430] fix newer NDK version compiling errors (#5389) target already gets assigned by the clang macro it points to, overwriting it causes it to target linux instead of android, making it check for usr directories instead of the NDK's directories --- examples/Makefile.Android | 2 +- projects/4coder/Makefile.Android | 2 +- projects/VSCode/Makefile.Android | 2 +- src/Makefile | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/Makefile.Android b/examples/Makefile.Android index c00da171e..cf4ad1257 100644 --- a/examples/Makefile.Android +++ b/examples/Makefile.Android @@ -130,7 +130,7 @@ ifeq ($(ANDROID_ARCH),ARM) CFLAGS = -std=c99 -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 endif ifeq ($(ANDROID_ARCH),ARM64) - CFLAGS = -std=c99 -target aarch64 -mfix-cortex-a53-835769 + CFLAGS = -std=c99 -mfix-cortex-a53-835769 endif # Compilation functions attributes options CFLAGS += -ffunction-sections -funwind-tables -fstack-protector-strong -fPIC diff --git a/projects/4coder/Makefile.Android b/projects/4coder/Makefile.Android index 29d437b1b..9e6773651 100644 --- a/projects/4coder/Makefile.Android +++ b/projects/4coder/Makefile.Android @@ -96,7 +96,7 @@ ifeq ($(ANDROID_ARCH),ARM) CFLAGS = -std=c99 -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 endif ifeq ($(ANDROID_ARCH),ARM64) - CFLAGS = -std=c99 -target aarch64 -mfix-cortex-a53-835769 + CFLAGS = -std=c99 -mfix-cortex-a53-835769 endif # Compilation functions attributes options CFLAGS += -ffunction-sections -funwind-tables -fstack-protector-strong -fPIC diff --git a/projects/VSCode/Makefile.Android b/projects/VSCode/Makefile.Android index 7e41ea52f..279790d2a 100644 --- a/projects/VSCode/Makefile.Android +++ b/projects/VSCode/Makefile.Android @@ -96,7 +96,7 @@ ifeq ($(ANDROID_ARCH),ARM) CFLAGS = -std=c99 -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 endif ifeq ($(ANDROID_ARCH),ARM64) - CFLAGS = -std=c99 -target aarch64 -mfix-cortex-a53-835769 + CFLAGS = -std=c99 -mfix-cortex-a53-835769 endif # Compilation functions attributes options CFLAGS += -ffunction-sections -funwind-tables -fstack-protector-strong -fPIC diff --git a/src/Makefile b/src/Makefile index 41867da1c..bc84abece 100644 --- a/src/Makefile +++ b/src/Makefile @@ -406,7 +406,7 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_ANDROID) CFLAGS += -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 endif ifeq ($(ANDROID_ARCH),arm64) - CFLAGS += -target aarch64 -mfix-cortex-a53-835769 + CFLAGS += -mfix-cortex-a53-835769 endif ifeq ($(ANDROID_ARCH),x86) CFLAGS += -march=i686 From 8115b7e92202b2c43dc9852b3ac678e45bf3649b Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 6 Dec 2025 20:40:23 +0100 Subject: [PATCH 202/430] Update rmodels.c --- src/rmodels.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rmodels.c b/src/rmodels.c index bea740b92..51e38008e 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -5589,7 +5589,7 @@ static Model LoadGLTF(const char *fileName) LOAD_ATTRIBUTE(attribute, 3, unsigned short, temp); // Convert data to raylib vertex data type (float) the matrix will scale it to the correct size as a float - for (unsigned int t = 0; t < attribute->count 3; t++) model.meshes[meshIndex].vertices[t] = (float)temp[t]; + for (unsigned int t = 0; t < attribute->count*3; t++) model.meshes[meshIndex].vertices[t] = (float)temp[t]; RL_FREE(temp); From 215ad78d5bf11933a8b6db331ef741807515e99d Mon Sep 17 00:00:00 2001 From: Sebastian Pineda <94144036+spineda2019@users.noreply.github.com> Date: Tue, 9 Dec 2025 13:10:22 -0500 Subject: [PATCH 203/430] Fix build.zig typos (#5390) * fix small typo * other small typos --- build.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.zig b/build.zig index 031a6824d..4e06ca757 100644 --- a/build.zig +++ b/build.zig @@ -155,7 +155,7 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. ); } - // Sets a flag indiciating the use of a custom `config.h` + // Sets a flag indicating the use of a custom `config.h` try raylib_flags_arr.append(b.allocator, "-DEXTERNAL_CONFIG_FLAGS"); if (options.config.len > 0) { // Splits a space-separated list of config flags into multiple flags @@ -187,7 +187,7 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. try raylib_flags_arr.append(b.allocator, flag); } } else { - // Set default config if no custome config got set + // Set default config if no custom config got set try raylib_flags_arr.appendSlice(b.allocator, &config_h_flags); } @@ -438,7 +438,7 @@ pub const Options = struct { pub fn getOptions(b: *std.Build) Options { return .{ - .platform = b.option(PlatformBackend, "platform", "Choose the platform backedn for desktop target") orelse defaults.platform, + .platform = b.option(PlatformBackend, "platform", "Choose the platform backend for desktop target") orelse defaults.platform, .raudio = b.option(bool, "raudio", "Compile with audio support") orelse defaults.raudio, .rmodels = b.option(bool, "rmodels", "Compile with models support") orelse defaults.rmodels, .rtext = b.option(bool, "rtext", "Compile with text support") orelse defaults.rtext, From 366300aafee6270ed94e99b3a67d9085353012fa Mon Sep 17 00:00:00 2001 From: JordSant <77529699+JordSant@users.noreply.github.com> Date: Tue, 9 Dec 2025 19:13:05 +0100 Subject: [PATCH 204/430] [examples] Add `shaders_game_of_life` (#5394) * [examples] Add `shaders_game_of_life` * Declaration hides another variable same name --- .../shaders/resources/game_of_life/acorn.png | Bin 0 -> 218 bytes .../resources/game_of_life/breeder.png | Bin 0 -> 1919 bytes .../shaders/resources/game_of_life/glider.png | Bin 0 -> 216 bytes .../resources/game_of_life/glider_gun.png | Bin 0 -> 291 bytes .../resources/game_of_life/oscillators.png | Bin 0 -> 463 bytes .../resources/game_of_life/puffer_train.png | Bin 0 -> 1378 bytes .../resources/game_of_life/r_pentomino.png | Bin 0 -> 213 bytes .../resources/game_of_life/spaceships.png | Bin 0 -> 828 bytes .../resources/game_of_life/still_lifes.png | Bin 0 -> 615 bytes .../resources/shaders/glsl100/game_of_life.fs | 44 ++ .../resources/shaders/glsl120/game_of_life.fs | 42 ++ .../resources/shaders/glsl330/game_of_life.fs | 45 ++ examples/shaders/shaders_game_of_life.c | 350 +++++++++++ examples/shaders/shaders_game_of_life.png | Bin 0 -> 13793 bytes .../examples/shaders_game_of_life.vcxproj | 569 ++++++++++++++++++ 15 files changed, 1050 insertions(+) create mode 100644 examples/shaders/resources/game_of_life/acorn.png create mode 100644 examples/shaders/resources/game_of_life/breeder.png create mode 100644 examples/shaders/resources/game_of_life/glider.png create mode 100644 examples/shaders/resources/game_of_life/glider_gun.png create mode 100644 examples/shaders/resources/game_of_life/oscillators.png create mode 100644 examples/shaders/resources/game_of_life/puffer_train.png create mode 100644 examples/shaders/resources/game_of_life/r_pentomino.png create mode 100644 examples/shaders/resources/game_of_life/spaceships.png create mode 100644 examples/shaders/resources/game_of_life/still_lifes.png create mode 100644 examples/shaders/resources/shaders/glsl100/game_of_life.fs create mode 100644 examples/shaders/resources/shaders/glsl120/game_of_life.fs create mode 100644 examples/shaders/resources/shaders/glsl330/game_of_life.fs create mode 100644 examples/shaders/shaders_game_of_life.c create mode 100644 examples/shaders/shaders_game_of_life.png create mode 100644 projects/VS2022/examples/shaders_game_of_life.vcxproj diff --git a/examples/shaders/resources/game_of_life/acorn.png b/examples/shaders/resources/game_of_life/acorn.png new file mode 100644 index 0000000000000000000000000000000000000000..58ea0b4d1c588535bc857e5ef7cb3b474260c014 GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^AT}!p8<4C?sm%aVjKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@pr;JNj1^1m%YcIHC7!;n>@Rs(c=SYXob8hb3K@91IEHXsPyX}k>+5z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vG?BLDy{BLR4&KXw2B00(qQO+^Rk3B4{fu`^gtNa30(F0Z`0uWkbu*WX7 zYKRJmkOt)QJpUPrEnp%7qFA4_(}`&(zWq$8hP;dUpp#@YF=A|?mRn7TJa*Nxb&Ct`$Kf?}c;Bj~r(d`E{W@ralBC-g1{x9$p(Zw{E&p@#f zdjX|gz-2yzaTgOZG1$05x|oX0ush(;WD|-B%s7K#Mwm@fFO1y#zomDv3O3Vf0urRc z2C}$~)3z_GzlZfEFm$~#{Cd{Elqc|GAV^Y-WiQ25LM(=6HB^Py_(kyq1jx$MSaB^I ztqk0>84T&~6sO*VW!La)@rdGawR;N@d&#XV9#^}C4C&T}8TF_Bo^?RDWCknXY-1eI zjj#}4;5^{+8*Iyz+XAAu`)S#}AonItURookfl_7kHpx7BH8A8FaKsL{;v5vu-nB_~awSex)aQ0BxtPa_mqGD^6JZT2kv5K?-@NqS?82Tbg2<7xVkbN9xqTFdFQ7M^b|_ zwV46?-qd4OMje|C*hvRQ2VqX#rYe3Fc2-WJ@IXj8%%POA$ zdk&KlFcJrapuZo2L1KmM48&=`rDwGE_C+ATk{%^{8FZpbLXiN(Zuz5=LMX-J zWOD1r(SXdn393HrKBu;&MmAN;urs^NWYEc?uMbfb82?>M4e!b)E4Og9eOgD-dek-V zn-FH56(F!h&1c^ayK8A33@adDJeFMpSbf(r+Qio@6}rNy0&cx46kOR1TKbeDh%|Cr zN7YOl8=w$ucdrN8ony|g=``PK8a4d-n&@s*V;)o%TJOlcpDol`I~aQpw# ztiaLkD6<+{eNK8Iq&H#2lQ{7vsN~M6;ulQeQw>QlI0cTX0h#iBNid`&7*f(^aOZ(Y z+7bx{CoZN*HNl;U#FdpT+3qM)3VJp~%cDTq3|#(jFOA)MyFE65r1Oth!W0;^0wZ4A z-Khuz0S>2_%z@0U?6cc+AZJZ_eaXEEA&=nXfHCI45#-QN`#Iwb%qpf%UEj%z;P6^j z?I#$R7~$mXB(^m2%GOvJrl}?r;t5vEBHvyIRC_XaSEYqG0!y61oAI_w@2g{WtK)83 zUK2z$&AxM6U7?yMoM-P$)i12OIC}=hnDJ^{hg}Ms&yb|bWRO4 zC>S-Z{?nqLr~NI~fFN0k6;rgsqR~t>X6kW<9(_}w9P@immy2ao8y zJHveike2S}UNk4R#mq($2ZQ9qeCeuz-i^PzeQ>jDNqq$Yb+AmkzBEO9W_K;~dLnz* z6BSJ$KnhY>ocz=H2bN(dHptAY32P<&M_}a$KU-o)Gs!KME<2Sf0c=f8vV!5XCXxlV ziWCDWmR4A+N40OjjTQ_moP!xnMj*ykeGO|fXu5eEEI5i7T2?>BBM>;#v zggY=;0{}Kb$0Cs3sR@b=8!gYnx`%RmW(G_s*YbT9$qWGv&Ty`!8nWJ4ydHUwuUkVmEUW*2&$UIUn}B_j}4 z1|k(buO@qkkrE6-0HJjGc481;X9H6Pq!oh-TRSnqKq73hzESK3*I?kBpop=LtSX-p$AF~rMn;}Tf+3LPu4PIXHmK!}Hkp_* z0Z{;Pe;o)E*9d3O>0|mnB{&!&297e$kdn-hl3++-l7HVm^}8B2ZLa_T002ovPDHLk FV1l3@Y~}y} literal 0 HcmV?d00001 diff --git a/examples/shaders/resources/game_of_life/glider.png b/examples/shaders/resources/game_of_life/glider.png new file mode 100644 index 0000000000000000000000000000000000000000..921adb8ed5e4361628af0df83a7000b078b0ee15 GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$61|)m))t&+=#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NK`IrJJ%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10g0sLQ zvY3HEPZ@+6E0)@q0R`DhJbhi+U-Gc9v$Cm$-Sq?t>3O<1hHzYu?Kb3NP!Kru;nA`G z^KV6}cWexHKFri-w@u~dJBAD;hr=#8=4Yz8#o1Mb+BF2lb^~=Xc)I$ztaD0e0swAE BH=h6i literal 0 HcmV?d00001 diff --git a/examples/shaders/resources/game_of_life/glider_gun.png b/examples/shaders/resources/game_of_life/glider_gun.png new file mode 100644 index 0000000000000000000000000000000000000000..29b65d3de8ae04baa7be9ae31a29d9704cc1eab5 GIT binary patch literal 291 zcmeAS@N?(olHy`uVBq!ia0vp^YCz1*!3HFS`Tx5CDaPU;cPGZ1Cw1z99F}xPUq=Rp zjs4tz5?O(Kg=CK)Uj~LMpst1%28Lfip@tU>45bDP46hOx7_4S6Fo+k-*%fF5l;AAz zh%9Dc&{GCs#)_r(Wk5mp5>H=O_Ln>?JZ6HiyD!B9g(^K=978NlpAB*3YcSxDKArdX ze{l6RVK&}loEg_H8;NDy;#1tYmG$C(38n@3*@#~6Qc9G}iX027_oA<62Uil&V>mC``O-mQ!T eYlqfvV6I_vynNnt{d}M!7(8A5T-G@yGywpkz@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vG?BLDy{BLR4&KXw2B00(qQO+^Rk3-1z@fecnz{5u#ZNG@)v8+!P=#7|agfyREZ*vIj^+8xbua zMxBy9KmhLsLY9LSkZK3OX^8EXSt@6b5M5`aHj?a?*2o5g zti=(EFrN4&l<6{ zV7>c;JH^&15MC$ReUD9fN8_4Q?Nj3fZIGJTN;TiwZzYPzv^m0>A+4@s$*N3c002ovPDHLk FV1jQ-vg`l= literal 0 HcmV?d00001 diff --git a/examples/shaders/resources/game_of_life/puffer_train.png b/examples/shaders/resources/game_of_life/puffer_train.png new file mode 100644 index 0000000000000000000000000000000000000000..8d77219b024579726c05be0bb520d0193da6e920 GIT binary patch literal 1378 zcmV-o1)chdP)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vG?BLDy{BLR4&KXw2B00(qQO+^Rk3GuBrQ+=K}xk{zd0!_1IYJ82AAPh}|F!?;s^XKOaulDn$ zSDN6zzrUvSJyUC=@XzzaYy1^Jqs8ZW)WW)%%JV#G6Bsw3S=(6x&rd+AP1M#uUT~-p z9$D8s1lHL6c#mdX_tx@FgDd+E*Er&2^IEBSrQ!JdBv34OiJYV6Pr|&PBTh^~m6i4+ z8ni^to}p>Y=3Gr>{BcBR#F-n@*&-R@r%EEBjkJyZ<74F1u*wu#i#l2vTC26$_kLsp zJyYWr;pwQNweL4_YM)lm)HWipOURE(BHZB6;;7Pft^r7*!SNmq>1tjAOKo@*P?Jog z7KB!&k!Issgn8jm>&$c;N0W(Et}<%yCd`hENztWyI;JIPy^g)Rr$w8G24zn7)18x< zYo2&aSO&BTG@2^Fe1B)yvP9F`;?J#|*BI!b@d4?&TLqw5HLg8{U|nW7)Y>Vl$kb6M z+X-wfW=hL>4bi|(i}R0K4ybMsxM^+L`b0FQ2SrXvVP|y=!f-%|lG;scnsXc$YDh?0 zx4@S6_yg6t$i_ABHDb9H_O!~Q*2hn1v-=#a3N)5hX5m*)VfL9FACWvkw`6&>sU2jF z8;~ByG?oLMkmW?Ob^_kauQL$<;Vn1n;9@E#I9!E&ZQ0_)_q$coGdDSfZbaid&C~i- zUL=q-97s&U6~|9-cp^@-v!3wXwLEbnX*+JcDvr+UT*?zCI9yTN*(`PmgM=2h2sk{U z3QE-6;Lw|syTRcI@hA%IzKaroyoy4n!e7LM*nagxf zNK?0R-4nNlgYw-IyH>sO-4i#=_2`}`c_c3Rp7N<%$eUNJGP8s+mQ3Bkx+j>aTWI%$ z$B}qQ_XIO_3+QwU;SL)rjSk%1B_Jt7rO~on%1~(p8OjWmMhEVu4wYtXHg%{pB1q~` zX;g?3L!}V`{tuP*&Vo*B(r7I@aJNLxiKcJJ2GR%aqUDOTfxEM|QzyQ!gu10~sK(P0 zi4_}69V(4FTl!FGWZ*86OnmJh8Kn-~MV^pb0Mgnx`hs7}HjfbwHEXFkXXNQbYC)*X kKX*h4ohrEX`dY#N0tbz~VkKQve*gdg07*qoM6N<$f;TL9mH+?% literal 0 HcmV?d00001 diff --git a/examples/shaders/resources/game_of_life/r_pentomino.png b/examples/shaders/resources/game_of_life/r_pentomino.png new file mode 100644 index 0000000000000000000000000000000000000000..1707f3c12d561bf0aad93a9de91a60aa452f6829 GIT binary patch literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$61|)m))t&+=#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NK`IrJJ%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10g0sLQ zvY3HEPZ@+6E0)@q0R`DhJbhi+U-Gc{uKbLh*2~7Y$eLAE7 literal 0 HcmV?d00001 diff --git a/examples/shaders/resources/game_of_life/spaceships.png b/examples/shaders/resources/game_of_life/spaceships.png new file mode 100644 index 0000000000000000000000000000000000000000..867f6fea32e5dbc5a62f3b52c78a2b762dae507c GIT binary patch literal 828 zcmV-C1H=4@P)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vG?BLDy{BLR4&KXw2B00(qQO+^Rk3#Z zM+~5~_5vx)s^hCNt5N_RWQ!6L;8!wHV_xZxO8a#{}#kLwP6<{^RhPE&&tV?1^ksV6{B*3ClwMKwTfY}4B z*x>fY8N49*P@1Xr&)n5IB!RS7*Az#Ji*GwNuGSWi2wj}6J=Et&j$ znnqnslIGjFGg8V~MHhW|?-~PHl&0p&q1k$9j`T-@YG&sINPwRJKn0Z5j5)l8@QtX7 zewq690CJhfwS_H5=X7S$B%vl4TMq9j9nh(5*1r2z7&QV}**_$~Rwagif>Bu(hPFWa zg8S0KiqJf9t2b(*yDL@AE588uc^zQ;kKAJb0000z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vG?BLDy{BLR4&KXw2B00(qQO+^Rk3kvdguYs+#Ud#*;>d(|Lm}J_;%nX4;!J>rPb4M7YQLIBfn^`1~y(GdYhBAtZwt;lu z2&^=35sL=!Uae*`H|$+oRx3uU`PNw1y~{_su^;qMRpWz@&V(Gh75AeJCjq%WLxpCk~y8kGd=*+rb4e&i@pE=002ovPDHLkV1ksY B1pEL1 literal 0 HcmV?d00001 diff --git a/examples/shaders/resources/shaders/glsl100/game_of_life.fs b/examples/shaders/resources/shaders/glsl100/game_of_life.fs new file mode 100644 index 000000000..70c12ac2c --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/game_of_life.fs @@ -0,0 +1,44 @@ +#version 100 + +precision highp float; + +// Input vertex attributes (from vertex shader) +varying vec2 fragTexCoord; +varying vec4 fragColor; + +// Input uniform values +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +// Input size in pixels of the textures +uniform vec2 resolution; + +void main() +{ + // Size of one pixel in texture coordinates (from 0.0 to 1.0) + float x = 1.0/resolution.x; + float y = 1.0/resolution.y; + + // Status of the current cell (1 = alive, 0 = dead) + int origValue = (texture2D(texture0, fragTexCoord).r < 0.1)? 1 : 0; + + // Sum of alive neighbors + int sumValue = (texture2D(texture0, vec2(fragTexCoord.x - x, fragTexCoord.y - y)).r < 0.1)? 1 : 0; // Top-left + sumValue += (texture2D(texture0, vec2(fragTexCoord.x - x, fragTexCoord.y )).r < 0.1)? 1 : 0; // Top + sumValue += (texture2D(texture0, vec2(fragTexCoord.x - x, fragTexCoord.y + y)).r < 0.1)? 1 : 0; // Top-right + + sumValue += (texture2D(texture0, vec2(fragTexCoord.x, fragTexCoord.y - y)).r < 0.1)? 1 : 0; // Left + sumValue += (texture2D(texture0, vec2(fragTexCoord.x, fragTexCoord.y + y)).r < 0.1)? 1 : 0; // Right + + sumValue += (texture2D(texture0, vec2(fragTexCoord.x + x, fragTexCoord.y - y)).r < 0.1)? 1 : 0; // Bottom-left + sumValue += (texture2D(texture0, vec2(fragTexCoord.x + x, fragTexCoord.y )).r < 0.1)? 1 : 0; // Bottom + sumValue += (texture2D(texture0, vec2(fragTexCoord.x + x, fragTexCoord.y + y)).r < 0.1)? 1 : 0; // Bottom-right + + // Game of life rules: + // Current cell remains alive when 2 or 3 neighbors are alive, dies otherwise + // Current cell goes from dead to alive when exactly 3 neighbors are alive + if ((origValue == 1 && sumValue == 2) || sumValue == 3) + gl_FragColor = vec4(0.0, 0.0, 0.0, 255.0); // Alive: draw the pixel black + else + gl_FragColor = fragColor; // Dead: draw the pixel with the background color, RAYWHITE +} diff --git a/examples/shaders/resources/shaders/glsl120/game_of_life.fs b/examples/shaders/resources/shaders/glsl120/game_of_life.fs new file mode 100644 index 000000000..611f961bc --- /dev/null +++ b/examples/shaders/resources/shaders/glsl120/game_of_life.fs @@ -0,0 +1,42 @@ +#version 120 + +// Input vertex attributes (from vertex shader) +varying vec2 fragTexCoord; +varying vec4 fragColor; + +// Input uniform values +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +// Input size in pixels of the textures +uniform vec2 resolution; + +void main() +{ + // Size of one pixel in texture coordinates (from 0.0 to 1.0) + float x = 1.0/resolution.x; + float y = 1.0/resolution.y; + + // Status of the current cell (1 = alive, 0 = dead) + int origValue = (texture2D(texture0, fragTexCoord).r < 0.1)? 1 : 0; + + // Sum of alive neighbors + int sumValue = (texture2D(texture0, vec2(fragTexCoord.x - x, fragTexCoord.y - y)).r < 0.1)? 1 : 0; // Top-left + sumValue += (texture2D(texture0, vec2(fragTexCoord.x - x, fragTexCoord.y )).r < 0.1)? 1 : 0; // Top + sumValue += (texture2D(texture0, vec2(fragTexCoord.x - x, fragTexCoord.y + y)).r < 0.1)? 1 : 0; // Top-right + + sumValue += (texture2D(texture0, vec2(fragTexCoord.x, fragTexCoord.y - y)).r < 0.1)? 1 : 0; // Left + sumValue += (texture2D(texture0, vec2(fragTexCoord.x, fragTexCoord.y + y)).r < 0.1)? 1 : 0; // Right + + sumValue += (texture2D(texture0, vec2(fragTexCoord.x + x, fragTexCoord.y - y)).r < 0.1)? 1 : 0; // Bottom-left + sumValue += (texture2D(texture0, vec2(fragTexCoord.x + x, fragTexCoord.y )).r < 0.1)? 1 : 0; // Bottom + sumValue += (texture2D(texture0, vec2(fragTexCoord.x + x, fragTexCoord.y + y)).r < 0.1)? 1 : 0; // Bottom-right + + // Game of life rules: + // Current cell remains alive when 2 or 3 neighbors are alive, dies otherwise + // Current cell goes from dead to alive when exactly 3 neighbors are alive + if (((origValue == 1) && (sumValue == 2)) || sumValue == 3) + gl_FragColor = vec4(0.0, 0.0, 0.0, 255.0); // Alive: draw the pixel black + else + gl_FragColor = fragColor; // Dead: draw the pixel with the background color, RAYWHITE +} diff --git a/examples/shaders/resources/shaders/glsl330/game_of_life.fs b/examples/shaders/resources/shaders/glsl330/game_of_life.fs new file mode 100644 index 000000000..cc80861d6 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/game_of_life.fs @@ -0,0 +1,45 @@ +#version 330 + +// Input vertex attributes (from vertex shader) +in vec2 fragTexCoord; +in vec4 fragColor; + +// Input uniform values +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +// Output fragment color +out vec4 finalColor; + +// Input size in pixels of the textures +uniform vec2 resolution; + +void main() +{ + // Size of one pixel in texture coordinates (from 0.0 to 1.0) + float x = 1.0/resolution.x; + float y = 1.0/resolution.y; + + // Status of the current cell (1 = alive, 0 = dead) + int origValue = (texture(texture0, fragTexCoord).r < 0.1)? 1 : 0; + + // Sum of alive neighbors + int sumValue = (texture(texture0, vec2(fragTexCoord.x - x, fragTexCoord.y - y)).r < 0.1)? 1 : 0; // Top-left + sumValue += (texture(texture0, vec2(fragTexCoord.x - x, fragTexCoord.y )).r < 0.1)? 1 : 0; // Top + sumValue += (texture(texture0, vec2(fragTexCoord.x - x, fragTexCoord.y + y)).r < 0.1)? 1 : 0; // Top-right + + sumValue += (texture(texture0, vec2(fragTexCoord.x, fragTexCoord.y - y)).r < 0.1)? 1 : 0; // Left + sumValue += (texture(texture0, vec2(fragTexCoord.x, fragTexCoord.y + y)).r < 0.1)? 1 : 0; // Right + + sumValue += (texture(texture0, vec2(fragTexCoord.x + x, fragTexCoord.y - y)).r < 0.1)? 1 : 0; // Bottom-left + sumValue += (texture(texture0, vec2(fragTexCoord.x + x, fragTexCoord.y )).r < 0.1)? 1 : 0; // Bottom + sumValue += (texture(texture0, vec2(fragTexCoord.x + x, fragTexCoord.y + y)).r < 0.1)? 1 : 0; // Bottom-right + + // Game of life rules: + // Current cell remains alive when 2 or 3 neighbors are alive, dies otherwise + // Current cell goes from dead to alive when exactly 3 neighbors are alive + if (((origValue == 1) && (sumValue == 2)) || sumValue == 3) + finalColor = vec4(0.0, 0.0, 0.0, 255.0); // Alive: draw the pixel black + else + finalColor = fragColor; // Dead: draw the pixel with the background color, RAYWHITE +} diff --git a/examples/shaders/shaders_game_of_life.c b/examples/shaders/shaders_game_of_life.c new file mode 100644 index 000000000..daeb4d789 --- /dev/null +++ b/examples/shaders/shaders_game_of_life.c @@ -0,0 +1,350 @@ +/******************************************************************************************* +* +* raylib [shaders] example - Conway's Game of Life with shaders +* +* Example complexity rating: [★★★☆] 3/4 +* +* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support, +* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version +* +* Example originally created with raylib 5.6, last time updated with raylib 5.6 +* +* Example contributed by Jordi Santonja (@JordSant) 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) 2025 Jordi Santonja (@JordSant) +* +********************************************************************************************/ + +#include "raylib.h" + +#define RAYGUI_IMPLEMENTATION +#include "raygui.h" // Required for GUI controls + +#if defined(PLATFORM_DESKTOP) + #define GLSL_VERSION 330 +#else // PLATFORM_ANDROID, PLATFORM_WEB + #define GLSL_VERSION 100 +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Interaction mode +typedef enum { + MODE_RUN = 0, + MODE_PAUSE, + MODE_DRAW, +} InteractionMode; + +// Struct to store example preset patterns +typedef struct { + char *name; + char *fileName; + Vector2 position; +} PresetPattern; + +//---------------------------------------------------------------------------------- +// Functions declaration +//---------------------------------------------------------------------------------- +void FreeImageToDraw(Image **imageToDraw); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + const int menuWidth = 100; + const int windowWidth = screenWidth - menuWidth; + const int windowHeight = screenHeight; + + const int worldWidth = 2048; + const int worldHeight = 2048; + + const int randomTiles = 8; // Random preset: divide the world to compute random points in each tile + + const Rectangle worldRectSource = { 0, 0, (float)worldWidth, (float)-worldHeight }; + const Rectangle worldRectDest = { 0, 0, (float)worldWidth, (float)worldHeight }; + const Rectangle textureOnScreen = { 0, 0, (float)windowWidth, (float)windowHeight }; + + const PresetPattern presetPatterns[] = { + { "Glider", "glider", { 0.5f, 0.5f } }, { "R-pentomino", "r_pentomino", { 0.5f, 0.5f } }, { "Acorn", "acorn", { 0.5f,0.5f } }, + { "Spaceships", "spaceships", { 0.1f, 0.5f } }, { "Still lifes", "still_lifes", { 0.5f, 0.5f } }, { "Oscillators", "oscillators", { 0.5f, 0.5f } }, + { "Puffer train", "puffer_train", { 0.1f, 0.5f } }, { "Glider Gun", "glider_gun", { 0.2f, 0.2f } }, { "Breeder", "breeder", { 0.1f, 0.5f } }, + { "Random", "", { 0.5f, 0.5f } } + }; + const int numberOfPresets = sizeof(presetPatterns) / sizeof(presetPatterns[0]); + + // Variable declaration + //-------------------------------------------------------------------------------------- + int zoom = 1; + float offsetX = (worldWidth - windowWidth)/2.0f; // Centered on window + float offsetY = (worldHeight - windowHeight)/2.0f; // Centered on window + int framesPerStep = 1; + int frame = 0; + + int preset = -1; // No button pressed for preset + int mode = MODE_RUN; // Starting mode: running + bool buttonZoomIn = false; // Button states: false not pressed + bool buttonZomOut = false; + bool buttonFaster = false; + bool buttonSlower = false; + + InitWindow(screenWidth, screenHeight, "raylib [shaders] example - conway's game of life"); + + // Load shader + Shader shdrGameOfLife = LoadShader(0, TextFormat("resources/shaders/glsl%i/game_of_life.fs", GLSL_VERSION)); + + // Set shader uniform size of the world + int resolutionLoc = GetShaderLocation(shdrGameOfLife, "resolution"); + const float resolution[2] = { (float)worldWidth, (float)worldHeight }; + SetShaderValue(shdrGameOfLife, resolutionLoc, resolution, SHADER_UNIFORM_VEC2); + + // Define two textures: the current world and the previous world + RenderTexture2D world1 = LoadRenderTexture(worldWidth, worldHeight); + RenderTexture2D world2 = LoadRenderTexture(worldWidth, worldHeight); + BeginTextureMode(world2); + ClearBackground(RAYWHITE); + EndTextureMode(); + + Image startPattern = LoadImage("resources/game_of_life/r_pentomino.png"); + UpdateTextureRec(world2.texture, (Rectangle) { worldWidth / 2.0f, worldHeight / 2.0f, (float)(startPattern.width), (float)(startPattern.height) }, startPattern.data); + UnloadImage(startPattern); + + // Pointers to the two textures, to be swapped + RenderTexture2D *currentWorld = &world2; + RenderTexture2D *previousWorld = &world1; + + // Image to be used in DRAW mode, to be changed with mouse input + Image *imageToDraw = NULL; + + SetTargetFPS(60); // Set at 60 frames-per-second + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + frame++; + + // Change zoom: both by buttons or by mouse wheel + float mouseWheelMove = GetMouseWheelMove(); + if (buttonZoomIn || (buttonZomOut && (zoom > 1)) || (mouseWheelMove != 0.0f)) + { + FreeImageToDraw(&imageToDraw); // Zoom change: free the image to draw to be recreated again + + const float centerX = offsetX + (windowWidth/2.0f)/zoom; + const float centerY = offsetY + (windowHeight/2.0f)/zoom; + if (buttonZoomIn || (mouseWheelMove > 0.0f)) + zoom *= 2; + if ((buttonZomOut || (mouseWheelMove < 0.0f)) && (zoom > 1)) + zoom /= 2; + offsetX = centerX - (windowWidth/2.0f)/zoom; + offsetY = centerY - (windowHeight/2.0f)/zoom; + } + + // Change speed: number of frames per step + if (buttonFaster && framesPerStep > 1) framesPerStep--; + if (buttonSlower) framesPerStep++; + + // Mouse management + //---------------------------------------------------------------------------------- + if ((mode == MODE_RUN) || (mode == MODE_PAUSE)) + { + FreeImageToDraw(&imageToDraw); // Free the image to draw: no longer needed in these modes + + // Pan with mouse left button + static Vector2 previousMousePosition = { 0.0f, 0.0f }; + const Vector2 mousePosition = GetMousePosition(); + if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && (mousePosition.x < windowWidth)) + { + offsetX -= (mousePosition.x - previousMousePosition.x)/zoom; + offsetY -= (mousePosition.y - previousMousePosition.y)/zoom; + } + previousMousePosition = mousePosition; + } + else // MODE_DRAW + { + const float offsetDecimalX = offsetX - floorf(offsetX); + const float offsetDecimalY = offsetY - floorf(offsetY); + int sizeInWorldX = (int)(ceilf((float)(windowWidth + offsetDecimalX*zoom)/zoom)); + int sizeInWorldY = (int)(ceilf((float)(windowHeight + offsetDecimalY*zoom)/zoom)); + if (offsetX + sizeInWorldX >= worldWidth) + sizeInWorldX = worldWidth - (int)floorf(offsetX); + if (offsetY + sizeInWorldY >= worldHeight) + sizeInWorldY = worldHeight - (int)floorf(offsetY); + + // Create image to draw if not created yet + if (imageToDraw == NULL) + { + RenderTexture2D worldOnScreen = LoadRenderTexture(sizeInWorldX, sizeInWorldY); + BeginTextureMode(worldOnScreen); + DrawTexturePro(currentWorld->texture, (Rectangle) { floorf(offsetX), floorf(offsetY), (float)(sizeInWorldX), -(float)(sizeInWorldY) }, + (Rectangle) { 0, 0, (float)(sizeInWorldX), (float)(sizeInWorldY) }, (Vector2) { 0, 0 }, 0.0f, WHITE); + EndTextureMode(); + imageToDraw = (Image*)RL_MALLOC(sizeof(Image)); + *imageToDraw = LoadImageFromTexture(worldOnScreen.texture); + UnloadRenderTexture(worldOnScreen); + } + + const Vector2 mousePosition = GetMousePosition(); + static int firstColor = -1; + if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && (mousePosition.x < windowWidth)) + { + int mouseX = (int)(mousePosition.x + offsetDecimalX*zoom)/zoom; + int mouseY = (int)(mousePosition.y + offsetDecimalY*zoom)/zoom; + if (mouseX >= sizeInWorldX) + mouseX = sizeInWorldX - 1; + if (mouseY >= sizeInWorldY) + mouseY = sizeInWorldY - 1; + if (firstColor == -1) + firstColor = (GetImageColor(*imageToDraw, mouseX, mouseY).r < 5)? 0 : 1; + const int prevColor = (GetImageColor(*imageToDraw, mouseX, mouseY).r < 5)? 0 : 1; + ImageDrawPixel(imageToDraw, mouseX, mouseY, (firstColor) ? BLACK : RAYWHITE); + if (prevColor != firstColor) + UpdateTextureRec(currentWorld->texture, (Rectangle){ floorf(offsetX), floorf(offsetY), (float)(sizeInWorldX), (float)(sizeInWorldY) }, imageToDraw->data); + } + else + firstColor = -1; + } + + // Load selected preset + //---------------------------------------------------------------------------------- + if (preset >= 0) + { + Image pattern; + if (preset < numberOfPresets - 1) // Preset with pattern image lo load + { + pattern = LoadImage(TextFormat("resources/game_of_life/%s.png", presetPatterns[preset].fileName)); + BeginTextureMode(*currentWorld); + ClearBackground(RAYWHITE); + EndTextureMode(); + UpdateTextureRec(currentWorld->texture, (Rectangle){ worldWidth*presetPatterns[preset].position.x - pattern.width/2.0f, + worldHeight*presetPatterns[preset].position.y - pattern.height/2.0f, + (float)(pattern.width), (float)(pattern.height) }, pattern.data); + } + else // Last preset: Random values + { + pattern = GenImageColor(worldWidth/randomTiles, worldHeight/randomTiles, RAYWHITE); + for (int i = 0; i < randomTiles; i++) + { + for (int j = 0; j < randomTiles; j++) + { + ImageClearBackground(&pattern, RAYWHITE); + for (int x = 0; x < pattern.width; x++) + for (int y = 0; y < pattern.height; y++) + if (GetRandomValue(0, 100) < 15) + ImageDrawPixel(&pattern, x, y, BLACK); + UpdateTextureRec(currentWorld->texture, + (Rectangle){ (float)(pattern.width*i), (float)(pattern.height*j), + (float)(pattern.width), (float)(pattern.height) }, pattern.data); + } + } + } + + UnloadImage(pattern); + mode = MODE_PAUSE; + offsetX = worldWidth * presetPatterns[preset].position.x - windowWidth/zoom/2.0f; + offsetY = worldHeight * presetPatterns[preset].position.y - windowHeight/zoom/2.0f; + } + + // Check window draw inside world limits + if (offsetX < 0) offsetX = 0; + if (offsetY < 0) offsetY = 0; + if (offsetX > worldWidth - (float)(windowWidth)/zoom) + offsetX = worldWidth - (float)(windowWidth)/zoom; + if (offsetY > worldHeight - (float)(windowHeight)/zoom) + offsetY = worldHeight - (float)(windowHeight)/zoom; + + // Rectangles for drawing texture portion to screen + //---------------------------------------------------------------------------------- + const Rectangle textureSourceToScreen = { offsetX, offsetY, (float)windowWidth/zoom, (float)windowHeight/zoom }; + + // Draw to texture + //---------------------------------------------------------------------------------- + if ((mode == MODE_RUN) && ((frame % framesPerStep) == 0)) + { + // Swap worlds + RenderTexture2D *tempWorld = currentWorld; + currentWorld = previousWorld; + previousWorld = tempWorld; + + // Draw to texture + BeginTextureMode(*currentWorld); + BeginShaderMode(shdrGameOfLife); + DrawTexturePro(previousWorld->texture, worldRectSource, worldRectDest, (Vector2){ 0, 0 }, 0.0f, RAYWHITE); + EndShaderMode(); + EndTextureMode(); + } + + // Draw to screen + //---------------------------------------------------------------------------------- + BeginDrawing(); + DrawTexturePro(currentWorld->texture, textureSourceToScreen, textureOnScreen, (Vector2){ 0, 0 }, 0.0f, WHITE); + + DrawLine(windowWidth, 0, windowWidth, screenHeight, (Color){ 218, 218, 218, 255 }); + DrawRectangle(windowWidth, 0, screenWidth - windowWidth, screenHeight, (Color){ 232, 232, 232, 255 }); + + DrawText("Conway's", 704, 4, 20, DARKBLUE); + DrawText(" game of", 704, 19, 20, DARKBLUE); + DrawText(" life", 708, 34, 20, DARKBLUE); + DrawText("in raylib", 757, 42, 6, BLACK); + + DrawText("Presets", 710, 58, 8, GRAY); + preset = -1; + for (int i = 0; i < numberOfPresets; i++) + if (GuiButton((Rectangle){ 710.0f, 70.0f + 18*i, 80.0f, 16.0f }, presetPatterns[i].name)) + preset = i; + + GuiToggleGroup((Rectangle){ 710, 258, 80, 16 }, "Run\nPause\nDraw", &mode); + + DrawText(TextFormat("Zoom: %ix", zoom), 710, 316, 8, GRAY); + buttonZoomIn = GuiButton((Rectangle){ 710, 328, 80, 16 }, "Zoom in"); + buttonZomOut = GuiButton((Rectangle){ 710, 346, 80, 16 }, "Zoom out"); + + DrawText(TextFormat("Speed: %i frame%s", framesPerStep, (framesPerStep > 1)? "s" : ""), 710, 370, 8, GRAY); + buttonFaster = GuiButton((Rectangle){ 710, 382, 80, 16 }, "Faster"); + buttonSlower = GuiButton((Rectangle){ 710, 400, 80, 16 }, "Slower"); + + //------------------------------------------------------------------------------ + + DrawFPS(712, 426); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadShader(shdrGameOfLife); + UnloadRenderTexture(world1); + UnloadRenderTexture(world2); + + FreeImageToDraw(&imageToDraw); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} + +//---------------------------------------------------------------------------------- +// Functions definition +//---------------------------------------------------------------------------------- +void FreeImageToDraw(Image **imageToDraw) +{ + if (*imageToDraw != NULL) + { + UnloadImage(**imageToDraw); + RL_FREE(*imageToDraw); + *imageToDraw = NULL; + } +} diff --git a/examples/shaders/shaders_game_of_life.png b/examples/shaders/shaders_game_of_life.png new file mode 100644 index 0000000000000000000000000000000000000000..8ebc2c12e6920fdab2ba0a85139fb4531b4180c6 GIT binary patch literal 13793 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 literal 0 HcmV?d00001 diff --git a/projects/VS2022/examples/shaders_game_of_life.vcxproj b/projects/VS2022/examples/shaders_game_of_life.vcxproj new file mode 100644 index 000000000..0ede87cda --- /dev/null +++ b/projects/VS2022/examples/shaders_game_of_life.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {071E64F3-1396-4A97-97CA-98CAC059B168} + Win32Proj + shaders_game_of_life + 10.0 + shaders_game_of_life + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shaders + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file From efeccfef613729cb2c47c991825b1b5f4ea3d7af Mon Sep 17 00:00:00 2001 From: JordSant <77529699+JordSant@users.noreply.github.com> Date: Tue, 9 Dec 2025 19:14:16 +0100 Subject: [PATCH 205/430] [examples] Add `textures_cellular_automata` (#5395) * [examples] Add `textures_cellular_automata` * Comparison always true. Fixed * Tabs to spaces --- .../textures/textures_cellular_automata.c | 212 +++++++ .../textures/textures_cellular_automata.png | Bin 0 -> 14803 bytes .../textures_cellular_automata.vcxproj | 569 ++++++++++++++++++ 3 files changed, 781 insertions(+) create mode 100644 examples/textures/textures_cellular_automata.c create mode 100644 examples/textures/textures_cellular_automata.png create mode 100644 projects/VS2022/examples/textures_cellular_automata.vcxproj diff --git a/examples/textures/textures_cellular_automata.c b/examples/textures/textures_cellular_automata.c new file mode 100644 index 000000000..d24104200 --- /dev/null +++ b/examples/textures/textures_cellular_automata.c @@ -0,0 +1,212 @@ +/******************************************************************************************* +* +* raylib [textures] example - one-dimensional elementary cellular automata +* +* Example complexity rating: [★★☆☆] 2/4 +* +* Example originally created with raylib 5.6, last time updated with raylib 5.6 +* +* Example contributed by Jordi Santonja (@JordSant) 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) 2025 Jordi Santonja (@JordSant) +* +********************************************************************************************/ + +#include "raylib.h" + +// Initialization constants +//-------------------------------------------------------------------------------------- +const int screenWidth = 800; +const int screenHeight = 450; +const int imageWidth = 800; +const int imageHeight = 800/2; + +// Rule button sizes and positions +const int drawRuleStartX = 585; +const int drawRuleStartY = 10; +const int drawRuleSpacing = 15; +const int drawRuleGroupSpacing = 50; +const int drawRuleSize = 14; +const int drawRuleInnerSize = 10; + +// Preset button sizes +const int presetsSizeX = 42; +const int presetsSizeY = 22; + +const int linesUpdatedPerFrame = 4; + +//---------------------------------------------------------------------------------- +// Functions +//---------------------------------------------------------------------------------- +void ComputeLine(Image *image, int line, int rule) +{ + // Compute next line pixels. Boundaries are not computed, always 0 + for (int i = 1; i < imageWidth - 1; i++) + { + // Get, from the previous line, the 3 pixels states as a binary value + const int prevValue = ((GetImageColor(*image, i - 1, line - 1).r < 5)? 4 : 0) + // Left pixel + ((GetImageColor(*image, i, line - 1).r < 5)? 2 : 0) + // Center pixel + ((GetImageColor(*image, i + 1, line - 1).r < 5)? 1 : 0); // Right pixel + // Get next value from rule bitmask + const bool currValue = (rule & (1 << prevValue)); + // Update pixel color + ImageDrawPixel(image, i, line, (currValue)? BLACK : RAYWHITE); + } +} + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + InitWindow(screenWidth, screenHeight, "raylib [textures] example - elementary cellular automata"); + + // Image that contains the cellular automaton + Image image = GenImageColor(imageWidth, imageHeight, RAYWHITE); + // The top central pixel set as black + ImageDrawPixel(&image, imageWidth/2, 0, BLACK); + + Texture2D texture = LoadTextureFromImage(image); + + // Some interesting rules + const int presetValues[] = { 18, 30, 60, 86, 102, 124, 126, 150, 182, 225 }; + const int presetsCount = sizeof(presetValues)/sizeof(presetValues[0]); + + // Variables + int rule = 30; // Starting rule + int line = 1; // Line to compute, starting from line 1. One point in line 0 is already set + + SetTargetFPS(60); + //--------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + // Handle mouse + const Vector2 mouse = GetMousePosition(); + int mouseInCell = -1; // -1: outside any button; 0-7: rule cells; 8+: preset cells + + // Check mouse on rule cells + for (int i = 0; i < 8; i++) + { + const int cellX = drawRuleStartX - drawRuleGroupSpacing*i + drawRuleSpacing; + const int cellY = drawRuleStartY + drawRuleSpacing; + if ((mouse.x >= cellX) && (mouse.x <= cellX + drawRuleSize) && + (mouse.y >= cellY) && (mouse.y <= cellY + drawRuleSize)) + { + mouseInCell = i; // 0-7: rule cells + break; + } + } + + // Check mouse on preset cells + if (mouseInCell < 0) + { + for (int i = 0; i < presetsCount; i++) + { + const int cellX = 4 + (presetsSizeX + 2)*(i/2); + const int cellY = 2 + (presetsSizeY + 2)*(i%2); + if ((mouse.x >= cellX) && (mouse.x <= cellX + presetsSizeX) && + (mouse.y >= cellY) && (mouse.y <= cellY + presetsSizeY)) + { + mouseInCell = i + 8; // 8+: preset cells + break; + } + } + } + + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && (mouseInCell >= 0)) + { + // Rule changed both by selecting a preset or toggling a bit + if (mouseInCell < 8) + rule ^= (1 << mouseInCell); + else + rule = presetValues[mouseInCell - 8]; + + // Reset image + ImageClearBackground(&image, RAYWHITE); + ImageDrawPixel(&image, imageWidth/2, 0, BLACK); + line = 1; + } + + // Compute next lines + //---------------------------------------------------------------------------------- + if (line < imageHeight) + { + for (int i = 0; (i < linesUpdatedPerFrame) && (line + i < imageHeight); i++) + ComputeLine(&image, line + i, rule); + line += linesUpdatedPerFrame; + + UpdateTexture(texture, image.data); + } + + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + ClearBackground(RAYWHITE); + + // Draw cellular automaton texture + DrawTexture(texture, 0, screenHeight - imageHeight, WHITE); + + // Draw preset values + for (int i = 0; i < presetsCount; i++) + { + DrawText(TextFormat("%i", presetValues[i]), 8 + (presetsSizeX + 2)*(i/2), 4 + (presetsSizeY + 2)*(i%2), 20, GRAY); + DrawRectangleLines(4 + (presetsSizeX + 2)*(i/2), 2 + (presetsSizeY + 2)*(i%2), presetsSizeX, presetsSizeY, BLUE); + + // If the mouse is on this preset, highlight it + if (mouseInCell == i + 8) + DrawRectangleLinesEx((Rectangle) { 2 + (presetsSizeX + 2.0f)*(i/2), + (presetsSizeY + 2.0f)*(i%2), + presetsSizeX + 4.0f, presetsSizeY + 4.0f }, 3, RED); + } + + // Draw rule bits + for (int i = 0; i < 8; i++) + { + // The three input bits + for (int j = 0; j < 3; j++) + { + DrawRectangleLines(drawRuleStartX - drawRuleGroupSpacing*i + drawRuleSpacing*j, drawRuleStartY, drawRuleSize, drawRuleSize, GRAY); + if (i & (4 >> j)) + DrawRectangle(drawRuleStartX + 2 - drawRuleGroupSpacing*i + drawRuleSpacing*j, drawRuleStartY + 2, drawRuleInnerSize, drawRuleInnerSize, BLACK); + } + + // The output bit + DrawRectangleLines(drawRuleStartX - drawRuleGroupSpacing*i + drawRuleSpacing, drawRuleStartY + drawRuleSpacing, drawRuleSize, drawRuleSize, BLUE); + if (rule & (1 << i)) + DrawRectangle(drawRuleStartX + 2 - drawRuleGroupSpacing*i + drawRuleSpacing, drawRuleStartY + 2 + drawRuleSpacing, drawRuleInnerSize, drawRuleInnerSize, BLACK); + + // If the mouse is on this rule bit, highlight it + if (mouseInCell == i) + DrawRectangleLinesEx((Rectangle){ drawRuleStartX - drawRuleGroupSpacing*i + drawRuleSpacing - 2.0f, + drawRuleStartY + drawRuleSpacing - 2.0f, + drawRuleSize + 4.0f, drawRuleSize + 4.0f }, 3, RED); + } + + DrawText(TextFormat("RULE: %i", rule), drawRuleStartX + drawRuleSpacing*4, drawRuleStartY + 1, 30, GRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadImage(image); + UnloadTexture(texture); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} + diff --git a/examples/textures/textures_cellular_automata.png b/examples/textures/textures_cellular_automata.png new file mode 100644 index 0000000000000000000000000000000000000000..2d88041d2c3641805cb5af8d4b52e059a6947ec4 GIT binary patch literal 14803 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⪚yb3S@1F18GP0lXh2 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 + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68} + Win32Proj + textures_cellular_automata + 10.0 + textures_cellular_automata + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file From be6007be9314d87684dd907b4a8477656efdab51 Mon Sep 17 00:00:00 2001 From: Gregory Mitchell Date: Tue, 9 Dec 2025 12:18:43 -0600 Subject: [PATCH 206/430] fix: sha1 computation on messages longer than 31 bytes (#5397) --- src/rcore.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 3a9a47359..20c882e79 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2926,8 +2926,15 @@ unsigned int *ComputeSHA1(unsigned char *data, int dataSize) memcpy(msg, data, dataSize); msg[dataSize] = 128; // Write the '1' bit - unsigned int bitsLen = 8*dataSize; - msg[newDataSize-1] = bitsLen; + unsigned long long bitsLen = 8ULL * dataSize; + msg[newDataSize-1] = (unsigned char)(bitsLen); + msg[newDataSize-2] = (unsigned char)(bitsLen >> 8); + msg[newDataSize-3] = (unsigned char)(bitsLen >> 16); + msg[newDataSize-4] = (unsigned char)(bitsLen >> 24); + msg[newDataSize-5] = (unsigned char)(bitsLen >> 32); + msg[newDataSize-6] = (unsigned char)(bitsLen >> 40); + msg[newDataSize-7] = (unsigned char)(bitsLen >> 48); + msg[newDataSize-8] = (unsigned char)(bitsLen >> 56); // Process the message in successive 512-bit chunks for (int offset = 0; offset < newDataSize; offset += (512/8)) From 19a1683641cc9497babb0ccde461bfd7aa668eb7 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 9 Dec 2025 19:25:08 +0100 Subject: [PATCH 207/430] REXM: Updated examples --- examples/Makefile | 4 + examples/Makefile.Web | 19 + examples/README.md | 14 +- examples/examples_list.txt | 4 + examples/shaders/shaders_game_of_life.c | 4 +- examples/shapes/shapes_penrose_tile.c | 2 +- examples/text/text_strings_management.c | 2 +- .../textures/textures_cellular_automata.c | 4 +- .../examples/shapes_ball_physics.vcxproj | 2 +- .../examples/shapes_penrose_tile.vcxproj | 569 ++++++++++++++++++ .../examples/text_strings_management.vcxproj | 569 ++++++++++++++++++ projects/VS2022/raylib.sln | 168 +++++- tools/rexm/reports/examples_issues.md | 1 + tools/rexm/reports/examples_validation.md | 4 + 14 files changed, 1324 insertions(+), 42 deletions(-) create mode 100644 projects/VS2022/examples/shapes_penrose_tile.vcxproj create mode 100644 projects/VS2022/examples/text_strings_management.vcxproj diff --git a/examples/Makefile b/examples/Makefile index 48cfba97a..06a0c2729 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -583,6 +583,7 @@ SHAPES = \ shapes/shapes_math_angle_rotation \ shapes/shapes_math_sine_cosine \ shapes/shapes_mouse_trail \ + shapes/shapes_penrose_tile \ shapes/shapes_pie_chart \ shapes/shapes_rectangle_advanced \ shapes/shapes_rectangle_scaling \ @@ -602,6 +603,7 @@ TEXTURES = \ textures/textures_background_scrolling \ textures/textures_blend_modes \ textures/textures_bunnymark \ + textures/textures_cellular_automata \ textures/textures_fog_of_war \ textures/textures_gif_player \ textures/textures_image_channel \ @@ -640,6 +642,7 @@ TEXT = \ text/text_input_box \ text/text_rectangle_bounds \ text/text_sprite_fonts \ + text/text_strings_management \ text/text_unicode_emojis \ text/text_unicode_ranges \ text/text_words_alignment \ @@ -685,6 +688,7 @@ SHADERS = \ shaders/shaders_depth_writing \ shaders/shaders_eratosthenes_sieve \ shaders/shaders_fog_rendering \ + shaders/shaders_game_of_life \ shaders/shaders_hot_reloading \ shaders/shaders_hybrid_rendering \ shaders/shaders_julia_set \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 522b50fe0..d638ace51 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -571,6 +571,7 @@ SHAPES = \ shapes/shapes_math_angle_rotation \ shapes/shapes_math_sine_cosine \ shapes/shapes_mouse_trail \ + shapes/shapes_penrose_tile \ shapes/shapes_pie_chart \ shapes/shapes_rectangle_advanced \ shapes/shapes_rectangle_scaling \ @@ -590,6 +591,7 @@ TEXTURES = \ textures/textures_background_scrolling \ textures/textures_blend_modes \ textures/textures_bunnymark \ + textures/textures_cellular_automata \ textures/textures_fog_of_war \ textures/textures_gif_player \ textures/textures_image_channel \ @@ -628,6 +630,7 @@ TEXT = \ text/text_input_box \ text/text_rectangle_bounds \ text/text_sprite_fonts \ + text/text_strings_management \ text/text_unicode_emojis \ text/text_unicode_ranges \ text/text_words_alignment \ @@ -673,6 +676,7 @@ SHADERS = \ shaders/shaders_depth_writing \ shaders/shaders_eratosthenes_sieve \ shaders/shaders_fog_rendering \ + shaders/shaders_game_of_life \ shaders/shaders_hot_reloading \ shaders/shaders_hybrid_rendering \ shaders/shaders_julia_set \ @@ -934,6 +938,9 @@ shapes/shapes_math_sine_cosine: shapes/shapes_math_sine_cosine.c shapes/shapes_mouse_trail: shapes/shapes_mouse_trail.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) +shapes/shapes_penrose_tile: shapes/shapes_penrose_tile.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + shapes/shapes_pie_chart: shapes/shapes_pie_chart.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) @@ -992,6 +999,9 @@ textures/textures_bunnymark: textures/textures_bunnymark.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file textures/resources/raybunny.png@resources/raybunny.png +textures/textures_cellular_automata: textures/textures_cellular_automata.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + textures/textures_fog_of_war: textures/textures_fog_of_war.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) @@ -1143,6 +1153,9 @@ text/text_sprite_fonts: text/text_sprite_fonts.c --preload-file text/resources/sprite_fonts/alpha_beta.png@resources/sprite_fonts/alpha_beta.png \ --preload-file text/resources/sprite_fonts/jupiter_crash.png@resources/sprite_fonts/jupiter_crash.png +text/text_strings_management: text/text_strings_management.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + text/text_unicode_emojis: text/text_unicode_emojis.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file text/resources/dejavu.fnt@resources/dejavu.fnt \ @@ -1351,6 +1364,12 @@ shaders/shaders_fog_rendering: shaders/shaders_fog_rendering.c --preload-file shaders/resources/shaders/glsl100/lighting.vs@resources/shaders/glsl100/lighting.vs \ --preload-file shaders/resources/shaders/glsl100/fog.fs@resources/shaders/glsl100/fog.fs +shaders/shaders_game_of_life: shaders/shaders_game_of_life.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ + --preload-file shaders/resources/shaders/glsl100/game_of_life.fs@resources/shaders/glsl100/game_of_life.fs \ + --preload-file shaders/resources/game_of_life/r_pentomino.png@resources/game_of_life/r_pentomino.png \ + --preload-file shaders/resources/game_of_life/.png@resources/game_of_life/.png + shaders/shaders_hot_reloading: shaders/shaders_hot_reloading.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file shaders/resources/shaders/glsl100/reload.fs@resources/shaders/glsl100/reload.fs diff --git a/examples/README.md b/examples/README.md index 148caffe0..d64ed7608 100644 --- a/examples/README.md +++ b/examples/README.md @@ -17,7 +17,7 @@ You may find it easier to use than other toolchains, especially when it comes to - `zig build [module]` to compile all examples for a module (e.g. `zig build core`) - `zig build [example]` to compile _and run_ a particular example (e.g. `zig build core_basic_window`) -## EXAMPLES COLLECTION [TOTAL: 201] +## EXAMPLES COLLECTION [TOTAL: 205] ### category: core [47] @@ -73,7 +73,7 @@ Examples using raylib [core](../src/rcore.c) module platform functionality: wind | [core_text_file_loading](core/core_text_file_loading.c) | core_text_file_loading | ⭐☆☆☆ | 5.5 | 5.6 | [Aanjishnu Bhattacharyya](https://github.com/NimComPoo-04) | | [core_compute_hash](core/core_compute_hash.c) | core_compute_hash | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | -### category: shapes [37] +### category: shapes [38] Examples using raylib shapes drawing functionality, provided by raylib [shapes](../src/rshapes.c) module. @@ -116,8 +116,9 @@ Examples using raylib shapes drawing functionality, provided by raylib [shapes]( | [shapes_rlgl_color_wheel](shapes/shapes_rlgl_color_wheel.c) | shapes_rlgl_color_wheel | ⭐⭐⭐☆ | 5.6-dev | 5.6-dev | [Robin](https://github.com/RobinsAviary) | | [shapes_rlgl_triangle](shapes/shapes_rlgl_triangle.c) | shapes_rlgl_triangle | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Robin](https://github.com/RobinsAviary) | | [shapes_ball_physics](shapes/shapes_ball_physics.c) | shapes_ball_physics | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [David Buzatto](https://github.com/davidbuzatto) | +| [shapes_penrose_tile](shapes/shapes_penrose_tile.c) | shapes_penrose_tile | ⭐⭐⭐⭐️ | 5.5 | 5.6-dev | [David Buzatto](https://github.com/davidbuzatto) | -### category: textures [28] +### category: textures [29] Examples using raylib textures functionality, including image/textures loading/generation and drawing, provided by raylib [textures](../src/rtextures.c) module. @@ -151,8 +152,9 @@ Examples using raylib textures functionality, including image/textures loading/g | [textures_screen_buffer](textures/textures_screen_buffer.c) | textures_screen_buffer | ⭐⭐☆☆ | 5.5 | 5.5 | [Agnis Aldiņš](https://github.com/nezvers) | | [textures_textured_curve](textures/textures_textured_curve.c) | textures_textured_curve | ⭐⭐⭐☆ | 4.5 | 4.5 | [Jeffery Myers](https://github.com/JeffM2501) | | [textures_sprite_stacking](textures/textures_sprite_stacking.c) | textures_sprite_stacking | ⭐⭐☆☆ | 5.6-dev | 6.0 | [Robin](https://github.com/RobinsAviary) | +| [textures_cellular_automata](textures/textures_cellular_automata.c) | textures_cellular_automata | ⭐⭐☆☆ | 5.6 | 5.6 | [Jordi Santonja](https://github.com/JordSant) | -### category: text [15] +### category: text [16] Examples using raylib text functionality, including sprite fonts loading/generation and text drawing, provided by raylib [text](../src/rtext.c) module. @@ -173,6 +175,7 @@ Examples using raylib text functionality, including sprite fonts loading/generat | [text_codepoints_loading](text/text_codepoints_loading.c) | text_codepoints_loading | ⭐⭐⭐☆ | 4.2 | 4.2 | [Ramon Santamaria](https://github.com/raysan5) | | [text_inline_styling](text/text_inline_styling.c) | text_inline_styling | ⭐⭐⭐☆ | 5.6-dev | 5.6-dev | [Wagner Barongello](https://github.com/SultansOfCode) | | [text_words_alignment](text/text_words_alignment.c) | text_words_alignment | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [JP Mortiboys](https://github.com/themushroompirates) | +| [text_strings_management](text/text_strings_management.c) | text_strings_management | ⭐⭐⭐☆ | 5.6-dev | 5.6-dev | [David Buzatto](https://github.com/davidbuzatto) | ### category: models [27] @@ -208,7 +211,7 @@ Examples using raylib models functionality, including models loading/generation | [models_decals](models/models_decals.c) | models_decals | ⭐⭐⭐⭐️ | 5.6-dev | 5.6-dev | [JP Mortiboys](https://github.com/themushroompirates) | | [models_directional_billboard](models/models_directional_billboard.c) | models_directional_billboard | ⭐⭐☆☆ | 5.6-dev | 5.6 | [Robin](https://github.com/RobinsAviary) | -### category: shaders [32] +### category: shaders [33] Examples using raylib shaders functionality, including shaders loading, parameters configuration and drawing using them (model shaders and postprocessing shaders). This functionality is directly provided by raylib [rlgl](../src/rlgl.c) module. @@ -246,6 +249,7 @@ Examples using raylib shaders functionality, including shaders loading, paramete | [shaders_lightmap_rendering](shaders/shaders_lightmap_rendering.c) | shaders_lightmap_rendering | ⭐⭐⭐☆ | 4.5 | 4.5 | [Jussi Viitala](https://github.com/nullstare) | | [shaders_rounded_rectangle](shaders/shaders_rounded_rectangle.c) | shaders_rounded_rectangle | ⭐⭐⭐☆ | 5.5 | 5.5 | [Anstro Pleuton](https://github.com/anstropleuton) | | [shaders_depth_rendering](shaders/shaders_depth_rendering.c) | shaders_depth_rendering | ⭐⭐⭐☆ | 5.6-dev | 5.6-dev | [Luís Almeida](https://github.com/luis605) | +| [shaders_game_of_life](shaders/shaders_game_of_life.c) | shaders_game_of_life | ⭐⭐⭐☆ | 5.6 | 5.6 | [Jordi Santonja](https://github.com/JordSant) | ### category: audio [9] diff --git a/examples/examples_list.txt b/examples/examples_list.txt index 2373fcc9b..1d4fcaf9e 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -208,3 +208,7 @@ others;easings_testbed;★★★☆;2.5;3.0;2019;2025;"Juan Miguel López";@flas others;raylib_opengl_interop;★★★★;3.8;4.0;2021;2025;"Stephan Soller";@arkanis others;embedded_files_loading;★★☆☆;3.0;3.5;2020;2025;"Kristian Holmgren";@defutura others;web_basic_window;★☆☆☆;5.6-dev;5.6-dev;2014;2025;"Ramon Santamaria";@raysan5 +shaders;shaders_game_of_life;★★★☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant +shapes;shapes_penrose_tile;★★★★;5.5;5.6-dev;2025;2025;"David Buzatto";@davidbuzatto +text;text_strings_management;★★★☆;5.6-dev;5.6-dev;2025;2025;"David Buzatto";@davidbuzatto +textures;textures_cellular_automata;★★☆☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant diff --git a/examples/shaders/shaders_game_of_life.c b/examples/shaders/shaders_game_of_life.c index daeb4d789..654e92643 100644 --- a/examples/shaders/shaders_game_of_life.c +++ b/examples/shaders/shaders_game_of_life.c @@ -1,6 +1,6 @@ /******************************************************************************************* * -* raylib [shaders] example - Conway's Game of Life with shaders +* raylib [shaders] example - game of life * * Example complexity rating: [★★★☆] 3/4 * @@ -97,7 +97,7 @@ int main(void) bool buttonFaster = false; bool buttonSlower = false; - InitWindow(screenWidth, screenHeight, "raylib [shaders] example - conway's game of life"); + InitWindow(screenWidth, screenHeight, "raylib [shaders] example - game of life"); // Load shader Shader shdrGameOfLife = LoadShader(0, TextFormat("resources/shaders/glsl%i/game_of_life.fs", GLSL_VERSION)); diff --git a/examples/shapes/shapes_penrose_tile.c b/examples/shapes/shapes_penrose_tile.c index 354ebb457..304dca3cc 100644 --- a/examples/shapes/shapes_penrose_tile.c +++ b/examples/shapes/shapes_penrose_tile.c @@ -4,7 +4,7 @@ * * Example complexity rating: [★★★★] 4/4 * -* Example originally created with raylib 5.5 +* Example originally created with raylib 5.5, last time updated with raylib 5.6-dev * Based on: https://processing.org/examples/penrosetile.html * * Example contributed by David Buzatto (@davidbuzatto) and reviewed by Ramon Santamaria (@raysan5) diff --git a/examples/text/text_strings_management.c b/examples/text/text_strings_management.c index d6b4aeb57..6c110e6ef 100644 --- a/examples/text/text_strings_management.c +++ b/examples/text/text_strings_management.c @@ -60,7 +60,7 @@ int main(void) const int screenWidth = 800; const int screenHeight = 450; - InitWindow(screenWidth, screenHeight, "raylib [shapes] example - strings management"); + InitWindow(screenWidth, screenHeight, "raylib [text] example - strings management"); TextParticle textParticles[MAX_TEXT_PARTICLES] = { 0 }; int particleCount = 0; diff --git a/examples/textures/textures_cellular_automata.c b/examples/textures/textures_cellular_automata.c index d24104200..affeeda93 100644 --- a/examples/textures/textures_cellular_automata.c +++ b/examples/textures/textures_cellular_automata.c @@ -1,6 +1,6 @@ /******************************************************************************************* * -* raylib [textures] example - one-dimensional elementary cellular automata +* raylib [textures] example - cellular automata * * Example complexity rating: [★★☆☆] 2/4 * @@ -64,7 +64,7 @@ int main(void) { // Initialization //-------------------------------------------------------------------------------------- - InitWindow(screenWidth, screenHeight, "raylib [textures] example - elementary cellular automata"); + InitWindow(screenWidth, screenHeight, "raylib [textures] example - cellular automata"); // Image that contains the cellular automaton Image image = GenImageColor(imageWidth, imageHeight, RAYWHITE); diff --git a/projects/VS2022/examples/shapes_ball_physics.vcxproj b/projects/VS2022/examples/shapes_ball_physics.vcxproj index 47bec68f2..9b7e98658 100644 --- a/projects/VS2022/examples/shapes_ball_physics.vcxproj +++ b/projects/VS2022/examples/shapes_ball_physics.vcxproj @@ -51,7 +51,7 @@ - {6B1A933E-71B8-4C1F-9E79-02D98830E671} + {0653AFAF-5578-4C02-AF29-0C873E7634AE} Win32Proj shapes_ball_physics 10.0 diff --git a/projects/VS2022/examples/shapes_penrose_tile.vcxproj b/projects/VS2022/examples/shapes_penrose_tile.vcxproj new file mode 100644 index 000000000..bde99f8c1 --- /dev/null +++ b/projects/VS2022/examples/shapes_penrose_tile.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC} + Win32Proj + shapes_penrose_tile + 10.0 + shapes_penrose_tile + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/examples/text_strings_management.vcxproj b/projects/VS2022/examples/text_strings_management.vcxproj new file mode 100644 index 000000000..41b9b3ac0 --- /dev/null +++ b/projects/VS2022/examples/text_strings_management.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {1F4722E7-F78E-413F-A106-D3490211EA57} + Win32Proj + text_strings_management + 10.0 + text_strings_management + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\text + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\text + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\text + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\text + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\text + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\text + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\text + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\text + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\text + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\text + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\text + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\text + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index b541b10fe..f0ff823da 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -421,7 +421,15 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_rlgl_triangle", "exa EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "textures_sprite_stacking", "examples\textures_sprite_stacking.vcxproj", "{FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_ball_physics", "examples\shapes_ball_physics.vcxproj", "{6B1A933E-71B8-4C1F-9E79-02D98830E671}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_ball_physics", "examples\shapes_ball_physics.vcxproj", "{0653AFAF-5578-4C02-AF29-0C873E7634AE}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shaders_game_of_life", "examples\shaders_game_of_life.vcxproj", "{071E64F3-1396-4A97-97CA-98CAC059B168}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_penrose_tile", "examples\shapes_penrose_tile.vcxproj", "{7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "text_strings_management", "examples\text_strings_management.vcxproj", "{1F4722E7-F78E-413F-A106-D3490211EA57}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "textures_cellular_automata", "examples\textures_cellular_automata.vcxproj", "{0A0FC982-6E31-401F-BA77-3C5E8AB02C68}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -5237,30 +5245,126 @@ Global {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release|x64.Build.0 = Release|x64 {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release|x86.ActiveCfg = Release|Win32 {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release|x86.Build.0 = Release|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|ARM64.Build.0 = Debug|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x64.ActiveCfg = Debug|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x64.Build.0 = Debug|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x86.ActiveCfg = Debug|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x86.Build.0 = Debug|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x64.Build.0 = Release.DLL|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x86.Build.0 = Release.DLL|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|ARM64.ActiveCfg = Release|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|ARM64.Build.0 = Release|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x64.ActiveCfg = Release|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x64.Build.0 = Release|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x86.ActiveCfg = Release|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x86.Build.0 = Release|Win32 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Debug|ARM64.Build.0 = Debug|ARM64 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Debug|x64.ActiveCfg = Debug|x64 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Debug|x64.Build.0 = Debug|x64 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Debug|x86.ActiveCfg = Debug|Win32 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Debug|x86.Build.0 = Debug|Win32 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Release|ARM64.ActiveCfg = Release|ARM64 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Release|ARM64.Build.0 = Release|ARM64 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Release|x64.ActiveCfg = Release|x64 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Release|x64.Build.0 = Release|x64 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Release|x86.ActiveCfg = Release|Win32 + {0653AFAF-5578-4C02-AF29-0C873E7634AE}.Release|x86.Build.0 = Release|Win32 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Debug|ARM64.Build.0 = Debug|ARM64 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Debug|x64.ActiveCfg = Debug|x64 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Debug|x64.Build.0 = Debug|x64 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Debug|x86.ActiveCfg = Debug|Win32 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Debug|x86.Build.0 = Debug|Win32 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Release|ARM64.ActiveCfg = Release|ARM64 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Release|ARM64.Build.0 = Release|ARM64 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Release|x64.ActiveCfg = Release|x64 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Release|x64.Build.0 = Release|x64 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Release|x86.ActiveCfg = Release|Win32 + {071E64F3-1396-4A97-97CA-98CAC059B168}.Release|x86.Build.0 = Release|Win32 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Debug|ARM64.Build.0 = Debug|ARM64 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Debug|x64.ActiveCfg = Debug|x64 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Debug|x64.Build.0 = Debug|x64 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Debug|x86.ActiveCfg = Debug|Win32 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Debug|x86.Build.0 = Debug|Win32 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Release|ARM64.ActiveCfg = Release|ARM64 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Release|ARM64.Build.0 = Release|ARM64 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Release|x64.ActiveCfg = Release|x64 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Release|x64.Build.0 = Release|x64 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Release|x86.ActiveCfg = Release|Win32 + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC}.Release|x86.Build.0 = Release|Win32 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Debug|ARM64.Build.0 = Debug|ARM64 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Debug|x64.ActiveCfg = Debug|x64 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Debug|x64.Build.0 = Debug|x64 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Debug|x86.ActiveCfg = Debug|Win32 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Debug|x86.Build.0 = Debug|Win32 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Release|ARM64.ActiveCfg = Release|ARM64 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Release|ARM64.Build.0 = Release|ARM64 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Release|x64.ActiveCfg = Release|x64 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Release|x64.Build.0 = Release|x64 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Release|x86.ActiveCfg = Release|Win32 + {1F4722E7-F78E-413F-A106-D3490211EA57}.Release|x86.Build.0 = Release|Win32 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Debug|ARM64.Build.0 = Debug|ARM64 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Debug|x64.ActiveCfg = Debug|x64 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Debug|x64.Build.0 = Debug|x64 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Debug|x86.ActiveCfg = Debug|Win32 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Debug|x86.Build.0 = Debug|Win32 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Release|ARM64.ActiveCfg = Release|ARM64 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Release|ARM64.Build.0 = Release|ARM64 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Release|x64.ActiveCfg = Release|x64 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Release|x64.Build.0 = Release|x64 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Release|x86.ActiveCfg = Release|Win32 + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5428,7 +5532,7 @@ Global {C54703BF-D68A-480D-BE27-49B62E45D582} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {9CD8BCAD-F212-4BCC-BA98-899743CE3279} = {CC132A4D-D081-4C26-BFB9-AB11984054F8} {0981CA28-E4A5-4DF1-987F-A41D09131EFC} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} - {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {CC132A4D-D081-4C26-BFB9-AB11984054F8} + {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {6BFF72EA-7362-4A3B-B6E5-9A3655BBBDA3} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {6777EC3C-077C-42FC-B4AD-B799CE55CCE4} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A} {A61DAD9C-271C-4E95-81AA-DB4CD58564D4} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} @@ -5437,9 +5541,9 @@ Global {3B27F358-2679-4F38-B297-17B536F580BB} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {718FCBD0-591D-448C-B7D5-9F1CA8544E7B} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {19CA0070-B4B2-4394-90B7-D0C259AA35BA} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E} = {AF5BEC5C-1F2B-4DA8-B12D-D09FE569237C} - {9DB1F875-6E65-4195-B23F-ED8095C0B99C} = {278D8859-20B1-428F-8448-064F46E1F021} - {52BA9067-A5FC-4CE8-82AD-7204ECFDEF9F} = {278D8859-20B1-428F-8448-064F46E1F021} + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E} = {278D8859-20B1-428F-8448-064F46E1F021} + {9DB1F875-6E65-4195-B23F-ED8095C0B99C} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A} + {52BA9067-A5FC-4CE8-82AD-7204ECFDEF9F} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} {8E132D5A-2C00-48D0-8747-97E41356F26F} = {278D8859-20B1-428F-8448-064F46E1F021} {A4662163-83E7-4309-8CAA-B0BF13655FE6} = {AF5BEC5C-1F2B-4DA8-B12D-D09FE569237C} {5F4B766F-DD52-4B53-B6C3-BC7611E17F20} = {278D8859-20B1-428F-8448-064F46E1F021} @@ -5473,7 +5577,11 @@ Global {32FE2658-1D70-442E-8672-0AC5C6F0BD7B} = {278D8859-20B1-428F-8448-064F46E1F021} {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F} = {278D8859-20B1-428F-8448-064F46E1F021} {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} - {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {278D8859-20B1-428F-8448-064F46E1F021} + {0653AFAF-5578-4C02-AF29-0C873E7634AE} = {278D8859-20B1-428F-8448-064F46E1F021} + {071E64F3-1396-4A97-97CA-98CAC059B168} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} + {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC} = {278D8859-20B1-428F-8448-064F46E1F021} + {1F4722E7-F78E-413F-A106-D3490211EA57} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A} + {0A0FC982-6E31-401F-BA77-3C5E8AB02C68} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} diff --git a/tools/rexm/reports/examples_issues.md b/tools/rexm/reports/examples_issues.md index 081170806..fceb44429 100644 --- a/tools/rexm/reports/examples_issues.md +++ b/tools/rexm/reports/examples_issues.md @@ -27,3 +27,4 @@ Example elements validated: | raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | | embedded_files_loading | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | web_basic_window | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shaders_game_of_life | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | diff --git a/tools/rexm/reports/examples_validation.md b/tools/rexm/reports/examples_validation.md index 831dbd978..7635a8714 100644 --- a/tools/rexm/reports/examples_validation.md +++ b/tools/rexm/reports/examples_validation.md @@ -221,3 +221,7 @@ Example elements validated: | raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | | embedded_files_loading | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | web_basic_window | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shaders_game_of_life | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_penrose_tile | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| text_strings_management | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| textures_cellular_automata | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | From 91ac3cc707750e987a6bfe8cd072d3e26a430bb6 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 9 Dec 2025 20:02:38 +0100 Subject: [PATCH 208/430] FIX: `LoadRandomSequence()`, using `GetRandomValue()` #5393 --- src/rcore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rcore.c b/src/rcore.c index 20c882e79..5a630b99e 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -1765,7 +1765,7 @@ int *LoadRandomSequence(unsigned int count, int min, int max) for (int i = 0; i < (int)count;) { - value = (rand()%(abs(max - min) + 1) + min); + value = GetRandomValue(min, max); dupValue = false; for (int j = 0; j < i; j++) From 3adfde42f7e83c5a8e09afc34b8cac3626466a1b Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 10 Dec 2025 09:21:33 +0100 Subject: [PATCH 209/430] REVIEWED: `rlLoadTeexture()`, max mipmap levels to use #5400 --- src/rlgl.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 6884ad183..6f3620067 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -3384,10 +3384,12 @@ unsigned int rlLoadTexture(const void *data, int width, int height, int format, #if defined(GRAPHICS_API_OPENGL_33) if (mipmapCount > 1) { - // Activate Trilinear filtering if mipmaps are available + // Activate trilinear filtering if mipmaps are available glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mipmapCount); // Required for user-defined mip count + + // Define thee maximum number of mipmap levels to be used, 0 is default texture size + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mipmapCount - 1); } #endif From f2a900a60d88004f9adc7bcc2151eb54497570e6 Mon Sep 17 00:00:00 2001 From: Marcos De La Torre <35145332+Marcos-D@users.noreply.github.com> Date: Wed, 10 Dec 2025 00:23:40 -0800 Subject: [PATCH 210/430] [rcore] Fix modulo bias in `GetRandomValue()` (#5392) * Fix modulo bias in GetRandomValue(); implement rejection sampling for uniformity * Replace do-while with for-loop in GetRandomValue rejection sampling --- src/rcore.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/rcore.c b/src/rcore.c index 5a630b99e..04f419eee 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -1743,7 +1743,30 @@ int GetRandomValue(int min, int max) TRACELOG(LOG_WARNING, "Invalid GetRandomValue() arguments, range should not be higher than %i", RAND_MAX); } - value = (rand()%(abs(max - min) + 1) + min); + int range = (max - min) + 1; + + // Degenerate/overflow case: fall back to min (same behavior as "always min" instead of UB) + if (range <= 0) + { + value = min; + } + else + { + // Rejection sampling to get a uniform integer in [min, max] + unsigned long c = (unsigned long)RAND_MAX + 1UL; // number of possible rand() results + unsigned long m = (unsigned long)range; // size of the target interval + unsigned long t = c - (c % m); // largest multiple of m <= c + unsigned long r; + + for (;;) + { + r = (unsigned long)rand(); + if (r < t) break; // Only accept values within the fair region + } + + + value = min + (int)(r % m); + } #endif return value; } From bc2057345be3e504b895edb6bfabce5fe7345996 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 10 Dec 2025 09:30:18 +0100 Subject: [PATCH 211/430] REVIEWED: `GetRandomValue()`, explained the new approach to get more uniform random values range --- src/rcore.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 04f419eee..c40fdbae4 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -1742,21 +1742,24 @@ int GetRandomValue(int min, int max) { TRACELOG(LOG_WARNING, "Invalid GetRandomValue() arguments, range should not be higher than %i", RAND_MAX); } + + // NOTE: This one-line approach produces a non-uniform distribution, + // as stated by Donald Knuth in the book The Art of Programming, so + // using below approach for more uniform results + //value = (rand()%(abs(max - min) + 1) + min); + // More uniform range solution int range = (max - min) + 1; // Degenerate/overflow case: fall back to min (same behavior as "always min" instead of UB) - if (range <= 0) - { - value = min; - } + if (range <= 0) value = min; else { // Rejection sampling to get a uniform integer in [min, max] unsigned long c = (unsigned long)RAND_MAX + 1UL; // number of possible rand() results unsigned long m = (unsigned long)range; // size of the target interval - unsigned long t = c - (c % m); // largest multiple of m <= c - unsigned long r; + unsigned long t = c - (c%m); // largest multiple of m <= c + unsigned long r = 0; for (;;) { @@ -1764,8 +1767,7 @@ int GetRandomValue(int min, int max) if (r < t) break; // Only accept values within the fair region } - - value = min + (int)(r % m); + value = min + (int)(r%m); } #endif return value; From 5e8118daf24240fe82db00dd0e90bf355edabe82 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 10 Dec 2025 09:46:06 +0100 Subject: [PATCH 212/430] Update shaders_game_of_life.c --- examples/shaders/shaders_game_of_life.c | 74 ++++++++++++------------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/examples/shaders/shaders_game_of_life.c b/examples/shaders/shaders_game_of_life.c index 654e92643..fa841d066 100644 --- a/examples/shaders/shaders_game_of_life.c +++ b/examples/shaders/shaders_game_of_life.c @@ -60,6 +60,8 @@ int main(void) //-------------------------------------------------------------------------------------- const int screenWidth = 800; const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [shaders] example - game of life"); const int menuWidth = 100; const int windowWidth = screenWidth - menuWidth; @@ -80,10 +82,9 @@ int main(void) { "Puffer train", "puffer_train", { 0.1f, 0.5f } }, { "Glider Gun", "glider_gun", { 0.2f, 0.2f } }, { "Breeder", "breeder", { 0.1f, 0.5f } }, { "Random", "", { 0.5f, 0.5f } } }; - const int numberOfPresets = sizeof(presetPatterns) / sizeof(presetPatterns[0]); + + const int numberOfPresets = sizeof(presetPatterns)/sizeof(presetPatterns[0]); - // Variable declaration - //-------------------------------------------------------------------------------------- int zoom = 1; float offsetX = (worldWidth - windowWidth)/2.0f; // Centered on window float offsetY = (worldHeight - windowHeight)/2.0f; // Centered on window @@ -97,8 +98,6 @@ int main(void) bool buttonFaster = false; bool buttonSlower = false; - InitWindow(screenWidth, screenHeight, "raylib [shaders] example - game of life"); - // Load shader Shader shdrGameOfLife = LoadShader(0, TextFormat("resources/shaders/glsl%i/game_of_life.fs", GLSL_VERSION)); @@ -115,7 +114,7 @@ int main(void) EndTextureMode(); Image startPattern = LoadImage("resources/game_of_life/r_pentomino.png"); - UpdateTextureRec(world2.texture, (Rectangle) { worldWidth / 2.0f, worldHeight / 2.0f, (float)(startPattern.width), (float)(startPattern.height) }, startPattern.data); + UpdateTextureRec(world2.texture, (Rectangle){ worldWidth/2.0f, worldHeight/2.0f, (float)(startPattern.width), (float)(startPattern.height) }, startPattern.data); UnloadImage(startPattern); // Pointers to the two textures, to be swapped @@ -143,10 +142,8 @@ int main(void) const float centerX = offsetX + (windowWidth/2.0f)/zoom; const float centerY = offsetY + (windowHeight/2.0f)/zoom; - if (buttonZoomIn || (mouseWheelMove > 0.0f)) - zoom *= 2; - if ((buttonZomOut || (mouseWheelMove < 0.0f)) && (zoom > 1)) - zoom /= 2; + if (buttonZoomIn || (mouseWheelMove > 0.0f)) zoom *= 2; + if ((buttonZomOut || (mouseWheelMove < 0.0f)) && (zoom > 1)) zoom /= 2; offsetX = centerX - (windowWidth/2.0f)/zoom; offsetY = centerY - (windowHeight/2.0f)/zoom; } @@ -156,7 +153,6 @@ int main(void) if (buttonSlower) framesPerStep++; // Mouse management - //---------------------------------------------------------------------------------- if ((mode == MODE_RUN) || (mode == MODE_PAUSE)) { FreeImageToDraw(&imageToDraw); // Free the image to draw: no longer needed in these modes @@ -177,10 +173,8 @@ int main(void) const float offsetDecimalY = offsetY - floorf(offsetY); int sizeInWorldX = (int)(ceilf((float)(windowWidth + offsetDecimalX*zoom)/zoom)); int sizeInWorldY = (int)(ceilf((float)(windowHeight + offsetDecimalY*zoom)/zoom)); - if (offsetX + sizeInWorldX >= worldWidth) - sizeInWorldX = worldWidth - (int)floorf(offsetX); - if (offsetY + sizeInWorldY >= worldHeight) - sizeInWorldY = worldHeight - (int)floorf(offsetY); + if (offsetX + sizeInWorldX >= worldWidth) sizeInWorldX = worldWidth - (int)floorf(offsetX); + if (offsetY + sizeInWorldY >= worldHeight) sizeInWorldY = worldHeight - (int)floorf(offsetY); // Create image to draw if not created yet if (imageToDraw == NULL) @@ -192,6 +186,7 @@ int main(void) EndTextureMode(); imageToDraw = (Image*)RL_MALLOC(sizeof(Image)); *imageToDraw = LoadImageFromTexture(worldOnScreen.texture); + UnloadRenderTexture(worldOnScreen); } @@ -201,32 +196,30 @@ int main(void) { int mouseX = (int)(mousePosition.x + offsetDecimalX*zoom)/zoom; int mouseY = (int)(mousePosition.y + offsetDecimalY*zoom)/zoom; - if (mouseX >= sizeInWorldX) - mouseX = sizeInWorldX - 1; - if (mouseY >= sizeInWorldY) - mouseY = sizeInWorldY - 1; - if (firstColor == -1) - firstColor = (GetImageColor(*imageToDraw, mouseX, mouseY).r < 5)? 0 : 1; + if (mouseX >= sizeInWorldX) mouseX = sizeInWorldX - 1; + if (mouseY >= sizeInWorldY) mouseY = sizeInWorldY - 1; + if (firstColor == -1) firstColor = (GetImageColor(*imageToDraw, mouseX, mouseY).r < 5)? 0 : 1; const int prevColor = (GetImageColor(*imageToDraw, mouseX, mouseY).r < 5)? 0 : 1; + ImageDrawPixel(imageToDraw, mouseX, mouseY, (firstColor) ? BLACK : RAYWHITE); - if (prevColor != firstColor) - UpdateTextureRec(currentWorld->texture, (Rectangle){ floorf(offsetX), floorf(offsetY), (float)(sizeInWorldX), (float)(sizeInWorldY) }, imageToDraw->data); + + if (prevColor != firstColor) UpdateTextureRec(currentWorld->texture, (Rectangle){ floorf(offsetX), floorf(offsetY), (float)(sizeInWorldX), (float)(sizeInWorldY) }, imageToDraw->data); } - else - firstColor = -1; + else firstColor = -1; } // Load selected preset - //---------------------------------------------------------------------------------- if (preset >= 0) { Image pattern; if (preset < numberOfPresets - 1) // Preset with pattern image lo load { pattern = LoadImage(TextFormat("resources/game_of_life/%s.png", presetPatterns[preset].fileName)); + BeginTextureMode(*currentWorld); ClearBackground(RAYWHITE); EndTextureMode(); + UpdateTextureRec(currentWorld->texture, (Rectangle){ worldWidth*presetPatterns[preset].position.x - pattern.width/2.0f, worldHeight*presetPatterns[preset].position.y - pattern.height/2.0f, (float)(pattern.width), (float)(pattern.height) }, pattern.data); @@ -240,9 +233,12 @@ int main(void) { ImageClearBackground(&pattern, RAYWHITE); for (int x = 0; x < pattern.width; x++) + { for (int y = 0; y < pattern.height; y++) - if (GetRandomValue(0, 100) < 15) - ImageDrawPixel(&pattern, x, y, BLACK); + { + if (GetRandomValue(0, 100) < 15) ImageDrawPixel(&pattern, x, y, BLACK); + } + } UpdateTextureRec(currentWorld->texture, (Rectangle){ (float)(pattern.width*i), (float)(pattern.height*j), (float)(pattern.width), (float)(pattern.height) }, pattern.data); @@ -251,26 +247,25 @@ int main(void) } UnloadImage(pattern); + mode = MODE_PAUSE; - offsetX = worldWidth * presetPatterns[preset].position.x - windowWidth/zoom/2.0f; - offsetY = worldHeight * presetPatterns[preset].position.y - windowHeight/zoom/2.0f; + offsetX = worldWidth*presetPatterns[preset].position.x - windowWidth/zoom/2.0f; + offsetY = worldHeight*presetPatterns[preset].position.y - windowHeight/zoom/2.0f; } // Check window draw inside world limits if (offsetX < 0) offsetX = 0; if (offsetY < 0) offsetY = 0; - if (offsetX > worldWidth - (float)(windowWidth)/zoom) - offsetX = worldWidth - (float)(windowWidth)/zoom; - if (offsetY > worldHeight - (float)(windowHeight)/zoom) - offsetY = worldHeight - (float)(windowHeight)/zoom; + if (offsetX > worldWidth - (float)(windowWidth)/zoom) offsetX = worldWidth - (float)(windowWidth)/zoom; + if (offsetY > worldHeight - (float)(windowHeight)/zoom) offsetY = worldHeight - (float)(windowHeight)/zoom; // Rectangles for drawing texture portion to screen - //---------------------------------------------------------------------------------- const Rectangle textureSourceToScreen = { offsetX, offsetY, (float)windowWidth/zoom, (float)windowHeight/zoom }; + //---------------------------------------------------------------------------------- // Draw to texture //---------------------------------------------------------------------------------- - if ((mode == MODE_RUN) && ((frame % framesPerStep) == 0)) + if ((mode == MODE_RUN) && ((frame%framesPerStep) == 0)) { // Swap worlds RenderTexture2D *tempWorld = currentWorld; @@ -284,10 +279,12 @@ int main(void) EndShaderMode(); EndTextureMode(); } + //---------------------------------------------------------------------------------- // Draw to screen //---------------------------------------------------------------------------------- BeginDrawing(); + DrawTexturePro(currentWorld->texture, textureSourceToScreen, textureOnScreen, (Vector2){ 0, 0 }, 0.0f, WHITE); DrawLine(windowWidth, 0, windowWidth, screenHeight, (Color){ 218, 218, 218, 255 }); @@ -301,8 +298,7 @@ int main(void) DrawText("Presets", 710, 58, 8, GRAY); preset = -1; for (int i = 0; i < numberOfPresets; i++) - if (GuiButton((Rectangle){ 710.0f, 70.0f + 18*i, 80.0f, 16.0f }, presetPatterns[i].name)) - preset = i; + if (GuiButton((Rectangle){ 710.0f, 70.0f + 18*i, 80.0f, 16.0f }, presetPatterns[i].name)) preset = i; GuiToggleGroup((Rectangle){ 710, 258, 80, 16 }, "Run\nPause\nDraw", &mode); @@ -314,8 +310,6 @@ int main(void) buttonFaster = GuiButton((Rectangle){ 710, 382, 80, 16 }, "Faster"); buttonSlower = GuiButton((Rectangle){ 710, 400, 80, 16 }, "Slower"); - //------------------------------------------------------------------------------ - DrawFPS(712, 426); EndDrawing(); From dad93abcf850420c1f22d8c9ac7523680953f81f Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 10 Dec 2025 18:49:52 +0100 Subject: [PATCH 213/430] REXM: Ignore some warnings on GCC/Clang --- tools/rexm/rexm.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 72b962874..f1701a3ca 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -72,6 +72,12 @@ // Create local commit with changes on example renaming //#define RENAME_AUTO_COMMIT_CREATION +#if defined(__GNUC__) // GCC and Clang + #pragma GCC diagnostic push + // Avoid GCC/Clang complaining about sprintf() second parameter not being a string literal (being TextFormat()) + #pragma GCC diagnostic ignored "-Wformat-security" +#endif + //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- @@ -2917,3 +2923,6 @@ static bool TextInList(const char *text, const char **list, int listCount) return result; } +#if defined(__GNUC__) // GCC and Clang + #pragma GCC diagnostic pop +#endif From f3f02b3e17b788594cf510a41912ebc5f80e67b3 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 10 Dec 2025 19:06:41 +0100 Subject: [PATCH 214/430] REXM: examples validation and update --- examples/core/core_clipboard_text.c | 2 ++ examples/examples_list.txt | 10 +++++----- tools/rexm/reports/examples_issues.md | 14 +++++++------- tools/rexm/reports/examples_validation.md | 20 ++++++++++---------- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/examples/core/core_clipboard_text.c b/examples/core/core_clipboard_text.c index 2f8e5712b..59de6509c 100644 --- a/examples/core/core_clipboard_text.c +++ b/examples/core/core_clipboard_text.c @@ -2,6 +2,8 @@ * * raylib [core] example - clipboard text * +* Example complexity rating: [★★☆☆] 2/4 +* * Example originally created with raylib 5.6-dev, last time updated with raylib 5.6-dev * * Example contributed by Ananth S (@Ananth1839) and reviewed by Ramon Santamaria (@raysan5) diff --git a/examples/examples_list.txt b/examples/examples_list.txt index 1d4fcaf9e..925ad9454 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -51,7 +51,7 @@ core;core_input_actions;★★☆☆;5.5;5.6;2025;2025;"Jett";@JettMonstersGoBoo core;core_directory_files;★☆☆☆;5.5;5.6;2025;2025;"Hugo ARNAL";@hugoarnal core;core_highdpi_testbed;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 core;core_screen_recording;★★☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 -core;core_clipboard_text;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ananth S";@Ananth1839 +core;core_clipboard_text;★★☆☆;5.6-dev;5.6-dev;2025;2025;"Ananth S";@Ananth1839 core;core_text_file_loading;★☆☆☆;5.5;5.6;0;0;"Aanjishnu Bhattacharyya";@NimComPoo-04 core;core_compute_hash;★★☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 shapes;shapes_basic_shapes;★☆☆☆;1.0;4.2;2014;2025;"Ramon Santamaria";@raysan5 @@ -91,6 +91,7 @@ shapes;shapes_math_angle_rotation;★☆☆☆;5.6-dev;5.6;2025;2025;"Kris";@kri shapes;shapes_rlgl_color_wheel;★★★☆;5.6-dev;5.6-dev;2025;2025;"Robin";@RobinsAviary shapes;shapes_rlgl_triangle;★★☆☆;5.6-dev;5.6-dev;2025;2025;"Robin";@RobinsAviary shapes;shapes_ball_physics;★★☆☆;5.6-dev;5.6-dev;2025;2025;"David Buzatto";@davidbuzatto +shapes;shapes_penrose_tile;★★★★;5.5;5.6-dev;2025;2025;"David Buzatto";@davidbuzatto textures;textures_logo_raylib;★☆☆☆;1.0;1.0;2014;2025;"Ramon Santamaria";@raysan5 textures;textures_srcrec_dstrec;★★★☆;1.3;1.3;2015;2025;"Ramon Santamaria";@raysan5 textures;textures_image_drawing;★★☆☆;1.4;1.4;2016;2025;"Ramon Santamaria";@raysan5 @@ -119,6 +120,7 @@ textures;textures_image_rotate;★★☆☆;1.0;1.0;2014;2025;"Ramon Santamaria" textures;textures_screen_buffer;★★☆☆;5.5;5.5;2025;2025;"Agnis Aldiņš";@nezvers textures;textures_textured_curve;★★★☆;4.5;4.5;2022;2025;"Jeffery Myers";@JeffM2501 textures;textures_sprite_stacking;★★☆☆;5.6-dev;6.0;2025;2025;"Robin";@RobinsAviary +textures;textures_cellular_automata;★★☆☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant text;text_sprite_fonts;★☆☆☆;1.7;3.7;2017;2025;"Ramon Santamaria";@raysan5 text;text_font_spritefont;★☆☆☆;1.0;1.0;2014;2025;"Ramon Santamaria";@raysan5 text;text_font_filters;★★☆☆;1.3;4.2;2015;2025;"Ramon Santamaria";@raysan5 @@ -134,6 +136,7 @@ text;text_3d_drawing;★★★★;3.5;4.0;2021;2025;"Vlad Adrian";@demizdor text;text_codepoints_loading;★★★☆;4.2;4.2;2022;2025;"Ramon Santamaria";@raysan5 text;text_inline_styling;★★★☆;5.6-dev;5.6-dev;2025;2025;"Wagner Barongello";@SultansOfCode text;text_words_alignment;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"JP Mortiboys";@themushroompirates +text;text_strings_management;★★★☆;5.6-dev;5.6-dev;2025;2025;"David Buzatto";@davidbuzatto models;models_animation_playing;★★☆☆;2.5;3.5;2019;2025;"Culacant";@culacant models;models_billboard_rendering;★★★☆;1.3;3.5;2015;2025;"Ramon Santamaria";@raysan5 models;models_box_collisions;★☆☆☆;1.3;3.5;2015;2025;"Ramon Santamaria";@raysan5 @@ -193,6 +196,7 @@ shaders;shaders_basic_pbr;★★★★;5.0;5.5;2023;2025;"Afan OLOVCIC";@_DevDad shaders;shaders_lightmap_rendering;★★★☆;4.5;4.5;2019;2025;"Jussi Viitala";@nullstare shaders;shaders_rounded_rectangle;★★★☆;5.5;5.5;2025;2025;"Anstro Pleuton";@anstropleuton shaders;shaders_depth_rendering;★★★☆;5.6-dev;5.6-dev;2025;2025;"Luís Almeida";@luis605 +shaders;shaders_game_of_life;★★★☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant audio;audio_module_playing;★☆☆☆;1.5;3.5;2016;2025;"Ramon Santamaria";@raysan5 audio;audio_music_stream;★☆☆☆;1.3;4.2;2015;2025;"Ramon Santamaria";@raysan5 audio;audio_raw_stream;★★★☆;1.6;4.2;2015;2025;"Ramon Santamaria";@raysan5 @@ -208,7 +212,3 @@ others;easings_testbed;★★★☆;2.5;3.0;2019;2025;"Juan Miguel López";@flas others;raylib_opengl_interop;★★★★;3.8;4.0;2021;2025;"Stephan Soller";@arkanis others;embedded_files_loading;★★☆☆;3.0;3.5;2020;2025;"Kristian Holmgren";@defutura others;web_basic_window;★☆☆☆;5.6-dev;5.6-dev;2014;2025;"Ramon Santamaria";@raysan5 -shaders;shaders_game_of_life;★★★☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant -shapes;shapes_penrose_tile;★★★★;5.5;5.6-dev;2025;2025;"David Buzatto";@davidbuzatto -text;text_strings_management;★★★☆;5.6-dev;5.6-dev;2025;2025;"David Buzatto";@davidbuzatto -textures;textures_cellular_automata;★★☆☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant diff --git a/tools/rexm/reports/examples_issues.md b/tools/rexm/reports/examples_issues.md index fceb44429..14e7a61c5 100644 --- a/tools/rexm/reports/examples_issues.md +++ b/tools/rexm/reports/examples_issues.md @@ -21,10 +21,10 @@ Example elements validated: | **EXAMPLE NAME** | [C] | [CAT]| [INFO]|[PNG]|[WPNG]| [RES]| [MK] |[MKWEB]| [VCX]| [SOL]|[RDME]|[JS] | [WOUT]|[WMETA]| |:---------------------------------|:---:|:----:|:-----:|:---:|:----:|:----:|:----:|:-----:|:----:|:----:|:----:|:---:|:-----:|:-----:| | core_highdpi_testbed | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| rlgl_standalone | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| rlgl_compute_shader | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| easings_testbed | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | -| embedded_files_loading | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| web_basic_window | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shaders_game_of_life | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shaders_game_of_life | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | +| rlgl_standalone | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | +| rlgl_compute_shader | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | +| easings_testbed | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ❌ | ✔ | ❌ | ✔ | ✔ | +| embedded_files_loading | ✔ | ❌ | ❌ | ✔ | ✔ | ❌ | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | +| web_basic_window | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | diff --git a/tools/rexm/reports/examples_validation.md b/tools/rexm/reports/examples_validation.md index 7635a8714..45c195415 100644 --- a/tools/rexm/reports/examples_validation.md +++ b/tools/rexm/reports/examples_validation.md @@ -104,6 +104,7 @@ Example elements validated: | shapes_rlgl_color_wheel | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_rlgl_triangle | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_ball_physics | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_penrose_tile | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_logo_raylib | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_srcrec_dstrec | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_image_drawing | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -132,6 +133,7 @@ Example elements validated: | textures_screen_buffer | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_textured_curve | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_sprite_stacking | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| textures_cellular_automata | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_sprite_fonts | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_font_spritefont | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_font_filters | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -147,6 +149,7 @@ Example elements validated: | text_codepoints_loading | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_inline_styling | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_words_alignment | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| text_strings_management | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | models_animation_playing | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | models_billboard_rendering | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | models_box_collisions | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -206,6 +209,7 @@ Example elements validated: | shaders_lightmap_rendering | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_rounded_rectangle | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_depth_rendering | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shaders_game_of_life | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | | audio_module_playing | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | audio_music_stream | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | audio_raw_stream | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -215,13 +219,9 @@ Example elements validated: | audio_sound_multi | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | audio_sound_positioning | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | audio_spectrum_visualizer | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| rlgl_standalone | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| rlgl_compute_shader | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| easings_testbed | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | -| embedded_files_loading | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| web_basic_window | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shaders_game_of_life | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_penrose_tile | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| text_strings_management | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| textures_cellular_automata | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| rlgl_standalone | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | +| rlgl_compute_shader | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | +| easings_testbed | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ❌ | ✔ | ❌ | ✔ | ✔ | +| embedded_files_loading | ✔ | ❌ | ❌ | ✔ | ✔ | ❌ | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | +| web_basic_window | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | From 2d8e346945352461ab97ad299d4e840a5cd8a0d8 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 10 Dec 2025 19:14:28 +0100 Subject: [PATCH 215/430] Update update_examples.yml --- .github/workflows/update_examples.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update_examples.yml b/.github/workflows/update_examples.yml index 7feba7d89..c4ada2bd1 100644 --- a/.github/workflows/update_examples.yml +++ b/.github/workflows/update_examples.yml @@ -29,8 +29,9 @@ jobs: shell: bash - name: Build and run rexm tool (requires GNU Makefile) - # "rexm update" validates and updates all required examples in raylib and even raylib.com repos, - # note that it calls examples/Makefile.Web internally, so it requires [make] tool available + # "rexm validate" validates examples collection, looking for inconsistencies, it does not rebuild examples + # "rexm update" validates and updates all examples with inconsistencies, pushing fixes to raylib and raylib.com repos + # note that rexm calls examples/Makefile.Web internally, so it requires [make] tool available run: | sudo apt-get update && sudo apt-get install -y libopengl0 libglu1-mesa libx11-dev libxrandr-dev libxcursor-dev libxi-dev libxinerama-dev libgl1-mesa-dev libglu1-mesa-dev cd "${{ github.workspace }}/src" @@ -47,7 +48,7 @@ jobs: export REXM_EXAMPLES_COLLECTION_FILE_PATH="${{ github.workspace }}/examples/examples_list.txt" export REXM_EXAMPLES_VS2022_SLN_FILE="${{ github.workspace }}/projects/VS2022/raylib.sln" export EMSDK_PATH="${{ github.workspace }}/emsdk-cache/emsdk-main" - ./rexm update + ./rexm validate shell: bash - name: Commit changes to raylib repo (DISABLED) From 71a35f661e92c72e1b1bbc10856789fb14efe14c Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 11 Dec 2025 12:33:05 +0100 Subject: [PATCH 216/430] Update rexm.c --- tools/rexm/rexm.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index f1701a3ca..aa491ff23 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1008,6 +1008,7 @@ int main(int argc, char *argv[]) LOG("INFO: Scanning available example (.c) files to be added to collection...\n"); FilePathList clist = LoadDirectoryFilesEx(exBasePath, ".c", true); + // Load examples collection list file (raylib/examples/examples_list.txt) char *exList = LoadFileText(exCollectionFilePath); char *exListUpdated = (char *)RL_CALLOC(REXM_MAX_BUFFER_SIZE, 1); bool listUpdated = false; @@ -1024,7 +1025,7 @@ int main(int argc, char *argv[]) for (int i = 1; i < lineCount; i++) { - if ((TextFindIndex(exListUpdated, exListLines[i]) == -1) || (exListLines[i][0] == '#')) + if ((TextFindIndex(exListUpdated, exListLines[i]) == -1) || (exListLines[i][0] == '#')) exListUpdatedOffset += sprintf(exListUpdated + exListUpdatedOffset, "%s\n", exListLines[i]); else listUpdated = true; } @@ -2328,7 +2329,7 @@ static rlExampleInfo *LoadExampleInfo(const char *exFileName) // Example found in collection exInfo = (rlExampleInfo *)RL_CALLOC(1, sizeof(rlExampleInfo)); - strcpy(exInfo->name, GetFileNameWithoutExt(exFileName)); + strncpy(exInfo->name, GetFileNameWithoutExt(exFileName), 128 - 1); strncpy(exInfo->category, exInfo->name, TextFindIndex(exInfo->name, "_")); char *exText = LoadFileText(exFileName); @@ -2511,7 +2512,7 @@ static char **LoadExampleResourcePaths(const char *filePath, int *resPathCount) if (!end) break; // WARNING: Some paths could be for saving files, not loading, those "resource" files must be omitted - // HACK: Just check previous position from pointer for function name including the string and the index "distance" + // TODO: HACK: Just check previous position from pointer for function name including the string and the index "distance" // This is a quick solution, the good one would be getting the data loading function names... int functionIndex01 = TextFindIndex(ptr - 40, "ExportImage"); // Check ExportImage() int functionIndex02 = TextFindIndex(ptr - 10, "TraceLog"); // Check TraceLog() @@ -2869,8 +2870,8 @@ static void UpdateWebMetadata(const char *exHtmlPath, const char *exFilePath) char exTitle[64] = { 0 }; // Example title: fileName without extension, replacing underscores by spaces // Get example name: replace underscore by spaces - strcpy(exName, GetFileNameWithoutExt(exHtmlPathCopy)); - strcpy(exTitle, exName); + strncpy(exName, GetFileNameWithoutExt(exHtmlPathCopy), 64 - 1); + strncpy(exTitle, exName, 64 - 1); for (int i = 0; (i < 256) && (exTitle[i] != '\0'); i++) { if (exTitle[i] == '_') exTitle[i] = ' '; } // Get example category from exName: copy until first underscore From 2a566544d4dabdd29628747e0ea2f18de063b69a Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 11 Dec 2025 12:59:55 +0100 Subject: [PATCH 217/430] ADDED: Multiply security checks to avoid crashes on wrongly provided string data #4751 - REVIEWED: Checking `NULL` input on functions getting `const char *text`, to avoid crashes - REVIEWED: `strcpy()` usage, prioritize `strncpy()` with limited copy to buffer size - REPLACED: `strlen()` by `TextLength()` on [rtext] module - REVIEWED: Replaced some early returns (but keeping others, for easier code following) --- src/raudio.c | 15 +- src/raylib.h | 2 +- src/rcore.c | 70 +++++---- src/rlgl.h | 8 +- src/rmodels.c | 16 +- src/rtext.c | 403 ++++++++++++++++++++++++++---------------------- src/rtextures.c | 19 ++- src/utils.c | 4 +- 8 files changed, 289 insertions(+), 248 deletions(-) diff --git a/src/raudio.c b/src/raudio.c index d208bb6eb..429a746eb 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -1140,7 +1140,7 @@ bool ExportWaveAsCode(Wave wave, const char *fileName) // Get file name from path and convert variable name to uppercase char varFileName[256] = { 0 }; - strcpy(varFileName, GetFileNameWithoutExt(fileName)); + strncpy(varFileName, GetFileNameWithoutExt(fileName), 256 - 1); for (int i = 0; varFileName[i] != '\0'; i++) if (varFileName[i] >= 'a' && varFileName[i] <= 'z') { varFileName[i] = varFileName[i] - 32; } // Add wave information @@ -2739,11 +2739,13 @@ static const char *GetFileExtension(const char *fileName) return dot; } -// String pointer reverse break: returns right-most occurrence of charset in s -static const char *strprbrk(const char *s, const char *charset) +// String pointer reverse break: returns right-most occurrence of charset in text +static const char *strprbrk(const char *text, const char *charset) { const char *latestMatch = NULL; - for (; s = strpbrk(s, charset), s != NULL; latestMatch = s++) { } + + for (; (text != NULL) && (text = strpbrk(text, charset)); latestMatch = text++) { } + return latestMatch; } @@ -2766,7 +2768,7 @@ static const char *GetFileNameWithoutExt(const char *filePath) static char fileName[MAX_FILENAMEWITHOUTEXT_LENGTH] = { 0 }; memset(fileName, 0, MAX_FILENAMEWITHOUTEXT_LENGTH); - if (filePath != NULL) strcpy(fileName, GetFileName(filePath)); // Get filename with extension + if (filePath != NULL) strncpy(fileName, GetFileName(filePath), MAX_FILENAMEWITHOUTEXT_LENGTH - 1); // Get filename with extension int size = (int)strlen(fileName); // Get size in bytes @@ -2864,7 +2866,8 @@ static bool SaveFileText(const char *fileName, char *text) if (file != NULL) { - int count = fprintf(file, "%s", text); + int count = 0; + if (text != NULL) count = fprintf(file, "%s", text); if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write text file", fileName); else TRACELOG(LOG_INFO, "FILEIO: [%s] Text file saved successfully", fileName); diff --git a/src/raylib.h b/src/raylib.h index 96dc316ae..c2aa4997d 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1145,7 +1145,7 @@ RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previ RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) RLAPI const char *GetApplicationDirectory(void); // Get the directory of the running application (uses static string) RLAPI int MakeDirectory(const char *dirPath); // Create directories (including full path requested), returns 0 on success -RLAPI bool ChangeDirectory(const char *dir); // Change working directory, return true on success +RLAPI bool ChangeDirectory(const char *dirPath); // Change working directory, return true on success RLAPI bool IsPathFile(const char *path); // Check if a given path is a file or a directory RLAPI bool IsFileNameValid(const char *fileName); // Check if fileName is valid for the platform/OS RLAPI FilePathList LoadDirectoryFiles(const char *dirPath); // Load directory filepaths diff --git a/src/rcore.c b/src/rcore.c index c40fdbae4..dbc864020 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -113,7 +113,7 @@ #include // Required for: srand(), rand(), atexit() #include // Required for: sprintf() [Used in OpenURL()] -#include // Required for: strlen(), strcpy(), strcmp(), strrchr(), memset() +#include // Required for: strlen(), strncpy(), strcmp(), strrchr(), memset() #include // Required for: time() [Used in InitTimer()] #include // Required for: tan() [Used in BeginMode3D()], atan2f() [Used in LoadVrStereoConfig()] @@ -1837,8 +1837,8 @@ void TakeScreenshot(const char *fileName) unsigned char *imgData = rlReadScreenPixels((int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); Image image = { imgData, (int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y), 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; - char path[512] = { 0 }; - strcpy(path, TextFormat("%s/%s", CORE.Storage.basePath, fileName)); + char path[MAX_FILEPATH_LENGTH] = { 0 }; + strncpy(path, TextFormat("%s/%s", CORE.Storage.basePath, fileName), MAX_FILEPATH_LENGTH - 1); ExportImage(image, path); // WARNING: Module required: rtextures RL_FREE(imgData); @@ -2022,7 +2022,7 @@ bool IsFileExtension(const char *fileName, const char *ext) int extLen = (int)strlen(ext); char *extList = (char *)RL_CALLOC(extLen + 1, 1); char *extListPtrs[MAX_FILE_EXTENSIONS] = { 0 }; - strcpy(extList, ext); + strncpy(extList, ext, extLen); extListPtrs[0] = extList; for (int i = 0; i < extLen; i++) @@ -2130,11 +2130,11 @@ const char *GetFileExtension(const char *fileName) } // String pointer reverse break: returns right-most occurrence of charset in s -static const char *strprbrk(const char *s, const char *charset) +static const char *strprbrk(const char *text, const char *charset) { const char *latestMatch = NULL; - for (; s = strpbrk(s, charset), s != NULL; latestMatch = s++) { } + for (; (text != NULL) && (text = strpbrk(text, charset)); latestMatch = text++) { } return latestMatch; } @@ -2161,7 +2161,7 @@ const char *GetFileNameWithoutExt(const char *filePath) if (filePath != NULL) { - strcpy(fileName, GetFileName(filePath)); // Get filename.ext without path + strncpy(fileName, GetFileName(filePath), MAX_FILENAME_LENGTH - 1); // Get filename.ext without path int size = (int)strlen(fileName); // Get size in bytes for (int i = size; i > 0; i--) // Reverse search '.' @@ -2233,7 +2233,7 @@ const char *GetPrevDirectoryPath(const char *dirPath) memset(prevDirPath, 0, MAX_FILEPATH_LENGTH); int pathLen = (int)strlen(dirPath); - if (pathLen <= 3) strcpy(prevDirPath, dirPath); + if (pathLen <= 3) strncpy(prevDirPath, dirPath, MAX_FILEPATH_LENGTH - 1); for (int i = (pathLen - 1); (i >= 0) && (pathLen > 3); i--) { @@ -2472,12 +2472,12 @@ int MakeDirectory(const char *dirPath) } // Change working directory, returns true on success -bool ChangeDirectory(const char *dir) +bool ChangeDirectory(const char *dirPath) { - bool result = CHDIR(dir); + bool result = CHDIR(dirPath); - if (result != 0) TRACELOG(LOG_WARNING, "SYSTEM: Failed to change to directory: %s", dir); - else TRACELOG(LOG_INFO, "SYSTEM: Working Directory: %s", dir); + if (result != 0) TRACELOG(LOG_WARNING, "SYSTEM: Failed to change to directory: %s", dirPath); + else TRACELOG(LOG_INFO, "SYSTEM: Working Directory: %s", dirPath); return (result == 0); } @@ -2708,6 +2708,9 @@ unsigned char *DecodeDataBase64(const char *text, int *outputSize) ['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55, ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, ['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63 }; + + *outputSize = 0; + if (text == NULL) return NULL; // Compute expected size and padding int dataSize = (int)strlen(text); // WARNING: Expecting NULL terminated strings! @@ -3952,7 +3955,7 @@ static void ScanDirectoryFiles(const char *basePath, FilePathList *files, const { if (IsFileExtension(path, filter)) { - strcpy(files->paths[files->count], path); + strncpy(files->paths[files->count], path, MAX_FILEPATH_LENGTH - 1); files->count++; } } @@ -3960,14 +3963,14 @@ static void ScanDirectoryFiles(const char *basePath, FilePathList *files, const { if (strstr(filter, DIRECTORY_FILTER_TAG) != NULL) { - strcpy(files->paths[files->count], path); + strncpy(files->paths[files->count], path, MAX_FILEPATH_LENGTH - 1); files->count++; } } } else { - strcpy(files->paths[files->count], path); + strncpy(files->paths[files->count], path, MAX_FILEPATH_LENGTH - 1); files->count++; } } @@ -4011,13 +4014,13 @@ static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *fi { if (IsFileExtension(path, filter)) { - strcpy(files->paths[files->count], path); + strncpy(files->paths[files->count], path, MAX_FILEPATH_LENGTH - 1); files->count++; } } else { - strcpy(files->paths[files->count], path); + strncpy(files->paths[files->count], path, MAX_FILEPATH_LENGTH - 1); files->count++; } @@ -4031,7 +4034,7 @@ static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *fi { if ((filter != NULL) && (strstr(filter, DIRECTORY_FILTER_TAG) != NULL)) { - strcpy(files->paths[files->count], path); + strncpy(files->paths[files->count], path, MAX_FILEPATH_LENGTH - 1); files->count++; } @@ -4334,22 +4337,25 @@ const char *TextFormat(const char *text, ...) char *currentBuffer = buffers[index]; memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using - - va_list args; - va_start(args, text); - int requiredByteCount = vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args); - va_end(args); - - // If requiredByteCount is larger than the MAX_TEXT_BUFFER_LENGTH, then overflow occurred - if (requiredByteCount >= MAX_TEXT_BUFFER_LENGTH) + + if (text != NULL) { - // Inserting "..." at the end of the string to mark as truncated - char *truncBuffer = buffers[index] + MAX_TEXT_BUFFER_LENGTH - 4; // Adding 4 bytes = "...\0" - snprintf(truncBuffer, 4, "..."); - } + va_list args; + va_start(args, text); + int requiredByteCount = vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args); + va_end(args); - index += 1; // Move to next buffer for next function call - if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0; + // If requiredByteCount is larger than the MAX_TEXT_BUFFER_LENGTH, then overflow occurred + if (requiredByteCount >= MAX_TEXT_BUFFER_LENGTH) + { + // Inserting "..." at the end of the string to mark as truncated + char *truncBuffer = buffers[index] + MAX_TEXT_BUFFER_LENGTH - 4; // Adding 4 bytes = "...\0" + snprintf(truncBuffer, 4, "..."); + } + + index += 1; // Move to next buffer for next function call + if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0; + } return currentBuffer; } diff --git a/src/rlgl.h b/src/rlgl.h index 6f3620067..67bd90251 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -2492,12 +2492,12 @@ void rlLoadExtensions(void *loader) const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string // NOTE: We have to duplicate string because glGetString() returns a const string - int size = strlen(extensions) + 1; // Get extensions string size in bytes - char *extensionsDup = (char *)RL_CALLOC(size, sizeof(char)); - strcpy(extensionsDup, extensions); + int extSize = (int)strlen(extensions); // Get extensions string size in bytes + char *extensionsDup = (char *)RL_CALLOC(extSize + 1, sizeof(char)); // Allocate space for copy with additional EOL byte + strncpy(extensionsDup, extensions, extSize); extList[numExt] = extensionsDup; - for (int i = 0; i < size; i++) + for (int i = 0; i < extSize; i++) { if (extensionsDup[i] == ' ') { diff --git a/src/rmodels.c b/src/rmodels.c index 51e38008e..98209add8 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -2060,7 +2060,7 @@ bool ExportMeshAsCode(Mesh mesh, const char *fileName) // Get file name from path and convert variable name to uppercase char varFileName[256] = { 0 }; - strcpy(varFileName, GetFileNameWithoutExt(fileName)); + strncpy(varFileName, GetFileNameWithoutExt(fileName), 256 - 1); // NOTE: Using function provided by [rcore] module for (int i = 0; varFileName[i] != '\0'; i++) if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; } // Add image information @@ -4306,8 +4306,8 @@ static Model LoadOBJ(const char *fileName) return model; } - char currentDir[1024] = { 0 }; - strcpy(currentDir, GetWorkingDirectory()); // Save current working directory + char currentDir[MAX_FILEPATH_LENGTH] = { 0 }; + strncpy(currentDir, GetWorkingDirectory(), MAX_FILEPATH_LENGTH - 1); // Save current working directory const char *workingDir = GetDirectoryPath(fileName); // Switch to OBJ directory for material path correctness if (CHDIR(workingDir) != 0) TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", workingDir); @@ -5025,10 +5025,8 @@ static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, int *animCou for (unsigned int j = 0; j < iqmHeader->num_poses; j++) { // If animations and skeleton are in the same file, copy bone names to anim - if (iqmHeader->num_joints > 0) - memcpy(animations[a].bones[j].name, fileDataPtr + iqmHeader->ofs_text + joints[j].name, BONE_NAME_LENGTH*sizeof(char)); - else - strcpy(animations[a].bones[j].name, "ANIMJOINTNAME"); // Default bone name otherwise + if (iqmHeader->num_joints > 0) memcpy(animations[a].bones[j].name, fileDataPtr + iqmHeader->ofs_text + joints[j].name, BONE_NAME_LENGTH*sizeof(char)); + else memcpy(animations[a].bones[j].name, "ANIMJOINTNAME", 13); // Default bone name otherwise animations[a].bones[j].parent = poses[j].parent; } @@ -6970,7 +6968,7 @@ static Model LoadM3D(const char *fileName) // Add a special "no bone" bone model.bones[i].parent = -1; - strcpy(model.bones[i].name, "NO BONE"); + memcpy(model.bones[i].name, "NO BONE", 7); model.bindPose[i].translation.x = 0.0f; model.bindPose[i].translation.y = 0.0f; model.bindPose[i].translation.z = 0.0f; @@ -7062,7 +7060,7 @@ static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, int *animCou // A special, never transformed "no bone" bone, used for boneless vertices animations[a].bones[i].parent = -1; - strcpy(animations[a].bones[i].name, "NO BONE"); + memcpy(animations[a].bones[i].name, "NO BONE", 7); // M3D stores frames at arbitrary intervals with sparse skeletons. We need full skeletons at // regular intervals, so let the M3D SDK do the heavy lifting and calculate interpolated bones diff --git a/src/rtext.c b/src/rtext.c index c17fbe9bf..74f68544e 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -67,7 +67,7 @@ #include // Required for: malloc(), free() #include // Required for: vsprintf() -#include // Required for: strcmp(), strstr(), strcpy(), strncpy() [Used in TextReplace()], sscanf() [Used in LoadBMFont()] +#include // Required for: strcmp(), strstr(), strncpy() [Used in TextReplace()], sscanf() [Used in LoadBMFont()] #include // Required for: va_list, va_start(), vsprintf(), va_end() [Used in TextFormat()] #include // Required for: toupper(), tolower() [Used in TextToUpper(), TextToLower()] @@ -164,9 +164,8 @@ extern void LoadFontDefault(void) { #define BIT_CHECK(a,b) ((a) & (1u << (b))) - // check to see if we have allready allocated the font for an image, and if we don't need to upload, then just return - if (defaultFont.glyphs != NULL && !isGpuReady) - return; + // Check to see if we have allready allocated the font for an image, and if we don't need to upload, then just return + if ((defaultFont.glyphs != NULL) && !isGpuReady) return; // NOTE: Using UTF-8 encoding table for Unicode U+0000..U+00FF Basic Latin + Latin-1 Supplement // Ref: http://www.utf8-chartable.de/unicode-utf8-table.pl @@ -1453,29 +1452,31 @@ Rectangle GetGlyphAtlasRec(Font font, int codepoint) char **LoadTextLines(const char *text, int *count) { char **lines = NULL; + int lineCount = 0; - if (text == NULL) { *count = 0; return lines; } - - int lineCount = 1; - int textSize = (int)strlen(text); - - // First text scan pass to get required line count - for (int i = 0; i < textSize; i++) + if (text != NULL) { - if (text[i] == '\n') lineCount++; - } + int textSize = TextLength(text); + lineCount = 1; - lines = (char **)RL_CALLOC(lineCount, sizeof(char *)); - for (int i = 0, l = 0, lineLen = 0; i <= textSize; i++) - { - if ((text[i] == '\n') || (text[i] == '\0')) + // First text scan pass to get required line count + for (int i = 0; i < textSize; i++) { - lines[l] = (char *)RL_CALLOC(lineLen + 1, 1); - strncpy(lines[l], &text[i - lineLen], lineLen); - lineLen = 0; - l++; + if (text[i] == '\n') lineCount++; + } + + lines = (char **)RL_CALLOC(lineCount, sizeof(char *)); + for (int i = 0, l = 0, lineLen = 0; i <= textSize; i++) + { + if ((text[i] == '\n') || (text[i] == '\0')) + { + lines[l] = (char *)RL_CALLOC(lineLen + 1, 1); + strncpy(lines[l], &text[i - lineLen], lineLen); + lineLen = 0; + l++; + } + else lineLen++; } - else lineLen++; } *count = lineCount; @@ -1517,23 +1518,26 @@ const char *TextFormat(const char *text, ...) static int index = 0; char *currentBuffer = buffers[index]; - memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using - - va_list args; - va_start(args, text); - int requiredByteCount = vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args); - va_end(args); - - // If requiredByteCount is larger than the MAX_TEXT_BUFFER_LENGTH, then overflow occurred - if (requiredByteCount >= MAX_TEXT_BUFFER_LENGTH) + memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using + + if (text != NULL) { - // Inserting "..." at the end of the string to mark as truncated - char *truncBuffer = buffers[index] + MAX_TEXT_BUFFER_LENGTH - 4; // Adding 4 bytes = "...\0" - snprintf(truncBuffer, 4, "..."); - } + va_list args; + va_start(args, text); + int requiredByteCount = vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args); + va_end(args); - index += 1; // Move to next buffer for next function call - if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0; + // If requiredByteCount is larger than the MAX_TEXT_BUFFER_LENGTH, then overflow occurred + if (requiredByteCount >= MAX_TEXT_BUFFER_LENGTH) + { + // Inserting "..." at the end of the string to mark as truncated + char *truncBuffer = buffers[index] + MAX_TEXT_BUFFER_LENGTH - 4; // Adding 4 bytes = "...\0" + snprintf(truncBuffer, 4, "..."); + } + + index += 1; // Move to next buffer for next function call + if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0; + } return currentBuffer; } @@ -1545,13 +1549,16 @@ int TextToInteger(const char *text) int value = 0; int sign = 1; - if ((text[0] == '+') || (text[0] == '-')) + if (text != NULL) { - if (text[0] == '-') sign = -1; - text++; - } + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1; + text++; + } - for (int i = 0; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10 + (int)(text[i] - '0'); + for (int i = 0; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10 + (int)(text[i] - '0'); + } return value*sign; } @@ -1564,22 +1571,25 @@ float TextToFloat(const char *text) float value = 0.0f; float sign = 1.0f; - if ((text[0] == '+') || (text[0] == '-')) + if (text != NULL) { - if (text[0] == '-') sign = -1.0f; - text++; - } - - int i = 0; - for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0'); - - if (text[i++] == '.') - { - float divisor = 10.0f; - for (; ((text[i] >= '0') && (text[i] <= '9')); i++) + if ((text[0] == '+') || (text[0] == '-')) { - value += ((float)(text[i] - '0'))/divisor; - divisor = divisor*10.0f; + if (text[0] == '-') sign = -1.0f; + text++; + } + + int i = 0; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0'); + + if (text[i++] == '.') + { + float divisor = 10.0f; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) + { + value += ((float)(text[i] - '0'))/divisor; + divisor = divisor*10.0f; + } } } @@ -1631,26 +1641,23 @@ const char *TextSubtext(const char *text, int position, int length) static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH); - int textLength = TextLength(text); - - if (position >= textLength) + if (text != NULL) { - return buffer; //First char is already '\0' by memset + int textLength = TextLength(text); + + if (position >= textLength) return buffer; // First char is already '\0' by memset + + int maxLength = textLength - position; + if (length > maxLength) length = maxLength; + if (length >= MAX_TEXT_BUFFER_LENGTH) length = MAX_TEXT_BUFFER_LENGTH - 1; + + // NOTE: Alternative: memcpy(buffer, text + position, length) + + for (int c = 0; c < length; c++) buffer[c] = text[position + c]; + + buffer[length] = '\0'; } - int maxLength = textLength - position; - if (length > maxLength) length = maxLength; - if (length >= MAX_TEXT_BUFFER_LENGTH) length = MAX_TEXT_BUFFER_LENGTH - 1; - - // NOTE: Alternative: memcpy(buffer, text + position, length) - - for (int c = 0 ; c < length ; c++) - { - buffer[c] = text[position + c]; - } - - buffer[length] = '\0'; - return buffer; } @@ -1684,7 +1691,7 @@ char *GetTextBetween(const char *text, const char *begin, const char *end) if (beginIndex > -1) { - int beginLen = (int)strlen(begin); + int beginLen = TextLength(begin); int endIndex = TextFindIndex(text + beginIndex + beginLen, end); if (endIndex > -1) @@ -1700,84 +1707,86 @@ char *GetTextBetween(const char *text, const char *begin, const char *end) } // Replace text string -// REQUIRES: strstr(), strncpy(), strcpy() +// REQUIRES: strstr(), strncpy() // TODO: If (replacement == "") remove "search" text // WARNING: Allocated memory must be manually freed char *TextReplace(const char *text, const char *search, const char *replacement) { char *result = NULL; - if (!text || !search) return NULL; // Sanity check - - char *insertPoint = NULL; // Next insert point - char *temp = NULL; // Temp pointer - int searchLen = 0; // Search string length of (the string to remove) - int replaceLen = 0; // Replacement length (the string to replace by) - int lastReplacePos = 0; // Distance between next search and end of last replace - int count = 0; // Number of replacements - - searchLen = TextLength(search); - if (searchLen == 0) return NULL; // Empty search causes infinite loop during count - - replaceLen = TextLength(replacement); - - // Count the number of replacements needed - insertPoint = (char *)text; - for (count = 0; (temp = strstr(insertPoint, search)); count++) insertPoint = temp + searchLen; - - // Allocate returning string and point temp to it - temp = result = (char *)RL_MALLOC(TextLength(text) + (replaceLen - searchLen)*count + 1); - - if (!result) return NULL; // Memory could not be allocated - - // First time through the loop, all the variable are set correctly from here on, - // - 'temp' points to the end of the result string - // - 'insertPoint' points to the next occurrence of replace in text - // - 'text' points to the remainder of text after "end of replace" - while (count--) + if ((text != NULL) && (search != NULL)) { - insertPoint = (char *)strstr(text, search); - lastReplacePos = (int)(insertPoint - text); - temp = strncpy(temp, text, lastReplacePos) + lastReplacePos; - temp = strcpy(temp, replacement) + replaceLen; - text += lastReplacePos + searchLen; // Move to next "end of replace" - } + char *insertPoint = NULL; // Next insert point + char *temp = NULL; // Temp pointer + int searchLen = 0; // Search string length of (the string to remove) + int replaceLen = 0; // Replacement length (the string to replace by) + int lastReplacePos = 0; // Distance between next search and end of last replace + int count = 0; // Number of replacements - // Copy remaind text part after replacement to result (pointed by moving temp) - strcpy(temp, text); + searchLen = TextLength(search); + if (searchLen == 0) return NULL; // Empty search causes infinite loop during count + + replaceLen = TextLength(replacement); + + // Count the number of replacements needed + insertPoint = (char *)text; + for (count = 0; (temp = strstr(insertPoint, search)); count++) insertPoint = temp + searchLen; + + // Allocate returning string and point temp to it + temp = result = (char *)RL_MALLOC(TextLength(text) + (replaceLen - searchLen)*count + 1); + + if (!result) return NULL; // Memory could not be allocated + + // First time through the loop, all the variable are set correctly from here on, + // - 'temp' points to the end of the result string + // - 'insertPoint' points to the next occurrence of replace in text + // - 'text' points to the remainder of text after "end of replace" + while (count--) + { + insertPoint = (char *)strstr(text, search); + lastReplacePos = (int)(insertPoint - text); + temp = strncpy(temp, text, lastReplacePos) + lastReplacePos; + temp = strcpy(temp, replacement) + replaceLen; + text += lastReplacePos + searchLen; // Move to next "end of replace" + } + + // Copy remaind text part after replacement to result (pointed by moving temp) + strcpy(temp, text); + } return result; } // Replace text between two specific strings -// REQUIRES: strlen(), strncpy() +// REQUIRES: strncpy() // NOTE: If (replacement == NULL) remove "begin"[ ]"end" text // WARNING: Returned string must be freed by user char *TextReplaceBetween(const char *text, const char *begin, const char *end, const char *replacement) { char *result = NULL; - if (!text || !begin || !end) return NULL; // Sanity check - - int beginIndex = TextFindIndex(text, begin); - - if (beginIndex > -1) + if ((text != NULL) && (begin != NULL) && (end != NULL)) { - int beginLen = (int)strlen(begin); - int endIndex = TextFindIndex(text + beginIndex + beginLen, end); + int beginIndex = TextFindIndex(text, begin); - if (endIndex > -1) + if (beginIndex > -1) { - endIndex += (beginIndex + beginLen); + int beginLen = TextLength(begin); + int endIndex = TextFindIndex(text + beginIndex + beginLen, end); - int textLen = (int)strlen(text); - int replaceLen = (replacement == NULL)? 0 : (int)strlen(replacement); - int toreplaceLen = endIndex - beginIndex - beginLen; - result = (char *)RL_CALLOC(textLen + replaceLen - toreplaceLen + 1, sizeof(char)); + if (endIndex > -1) + { + endIndex += (beginIndex + beginLen); - strncpy(result, text, beginIndex + beginLen); // Copy first text part - if (replacement != NULL) strncpy(result + beginIndex + beginLen, replacement, replaceLen); // Copy replacement (if provided) - strncpy(result + beginIndex + beginLen + replaceLen, text + endIndex, textLen - endIndex); // Copy end text part + int textLen = TextLength(text); + int replaceLen = (replacement == NULL)? 0 : TextLength(replacement); + int toreplaceLen = endIndex - beginIndex - beginLen; + result = (char *)RL_CALLOC(textLen + replaceLen - toreplaceLen + 1, sizeof(char)); + + strncpy(result, text, beginIndex + beginLen); // Copy first text part + if (replacement != NULL) strncpy(result + beginIndex + beginLen, replacement, replaceLen); // Copy replacement (if provided) + strncpy(result + beginIndex + beginLen + replaceLen, text + endIndex, textLen - endIndex); // Copy end text part + } } } @@ -1788,16 +1797,21 @@ char *TextReplaceBetween(const char *text, const char *begin, const char *end, c // WARNING: Allocated memory must be manually freed char *TextInsert(const char *text, const char *insert, int position) { - int textLen = TextLength(text); - int insertLen = TextLength(insert); + char *result = NULL; - char *result = (char *)RL_MALLOC(textLen + insertLen + 1); + if ((text != NULL) && (insert != NULL)) + { + int textLen = TextLength(text); + int insertLen = TextLength(insert); - for (int i = 0; i < position; i++) result[i] = text[i]; - for (int i = position; i < insertLen + position; i++) result[i] = insert[i]; - for (int i = (insertLen + position); i < (textLen + insertLen); i++) result[i] = text[i]; + result = (char *)RL_MALLOC(textLen + insertLen + 1); - result[textLen + insertLen] = '\0'; // Make sure text string is valid! + for (int i = 0; i < position; i++) result[i] = text[i]; + for (int i = position; i < insertLen + position; i++) result[i] = insert[i]; + for (int i = (insertLen + position); i < (textLen + insertLen); i++) result[i] = text[i]; + + result[textLen + insertLen] = '\0'; // Add EOL + } return result; } @@ -1879,11 +1893,13 @@ char **TextSplit(const char *text, char delimiter, int *count) // Append text at specific position and move cursor // WARNING: It's up to the user to make sure appended text does not overflow the buffer! -// REQUIRES: strcpy() void TextAppend(char *text, const char *append, int *position) { - strcpy(text + *position, append); - *position += TextLength(append); + if ((text != NULL) && (append != NULL)) + { + TextCopy(text + *position, append); + *position += TextLength(append); + } } // Find first text occurrence within a string @@ -1891,11 +1907,13 @@ void TextAppend(char *text, const char *append, int *position) int TextFindIndex(const char *text, const char *search) { int position = -1; - if (text == NULL) return position; - char *ptr = (char *)strstr(text, search); + if (text != NULL) + { + char *ptr = (char *)strstr(text, search); - if (ptr != NULL) position = (int)(ptr - text); + if (ptr != NULL) position = (int)(ptr - text); + } return position; } @@ -2029,24 +2047,29 @@ char *TextToCamel(const char *text) // WARNING: Allocated memory must be manually freed char *LoadUTF8(const int *codepoints, int length) { - // We allocate enough memory to fit all possible codepoints - // NOTE: 5 bytes for every codepoint should be enough - char *text = (char *)RL_CALLOC(length*5, 1); - const char *utf8 = NULL; - int size = 0; - - for (int i = 0, bytes = 0; i < length; i++) + char *text = NULL; + + if ((codepoints != NULL) && (length > 0)) { - utf8 = CodepointToUTF8(codepoints[i], &bytes); - memcpy(text + size, utf8, bytes); - size += bytes; - } + // We allocate enough memory to fit all possible codepoints + // NOTE: 5 bytes for every codepoint should be enough + text = (char *)RL_CALLOC(length*5, 1); + const char *utf8 = NULL; + int size = 0; - // Create second buffer and copy data manually to it - char *temp = (char *)RL_CALLOC(size + 1, 1); - memcpy(temp, text, size); - RL_FREE(text); - text = temp; + for (int i = 0, bytes = 0; i < length; i++) + { + utf8 = CodepointToUTF8(codepoints[i], &bytes); + memcpy(text + size, utf8, bytes); + size += bytes; + } + + // Create second buffer and copy data manually to it + char *temp = (char *)RL_CALLOC(size + 1, 1); + memcpy(temp, text, size); + RL_FREE(text); + text = temp; + } return text; } @@ -2060,28 +2083,31 @@ void UnloadUTF8(char *text) // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter int *LoadCodepoints(const char *text, int *count) { - int textLength = TextLength(text); - - int codepointSize = 0; + int *codepoints = NULL; int codepointCount = 0; - - // Allocate a big enough buffer to store as many codepoints as text bytes - int *codepoints = (int *)RL_CALLOC(textLength, sizeof(int)); - - for (int i = 0; i < textLength; codepointCount++) + + if (text != NULL) { - codepoints[codepointCount] = GetCodepointNext(text + i, &codepointSize); - i += codepointSize; + int textLength = TextLength(text); + + // Allocate a big enough buffer to store as many codepoints as text bytes + int *codepoints = (int *)RL_CALLOC(textLength, sizeof(int)); + + int codepointSize = 0; + for (int i = 0; i < textLength; codepointCount++) + { + codepoints[codepointCount] = GetCodepointNext(text + i, &codepointSize); + i += codepointSize; + } + + // Create second buffer and copy data manually to it + int *temp = (int *)RL_CALLOC(codepointCount, sizeof(int)); + for (int i = 0; i < codepointCount; i++) temp[i] = codepoints[i]; + RL_FREE(codepoints); + codepoints = temp; } - // Create second buffer and copy data manually to it - int *temp = (int *)RL_CALLOC(codepointCount, sizeof(int)); - for (int i = 0; i < codepointCount; i++) temp[i] = codepoints[i]; - RL_FREE(codepoints); - codepoints = temp; - *count = codepointCount; - return codepoints; } @@ -2098,14 +2124,15 @@ int GetCodepointCount(const char *text) unsigned int length = 0; const char *ptr = text; - while (*ptr != '\0') + if (ptr != NULL) { - int next = 0; - GetCodepointNext(ptr, &next); - - ptr += next; - - length++; + while (*ptr != '\0') + { + int next = 0; + GetCodepointNext(ptr, &next); + ptr += next; + length++; + } } return length; @@ -2170,11 +2197,14 @@ int GetCodepoint(const char *text, int *codepointSize) 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ - // NOTE: on decode errors we return as soon as possible + int codepoint = 0x3f; // Codepoint (defaults to '?') - int octet = (unsigned char)(text[0]); // The first UTF8 octet *codepointSize = 1; + if (text == NULL) return codepoint; + + // NOTE: on decode errors we return as soon as possible + int octet = (unsigned char)(text[0]); // The first UTF8 octet if (octet <= 0x7f) { @@ -2266,6 +2296,7 @@ int GetCodepointNext(const char *text, int *codepointSize) const char *ptr = text; int codepoint = 0x3f; // Codepoint (defaults to '?') *codepointSize = 1; + if (text == NULL) return codepoint; // Get current codepoint and bytes processed if (0xf0 == (0xf8 & ptr[0])) @@ -2304,15 +2335,15 @@ int GetCodepointPrevious(const char *text, int *codepointSize) { const char *ptr = text; int codepoint = 0x3f; // Codepoint (defaults to '?') - int cpSize = 0; - *codepointSize = 0; + *codepointSize = 1; + if (text == NULL) return codepoint; // Move to previous codepoint do ptr--; while (((0x80 & ptr[0]) != 0) && ((0xc0 & ptr[0]) == 0x80)); + int cpSize = 0; codepoint = GetCodepointNext(ptr, &cpSize); - if (codepoint != 0) *codepointSize = cpSize; return codepoint; diff --git a/src/rtextures.c b/src/rtextures.c index 17065822a..24f21e333 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -771,7 +771,7 @@ bool ExportImageAsCode(Image image, const char *fileName) // Get file name from path and convert variable name to uppercase char varFileName[256] = { 0 }; - strcpy(varFileName, GetFileNameWithoutExt(fileName)); + strncpy(varFileName, GetFileNameWithoutExt(fileName), 256 - 1); // NOTE: Using function provided by [rcore] module for (int i = 0; varFileName[i] != '\0'; i++) if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; } // Add image information @@ -1125,17 +1125,19 @@ Image GenImageCellular(int width, int height, int tileSize) Image GenImageText(int width, int height, const char *text) { Image image = { 0 }; - - int textLength = (int)strlen(text); - int imageViewSize = width*height; - + + int imageSize = width*height; image.width = width; image.height = height; image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; - image.data = RL_CALLOC(imageViewSize, 1); + image.data = RL_CALLOC(imageSize, 1); image.mipmaps = 1; - memcpy(image.data, text, (textLength > imageViewSize)? imageViewSize : textLength); + if (text != NULL) + { + int textLength = (int)strlen(text); + memcpy(image.data, text, (textLength > imageSize)? imageSize : textLength); + } return image; } @@ -1484,8 +1486,9 @@ Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Co { Image imText = { 0 }; #if defined(SUPPORT_MODULE_RTEXT) + if (text == NULL) return imText; + int size = (int)strlen(text); // Get size in bytes of text - int textOffsetX = 0; // Image drawing position X int textOffsetY = 0; // Offset between lines (on linebreak '\n') diff --git a/src/utils.c b/src/utils.c index 892f96cf4..44facea3a 100644 --- a/src/utils.c +++ b/src/utils.c @@ -105,7 +105,7 @@ void TraceLog(int logType, const char *text, ...) { #if defined(SUPPORT_TRACELOG) // Message has level below current threshold, don't emit - if (logType < logTypeLevel) return; + if ((logType < logTypeLevel) || (text == NULL)) return; va_list args; va_start(args, text); @@ -313,7 +313,7 @@ bool ExportDataAsCode(const unsigned char *data, int dataSize, const char *fileN // Get file name from path char varFileName[256] = { 0 }; - strcpy(varFileName, GetFileNameWithoutExt(fileName)); + strncpy(varFileName, GetFileNameWithoutExt(fileName), 256 - 1); for (int i = 0; varFileName[i] != '\0'; i++) { // Convert variable name to uppercase From ae438e804e338d56e0d50873be4e6486bc66a817 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:00:15 +0000 Subject: [PATCH 218/430] rlparser: update raylib_api.* by CI --- tools/rlparser/output/raylib_api.json | 2 +- tools/rlparser/output/raylib_api.lua | 2 +- tools/rlparser/output/raylib_api.txt | 2 +- tools/rlparser/output/raylib_api.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/rlparser/output/raylib_api.json b/tools/rlparser/output/raylib_api.json index a1af3c9fc..ce17825a3 100644 --- a/tools/rlparser/output/raylib_api.json +++ b/tools/rlparser/output/raylib_api.json @@ -4646,7 +4646,7 @@ "params": [ { "type": "const char *", - "name": "dir" + "name": "dirPath" } ] }, diff --git a/tools/rlparser/output/raylib_api.lua b/tools/rlparser/output/raylib_api.lua index 20043c12d..1b5075c35 100644 --- a/tools/rlparser/output/raylib_api.lua +++ b/tools/rlparser/output/raylib_api.lua @@ -4167,7 +4167,7 @@ return { description = "Change working directory, return true on success", returnType = "bool", params = { - {type = "const char *", name = "dir"} + {type = "const char *", name = "dirPath"} } }, { diff --git a/tools/rlparser/output/raylib_api.txt b/tools/rlparser/output/raylib_api.txt index 3578e41df..bc55918ce 100644 --- a/tools/rlparser/output/raylib_api.txt +++ b/tools/rlparser/output/raylib_api.txt @@ -1763,7 +1763,7 @@ Function 143: ChangeDirectory() (1 input parameters) Name: ChangeDirectory Return type: bool Description: Change working directory, return true on success - Param[1]: dir (type: const char *) + Param[1]: dirPath (type: const char *) Function 144: IsPathFile() (1 input parameters) Name: IsPathFile Return type: bool diff --git a/tools/rlparser/output/raylib_api.xml b/tools/rlparser/output/raylib_api.xml index ea7792612..5c83e9b86 100644 --- a/tools/rlparser/output/raylib_api.xml +++ b/tools/rlparser/output/raylib_api.xml @@ -1111,7 +1111,7 @@ - + From 6f7cd3a9ab1ded457e198bdf7789e599771ff56e Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Thu, 11 Dec 2025 04:37:17 -0800 Subject: [PATCH 219/430] [core] Camera2d comment updates (#5401) * Make the comments on the camera 2d fields more clear about what space each one is in. * rlparser: update raylib_api.* by CI --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/raylib.h | 8 ++++---- tools/rlparser/output/raylib_api.json | 8 ++++---- tools/rlparser/output/raylib_api.lua | 8 ++++---- tools/rlparser/output/raylib_api.txt | 8 ++++---- tools/rlparser/output/raylib_api.xml | 8 ++++---- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/raylib.h b/src/raylib.h index c2aa4997d..ba80e40c7 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -334,10 +334,10 @@ typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D // Camera2D, defines position/orientation in 2d space typedef struct Camera2D { - Vector2 offset; // Camera offset (displacement from target) - Vector2 target; // Camera target (rotation and zoom origin) - float rotation; // Camera rotation in degrees - float zoom; // Camera zoom (scaling), should be 1.0f by default + Vector2 offset; // Camera offset (screen space offset from window origin) + Vector2 target; // Camera target (world space target point that is mapped to screen space offset) + float rotation; // Camera rotation in degrees (pivots around target) + float zoom; // Camera zoom (scaling around target), must not be set to 0, set to 1.0f for no scale } Camera2D; // Mesh, vertex data and vao/vbo diff --git a/tools/rlparser/output/raylib_api.json b/tools/rlparser/output/raylib_api.json index ce17825a3..66d8e9f30 100644 --- a/tools/rlparser/output/raylib_api.json +++ b/tools/rlparser/output/raylib_api.json @@ -769,22 +769,22 @@ { "type": "Vector2", "name": "offset", - "description": "Camera offset (displacement from target)" + "description": "Camera offset (screen space offset from window origin)" }, { "type": "Vector2", "name": "target", - "description": "Camera target (rotation and zoom origin)" + "description": "Camera target (world space target point that is mapped to screen space offset)" }, { "type": "float", "name": "rotation", - "description": "Camera rotation in degrees" + "description": "Camera rotation in degrees (pivots around target)" }, { "type": "float", "name": "zoom", - "description": "Camera zoom (scaling), should be 1.0f by default" + "description": "Camera zoom (scaling around target), must not be set to 0, set to 1.0f for no scale" } ] }, diff --git a/tools/rlparser/output/raylib_api.lua b/tools/rlparser/output/raylib_api.lua index 1b5075c35..192ad963a 100644 --- a/tools/rlparser/output/raylib_api.lua +++ b/tools/rlparser/output/raylib_api.lua @@ -769,22 +769,22 @@ return { { type = "Vector2", name = "offset", - description = "Camera offset (displacement from target)" + description = "Camera offset (screen space offset from window origin)" }, { type = "Vector2", name = "target", - description = "Camera target (rotation and zoom origin)" + description = "Camera target (world space target point that is mapped to screen space offset)" }, { type = "float", name = "rotation", - description = "Camera rotation in degrees" + description = "Camera rotation in degrees (pivots around target)" }, { type = "float", name = "zoom", - description = "Camera zoom (scaling), should be 1.0f by default" + description = "Camera zoom (scaling around target), must not be set to 0, set to 1.0f for no scale" } } }, diff --git a/tools/rlparser/output/raylib_api.txt b/tools/rlparser/output/raylib_api.txt index bc55918ce..f60f8fc81 100644 --- a/tools/rlparser/output/raylib_api.txt +++ b/tools/rlparser/output/raylib_api.txt @@ -399,10 +399,10 @@ Struct 13: Camera3D (5 fields) Struct 14: Camera2D (4 fields) Name: Camera2D Description: Camera2D, defines position/orientation in 2d space - Field[1]: Vector2 offset // Camera offset (displacement from target) - Field[2]: Vector2 target // Camera target (rotation and zoom origin) - Field[3]: float rotation // Camera rotation in degrees - Field[4]: float zoom // Camera zoom (scaling), should be 1.0f by default + Field[1]: Vector2 offset // Camera offset (screen space offset from window origin) + Field[2]: Vector2 target // Camera target (world space target point that is mapped to screen space offset) + Field[3]: float rotation // Camera rotation in degrees (pivots around target) + Field[4]: float zoom // Camera zoom (scaling around target), must not be set to 0, set to 1.0f for no scale Struct 15: Mesh (17 fields) Name: Mesh Description: Mesh, vertex data and vao/vbo diff --git a/tools/rlparser/output/raylib_api.xml b/tools/rlparser/output/raylib_api.xml index 5c83e9b86..1bbeb175c 100644 --- a/tools/rlparser/output/raylib_api.xml +++ b/tools/rlparser/output/raylib_api.xml @@ -155,10 +155,10 @@ - - - - + + + + From 8fa5f1fe2cf7efeda59a5d935a259ccb1cb97f1c Mon Sep 17 00:00:00 2001 From: Jordi Santonja <77529699+JordSant@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:38:08 +0100 Subject: [PATCH 220/430] [examples] Fixed `shaders_game_of_life` for web (#5399) * [examples] Fixed `shaders_game_of_life` for web * Fixed image loadig for rexm --- examples/Makefile.Web | 9 ++++++++- examples/shaders/shaders_game_of_life.c | 23 ++++++++++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/examples/Makefile.Web b/examples/Makefile.Web index d638ace51..7101ed7e3 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -1367,8 +1367,15 @@ shaders/shaders_fog_rendering: shaders/shaders_fog_rendering.c shaders/shaders_game_of_life: shaders/shaders_game_of_life.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file shaders/resources/shaders/glsl100/game_of_life.fs@resources/shaders/glsl100/game_of_life.fs \ + --preload-file shaders/resources/game_of_life/acorn.png@resources/game_of_life/acorn.png \ + --preload-file shaders/resources/game_of_life/breeder.png@resources/game_of_life/breeder.png \ + --preload-file shaders/resources/game_of_life/glider.png@resources/game_of_life/glider.png \ + --preload-file shaders/resources/game_of_life/glider_gun.png@resources/game_of_life/glider_gun.png \ + --preload-file shaders/resources/game_of_life/oscillators.png@resources/game_of_life/oscillators.png \ + --preload-file shaders/resources/game_of_life/puffer_train.png@resources/game_of_life/puffer_train.png \ --preload-file shaders/resources/game_of_life/r_pentomino.png@resources/game_of_life/r_pentomino.png \ - --preload-file shaders/resources/game_of_life/.png@resources/game_of_life/.png + --preload-file shaders/resources/game_of_life/spaceships.png@resources/game_of_life/spaceships.png \ + --preload-file shaders/resources/game_of_life/still_lifes.png@resources/game_of_life/still_lifes.png shaders/shaders_hot_reloading: shaders/shaders_hot_reloading.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ diff --git a/examples/shaders/shaders_game_of_life.c b/examples/shaders/shaders_game_of_life.c index fa841d066..9b9242a0d 100644 --- a/examples/shaders/shaders_game_of_life.c +++ b/examples/shaders/shaders_game_of_life.c @@ -42,7 +42,6 @@ typedef enum { // Struct to store example preset patterns typedef struct { char *name; - char *fileName; Vector2 position; } PresetPattern; @@ -77,10 +76,10 @@ int main(void) const Rectangle textureOnScreen = { 0, 0, (float)windowWidth, (float)windowHeight }; const PresetPattern presetPatterns[] = { - { "Glider", "glider", { 0.5f, 0.5f } }, { "R-pentomino", "r_pentomino", { 0.5f, 0.5f } }, { "Acorn", "acorn", { 0.5f,0.5f } }, - { "Spaceships", "spaceships", { 0.1f, 0.5f } }, { "Still lifes", "still_lifes", { 0.5f, 0.5f } }, { "Oscillators", "oscillators", { 0.5f, 0.5f } }, - { "Puffer train", "puffer_train", { 0.1f, 0.5f } }, { "Glider Gun", "glider_gun", { 0.2f, 0.2f } }, { "Breeder", "breeder", { 0.1f, 0.5f } }, - { "Random", "", { 0.5f, 0.5f } } + { "Glider", { 0.5f, 0.5f } }, { "R-pentomino", { 0.5f, 0.5f } }, { "Acorn", { 0.5f,0.5f } }, + { "Spaceships", { 0.1f, 0.5f } }, { "Still lifes", { 0.5f, 0.5f } }, { "Oscillators", { 0.5f, 0.5f } }, + { "Puffer train", { 0.1f, 0.5f } }, { "Glider Gun", { 0.2f, 0.2f } }, { "Breeder", { 0.1f, 0.5f } }, + { "Random", { 0.5f, 0.5f } } }; const int numberOfPresets = sizeof(presetPatterns)/sizeof(presetPatterns[0]); @@ -214,8 +213,18 @@ int main(void) Image pattern; if (preset < numberOfPresets - 1) // Preset with pattern image lo load { - pattern = LoadImage(TextFormat("resources/game_of_life/%s.png", presetPatterns[preset].fileName)); - + switch (preset) + { + case 0: pattern = LoadImage("resources/game_of_life/glider.png"); break; + case 1: pattern = LoadImage("resources/game_of_life/r_pentomino.png"); break; + case 2: pattern = LoadImage("resources/game_of_life/acorn.png"); break; + case 3: pattern = LoadImage("resources/game_of_life/spaceships.png"); break; + case 4: pattern = LoadImage("resources/game_of_life/still_lifes.png"); break; + case 5: pattern = LoadImage("resources/game_of_life/oscillators.png"); break; + case 6: pattern = LoadImage("resources/game_of_life/puffer_train.png"); break; + case 7: pattern = LoadImage("resources/game_of_life/glider_gun.png"); break; + case 8: pattern = LoadImage("resources/game_of_life/breeder.png"); break; + } BeginTextureMode(*currentWorld); ClearBackground(RAYWHITE); EndTextureMode(); From 2853b28d6d51049543383e0854bb332b9f6ad900 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 11 Dec 2025 18:21:57 +0100 Subject: [PATCH 221/430] REVIEWED: Avoid program crash if GPU data is tried to be loaded before `InitWindow()` #4751 Following raylib design, a warning log message is shown and program can continue execution. Some early return checks have been added on most critical functions. [rtext] Previous implementation checking `isGpuReady` cross-module variable is not needed any more, resulting in a more decoupled code, load failure is managed at rlgl level --- src/platforms/rcore_android.c | 2 -- src/rcore.c | 19 ++++++------- src/rlgl.h | 21 ++++++++++++--- src/rmodels.c | 4 +++ src/rtext.c | 51 +++++++++++++++-------------------- 5 files changed, 53 insertions(+), 44 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 88b3b4bba..0caa6f222 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -84,7 +84,6 @@ typedef struct { // Global Variables Definition //---------------------------------------------------------------------------------- extern CoreData CORE; // Global CORE state context -extern bool isGpuReady; // Flag to note GPU has been initialized successfully static PlatformData platform = { 0 }; // Platform specific data //---------------------------------------------------------------------------------- @@ -1042,7 +1041,6 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd) // Initialize OpenGL context (states and resources) // NOTE: CORE.Window.currentFbo.width and CORE.Window.currentFbo.height not used, just stored as globals in rlgl rlglInit(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); - isGpuReady = true; // Setup default viewport // NOTE: It updated CORE.Window.render.width and CORE.Window.render.height diff --git a/src/rcore.c b/src/rcore.c index dbc864020..72db47da6 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -387,11 +387,6 @@ RLAPI const char *raylib_version = RAYLIB_VERSION; // raylib version exported s CoreData CORE = { 0 }; // Global CORE state context -// Flag to note GPU acceleration is available, -// referenced from other modules to support GPU data loading -// NOTE: Useful to allow Texture, RenderTexture, Font.texture, Mesh.vaoId/vboId, Shader loading -bool isGpuReady = false; - #if defined(SUPPORT_SCREEN_CAPTURE) static int screenshotCounter = 0; // Screenshots counter #endif @@ -697,7 +692,6 @@ void InitWindow(int width, int height, const char *title) // Initialize rlgl default data (buffers and shaders) // NOTE: Current fbo size stored as globals in rlgl for convenience rlglInit(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); - isGpuReady = true; // Flag to note GPU has been initialized successfully // Setup default viewport SetupViewport(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); @@ -1266,7 +1260,14 @@ Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode) shader.id = rlLoadShaderCode(vsCode, fsCode); - if (shader.id == rlGetShaderIdDefault()) shader.locs = rlGetShaderLocsDefault(); + if (shader.id == 0) + { + // Shader could not be loaded but we still load the location points to avoid potential crashes + // NOTE: All locations set to -1 (no location) + shader.locs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int)); + for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; + } + else if (shader.id == rlGetShaderIdDefault()) shader.locs = rlGetShaderLocsDefault(); else if (shader.id > 0) { // After custom shader loading, we TRY to set default location names @@ -1282,9 +1283,9 @@ Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode) // NOTE: If any location is not found, loc point becomes -1 + // Load shader locations array + // NOTE: All locations set to -1 (no location) shader.locs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int)); - - // All locations reset to -1 (no location) for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; // Get handles to GLSL input attribute locations diff --git a/src/rlgl.h b/src/rlgl.h index 67bd90251..2124d0daf 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -1153,6 +1153,7 @@ static double rlCullDistanceFar = RL_CULL_DISTANCE_FAR; #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) static rlglData RLGL = { 0 }; #endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 +static bool isGpuReady = false; #if defined(GRAPHICS_API_OPENGL_ES2) && !defined(GRAPHICS_API_OPENGL_ES3) // NOTE: VAO functionality is exposed through extensions (OES) @@ -2283,6 +2284,8 @@ static void GLAPIENTRY rlDebugMessageCallback(GLenum source, GLenum type, GLuint // Initialize rlgl: OpenGL extensions, default buffers/shaders/textures, OpenGL states void rlglInit(int width, int height) { + isGpuReady = true; + // Enable OpenGL debug context if required #if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) && defined(GRAPHICS_API_OPENGL_43) if ((glDebugMessageCallback != NULL) && (glDebugMessageControl != NULL)) @@ -2395,6 +2398,7 @@ void rlglClose(void) #if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) swClose(); // Unload sofware renderer resources #endif + isGpuReady = false; } // Load OpenGL extensions @@ -2799,6 +2803,7 @@ int *rlGetShaderLocsDefault(void) rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements) { rlRenderBatch batch = { 0 }; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return batch; } #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // Initialize CPU (RAM) vertex buffers (position, texcoord, color data and indexes) @@ -3253,6 +3258,7 @@ bool rlCheckRenderBatchLimit(int vCount) unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount) { unsigned int id = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return id; } glBindTexture(GL_TEXTURE_2D, 0); // Free any old binding @@ -3411,6 +3417,7 @@ unsigned int rlLoadTexture(const void *data, int width, int height, int format, unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) { unsigned int id = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return id; } #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // In case depth textures not supported, we force renderbuffer usage @@ -3469,6 +3476,7 @@ unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount) { unsigned int id = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return id; } #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) int mipSize = size; @@ -3813,6 +3821,7 @@ unsigned char *rlReadScreenPixels(int width, int height) unsigned int rlLoadFramebuffer(void) { unsigned int fboId = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return fboId; } #if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) glGenFramebuffers(1, &fboId); // Create the framebuffer object @@ -3928,6 +3937,7 @@ void rlUnloadFramebuffer(unsigned int id) unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic) { unsigned int id = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return id; } #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) glGenBuffers(1, &id); @@ -3942,6 +3952,7 @@ unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic) unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic) { unsigned int id = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return id; } #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) glGenBuffers(1, &id); @@ -4107,12 +4118,12 @@ void rlDisableStatePointer(int vertexAttribType) unsigned int rlLoadVertexArray(void) { unsigned int vaoId = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return vaoId; } + #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.vao) - { - glGenVertexArrays(1, &vaoId); - } + if (RLGL.ExtSupported.vao) glGenVertexArrays(1, &vaoId); #endif + return vaoId; } @@ -4167,6 +4178,7 @@ void rlUnloadVertexBuffer(unsigned int vboId) unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) { unsigned int id = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return id; } #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) unsigned int vertexShaderId = 0; @@ -4309,6 +4321,7 @@ unsigned int rlCompileShader(const char *shaderCode, int type) unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) { unsigned int programId = 0; + if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return programId; } #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) GLint success = 0; diff --git a/src/rmodels.c b/src/rmodels.c index 98209add8..7458624fa 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -1282,6 +1282,8 @@ void UploadMesh(Mesh *mesh, bool dynamic) #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) mesh->vaoId = rlLoadVertexArray(); + if (mesh->vaoId == 0) return; + rlEnableVertexArray(mesh->vaoId); // NOTE: Vertex attributes must be uploaded considering default locations points and available vertex data @@ -1470,6 +1472,8 @@ void DrawMesh(Mesh mesh, Material material, Matrix transform) // Bind shader program rlEnableShader(material.shader.id); + if (material.shader.locs == NULL) return; + // Send required data to shader (matrices, values) //----------------------------------------------------- // Upload to shader material.colDiffuse diff --git a/src/rtext.c b/src/rtext.c index 74f68544e..994869b06 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -127,13 +127,12 @@ //---------------------------------------------------------------------------------- // Global variables //---------------------------------------------------------------------------------- -extern bool isGpuReady; #if defined(SUPPORT_DEFAULT_FONT) // Default font provided by raylib // NOTE: Default font is loaded on InitWindow() and disposed on CloseWindow() [module: core] static Font defaultFont = { 0 }; #endif -static int textLineSpacing = 2; // Text vertical line spacing in pixels (between lines) +static int textLineSpacing = 2; // Text vertical line spacing in pixels (between lines) //---------------------------------------------------------------------------------- // Other Modules Functions Declaration (required by text) @@ -164,8 +163,8 @@ extern void LoadFontDefault(void) { #define BIT_CHECK(a,b) ((a) & (1u << (b))) - // Check to see if we have allready allocated the font for an image, and if we don't need to upload, then just return - if ((defaultFont.glyphs != NULL) && !isGpuReady) return; + // Check to see if we have already allocated the font for an image, and if we don't need to upload, then just return + if (defaultFont.glyphs != NULL) return; // NOTE: Using UTF-8 encoding table for Unicode U+0000..U+00FF Basic Latin + Latin-1 Supplement // Ref: http://www.utf8-chartable.de/unicode-utf8-table.pl @@ -263,17 +262,14 @@ extern void LoadFontDefault(void) counter++; } - if (isGpuReady) - { - defaultFont.texture = LoadTextureFromImage(imFont); + defaultFont.texture = LoadTextureFromImage(imFont); - // we have already loaded the font glyph data an image, and the GPU is ready, we are done - // if we don't do this, we will leak memory by reallocating the glyphs and rects - if (defaultFont.glyphs != NULL) - { - UnloadImage(imFont); - return; - } + // we have already loaded the font glyph data an image, and the GPU is ready, we are done + // if we don't do this, we will leak memory by reallocating the glyphs and rects + if (defaultFont.glyphs != NULL) + { + UnloadImage(imFont); + return; } // Reconstruct charSet using charsWidth[], charsHeight, charsDivisor, glyphCount @@ -330,7 +326,7 @@ extern void LoadFontDefault(void) extern void UnloadFontDefault(void) { for (int i = 0; i < defaultFont.glyphCount; i++) UnloadImage(defaultFont.glyphs[i].image); - if (isGpuReady) UnloadTexture(defaultFont.texture); + UnloadTexture(defaultFont.texture); RL_FREE(defaultFont.glyphs); RL_FREE(defaultFont.recs); defaultFont.glyphCount = 0; @@ -384,17 +380,15 @@ Font LoadFont(const char *fileName) { Image image = LoadImage(fileName); if (image.data != NULL) font = LoadFontFromImage(image, MAGENTA, FONT_TTF_DEFAULT_FIRST_CHAR); + else font = GetFontDefault(); UnloadImage(image); } - if (isGpuReady) + if (font.texture.id == 0) TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load font texture -> Using default font", fileName); + else { - if (font.texture.id == 0) TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load font texture -> Using default font", fileName); - else - { - SetTextureFilter(font.texture, TEXTURE_FILTER_POINT); // By default, we set point filter (the best performance) - TRACELOG(LOG_INFO, "FONT: Data loaded successfully (%i pixel size | %i glyphs)", font.baseSize, font.glyphCount); - } + SetTextureFilter(font.texture, TEXTURE_FILTER_POINT); // By default, we set point filter (the best performance) + TRACELOG(LOG_INFO, "FONT: Data loaded successfully (%i pixel size | %i glyphs)", font.baseSize, font.glyphCount); } return font; @@ -515,7 +509,7 @@ Font LoadFontFromImage(Image image, Color key, int firstChar) }; // Set font with all data parsed from image - if (isGpuReady) font.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture + font.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture font.glyphCount = index; font.glyphPadding = 0; @@ -584,7 +578,7 @@ Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int font.glyphPadding = FONT_TTF_DEFAULT_CHARS_PADDING; Image atlas = GenImageFontAtlas(font.glyphs, &font.recs, font.glyphCount, font.baseSize, font.glyphPadding, 0); - if (isGpuReady) font.texture = LoadTextureFromImage(atlas); + font.texture = LoadTextureFromImage(atlas); // Update glyphs[i].image to use alpha, required to be used on ImageDrawText() for (int i = 0; i < font.glyphCount; i++) @@ -1008,7 +1002,7 @@ void UnloadFont(Font font) if (font.texture.id != GetFontDefault().texture.id) { UnloadFontData(font.glyphs, font.glyphCount); - if (isGpuReady) UnloadTexture(font.texture); + UnloadTexture(font.texture); RL_FREE(font.recs); TRACELOGD("FONT: Unloaded font data from RAM and VRAM"); @@ -1339,8 +1333,7 @@ Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing { Vector2 textSize = { 0 }; - if ((isGpuReady && (font.texture.id == 0)) || - (text == NULL) || (text[0] == '\0')) return textSize; // Security check + if ((font.texture.id == 0) || (text == NULL) || (text[0] == '\0')) return textSize; // Security check int size = TextLength(text); // Get size in bytes of text int tempByteCounter = 0; // Used to count longer text line num chars @@ -2481,7 +2474,7 @@ static Font LoadBMFont(const char *fileName) RL_FREE(imFonts); - if (isGpuReady) font.texture = LoadTextureFromImage(fullFont); + font.texture = LoadTextureFromImage(fullFont); // Fill font characters info data font.baseSize = fontSize; @@ -2523,7 +2516,7 @@ static Font LoadBMFont(const char *fileName) UnloadImage(fullFont); UnloadFileText(fileText); - if (isGpuReady && (font.texture.id == 0)) + if (font.texture.id == 0) { UnloadFont(font); font = GetFontDefault(); From b465b4e2eafea11d931130809ff2cc3592d1aed1 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 11 Dec 2025 21:41:25 +0100 Subject: [PATCH 222/430] RENAMED: Variable names for consistency, `textLength` (length in bytes) vs `textSize` (measure in pixels) --- src/raudio.c | 4 ++-- src/rcore.c | 46 +++++++++++++++++++++++----------------------- src/rlgl.h | 8 ++++---- src/rtext.c | 6 +++--- src/rtextures.c | 4 ++-- src/utils.c | 4 ++-- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/raudio.c b/src/raudio.c index 429a746eb..c65aaa134 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -2770,9 +2770,9 @@ static const char *GetFileNameWithoutExt(const char *filePath) if (filePath != NULL) strncpy(fileName, GetFileName(filePath), MAX_FILENAMEWITHOUTEXT_LENGTH - 1); // Get filename with extension - int size = (int)strlen(fileName); // Get size in bytes + int fileNameLength = (int)strlen(fileName); // Get size in bytes - for (int i = 0; (i < size) && (i < MAX_FILENAMEWITHOUTEXT_LENGTH); i++) + for (int i = 0; (i < fileNameLength) && (i < MAX_FILENAMEWITHOUTEXT_LENGTH); i++) { if (fileName[i] == '.') { diff --git a/src/rcore.c b/src/rcore.c index 72db47da6..dfe7b3398 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2009,10 +2009,10 @@ bool IsFileExtension(const char *fileName, const char *ext) if (fileExt != NULL) { - int fileExtLen = (int)strlen(fileExt); + int fileExtLength = (int)strlen(fileExt); char fileExtLower[16] = { 0 }; char *fileExtLowerPtr = fileExtLower; - for (int i = 0; (i < fileExtLen) && (i < 16); i++) + for (int i = 0; (i < fileExtLength) && (i < 16); i++) { // Copy and convert to lower-case if ((fileExt[i] >= 'A') && (fileExt[i] <= 'Z')) fileExtLower[i] = fileExt[i] + 32; @@ -2020,13 +2020,13 @@ bool IsFileExtension(const char *fileName, const char *ext) } int extCount = 1; - int extLen = (int)strlen(ext); - char *extList = (char *)RL_CALLOC(extLen + 1, 1); + int extLength = (int)strlen(ext); + char *extList = (char *)RL_CALLOC(extLength + 1, 1); char *extListPtrs[MAX_FILE_EXTENSIONS] = { 0 }; - strncpy(extList, ext, extLen); + strncpy(extList, ext, extLength); extListPtrs[0] = extList; - for (int i = 0; i < extLen; i++) + for (int i = 0; i < extLength; i++) { // Convert to lower-case if extension is upper-case if ((extList[i] >= 'A') && (extList[i] <= 'Z')) extList[i] += 32; @@ -2163,9 +2163,9 @@ const char *GetFileNameWithoutExt(const char *filePath) if (filePath != NULL) { strncpy(fileName, GetFileName(filePath), MAX_FILENAME_LENGTH - 1); // Get filename.ext without path - int size = (int)strlen(fileName); // Get size in bytes + int fileNameLenght = (int)strlen(fileName); // Get size in bytes - for (int i = size; i > 0; i--) // Reverse search '.' + for (int i = fileNameLenght; i > 0; i--) // Reverse search '.' { if (fileName[i] == '.') { @@ -2232,11 +2232,11 @@ const char *GetPrevDirectoryPath(const char *dirPath) { static char prevDirPath[MAX_FILEPATH_LENGTH] = { 0 }; memset(prevDirPath, 0, MAX_FILEPATH_LENGTH); - int pathLen = (int)strlen(dirPath); + int dirPathLength = (int)strlen(dirPath); - if (pathLen <= 3) strncpy(prevDirPath, dirPath, MAX_FILEPATH_LENGTH - 1); + if (dirPathLength <= 3) strncpy(prevDirPath, dirPath, MAX_FILEPATH_LENGTH - 1); - for (int i = (pathLen - 1); (i >= 0) && (pathLen > 3); i--) + for (int i = (dirPathLength - 1); (i >= 0) && (dirPathLength > 3); i--) { if ((dirPath[i] == '\\') || (dirPath[i] == '/')) { @@ -2323,8 +2323,8 @@ const char *GetApplicationDirectory(void) if (_NSGetExecutablePath(appDir, &size) == 0) { - int len = strlen(appDir); - for (int i = len; i >= 0; --i) + int appDirLength = (int)strlen(appDir); + for (int i = appDirLength; i >= 0; --i) { if (appDir[i] == '/') { @@ -2346,8 +2346,8 @@ const char *GetApplicationDirectory(void) if (sysctl(mib, 4, appDir, &size, NULL, 0) == 0) { - int len = strlen(appDir); - for (int i = len; i >= 0; --i) + int appDirLength = (int)strlen(appDir); + for (int i = appDirLength; i >= 0; --i) { if (appDir[i] == '/') { @@ -2442,12 +2442,12 @@ int MakeDirectory(const char *dirPath) if (DirectoryExists(dirPath)) return 0; // Path already exists (is valid) // Copy path string to avoid modifying original - int len = (int)strlen(dirPath) + 1; - char *pathcpy = (char *)RL_CALLOC(len, 1); - memcpy(pathcpy, dirPath, len); + int dirPathLength = (int)strlen(dirPath) + 1; + char *pathcpy = (char *)RL_CALLOC(dirPathLength, 1); + memcpy(pathcpy, dirPath, dirPathLength); // Iterate over pathcpy, create each subdirectory as needed - for (int i = 0; (i < len) && (pathcpy[i] != '\0'); i++) + for (int i = 0; (i < dirPathLength) && (pathcpy[i] != '\0'); i++) { if (pathcpy[i] == ':') i++; else @@ -2499,10 +2499,10 @@ bool IsFileNameValid(const char *fileName) if ((fileName != NULL) && (fileName[0] != '\0')) { - int length = (int)strlen(fileName); + int fileNameLength = (int)strlen(fileName); bool allPeriods = true; - for (int i = 0; i < length; i++) + for (int i = 0; i < fileNameLength; i++) { // Check invalid characters if ((fileName[i] == '<') || @@ -2528,7 +2528,7 @@ bool IsFileNameValid(const char *fileName) if (valid) { // Check invalid DOS names - if (length >= 3) + if (fileNameLength >= 3) { if (((fileName[0] == 'C') && (fileName[1] == 'O') && (fileName[2] == 'N')) || // CON ((fileName[0] == 'P') && (fileName[1] == 'R') && (fileName[2] == 'N')) || // PRN @@ -2536,7 +2536,7 @@ bool IsFileNameValid(const char *fileName) ((fileName[0] == 'N') && (fileName[1] == 'U') && (fileName[2] == 'L'))) valid = false; // NUL } - if (length >= 4) + if (fileNameLength >= 4) { if (((fileName[0] == 'C') && (fileName[1] == 'O') && (fileName[2] == 'M') && ((fileName[3] >= '0') && (fileName[3] <= '9'))) || // COM0-9 ((fileName[0] == 'L') && (fileName[1] == 'P') && (fileName[2] == 'T') && ((fileName[3] >= '0') && (fileName[3] <= '9')))) valid = false; // LPT0-9 diff --git a/src/rlgl.h b/src/rlgl.h index 2124d0daf..c294e0e27 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -2496,12 +2496,12 @@ void rlLoadExtensions(void *loader) const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string // NOTE: We have to duplicate string because glGetString() returns a const string - int extSize = (int)strlen(extensions); // Get extensions string size in bytes - char *extensionsDup = (char *)RL_CALLOC(extSize + 1, sizeof(char)); // Allocate space for copy with additional EOL byte - strncpy(extensionsDup, extensions, extSize); + int extensionsLength = (int)strlen(extensions); // Get extensions string size in bytes + char *extensionsDup = (char *)RL_CALLOC(extensionsLength + 1, sizeof(char)); // Allocate space for copy with additional EOL byte + strncpy(extensionsDup, extensions, extensionsLength); extList[numExt] = extensionsDup; - for (int i = 0; i < extSize; i++) + for (int i = 0; i < extensionsLength; i++) { if (extensionsDup[i] == ' ') { diff --git a/src/rtext.c b/src/rtext.c index 994869b06..53e2c0aa5 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1449,17 +1449,17 @@ char **LoadTextLines(const char *text, int *count) if (text != NULL) { - int textSize = TextLength(text); + int textLength = TextLength(text); lineCount = 1; // First text scan pass to get required line count - for (int i = 0; i < textSize; i++) + for (int i = 0; i < textLength; i++) { if (text[i] == '\n') lineCount++; } lines = (char **)RL_CALLOC(lineCount, sizeof(char *)); - for (int i = 0, l = 0, lineLen = 0; i <= textSize; i++) + for (int i = 0, l = 0, lineLen = 0; i <= textLength; i++) { if ((text[i] == '\n') || (text[i] == '\0')) { diff --git a/src/rtextures.c b/src/rtextures.c index 24f21e333..9ec2c33a0 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -1488,7 +1488,7 @@ Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Co #if defined(SUPPORT_MODULE_RTEXT) if (text == NULL) return imText; - int size = (int)strlen(text); // Get size in bytes of text + int textLength = (int)strlen(text); // Get length of text in bytes int textOffsetX = 0; // Image drawing position X int textOffsetY = 0; // Offset between lines (on linebreak '\n') @@ -1499,7 +1499,7 @@ Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Co // Create image to store text imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK); - for (int i = 0; i < size;) + for (int i = 0; i < textLength;) { // Get next codepoint from byte string and glyph index in font int codepointByteCount = 0; diff --git a/src/utils.c b/src/utils.c index 44facea3a..15161b443 100644 --- a/src/utils.c +++ b/src/utils.c @@ -142,8 +142,8 @@ void TraceLog(int logType, const char *text, ...) default: break; } - unsigned int textSize = (unsigned int)strlen(text); - memcpy(buffer + strlen(buffer), text, (textSize < (MAX_TRACELOG_MSG_LENGTH - 12))? textSize : (MAX_TRACELOG_MSG_LENGTH - 12)); + unsigned int textLength = (unsigned int)strlen(text); + memcpy(buffer + strlen(buffer), text, (textLength < (MAX_TRACELOG_MSG_LENGTH - 12))? textLength : (MAX_TRACELOG_MSG_LENGTH - 12)); strcat(buffer, "\n"); vprintf(buffer, args); fflush(stdout); From 9c04b1de822d1adfa9353c2d9ff62d9b53713246 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 13 Dec 2025 11:58:04 +0100 Subject: [PATCH 223/430] REVIEWED: Store canvas name id at platform initialization Useful to support multiple canvases running different wasm instances in same webpage --- src/platforms/rcore_web.c | 67 +++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 38 deletions(-) diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index 2d2f8d1c0..7324e121e 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -76,6 +76,8 @@ typedef struct { bool ourFullscreen; // Internal var to filter our handling of fullscreen vs the user handling of fullscreen int unmaximizedWidth; // Internal var to store the unmaximized window (canvas) width int unmaximizedHeight; // Internal var to store the unmaximized window (canvas) height + + char canvasId[64]; // Keep current canvas id where wasm app is running } PlatformData; //---------------------------------------------------------------------------------- @@ -142,7 +144,11 @@ static EM_BOOL EmscriptenPointerlockCallback(int eventType, const EmscriptenPoin static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData); -static const char *GetCanvasId(void); +// JS: Set the canvas id provided by the module configuration +EM_JS(void, SetCanvasIdJs, (char *out, int outSize), { + var canvasId = "#" + Module.canvas.id; + stringToUTF8(canvasId, out, outSize); +}); //---------------------------------------------------------------------------------- // Module Functions Declaration @@ -233,7 +239,7 @@ void ToggleFullscreen(void) // This option does not seem to work at all: // emscripten_request_pointerlock() and emscripten_request_fullscreen() are affected by web security, // the user must click once on the canvas to hide the pointer or transition to full screen - //emscripten_request_fullscreen("#canvas", false); + //emscripten_request_fullscreen(platform.canvasId, false); // Option 2: Request fullscreen for the canvas element with strategy // This option does not seem to work at all @@ -245,7 +251,7 @@ void ToggleFullscreen(void) // .canvasResizedCallback = EmscriptenWindowResizedCallback, // .canvasResizedCallbackUserData = NULL // }; - //emscripten_request_fullscreen_strategy("#canvas", EM_FALSE, &strategy); + //emscripten_request_fullscreen_strategy(platform.canvasId, EM_FALSE, &strategy); // Option 3: Request fullscreen for the canvas element with strategy // It works as expected but only inside the browser (client area) @@ -256,10 +262,10 @@ void ToggleFullscreen(void) .canvasResizedCallback = EmscriptenWindowResizedCallback, .canvasResizedCallbackUserData = NULL }; - emscripten_enter_soft_fullscreen("#canvas", &strategy); + emscripten_enter_soft_fullscreen(platform.canvasId, &strategy); int width, height; - emscripten_get_canvas_element_size("#canvas", &width, &height); + emscripten_get_canvas_element_size(platform.canvasId, &width, &height); TRACELOG(LOG_WARNING, "Emscripten: Enter fullscreen: Canvas size: %i x %i", width, height); CORE.Window.fullscreen = true; // Toggle fullscreen flag @@ -271,7 +277,7 @@ void ToggleFullscreen(void) //emscripten_exit_soft_fullscreen(); int width, height; - emscripten_get_canvas_element_size("#canvas", &width, &height); + emscripten_get_canvas_element_size(platform.canvasId, &width, &height); TRACELOG(LOG_WARNING, "Emscripten: Exit fullscreen: Canvas size: %i x %i", width, height); CORE.Window.fullscreen = false; // Toggle fullscreen flag @@ -866,7 +872,7 @@ void EnableCursor(void) // Disables cursor (lock cursor) void DisableCursor(void) { - emscripten_request_pointerlock(GetCanvasId(), 1); + emscripten_request_pointerlock(platform.canvasId, 1); // Set cursor position in the middle SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); @@ -1097,6 +1103,8 @@ void PollInputEvents(void) // Initialize platform: graphics, inputs and more int InitPlatform(void) { + SetCanvasIdJs(platform.canvasId, 64); // Get the current canvas id + glfwSetErrorCallback(ErrorCallback); // Initialize GLFW internal global state @@ -1347,14 +1355,14 @@ int InitPlatform(void) //---------------------------------------------------------------------------- // Setup window events callbacks emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenFullscreenChangeCallback); - emscripten_set_blur_callback(GetCanvasId(), platform.handle, 1, EmscriptenFocusCallback); - emscripten_set_focus_callback(GetCanvasId(), platform.handle, 1, EmscriptenFocusCallback); + emscripten_set_blur_callback(platform.canvasId, platform.handle, 1, EmscriptenFocusCallback); + emscripten_set_focus_callback(platform.canvasId, platform.handle, 1, EmscriptenFocusCallback); emscripten_set_visibilitychange_callback(NULL, 1, EmscriptenVisibilityChangeCallback); // WARNING: Below resize code was breaking fullscreen mode for sample games and examples, it needs review - // Check fullscreen change events(note this is done on the window since most browsers don't support this on #canvas) + // Check fullscreen change events(note this is done on the window since most browsers don't support this on canvas) // emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback); - // Check Resize event (note this is done on the window since most browsers don't support this on #canvas) + // Check Resize event (note this is done on the window since most browsers don't support this on canvas) emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback); // Trigger resize callback to force initial size @@ -1362,15 +1370,15 @@ int InitPlatform(void) // Setup input events // NOTE: Keyboard callbacks only used to consume some events, libglfw.js takes care of the actual input - //emscripten_set_keypress_callback(GetCanvasId(), NULL, 1, EmscriptenKeyboardCallback); // WRNING: Breaks input - //emscripten_set_keydown_callback(GetCanvasId(), NULL, 1, EmscriptenKeyboardCallback); - emscripten_set_click_callback(GetCanvasId(), NULL, 1, EmscriptenMouseCallback); + //emscripten_set_keypress_callback(platform.canvasId, NULL, 1, EmscriptenKeyboardCallback); // WRNING: Breaks input + //emscripten_set_keydown_callback(platform.canvasId, NULL, 1, EmscriptenKeyboardCallback); + emscripten_set_click_callback(platform.canvasId, NULL, 1, EmscriptenMouseCallback); emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenPointerlockCallback); - emscripten_set_mousemove_callback(GetCanvasId(), NULL, 1, EmscriptenMouseMoveCallback); - emscripten_set_touchstart_callback(GetCanvasId(), NULL, 1, EmscriptenTouchCallback); - emscripten_set_touchend_callback(GetCanvasId(), NULL, 1, EmscriptenTouchCallback); - emscripten_set_touchmove_callback(GetCanvasId(), NULL, 1, EmscriptenTouchCallback); - emscripten_set_touchcancel_callback(GetCanvasId(), NULL, 1, EmscriptenTouchCallback); + emscripten_set_mousemove_callback(platform.canvasId, NULL, 1, EmscriptenMouseMoveCallback); + emscripten_set_touchstart_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchend_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchmove_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchcancel_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); emscripten_set_gamepadconnected_callback(NULL, 1, EmscriptenGamepadCallback); emscripten_set_gamepaddisconnected_callback(NULL, 1, EmscriptenGamepadCallback); //---------------------------------------------------------------------------- @@ -1691,7 +1699,7 @@ static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent // NOTE: emscripten_get_canvas_element_size() returns canvas.width and canvas.height but // we are looking for actual CSS size: canvas.style.width and canvas.style.height // EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight); - emscripten_get_element_css_size(GetCanvasId(), &canvasWidth, &canvasHeight); + emscripten_get_element_css_size(platform.canvasId, &canvasWidth, &canvasHeight); for (int i = 0; (i < CORE.Input.Touch.pointCount) && (i < MAX_TOUCH_POINTS); i++) { @@ -1802,7 +1810,7 @@ static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent * if (height < (int)CORE.Window.screenMin.height) height = CORE.Window.screenMin.height; else if ((height > (int)CORE.Window.screenMax.height) && (CORE.Window.screenMax.height > 0)) height = CORE.Window.screenMax.height; - emscripten_set_canvas_element_size(GetCanvasId(), width, height); + emscripten_set_canvas_element_size(platform.canvasId, width, height); SetupViewport(width, height); // Reset viewport and projection matrix for new size @@ -1845,21 +1853,4 @@ static EM_BOOL EmscriptenVisibilityChangeCallback(int eventType, const Emscripte } //------------------------------------------------------------------------------------------------------- -// JS: Get the canvas id provided by the module configuration -EM_JS(char *, GetCanvasIdJs, (), { - var canvasId = "#" + Module.canvas.id; - var lengthBytes = lengthBytesUTF8(canvasId) + 1; - var stringOnWasmHeap = _malloc(lengthBytes); - stringToUTF8(canvasId, stringOnWasmHeap, lengthBytes); - return stringOnWasmHeap; -}); - -// Get canvas id (using embedded JS function) -static const char *GetCanvasId(void) -{ - static char *canvasId = NULL; - if (canvasId == NULL) canvasId = GetCanvasIdJs(); - return canvasId; -} - // EOF From c96669e1238830b5af04aab705aa83cd6d6c6876 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 13 Dec 2025 13:03:41 +0100 Subject: [PATCH 224/430] REVIEWED: Webpage reference comments starting with `REF:`, more consistent with `TODO:` and `NOTE:` comments --- src/platforms/rcore_android.c | 4 ++-- src/platforms/rcore_desktop_glfw.c | 12 ++++++------ src/platforms/rcore_desktop_sdl.c | 4 ++-- src/platforms/rcore_desktop_win32.c | 2 +- src/platforms/rcore_drm.c | 4 ++-- src/platforms/rcore_memory.c | 2 +- src/platforms/rcore_web.c | 10 +++++----- src/rcore.c | 4 ++-- src/rlgl.h | 4 ++-- src/rmodels.c | 4 ++-- src/rtext.c | 2 +- src/rtextures.c | 4 ++-- src/utils.c | 4 ++-- 13 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 0caa6f222..bc8a25f8f 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -1162,8 +1162,8 @@ static GamepadButton AndroidTranslateGamepadButton(int button) static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) { // If additional inputs are required check: - // Ref: https://developer.android.com/ndk/reference/group/input - // Ref: https://developer.android.com/training/game-controllers/controller-input + // REF: https://developer.android.com/ndk/reference/group/input + // REF: https://developer.android.com/training/game-controllers/controller-input int type = AInputEvent_getType(event); int source = AInputEvent_getSource(event); diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 211e0f701..8bd4b3a69 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1122,7 +1122,7 @@ double GetTime(void) // NOTE: This function is only safe to use if you control the URL given // A user could craft a malicious string performing another action // Only call this function yourself not with user input or make sure to check the string yourself -// Ref: https://github.com/raysan5/raylib/issues/686 +// REF: https://github.com/raysan5/raylib/issues/686 void OpenURL(const char *url) { // Security check to (partially) avoid malicious code @@ -1234,8 +1234,8 @@ void PollInputEvents(void) // Map touch position to mouse position for convenience // WARNING: If the target desktop device supports touch screen, this behaviour should be reviewed! // TODO: GLFW does not support multi-touch input yet - // Ref: https://www.codeproject.com/Articles/668404/Programming-for-Multi-Touch - // Ref: https://docs.microsoft.com/en-us/windows/win32/wintouch/getting-started-with-multi-touch-messages + // REF: https://www.codeproject.com/Articles/668404/Programming-for-Multi-Touch + // REF: https://docs.microsoft.com/en-us/windows/win32/wintouch/getting-started-with-multi-touch-messages CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; // Check if gamepads are ready @@ -1347,7 +1347,7 @@ void PollInputEvents(void) // Function wrappers around RL_*alloc macros, used by glfwInitAllocator() inside of InitPlatform() // We need to provide these because GLFWallocator expects function pointers with specific signatures // Similar wrappers exist in utils.c but we cannot reuse them here due to declaration mismatch -// Ref: https://www.glfw.org/docs/latest/intro_guide.html#init_allocator +// REF: https://www.glfw.org/docs/latest/intro_guide.html#init_allocator static void *AllocateWrapper(size_t size, void *user) { (void)user; @@ -1945,8 +1945,8 @@ static void CharCallback(GLFWwindow *window, unsigned int codepoint) { // NOTE: Registers any key down considering OS keyboard layout but // does not detect action events, those should be managed by user... - // Ref: https://github.com/glfw/glfw/issues/668#issuecomment-166794907 - // Ref: https://www.glfw.org/docs/latest/input_guide.html#input_char + // REF: https://github.com/glfw/glfw/issues/668#issuecomment-166794907 + // REF: https://www.glfw.org/docs/latest/input_guide.html#input_char // Check if there is space available in the queue if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 36235a6c8..995336ec0 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -1129,7 +1129,7 @@ Vector2 GetWindowScaleDPI(void) #ifndef USING_VERSION_SDL3 // NOTE: SDL_GetWindowDisplayScale was only added on SDL3 - // Ref: https://wiki.libsdl.org/SDL3/SDL_GetWindowDisplayScale + // REF: https://wiki.libsdl.org/SDL3/SDL_GetWindowDisplayScale // TODO: Implement the window scale factor calculation manually TRACELOG(LOG_WARNING, "GetWindowScaleDPI() not implemented on target platform"); #else @@ -1279,7 +1279,7 @@ double GetTime(void) // NOTE: This function is only safe to use if you control the URL given // A user could craft a malicious string performing another action // Only call this function yourself not with user input or make sure to check the string yourself -// Ref: https://github.com/raysan5/raylib/issues/686 +// REF: https://github.com/raysan5/raylib/issues/686 void OpenURL(const char *url) { // Security check to (partially) avoid malicious code diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 8a4050332..29702921f 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -1239,7 +1239,7 @@ double GetTime(void) // NOTE: This function is only safe to use if you control the URL given // A user could craft a malicious string performing another action // Only call this function yourself not with user input or make sure to check the string yourself -// Ref: https://github.com/raysan5/raylib/issues/686 +// REF: https://github.com/raysan5/raylib/issues/686 void OpenURL(const char *url) { // Security check to (partially) avoid malicious code on target platform diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 68d5b9685..640799b0a 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -220,7 +220,7 @@ static const short linuxToRaylibMap[KEYMAP_SIZE] = { 248, 0, 0, 0, 0, 0, 0, 0, // Gamepads are mapped according to: - // Ref: https://www.kernel.org/doc/html/next/input/gamepad.html + // REF: https://www.kernel.org/doc/html/next/input/gamepad.html // Those mappings are standardized, but that doesn't mean people follow // the standards, so this is more of an approximation [BTN_DPAD_UP] = GAMEPAD_BUTTON_LEFT_FACE_UP, @@ -1013,7 +1013,7 @@ double GetTime(void) // NOTE: This function is only safe to use if you control the URL given // A user could craft a malicious string performing another action // Only call this function yourself not with user input or make sure to check the string yourself -// Ref: https://github.com/raysan5/raylib/issues/686 +// REF: https://github.com/raysan5/raylib/issues/686 void OpenURL(const char *url) { TRACELOG(LOG_WARNING, "OpenURL() not implemented on target platform"); diff --git a/src/platforms/rcore_memory.c b/src/platforms/rcore_memory.c index f78b72fed..5a3947561 100644 --- a/src/platforms/rcore_memory.c +++ b/src/platforms/rcore_memory.c @@ -383,7 +383,7 @@ double GetTime(void) // NOTE: This function is only safe to use if you control the URL given. // A user could craft a malicious string performing another action. // Only call this function yourself not with user input or make sure to check the string yourself. -// Ref: https://github.com/raysan5/raylib/issues/686 +// REF: https://github.com/raysan5/raylib/issues/686 void OpenURL(const char *url) { // Security check to (partially) avoid malicious code on target platform diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index 7324e121e..dc779d0fb 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -131,7 +131,7 @@ static void MouseEnterCallback(GLFWwindow *window, int enter); // Emscripten window callback events static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); -// static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData); +//static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData); static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData); static EM_BOOL EmscriptenFocusCallback(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData); static EM_BOOL EmscriptenVisibilityChangeCallback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData); @@ -165,7 +165,7 @@ EM_JS(void, SetCanvasIdJs, (char *out, int outSize), { bool WindowShouldClose(void) { // Emscripten Asyncify is required to run synchronous code in asynchronous JS - // Ref: https://emscripten.org/docs/porting/asyncify.html + // REF: https://emscripten.org/docs/porting/asyncify.html // WindowShouldClose() is not called on a web-ready raylib application if using emscripten_set_main_loop() // and encapsulating one frame execution on a UpdateDrawFrame() function, @@ -243,7 +243,7 @@ void ToggleFullscreen(void) // Option 2: Request fullscreen for the canvas element with strategy // This option does not seem to work at all - // Ref: https://github.com/emscripten-core/emscripten/issues/5124 + // REF: https://github.com/emscripten-core/emscripten/issues/5124 // EmscriptenFullscreenStrategy strategy = { // .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH, //EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, // .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, @@ -1520,8 +1520,8 @@ static void CharCallback(GLFWwindow *window, unsigned int key) // NOTE: Registers any key down considering OS keyboard layout but // does not detect action events, those should be managed by user... - // Ref: https://github.com/glfw/glfw/issues/668#issuecomment-166794907 - // Ref: https://www.glfw.org/docs/latest/input_guide.html#input_char + // REF: https://github.com/glfw/glfw/issues/668#issuecomment-166794907 + // REF: https://www.glfw.org/docs/latest/input_guide.html#input_char // Check if there is space available in the queue if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE) diff --git a/src/rcore.c b/src/rcore.c index dfe7b3398..24ebb6dec 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -1660,8 +1660,8 @@ float GetFrameTime(void) // Wait for some time (stop program execution) // NOTE: Sleep() granularity could be around 10 ms, it means, Sleep() could // take longer than expected... for that reason we use the busy wait loop -// Ref: http://stackoverflow.com/questions/43057578/c-programming-win32-games-sleep-taking-longer-than-expected -// Ref: http://www.geisswerks.com/ryan/FAQS/timing.html --> All about timing on Win32! +// REF: http://stackoverflow.com/questions/43057578/c-programming-win32-games-sleep-taking-longer-than-expected +// REF: http://www.geisswerks.com/ryan/FAQS/timing.html --> All about timing on Win32! void WaitTime(double seconds) { if (seconds < 0) return; // Security check diff --git a/src/rlgl.h b/src/rlgl.h index c294e0e27..b5955e613 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -3914,7 +3914,7 @@ void rlUnloadFramebuffer(unsigned int id) // TODO: Review warning retrieving object name in WebGL // WARNING: WebGL: INVALID_ENUM: getFramebufferAttachmentParameter: invalid parameter name - // Ref: https://registry.khronos.org/webgl/specs/latest/1.0/ + // REF: https://registry.khronos.org/webgl/specs/latest/1.0/ glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthId); unsigned int depthIdU = (unsigned int)depthId; @@ -4485,7 +4485,7 @@ void rlSetUniformMatrices(int locIndex, const Matrix *matrices, int count) glUniformMatrix4fv(locIndex, count, true, (const float *)matrices); #elif defined(GRAPHICS_API_OPENGL_ES2) // WARNING: WebGL does not support Matrix transpose ("true" parameter) - // Ref: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniformMatrix + // REF: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniformMatrix glUniformMatrix4fv(locIndex, count, false, (const float *)matrices); #endif } diff --git a/src/rmodels.c b/src/rmodels.c index 7458624fa..1502e46af 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -5998,8 +5998,8 @@ static Model LoadGLTF(const char *fileName) //---------------------------------------------------------------------------------------------------- // Load animation data - // Ref: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skins - // Ref: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skinned-mesh-attributes + // REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skins + // REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skinned-mesh-attributes // // LIMITATIONS: // - Only supports 1 armature per file, and skips loading it if there are multiple armatures diff --git a/src/rtext.c b/src/rtext.c index 53e2c0aa5..0efd7504b 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -167,7 +167,7 @@ extern void LoadFontDefault(void) if (defaultFont.glyphs != NULL) return; // NOTE: Using UTF-8 encoding table for Unicode U+0000..U+00FF Basic Latin + Latin-1 Supplement - // Ref: http://www.utf8-chartable.de/unicode-utf8-table.pl + // REF: http://www.utf8-chartable.de/unicode-utf8-table.pl defaultFont.glyphCount = 224; // Number of glyphs included in our default font defaultFont.glyphPadding = 0; // Characters padding diff --git a/src/rtextures.c b/src/rtextures.c index 9ec2c33a0..02a9ff1a5 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -4203,7 +4203,7 @@ TextureCubemap LoadTextureCubemap(Image image, int layout) /*else if (layout == CUBEMAP_LAYOUT_PANORAMA) { // TODO: Implement panorama by converting image to square faces... - // Ref: https://github.com/denivip/panorama/blob/master/panorama.cpp + // REF: https://github.com/denivip/panorama/blob/master/panorama.cpp } */ else { @@ -5410,7 +5410,7 @@ int GetPixelDataSize(int width, int height, int format) // Module Internal Functions Definition //---------------------------------------------------------------------------------- // Convert half-float (stored as unsigned short) to float -// Ref: https://stackoverflow.com/questions/1659440/32-bit-to-16-bit-floating-point-conversion/60047308#60047308 +// REF: https://stackoverflow.com/questions/1659440/32-bit-to-16-bit-floating-point-conversion/60047308#60047308 static float HalfToFloat(unsigned short x) { float result = 0.0f; diff --git a/src/utils.c b/src/utils.c index 15161b443..09158893a 100644 --- a/src/utils.c +++ b/src/utils.c @@ -446,7 +446,7 @@ void InitAssetManager(AAssetManager *manager, const char *dataPath) } // Replacement for fopen() -// Ref: https://developer.android.com/ndk/reference/group/asset +// REF: https://developer.android.com/ndk/reference/group/asset FILE *android_fopen(const char *fileName, const char *mode) { if (mode[0] == 'w') @@ -454,7 +454,7 @@ FILE *android_fopen(const char *fileName, const char *mode) // NOTE: fopen() is mapped to android_fopen() that only grants read access to // assets directory through AAssetManager but we want to also be able to // write data when required using the standard stdio FILE access functions - // Ref: https://stackoverflow.com/questions/11294487/android-writing-saving-files-from-native-code-only + // REF: https://stackoverflow.com/questions/11294487/android-writing-saving-files-from-native-code-only #undef fopen return fopen(TextFormat("%s/%s", internalDataPath, fileName), mode); #define fopen(name, mode) android_fopen(name, mode) From 6f5cabf60ceb60c44e7c7b5f1b1db9e89f1da9a3 Mon Sep 17 00:00:00 2001 From: Kaluub <60589762+Kaluub@users.noreply.github.com> Date: Sun, 14 Dec 2025 11:43:54 -0500 Subject: [PATCH 225/430] Fix misleading example text. (#5409) --- examples/core/core_2d_camera.c | 4 ++-- examples/core/core_2d_camera.png | Bin 21470 -> 8530 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/core/core_2d_camera.c b/examples/core/core_2d_camera.c index b752a9640..7e5a14a70 100644 --- a/examples/core/core_2d_camera.c +++ b/examples/core/core_2d_camera.c @@ -125,8 +125,8 @@ int main(void) DrawRectangle( 10, 10, 250, 113, Fade(SKYBLUE, 0.5f)); DrawRectangleLines( 10, 10, 250, 113, BLUE); - DrawText("Free 2d camera controls:", 20, 20, 10, BLACK); - DrawText("- Right/Left to move Offset", 40, 40, 10, DARKGRAY); + DrawText("Free 2D camera controls:", 20, 20, 10, BLACK); + DrawText("- Right/Left to move player", 40, 40, 10, DARKGRAY); DrawText("- Mouse Wheel to Zoom in-out", 40, 60, 10, DARKGRAY); DrawText("- A / S to Rotate", 40, 80, 10, DARKGRAY); DrawText("- R to reset Zoom and Rotation", 40, 100, 10, DARKGRAY); diff --git a/examples/core/core_2d_camera.png b/examples/core/core_2d_camera.png index d2f9e634f6fc1798e8466900973fb961f0133c9a..a0fdabb497a870bf7abd19a273b0d57b984d57a6 100644 GIT binary patch literal 8530 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 literal 21470 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 From 50250098608897e7daa78b7d58da281dbb83eb59 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 14 Dec 2025 19:45:28 +0100 Subject: [PATCH 226/430] REVIEWED: Make sure all variables are initialized on definition, prioritize one line per variable definitions --- src/external/rlsw.h | 8 +++--- src/platforms/rcore_desktop_glfw.c | 3 +- src/platforms/rcore_memory.c | 2 +- src/rmodels.c | 8 +++--- src/rtext.c | 14 +++++++-- src/rtextures.c | 46 ++++++++++++++++++++---------- 6 files changed, 54 insertions(+), 27 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index 025216e39..80fb02c4f 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -2217,7 +2217,7 @@ static inline bool sw_triangle_face_culling(void) const float *h2 = RLSW.vertexBuffer[2].homogeneous; // Compute a value proportional to the signed area in the projected 2D plane, - // calculated directly using homogeneous coordinates BEFORE division by w. + // calculated directly using homogeneous coordinates BEFORE division by w // This is the determinant of the matrix formed by the (x, y, w) components // of the vertices, which correctly captures the winding order in homogeneous // space and its relationship to the projected 2D winding order, even with @@ -2235,13 +2235,13 @@ static inline bool sw_triangle_face_culling(void) // Discard the triangle if its winding order (determined by the sign // of the homogeneous area/determinant) matches the culled direction // A positive hSgnArea typically corresponds to a counter-clockwise - // winding in the projected space when all w > 0. + // winding in the projected space when all w > 0 // This test is robust for points with w > 0 or w < 0, correctly // capturing the change in orientation when crossing the w=0 plane // The culling logic remains the same based on the signed area/determinant // A value of 0 for hSgnArea means the points are collinear in (x, y, w) - // space, which corresponds to a degenerate triangle projection. + // space, which corresponds to a degenerate triangle projection // Such triangles are typically not culled by this test (0 < 0 is false, 0 > 0 is false) // and should be handled by the clipper if necessary return (RLSW.cullFace == SW_FRONT)? (hSgnArea < 0) : (hSgnArea > 0); // Cull if winding is "clockwise" : "counter-clockwise" @@ -2602,7 +2602,7 @@ static inline bool sw_quad_face_culling(void) // space, which corresponds to a degenerate triangle projection // Such quads might also be degenerate or non-planar. They are typically // not culled by this test (0 < 0 is false, 0 > 0 is false) - // and should be handled by the clipper if necessary. + // and should be handled by the clipper if necessary return (RLSW.cullFace == SW_FRONT)? (hSgnArea < 0.0f) : (hSgnArea > 0.0f); // Cull if winding is "clockwise" : "counter-clockwise" } diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 8bd4b3a69..d6ed11c2f 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -894,7 +894,8 @@ Vector2 GetMonitorPosition(int monitor) if ((monitor >= 0) && (monitor < monitorCount)) { - int x, y; + int x = 0; + int y = 0; glfwGetMonitorPos(monitors[monitor], &x, &y); return (Vector2){ (float)x, (float)y }; diff --git a/src/platforms/rcore_memory.c b/src/platforms/rcore_memory.c index 5a3947561..1b7a55fd8 100644 --- a/src/platforms/rcore_memory.c +++ b/src/platforms/rcore_memory.c @@ -428,7 +428,7 @@ void SetMouseCursor(int cursor) TRACELOG(LOG_WARNING, "SetMouseCursor() not implemented on target platform"); } -// Get physical key name. +// Get physical key name const char *GetKeyName(int key) { TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform"); diff --git a/src/rmodels.c b/src/rmodels.c index 1502e46af..883f98660 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -3655,7 +3655,7 @@ void GenMeshTangents(Mesh *mesh) for (int t = 0; t < mesh->triangleCount; t++) { // Get triangle vertex indices - int i0, i1, i2; + int i0 = 0, i1 = 0, i2 = 0; if (mesh->indices != NULL) { @@ -4150,7 +4150,7 @@ RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform) // Test against all triangles in mesh for (int i = 0; i < triangleCount; i++) { - Vector3 a, b, c; + Vector3 a = 0, b = 0, c = 0; Vector3 *vertdata = (Vector3 *)mesh.vertices; if (mesh.indices) @@ -4193,8 +4193,8 @@ RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3 RayCollision collision = { 0 }; Vector3 edge1 = { 0 }; Vector3 edge2 = { 0 }; - Vector3 p, q, tv; - float det, invDet, u, v, t; + Vector3 p = 0, q = 0, tv = 0; + float det = 0.0f, invDet = 0.0f, u = 0.0f, v = 0.0f, t = 0.0f; // Find vectors for two edges sharing V1 edge1 = Vector3Subtract(p2, p1); diff --git a/src/rtext.c b/src/rtext.c index 0efd7504b..f04f9ade4 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -649,7 +649,9 @@ GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSiz // Calculate font basic metrics // NOTE: ascent is equivalent to font baseline - int ascent, descent, lineGap; + int ascent = 0; + int descent = 0; + int lineGap = 0; stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap); // In case no chars count provided, default to 95 @@ -2483,7 +2485,15 @@ static Font LoadBMFont(const char *fileName) font.glyphs = (GlyphInfo *)RL_MALLOC(glyphCount*sizeof(GlyphInfo)); font.recs = (Rectangle *)RL_MALLOC(glyphCount*sizeof(Rectangle)); - int charId, charX, charY, charWidth, charHeight, charOffsetX, charOffsetY, charAdvanceX, pageID; + int charId = 0; + int charX = 0; + int charY = 0; + int charWidth = 0; + int charHeight = 0; + int charOffsetX = 0; + int charOffsetY = 0; + int charAdvanceX = 0; + int pageID = 0; for (int i = 0; i < glyphCount; i++) { diff --git a/src/rtextures.c b/src/rtextures.c index 02a9ff1a5..4208b40bd 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -1727,7 +1727,8 @@ void ImageResizeNN(Image *image, int newWidth, int newHeight) int xRatio = (int)((image->width << 16)/newWidth) + 1; int yRatio = (int)((image->height << 16)/newHeight) + 1; - int x2, y2; + int x2 = 0; + int y2 = 0; for (int y = 0; y < newHeight; y++) { for (int x = 0; x < newWidth; x++) @@ -2488,8 +2489,13 @@ void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) Color oldPixel = WHITE; Color newPixel = WHITE; - int rError, gError, bError; - unsigned short rPixel, gPixel, bPixel, aPixel; // Used for 16bit pixel composition + int rError = 0; + int gError = 0; + int bError = 0; + unsigned short rPixel = 0; // Used for 16bit pixel composition + unsigned short gPixel = 0; + unsigned short bPixel = 0; + unsigned short aPixel = 0; #define MIN(a,b) (((a)<(b))?(a):(b)) @@ -4006,7 +4012,9 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color // [-] GetPixelColor(): Get Vector4 instead of Color, easier for ColorAlphaBlend() // [ ] TODO: Support 16bit and 32bit (float) channels drawing - Color colSrc, colDst, blend; + Color colSrc = { 0 }; + Color colDst = { 0 }; + Color blend = { 0 }; bool blendRequired = true; // Fast path: Avoid blend if source has no alpha to blend @@ -4681,17 +4689,23 @@ void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, bottomBorder = patchHeight - topBorder; } - Vector2 vertA, vertB, vertC, vertD; - vertA.x = 0.0f; // outer left - vertA.y = 0.0f; // outer top - vertB.x = leftBorder; // inner left - vertB.y = topBorder; // inner top - vertC.x = patchWidth - rightBorder; // inner right - vertC.y = patchHeight - bottomBorder; // inner bottom - vertD.x = patchWidth; // outer right - vertD.y = patchHeight; // outer bottom + Vector2 vertA = { 0 }; + Vector2 vertB = { 0 }; + Vector2 vertC = { 0 }; + Vector2 vertD = { 0 }; + vertA.x = 0.0f; // Outer left + vertA.y = 0.0f; // Outer top + vertB.x = leftBorder; // Inner left + vertB.y = topBorder; // Inner top + vertC.x = patchWidth - rightBorder; // Inner right + vertC.y = patchHeight - bottomBorder; // Inner bottom + vertD.x = patchWidth; // Outer right + vertD.y = patchHeight; // Outer bottom - Vector2 coordA, coordB, coordC, coordD; + Vector2 coordA = { 0 }; + Vector2 coordB = { 0 }; + Vector2 coordC = { 0 }; + Vector2 coordD = { 0 }; coordA.x = nPatchInfo.source.x/width; coordA.y = nPatchInfo.source.y/height; coordB.x = (nPatchInfo.source.x + leftBorder)/width; @@ -4907,7 +4921,9 @@ Vector3 ColorToHSV(Color color) { Vector3 hsv = { 0 }; Vector3 rgb = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; - float min, max, delta; + float min = 0.0f; + float max = 0.0f; + float delta = 0.0f; min = rgb.x < rgb.y? rgb.x : rgb.y; min = min < rgb.z? min : rgb.z; From 9a337f3b3b6c8208574fea3eebd0bd46bc9a386e Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 14 Dec 2025 19:52:18 +0100 Subject: [PATCH 227/430] ADDED: Support software renderer on Web, blitting framebuffer data directly to a 2d canvas This improvement is just a prove of concept, at this moment `PLATFORM_WEB` is limited in terms of software rendering by `GLFW` that only allows creating a WebGL canvas context with `glfwCreateWindow()`. We can skip that call but then some GLFW functionality is not available (windowing, inputs). The best solution is replacing GLFW completely by a pure Emscripten implementation for `PLATFORM_WEB`. --- src/platforms/rcore_web.c | 52 ++++++++++++++++++++++++++++++++++++--- src/rcore.c | 2 +- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index dc779d0fb..934f778c3 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -78,6 +78,11 @@ typedef struct { int unmaximizedHeight; // Internal var to store the unmaximized window (canvas) height char canvasId[64]; // Keep current canvas id where wasm app is running + // NOTE: Useful when trying to run multiple wasms in different canvases in same webpage + +#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) + unsigned int *pixels; // Pointer to pixel data buffer (RGBA 32bit format) +#endif } PlatformData; //---------------------------------------------------------------------------------- @@ -264,7 +269,8 @@ void ToggleFullscreen(void) }; emscripten_enter_soft_fullscreen(platform.canvasId, &strategy); - int width, height; + int width = 0; + int height = 0; emscripten_get_canvas_element_size(platform.canvasId, &width, &height); TRACELOG(LOG_WARNING, "Emscripten: Enter fullscreen: Canvas size: %i x %i", width, height); @@ -883,7 +889,32 @@ void DisableCursor(void) // Swap back buffer with front buffer (screen drawing) void SwapScreenBuffer(void) { +#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) + // Update framebuffer + rlCopyFramebuffer(0, 0, CORE.Window.render.width, CORE.Window.render.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, platform.pixels); + + // Copy framebuffer data into canvas + EM_ASM({ + const width = $0; + const height = $1; + const ptr = $2; + + // Get canvas and 2d context created + const canvas = Module.canvas; + const ctx = canvas.getContext('2d'); + + if (!Module.__img || (Module.__img.width !== width) || (Module.__img.height !== height)) { + Module.__img = ctx.createImageData(width, height); + } + + const src = HEAPU8.subarray(ptr, ptr + width*height*4); // RGBA (4 bytes) + Module.__img.data.set(src); + ctx.putImageData(Module.__img, 0, 0); + + }, CORE.Window.screen.width, CORE.Window.screen.height, platform.pixels); +#else glfwSwapBuffers(platform.handle); +#endif } //---------------------------------------------------------------------------------- @@ -974,7 +1005,7 @@ void SetMouseCursor(int cursor) } } -// Get physical key name. +// Get physical key name const char *GetKeyName(int key) { TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform"); @@ -1214,7 +1245,21 @@ int InitPlatform(void) // Init fullscreen toggle required var: platform.ourFullscreen = false; - + +#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) + // Avoid creating a WebGL canvas, avoid calling glfwCreateWindow() + emscripten_set_canvas_element_size(platform.canvasId, CORE.Window.screen.width, CORE.Window.screen.height); + EM_ASM({ + const canvas = document.getElementById("canvas"); + Module.canvas = canvas; + }); + + // Load memory framebuffer with desired screen size + // NOTE: Despite using a software framebuffer for blitting, GLFW still creates a WebGL canvas, + // but it is not being used, on SwapScreenBuffer() the pure software renderer is used + // TODO: Consider requesting another type of canvas, not a WebGL one --> Replace GLFW-web by Emscripten? + platform.pixels = (unsigned int *)RL_CALLOC(CORE.Window.screen.width*CORE.Window.screen.height, sizeof(unsigned int)); +#else if (CORE.Window.fullscreen) { // remember center for switchinging from fullscreen to window @@ -1289,6 +1334,7 @@ int InitPlatform(void) TRACELOG(LOG_WARNING, "GLFW: Failed to initialize Window"); return -1; } +#endif // WARNING: glfwCreateWindow() title doesn't work with emscripten emscripten_set_window_title((CORE.Window.title != 0)? CORE.Window.title : " "); diff --git a/src/rcore.c b/src/rcore.c index 24ebb6dec..19228cc8e 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -117,7 +117,7 @@ #include // Required for: time() [Used in InitTimer()] #include // Required for: tan() [Used in BeginMode3D()], atan2f() [Used in LoadVrStereoConfig()] -#if defined(PLATFORM_MEMORY) +#if defined(PLATFORM_MEMORY) || defined(PLATFORM_WEB) #define SW_GL_FRAMEBUFFER_COPY_BGRA false #endif #define RLGL_IMPLEMENTATION From a0fd5ab1d90e0c34d9e6e173b08f23453f7561ff Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 14 Dec 2025 19:59:12 +0100 Subject: [PATCH 228/430] Update rmodels.c --- src/rmodels.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index 883f98660..2347ca0e6 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -4150,7 +4150,9 @@ RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform) // Test against all triangles in mesh for (int i = 0; i < triangleCount; i++) { - Vector3 a = 0, b = 0, c = 0; + Vector3 a = { 0 }; + Vector3 b = { 0 }; + Vector3 c = { 0 }; Vector3 *vertdata = (Vector3 *)mesh.vertices; if (mesh.indices) @@ -4193,7 +4195,9 @@ RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3 RayCollision collision = { 0 }; Vector3 edge1 = { 0 }; Vector3 edge2 = { 0 }; - Vector3 p = 0, q = 0, tv = 0; + Vector3 p = { 0 }; + Vector3 q = { 0 }; + Vector3 tv = { 0 }; float det = 0.0f, invDet = 0.0f, u = 0.0f, v = 0.0f, t = 0.0f; // Find vectors for two edges sharing V1 From 8d246fdaff8ae5f23593f70c827029d3e8d43a7c Mon Sep 17 00:00:00 2001 From: ALONZO Robin Date: Mon, 15 Dec 2025 00:03:31 +0100 Subject: [PATCH 229/430] Fix EXTERNAL_CONFIG_FLAGS being defined even when no custom config is used when building with zig (#5410) --- build.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.zig b/build.zig index 4e06ca757..5d2902111 100644 --- a/build.zig +++ b/build.zig @@ -155,9 +155,9 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. ); } - // Sets a flag indicating the use of a custom `config.h` - try raylib_flags_arr.append(b.allocator, "-DEXTERNAL_CONFIG_FLAGS"); if (options.config.len > 0) { + // Sets a flag indicating the use of a custom `config.h` + try raylib_flags_arr.append(b.allocator, "-DEXTERNAL_CONFIG_FLAGS"); // Splits a space-separated list of config flags into multiple flags // // Note: This means certain flags like `-x c++` won't be processed properly. From d74556d35cca3befc6ac924695f863b8c3827d1f Mon Sep 17 00:00:00 2001 From: RANDRIA Luca Date: Mon, 15 Dec 2025 20:49:40 +0300 Subject: [PATCH 230/430] Modify text_words_alignment.c (#5411) --- examples/text/text_words_alignment.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/text/text_words_alignment.c b/examples/text/text_words_alignment.c index 6cfd2a85c..a558d5b11 100644 --- a/examples/text/text_words_alignment.c +++ b/examples/text/text_words_alignment.c @@ -19,6 +19,8 @@ #include "raymath.h" // Required for: Lerp() +#include + typedef enum TextAlignment { TEXT_ALIGN_LEFT = 0, TEXT_ALIGN_TOP = 0, @@ -58,7 +60,7 @@ int main(void) // And of course the font... Font font = GetFontDefault(); - // Intialize the alignment variables + // Initialize the alignment variables TextAlignment hAlign = TEXT_ALIGN_CENTRE; TextAlignment vAlign = TEXT_ALIGN_MIDDLE; @@ -72,8 +74,7 @@ int main(void) //---------------------------------------------------------------------------------- if (IsKeyPressed(KEY_LEFT)) { - hAlign = hAlign - 1; - if (hAlign < 0) hAlign = 0; + if (hAlign > 0) hAlign = hAlign - 1; } if (IsKeyPressed(KEY_RIGHT)) @@ -84,8 +85,7 @@ int main(void) if (IsKeyPressed(KEY_UP)) { - vAlign = vAlign - 1; - if (vAlign < 0) vAlign = 0; + if (vAlign > 0) vAlign = vAlign - 1; } if (IsKeyPressed(KEY_DOWN)) @@ -95,7 +95,8 @@ int main(void) } // One word per second - wordIndex = (int)GetTime()%wordCount; + if (wordCount > 0) wordIndex = (int)GetTime()%wordCount; + else wordIndex = 0; //---------------------------------------------------------------------------------- // Draw @@ -132,4 +133,4 @@ int main(void) //-------------------------------------------------------------------------------------- return 0; -} \ No newline at end of file +} From cbe31759ab1b578b5f5c58b81cd67dcc2db1047e Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 15 Dec 2025 18:52:27 +0100 Subject: [PATCH 231/430] Fix #5405 --- src/rmodels.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index 2347ca0e6..40af4afc4 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -127,10 +127,13 @@ // Defines and Macros //---------------------------------------------------------------------------------- #ifndef MAX_MATERIAL_MAPS - #define MAX_MATERIAL_MAPS 12 // Maximum number of maps supported + #define MAX_MATERIAL_MAPS 12 // Maximum number of maps supported #endif #ifndef MAX_MESH_VERTEX_BUFFERS - #define MAX_MESH_VERTEX_BUFFERS 9 // Maximum vertex buffers (VBO) per mesh + #define MAX_MESH_VERTEX_BUFFERS 9 // Maximum vertex buffers (VBO) per mesh +#endif +#ifndef MAX_FILEPATH_LENGTH + #define MAX_FILEPATH_LENGTH 4096 // Maximum length for filepaths (Linux PATH_MAX default value) #endif //---------------------------------------------------------------------------------- From 615fc36eeb4570be90ba53308f99ba97755281ff Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 15 Dec 2025 18:56:14 +0100 Subject: [PATCH 232/430] Fix #5406 --- src/rlgl.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index b5955e613..97f892eb5 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -1762,11 +1762,6 @@ void rlTextureParameters(unsigned int id, int param, int value) { glBindTexture(GL_TEXTURE_2D, id); -#if !defined(GRAPHICS_API_OPENGL_11) - // Reset anisotropy filter, in case it was set - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); -#endif - switch (param) { case RL_TEXTURE_WRAP_S: @@ -1786,6 +1781,9 @@ void rlTextureParameters(unsigned int id, int param, int value) case RL_TEXTURE_FILTER_ANISOTROPIC: { #if !defined(GRAPHICS_API_OPENGL_11) + // Reset anisotropy filter, in case it was set + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); + if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) { From cf0d6fc664f1d0775c6b21ed79feaa1e38b2ddb4 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 15 Dec 2025 20:44:28 +0100 Subject: [PATCH 233/430] REVIEWED: Alignment with other platforms --- src/external/RGFW.h | 17630 ++++++++++++++++----------- src/platforms/rcore_desktop_rgfw.c | 5 - 2 files changed, 10239 insertions(+), 7396 deletions(-) diff --git a/src/external/RGFW.h b/src/external/RGFW.h index 7205bf9d8..b913d4f2d 100644 --- a/src/external/RGFW.h +++ b/src/external/RGFW.h @@ -1,8 +1,8 @@ /* * -* RGFW 1.7.5-dev +* RGFW 1.8.1 -* Copyright (C) 2022-25 ColleagueRiley +* Copyright (C) 2022-25 Riley Mabb (@ColleagueRiley) * * libpng license * @@ -33,19 +33,12 @@ /* #define RGFW_IMPLEMENTATION - (required) makes it so the source code is included #define RGFW_DEBUG - (optional) makes it so RGFW prints debug messages and errors when they're found - #define RGFW_OSMESA - (optional) use OSmesa as backend (instead of system's opengl api + regular opengl) - #define RGFW_BUFFER - (optional) draw directly to (RGFW) window pixel buffer that is drawn to screen (the buffer is in the RGBA format) - #define RGFW_EGL - (optional) use EGL for loading an OpenGL context (instead of the system's opengl api) - #define RGFW_OPENGL_ES1 - (optional) use EGL to load and use Opengl ES (version 1) for backend rendering (instead of the system's opengl api) - 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_EGL - (optional) compile with OpenGL functions, allowing you to use to use EGL instead of the native OpenGL functions #define RGFW_DIRECTX - (optional) include integration directX functions (windows only) #define RGFW_VULKAN - (optional) include helpful vulkan integration functions and macros - #define RGFW_WEBGPU - (optional) use webGPU for rendering (Web ONLY) - #define RGFW_NO_API - (optional) don't use any rendering API (no opengl, no vulkan, no directX) + #define RGFW_WEBGPU - (optional) use WebGPU for rendering + #define RGFW_NATIVE - (optional) define native RGFW types that use native API structures - #define RGFW_LINK_EGL (optional) (windows only) if EGL is being used, if EGL functions should be defined dymanically (using GetProcAddress) #define RGFW_X11 (optional) (unix only) if X11 should be used. This option is turned on by default by unix systems except for MacOS #define RGFW_WAYLAND (optional) (unix only) use Wayland. (This can be used with X11) #define RGFW_NO_X11 (optional) (unix only) don't fallback to X11 when using Wayland @@ -62,8 +55,9 @@ #define RGFW_COCOA_GRAPHICS_SWITCHING - (optional) (cocoa) use automatic graphics switching (allow the system to choose to use GPU or iGPU) #define RGFW_COCOA_FRAME_NAME (optional) (cocoa) set frame name #define RGFW_NO_DPI - do not calculate DPI (no XRM nor libShcore included) - #define RGFW_BUFFER_BGR - use the BGR format for bufffers instead of RGB, saves processing time #define RGFW_ADVANCED_SMOOTH_RESIZE - use advanced methods for smooth resizing (may result in a spike in memory usage or worse performance) (eg. WM_TIMER and XSyncValue) + #define RGFW_NO_INFO - do not define the RGFW_info struct (without RGFW_IMPLEMENTATION) + #define RGFW_NO_GLXWINDOW - do not use GLXWindow #define RGFW_ALLOC x - choose the default allocation function (defaults to standard malloc) #define RGFW_FREE x - choose the default deallocation function (defaults to standard free) @@ -89,20 +83,17 @@ macos : gcc main.c -framework Cocoa -framework CoreVideo -framework OpenGL -fram 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(100, 100, 500, 500), (u64)0); + RGFW_window* win = RGFW_createWindow("name", 100, 100, 500, 500, (u64)0); + RGFW_event event; - RGFW_window_setIcon(win, icon, RGFW_AREA(3, 3), 4); + RGFW_window_setExitKey(win, RGFW_escape); + RGFW_window_setIcon(win, icon, 3, 3, RGFW_formatRGBA8); while (RGFW_window_shouldClose(win) == RGFW_FALSE) { - while (RGFW_window_checkEvent(win)) { - if (win->event.type == RGFW_quit || RGFW_isPressed(win, RGFW_escape)) - break; - } - - RGFW_window_swapBuffers(win); - - glClearColor(1.0f, 1.0f, 1.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); + while (RGFW_window_checkEvent(win, &event)) { + if (event.type == RGFW_quit) + break; + } } RGFW_window_close(win); @@ -143,25 +134,40 @@ int main() { /* Credits : - EimaMei/Sacode : Much of the code for creating windows using winapi, Wrote the Silicon library, helped with MacOS Support, siliapp.h -> referencing + EimaMei/Sacode : Code review, helped with X11, MacOS and Windows support, Silicon, siliapp.h -> referencing - stb - This project is heavily inspired by the stb single header files + stb : This project is heavily inspired by the stb single header files - GLFW: - certain parts of winapi and X11 are very poorly documented, - GLFW's source code was referenced and used throughout the project. + SDL, GLFW and other online resources : reference implementations 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, MacOS/Cocoa fixing memory issues/bugs + krisvers (@krisvers) -> code review + EimaMei (@SaCode) -> code review + Nycticebus (@Code-Nycticebus) -> bug fixes + Rob Rohan (@robrohan) -> X11 bugs and missing features, MacOS/Cocoa fixing memory issues/bugs AICDG (@THISISAGOODNAME) -> vulkan support (example) @Easymode -> support, testing/debugging, bug fixes and reviews Joshua Rowe (omnisci3nce) - bug fix, review (macOS) @lesleyrs -> bug fix, review (OpenGL) - Nick Porcino (meshula) - testing, organization, review (MacOS, examples) - @DarekParodia -> code review (X11) (C++) + Nick Porcino (@meshula) - testing, organization, review (MacOS, examples) + @therealmarrakesh -> documentation + @DarekParodia -> code review (X11) (C++) + @NishiOwO -> fix BSD support, fix OSMesa example + @BaynariKattu -> code review and documentation + Miguel Pinto (@konopimi) -> code review, fix vulkan example + @m-doescode -> code review (wayland) + Robert Gonzalez (@uni-dos) -> code review (wayland) + @TheLastVoyager -> code review + @yehoravramenko -> code review (winapi) + @halocupcake -> code review (OpenGL) + @GideonSerf -> documentation + Alexandre Almeida (@M374LX) -> code review (keycodes) + Vũ Xuân Trường (@wanwanvxt) -> code review (winapi) + Lucas (@lightspeedlucas) -> code review (msvc++) + Jeffery Myers (@JeffM2501) -> code review (msvc) + Zeni (@zenitsuyo) -> documentation + TheYahton (@TheYahton) -> documentation + nonexistant_object (@DiarrheaMcgee */ #if _MSC_VER @@ -179,6 +185,74 @@ int main() { #endif #endif +#if defined(RGFW_EGL) && !defined(RGFW_OPENGL) + #define RGFW_OPENGL +#endif + +/* these OS macros look better & are standardized */ +/* plus it helps with cross-compiling */ + +#ifdef __EMSCRIPTEN__ + #define RGFW_WASM +#endif + +#if defined(RGFW_X11) && defined(__APPLE__) && !defined(RGFW_CUSTOM_BACKEND) + #define RGFW_MACOS_X11 + #define RGFW_UNIX +#endif + +#if defined(_WIN32) && !defined(RGFW_X11) && !defined(RGFW_UNIX) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) /* (if you're using X11 on windows some how) */ + #define RGFW_WINDOWS +#endif +#if defined(RGFW_WAYLAND) + #define RGFW_DEBUG /* wayland will be in debug mode by default for now */ + #define RGFW_UNIX + #ifdef RGFW_OPENGL + #define RGFW_EGL + #endif + #ifdef RGFW_X11 + #define RGFW_DYNAMIC + #endif +#endif +#if (!defined(RGFW_WAYLAND) && !defined(RGFW_X11)) && (defined(__unix__) || defined(RGFW_MACOS_X11) || defined(RGFW_X11)) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) + #define RGFW_MACOS_X11 + #define RGFW_X11 + #define RGFW_UNIX +#elif defined(__APPLE__) && !defined(RGFW_MACOS_X11) && !defined(RGFW_X11) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) + #define RGFW_MACOS +#endif + +#ifndef RGFW_ASSERT + #include + #define RGFW_ASSERT assert +#endif + +#if !defined(__STDC_VERSION__) + #define RGFW_C89 +#endif + +#if !defined(RGFW_SNPRINTF) && (defined(RGFW_X11) || defined(RGFW_WAYLAND)) + + /* required for X11 errors */ + #include + + #ifdef RGFW_C89 + #include + static int RGFW_c89_snprintf(char *dst, size_t size, const char *format, ...) { + va_list args; + size_t count = 0; + va_start(args, format); + count = (size_t)vsprintf(dst, format, args); + RGFW_ASSERT(count + 1 < size && "Buffer overflow"); + va_end(args); + return (int)count; + } + #define RGFW_SNPRINTF RGFW_c89_snprintf + #else + #define RGFW_SNPRINTF snprintf + #endif /*RGFW_C89*/ +#endif + #ifndef RGFW_USERPTR #define RGFW_USERPTR NULL #endif @@ -191,17 +265,16 @@ int main() { #define RGFW_ROUND(x) (i32)((x) >= 0 ? (x) + 0.5f : (x) - 0.5f) #endif +#ifndef RGFW_MIN + #define RGFW_MIN(x, y) ((x < y) ? x : y) +#endif + #ifndef RGFW_ALLOC #include #define RGFW_ALLOC malloc #define RGFW_FREE free #endif -#ifndef RGFW_ASSERT - #include - #define RGFW_ASSERT assert -#endif - #if !defined(RGFW_MEMCPY) || !defined(RGFW_STRNCMP) || !defined(RGFW_STRNCPY) || !defined(RGFW_MEMSET) #include #endif @@ -233,6 +306,31 @@ int main() { #define RGFW_ATOF(num) atof(num) #endif +#if !defined(RGFW_PRINTF) && ( defined(RGFW_DEBUG) || defined(RGFW_WAYLAND) ) + /* required when using RGFW_DEBUG */ + #include + #define RGFW_PRINTF printf +#endif + +#ifndef RGFW_MAX_PATH + #define RGFW_MAX_PATH 260 /* max length of a path (for drag andn drop) */ +#endif +#ifndef RGFW_MAX_DROPS + #define RGFW_MAX_DROPS 260 /* max items you can drop at once */ +#endif + +#ifndef RGFW_MAX_EVENTS + #define RGFW_MAX_EVENTS 32 +#endif + +#ifndef RGFW_MAX_MONITORS + #define RGFW_MAX_MONITORS 6 +#endif + +#ifndef RGFW_COCOA_FRAME_NAME + #define RGFW_COCOA_FRAME_NAME NULL +#endif + #ifdef RGFW_WIN95 /* for windows 95 testing (not that it really works) */ #define RGFW_NO_MONITOR #define RGFW_NO_PASSTHROUGH @@ -267,16 +365,11 @@ int main() { #endif #endif -#ifndef RGFW_ENUM - #define RGFW_ENUM(type, name) type name; enum -#endif - - #if defined(__cplusplus) && !defined(__EMSCRIPTEN__) extern "C" { #endif - /* makes sure the header file part is only defined once by default */ +/* makes sure the header file part is only defined once by default */ #ifndef RGFW_HEADER #define RGFW_HEADER @@ -307,1023 +400,31 @@ int main() { #define RGFW_INT_DEFINED #endif +typedef ptrdiff_t RGFW_ssize_t; + #ifndef RGFW_BOOL_DEFINED #define RGFW_BOOL_DEFINED typedef u8 RGFW_bool; #endif -#define RGFW_BOOL(x) (RGFW_bool)((x) ? RGFW_TRUE : RGFW_FALSE) /* force an value to be 0 or 1 */ +#define RGFW_BOOL(x) (RGFW_bool)((x) != 0) /* force a value to be 0 or 1 */ #define RGFW_TRUE (RGFW_bool)1 #define RGFW_FALSE (RGFW_bool)0 -/* these OS macros look better & are standardized */ -/* plus it helps with cross-compiling */ +#define RGFW_ENUM(type, name) type name; enum +#define RGFW_BIT(x) (1 << (x)) -#ifdef __EMSCRIPTEN__ - #define RGFW_WASM - - #if !defined(RGFW_NO_API) && !defined(RGFW_WEBGPU) - #define RGFW_OPENGL - #endif - - #ifdef RGFW_EGL - #undef RGFW_EGL - #endif - - #include - #include - - #ifdef RGFW_WEBGPU - #include - #endif -#endif - -#if defined(RGFW_X11) && defined(__APPLE__) && !defined(RGFW_CUSTOM_BACKEND) - #define RGFW_MACOS_X11 - #define RGFW_UNIX - #undef __APPLE__ -#endif - -#if defined(_WIN32) && !defined(RGFW_X11) && !defined(RGFW_UNIX) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) /* (if you're using X11 on windows some how) */ - #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 - - #ifndef RGFW_NO_XINPUT - #ifdef __MINGW32__ /* try to find the right header */ - #include - #else - #include - #endif - #endif -#endif -#if defined(RGFW_WAYLAND) - #define RGFW_DEBUG /* wayland will be in debug mode by default for now */ - #if !defined(RGFW_NO_API) && (!defined(RGFW_BUFFER) || defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) - #define RGFW_EGL - #define RGFW_OPENGL - #include - #endif - - #define RGFW_UNIX - #include -#endif -#if !defined(RGFW_NO_X11) && (defined(__unix__) || defined(RGFW_MACOS_X11) || defined(RGFW_X11)) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) - #define RGFW_MACOS_X11 - #define RGFW_X11 - #define RGFW_UNIX - #include - #include -#elif defined(__APPLE__) && !defined(RGFW_MACOS_X11) && !defined(RGFW_X11) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) - #define RGFW_MACOS - #if !defined(RGFW_BUFFER_BGR) - #define RGFW_BUFFER_BGR - #else - #undef RGFW_BUFFER_BGR - #endif -#endif - -#if (defined(RGFW_OPENGL_ES1) || defined(RGFW_OPENGL_ES2) || defined(RGFW_OPENGL_ES3)) && !defined(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 - -#ifdef RGFW_EGL - #include -#elif defined(RGFW_OSMESA) - #ifdef RGFW_WINDOWS - #define OEMRESOURCE - #include - #ifndef GLAPIENTRY - #define GLAPIENTRY APIENTRY - #endif - #ifndef GLAPI - #define GLAPI WINGDIAPI - #endif - #endif - - #ifndef __APPLE__ - #include - #else - #include - #endif -#endif - -#if (defined(RGFW_OPENGL) || defined(RGFW_WEGL)) && defined(_MSC_VER) - #pragma comment(lib, "opengl32") -#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 - -#define RGFW_COCOA_FRAME_NAME NULL - -/*! (unix) Toggle use of wayland. This will be on by default if you use `RGFW_WAYLAND` (if you don't use RGFW_WAYLAND, you don't expose WAYLAND functions) - this is mostly used to allow you to force the use of XWayland -*/ -RGFWDEF void RGFW_useWayland(RGFW_bool wayland); -RGFWDEF RGFW_bool RGFW_usingWayland(void); -/* - regular RGFW stuff -*/ - -#define RGFW_key u8 - -typedef RGFW_ENUM(u8, RGFW_eventType) { - /*! event codes */ - RGFW_eventNone = 0, /*!< no event has been sent */ - RGFW_keyPressed, /* a key has been pressed */ - RGFW_keyReleased, /*!< a key has been released */ - /*! key event note - the code of the key pressed is stored in - RGFW_event.key - !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! - - while a string version is stored in - RGFW_event.KeyString - - RGFW_event.keyMod holds the current keyMod - this means if CapsLock, NumLock are active or not - */ - RGFW_mouseButtonPressed, /*!< a mouse button has been pressed (left,middle,right) */ - RGFW_mouseButtonReleased, /*!< a mouse button has been released (left,middle,right) */ - RGFW_mousePosChanged, /*!< the position of the mouse has been changed */ - /*! mouse event note - the x and y of the mouse can be found in the vector, RGFW_event.point - - RGFW_event.button holds which mouse button was pressed - */ - RGFW_gamepadConnected, /*!< a gamepad was connected */ - RGFW_gamepadDisconnected, /*!< a gamepad was disconnected */ - RGFW_gamepadButtonPressed, /*!< a gamepad button was pressed */ - RGFW_gamepadButtonReleased, /*!< a gamepad button was released */ - RGFW_gamepadAxisMove, /*!< an axis of a gamepad was moved */ - /*! gamepad event note - RGFW_event.gamepad holds which gamepad was altered, if any - RGFW_event.button holds which gamepad button was pressed - - RGFW_event.axis holds the data of all the axises - RGFW_event.axisesCount says how many axises there are - */ - RGFW_windowMoved, /*!< the window was moved (by the user) */ - RGFW_windowResized, /*!< the window was resized (by the user), [on WASM this means the browser was resized] */ - RGFW_focusIn, /*!< window is in focus now */ - RGFW_focusOut, /*!< window is out of focus now */ - RGFW_mouseEnter, /* mouse entered the window */ - RGFW_mouseLeave, /* mouse left the window */ - RGFW_windowRefresh, /* The window content needs to be refreshed */ - - /* attribs change event note - The event data is sent straight to the window structure - with win->r.x, win->r.y, win->r.w and win->r.h - */ - RGFW_quit, /*!< the user clicked the quit button */ - RGFW_DND, /*!< a file has been dropped into the window */ - RGFW_DNDInit, /*!< the start of a dnd event, when the place where the file drop is known */ - /* dnd data note - The x and y coords of the drop are stored in the vector RGFW_event.point - - RGFW_event.droppedFilesCount holds how many files were dropped - - This is also the size of the array which stores all the dropped file string, - RGFW_event.droppedFiles - */ - RGFW_windowMaximized, /*!< the window was maximized */ - RGFW_windowMinimized, /*!< the window was minimized */ - RGFW_windowRestored, /*!< the window was restored */ - RGFW_scaleUpdated /*!< content scale factor changed */ -}; - -/*! mouse button codes (RGFW_event.button) */ -typedef RGFW_ENUM(u8, RGFW_mouseButton) { - RGFW_mouseLeft = 0, /*!< left mouse button is pressed */ - RGFW_mouseMiddle, /*!< mouse-wheel-button is pressed */ - RGFW_mouseRight, /*!< right mouse button is pressed */ - RGFW_mouseScrollUp, /*!< mouse wheel is scrolling up */ - RGFW_mouseScrollDown, /*!< mouse wheel is scrolling down */ - RGFW_mouseMisc1, RGFW_mouseMisc2, RGFW_mouseMisc3, RGFW_mouseMisc4, RGFW_mouseMisc5, - RGFW_mouseFinal -}; - -#ifndef RGFW_MAX_PATH -#define RGFW_MAX_PATH 260 /* max length of a path (for dnd) */ -#endif -#ifndef RGFW_MAX_DROPS -#define RGFW_MAX_DROPS 260 /* max items you can drop at once */ -#endif - -#define RGFW_BIT(x) (1 << x) - -/* for RGFW_event.lockstate */ -typedef RGFW_ENUM(u8, RGFW_keymod) { - RGFW_modCapsLock = RGFW_BIT(0), - RGFW_modNumLock = RGFW_BIT(1), - RGFW_modControl = RGFW_BIT(2), - RGFW_modAlt = RGFW_BIT(3), - RGFW_modShift = RGFW_BIT(4), - RGFW_modSuper = RGFW_BIT(5), - RGFW_modScrollLock = RGFW_BIT(6) -}; - -/*! gamepad button codes (based on xbox/playstation), you may need to change these values per controller */ -typedef RGFW_ENUM(u8, RGFW_gamepadCodes) { - RGFW_gamepadNone = 0, /*!< or PS X button */ - RGFW_gamepadA, /*!< or PS X button */ - RGFW_gamepadB, /*!< or PS circle button */ - RGFW_gamepadY, /*!< or PS triangle button */ - RGFW_gamepadX, /*!< or PS square button */ - RGFW_gamepadStart, /*!< start button */ - RGFW_gamepadSelect, /*!< select button */ - RGFW_gamepadHome, /*!< home button */ - RGFW_gamepadUp, /*!< dpad up */ - RGFW_gamepadDown, /*!< dpad down */ - RGFW_gamepadLeft, /*!< dpad left */ - RGFW_gamepadRight, /*!< dpad right */ - RGFW_gamepadL1, /*!< left bump */ - RGFW_gamepadL2, /*!< left trigger */ - RGFW_gamepadR1, /*!< right bumper */ - RGFW_gamepadR2, /*!< right trigger */ - RGFW_gamepadL3, /* left thumb stick */ - RGFW_gamepadR3, /*!< right thumb stick */ - RGFW_gamepadFinal -}; - -/*! basic vector type, if there's not already a point/vector type of choice */ -#ifndef RGFW_point - typedef struct RGFW_point { i32 x, y; } RGFW_point; -#endif - -/*! basic rect type, if there's not already a rect type of choice */ -#ifndef RGFW_rect - typedef struct RGFW_rect { i32 x, y, w, h; } RGFW_rect; -#endif - -/*! basic area type, if there's not already a area type of choice */ -#ifndef RGFW_area - typedef struct RGFW_area { u32 w, h; } RGFW_area; -#endif - -#if defined(__cplusplus) && !defined(__APPLE__) -#define RGFW_POINT(x, y) {(i32)x, (i32)y} -#define RGFW_RECT(x, y, w, h) {(i32)x, (i32)y, (i32)w, (i32)h} -#define RGFW_AREA(w, h) {(u32)w, (u32)h} -#else -#define RGFW_POINT(x, y) (RGFW_point){(i32)(x), (i32)(y)} -#define RGFW_RECT(x, y, w, h) (RGFW_rect){(i32)(x), (i32)(y), (i32)(w), (i32)(h)} -#define RGFW_AREA(w, h) (RGFW_area){(u32)(w), (u32)(h)} -#endif - -#ifndef RGFW_NO_MONITOR - /* monitor mode data | can be changed by the user (with functions)*/ - typedef struct RGFW_monitorMode { - RGFW_area area; /*!< monitor workarea size */ - u32 refreshRate; /*!< monitor refresh rate */ - u8 red, blue, green; - } RGFW_monitorMode; - - /*! structure for monitor data */ - typedef struct RGFW_monitor { - i32 x, y; /*!< x - y of the monitor workarea */ - char name[128]; /*!< monitor name */ - float scaleX, scaleY; /*!< monitor content scale */ - float pixelRatio; /*!< pixel ratio for monitor (1.0 for regular, 2.0 for hiDPI) */ - float physW, physH; /*!< monitor physical size in inches */ - - RGFW_monitorMode mode; - } RGFW_monitor; - - /*! get an array of all the monitors (max 6) */ - RGFWDEF RGFW_monitor* RGFW_getMonitors(size_t* len); - /*! get the primary monitor */ - RGFWDEF RGFW_monitor RGFW_getPrimaryMonitor(void); - - typedef RGFW_ENUM(u8, RGFW_modeRequest) { - RGFW_monitorScale = RGFW_BIT(0), /*!< scale the monitor size */ - RGFW_monitorRefresh = RGFW_BIT(1), /*!< change the refresh rate */ - RGFW_monitorRGB = RGFW_BIT(2), /*!< change the monitor RGB bits size */ - RGFW_monitorAll = RGFW_monitorScale | RGFW_monitorRefresh | RGFW_monitorRGB - }; - - /*! request a specific mode */ - RGFWDEF RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request); - /*! check if 2 monitor modes are the same */ - RGFWDEF RGFW_bool RGFW_monitorModeCompare(RGFW_monitorMode mon, RGFW_monitorMode mon2, RGFW_modeRequest request); -#endif - -/* RGFW mouse loading */ -typedef void RGFW_mouse; - -/*!< loads mouse icon from bitmap (similar to RGFW_window_setIcon). Icon NOT resized by default */ -RGFWDEF RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels); -/*!< frees RGFW_mouse data */ -RGFWDEF void RGFW_freeMouse(RGFW_mouse* mouse); - -/* NOTE: some parts of the data can represent different things based on the event (read comments in RGFW_event struct) */ -/*! Event structure for checking/getting events */ -typedef struct RGFW_event { - RGFW_eventType type; /*!< which event has been sent?*/ - RGFW_point point; /*!< mouse x, y of event (or drop point) */ - RGFW_point vector; /*!< raw mouse movement */ - float scaleX, scaleY; /*!< DPI scaling */ - - RGFW_key key; /*!< the physical key of the event, refers to where key is physically !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! */ - u8 keyChar; /*!< mapped key char of the event */ - - RGFW_bool repeat; /*!< key press event repeated (the key is being held) */ - RGFW_keymod keyMod; - - u8 button; /* !< which mouse (or gamepad) button was pressed */ - double scroll; /*!< the raw mouse scroll value */ - - u16 gamepad; /*! which gamepad this event applies to (if applicable to any) */ - u8 axisesCount; /*!< number of axises */ - - u8 whichAxis; /* which axis was effected */ - RGFW_point axis[4]; /*!< x, y of axises (-100 to 100) */ - - /*! drag and drop data */ - /* 260 max paths with a max length of 260 */ - char** droppedFiles; /*!< dropped files */ - size_t droppedFilesCount; /*!< house many files were dropped */ - - void* _win; /*!< the window this event applies too (for event queue events) */ -} RGFW_event; - -/*! source data for the window (used by the APIs) */ -#ifdef RGFW_WINDOWS -typedef struct RGFW_window_src { - HWND window; /*!< source window */ - HDC hdc; /*!< source HDC */ - u32 hOffset; /*!< height offset for window */ - HICON hIconSmall, hIconBig; /*!< source window icons */ - #if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) - HGLRC 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) - HDC hdcMem; - HBITMAP bitmap; - u8* bitmapBits; - #endif - RGFW_area maxSize, minSize, aspectRatio; /*!< for setting max/min resize (RGFW_WINDOWS) */ -} RGFW_window_src; -#elif defined(RGFW_UNIX) -typedef struct RGFW_window_src { -#if 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 */ - GLXFBConfig bestFbc; - #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; - #endif - GC gc; - XVisualInfo visual; - #ifdef RGFW_ADVANCED_SMOOTH_RESIZE - i64 counter_value; - XID counter; - #endif - RGFW_rect r; -#endif /* RGFW_X11 */ -#if defined(RGFW_WAYLAND) - struct wl_display* wl_display; - struct wl_surface* surface; - struct wl_buffer* wl_buffer; - struct wl_keyboard* keyboard; - - struct wl_compositor* compositor; - struct xdg_surface* xdg_surface; - struct xdg_toplevel* xdg_toplevel; - struct zxdg_toplevel_decoration_v1* decoration; - struct xdg_wm_base* xdg_wm_base; - struct wl_shm* shm; - struct wl_seat *seat; - u8* buffer; - #if defined(RGFW_EGL) - struct wl_egl_window* eglWindow; - #endif - #if defined(RGFW_EGL) && !defined(RGFW_X11) - EGLSurface EGL_surface; - EGLDisplay EGL_display; - EGLContext EGL_context; - #elif defined(RGFW_OSMESA) && !defined(RGFW_X11) - OSMesaContext ctx; - #endif -#endif /* RGFW_WAYLAND */ -} RGFW_window_src; -#endif /* RGFW_UNIX */ -#if defined(RGFW_MACOS) -typedef struct RGFW_window_src { - void* window; -#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 */ - void* mouse; -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) -#endif -} RGFW_window_src; -#elif defined(RGFW_WASM) -typedef struct RGFW_window_src { - #if defined(RGFW_WEBGPU) - WGPUInstance ctx; - WGPUDevice device; - WGPUQueue queue; - #elif defined(RGFW_OSMESA) - OSMesaContext ctx; - #else - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx; - #endif -} RGFW_window_src; -#endif - -/*! Optional arguments for making a windows */ -typedef RGFW_ENUM(u32, RGFW_windowFlags) { - RGFW_windowNoInitAPI = RGFW_BIT(0), /* do NOT init an API (including the software rendering buffer) (mostly for bindings. you can also use `#define RGFW_NO_API`) */ - RGFW_windowNoBorder = RGFW_BIT(1), /*!< the window doesn't have a border */ - RGFW_windowNoResize = RGFW_BIT(2), /*!< the window cannot be resized by the user */ - RGFW_windowAllowDND = RGFW_BIT(3), /*!< the window supports drag and drop */ - RGFW_windowHideMouse = RGFW_BIT(4), /*! the window should hide the mouse (can be toggled later on using `RGFW_window_mouseShow`) */ - RGFW_windowFullscreen = RGFW_BIT(5), /*!< the window is fullscreen by default */ - RGFW_windowTransparent = RGFW_BIT(6), /*!< the window is transparent (only properly works on X11 and MacOS, although it's meant for for windows) */ - RGFW_windowCenter = RGFW_BIT(7), /*! center the window on the screen */ - RGFW_windowOpenglSoftware = RGFW_BIT(8), /*! use OpenGL software rendering */ - RGFW_windowCocoaCHDirToRes = RGFW_BIT(9), /*! (cocoa only), change directory to resource folder */ - RGFW_windowScaleToMonitor = RGFW_BIT(10), /*! scale the window to the screen */ - RGFW_windowHide = RGFW_BIT(11), /*! the window is hidden */ - RGFW_windowMaximize = RGFW_BIT(12), - RGFW_windowCenterCursor = RGFW_BIT(13), - RGFW_windowFloating = RGFW_BIT(14), /*!< create a floating window */ - RGFW_windowFreeOnClose = RGFW_BIT(15), /*!< free (RGFW_window_close) the RGFW_window struct when the window is closed (by the end user) */ - RGFW_windowFocusOnShow = RGFW_BIT(16), /*!< focus the window when it's shown */ - RGFW_windowMinimize = RGFW_BIT(17), /*!< focus the window when it's shown */ - RGFW_windowFocus = RGFW_BIT(18), /*!< if the window is in focus */ - RGFW_windowedFullscreen = RGFW_windowNoBorder | RGFW_windowMaximize -}; - -typedef struct RGFW_window { - RGFW_window_src src; /*!< src window data */ - -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - u8* buffer; /*!< buffer for non-GPU systems (OSMesa, basic software rendering) */ - /* when rendering using RGFW_BUFFER, the buffer is in the RGBA format */ - RGFW_area bufferSize; -#endif - void* userPtr; /* ptr for usr data */ - - RGFW_event event; /*!< current event */ - - RGFW_rect r; /*!< the x, y, w and h of the struct */ - - /*! which key RGFW_window_shouldClose checks. Settting this to RGFW_keyNULL disables the feature. */ - RGFW_key exitKey; - RGFW_point _lastMousePoint; /*!< last cusor point (for raw mouse data) */ - - u32 _flags; /*!< windows flags (for RGFW to check) */ - RGFW_rect _oldRect; /*!< rect before fullscreen */ -} RGFW_window; /*!< window structure for managing the window */ - -#if defined(RGFW_X11) || defined(RGFW_MACOS) - typedef u64 RGFW_thread; /*!< thread type unix */ -#else - typedef void* RGFW_thread; /*!< thread type for windows */ -#endif - -/*! scale monitor to window size */ -RGFWDEF RGFW_bool RGFW_monitor_scaleToWindow(RGFW_monitor mon, RGFW_window* win); - -/** * @defgroup Window_management -* @{ */ - - -/*! - * the class name for X11 and WinAPI. apps with the same class will be grouped by the WM - * by default the class name will == the root window's name -*/ -RGFWDEF void RGFW_setClassName(const char* name); -RGFWDEF void RGFW_setXInstName(const char* name); /*!< X11 instance name (window name will by used by default) */ - -/*! (cocoa only) change directory to resource folder */ -RGFWDEF void RGFW_moveToMacOSResourceDir(void); - -/* NOTE: (windows) if the executable has an icon resource named RGFW_ICON, it will be set as the initial icon for the window */ - -RGFWDEF RGFW_window* RGFW_createWindow( - const char* name, /* name of the window */ - RGFW_rect rect, /* rect of window */ - RGFW_windowFlags flags /* extra arguments ((u32)0 means no flags used)*/ -); /*!< function to create a window and struct */ - -RGFWDEF RGFW_window* RGFW_createWindowPtr( - const char* name, /* name of the window */ - RGFW_rect rect, /* rect of window */ - RGFW_windowFlags flags, /* extra arguments (NULL / (u32)0 means no flags used) */ - RGFW_window* win /* ptr to the window struct you want to use */ -); /*!< function to create a window (without allocating a window struct) */ - -RGFWDEF void RGFW_window_initBuffer(RGFW_window* win); -RGFWDEF void RGFW_window_initBufferSize(RGFW_window* win, RGFW_area area); -RGFWDEF void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area); - -/*! set the window flags (will undo flags if they don't match the old ones) */ -RGFWDEF void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags); - -/*! 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 - - 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` -*/ - -RGFWDEF RGFW_event* RGFW_window_checkEvent(RGFW_window* win); /*!< check current event (returns a pointer to win->event or NULL if there is no event)*/ - -/*! - for RGFW_window_eventWait and RGFW_window_checkEvents - waitMS -> Allows the function to keep checking for events even after `RGFW_window_checkEvent == NULL` - if waitMS == 0, the loop will not wait for events - if waitMS > 0, the loop will wait that many miliseconds after there are no more events until it returns - if waitMS == -1 or waitMS == the max size of an unsigned 32-bit int, the loop will not return until it gets another event -*/ -typedef RGFW_ENUM(i32, RGFW_eventWait) { - RGFW_eventNoWait = 0, - RGFW_eventWaitNext = -1 -}; - -/*! sleep until RGFW gets an event or the timer ends (defined by OS) */ -RGFWDEF void RGFW_window_eventWait(RGFW_window* win, i32 waitMS); - -/*! - check all the events until there are none left. - This should only be used if you're using callbacks only -*/ -RGFWDEF void RGFW_window_checkEvents(RGFW_window* win, i32 waitMS); - -/*! - tell RGFW_window_eventWait to stop waiting (to be ran from another thread) -*/ -RGFWDEF void RGFW_stopCheckEvents(void); - -/*! window managment functions */ -RGFWDEF void RGFW_window_close(RGFW_window* win); /*!< close the window and free leftover data */ - -/*! move a window to a given point */ -RGFWDEF void RGFW_window_move(RGFW_window* win, - RGFW_point v /*!< new pos */ -); - -#ifndef RGFW_NO_MONITOR - /*! move window to a specific monitor */ - RGFWDEF void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor m /* monitor */); -#endif - -/*! resize window to a current size/area */ -RGFWDEF void RGFW_window_resize(RGFW_window* win, /*!< source window */ - RGFW_area a /*!< new size */ -); - -/*! set window aspect ratio */ -RGFWDEF void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a); -/*! set the minimum dimensions of a window */ -RGFWDEF void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a); -/*! set the maximum dimensions of a window */ -RGFWDEF void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a); - -RGFWDEF void RGFW_window_focus(RGFW_window* win); /*!< sets the focus to this window */ -RGFWDEF RGFW_bool RGFW_window_isInFocus(RGFW_window* win); /*!< checks the focus to this window */ -RGFWDEF void RGFW_window_raise(RGFW_window* win); /*!< raise the window (to the top) */ -RGFWDEF void RGFW_window_maximize(RGFW_window* win); /*!< maximize the window */ -RGFWDEF void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen); /*!< turn fullscreen on / off for a window */ -RGFWDEF void RGFW_window_center(RGFW_window* win); /*!< center the window */ -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_setFloating(RGFW_window* win, RGFW_bool floating); /*!< make the window a floating window */ -RGFWDEF void RGFW_window_setOpacity(RGFW_window* win, u8 opacity); /*!< sets the opacity of a window */ - -RGFWDEF RGFW_bool RGFW_window_opengl_isSoftware(RGFW_window* win); - -/*! if the window should have a border or not (borderless) based on bool value of `border` */ -RGFWDEF void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border); -RGFWDEF RGFW_bool RGFW_window_borderless(RGFW_window* win); - -/*! turn on / off dnd (RGFW_windowAllowDND stil must be passed to the window)*/ -RGFWDEF void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow); -/*! check if DND is allowed */ -RGFWDEF RGFW_bool RGFW_window_allowsDND(RGFW_window* win); - - -#ifndef RGFW_NO_PASSTHROUGH - /*! turn on / off mouse passthrough */ - RGFWDEF void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough); -#endif - -/*! rename window to a given string */ -RGFWDEF void RGFW_window_setName(RGFW_window* win, - const char* name -); - -RGFWDEF RGFW_bool 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 MAY be resized by default, set both the taskbar and window icon */ - -typedef RGFW_ENUM(u8, RGFW_icon) { - RGFW_iconTaskbar = RGFW_BIT(0), - RGFW_iconWindow = RGFW_BIT(1), - RGFW_iconBoth = RGFW_iconTaskbar | RGFW_iconWindow -}; -RGFWDEF RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* icon, RGFW_area a, i32 channels, u8 type); - -/*!< sets mouse to RGFW_mouse icon (loaded from a bitmap struct) */ -RGFWDEF void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse); - -/*!< 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 RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse); - -RGFWDEF RGFW_bool 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 becomes raw mouse movement data - - this is useful for a 3D camera -*/ -RGFWDEF void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area); -/*! if the mouse is held by RGFW */ -RGFWDEF RGFW_bool RGFW_window_mouseHeld(RGFW_window* win); -/*! stop holding the mouse and let it move freely */ -RGFWDEF void RGFW_window_mouseUnhold(RGFW_window* win); - -/*! hide the window */ -RGFWDEF void RGFW_window_hide(RGFW_window* win); -/*! show the window */ -RGFWDEF void RGFW_window_show(RGFW_window* win); - -/* - makes it so `RGFW_window_shouldClose` returns true or overrides a window close - by modifying window flags -*/ -RGFWDEF void RGFW_window_setShouldClose(RGFW_window* win, RGFW_bool shouldClose); - -/*! where the mouse is on the screen */ -RGFWDEF RGFW_point RGFW_getGlobalMousePoint(void); - -/*! 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, RGFW_bool show); -/*! if the mouse is hidden */ -RGFWDEF RGFW_bool RGFW_window_mouseHidden(RGFW_window* win); -/*! move the mouse to a given point */ -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 RGFW_bool RGFW_window_shouldClose(RGFW_window* win); -/*! if the window is fullscreen */ -RGFWDEF RGFW_bool RGFW_window_isFullscreen(RGFW_window* win); -/*! if the window is hidden */ -RGFWDEF RGFW_bool RGFW_window_isHidden(RGFW_window* win); -/*! if the window is minimized */ -RGFWDEF RGFW_bool RGFW_window_isMinimized(RGFW_window* win); -/*! if the window is maximized */ -RGFWDEF RGFW_bool RGFW_window_isMaximized(RGFW_window* win); -/*! if the window is floating */ -RGFWDEF RGFW_bool RGFW_window_isFloating(RGFW_window* win); -/** @} */ - -/** * @defgroup Monitor -* @{ */ - -#ifndef RGFW_NO_MONITOR -/* - scale the window to the monitor. - This is run by default if the user uses the arg `RGFW_scaleToMonitor` 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 - -/** @} */ - -/** * @defgroup Input -* @{ */ - -/*! 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 RGFW_bool RGFW_isPressed(RGFW_window* win, RGFW_key key); /*!< if key is pressed (key code)*/ - -RGFWDEF RGFW_bool RGFW_wasPressed(RGFW_window* win, RGFW_key key); /*!< if key was pressed (checks previous state only) (key code) */ - -RGFWDEF RGFW_bool RGFW_isHeld(RGFW_window* win, RGFW_key key); /*!< if key is held (key code) */ -RGFWDEF RGFW_bool RGFW_isReleased(RGFW_window* win, RGFW_key key); /*!< if key is released (key code) */ - -/* if a key is pressed and then released, pretty much the same as RGFW_isReleased */ -RGFWDEF RGFW_bool RGFW_isClicked(RGFW_window* win, RGFW_key key /*!< key code */); - -/*! if a mouse button is pressed */ -RGFWDEF RGFW_bool RGFW_isMousePressed(RGFW_window* win, RGFW_mouseButton button /*!< mouse button code */ ); -/*! if a mouse button is held */ -RGFWDEF RGFW_bool RGFW_isMouseHeld(RGFW_window* win, RGFW_mouseButton button /*!< mouse button code */ ); -/*! if a mouse button was released */ -RGFWDEF RGFW_bool RGFW_isMouseReleased(RGFW_window* win, RGFW_mouseButton button /*!< mouse button code */ ); -/*! if a mouse button was pressed (checks previous state only) */ -RGFWDEF RGFW_bool RGFW_wasMousePressed(RGFW_window* win, RGFW_mouseButton button /*!< mouse button code */ ); -/** @} */ - -/** * @defgroup Clipboard -* @{ */ -typedef ptrdiff_t RGFW_ssize_t; - -RGFWDEF const char* RGFW_readClipboard(size_t* size); /*!< read clipboard data */ -/*! read clipboard data or send a NULL str to just get the length of the clipboard data */ -RGFWDEF RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity); -RGFWDEF void RGFW_writeClipboard(const char* text, u32 textLen); /*!< write text to the clipboard */ -/** @} */ - - - -/** * @defgroup error handling -* @{ */ -typedef RGFW_ENUM(u8, RGFW_debugType) { - RGFW_typeError = 0, RGFW_typeWarning, RGFW_typeInfo -}; - -typedef RGFW_ENUM(u8, RGFW_errorCode) { - RGFW_noError = 0, /*!< no error */ - RGFW_errOpenglContext, RGFW_errEGLContext, /*!< error with the OpenGL context */ - RGFW_errWayland, - RGFW_errDirectXContext, - RGFW_errIOKit, - RGFW_errClipboard, - RGFW_errFailedFuncLoad, - RGFW_errBuffer, - RGFW_infoMonitor, RGFW_infoWindow, RGFW_infoBuffer, RGFW_infoGlobal, RGFW_infoOpenGL, - RGFW_warningWayland, RGFW_warningOpenGL -}; - -typedef struct RGFW_debugContext { RGFW_window* win; RGFW_monitor* monitor; u32 srcError; } RGFW_debugContext; - -#if defined(__cplusplus) && !defined(__APPLE__) -#define RGFW_DEBUG_CTX(win, err) {win, NULL, err} -#define RGFW_DEBUG_CTX_MON(monitor) {_RGFW.root, &monitor, 0} -#else -#define RGFW_DEBUG_CTX(win, err) (RGFW_debugContext){win, NULL, err} -#define RGFW_DEBUG_CTX_MON(monitor) (RGFW_debugContext){_RGFW.root, &monitor, 0} -#endif - -typedef void (* RGFW_debugfunc)(RGFW_debugType type, RGFW_errorCode err, RGFW_debugContext ctx, const char* msg); -RGFWDEF RGFW_debugfunc RGFW_setDebugCallback(RGFW_debugfunc func); -RGFWDEF void RGFW_sendDebugInfo(RGFW_debugType type, RGFW_errorCode err, RGFW_debugContext ctx, const char* msg); -/** @} */ - -/** - - - event callbacks. - These are completely optional, so you can use the normal - RGFW_checkEvent() method if you prefer that - -* @defgroup Callbacks -* @{ -*/ - -/*! RGFW_windowMoved, the window and its new rect value */ -typedef void (* RGFW_windowMovedfunc)(RGFW_window* win, RGFW_rect r); -/*! RGFW_windowResized, the window and its new rect value */ -typedef void (* RGFW_windowResizedfunc)(RGFW_window* win, RGFW_rect r); -/*! RGFW_windowRestored, the window and its new rect value */ -typedef void (* RGFW_windowRestoredfunc)(RGFW_window* win, RGFW_rect r); -/*! RGFW_windowMaximized, the window and its new rect value */ -typedef void (* RGFW_windowMaximizedfunc)(RGFW_window* win, RGFW_rect r); -/*! RGFW_windowMinimized, the window and its new rect value */ -typedef void (* RGFW_windowMinimizedfunc)(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 in focus */ -typedef void (* RGFW_focusfunc)(RGFW_window* win, RGFW_bool 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, RGFW_bool 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_point vector); -/*! RGFW_DNDInit, 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 mapped key, the physical key, the string version, the state of the mod keys, if it was a press (else it's a release) */ -typedef void (* RGFW_keyfunc)(RGFW_window* win, u8 key, u8 keyChar, RGFW_keymod keyMod, RGFW_bool 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, RGFW_mouseButton button, double scroll, RGFW_bool pressed); -/*! RGFW_gamepadButtonPressed, 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_gamepadButtonfunc)(RGFW_window* win, u16 gamepad, u8 button, RGFW_bool pressed); -/*! RGFW_gamepadAxisMove, the window that got the event, the gamepad in question, the axis values and the axis count */ -typedef void (* RGFW_gamepadAxisfunc)(RGFW_window* win, u16 gamepad, RGFW_point axis[2], u8 axisesCount, u8 whichAxis); -/*! RGFW_gamepadConnected / RGFW_gamepadDisconnected, the window that got the event, the gamepad in question, if the controller was connected (else it was disconnected) */ -typedef void (* RGFW_gamepadfunc)(RGFW_window* win, u16 gamepad, RGFW_bool connected); -/*! RGFW_dnd, the window that had the drop, the drop data and the number of files dropped */ -typedef void (* RGFW_dndfunc)(RGFW_window* win, char** droppedFiles, size_t droppedFilesCount); -/*! RGFW_scaleUpdated, the window the event was sent to, content scaleX, content scaleY */ -typedef void (* RGFW_scaleUpdatedfunc)(RGFW_window* win, float scaleX, float scaleY); - -/*! set callback for a window move event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_windowMovedfunc RGFW_setWindowMovedCallback(RGFW_windowMovedfunc func); -/*! set callback for a window resize event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_windowResizedfunc RGFW_setWindowResizedCallback(RGFW_windowResizedfunc func); -/*! set callback for a window quit event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_windowQuitfunc RGFW_setWindowQuitCallback(RGFW_windowQuitfunc func); -/*! set callback for a mouse move event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_mousePosfunc RGFW_setMousePosCallback(RGFW_mousePosfunc func); -/*! set callback for a window refresh event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_windowRefreshfunc RGFW_setWindowRefreshCallback(RGFW_windowRefreshfunc func); -/*! set callback for a window focus change event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_focusfunc RGFW_setFocusCallback(RGFW_focusfunc func); -/*! set callback for a mouse notify event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_mouseNotifyfunc RGFW_setMouseNotifyCallback(RGFW_mouseNotifyfunc func); -/*! set callback for a drop event event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_dndfunc RGFW_setDndCallback(RGFW_dndfunc func); -/*! set callback for a start of a drop event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_dndInitfunc RGFW_setDndInitCallback(RGFW_dndInitfunc func); -/*! set callback for a key (press / release) event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_keyfunc RGFW_setKeyCallback(RGFW_keyfunc func); -/*! set callback for a mouse button (press / release) event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_mouseButtonfunc RGFW_setMouseButtonCallback(RGFW_mouseButtonfunc func); -/*! set callback for a controller button (press / release) event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_gamepadButtonfunc RGFW_setGamepadButtonCallback(RGFW_gamepadButtonfunc func); -/*! set callback for a gamepad axis move event. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_gamepadAxisfunc RGFW_setGamepadAxisCallback(RGFW_gamepadAxisfunc func); -/*! set callback for when a controller is connected or disconnected. Returns the previous callback function (if it was set) */ -RGFWDEF RGFW_gamepadfunc RGFW_setGamepadCallback(RGFW_gamepadfunc func); -/*! set call back for when window is maximized. Returns the previous callback function (if it was set) */ -RGFWDEF RGFW_windowResizedfunc RGFW_setWindowMaximizedCallback(RGFW_windowResizedfunc func); -/*! set call back for when window is minimized. Returns the previous callback function (if it was set) */ -RGFWDEF RGFW_windowResizedfunc RGFW_setWindowMinimizedCallback(RGFW_windowResizedfunc func); -/*! set call back for when window is restored. Returns the previous callback function (if it was set) */ -RGFWDEF RGFW_windowResizedfunc RGFW_setWindowRestoredCallback(RGFW_windowResizedfunc func); -/*! set callback for when the DPI changes. Returns previous callback function (if it was set) */ -RGFWDEF RGFW_scaleUpdatedfunc RGFW_setScaleUpdatedCallback(RGFW_scaleUpdatedfunc func); -/** @} */ - -/** * @defgroup Threads -* @{ */ - -#ifndef RGFW_NO_THREADS -/*! threading functions */ - -/*! NOTE! (for X11/linux) : if you define a window in a thread, it must be run after the original thread's window is created or else there will be a memory error */ -/* - I'd suggest you use sili's threading functions instead - if you're going to use sili - which is a good idea generally -*/ - -#if defined(__unix__) || defined(__APPLE__) || defined(RGFW_WASM) || defined(RGFW_CUSTOM_BACKEND) - typedef void* (* RGFW_threadFunc_ptr)(void*); -#else - typedef DWORD (__stdcall *RGFW_threadFunc_ptr) (LPVOID lpThreadParameter); -#endif - -RGFWDEF RGFW_thread RGFW_createThread(RGFW_threadFunc_ptr ptr, void* args); /*!< create a thread */ -RGFWDEF void RGFW_cancelThread(RGFW_thread thread); /*!< cancels a thread */ -RGFWDEF void RGFW_joinThread(RGFW_thread thread); /*!< join thread to current thread */ -RGFWDEF void RGFW_setThreadPriority(RGFW_thread thread, u8 priority); /*!< sets the priority priority */ -#endif - -/** @} */ - -/** * @defgroup gamepad -* @{ */ - -typedef RGFW_ENUM(u8, RGFW_gamepadType) { - RGFW_gamepadMicrosoft = 0, RGFW_gamepadSony, RGFW_gamepadNintendo, RGFW_gamepadLogitech, RGFW_gamepadUnknown -}; - -/*! gamepad count starts at 0*/ -RGFWDEF u32 RGFW_isPressedGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button); -RGFWDEF u32 RGFW_isReleasedGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button); -RGFWDEF u32 RGFW_isHeldGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button); -RGFWDEF u32 RGFW_wasPressedGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button); -RGFWDEF RGFW_point RGFW_getGamepadAxis(RGFW_window* win, u16 controller, u16 whichAxis); -RGFWDEF const char* RGFW_getGamepadName(RGFW_window* win, u16 controller); -RGFWDEF size_t RGFW_getGamepadCount(RGFW_window* win); -RGFWDEF RGFW_gamepadType RGFW_getGamepadType(RGFW_window* win, u16 controller); - -/** @} */ - -/** * @defgroup graphics_API -* @{ */ - -/*!< make the window the current opengl drawing context - - NOTE: - if you want to switch the graphics context's thread, - you have to run RGFW_window_makeCurrent(NULL); on the old thread - then RGFW_window_makeCurrent(valid_window) on the new thread -*/ -RGFWDEF void RGFW_window_makeCurrent(RGFW_window* win); - -/*! get current RGFW window graphics context */ -RGFWDEF RGFW_window* RGFW_getCurrent(void); - -/* 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); -/*!< render the software rendering buffer (this is called by RGFW_window_swapInterval) */ -RGFWDEF void RGFW_window_swapBuffers_software(RGFW_window* win); - -typedef void (*RGFW_proc)(void); /* function pointer equivalent of void* */ - -/*! native API functions */ -#if defined(RGFW_OPENGL) || defined(RGFW_EGL) -/*!< create an opengl context for the RGFW window, run by createWindow by default (unless the RGFW_windowNoInitAPI is included) */ -RGFWDEF void RGFW_window_initOpenGL(RGFW_window* win); -/*!< called by `RGFW_window_close` by default (unless the RGFW_windowNoInitAPI is set) */ -RGFWDEF void RGFW_window_freeOpenGL(RGFW_window* win); - -/*! OpenGL init hints */ -typedef RGFW_ENUM(u8, RGFW_glHints) { - RGFW_glStencil = 0, /*!< set stencil buffer bit size (8 by default) */ - RGFW_glSamples, /*!< set number of sampiling buffers (4 by default) */ - RGFW_glStereo, /*!< use GL_STEREO (GL_FALSE by default) */ - RGFW_glAuxBuffers, /*!< number of aux buffers (0 by default) */ - RGFW_glDoubleBuffer, /*!< request double buffering */ - RGFW_glRed, RGFW_glGreen, RGFW_glBlue, RGFW_glAlpha, /*!< set RGBA bit sizes */ - RGFW_glDepth, - RGFW_glAccumRed, RGFW_glAccumGreen, RGFW_glAccumBlue,RGFW_glAccumAlpha, /*!< set accumulated RGBA bit sizes */ - RGFW_glSRGB, /*!< request sRGA */ - RGFW_glRobustness, /*!< request a robust context */ - RGFW_glDebug, /*!< request opengl debugging */ - RGFW_glNoError, /*!< request no opengl errors */ - RGFW_glReleaseBehavior, - RGFW_glProfile, - RGFW_glMajor, RGFW_glMinor, - RGFW_glFinalHint = 32, /*!< the final hint (not for setting) */ - RGFW_releaseFlush = 0, RGFW_glReleaseNone, /* RGFW_glReleaseBehavior options */ - RGFW_glCore = 0, RGFW_glCompatibility /*!< RGFW_glProfile options */ -}; -RGFWDEF void RGFW_setGLHint(RGFW_glHints hint, i32 value); -RGFWDEF RGFW_bool RGFW_extensionSupported(const char* extension, size_t len); /*!< check if whether the specified API extension is supported by the current OpenGL or OpenGL ES context */ -RGFWDEF RGFW_proc 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 */ -RGFWDEF void RGFW_window_swapBuffers_OpenGL(RGFW_window* win); /*!< swap opengl buffer (only) called by RGFW_window_swapInterval */ -void* RGFW_getCurrent_OpenGL(void); /*!< get the current context (OpenGL backend (GLX) (WGL) (EGL) (cocoa) (webgl))*/ - -RGFWDEF RGFW_bool RGFW_extensionSupportedPlatform(const char* extension, size_t len); /*!< check if whether the specified platform-specific API extension is supported by the current OpenGL or OpenGL ES context */ -#endif #ifdef RGFW_VULKAN + #if defined(RGFW_WAYLAND) && defined(RGFW_X11) - #define VK_USE_PLATFORM_WAYLAND_KHR - #define VK_USE_PLATFORM_XLIB_KHR - #define RGFW_VK_SURFACE ((RGFW_usingWayland()) ? ("VK_KHR_wayland_surface") : ("VK_KHR_xlib_surface")) - #elif defined(RGFW_WAYLAND) #define VK_USE_PLATFORM_WAYLAND_KHR #define VK_USE_PLATFORM_XLIB_KHR - #define RGFW_VK_SURFACE "VK_KHR_wayland_surface" - #elif defined(RGFW_X11) + #define RGFW_VK_SURFACE ((RGFW_usingWayland()) ? ("VK_KHR_wayland_surface") : ("VK_KHR_xlib_surface")) + #elif defined(RGFW_WAYLAND) + #define VK_USE_PLATFORM_WAYLAND_KHR + #define VK_USE_PLATFORM_XLIB_KHR + #define RGFW_VK_SURFACE "VK_KHR_wayland_surface" + #elif defined(RGFW_X11) #define VK_USE_PLATFORM_XLIB_KHR #define RGFW_VK_SURFACE "VK_KHR_xlib_surface" #elif defined(RGFW_WINDOWS) @@ -1337,63 +438,39 @@ RGFWDEF RGFW_bool RGFW_extensionSupportedPlatform(const char* extension, size_t #define RGFW_VK_SURFACE NULL #endif -/* if you don't want to use the above macros */ -RGFWDEF const char** RGFW_getVKRequiredInstanceExtensions(size_t* count); /*!< gets (static) extension array (and size (which will be 2)) */ - -#include - -RGFWDEF VkResult RGFW_window_createVKSurface(RGFW_window* win, VkInstance instance, VkSurfaceKHR* surface); -RGFWDEF RGFW_bool RGFW_getVKPresentationSupport(VkInstance instance, VkPhysicalDevice physicalDevice, u32 queueFamilyIndex); -#endif -#ifdef RGFW_DIRECTX -#ifndef RGFW_WINDOWS - #undef RGFW_DIRECTX -#else - #define OEMRESOURCE - #include - - #ifndef __cplusplus - #define __uuidof(T) IID_##T - #endif -RGFWDEF int RGFW_window_createDXSwapChain(RGFW_window* win, IDXGIFactory* pFactory, IUnknown* pDevice, IDXGISwapChain** swapchain); -#endif #endif -/** @} */ -/** * @defgroup Supporting -* @{ */ +/*! @brief The stucture that contains information about the current RGFW instance */ +typedef struct RGFW_info RGFW_info; -/*! optional init/deinit function */ -RGFWDEF i32 RGFW_init(void); /*!< is called by default when the first window is created by default */ -RGFWDEF void RGFW_deinit(void); /*!< is called by default when the last open window is closed */ +/*! @brief The window stucture for interfacing with the window */ +typedef struct RGFW_window RGFW_window; -RGFWDEF double RGFW_getTime(void); /*!< get time in seconds since RGFW_setTime, which ran when the first window is open */ -RGFWDEF u64 RGFW_getTimeNS(void); /*!< get time in nanoseconds RGFW_setTime, which ran when the first window is open */ -RGFWDEF void RGFW_sleep(u64 milisecond); /*!< sleep for a set time */ -RGFWDEF void RGFW_setTime(double time); /*!< set timer in seconds */ -RGFWDEF u64 RGFW_getTimerValue(void); /*!< get API timer value */ -RGFWDEF u64 RGFW_getTimerFreq(void); /*!< get API time freq */ +/*! @brief The source window stucture for interfacing with the underlying windowing API (e.g. winapi, wayland, cocoa, etc) */ +typedef struct RGFW_window_src RGFW_window_src; -/*< updates fps / sets fps to cap (must by ran manually by the user at the end of a frame), returns current fps */ -RGFWDEF u32 RGFW_checkFPS(double startTime, u32 frameCount, u32 fpsCap); +/*! @brief The color format for pixel data */ +typedef RGFW_ENUM(u8, RGFW_format) { + RGFW_formatRGB8 = 0, /*!< 8-bit RGB (3 channels) */ + RGFW_formatBGR8, /*!< 8-bit BGR (3 channels) */ + RGFW_formatRGBA8, /*!< 8-bit RGBA (4 channels) */ + RGFW_formatARGB8, /*!< 8-bit RGBA (4 channels) */ + RGFW_formatBGRA8, /*!< 8-bit BGRA (4 channels) */ + RGFW_formatABGR8, /*!< 8-bit BGRA (4 channels) */ + RGFW_formatCount +}; -/*!< change which window is the root window */ -RGFWDEF void RGFW_setRootWindow(RGFW_window* win); -RGFWDEF RGFW_window* RGFW_getRootWindow(void); +/*! @brief a stucture for interfacing with the underlying native image (e.g. XImage, HBITMAP, etc) */ +typedef struct RGFW_nativeImage RGFW_nativeImage; -/*! standard event queue, used for injecting events and returning source API callback events like any other queue check */ -/* these are all used internally by RGFW */ -void RGFW_eventQueuePush(RGFW_event event); -RGFW_event* RGFW_eventQueuePop(RGFW_window* win); +/*! @brief a stucture for interfacing with pixel data as a renderable surface */ +typedef struct RGFW_surface RGFW_surface; -/* for C++ / C89 */ -#define RGFW_eventQueuePushEx(eventInit) { RGFW_event e; eventInit; RGFW_eventQueuePush(e); } +/*! a raw pointer to the underlying mouse handle for setting and creating custom mouse icons */ +typedef void RGFW_mouse; -/*! - key codes and mouse icon enums -*/ -#undef RGFW_key +/*! @brief RGFW's abstract keycodes */ typedef RGFW_ENUM(u8, RGFW_key) { RGFW_keyNULL = 0, RGFW_escape = '\033', @@ -1408,13 +485,11 @@ typedef RGFW_ENUM(u8, RGFW_key) { RGFW_7 = '7', RGFW_8 = '8', RGFW_9 = '9', - RGFW_minus = '-', RGFW_equals = '=', RGFW_backSpace = '\b', RGFW_tab = '\t', RGFW_space = ' ', - RGFW_a = 'a', RGFW_b = 'b', RGFW_c = 'c', @@ -1441,20 +516,17 @@ typedef RGFW_ENUM(u8, RGFW_key) { RGFW_x = 'x', RGFW_y = 'y', RGFW_z = 'z', - RGFW_period = '.', RGFW_comma = ',', RGFW_slash = '/', RGFW_bracket = '[', - RGFW_closeBracket = ']', + RGFW_closeBracket = ']', RGFW_semicolon = ';', RGFW_apostrophe = '\'', RGFW_backSlash = '\\', RGFW_return = '\n', RGFW_enter = RGFW_return, - RGFW_delete = '\177', /* 127 */ - RGFW_F1, RGFW_F2, RGFW_F3, @@ -1467,7 +539,19 @@ typedef RGFW_ENUM(u8, RGFW_key) { RGFW_F10, RGFW_F11, RGFW_F12, - + RGFW_F13, + RGFW_F14, + RGFW_F15, + RGFW_F16, + RGFW_F17, + RGFW_F18, + RGFW_F19, + RGFW_F20, + RGFW_F21, + RGFW_F22, + RGFW_F23, + RGFW_F24, + RGFW_F25, RGFW_capsLock, RGFW_shiftL, RGFW_controlL, @@ -1482,41 +566,262 @@ typedef RGFW_ENUM(u8, RGFW_key) { RGFW_left, RGFW_right, RGFW_insert, + RGFW_menu, RGFW_end, RGFW_home, RGFW_pageUp, RGFW_pageDown, - 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_kpSlash, + RGFW_kpMultiply, + RGFW_kpPlus, + RGFW_kpMinus, + RGFW_kpEqual, + RGFW_kp1, + RGFW_kp2, + RGFW_kp3, + RGFW_kp4, + RGFW_kp5, + RGFW_kp6, + RGFW_kp7, + RGFW_kp8, + RGFW_kp9, + RGFW_kp0, + RGFW_kpPeriod, + RGFW_kpReturn, RGFW_scrollLock, RGFW_printScreen, RGFW_pause, + RGFW_world1, + RGFW_world2, RGFW_keyLast = 256 /* padding for alignment ~(175 by default) */ - }; +}; + +/*! @brief abstract mouse button codes */ +typedef RGFW_ENUM(u8, RGFW_mouseButton) { + RGFW_mouseLeft = 0, /*!< left mouse button is pressed */ + RGFW_mouseMiddle, /*!< mouse-wheel-button is pressed */ + RGFW_mouseRight, /*!< right mouse button is pressed */ + RGFW_mouseMisc1, RGFW_mouseMisc2, RGFW_mouseMisc3, RGFW_mouseMisc4, RGFW_mouseMisc5, + RGFW_mouseFinal +}; + +/*! abstract key modifier codes */ +typedef RGFW_ENUM(u8, RGFW_keymod) { + RGFW_modCapsLock = RGFW_BIT(0), + RGFW_modNumLock = RGFW_BIT(1), + RGFW_modControl = RGFW_BIT(2), + RGFW_modAlt = RGFW_BIT(3), + RGFW_modShift = RGFW_BIT(4), + RGFW_modSuper = RGFW_BIT(5), + RGFW_modScrollLock = RGFW_BIT(6) +}; + +/*! @brief codes for the event types that can be sent */ +typedef RGFW_ENUM(u8, RGFW_eventType) { + RGFW_eventNone = 0, /*!< no event has been sent */ + RGFW_keyPressed, /* a key has been pressed */ + RGFW_keyReleased, /*!< a key has been released */ + /*! key event note + the code of the key pressed is stored in + RGFW_event.key.value + !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! + + while a string version is stored in + RGFW_event.key.valueString + + RGFW_event.key.mod holds the current mod + this means if CapsLock, NumLock are active or not + */ + RGFW_mouseButtonPressed, /*!< a mouse button has been pressed (left,middle,right) */ + RGFW_mouseButtonReleased, /*!< a mouse button has been released (left,middle,right) */ + RGFW_mouseScroll, /*!< a mouse scroll event */ + RGFW_mousePosChanged, /*!< the position of the mouse has been changed */ + /*! mouse event note + the x and y of the mouse can be found in the vector, RGFW_x, y + + RGFW_event.button.value holds which mouse button was pressed + */ + RGFW_windowMoved, /*!< the window was moved (by the user) */ + RGFW_windowResized, /*!< the window was resized (by the user), [on WASM this means the browser was resized] */ + RGFW_focusIn, /*!< window is in focus now */ + RGFW_focusOut, /*!< window is out of focus now */ + RGFW_mouseEnter, /* mouse entered the window */ + RGFW_mouseLeave, /* mouse left the window */ + RGFW_windowRefresh, /* The window content needs to be refreshed */ + + /* attribs change event note + The event data is sent straight to the window structure + with win->x, win->y, win->w and win->h + */ + RGFW_quit, /*!< the user clicked the quit button */ + RGFW_dataDrop, /*!< a file has been dropped into the window */ + RGFW_dataDrag, /*!< the start of a drag and drop event, when the file is being dragged */ + /* drop data note + The x and y coords of the drop are stored in the vector RGFW_x, y + + RGFW_event.drop.count holds how many files were dropped + + This is also the size of the array which stores all the dropped file string, + RGFW_event.drop.files + */ + RGFW_windowMaximized, /*!< the window was maximized */ + RGFW_windowMinimized, /*!< the window was minimized */ + RGFW_windowRestored, /*!< the window was restored */ + RGFW_scaleUpdated /*!< content scale factor changed */ +}; + +/*! @brief flags for toggling wether or not an event should be processed */ +typedef RGFW_ENUM(u32, RGFW_eventFlag) { + RGFW_keyPressedFlag = RGFW_BIT(RGFW_keyPressed), + RGFW_keyReleasedFlag = RGFW_BIT(RGFW_keyReleased), + RGFW_mouseScrollFlag = RGFW_BIT(RGFW_mouseScroll), + RGFW_mouseButtonPressedFlag = RGFW_BIT(RGFW_mouseButtonPressed), + RGFW_mouseButtonReleasedFlag = RGFW_BIT(RGFW_mouseButtonReleased), + RGFW_mousePosChangedFlag = RGFW_BIT(RGFW_mousePosChanged), + RGFW_mouseEnterFlag = RGFW_BIT(RGFW_mouseEnter), + RGFW_mouseLeaveFlag = RGFW_BIT(RGFW_mouseLeave), + RGFW_windowMovedFlag = RGFW_BIT(RGFW_windowMoved), + RGFW_windowResizedFlag = RGFW_BIT(RGFW_windowResized), + RGFW_focusInFlag = RGFW_BIT(RGFW_focusIn), + RGFW_focusOutFlag = RGFW_BIT(RGFW_focusOut), + RGFW_windowRefreshFlag = RGFW_BIT(RGFW_windowRefresh), + RGFW_windowMaximizedFlag = RGFW_BIT(RGFW_windowMaximized), + RGFW_windowMinimizedFlag = RGFW_BIT(RGFW_windowMinimized), + RGFW_windowRestoredFlag = RGFW_BIT(RGFW_windowRestored), + RGFW_scaleUpdatedFlag = RGFW_BIT(RGFW_scaleUpdated), + RGFW_quitFlag = RGFW_BIT(RGFW_quit), + RGFW_dataDropFlag = RGFW_BIT(RGFW_dataDrop), + RGFW_dataDragFlag = RGFW_BIT(RGFW_dataDrag), + + RGFW_keyEventsFlag = RGFW_keyPressedFlag | RGFW_keyReleasedFlag, + RGFW_mouseEventsFlag = RGFW_mouseButtonPressedFlag | RGFW_mouseButtonReleasedFlag | RGFW_mousePosChangedFlag | RGFW_mouseEnterFlag | RGFW_mouseLeaveFlag | RGFW_mouseScrollFlag , + RGFW_windowEventsFlag = RGFW_windowMovedFlag | RGFW_windowResizedFlag | RGFW_windowRefreshFlag | RGFW_windowMaximizedFlag | RGFW_windowMinimizedFlag | RGFW_windowRestoredFlag | RGFW_scaleUpdatedFlag, + RGFW_focusEventsFlag = RGFW_focusInFlag | RGFW_focusOutFlag, + RGFW_dataDropEventsFlag = RGFW_dataDropFlag | RGFW_dataDragFlag, + RGFW_allEventFlags = RGFW_keyEventsFlag | RGFW_mouseEventsFlag | RGFW_windowEventsFlag | RGFW_focusEventsFlag | RGFW_dataDropEventsFlag | RGFW_quitFlag +}; + +/*! Event structure(s) and union for checking/getting events */ + +/*! @brief common event data across all events */ +typedef struct RGFW_commonEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies too (for event queue events) */ +} RGFW_commonEvent; + +/*! @brief event data for any mouse button event (press/release) */ +typedef struct RGFW_mouseButtonEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies too (for event queue events) */ + u8 value; /* !< which mouse button was pressed */ +} RGFW_mouseButtonEvent; + +/*! @brief event data for any mouse scroll event */ +typedef struct RGFW_mouseScrollEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies too (for event queue events) */ + float x, y; /*!< the raw mouse scroll value */ +} RGFW_mouseScrollEvent; + +/*! @brief event data for any mouse position event (RGFW_mousePosChanged) */ +typedef struct RGFW_mousePosEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies too (for event queue events) */ + i32 x, y; /*!< mouse x, y of event (or drop point) */ + float vecX, vecY; /*!< raw mouse movement */ +} RGFW_mousePosEvent; + +/*! @brief event data for any key event (press/release) */ +typedef struct RGFW_keyEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies too (for event queue events) */ + RGFW_key value; /*!< the physical key of the event, refers to where key is physically !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! */ + u8 sym; /*!< mapped key char of the event */ + RGFW_bool repeat; /*!< key press event repeated (the key is being held) */ + RGFW_keymod mod; +} RGFW_keyEvent; + +/*! @brief event data for any data drop event */ +typedef struct RGFW_dataDropEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies too (for event queue events) */ + /* 260 max paths with a max length of 260 */ + char** files; /*!< dropped files */ + size_t count; /*!< how many files were dropped */ +} RGFW_dataDropEvent; + +/*! @brief event data for any data drag event */ +typedef struct RGFW_dataDragEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies too (for event queue events) */ + i32 x, y; /*!< mouse x, y of event (or drop point) */ +} RGFW_dataDragEvent; + +/*! @brief event data for when the window scale (DPI) is updated */ +typedef struct RGFW_scaleUpdatedEvent { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_window* win; /*!< the window this event applies too (for event queue events) */ + float x, y; /*!< DPI scaling */ +} RGFW_scaleUpdatedEvent; + +/*! @brief union for all of the event stucture types */ +typedef union RGFW_event { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_commonEvent common; /*!< common event data (e.g.) type and win */ + RGFW_mouseButtonEvent button; /*!< data for a button press/release */ + RGFW_mouseScrollEvent scroll; /*!< data for a mouse scroll */ + RGFW_mousePosEvent mouse; /*!< data for mouse motion events */ + RGFW_keyEvent key; /*!< data for key press/release/hold events */ + RGFW_dataDropEvent drop; /*!< dropping a file events */ + RGFW_dataDragEvent drag; /* data for dragging a file events */ + RGFW_scaleUpdatedEvent scale; /* data for monitor scaling events */ +} RGFW_event; + +/*! + @!brief codes for for RGFW_the code is stupid and C++ waitForEvent + waitMS -> Allows the function to keep checking for events even after there are no more events + if waitMS == 0, the loop will not wait for events + if waitMS > 0, the loop will wait that many miliseconds after there are no more events until it returns + if waitMS == -1 or waitMS == the max size of an unsigned 32-bit int, the loop will not return until it gets another event +*/ +typedef RGFW_ENUM(i32, RGFW_eventWait) { + RGFW_eventNoWait = 0, + RGFW_eventWaitNext = -1 +}; -/*! converts api keycode to the RGFW unmapped/physical key */ -RGFWDEF u32 RGFW_apiKeyToRGFW(u32 keycode); -/*! converts RGFW keycode to the unmapped/physical api key */ -RGFWDEF u32 RGFW_rgfwToApiKey(u32 keycode); -/*! converts RGFW keycode to the mapped keychar */ -RGFWDEF u8 RGFW_rgfwToKeyChar(u32 keycode); +/*! @brief optional bitwise arguments for making a windows, these can be OR'd together */ +typedef RGFW_ENUM(u32, RGFW_windowFlags) { + RGFW_windowNoBorder = RGFW_BIT(0), /*!< the window doesn't have a border */ + RGFW_windowNoResize = RGFW_BIT(1), /*!< the window cannot be resized by the user */ + RGFW_windowAllowDND = RGFW_BIT(2), /*!< the window supports drag and drop */ + RGFW_windowHideMouse = RGFW_BIT(3), /*! the window should hide the mouse (can be toggled later on using `RGFW_window_showMouse`) */ + RGFW_windowFullscreen = RGFW_BIT(4), /*!< the window is fullscreen by default */ + RGFW_windowTransparent = RGFW_BIT(5), /*!< the window is transparent (only properly works on X11 and MacOS, although it's meant for for windows) */ + RGFW_windowCenter = RGFW_BIT(6), /*! center the window on the screen */ + RGFW_windowScaleToMonitor = RGFW_BIT(8), /*! scale the window to the screen */ + RGFW_windowHide = RGFW_BIT(9), /*! the window is hidden */ + RGFW_windowMaximize = RGFW_BIT(10), /*!< maximize the window on creation */ + RGFW_windowCenterCursor = RGFW_BIT(11), /*!< center the cursor to the window on creation */ + RGFW_windowFloating = RGFW_BIT(12), /*!< create a floating window */ + RGFW_windowFocusOnShow = RGFW_BIT(13), /*!< focus the window when it's shown */ + RGFW_windowMinimize = RGFW_BIT(14), /*!< focus the window when it's shown */ + RGFW_windowFocus = RGFW_BIT(15), /*!< if the window is in focus */ + RGFW_windowOpenGL = RGFW_BIT(17), /*!< create an OpenGL context (you can also do this manually with RGFW_window_createContext_OpenGL) */ + RGFW_windowEGL = RGFW_BIT(18), /*!< create an EGL context (you can also do this manually with RGFW_window_createContext_EGL) */ + RGFW_windowedFullscreen = RGFW_windowNoBorder | RGFW_windowMaximize +}; + +/*! @brief the types of icon to set */ +typedef RGFW_ENUM(u8, RGFW_icon) { + RGFW_iconTaskbar = RGFW_BIT(0), + RGFW_iconWindow = RGFW_BIT(1), + RGFW_iconBoth = RGFW_iconTaskbar | RGFW_iconWindow +}; + +/*! @brief standard mouse icons */ typedef RGFW_ENUM(u8, RGFW_mouseIcons) { RGFW_mouseNormal = 0, RGFW_mouseArrow, @@ -1529,46 +834,2188 @@ typedef RGFW_ENUM(u8, RGFW_mouseIcons) { RGFW_mouseResizeNESW, RGFW_mouseResizeAll, RGFW_mouseNotAllowed, + RGFW_mouseIconCount, RGFW_mouseIconFinal = 16 /* padding for alignment */ }; + +/*! @brief the type of debug message */ +typedef RGFW_ENUM(u8, RGFW_debugType) { + RGFW_typeError = 0, RGFW_typeWarning, RGFW_typeInfo +}; + +/*! @brief error codes for known failure types */ +typedef RGFW_ENUM(u8, RGFW_errorCode) { + RGFW_noError = 0, /*!< no error */ + RGFW_errOutOfMemory, + RGFW_errOpenGLContext, RGFW_errEGLContext, /*!< error with the OpenGL context */ + RGFW_errWayland, RGFW_errX11, + RGFW_errDirectXContext, + RGFW_errIOKit, + RGFW_errClipboard, + RGFW_errFailedFuncLoad, + RGFW_errBuffer, + RGFW_errEventQueue, + RGFW_infoMonitor, RGFW_infoWindow, RGFW_infoBuffer, RGFW_infoGlobal, RGFW_infoOpenGL, + RGFW_warningWayland, RGFW_warningOpenGL +}; + +/*! @brief callback function type for debug messags */ +typedef void (* RGFW_debugfunc)(RGFW_debugType type, RGFW_errorCode err, const char* msg); + +/*! @brief RGFW_windowMoved, the window and its new rect value */ +typedef void (* RGFW_windowMovedfunc)(RGFW_window* win, i32 x, i32 y); +/*! @brief RGFW_windowResized, the window and its new rect value */ +typedef void (* RGFW_windowResizedfunc)(RGFW_window* win, i32 w, i32 h); +/*! @brief RGFW_windowRestored, the window and its new rect value */ +typedef void (* RGFW_windowRestoredfunc)(RGFW_window* win, i32 x, i32 y, i32 w, i32 h); +/*! @brief RGFW_windowMaximized, the window and its new rect value */ +typedef void (* RGFW_windowMaximizedfunc)(RGFW_window* win, i32 x, i32 y, i32 w, i32 h); +/*! @brief RGFW_windowMinimized, the window and its new rect value */ +typedef void (* RGFW_windowMinimizedfunc)(RGFW_window* win); +/*! @brief RGFW_quit, the window that was closed */ +typedef void (* RGFW_windowQuitfunc)(RGFW_window* win); +/*! @brief RGFW_focusIn / RGFW_focusOut, the window who's focus has changed and if its in focus */ +typedef void (* RGFW_focusfunc)(RGFW_window* win, RGFW_bool inFocus); +/*! @brief 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, i32 x, i32 y, RGFW_bool status); +/*! @brief RGFW_mousePosChanged, the window that the move happened on, and the new point of the mouse */ +typedef void (* RGFW_mousePosfunc)(RGFW_window* win, i32 x, i32 y, float vecX, float vecY); +/*! @brief RGFW_dataDrag, the window, the point of the drop on the windows */ +typedef void (* RGFW_dataDragfunc)(RGFW_window* win, i32 x, i32 y); +/*! @brief RGFW_windowRefresh, the window that needs to be refreshed */ +typedef void (* RGFW_windowRefreshfunc)(RGFW_window* win); +/*! @brief RGFW_keyPressed / RGFW_keyReleased, the window that got the event, the mapped key, the physical key, the string version, the state of the mod keys, if it was a press (else it's a release) */ +typedef void (* RGFW_keyfunc)(RGFW_window* win, u8 key, u8 sym, RGFW_keymod mod, RGFW_bool repeat, RGFW_bool pressed); +/*! @brief 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, RGFW_mouseButton button, RGFW_bool pressed); +/*! @brief RGFW_mouseScroll, the window that got the event, the x scroll value, the y scroll value */ +typedef void (* RGFW_mouseScrollfunc)(RGFW_window* win, float x, float y); +/*! @brief RGFW_dataDrop the window that had the drop, the drop data and the number of files dropped */ +typedef void (* RGFW_dataDropfunc)(RGFW_window* win, char** files, size_t count); +/*! @brief RGFW_scaleUpdated, the window the event was sent to, content scaleX, content scaleY */ +typedef void (* RGFW_scaleUpdatedfunc)(RGFW_window* win, float scaleX, float scaleY); + +/*! @brief function pointer equivalent of void* */ +typedef void (*RGFW_proc)(void); + +#ifndef RGFW_NO_MONITOR + +/*! @brief monitor mode data | can be changed by the user (with functions)*/ +typedef struct RGFW_monitorMode { + i32 w, h; /*!< monitor workarea size */ + u32 refreshRate; /*!< monitor refresh rate */ + u8 red, blue, green; +} RGFW_monitorMode; + +/*! @brief structure for monitor data */ +typedef struct RGFW_monitor { + i32 x, y; /*!< x - y of the monitor workarea */ + char name[128]; /*!< monitor name */ + float scaleX, scaleY; /*!< monitor content scale */ + float pixelRatio; /*!< pixel ratio for monitor (1.0 for regular, 2.0 for hiDPI) */ + float physW, physH; /*!< monitor physical size in inches */ + RGFW_monitorMode mode; +} RGFW_monitor; + +/*! @brief what type of request you are making for the monitor */ +typedef RGFW_ENUM(u8, RGFW_modeRequest) { + RGFW_monitorScale = RGFW_BIT(0), /*!< scale the monitor size */ + RGFW_monitorRefresh = RGFW_BIT(1), /*!< change the refresh rate */ + RGFW_monitorRGB = RGFW_BIT(2), /*!< change the monitor RGB bits size */ + RGFW_monitorAll = RGFW_monitorScale | RGFW_monitorRefresh | RGFW_monitorRGB +}; + +#endif + +#if defined(RGFW_OPENGL) + +/*! @brief abstract structure for interfacing with the underlying OpenGL API */ +typedef struct RGFW_glContext RGFW_glContext; + +/*! @brief abstract structure for interfacing with the underlying EGL API */ +typedef struct RGFW_eglContext RGFW_eglContext; + +/*! values for the releaseBehavior hint */ +typedef RGFW_ENUM(i32, RGFW_glReleaseBehavior) { + RGFW_glReleaseFlush = 0, /*!< flush the pipeline will be flushed when the context is release */ + RGFW_glReleaseNone /*!< do nothing on release */ +}; + +/*! values for the profile hint */ +typedef RGFW_ENUM(i32, RGFW_glProfile) { + RGFW_glCore = 0, /*!< the core OpenGL version, e.g. just support for that version */ + RGFW_glCompatibility, /*!< allow compatibility for older versions of RGFW as well as the requested version */ + RGFW_glES /*!< use OpenGL ES */ +}; + +/*! values for the renderer hint */ +typedef RGFW_ENUM(i32, RGFW_glRenderer) { + RGFW_glAccelerated = 0, /*!< hardware accelerated (GPU) */ + RGFW_glSoftware /*!< software rendered (CPU) */ +}; + +/*! OpenGL initalization hints */ +typedef struct RGFW_glHints { + i32 stencil; /*!< set stencil buffer bit size (0 by default) */ + i32 samples; /*!< set number of sample buffers (0 by default) */ + i32 stereo; /*!< hint the context to use stereoscopic frame buffers for 3D (false by default) */ + i32 auxBuffers; /*!< number of aux buffers (0 by default) */ + i32 doubleBuffer; /*!< request double buffering (true by default) */ + i32 red, green, blue, alpha; /*!< set color bit sizes (all 8 by default) */ + i32 depth; /*!< set depth buffer bit size (24 by default) */ + i32 accumRed, accumGreen, accumBlue, accumAlpha; /*!< set accumulated RGBA bit sizes (all 0 by default) */ + RGFW_bool sRGB; /*!< request sRGA format (false by default) */ + RGFW_bool robustness; /*!< request a "robust" (as in memory-safe) context (false by default). For more information check the overview section: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_robustness.txt */ + RGFW_bool debug; /*!< request OpenGL debugging (false by default). */ + RGFW_bool noError; /*!< request no OpenGL errors (false by default). This causes OpenGL errors to be undefined behavior. For more information check the overview section: https://registry.khronos.org/OpenGL/extensions/KHR/KHR_no_error.txt */ + RGFW_glReleaseBehavior releaseBehavior; /*!< hint how the OpenGL driver should behave when changing contexts (RGFW_glReleaseNone by default). For more information check the overview section: https://registry.khronos.org/OpenGL/extensions/KHR/KHR_context_flush_control.txt */ + RGFW_glProfile profile; /*!< set OpenGL API profile (RGFW_glCore by default) */ + i32 major, minor; /*!< set the OpenGL API profile version (by default RGFW_glMajor is 1, RGFW_glMinor is 0) */ + RGFW_glContext* share; /*!< Share this OpenGL context with newly created OpenGL contexts; defaults to NULL. */ + RGFW_eglContext* shareEGL; /*!< Share this EGL context with newly created OpenGL contexts; defaults to NULL. */ + RGFW_glRenderer renderer; /*!< renderer to use e.g. accelerated or software defaults to accelerated */ +} RGFW_glHints; + +#endif + +/**! + * @brief Allocates memory using the allocator defined by RGFW_ALLOC at compile time. + * @param size The size (in bytes) of the memory block to allocate. + * @return A pointer to the allocated memory block. +*/ +RGFWDEF void* RGFW_alloc(size_t size); + +/**! + * @brief Frees memory using the deallocator defined by RGFW_FREE at compile time. + * @param ptr A pointer to the memory block to free. +*/ +RGFWDEF void RGFW_free(void* ptr); + +/**! + * @brief Returns the size (in bytes) of the RGFW_window structure. + * @return The size of the RGFW_window structure. +*/ +RGFWDEF size_t RGFW_sizeofWindow(void); + +/**! + * @brief Returns the size (in bytes) of the RGFW_window_src structure. + * @return The size of the RGFW_window_src structure. +*/ +RGFWDEF size_t RGFW_sizeofWindowSrc(void); + +/**! + * @brief (Unix) Toggles the use of Wayland. + * This is enabled by default when compiled with `RGFW_WAYLAND`. + * If not using `RGFW_WAYLAND`, Wayland functions are not exposed. + * This function can be used to force the use of XWayland. + * @param wayland A boolean value indicating whether to use Wayland (true) or not (false). +*/ +RGFWDEF void RGFW_useWayland(RGFW_bool wayland); + +/**! + * @brief Checks if Wayland is currently being used. + * @return RGFW_TRUE if using Wayland, RGFW_FALSE otherwise. +*/ +RGFWDEF RGFW_bool RGFW_usingWayland(void); + +/**! + * @brief Retrieves the current Cocoa layer (macOS only). + * @return A pointer to the Cocoa layer, or NULL if the platform is not in use. +*/ +RGFWDEF void* RGFW_getLayer_OSX(void); + +/**! + * @brief Retrieves the current X11 display connection. + * @return A pointer to the X11 display, or NULL if the platform is not in use. +*/ +RGFWDEF void* RGFW_getDisplay_X11(void); + +/**! + * @brief Retrieves the current Wayland display connection. + * @return A pointer to the Wayland display (`struct wl_display*`), or NULL if the platform is not in use. +*/ +RGFWDEF struct wl_display* RGFW_getDisplay_Wayland(void); + +/**! + * @brief Sets the class name for X11 and WinAPI windows. + * Windows with the same class name will be grouped by the window manager. + * By default, the class name matches the root window’s name. + * @param name The class name to assign. +*/ +RGFWDEF void RGFW_setClassName(const char* name); + +/**! + * @brief Sets the X11 instance name. + * By default, the window name will be used as the instance name. + * @param name The X11 instance name to set. +*/ +RGFWDEF void RGFW_setXInstName(const char* name); + +/**! + * @brief (macOS only) Changes the current working directory to the application’s resource folder. +*/ +RGFWDEF void RGFW_moveToMacOSResourceDir(void); + +/*! copy image to another image, respecting each image's format */ +RGFWDEF void RGFW_copyImageData(u8* dest_data, i32 w, i32 h, RGFW_format dest_format, u8* src_data, RGFW_format src_format); + +/**! + * @brief Returns the size (in bytes) of the RGFW_nativeImage structure. + * @return The size of the RGFW_nativeImage structure. +*/ +RGFWDEF size_t RGFW_sizeofNativeImage(void); + +/**! + * @brief Returns the size (in bytes) of the RGFW_surface structure. + * @return The size of the RGFW_surface structure. +*/ +RGFWDEF size_t RGFW_sizeofSurface(void); + +/**! + * @brief Creates a new surface from raw pixel data. + * @param data A pointer to the pixel data buffer. + * @param w The width of the surface in pixels. + * @param h The height of the surface in pixels. + * @param format The pixel format of the data. + * @return A pointer to the newly created RGFW_surface. + * + * NOTE: when you create a surface using RGFW_createSurface / ptr, on X11 it uses the root window's visual + * this means it may fail to render on any other window if the visual does not match + * RGFW_window_createSurface and RGFW_window_createSurfacePtr exist only for X11 to address this issues + * Of course, you can also manually set the root window with RGFW_setRootWindow +*/ +RGFWDEF RGFW_surface* RGFW_createSurface(u8* data, i32 w, i32 h, RGFW_format format); + +/**! + * @brief Creates a surface using a pre-allocated RGFW_surface structure. + * @param data A pointer to the pixel data buffer. + * @param w The width of the surface in pixels. + * @param h The height of the surface in pixels. + * @param format The pixel format of the data. + * @param surface A pointer to a pre-allocated RGFW_surface structure. + * @return RGFW_TRUE if successful, RGFW_FALSE otherwise. +*/ +RGFWDEF RGFW_bool RGFW_createSurfacePtr(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface); + +/**! + * @brief Retrieves the native image associated with a surface. + * @param surface A pointer to the RGFW_surface. + * @return A pointer to the native RGFW_nativeImage associated with the surface. +*/ +RGFWDEF RGFW_nativeImage* RGFW_surface_getNativeImage(RGFW_surface* surface); + +/**! + * @brief Frees the surface pointer and any buffers used for software rendering. + * @param surface A pointer to the RGFW_surface to free. +*/ +RGFWDEF void RGFW_surface_free(RGFW_surface* surface); + +/**! + * @brief Frees only the internal buffers used for software rendering, leaving the surface struct intact. + * @param surface A pointer to the RGFW_surface whose buffers should be freed. +*/ +RGFWDEF void RGFW_surface_freePtr(RGFW_surface* surface); + + +/**! + * @brief Loads a mouse icon from bitmap data (similar to RGFW_window_setIcon). + * @param data A pointer to the bitmap pixel data. + * @param w The width of the mouse icon in pixels. + * @param h The height of the mouse icon in pixels. + * @param format The pixel format of the data. + * @return A pointer to the newly loaded RGFW_mouse structure. + * + * @note The icon is not resized by default. +*/ +RGFWDEF RGFW_mouse* RGFW_loadMouse(u8* data, i32 w, i32 h, RGFW_format format); + +/**! + * @brief Frees the data associated with an RGFW_mouse structure. + * @param mouse A pointer to the RGFW_mouse to free. +*/ +RGFWDEF void RGFW_freeMouse(RGFW_mouse* mouse); + +#ifndef RGFW_NO_MONITOR + +/**! + * @brief Retrieves an array of all available monitors. + * @param len [OUTPUT] A pointer to store the number of monitors found (maximum of 6). + * @return A pointer to an array of RGFW_monitor structures. +*/ +RGFWDEF RGFW_monitor* RGFW_getMonitors(size_t* len); + +/**! + * @brief Retrieves the primary monitor. + * @return The RGFW_monitor structure representing the primary monitor. +*/ +RGFWDEF RGFW_monitor RGFW_getPrimaryMonitor(void); + +/**! + * @brief Requests a specific display mode for a monitor. + * @param mon The monitor to apply the mode change to. + * @param mode The desired RGFW_monitorMode. + * @param request The RGFW_modeRequest describing how to handle the mode change. + * @return RGFW_TRUE if the mode was successfully applied, otherwise RGFW_FALSE. +*/ +RGFWDEF RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request); + +/**! + * @brief Compares two monitor modes to check if they are equivalent. + * @param mon The first monitor mode. + * @param mon2 The second monitor mode. + * @param request The RGFW_modeRequest that defines the comparison parameters. + * @return RGFW_TRUE if both modes are equivalent, otherwise RGFW_FALSE. +*/ +RGFWDEF RGFW_bool RGFW_monitorModeCompare(RGFW_monitorMode mon, RGFW_monitorMode mon2, RGFW_modeRequest request); + +/**! + * @brief Scales a monitor’s mode to match a window’s size. + * @param mon The monitor to be scaled. + * @param win The window whose size should be used as a reference. + * @return RGFW_TRUE if the scaling was successful, otherwise RGFW_FALSE. +*/ +RGFWDEF RGFW_bool RGFW_monitor_scaleToWindow(RGFW_monitor mon, struct RGFW_window* win); + +#endif + +/**! +* @brief sleep until RGFW gets an event or the timer ends (defined by OS) +* @param waitMS how long to wait for the next event (in miliseconds) +*/ +RGFWDEF void RGFW_waitForEvent(i32 waitMS); + +/**! +* @brief Set if events should be queued or not (enabled by default if the event queue is checked) +* @param queue boolean value if RGFW should queue events or not +*/ +RGFWDEF void RGFW_setQueueEvents(RGFW_bool queue); + +/**! +* @brief check all the events until there are none left and updates window structure attributes +*/ +RGFWDEF void RGFW_pollEvents(void); + +/**! +* @brief check all the events until there are none left and updates window structure attributes +* queues events if the queue is checked and/or requested +*/ +RGFWDEF void RGFW_stopCheckEvents(void); + +/** * @defgroup Input +* @{ */ + +/**! + * @brief returns true if the key is pressed during the current frame + * @param key the key code of the key you want to check + * @return The boolean value if the key is pressed or not +*/ +RGFWDEF RGFW_bool RGFW_isKeyPressed(RGFW_key key); + +/**! + * @brief returns true if the key was released during the current frame + * @param key the key code of the key you want to check + * @return The boolean value if the key is released or not +*/ +RGFWDEF RGFW_bool RGFW_isKeyReleased(RGFW_key key); + +/**! + * @brief returns true if the key is down + * @param key the key code of the key you want to check + * @return The boolean value if the key is down or not +*/ +RGFWDEF RGFW_bool RGFW_isKeyDown(RGFW_key key); + +/**! + * @brief returns true if the mouse button is pressed during the current frame + * @param button the mouse button code of the button you want to check + * @return The boolean value if the button is pressed or not +*/ +RGFWDEF RGFW_bool RGFW_isMousePressed(RGFW_mouseButton button); + +/**! + * @brief returns true if the mouse button is released during the current frame + * @param button the mouse button code of the button you want to check + * @return The boolean value if the button is released or not +*/ +RGFWDEF RGFW_bool RGFW_isMouseReleased(RGFW_mouseButton button); + +/**! + * @brief returns true if the mouse button is down + * @param button the mouse button code of the button you want to check + * @return The boolean value if the button is down or not +*/ +RGFWDEF RGFW_bool RGFW_isMouseDown(RGFW_mouseButton button); + +/**! + * @brief outputs the current x, y position of the mouse + * @param X [OUTPUT] a pointer for the output X value + * @param Y [OUTPUT] a pointer for the output Y value +*/ +RGFWDEF void RGFW_getMouseScroll(float* x, float* y); + +/**! + * @brief outputs the current x, y movement vector of the mouse + * @param X [OUTPUT] a pointer for the output X vector value + * @param Y [OUTPUT] a pointer for the output Y vector value +*/ +RGFWDEF void RGFW_getMouseVector(float* x, float* y); /** @} */ -#endif /* RGFW_HEADER */ -#if defined(RGFW_X11) || defined(RGFW_WAYLAND) - #define RGFW_OS_BASED_VALUE(l, w, m, h) l -#elif defined(RGFW_WINDOWS) - #define RGFW_OS_BASED_VALUE(l, w, m, h) w -#elif defined(RGFW_MACOS) - #define RGFW_OS_BASED_VALUE(l, w, m, h) m -#elif defined(RGFW_WASM) - #define RGFW_OS_BASED_VALUE(l, w, m, h) h +/**! + * @brief creates a new window + * @param name the requested title of the window + * @param x the requested x position of the window + * @param y the requested y position of the window + * @param w the requested width of the window + * @param h the requested height of the window + * @param flags extra arguments ((u32)0 means no flags used) + * @return A pointer to the newly created window structure + * + * NOTE: (windows) if the executable has an icon resource named RGFW_ICON, it will be set as the initial icon for the window +*/ +RGFWDEF RGFW_window* RGFW_createWindow(const char* name, i32 x, i32 y, i32 w, i32 h, RGFW_windowFlags flags); + +/**! + * @brief creates a new window using a pre-allocated window structure + * @param name the requested title of the window + * @param x the requested x position of the window + * @param y the requested y position of the window + * @param w the requested width of the window + * @param h the requested height of the window + * @param flags extra arguments ((u32)0 means no flags used) + * @param win a pointer the pre-allocated window structure + * @return A pointer to the newly created window structure +*/ +RGFWDEF RGFW_window* RGFW_createWindowPtr(const char* name, i32 x, i32 y, i32 w, i32 h, RGFW_windowFlags flags, RGFW_window* win); + +/**! + * @brief creates a new surface structure + * @param win the source window of the surface + * @param data a pointer to the raw data of the structure (you allocate this) + * @param w the width the data + * @param h the height of the data + * @return A pointer to the newly created surface structure + * + * NOTE: when you create a surface using RGFW_createSurface / ptr, on X11 it uses the root window's visual + * this means it may fail to render on any other window if the visual does not match + * RGFW_window_createSurface and RGFW_window_createSurfacePtr exist only for X11 to address this issues + * Of course, you can also manually set the root window with RGFW_setRootWindow + */ +RGFWDEF RGFW_surface* RGFW_window_createSurface(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format); + +/**! + * @brief creates a new surface structure using a pre-allocated surface structure + * @param win the source window of the surface + * @param data a pointer to the raw data of the structure (you allocate this) + * @param w the width the data + * @param h the height of the data + * @param a pointer to the pre-allocated surface structure + * @return a bool if the creation was successful or not +*/ +RGFWDEF RGFW_bool RGFW_window_createSurfacePtr(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface); + +/**! + * @brief blits a surface stucture to the window + * @param win a pointer the window to blit to + * @param surface a pointer to the surface +*/ +RGFWDEF void RGFW_window_blitSurface(RGFW_window* win, RGFW_surface* surface); + +/**! + * @brief gets the position of the window | with RGFW_window.x and window.y + * @param x [OUTPUT] the x position of the window + * @param y [OUTPUT] the y position of the window + * @return a bool if the function was successful +*/ +RGFWDEF RGFW_bool RGFW_window_getPosition(RGFW_window* win, i32* x, i32* y); /*!< */ + +/**! + * @brief gets the size of the window | with RGFW_window.w and window.h + * @param win a pointer to the window + * @param w [OUTPUT] the width of the window + * @param h [OUTPUT] the height of the window + * @return a bool if the function was successful +*/ +RGFWDEF RGFW_bool RGFW_window_getSize(RGFW_window* win, i32* w, i32* h); + +/**! + * @brief gets the flags of the window | returns RGFW_window._flags + * @param win a pointer to the window + * @return the window flags +*/ +RGFWDEF u32 RGFW_window_getFlags(RGFW_window* win); + +/**! + * @brief returns the exit key assigned to the window + * @param win a pointer to the target window + * @return The key code assigned as the exit key +*/ +RGFWDEF RGFW_key RGFW_window_getExitKey(RGFW_window* win); + +/**! + * @brief sets the exit key for the window + * @param win a pointer to the target window + * @param key the key code to assign as the exit key +*/ +RGFWDEF void RGFW_window_setExitKey(RGFW_window* win, RGFW_key key); + +/**! + * @brief sets the types of events you want the window to receive + * @param win a pointer to the target window + * @param events the event flags to enable (use RGFW_allEventFlags for all) +*/ +RGFWDEF void RGFW_window_setEnabledEvents(RGFW_window* win, RGFW_eventFlag events); + +/**! + * @brief gets the currently enabled events for the window + * @param win a pointer to the target window + * @return The enabled event flags for the window +*/ +RGFWDEF RGFW_eventFlag RGFW_window_getEnabledEvents(RGFW_window* win); + +/**! + * @brief enables all events and disables selected ones + * @param win a pointer to the target window + * @param events the event flags to disable +*/ +RGFWDEF void RGFW_window_setDisabledEvents(RGFW_window* win, RGFW_eventFlag events); + +/**! + * @brief directly enables or disables a specific event or group of events + * @param win a pointer to the target window + * @param event the event flag or group of flags to modify + * @param state RGFW_TRUE to enable, RGFW_FALSE to disable +*/ +RGFWDEF void RGFW_window_setEventState(RGFW_window* win, RGFW_eventFlag event, RGFW_bool state); + +/**! + * @brief gets the user pointer associated with the window + * @param win a pointer to the target window + * @return The user-defined pointer stored in the window +*/ +RGFWDEF void* RGFW_window_getUserPtr(RGFW_window* win); + +/**! + * @brief sets a user pointer for the window + * @param win a pointer to the target window + * @param ptr a pointer to associate with the window +*/ +RGFWDEF void RGFW_window_setUserPtr(RGFW_window* win, void* ptr); + +/**! + * @brief retrieves the platform-specific window source pointer + * @param win a pointer to the target window + * @return A pointer to the internal RGFW_window_src structure +*/ +RGFWDEF RGFW_window_src* RGFW_window_getSrc(RGFW_window* win); + +/**! + * @brief sets the macOS layer object associated with the window + * @param win a pointer to the target window + * @param layer a pointer to the macOS layer object + * @note Only available on macOS platforms +*/ +RGFWDEF void RGFW_window_setLayer_OSX(RGFW_window* win, void* layer); + +/**! + * @brief retrieves the macOS view object associated with the window + * @param win a pointer to the target window + * @return A pointer to the macOS view object, or NULL if not on macOS +*/ +RGFWDEF void* RGFW_window_getView_OSX(RGFW_window* win); + +/**! + * @brief retrieves the macOS window object + * @param win a pointer to the target window + * @return A pointer to the macOS window object, or NULL if not on macOS +*/ +RGFWDEF void* RGFW_window_getWindow_OSX(RGFW_window* win); + +/**! + * @brief retrieves the HWND handle for the window + * @param win a pointer to the target window + * @return A pointer to the Windows HWND handle, or NULL if not on Windows +*/ +RGFWDEF void* RGFW_window_getHWND(RGFW_window* win); + +/**! + * @brief retrieves the HDC handle for the window + * @param win a pointer to the target window + * @return A pointer to the Windows HDC handle, or NULL if not on Windows +*/ +RGFWDEF void* RGFW_window_getHDC(RGFW_window* win); + +/**! + * @brief retrieves the X11 Window handle for the window + * @param win a pointer to the target window + * @return The X11 Window handle, or 0 if not on X11 +*/ +RGFWDEF u64 RGFW_window_getWindow_X11(RGFW_window* win); + +/**! + * @brief retrieves the Wayland surface handle for the window + * @param win a pointer to the target window + * @return A pointer to the Wayland wl_surface, or NULL if not on Wayland +*/ +RGFWDEF struct wl_surface* RGFW_window_getWindow_Wayland(RGFW_window* win); + +/** * @defgroup Window_management +* @{ */ + +/*! set the window flags (will undo flags if they don't match the old ones) */ +RGFWDEF void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags); + +/**! + * @brief polls and pops the next event from the window's event queue + * @param win a pointer to the target window + * @param event [OUTPUT] a pointer to store the retrieved event + * @return RGFW_TRUE if an event was found, RGFW_FALSE otherwise + * + * NOTE: Using this function without a loop may cause event lag. + * For multi-threaded systems, use RGFW_pollEvents combined with RGFW_window_checkQueuedEvent. + * + * Example: + * RGFW_event event; + * while (RGFW_window_checkEvent(win, &event)) { + * // handle event + * } +*/ +RGFWDEF RGFW_bool RGFW_window_checkEvent(RGFW_window* win, RGFW_event* event); + +/**! + * @brief pops the first queued event for the window + * @param win a pointer to the target window + * @param event [OUTPUT] a pointer to store the retrieved event + * @return RGFW_TRUE if an event was found, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_checkQueuedEvent(RGFW_window* win, RGFW_event* event); + +/**! + * @brief checks if a key was pressed while the window is in focus + * @param win a pointer to the target window + * @param key the key code to check + * @return RGFW_TRUE if the key was pressed, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isKeyPressed(RGFW_window* win, RGFW_key key); + +/**! + * @brief checks if a key is currently being held down + * @param win a pointer to the target window + * @param key the key code to check + * @return RGFW_TRUE if the key is held down, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isKeyDown(RGFW_window* win, RGFW_key key); + +/**! + * @brief checks if a key was released + * @param win a pointer to the target window + * @param key the key code to check + * @return RGFW_TRUE if the key was released, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isKeyReleased(RGFW_window* win, RGFW_key key); + +/**! + * @brief checks if a mouse button was pressed + * @param win a pointer to the target window + * @param button the mouse button code to check + * @return RGFW_TRUE if the mouse button was pressed, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isMousePressed(RGFW_window* win, RGFW_mouseButton button); + +/**! + * @brief checks if a mouse button is currently held down + * @param win a pointer to the target window + * @param button the mouse button code to check + * @return RGFW_TRUE if the mouse button is down, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isMouseDown(RGFW_window* win, RGFW_mouseButton button); + +/**! + * @brief checks if a mouse button was released + * @param win a pointer to the target window + * @param button the mouse button code to check + * @return RGFW_TRUE if the mouse button was released, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isMouseReleased(RGFW_window* win, RGFW_mouseButton button); + +/**! + * @brief checks if the mouse left the window (true only for the first frame) + * @param win a pointer to the target window + * @return RGFW_TRUE if the mouse left, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_didMouseLeave(RGFW_window* win); + +/**! + * @brief checks if the mouse entered the window (true only for the first frame) + * @param win a pointer to the target window + * @return RGFW_TRUE if the mouse entered, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_didMouseEnter(RGFW_window* win); + +/**! + * @brief checks if the mouse is currently inside the window bounds + * @param win a pointer to the target window + * @return RGFW_TRUE if the mouse is inside, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isMouseInside(RGFW_window* win); + +/**! + * @brief checks if there is data being dragged into or within the window + * @param win a pointer to the target window + * @return RGFW_TRUE if data is being dragged, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isDataDragging(RGFW_window* win); + +/**! + * @brief gets the position of a data drag + * @param win a pointer to the target window + * @param x [OUTPUT] pointer to store the x position + * @param y [OUTPUT] pointer to store the y position + * @return RGFW_TRUE if there is an active drag, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_getDataDrag(RGFW_window* win, i32* x, i32* y); + +/**! + * @brief checks if a data drop occurred in the window (first frame only) + * @param win a pointer to the target window + * @return RGFW_TRUE if data was dropped, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_didDataDrop(RGFW_window* win); + +/**! + * @brief retrieves files from a data drop (drag and drop) + * @param win a pointer to the target window + * @param files [OUTPUT] a pointer to the array of file paths + * @param count [OUTPUT] the number of dropped files + * @return RGFW_TRUE if a data drop occurred, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_getDataDrop(RGFW_window* win, const char*** files, size_t* count); + +/**! + * @brief closes the window and frees its associated structure + * @param win a pointer to the target window +*/ +RGFWDEF void RGFW_window_close(RGFW_window* win); + +/**! + * @brief closes the window without freeing its structure + * @param win a pointer to the target window +*/ +RGFWDEF void RGFW_window_closePtr(RGFW_window* win); + +/**! + * @brief moves the window to a new position on the screen + * @param win a pointer to the target window + * @param x the new x position + * @param y the new y position +*/ +RGFWDEF void RGFW_window_move(RGFW_window* win, i32 x, i32 y); + +#ifndef RGFW_NO_MONITOR +/**! + * @brief moves the window to a specific monitor + * @param win a pointer to the target window + * @param m the target monitor +*/ +RGFWDEF void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor m); #endif +/**! + * @brief resizes the window to the given dimensions + * @param win a pointer to the target window + * @param w the new width + * @param h the new height +*/ +RGFWDEF void RGFW_window_resize(RGFW_window* win, i32 w, i32 h); + +/**! + * @brief sets the aspect ratio of the window + * @param win a pointer to the target window + * @param w the width ratio + * @param h the height ratio +*/ +RGFWDEF void RGFW_window_setAspectRatio(RGFW_window* win, i32 w, i32 h); + +/**! + * @brief sets the minimum size of the window + * @param win a pointer to the target window + * @param w the minimum width + * @param h the minimum height +*/ +RGFWDEF void RGFW_window_setMinSize(RGFW_window* win, i32 w, i32 h); + +/**! + * @brief sets the maximum size of the window + * @param win a pointer to the target window + * @param w the maximum width + * @param h the maximum height +*/ +RGFWDEF void RGFW_window_setMaxSize(RGFW_window* win, i32 w, i32 h); + +/**! + * @brief sets focus to the window + * @param win a pointer to the target window +*/ +RGFWDEF void RGFW_window_focus(RGFW_window* win); + +/**! + * @brief checks if the window is currently in focus + * @param win a pointer to the target window + * @return RGFW_TRUE if the window is in focus, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_isInFocus(RGFW_window* win); + +/**! + * @brief raises the window to the top of the stack + * @param win a pointer to the target window +*/ +RGFWDEF void RGFW_window_raise(RGFW_window* win); + +/**! + * @brief maximizes the window + * @param win a pointer to the target window +*/ +RGFWDEF void RGFW_window_maximize(RGFW_window* win); + +/**! + * @brief toggles fullscreen mode for the window + * @param win a pointer to the target window + * @param fullscreen RGFW_TRUE to enable fullscreen, RGFW_FALSE to disable +*/ +RGFWDEF void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen); + +/**! + * @brief centers the window on the screen + * @param win a pointer to the target window +*/ +RGFWDEF void RGFW_window_center(RGFW_window* win); + +/**! + * @brief minimizes the window + * @param win a pointer to the target window +*/ +RGFWDEF void RGFW_window_minimize(RGFW_window* win); + +/**! + * @brief restores the window from minimized state + * @param win a pointer to the target window +*/ +RGFWDEF void RGFW_window_restore(RGFW_window* win); + +/**! + * @brief makes the window a floating window + * @param win a pointer to the target window + * @param floating RGFW_TRUE to float, RGFW_FALSE to disable +*/ +RGFWDEF void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating); + +/**! + * @brief sets the opacity level of the window + * @param win a pointer to the target window + * @param opacity the opacity level (0–255) +*/ +RGFWDEF void RGFW_window_setOpacity(RGFW_window* win, u8 opacity); + +/**! + * @brief toggles window borders + * @param win a pointer to the target window + * @param border RGFW_TRUE for bordered, RGFW_FALSE for borderless +*/ +RGFWDEF void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border); + +/**! + * @brief checks if the window is borderless + * @param win a pointer to the target window + * @return RGFW_TRUE if borderless, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_borderless(RGFW_window* win); + +/**! + * @brief toggles drag-and-drop (DND) support for the window + * @param win a pointer to the target window + * @param allow RGFW_TRUE to allow DND, RGFW_FALSE to disable + * @note RGFW_windowAllowDND must still be passed when creating the window +*/ +RGFWDEF void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow); + +/**! + * @brief checks if drag-and-drop (DND) is allowed + * @param win a pointer to the target window + * @return RGFW_TRUE if DND is enabled, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_allowsDND(RGFW_window* win); + +#ifndef RGFW_NO_PASSTHROUGH +/**! + * @brief toggles mouse passthrough for the window + * @param win a pointer to the target window + * @param passthrough RGFW_TRUE to enable passthrough, RGFW_FALSE to disable +*/ +RGFWDEF void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough); +#endif + +/**! + * @brief renames the window + * @param win a pointer to the target window + * @param name the new title string for the window +*/ +RGFWDEF void RGFW_window_setName(RGFW_window* win, const char* name); + +/**! + * @brief sets the icon for the window and taskbar + * @param win a pointer to the target window + * @param data the image data + * @param w the width of the icon + * @param h the height of the icon + * @param format the image format + * @return RGFW_TRUE if successful, RGFW_FALSE otherwise + * + * NOTE: The image may be resized by default. +*/ +RGFWDEF RGFW_bool RGFW_window_setIcon(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format); + +/**! + * @brief sets the icon for the window and/or taskbar + * @param win a pointer to the target window + * @param data the image data + * @param w the width of the icon + * @param h the height of the icon + * @param format the image format + * @param type the target icon type (taskbar, window, or both) + * @return RGFW_TRUE if successful, RGFW_FALSE otherwise +*/ +RGFWDEF RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_icon type); + +/**! + * @brief sets the mouse icon for the window using a loaded bitmap + * @param win a pointer to the target window + * @param mouse a pointer to the RGFW_mouse struct containing the icon +*/ +RGFWDEF void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse); + +/**! + * @brief Sets the mouse to a standard system cursor. + * @param win The target window. + * @param mouse The standard cursor type (see RGFW_MOUSE enum). + * @return True if the standard cursor was successfully applied. +*/ +RGFWDEF RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, RGFW_mouseIcons mouse); + +/**! + * @brief Sets the mouse to the default cursor icon. + * @param win The target window. + * @return True if the default cursor was successfully set. +*/ +RGFWDEF RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win); + +/**! + * @brief Locks the cursor to the center of the window. + * @param win The target window. + * + * While the cursor is held, X and Y report raw mouse movement data. + * Useful for 3D camera or first-person movement systems. +*/ +RGFWDEF void RGFW_window_holdMouse(RGFW_window* win); + +/**! + * @brief Returns true if the mouse is currently held by RGFW. + * @param win The target window. + * @return True if the mouse is being held. +*/ +RGFWDEF RGFW_bool RGFW_window_isHoldingMouse(RGFW_window* win); + +/**! + * @brief Releases the mouse so it can move freely again. + * @param win The target window. +*/ +RGFWDEF void RGFW_window_unholdMouse(RGFW_window* win); + +/**! + * @brief Hides the window from view. + * @param win The target window. +*/ +RGFWDEF void RGFW_window_hide(RGFW_window* win); + +/**! + * @brief Shows the window if it was hidden. + * @param win The target window. +*/ +RGFWDEF void RGFW_window_show(RGFW_window* win); + +/**! + * @brief Sets whether the window should close. + * @param win The target window. + * @param shouldClose True to signal the window should close, false to keep it open. + * + * This can override or trigger the `RGFW_window_shouldClose` state by modifying window flags. +*/ +RGFWDEF void RGFW_window_setShouldClose(RGFW_window* win, RGFW_bool shouldClose); + +/**! + * @brief Retrieves the current global mouse position. + * @param x [OUTPUT] Pointer to store the X position of the mouse on the screen. + * @param y [OUTPUT] Pointer to store the Y position of the mouse on the screen. + * @return True if the position was successfully retrieved. +*/ +RGFWDEF RGFW_bool RGFW_getGlobalMouse(i32* x, i32* y); + +/**! + * @brief Retrieves the mouse position relative to the window. + * @param win The target window. + * @param x [OUTPUT] Pointer to store the X position within the window. + * @param y [OUTPUT] Pointer to store the Y position within the window. + * @return True if the position was successfully retrieved. +*/ +RGFWDEF RGFW_bool RGFW_window_getMouse(RGFW_window* win, i32* x, i32* y); + +/**! + * @brief Shows or hides the mouse cursor for the window. + * @param win The target window. + * @param show True to show the mouse, false to hide it. +*/ +RGFWDEF void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show); + +/**! + * @brief Checks if the mouse is currently hidden in the window. + * @param win The target window. + * @return True if the mouse is hidden. +*/ +RGFWDEF RGFW_bool RGFW_window_isMouseHidden(RGFW_window* win); + +/**! + * @brief Moves the mouse to the specified position within the window. + * @param win The target window. + * @param x The new X position. + * @param y The new Y position. +*/ +RGFWDEF void RGFW_window_moveMouse(RGFW_window* win, i32 x, i32 y); + +/**! + * @brief Checks if the window should close. + * @param win The target window. + * @return True if the window should close (for example, if ESC was pressed or a close event occurred). +*/ +RGFWDEF RGFW_bool RGFW_window_shouldClose(RGFW_window* win); + +/**! + * @brief Checks if the window is currently fullscreen. + * @param win The target window. + * @return True if the window is fullscreen. +*/ +RGFWDEF RGFW_bool RGFW_window_isFullscreen(RGFW_window* win); + +/**! + * @brief Checks if the window is currently hidden. + * @param win The target window. + * @return True if the window is hidden. +*/ +RGFWDEF RGFW_bool RGFW_window_isHidden(RGFW_window* win); + +/**! + * @brief Checks if the window is minimized. + * @param win The target window. + * @return True if the window is minimized. +*/ +RGFWDEF RGFW_bool RGFW_window_isMinimized(RGFW_window* win); + +/**! + * @brief Checks if the window is maximized. + * @param win The target window. + * @return True if the window is maximized. +*/ +RGFWDEF RGFW_bool RGFW_window_isMaximized(RGFW_window* win); + +/**! + * @brief Checks if the window is floating. + * @param win The target window. + * @return True if the window is floating. +*/ +RGFWDEF RGFW_bool RGFW_window_isFloating(RGFW_window* win); +/** @} */ + +/** * @defgroup Monitor +* @{ */ + +#ifndef RGFW_NO_MONITOR +/**! + * @brief Scales the window to match its monitor’s resolution. + * @param win The target window. + * + * This function is automatically called when the flag `RGFW_scaleToMonitor` + * is used during window creation. +*/ +RGFWDEF void RGFW_window_scaleToMonitor(RGFW_window* win); + +/**! + * @brief Retrieves the monitor structure associated with the window. + * @param win The target window. + * @return The monitor structure of the window. +*/ +RGFWDEF RGFW_monitor RGFW_window_getMonitor(RGFW_window* win); +#endif + +/** @} */ + +/** * @defgroup Clipboard +* @{ */ + +/**! + * @brief Reads clipboard data. + * @param size [OUTPUT] A pointer that will be filled with the size of the clipboard data. + * @return A pointer to the clipboard data as a string. +*/ +RGFWDEF const char* RGFW_readClipboard(size_t* size); + +/**! + * @brief Reads clipboard data into a provided buffer, or returns the required length if str is NULL. + * @param str [OUTPUT] A pointer to the buffer that will receive the clipboard data (or NULL to get required size). + * @param strCapacity The capacity of the provided buffer. + * @return The number of bytes read or required length of clipboard data. +*/ +RGFWDEF RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity); + +/**! + * @brief Writes text to the clipboard. + * @param text The text to be written to the clipboard. + * @param textLen The length of the text being written. +*/ +RGFWDEF void RGFW_writeClipboard(const char* text, u32 textLen); +/** @} */ + + + +/** * @defgroup error handling +* @{ */ +/**! + * @brief Sets the callback function to handle debug messages from RGFW. + * @param func The function pointer to be used as the debug callback. + * @return The previously set debug callback function. +*/ +RGFWDEF RGFW_debugfunc RGFW_setDebugCallback(RGFW_debugfunc func); + +/**! + * @brief Sends a debug message manually through the currently set debug callback. + * @param type The type of debug message being sent. + * @param err The associated error code. + * @param msg The debug message text. +*/ +RGFWDEF void RGFW_sendDebugInfo(RGFW_debugType type, RGFW_errorCode err, const char* msg); +/** @} */ + +/** + + + event callbacks. + These are completely optional, so you can use the normal + RGFW_checkEvent() method if you prefer that + +* @defgroup Callbacks +* @{ +*/ + +/**! + * @brief Sets the callback function for window move events. + * @param func The function to be called when the window is moved. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_windowMovedfunc RGFW_setWindowMovedCallback(RGFW_windowMovedfunc func); + +/**! + * @brief Sets the callback function for window resize events. + * @param func The function to be called when the window is resized. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_windowResizedfunc RGFW_setWindowResizedCallback(RGFW_windowResizedfunc func); + +/**! + * @brief Sets the callback function for window quit events. + * @param func The function to be called when the window receives a quit signal. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_windowQuitfunc RGFW_setWindowQuitCallback(RGFW_windowQuitfunc func); + +/**! + * @brief Sets the callback function for mouse move events. + * @param func The function to be called when the mouse moves within the window. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_mousePosfunc RGFW_setMousePosCallback(RGFW_mousePosfunc func); + +/**! + * @brief Sets the callback function for window refresh events. + * @param func The function to be called when the window needs to be refreshed. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_windowRefreshfunc RGFW_setWindowRefreshCallback(RGFW_windowRefreshfunc func); + +/**! + * @brief Sets the callback function for focus change events. + * @param func The function to be called when the window gains or loses focus. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_focusfunc RGFW_setFocusCallback(RGFW_focusfunc func); + +/**! + * @brief Sets the callback function for mouse notification events. + * @param func The function to be called when a mouse notification event occurs. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_mouseNotifyfunc RGFW_setMouseNotifyCallback(RGFW_mouseNotifyfunc func); + +/**! + * @brief Sets the callback function for data drop events. + * @param func The function to be called when data is dropped into the window. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_dataDropfunc RGFW_setDataDropCallback(RGFW_dataDropfunc func); + +/**! + * @brief Sets the callback function for the start of a data drag event. + * @param func The function to be called when data dragging begins. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_dataDragfunc RGFW_setDataDragCallback(RGFW_dataDragfunc func); + +/**! + * @brief Sets the callback function for key press and release events. + * @param func The function to be called when a key is pressed or released. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_keyfunc RGFW_setKeyCallback(RGFW_keyfunc func); + +/**! + * @brief Sets the callback function for mouse button press and release events. + * @param func The function to be called when a mouse button is pressed or released. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_mouseButtonfunc RGFW_setMouseButtonCallback(RGFW_mouseButtonfunc func); + +/**! + * @brief Sets the callback function for mouse scroll events. + * @param func The function to be called when the mouse wheel is scrolled. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_mouseScrollfunc RGFW_setMouseScrollCallback(RGFW_mouseScrollfunc func); + +/**! + * @brief Sets the callback function for window maximize events. + * @param func The function to be called when the window is maximized. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_windowMaximizedfunc RGFW_setWindowMaximizedCallback(RGFW_windowMaximizedfunc func); + +/**! + * @brief Sets the callback function for window minimize events. + * @param func The function to be called when the window is minimized. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_windowMinimizedfunc RGFW_setWindowMinimizedCallback(RGFW_windowMinimizedfunc func); + +/**! + * @brief Sets the callback function for window restore events. + * @param func The function to be called when the window is restored from a minimized or maximized state. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_windowRestoredfunc RGFW_setWindowRestoredCallback(RGFW_windowRestoredfunc func); + +/**! + * @brief Sets the callback function for DPI (scale) update events. + * @param func The function to be called when the window’s DPI or scale changes. + * @return The previously set callback function, if any. +*/ +RGFWDEF RGFW_scaleUpdatedfunc RGFW_setScaleUpdatedCallback(RGFW_scaleUpdatedfunc func); +/** @} */ + +/** * @defgroup graphics_API +* @{ */ + +/*! native rendering API functions */ +#if defined(RGFW_OPENGL) +/* these are native opengl specific functions and will NOT work with EGL */ + +/*!< make the window the current OpenGL drawing context + + NOTE: + if you want to switch the graphics context's thread, + you have to run RGFW_window_makeCurrentContext_OpenGL(NULL); on the old thread + then RGFW_window_makeCurrentContext_OpenGL(valid_window) on the new thread +*/ + +/**! + * @brief Sets the global OpenGL hints to the specified pointer. + * @param hints A pointer to the RGFW_glHints structure containing the desired OpenGL settings. +*/ +RGFWDEF void RGFW_setGlobalHints_OpenGL(RGFW_glHints* hints); + +/**! + * @brief Resets the global OpenGL hints to their default values. +*/ +RGFWDEF void RGFW_resetGlobalHints_OpenGL(void); + +/**! + * @brief Gets the current global OpenGL hints pointer. + * @return A pointer to the currently active RGFW_glHints structure. +*/ +RGFWDEF RGFW_glHints* RGFW_getGlobalHints_OpenGL(void); + +/**! + * @brief Creates and allocates an OpenGL context for the specified window. + * @param win A pointer to the target RGFW_window. + * @param hints A pointer to an RGFW_glHints structure defining context creation parameters. + * @return A pointer to the newly created RGFW_glContext. +*/ +RGFWDEF RGFW_glContext* RGFW_window_createContext_OpenGL(RGFW_window* win, RGFW_glHints* hints); + +/**! + * @brief Creates an OpenGL context for the specified window using a preallocated context structure. + * @param win A pointer to the target RGFW_window. + * @param ctx A pointer to an already allocated RGFW_glContext structure. + * @param hints A pointer to an RGFW_glHints structure defining context creation parameters. + * @return RGFW_TRUE on success, RGFW_FALSE on failure. +*/ +RGFWDEF RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints); + +/**! + * @brief Retrieves the OpenGL context associated with a window. + * @param win A pointer to the RGFW_window. + * @return A pointer to the associated RGFW_glContext, or NULL if none exists or if the context is EGL-based. +*/ +RGFWDEF RGFW_glContext* RGFW_window_getContext_OpenGL(RGFW_window* win); + +/**! + * @brief Deletes and frees the OpenGL context. + * @param win A pointer to the RGFW_window. + * @param ctx A pointer to the RGFW_glContext to delete. + * + * @note This is automatically called by RGFW_window_close if the window’s context is not NULL. +*/ +RGFWDEF void RGFW_window_deleteContext_OpenGL(RGFW_window* win, RGFW_glContext* ctx); + +/**! + * @brief Deletes the OpenGL context without freeing its memory. + * @param win A pointer to the RGFW_window. + * @param ctx A pointer to the RGFW_glContext to delete. + * + * @note This is automatically called by RGFW_window_close if the window’s context is not NULL. +*/ +RGFWDEF void RGFW_window_deleteContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx); + +/**! + * @brief Retrieves the native source context from an RGFW_glContext. + * @param ctx A pointer to the RGFW_glContext. + * @return A pointer to the native OpenGL context handle. +*/ +RGFWDEF void* RGFW_glContext_getSourceContext(RGFW_glContext* ctx); + +/**! + * @brief Makes the specified window the current OpenGL rendering target. + * @param win A pointer to the RGFW_window to make current. + * + * @note This is typically called internally by RGFW_window_makeCurrent. +*/ +RGFWDEF void RGFW_window_makeCurrentWindow_OpenGL(RGFW_window* win); + +/**! + * @brief Makes the OpenGL context of the specified window current. + * @param win A pointer to the RGFW_window whose context should be made current. + * + * @note To move a context between threads, call RGFW_window_makeCurrentContext_OpenGL(NULL) + * on the old thread before making it current on the new one. +*/ +RGFWDEF void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win); + +/**! + * @brief Swaps the OpenGL buffers for the specified window. + * @param win A pointer to the RGFW_window whose buffers should be swapped. + * + * @note Typically called by RGFW_window_swapInterval. +*/ +RGFWDEF void RGFW_window_swapBuffers_OpenGL(RGFW_window* win); + +/**! + * @brief Retrieves the current OpenGL context. + * @return A pointer to the currently active OpenGL context (GLX, WGL, Cocoa, or WebGL backend). +*/ +RGFWDEF void* RGFW_getCurrentContext_OpenGL(void); + +/**! + * @brief Retrieves the current OpenGL window. + * @return A pointer to the RGFW_window currently bound as the OpenGL context target. +*/ +RGFWDEF RGFW_window* RGFW_getCurrentWindow_OpenGL(void); + +/**! + * @brief Sets the OpenGL swap interval (vsync). + * @param win A pointer to the RGFW_window. + * @param swapInterval The desired swap interval value (0 to disable vsync, 1 to enable). +*/ +RGFWDEF void RGFW_window_swapInterval_OpenGL(RGFW_window* win, i32 swapInterval); + +/**! + * @brief Retrieves the address of a native OpenGL procedure. + * @param procname The name of the OpenGL function to look up. + * @return A pointer to the function, or NULL if not found. +*/ +RGFWDEF RGFW_proc RGFW_getProcAddress_OpenGL(const char* procname); + +/**! + * @brief Checks whether a specific OpenGL or OpenGL ES API extension is supported. + * @param extension The name of the extension to check. + * @param len The length of the extension string. + * @return RGFW_TRUE if supported, RGFW_FALSE otherwise. +*/ +RGFWDEF RGFW_bool RGFW_extensionSupported_OpenGL(const char* extension, size_t len); + +/**! + * @brief Checks whether a specific platform-dependent OpenGL extension is supported. + * @param extension The name of the extension to check. + * @param len The length of the extension string. + * @return RGFW_TRUE if supported, RGFW_FALSE otherwise. +*/ +RGFWDEF RGFW_bool RGFW_extensionSupportedPlatform_OpenGL(const char* extension, size_t len); + +/* these are EGL specific functions, they may fallback to OpenGL */ +#ifdef RGFW_EGL +/**! + * @brief Creates and allocates an OpenGL/EGL context for the specified window. + * @param win A pointer to the target RGFW_window. + * @param hints A pointer to an RGFW_glHints structure defining context creation parameters. + * @return A pointer to the newly created RGFW_eglContext. +*/ +RGFWDEF RGFW_eglContext* RGFW_window_createContext_EGL(RGFW_window* win, RGFW_glHints* hints); + +/**! + * @brief Creates an OpenGL/EGL context for the specified window using a preallocated context structure. + * @param win A pointer to the target RGFW_window. + * @param ctx A pointer to an already allocated RGFW_eglContext structure. + * @param hints A pointer to an RGFW_glHints structure defining context creation parameters. + * @return RGFW_TRUE on success, RGFW_FALSE on failure. +*/ +RGFWDEF RGFW_bool RGFW_window_createContextPtr_EGL(RGFW_window* win, RGFW_eglContext* ctx, RGFW_glHints* hints); + +/**! + * @brief Frees and deletes an OpenGL/EGL context. + * @param win A pointer to the RGFW_window. + * @param ctx A pointer to the RGFW_eglContext to delete. + * + * @note Automatically called by RGFW_window_close if RGFW owns the context. +*/ +RGFWDEF void RGFW_window_deleteContext_EGL(RGFW_window* win, RGFW_eglContext* ctx); + +/**! + * @brief Deletes an OpenGL/EGL context without freeing its memory. + * @param win A pointer to the RGFW_window. + * @param ctx A pointer to the RGFW_eglContext to delete. + * + * @note Automatically called by RGFW_window_close if RGFW owns the context. +*/ +RGFWDEF void RGFW_window_deleteContextPtr_EGL(RGFW_window* win, RGFW_eglContext* ctx); + +/**! + * @brief Retrieves the OpenGL/EGL context associated with a window. + * @param win A pointer to the RGFW_window. + * @return A pointer to the associated RGFW_eglContext, or NULL if none exists or if the context is a native OpenGL context. +*/ +RGFWDEF RGFW_eglContext* RGFW_window_getContext_EGL(RGFW_window* win); + +/**! + * @brief Retrieves the EGL display handle. + * @return A pointer to the native EGLDisplay. +*/ +RGFWDEF void* RGFW_getDisplay_EGL(void); + +/**! + * @brief Retrieves the native source context from an RGFW_eglContext. + * @param ctx A pointer to the RGFW_eglContext. + * @return A pointer to the native EGLContext handle. +*/ +RGFWDEF void* RGFW_eglContext_getSourceContext(RGFW_eglContext* ctx); + +/**! + * @brief Retrieves the EGL surface handle from an RGFW_eglContext. + * @param ctx A pointer to the RGFW_eglContext. + * @return A pointer to the EGLSurface associated with the context. +*/ +RGFWDEF void* RGFW_eglContext_getSurface(RGFW_eglContext* ctx); + +/**! + * @brief Retrieves the Wayland EGL window handle from an RGFW_eglContext. + * @param ctx A pointer to the RGFW_eglContext. + * @return A pointer to the wl_egl_window associated with the EGL context. +*/ +RGFWDEF struct wl_egl_window* RGFW_eglContext_wlEGLWindow(RGFW_eglContext* ctx); + +/**! + * @brief Swaps the EGL buffers for the specified window. + * @param win A pointer to the RGFW_window whose buffers should be swapped. + * + * @note Typically called by RGFW_window_swapInterval. +*/ +RGFWDEF void RGFW_window_swapBuffers_EGL(RGFW_window* win); + +/**! + * @brief Makes the specified window the current EGL rendering target. + * @param win A pointer to the RGFW_window to make current. + * + * @note This is typically called internally by RGFW_window_makeCurrent. +*/ +RGFWDEF void RGFW_window_makeCurrentWindow_EGL(RGFW_window* win); + +/**! + * @brief Makes the EGL context of the specified window current. + * @param win A pointer to the RGFW_window whose context should be made current. + * + * @note To move a context between threads, call RGFW_window_makeCurrentContext_EGL(NULL) + * on the old thread before making it current on the new one. +*/ +RGFWDEF void RGFW_window_makeCurrentContext_EGL(RGFW_window* win); + +/**! + * @brief Retrieves the current EGL context. + * @return A pointer to the currently active EGLContext. +*/ +RGFWDEF void* RGFW_getCurrentContext_EGL(void); + +/**! + * @brief Retrieves the current EGL window. + * @return A pointer to the RGFW_window currently bound as the EGL context target. +*/ +RGFWDEF RGFW_window* RGFW_getCurrentWindow_EGL(void); + +/**! + * @brief Sets the EGL swap interval (vsync). + * @param win A pointer to the RGFW_window. + * @param swapInterval The desired swap interval value (0 to disable vsync, 1 to enable). +*/ +RGFWDEF void RGFW_window_swapInterval_EGL(RGFW_window* win, i32 swapInterval); + +/**! + * @brief Retrieves the address of a native OpenGL or OpenGL ES procedure in an EGL context. + * @param procname The name of the OpenGL function to look up. + * @return A pointer to the function, or NULL if not found. +*/ +RGFWDEF RGFW_proc RGFW_getProcAddress_EGL(const char* procname); + +/**! + * @brief Checks whether a specific OpenGL or OpenGL ES API extension is supported in the current EGL context. + * @param extension The name of the extension to check. + * @param len The length of the extension string. + * @return RGFW_TRUE if supported, RGFW_FALSE otherwise. +*/ +RGFWDEF RGFW_bool RGFW_extensionSupported_EGL(const char* extension, size_t len); + +/**! + * @brief Checks whether a specific platform-dependent EGL extension is supported in the current context. + * @param extension The name of the extension to check. + * @param len The length of the extension string. + * @return RGFW_TRUE if supported, RGFW_FALSE otherwise. +*/ +RGFWDEF RGFW_bool RGFW_extensionSupportedPlatform_EGL(const char* extension, size_t len); +#endif +#endif + +#ifdef RGFW_VULKAN +#include + +/* if you don't want to use the above macros */ + +/**! + * @brief Retrieves the Vulkan instance extensions required by RGFW. + * @param count [OUTPUT] A pointer that will receive the number of required extensions (typically 2). + * @return A pointer to a static array of required Vulkan instance extension names. +*/ +RGFWDEF const char** RGFW_getRequiredInstanceExtensions_Vulkan(size_t* count); + +/**! + * @brief Creates a Vulkan surface for the specified window. + * @param win A pointer to the RGFW_window for which to create the Vulkan surface. + * @param instance The Vulkan instance used to create the surface. + * @param surface [OUTPUT] A pointer to a VkSurfaceKHR handle that will receive the created surface. + * @return A VkResult indicating success or failure. +*/ +RGFWDEF VkResult RGFW_window_createSurface_Vulkan(RGFW_window* win, VkInstance instance, VkSurfaceKHR* surface); + +/**! + * @brief Checks whether the specified Vulkan physical device and queue family support presentation for RGFW. + * @param instance The Vulkan instance. + * @param physicalDevice The Vulkan physical device to check. + * @param queueFamilyIndex The index of the queue family to query for presentation support. + * @return RGFW_TRUE if presentation is supported, RGFW_FALSE otherwise. +*/ +RGFWDEF RGFW_bool RGFW_getPresentationSupport_Vulkan(VkInstance instance, VkPhysicalDevice physicalDevice, u32 queueFamilyIndex); +#endif + +#ifdef RGFW_DIRECTX +#ifndef RGFW_WINDOWS + #undef RGFW_DIRECTX +#else + #define OEMRESOURCE + #include + + #ifndef __cplusplus + #define __uuidof(T) IID_##T + #endif +/**! + * @brief Creates a DirectX swap chain for the specified RGFW window. + * @param win A pointer to the RGFW_window for which to create the swap chain. + * @param pFactory A pointer to the IDXGIFactory used to create the swap chain. + * @param pDevice A pointer to the DirectX device (e.g., ID3D11Device or ID3D12Device). + * @param swapchain [OUTPUT] A pointer to an IDXGISwapChain pointer that will receive the created swap chain. + * @return An integer result code (0 on success, or a DirectX error code on failure). +*/ +RGFWDEF int RGFW_window_createSwapChain_DirectX(RGFW_window* win, IDXGIFactory* pFactory, IUnknown* pDevice, IDXGISwapChain** swapchain); +#endif +#endif + +#ifdef RGFW_WEBGPU + #include + /**! + * @brief Creates a WebGPU surface for the specified RGFW window. + * @param window A pointer to the RGFW_window for which to create the surface. + * @param instance The WebGPU instance used to create the surface. + * @return The created WGPUSurface handle. + */ + RGFWDEF WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance instance); +#endif + +/** @} */ + +/** * @defgroup Supporting +* @{ */ + +/**! + * @brief Sets the root (main) RGFW window. + * @param win A pointer to the RGFW_window to set as the root window. +*/ +RGFWDEF void RGFW_setRootWindow(RGFW_window* win); + +/**! + * @brief Retrieves the current root RGFW window. + * @return A pointer to the current root RGFW_window. +*/ +RGFWDEF RGFW_window* RGFW_getRootWindow(void); + +/**! + * @brief Pushes an event into the standard RGFW event queue. + * @param event A pointer to the RGFW_event to be added to the queue. +*/ +RGFWDEF void RGFW_eventQueuePush(const RGFW_event* event); + +/**! + * @brief Clears all events from the RGFW event queue without processing them. +*/ +RGFWDEF void RGFW_eventQueueFlush(void); + +/**! + * @brief Pops the next event from the RGFW event queue for the specified window. + * @param win A pointer to the RGFW_window to retrieve an event for. + * @return A pointer to the popped RGFW_event, or NULL if the queue is empty. +*/ +RGFWDEF RGFW_event* RGFW_eventQueuePop(RGFW_window* win); + +/**! + * @brief Converts an API keycode to the RGFW unmapped (physical) key. + * @param keycode The platform-specific keycode. + * @return The corresponding RGFW keycode. +*/ +RGFWDEF u32 RGFW_apiKeyToRGFW(u32 keycode); + +/**! + * @brief Converts an RGFW keycode to the unmapped (physical) API key. + * @param keycode The RGFW keycode. + * @return The corresponding platform-specific keycode. +*/ +RGFWDEF u32 RGFW_rgfwToApiKey(u32 keycode); + +/**! + * @brief Converts an RGFW keycode to the mapped character representation. + * @param keycode The RGFW keycode. + * @return The corresponding key character. +*/ +RGFWDEF u8 RGFW_rgfwToKeyChar(u32 keycode); + +/**! + * @brief Retrieves the size of the RGFW_info structure. + * @return The size (in bytes) of RGFW_info. +*/ +RGFWDEF size_t RGFW_sizeofInfo(void); + +/**! + * @brief Initializes the RGFW library. + * @return 0 on success, or a negative error code on failure. + * @note This is automatically called when the first window is created. +*/ +RGFWDEF i32 RGFW_init(void); + +/**! + * @brief Deinitializes the RGFW library. + * @note This is automatically called when the last open window is closed. +*/ +RGFWDEF void RGFW_deinit(void); + +/**! + * @brief Initializes RGFW using a user-provided RGFW_info structure. + * @param info A pointer to an RGFW_info structure to be used for initialization. + * @return 0 on success, or a negative error code on failure. +*/ +RGFWDEF i32 RGFW_init_ptr(RGFW_info* info); + +/**! + * @brief Deinitializes a specific RGFW instance stored in the provided RGFW_info pointer. + * @param info A pointer to the RGFW_info structure representing the instance to deinitialize. +*/ +RGFWDEF void RGFW_deinit_ptr(RGFW_info* info); + +/**! + * @brief Sets the global RGFW_info structure pointer. + * @param info A pointer to the RGFW_info structure to set. +*/ +RGFWDEF void RGFW_setInfo(RGFW_info* info); + +/**! + * @brief Retrieves the global RGFW_info structure pointer. + * @return A pointer to the current RGFW_info structure. +*/ +RGFWDEF RGFW_info* RGFW_getInfo(void); + +/** @} */ +#endif /* RGFW_HEADER */ + +#if !defined(RGFW_NATIVE_HEADER) && (defined(RGFW_NATIVE) || defined(RGFW_IMPLEMENTATION)) +#define RGFW_NATIVE_HEADER + #if (defined(RGFW_OPENGL) || defined(RGFW_WEGL)) && defined(_MSC_VER) + #pragma comment(lib, "opengl32") + #endif + + #ifdef RGFW_OPENGL + struct RGFW_eglContext { + void* ctx; + void* surface; + struct wl_egl_window* eglWindow; + }; + + typedef union RGFW_gfxContext { + RGFW_glContext* native; + RGFW_eglContext* egl; + } RGFW_gfxContext; + + typedef RGFW_ENUM(u32, RGFW_gfxContextType) { + RGFW_gfxNativeOpenGL = RGFW_BIT(0), + RGFW_gfxEGL = RGFW_BIT(1), + RGFW_gfxOwnedByRGFW = RGFW_BIT(2) + }; + #endif + + /*! source data for the window (used by the APIs) */ + #ifdef RGFW_WINDOWS + + #define WIN32_LEAN_AND_MEAN + #define OEMRESOURCE + #include + + struct RGFW_nativeImage { + HBITMAP bitmap; + u8* bitmapBits; + RGFW_format format; + HDC hdcMem; + }; + + #ifdef RGFW_OPENGL + struct RGFW_glContext { HGLRC ctx; }; + #endif + + struct RGFW_window_src { + HWND window; /*!< source window */ + HDC hdc; /*!< source HDC */ + i32 offsetW, offsetH; /*!< width and height offset for window */ + HICON hIconSmall, hIconBig; /*!< source window icons */ + i32 maxSizeW, maxSizeH, minSizeW, minSizeH, aspectRatioW, aspectRatioH; /*!< for setting max/min resize (RGFW_WINDOWS) */ + #ifdef RGFW_OPENGL + RGFW_gfxContext ctx; + RGFW_gfxContextType gfxType; + #endif + }; + +#elif defined(RGFW_UNIX) + #ifdef RGFW_X11 + #include + #include + #endif + + #ifdef RGFW_WAYLAND + #ifdef RGFW_LIBDECOR + #include + #endif + + #include + #include + #endif + + struct RGFW_nativeImage { + #ifdef RGFW_X11 + XImage* bitmap; + #endif + #ifdef RGFW_WAYLAND + struct wl_buffer* wl_buffer; + #endif + u8* buffer; + RGFW_format format; + }; + + #ifdef RGFW_OPENGL + struct RGFW_glContext { + #ifdef RGFW_X11 + struct __GLXcontextRec* ctx; /*!< source graphics context */ + Window window; + #endif + #ifdef RGFW_WAYLAND + RGFW_eglContext egl; + #endif + }; + #endif + + struct RGFW_window_src { + i32 x, y, w, h; + #ifdef RGFW_OPENGL + RGFW_gfxContext ctx; + RGFW_gfxContextType gfxType; + #endif +#ifdef RGFW_X11 + Window window; /*!< source window */ + Window parent; /*!< parent window */ + GC gc; + #ifdef RGFW_ADVANCED_SMOOTH_RESIZE + i64 counter_value; + XID counter; + #endif +#endif /* RGFW_X11 */ + +#if defined(RGFW_WAYLAND) + struct wl_surface* surface; + struct xdg_surface* xdg_surface; + struct xdg_toplevel* xdg_toplevel; + struct zxdg_toplevel_decoration_v1* decoration; + struct zwp_locked_pointer_v1 *locked_pointer; + struct xdg_toplevel_icon_v1 *icon; + u32 decoration_mode; + /* State flags to configure the window */ + RGFW_bool pending_activated; + RGFW_bool activated; + RGFW_bool resizing; + RGFW_bool pending_maximized; + RGFW_bool maximized; + RGFW_bool minimized; + + RGFW_bool using_custom_cursor; + struct wl_surface* custom_cursor_surface; + + RGFW_monitor active_monitor; + + struct wl_data_source *data_source; // offer data to other clients + + #ifdef RGFW_LIBDECOR + struct libdecor* decorContext; + #endif +#endif /* RGFW_WAYLAND */ + }; + +#elif defined(RGFW_MACOS) + + struct RGFW_nativeImage { + RGFW_format format; + }; + + #ifdef RGFW_OPENGL + struct RGFW_glContext { void* ctx; }; + #endif + + struct RGFW_window_src { + void* window; + void* view; /* apple viewpoint thingy */ + void* mouse; + #ifdef RGFW_OPENGL + RGFW_gfxContext ctx; + RGFW_gfxContextType gfxType; + #endif + }; + +#elif defined(RGFW_WASM) + + #include + #include + + struct RGFW_nativeImage { + RGFW_format format; + }; + + #ifdef RGFW_OPENGL + struct RGFW_glContext { + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx; + }; + #endif + + struct RGFW_window_src { + #ifdef RGFW_OPENGL + RGFW_gfxContext ctx; + RGFW_gfxContextType gfxType; + #endif + }; + +#endif + +struct RGFW_surface { + u8* data; + i32 w, h; + RGFW_format format; + RGFW_nativeImage native; +}; + +/*! internal window data that is not specific to the OS */ +typedef struct RGFW_windowInternal { + /*! which key RGFW_window_shouldClose checks. Settting this to RGFW_keyNULL disables the feature. */ + RGFW_key exitKey; + i32 lastMouseX, lastMouseY; /*!< last cusor point (for raw mouse data) */ + + RGFW_bool shouldClose; + RGFW_bool holdMouse; + RGFW_bool inFocus; + RGFW_bool mouseInside; + RGFW_keymod mod; + RGFW_eventFlag enabledEvents; + u32 flags; /*!< windows flags (for RGFW to check and modify) */ + i32 oldX, oldY, oldW, oldH; +} RGFW_windowInternal; + +struct RGFW_window { + RGFW_window_src src; /*!< src window data */ + RGFW_windowInternal internal; /*!< internal window data that is not specific to the OS */ + void* userPtr; /* ptr for usr data */ + i32 x, y, w, h; /*!< position and size of the window */ +}; /*!< window structure for the window */ + +typedef struct RGFW_windowState { + RGFW_bool mouseEnter; + RGFW_bool dataDragging; + RGFW_bool dataDrop; + size_t filesCount; + i32 dropX, dropY; + RGFW_window* win; /*!< it's not possible for one of these events to happen in the frame that the other event happened */ + + RGFW_bool mouseLeave; + RGFW_window* winLeave; /*!< if a mouse leaves one widow and enters the next */ +} RGFW_windowState; + +typedef struct { + RGFW_bool current; + RGFW_bool prev; +} RGFW_keyState; + +#ifndef RGFW_NO_MONITOR + typedef struct RGFW_monitorNode { + RGFW_monitor mon; + struct RGFW_monitorNode* next; +#ifdef RGFW_WAYLAND + u32 id; /* Add id so wl_outputs can be removed */ + struct wl_output *output; + struct zxdg_output_v1 *xdg_output; +#endif + } RGFW_monitorNode; + + typedef struct RGFW_monitorList { + RGFW_monitorNode* head; + RGFW_monitorNode* cur; + } RGFW_monitorList; + + typedef struct RGFW_monitors { + RGFW_monitorList list; + RGFW_monitorList freeList; + size_t count; + RGFW_monitorNode data[RGFW_MAX_MONITORS]; + } RGFW_monitors; + + RGFWDEF RGFW_monitorNode* RGFW_monitors_add(RGFW_monitor mon); + RGFWDEF void RGFW_monitors_remove(RGFW_monitorNode* node, RGFW_monitorNode* prev); +#endif + +struct RGFW_info { + RGFW_window* root; + i32 windowCount; + + RGFW_mouse* hiddenMouse; + + RGFW_event events[RGFW_MAX_EVENTS]; /* A circular buffer (FIFO), using eventBottom/Len */ + + i32 eventBottom; + i32 eventLen; + RGFW_bool queueEvents; + RGFW_bool polledEvents; + + u32 apiKeycodes[RGFW_keyLast]; + #if defined(RGFW_X11) || defined(RGFW_WAYLAND) + u8 keycodes[256]; + #elif defined(RGFW_WINDOWS) + u8 keycodes[512]; + #elif defined(RGFW_MACOS) + u8 keycodes[128]; + #elif defined(RGFW_WASM) + u8 keycodes[256]; + #endif + + const char* className; + RGFW_bool useWaylandBool; + RGFW_bool stopCheckEvents_bool ; + u64 timerOffset; + + char* clipboard_data; + char* clipboard; /* for writing to the clipboard selection */ + size_t clipboard_len; + char filesSrc[RGFW_MAX_PATH * RGFW_MAX_DROPS]; + char** files; + #ifdef RGFW_X11 + Display* display; + XContext context; + Window helperWindow; + const char* instName; + XErrorEvent* x11Error; + #endif + #ifdef RGFW_WAYLAND + struct wl_display* wl_display; + struct xkb_context *xkb_context; + struct xkb_keymap *keymap; + struct xkb_state *xkb_state; + struct zxdg_decoration_manager_v1 *decoration_manager; + struct zwp_relative_pointer_manager_v1 *relative_pointer_manager; + struct zwp_relative_pointer_v1 *relative_pointer; + struct zwp_pointer_constraints_v1 *constraint_manager; + struct xdg_toplevel_icon_manager_v1 *icon_manager; + + struct zxdg_output_manager_v1 *xdg_output_manager; + + struct wl_data_device_manager *data_device_manager; + struct wl_data_device *data_device; // supports clipboard and DND + + struct wl_keyboard* wl_keyboard; + struct wl_pointer* wl_pointer; + struct wl_compositor* compositor; + struct xdg_wm_base* xdg_wm_base; + struct wl_shm* shm; + struct wl_seat *seat; + struct wl_registry *registry; + u32 mouse_enter_serial; + struct wl_cursor_theme* wl_cursor_theme; + struct wl_surface* cursor_surface; + + RGFW_window* kbOwner; + + #endif + + RGFW_monitors monitors; + + #ifdef RGFW_UNIX + int eventWait_forceStop[3]; + #endif + + #ifdef RGFW_MACOS + void* NSApp; + void* customViewClasses[2]; /* NSView and NSOpenGLView */ + void* customWindowDelegateClass; + #endif + + #ifdef RGFW_OPENGL + RGFW_window* current; + #endif + #ifdef RGFW_EGL + void* EGL_display; + #endif + + RGFW_window* mouseOwner; + RGFW_windowState windowState; /*! for checking window state events */ + + RGFW_keyState mouseButtons[RGFW_mouseFinal]; + RGFW_keyState keyboard[RGFW_keyLast]; + float scrollX, scrollY; + float vectorX, vectorY; +}; +#endif /* RGFW_NATIVE_HEADER */ #ifdef RGFW_IMPLEMENTATION -RGFW_bool RGFW_useWaylandBool = 1; -void RGFW_useWayland(RGFW_bool wayland) { RGFW_useWaylandBool = wayland; } -RGFW_bool RGFW_usingWayland(void) { return RGFW_useWaylandBool; } -#if !defined(RGFW_NO_X11) && defined(RGFW_WAYLAND) -#define RGFW_GOTO_WAYLAND(fallback) if (RGFW_useWaylandBool && fallback == 0) goto wayland -#define RGFW_WAYLAND_LABEL wayland:; -#else -#define RGFW_GOTO_WAYLAND(fallback) -#define RGFW_WAYLAND_LABEL +/* global private API */ + +/* for C++ / C89 */ +#define RGFW_eventQueuePushEx(eventInit) { RGFW_event e; eventInit; RGFW_eventQueuePush(&e); } + +RGFWDEF RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, RGFW_window* win); +RGFWDEF void RGFW_window_closePlatform(RGFW_window* win); + +RGFWDEF void RGFW_window_focusLost(RGFW_window* win); +RGFWDEF void RGFW_window_setFlagsInternal(RGFW_window* win, RGFW_windowFlags flags, RGFW_windowFlags cmpFlags); + +RGFWDEF void RGFW_initKeycodes(void); +RGFWDEF void RGFW_initKeycodesPlatform(void); +RGFWDEF void RGFW_resetPrevState(void); +RGFWDEF void RGFW_resetKey(void); +RGFWDEF void RGFW_unloadEGL(void); +RGFWDEF void RGFW_updateKeyModsEx(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll); +RGFWDEF void RGFW_updateKeyMods(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool scroll); +RGFWDEF void RGFW_window_showMouseFlags(RGFW_window* win, RGFW_bool show); +RGFWDEF void RGFW_updateKeyMod(RGFW_window* win, RGFW_keymod mod, RGFW_bool value); + +RGFWDEF void RGFW_setBit(u32* var, u32 mask, RGFW_bool set); +RGFWDEF void RGFW_splitBPP(u32 bpp, RGFW_monitorMode* mode); + +RGFWDEF void RGFW_captureCursor(RGFW_window* win); +RGFWDEF void RGFW_releaseCursor(RGFW_window* win); + +RGFWDEF void RGFW_copyImageData64(u8* dest_data, i32 w, i32 h, RGFW_format dest_format, + u8* src_data, RGFW_format src_format, RGFW_bool is64bit); + +RGFWDEF RGFW_bool RGFW_loadEGL(void); + +#ifdef RGFW_OPENGL +typedef struct RGFW_attribStack { + i32* attribs; + size_t count; + size_t max; +} RGFW_attribStack; +RGFWDEF void RGFW_attribStack_init(RGFW_attribStack* stack, i32* attribs, size_t max); +RGFWDEF void RGFW_attribStack_pushAttrib(RGFW_attribStack* stack, i32 attrib); +RGFWDEF void RGFW_attribStack_pushAttribs(RGFW_attribStack* stack, i32 attrib1, i32 attrib2); + +RGFWDEF RGFW_bool RGFW_extensionSupportedStr(const char* extensions, const char* ext, size_t len); #endif -char* RGFW_clipboard_data; +typedef struct RGFW_colorLayout { i32 r, g, b, a; } RGFW_colorLayout; + +#ifdef RGFW_X11 +RGFWDEF void RGFW_XCreateWindow (XVisualInfo visual, const char* name, RGFW_windowFlags flags, RGFW_window* win); +#endif +#ifdef RGFW_MACOS +RGFWDEF void RGFW_osx_initView(RGFW_window* win); +#endif +/* end of global private API defs */ + +RGFW_info* _RGFW = NULL; +void RGFW_setInfo(RGFW_info* info) { _RGFW = info; } +RGFW_info* RGFW_getInfo(void) { return _RGFW; } + + +void* RGFW_alloc(size_t size) { return RGFW_ALLOC(size); } +void RGFW_free(void* ptr) { RGFW_FREE(ptr); } + +void RGFW_useWayland(RGFW_bool wayland) { RGFW_init(); _RGFW->useWaylandBool = RGFW_BOOL(wayland); } +RGFW_bool RGFW_usingWayland(void) { return _RGFW->useWaylandBool; } + void RGFW_clipboard_switch(char* newstr); void RGFW_clipboard_switch(char* newstr) { - if (RGFW_clipboard_data != NULL) - RGFW_FREE(RGFW_clipboard_data); - RGFW_clipboard_data = newstr; + if (_RGFW->clipboard_data != NULL) + RGFW_FREE(_RGFW->clipboard_data); + _RGFW->clipboard_data = newstr; } #define RGFW_CHECK_CLIPBOARD() \ - if (size <= 0 && RGFW_clipboard_data != NULL) \ - return (const char*)RGFW_clipboard_data; \ + if (size <= 0 && _RGFW->clipboard_data != NULL) \ + return (const char*)_RGFW->clipboard_data; \ else if (size <= 0) \ return "\0"; @@ -1589,52 +3036,6 @@ const char* RGFW_readClipboard(size_t* len) { return (const char*)str; } -RGFW_debugfunc RGFW_debugCallback = NULL; -RGFW_debugfunc RGFW_setDebugCallback(RGFW_debugfunc func) { - RGFW_debugfunc RGFW_debugCallbackPrev = RGFW_debugCallback; - RGFW_debugCallback = func; - return RGFW_debugCallbackPrev; -} - -#ifdef RGFW_DEBUG -#include -#endif - -void RGFW_sendDebugInfo(RGFW_debugType type, RGFW_errorCode err, RGFW_debugContext ctx, const char* msg) { - if (RGFW_debugCallback) RGFW_debugCallback(type, err, ctx, msg); - #ifdef RGFW_DEBUG - switch (type) { - case RGFW_typeInfo: printf("RGFW INFO (%i %i): %s", type, err, msg); break; - case RGFW_typeError: printf("RGFW DEBUG (%i %i): %s", type, err, msg); break; - case RGFW_typeWarning: printf("RGFW WARNING (%i %i): %s", type, err, msg); break; - default: break; - } - - switch (err) { - #ifdef RGFW_BUFFER - case RGFW_errBuffer: case RGFW_infoBuffer: printf(" buffer size: %i %i\n", ctx.win->bufferSize.w, ctx.win->bufferSize.h); break; - #endif - case RGFW_infoMonitor: printf(": scale (%s):\n rect: {%i, %i, %i, %i}\n physical size:%f %f\n scale: %f %f\n pixelRatio: %f\n refreshRate: %i\n depth: %i\n", ctx.monitor->name, ctx.monitor->x, ctx.monitor->y, ctx.monitor->mode.area.w, ctx.monitor->mode.area.h, ctx.monitor->physW, ctx.monitor->physH, ctx.monitor->scaleX, ctx.monitor->scaleY, ctx.monitor->pixelRatio, ctx.monitor->mode.refreshRate, ctx.monitor->mode.red + ctx.monitor->mode.green + ctx.monitor->mode.blue); break; - case RGFW_infoWindow: printf(" with rect of {%i, %i, %i, %i} \n", ctx.win->r.x, ctx.win->r.y,ctx. win->r.w, ctx.win->r.h); break; - case RGFW_errDirectXContext: printf(" srcError %i\n", ctx.srcError); break; - default: printf("\n"); - } - #endif -} - -u64 RGFW_timerOffset = 0; -void RGFW_setTime(double time) { - RGFW_timerOffset = RGFW_getTimerValue() - (u64)(time * (double)RGFW_getTimerFreq()); -} - -double RGFW_getTime(void) { - return (double) ((double)(RGFW_getTimerValue() - RGFW_timerOffset) / (double)RGFW_getTimerFreq()); -} - -u64 RGFW_getTimeNS(void) { - return (u64)(((double)((RGFW_getTimerValue() - RGFW_timerOffset)) * 1e9) / (double)RGFW_getTimerFreq()); -} - /* RGFW_IMPLEMENTATION starts with generic RGFW defines @@ -1643,205 +3044,44 @@ This is the start of keycode data -/* - the c++ compiler doesn't support setting up an array like, - we'll have to do it during runtime using a function & this messy setup -*/ - -#ifndef RGFW_CUSTOM_BACKEND - -#if !defined(__cplusplus) && !defined(RGFW_C89) -#define RGFW_NEXT , -#define RGFW_MAP -#else -#define RGFW_NEXT ; -#define RGFW_MAP RGFW_keycodes -#endif - -u32 RGFW_apiKeycodes[RGFW_keyLast] = { 0 }; - -u8 RGFW_keycodes [RGFW_OS_BASED_VALUE(256, 512, 128, 256)] = { -#if defined(__cplusplus) || defined(RGFW_C89) - 0 -}; -void RGFW_init_keys(void); -void RGFW_init_keys(void) { -#endif - RGFW_MAP [RGFW_OS_BASED_VALUE(49, 0x029, 50, DOM_VK_BACK_QUOTE)] = RGFW_backtick RGFW_NEXT - - RGFW_MAP [RGFW_OS_BASED_VALUE(19, 0x00B, 29, DOM_VK_0)] = RGFW_0 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(10, 0x002, 18, DOM_VK_1)] = RGFW_1 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(11, 0x003, 19, DOM_VK_2)] = RGFW_2 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(12, 0x004, 20, DOM_VK_3)] = RGFW_3 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(13, 0x005, 21, DOM_VK_4)] = RGFW_4 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(14, 0x006, 23, DOM_VK_5)] = RGFW_5 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(15, 0x007, 22, DOM_VK_6)] = RGFW_6 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(16, 0x008, 26, DOM_VK_7)] = RGFW_7 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(17, 0x009, 28, DOM_VK_8)] = RGFW_8 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(18, 0x00A, 25, DOM_VK_9)] = RGFW_9, - RGFW_MAP [RGFW_OS_BASED_VALUE(65, 0x039, 49, DOM_VK_SPACE)] = RGFW_space, - RGFW_MAP [RGFW_OS_BASED_VALUE(38, 0x01E, 0, DOM_VK_A)] = RGFW_a RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(56, 0x030, 11, DOM_VK_B)] = RGFW_b RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(54, 0x02E, 8, DOM_VK_C)] = RGFW_c RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(40, 0x020, 2, DOM_VK_D)] = RGFW_d RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(26, 0x012, 14, DOM_VK_E)] = RGFW_e RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(41, 0x021, 3, DOM_VK_F)] = RGFW_f RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(42, 0x022, 5, DOM_VK_G)] = RGFW_g RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(43, 0x023, 4, DOM_VK_H)] = RGFW_h RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(31, 0x017, 34, DOM_VK_I)] = RGFW_i RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(44, 0x024, 38, DOM_VK_J)] = RGFW_j RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(45, 0x025, 40, DOM_VK_K)] = RGFW_k RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(46, 0x026, 37, DOM_VK_L)] = RGFW_l RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(58, 0x032, 46, DOM_VK_M)] = RGFW_m RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(57, 0x031, 45, DOM_VK_N)] = RGFW_n RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(32, 0x018, 31, DOM_VK_O)] = RGFW_o RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(33, 0x019, 35, DOM_VK_P)] = RGFW_p RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(24, 0x010, 12, DOM_VK_Q)] = RGFW_q RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(27, 0x013, 15, DOM_VK_R)] = RGFW_r RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(39, 0x01F, 1, DOM_VK_S)] = RGFW_s RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(28, 0x014, 17, DOM_VK_T)] = RGFW_t RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(30, 0x016, 32, DOM_VK_U)] = RGFW_u RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(55, 0x02F, 9, DOM_VK_V)] = RGFW_v RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(25, 0x011, 13, DOM_VK_W)] = RGFW_w RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(53, 0x02D, 7, DOM_VK_X)] = RGFW_x RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(29, 0x015, 16, DOM_VK_Y)] = RGFW_y RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(52, 0x02C, 6, DOM_VK_Z)] = RGFW_z, - RGFW_MAP [RGFW_OS_BASED_VALUE(60, 0x034, 47, DOM_VK_PERIOD)] = RGFW_period RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(59, 0x033, 43, DOM_VK_COMMA)] = RGFW_comma RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(61, 0x035, 44, DOM_VK_SLASH)] = RGFW_slash RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(34, 0x01A, 33, DOM_VK_OPEN_BRACKET)] = RGFW_bracket RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(35, 0x01B, 30, DOM_VK_CLOSE_BRACKET)] = RGFW_closeBracket RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(47, 0x027, 41, DOM_VK_SEMICOLON)] = RGFW_semicolon RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(48, 0x028, 39, DOM_VK_QUOTE)] = RGFW_apostrophe RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(51, 0x02B, 42, DOM_VK_BACK_SLASH)] = RGFW_backSlash, - RGFW_MAP [RGFW_OS_BASED_VALUE(36, 0x01C, 36, DOM_VK_RETURN)] = RGFW_return RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(119, 0x153, 118, DOM_VK_DELETE)] = RGFW_delete RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(77, 0x145, 72, DOM_VK_NUM_LOCK)] = RGFW_numLock RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(106, 0x135, 82, DOM_VK_DIVIDE)] = RGFW_KP_Slash RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(63, 0x037, 76, DOM_VK_MULTIPLY)] = RGFW_multiply RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(82, 0x04A, 67, DOM_VK_SUBTRACT)] = RGFW_KP_Minus RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(87, 0x04F, 84, DOM_VK_NUMPAD1)] = RGFW_KP_1 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(88, 0x050, 85, DOM_VK_NUMPAD2)] = RGFW_KP_2 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(89, 0x051, 86, DOM_VK_NUMPAD3)] = RGFW_KP_3 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(83, 0x04B, 87, DOM_VK_NUMPAD4)] = RGFW_KP_4 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(84, 0x04C, 88, DOM_VK_NUMPAD5)] = RGFW_KP_5 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(85, 0x04D, 89, DOM_VK_NUMPAD6)] = RGFW_KP_6 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(79, 0x047, 90, DOM_VK_NUMPAD7)] = RGFW_KP_7 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(80, 0x048, 92, DOM_VK_NUMPAD8)] = RGFW_KP_8 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(81, 0x049, 93, DOM_VK_NUMPAD9)] = RGFW_KP_9 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(90, 0x052, 83, DOM_VK_NUMPAD0)] = RGFW_KP_0 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(91, 0x053, 65, DOM_VK_DECIMAL)] = RGFW_KP_Period RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(104, 0x11C, 77, 0)] = RGFW_KP_Return, - RGFW_MAP [RGFW_OS_BASED_VALUE(20, 0x00C, 27, DOM_VK_HYPHEN_MINUS)] = RGFW_minus RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(21, 0x00D, 24, DOM_VK_EQUALS)] = RGFW_equals RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(22, 0x00E, 51, DOM_VK_BACK_SPACE)] = RGFW_backSpace RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(23, 0x00F, 48, DOM_VK_TAB)] = RGFW_tab RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(66, 0x03A, 57, DOM_VK_CAPS_LOCK)] = RGFW_capsLock RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(50, 0x02A, 56, DOM_VK_SHIFT)] = RGFW_shiftL RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(37, 0x01D, 59, DOM_VK_CONTROL)] = RGFW_controlL RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(64, 0x038, 58, DOM_VK_ALT)] = RGFW_altL RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(133, 0x15B, 55, DOM_VK_WIN)] = RGFW_superL, - #if !defined(RGFW_MACOS) && !defined(RGFW_WASM) - RGFW_MAP [RGFW_OS_BASED_VALUE(105, 0x11D, 59, 0)] = RGFW_controlR RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(134, 0x15C, 55, 0)] = RGFW_superR, - RGFW_MAP [RGFW_OS_BASED_VALUE(62, 0x036, 56, 0)] = RGFW_shiftR RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(108, 0x138, 58, 0)] = RGFW_altR, - #endif - RGFW_MAP [RGFW_OS_BASED_VALUE(67, 0x03B, 127, DOM_VK_F1)] = RGFW_F1 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(68, 0x03C, 121, DOM_VK_F2)] = RGFW_F2 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(69, 0x03D, 100, DOM_VK_F3)] = RGFW_F3 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(70, 0x03E, 119, DOM_VK_F4)] = RGFW_F4 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(71, 0x03F, 97, DOM_VK_F5)] = RGFW_F5 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(72, 0x040, 98, DOM_VK_F6)] = RGFW_F6 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(73, 0x041, 99, DOM_VK_F7)] = RGFW_F7 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(74, 0x042, 101, DOM_VK_F8)] = RGFW_F8 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(75, 0x043, 102, DOM_VK_F9)] = RGFW_F9 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(76, 0x044, 110, DOM_VK_F10)] = RGFW_F10 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(95, 0x057, 104, DOM_VK_F11)] = RGFW_F11 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(96, 0x058, 111, DOM_VK_F12)] = RGFW_F12 RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(111, 0x148, 126, DOM_VK_UP)] = RGFW_up RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(116, 0x150, 125, DOM_VK_DOWN)] = RGFW_down RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(113, 0x14B, 123, DOM_VK_LEFT)] = RGFW_left RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(114, 0x14D, 124, DOM_VK_RIGHT)] = RGFW_right RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(118, 0x152, 115, DOM_VK_INSERT)] = RGFW_insert RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(115, 0x14F, 120, DOM_VK_END)] = RGFW_end RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(112, 0x149, 117, DOM_VK_PAGE_UP)] = RGFW_pageUp RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(117, 0x151, 122, DOM_VK_PAGE_DOWN)] = RGFW_pageDown RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(9, 0x001, 53, DOM_VK_ESCAPE)] = RGFW_escape RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(110, 0x147, 116, DOM_VK_HOME)] = RGFW_home RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(78, 0x046, 107, DOM_VK_SCROLL_LOCK)] = RGFW_scrollLock RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(107, 0x137, 105, DOM_VK_PRINTSCREEN)] = RGFW_printScreen RGFW_NEXT - RGFW_MAP [RGFW_OS_BASED_VALUE(128, 0x045, 113, DOM_VK_PAUSE)] = RGFW_pause RGFW_NEXT -#if defined(__cplusplus) || defined(RGFW_C89) -} -#else -}; -#endif - -#undef RGFW_NEXT -#undef RGFW_MAP - -u32 RGFW_apiKeyToRGFW(u32 keycode) { - #if defined(__cplusplus) || defined(RGFW_C89) - if (RGFW_keycodes[RGFW_OS_BASED_VALUE(49, 0x029, 50, DOM_VK_BACK_QUOTE)] != RGFW_backtick) { - RGFW_init_keys(); - } - #endif - - /* make sure the key isn't out of bounds */ - if (keycode > sizeof(RGFW_keycodes) / sizeof(u8)) - return 0; - - return RGFW_keycodes[keycode]; -} - -u32 RGFW_rgfwToApiKey(u32 keycode) { - if (RGFW_apiKeycodes[RGFW_backtick] != RGFW_OS_BASED_VALUE(49, 0x029, 50, DOM_VK_BACK_QUOTE)) { - for (u32 i = 0; i < RGFW_keyLast; i++) { - for (u32 y = 0; y < sizeof(RGFW_keycodes); y++) { - if (RGFW_keycodes[y] == i) { - RGFW_apiKeycodes[i] = y; - break; - } +void RGFW_initKeycodes(void) { + RGFW_MEMSET(_RGFW->keycodes, 0, sizeof(_RGFW->keycodes)); + RGFW_initKeycodesPlatform(); + u32 i, y; + for (i = 0; i < RGFW_keyLast; i++) { + for (y = 0; y < sizeof(_RGFW->keycodes); y++) { + if (_RGFW->keycodes[y] == i) { + _RGFW->apiKeycodes[i] = y; + break; } } } - /* make sure the key isn't out of bounds */ - if (keycode > sizeof(RGFW_apiKeycodes) / sizeof(u32)) + + RGFW_resetKey(); +} + +u32 RGFW_apiKeyToRGFW(u32 keycode) { + /* make sure the key isn't out of bounds */ + if (keycode > sizeof(_RGFW->keycodes) / sizeof(u8)) return 0; - return RGFW_apiKeycodes[keycode]; + return _RGFW->keycodes[keycode]; } -#endif /* RGFW_CUSTOM_BACKEND */ -typedef struct { - RGFW_bool current : 1; - RGFW_bool prev : 1; -} RGFW_keyState; +u32 RGFW_rgfwToApiKey(u32 keycode) { + /* make sure the key isn't out of bounds */ + if (keycode > sizeof(_RGFW->apiKeycodes) / sizeof(u32)) + return 0; -RGFW_keyState RGFW_keyboard[RGFW_keyLast] = { {0, 0} }; - -RGFWDEF void RGFW_resetKeyPrev(void); -void RGFW_resetKeyPrev(void) { - size_t i; /*!< reset each previous state */ - for (i = 0; i < RGFW_keyLast; i++) RGFW_keyboard[i].prev = 0; + return _RGFW->apiKeycodes[keycode]; } -RGFWDEF void RGFW_resetKey(void); -void RGFW_resetKey(void) { RGFW_MEMSET(RGFW_keyboard, 0, sizeof(RGFW_keyboard)); } + +void RGFW_resetKey(void) { RGFW_MEMSET(_RGFW->keyboard, 0, sizeof(_RGFW->keyboard)); } /* this is the end of keycode data */ -/* gamepad data */ -RGFW_keyState RGFW_gamepadPressed[4][32]; /*!< if a key is currently pressed or not (per gamepad) */ -RGFW_point RGFW_gamepadAxes[4][4]; /*!< if a key is currently pressed or not (per gamepad) */ - -RGFW_gamepadType RGFW_gamepads_type[4]; /*!< if a key is currently pressed or not (per gamepad) */ -i32 RGFW_gamepads[4] = {0, 0, 0, 0}; /*!< limit of 4 gamepads at a time */ -char RGFW_gamepads_name[4][128]; /*!< gamepad names */ -u16 RGFW_gamepadCount = 0; /*!< the actual amount of gamepads */ - /* event callback defines start here */ @@ -1854,79 +3094,101 @@ u16 RGFW_gamepadCount = 0; /*!< the actual amount of gamepads */ RGFW_EMPTY_DEF exists to prevent the missing-prototypes warning */ -static void RGFW_windowMovedfuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } -static void RGFW_windowResizedfuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } -static void RGFW_windowRestoredfuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } -static void RGFW_windowMinimizedfuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } -static void RGFW_windowMaximizedfuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } -static void RGFW_windowQuitfuncEMPTY(RGFW_window* win) { RGFW_UNUSED(win); } -static void RGFW_focusfuncEMPTY(RGFW_window* win, RGFW_bool inFocus) {RGFW_UNUSED(win); RGFW_UNUSED(inFocus);} -static void RGFW_mouseNotifyfuncEMPTY(RGFW_window* win, RGFW_point point, RGFW_bool status) {RGFW_UNUSED(win); RGFW_UNUSED(point); RGFW_UNUSED(status);} -static void RGFW_mousePosfuncEMPTY(RGFW_window* win, RGFW_point point, RGFW_point vector) {RGFW_UNUSED(win); RGFW_UNUSED(point); RGFW_UNUSED(vector);} -static void RGFW_dndInitfuncEMPTY(RGFW_window* win, RGFW_point point) {RGFW_UNUSED(win); RGFW_UNUSED(point);} -static void RGFW_windowRefreshfuncEMPTY(RGFW_window* win) {RGFW_UNUSED(win); } -static void RGFW_keyfuncEMPTY(RGFW_window* win, RGFW_key key, u8 keyChar, RGFW_keymod keyMod, RGFW_bool pressed) {RGFW_UNUSED(win); RGFW_UNUSED(key); RGFW_UNUSED(keyChar); RGFW_UNUSED(keyMod); RGFW_UNUSED(pressed);} -static void RGFW_mouseButtonfuncEMPTY(RGFW_window* win, RGFW_mouseButton button, double scroll, RGFW_bool pressed) {RGFW_UNUSED(win); RGFW_UNUSED(button); RGFW_UNUSED(scroll); RGFW_UNUSED(pressed);} -static void RGFW_gamepadButtonfuncEMPTY(RGFW_window* win, u16 gamepad, u8 button, RGFW_bool pressed) {RGFW_UNUSED(win); RGFW_UNUSED(gamepad); RGFW_UNUSED(button); RGFW_UNUSED(pressed); } -static void RGFW_gamepadAxisfuncEMPTY(RGFW_window* win, u16 gamepad, RGFW_point axis[2], u8 axisesCount, u8 whichAxis) {RGFW_UNUSED(win); RGFW_UNUSED(gamepad); RGFW_UNUSED(axis); RGFW_UNUSED(axisesCount); RGFW_UNUSED(whichAxis); } -static void RGFW_gamepadfuncEMPTY(RGFW_window* win, u16 gamepad, RGFW_bool connected) {RGFW_UNUSED(win); RGFW_UNUSED(gamepad); RGFW_UNUSED(connected);} -static void RGFW_dndfuncEMPTY(RGFW_window* win, char** droppedFiles, size_t droppedFilesCount) {RGFW_UNUSED(win); RGFW_UNUSED(droppedFiles); RGFW_UNUSED(droppedFilesCount);} -static void RGFW_scaleUpdatedfuncEMPTY(RGFW_window* win, float scaleX, float scaleY) {RGFW_UNUSED(win); RGFW_UNUSED(scaleX); RGFW_UNUSED(scaleY); } - #define RGFW_CALLBACK_DEFINE(x, x2) \ -RGFW_##x##func RGFW_##x##Callback = RGFW_##x##funcEMPTY; \ +RGFW_##x##func RGFW_##x##CallbackSrc = NULL; \ RGFW_##x##func RGFW_set##x2##Callback(RGFW_##x##func func) { \ - RGFW_##x##func prev = RGFW_##x##Callback; \ - RGFW_##x##Callback = func; \ + RGFW_##x##func prev = RGFW_##x##CallbackSrc; \ + RGFW_##x##CallbackSrc = func; \ return prev; \ } + RGFW_CALLBACK_DEFINE(windowMaximized, WindowMaximized) +#define RGFW_windowMaximizedCallback(win, x, y, w, h) if (RGFW_windowMaximizedCallbackSrc) RGFW_windowMaximizedCallbackSrc(win, x, y, w, h); + RGFW_CALLBACK_DEFINE(windowMinimized, WindowMinimized) +#define RGFW_windowMinimizedCallback(w) if (RGFW_windowMinimizedCallbackSrc) RGFW_windowMinimizedCallbackSrc(w); + RGFW_CALLBACK_DEFINE(windowRestored, WindowRestored) +#define RGFW_windowRestoredCallback(win, x, y, w, h) if (RGFW_windowRestoredCallbackSrc) RGFW_windowRestoredCallbackSrc(win, x, y, w, h); + RGFW_CALLBACK_DEFINE(windowMoved, WindowMoved) +#define RGFW_windowMovedCallback(w, x, y) if (RGFW_windowMovedCallbackSrc) RGFW_windowMovedCallbackSrc(w, x, y); + RGFW_CALLBACK_DEFINE(windowResized, WindowResized) +#define RGFW_windowResizedCallback(win, w, h) if (RGFW_windowResizedCallbackSrc) RGFW_windowResizedCallbackSrc(win, w, h); + RGFW_CALLBACK_DEFINE(windowQuit, WindowQuit) +#define RGFW_windowQuitCallback(w) if (RGFW_windowQuitCallbackSrc) RGFW_windowQuitCallbackSrc(w); + RGFW_CALLBACK_DEFINE(mousePos, MousePos) +#define RGFW_mousePosCallback(w, x, y, vecX, vecY) if (RGFW_mousePosCallbackSrc) RGFW_mousePosCallbackSrc(w, x, y, vecX, vecY); + RGFW_CALLBACK_DEFINE(windowRefresh, WindowRefresh) +#define RGFW_windowRefreshCallback(w) if (RGFW_windowRefreshCallbackSrc) RGFW_windowRefreshCallbackSrc(w); + RGFW_CALLBACK_DEFINE(focus, Focus) +#define RGFW_focusCallback(w, inFocus) if (RGFW_focusCallbackSrc) RGFW_focusCallbackSrc(w, inFocus); + RGFW_CALLBACK_DEFINE(mouseNotify, MouseNotify) -RGFW_CALLBACK_DEFINE(dnd, Dnd) -RGFW_CALLBACK_DEFINE(dndInit, DndInit) +#define RGFW_mouseNotifyCallback(w, x, y, status) if (RGFW_mouseNotifyCallbackSrc) RGFW_mouseNotifyCallbackSrc(w, x, y, status); + +RGFW_CALLBACK_DEFINE(dataDrop, DataDrop) +#define RGFW_dataDropCallback(w, files, count) if (RGFW_dataDropCallbackSrc) RGFW_dataDropCallbackSrc(w, files, count); + +RGFW_CALLBACK_DEFINE(dataDrag, DataDrag) +#define RGFW_dataDragCallback(w, x, y) if (RGFW_dataDragCallbackSrc) RGFW_dataDragCallbackSrc(w, x, y); + RGFW_CALLBACK_DEFINE(key, Key) +#define RGFW_keyCallback(w, key, sym, mod, repeat, press) if (RGFW_keyCallbackSrc) RGFW_keyCallbackSrc(w, key, sym, mod, repeat, press); + RGFW_CALLBACK_DEFINE(mouseButton, MouseButton) -RGFW_CALLBACK_DEFINE(gamepadButton, GamepadButton) -RGFW_CALLBACK_DEFINE(gamepadAxis, GamepadAxis) -RGFW_CALLBACK_DEFINE(gamepad, Gamepad) +#define RGFW_mouseButtonCallback(w, button, press) if (RGFW_mouseButtonCallbackSrc) RGFW_mouseButtonCallbackSrc(w, button, press); + +RGFW_CALLBACK_DEFINE(mouseScroll, MouseScroll) +#define RGFW_mouseScrollCallback(w, x, y) if (RGFW_mouseScrollCallbackSrc) RGFW_mouseScrollCallbackSrc(w, x, y); + RGFW_CALLBACK_DEFINE(scaleUpdated, ScaleUpdated) +#define RGFW_scaleUpdatedCallback(w, scaleX, scaleY) if (RGFW_scaleUpdatedCallbackSrc) RGFW_scaleUpdatedCallbackSrc(w, scaleX, scaleY); + +RGFW_CALLBACK_DEFINE(debug, Debug) +#define RGFW_debugCallback(type, err, msg) if (RGFW_debugCallbackSrc) RGFW_debugCallbackSrc(type, err, msg); #undef RGFW_CALLBACK_DEFINE -void RGFW_window_checkEvents(RGFW_window* win, i32 waitMS) { - RGFW_window_eventWait(win, waitMS); +#ifdef RGFW_DEBUG +#include +#endif - while (RGFW_window_checkEvent(win) != NULL && RGFW_window_shouldClose(win) == 0) { - if (win->event.type == RGFW_quit) return; +void RGFW_sendDebugInfo(RGFW_debugType type, RGFW_errorCode err, const char* msg) { + RGFW_debugCallback(type, err, msg); + + #ifdef RGFW_DEBUG + switch (type) { + case RGFW_typeInfo: RGFW_PRINTF("RGFW INFO (%i %i): %s", type, err, msg); break; + case RGFW_typeError: RGFW_PRINTF("RGFW DEBUG (%i %i): %s", type, err, msg); break; + case RGFW_typeWarning: RGFW_PRINTF("RGFW WARNING (%i %i): %s", type, err, msg); break; + default: break; } - #ifdef RGFW_WASM /* WASM needs to run the sleep function for asyncify */ - RGFW_sleep(0); + RGFW_PRINTF("\n"); #endif } void RGFW_window_checkMode(RGFW_window* win); void RGFW_window_checkMode(RGFW_window* win) { - if (RGFW_window_isMinimized(win)) { - win->_flags |= RGFW_windowMinimize; - RGFW_windowMinimizedCallback(win, win->r); - } else if (RGFW_window_isMaximized(win)) { - win->_flags |= RGFW_windowMaximize; - RGFW_eventQueuePushEx(e.type = RGFW_windowMaximized; e._win = win); - RGFW_windowMaximizedCallback(win, win->r); - } else if (((win->_flags & RGFW_windowMinimize) && !RGFW_window_isMaximized(win)) || - (win->_flags & RGFW_windowMaximize && !RGFW_window_isMaximized(win))) { - win->_flags &= ~(u32)RGFW_windowMinimize; - if (RGFW_window_isMaximized(win) == RGFW_FALSE) win->_flags &= ~(u32)RGFW_windowMaximize; - RGFW_eventQueuePushEx(e.type = RGFW_windowRestored; e._win = win); - RGFW_windowRestoredCallback(win, win->r); + if (RGFW_window_isMinimized(win) && (win->internal.enabledEvents & RGFW_windowMinimizedFlag)) { + win->internal.flags |= RGFW_windowMinimize; + RGFW_eventQueuePushEx(e.type = RGFW_windowMinimized; e.common.win = win); + RGFW_windowMinimizedCallback(win); + } else if (RGFW_window_isMaximized(win) && (win->internal.enabledEvents & RGFW_windowMaximizedFlag)) { + win->internal.flags |= RGFW_windowMaximize; + RGFW_eventQueuePushEx(e.type = RGFW_windowMaximized; e.common.win = win); + RGFW_windowMaximizedCallback(win, win->x, win->y, win->w, win->h); + } else if ((((win->internal.flags & RGFW_windowMinimize) && !RGFW_window_isMaximized(win)) || + (win->internal.flags & RGFW_windowMaximize && !RGFW_window_isMaximized(win))) && (win->internal.enabledEvents & RGFW_windowRestoredFlag)) { + win->internal.flags &= ~(u32)RGFW_windowMinimize; + if (RGFW_window_isMaximized(win) == RGFW_FALSE) win->internal.flags &= ~(u32)RGFW_windowMaximize; + RGFW_eventQueuePushEx(e.type = RGFW_windowRestored; e.common.win = win); + RGFW_windowRestoredCallback(win, win->x, win->y, win->w, win->h); } } @@ -1934,173 +3196,359 @@ void RGFW_window_checkMode(RGFW_window* win) { no more event call back defines */ -#define SET_ATTRIB(a, v) { \ - RGFW_ASSERT(((size_t) index + 1) < sizeof(attribs) / sizeof(attribs[0])); \ - attribs[index++] = a; \ - attribs[index++] = v; \ -} +size_t RGFW_sizeofInfo(void) { return sizeof(RGFW_info); } +size_t RGFW_sizeofNativeImage(void) { return sizeof(RGFW_nativeImage); } +size_t RGFW_sizeofSurface(void) { return sizeof(RGFW_surface); } +size_t RGFW_sizeofWindow(void) { return sizeof(RGFW_window); } +size_t RGFW_sizeofWindowSrc(void) { return sizeof(RGFW_window_src); } -#define RGFW_EVENT_PASSED RGFW_BIT(24) /* if a queued event was passed */ -#define RGFW_EVENT_QUIT RGFW_BIT(25) /* the window close button was pressed */ -#define RGFW_HOLD_MOUSE RGFW_BIT(26) /*!< hold the moues still */ -#define RGFW_MOUSE_LEFT RGFW_BIT(27) /* if mouse left the window */ -#define RGFW_WINDOW_ALLOC RGFW_BIT(28) /* if window was allocated by RGFW */ -#define RGFW_BUFFER_ALLOC RGFW_BIT(29) /* if window.buffer was allocated by RGFW */ -#define RGFW_WINDOW_INIT RGFW_BIT(30) /* if window.buffer was allocated by RGFW */ -#define RGFW_INTERNAL_FLAGS (RGFW_EVENT_QUIT | RGFW_EVENT_PASSED | RGFW_HOLD_MOUSE | RGFW_MOUSE_LEFT | RGFW_WINDOW_ALLOC | RGFW_BUFFER_ALLOC | RGFW_windowFocus) +RGFW_window_src* RGFW_window_getSrc(RGFW_window* win) { return &win->src; } +RGFW_bool RGFW_window_getPosition(RGFW_window* win, i32* x, i32* y) { if (x) *x = win->x; if (y) *y = win->y; return RGFW_TRUE; } +RGFW_bool RGFW_window_getSize(RGFW_window* win, i32* w, i32* h) { if (w) *w = win->w; if (h) *h = win->h; return RGFW_TRUE; } +u32 RGFW_window_getFlags(RGFW_window* win) { return win->internal.flags; } +RGFW_key RGFW_window_getExitKey(RGFW_window* win) { return win->internal.exitKey; } +void RGFW_window_setExitKey(RGFW_window* win, RGFW_key key) { win->internal.exitKey = key; } +void RGFW_window_setEnabledEvents(RGFW_window* win, RGFW_eventFlag events) { win->internal.enabledEvents = events; } +RGFW_eventFlag RGFW_window_getEnabledEvents(RGFW_window* win) { return win->internal.enabledEvents; } +void RGFW_window_setDisabledEvents(RGFW_window* win, RGFW_eventFlag events) { RGFW_window_setEnabledEvents(win, (RGFW_allEventFlags) & ~(u32)events); } +void RGFW_window_setEventState(RGFW_window* win, RGFW_eventFlag event, RGFW_bool state) { RGFW_setBit(&win->internal.enabledEvents, event, state); } +void* RGFW_window_getUserPtr(RGFW_window* win) { return win->userPtr; } +void RGFW_window_setUserPtr(RGFW_window* win, void* ptr) { win->userPtr = ptr; } -RGFW_window* RGFW_createWindow(const char* name, RGFW_rect rect, RGFW_windowFlags flags) { - RGFW_window* win = (RGFW_window*)RGFW_ALLOC(sizeof(RGFW_window)); - RGFW_ASSERT(win != NULL); - win->_flags = RGFW_WINDOW_ALLOC; - return RGFW_createWindowPtr(name, rect, flags, win); -} #if defined(RGFW_USE_XDL) && defined(RGFW_X11) #define XDL_IMPLEMENTATION #include "XDL.h" #endif -#define RGFW_MAX_EVENTS 32 -typedef struct RGFW_globalStruct { - RGFW_window* root; - RGFW_window* current; - i32 windowCount; - i32 eventLen; - i32 eventIndex; - - #ifdef RGFW_X11 - Display* display; - Window helperWindow; - char* clipboard; /* for writing to the clipboard selection */ - size_t clipboard_len; - #endif - #ifdef RGFW_WAYLAND - struct wl_display* wl_display; - #endif - #if defined(RGFW_X11) || defined(RGFW_WINDOWS) || defined(RGFW_WAYLAND) - RGFW_mouse* hiddenMouse; - #endif - RGFW_event events[RGFW_MAX_EVENTS]; - -} RGFW_globalStruct; -#if !defined(RGFW_C89) && !defined(__cplusplus) -RGFW_globalStruct _RGFW = {.root = NULL, .current = NULL, .windowCount = -1, .eventLen = 0, .eventIndex = 0}; -#define _RGFW_init RGFW_TRUE -#else -RGFW_bool _RGFW_init = RGFW_FALSE; -RGFW_globalStruct _RGFW; +#ifndef RGFW_FORCE_INIT +RGFW_info _rgfwGlobal; #endif -void RGFW_eventQueuePush(RGFW_event event) { - if (_RGFW.eventLen >= RGFW_MAX_EVENTS) return; - _RGFW.events[_RGFW.eventLen] = event; - _RGFW.eventLen++; +i32 RGFW_init(void) { return RGFW_init_ptr(&_rgfwGlobal); } +void RGFW_deinit(void) { RGFW_deinit_ptr(&_rgfwGlobal); } + +i32 RGFW_initPlatform(void); +void RGFW_deinitPlatform(void); + +i32 RGFW_init_ptr(RGFW_info* info) { + if (info == _RGFW || info == NULL) return 1; + + RGFW_setInfo(info); + RGFW_MEMSET(_RGFW, 0, sizeof(RGFW_info)); + _RGFW->queueEvents = RGFW_FALSE; + _RGFW->polledEvents = RGFW_FALSE; +#ifdef RGFW_WAYLAND + _RGFW->useWaylandBool = RGFW_TRUE; +#endif + + _RGFW->files = (char**)(void*)_RGFW->filesSrc; + u32 i; + for (i = 0; i < RGFW_MAX_DROPS; i++) + _RGFW->files[i] = (char*)(_RGFW->filesSrc + RGFW_MAX_DROPS + (i * RGFW_MAX_PATH)); + + _RGFW->monitors.freeList.head = &_RGFW->monitors.data[0]; + _RGFW->monitors.freeList.cur = _RGFW->monitors.freeList.head; + + for (i = 1; i < RGFW_MAX_MONITORS; i++) { + RGFW_monitorNode* newNode = &_RGFW->monitors.data[i]; + _RGFW->monitors.freeList.cur->next = newNode; + _RGFW->monitors.freeList.cur = _RGFW->monitors.freeList.cur->next; + } + + RGFW_initKeycodes(); + i32 out = RGFW_initPlatform(); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, "global context initialized"); + + return out; +} + +#ifndef RGFW_EGL +void RGFW_unloadEGL(void) { } +#endif + +void RGFW_deinit_ptr(RGFW_info* info) { + if (info == NULL) return; + + RGFW_setInfo(info); + RGFW_unloadEGL(); + RGFW_deinitPlatform(); + + _RGFW->root = NULL; + _RGFW->windowCount = 0; + RGFW_setInfo(NULL); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, "global context deinitialized"); +} + +RGFW_window* RGFW_createWindow(const char* name, i32 x, i32 y, i32 w, i32 h, RGFW_windowFlags flags) { + RGFW_window* win = (RGFW_window*)RGFW_ALLOC(sizeof(RGFW_window)); + RGFW_ASSERT(win != NULL); + return RGFW_createWindowPtr(name, x, y, w, h, flags, win); +} + +void RGFW_window_close(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_window_closePtr(win); + RGFW_FREE(win); +} + +RGFW_window* RGFW_createWindowPtr(const char* name, i32 x, i32 y, i32 w, i32 h, RGFW_windowFlags flags, RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_MEMSET(win, 0, sizeof(RGFW_window)); + if (_RGFW == NULL) RGFW_init(); + _RGFW->windowCount++; + + /* rect based the requested flags */ + if (_RGFW->root == NULL) { + RGFW_setRootWindow(win); + } + + /* set and init the new window's data */ + win->x = x; + win->y = y; + win->w = w; + win->h = h; + win->internal.flags = flags; + win->internal.enabledEvents = RGFW_allEventFlags; + + RGFW_window* ret = RGFW_createWindowPlatform(name, flags, win); + +#ifndef RGFW_X11 + RGFW_window_setFlagsInternal(win, flags, 0); +#endif + +#ifdef RGFW_OPENGL + win->src.gfxType = 0; + if (flags & RGFW_windowOpenGL) + RGFW_window_createContext_OpenGL(win, RGFW_getGlobalHints_OpenGL()); +#endif + +#ifdef RGFW_EGL + if (flags & RGFW_windowEGL) + RGFW_window_createContext_EGL(win, RGFW_getGlobalHints_OpenGL()); +#endif + + /* X11 creates the window after the OpenGL context is created (because of visual garbage), + * so we have to wait to set the flags + * This is required so that way the user can create their own OpenGL context after RGFW_createWindow is used + * if a window is crated, CreateContext will delete the window and create a new one + * */ +#ifdef RGFW_X11 + RGFW_window_setFlagsInternal(win, flags, 0); +#endif + +#ifdef RGFW_MACOS + /*NOTE: another OpenGL/setFlags related hack, this because OSX the 'view' class must be setup after the NSOpenGL view is made AND after setFlags happens */ + RGFW_osx_initView(win); +#endif + +#ifdef RGFW_WAYLAND + /* recieve all events needed to configure the surface */ + /* also gets the wl_outputs */ + if (RGFW_usingWayland()) { + wl_display_roundtrip(_RGFW->wl_display); + /* NOTE: this is a hack so that way wayland spawns a window, even if nothing is drawn */ + if (!(flags & RGFW_windowOpenGL) && !(flags & RGFW_windowEGL)) { + u8* data = (u8*)RGFW_ALLOC((u32)(win->w * win->h * 3)); + RGFW_MEMSET(data, 0, (u32)(win->w * win->h * 3) * sizeof(u8)); + RGFW_surface* surface = RGFW_createSurface(data, win->w, win->h, RGFW_formatBGR8); + RGFW_window_blitSurface(win, surface); + RGFW_FREE(data); + RGFW_surface_free(surface); + } + } +#endif + + RGFW_window_setMouseDefault(win); + RGFW_window_setName(win, name); + if (!(flags & RGFW_windowHide)) { + RGFW_window_show(win); + } + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, "a new window was created"); + + + return ret; +} + +void RGFW_window_closePtr(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + #ifdef RGFW_EGL + if ((win->src.gfxType & RGFW_gfxEGL) && win->src.ctx.egl) { + RGFW_window_deleteContext_EGL(win, win->src.ctx.egl); + win->src.ctx.egl = NULL; + } + #endif + + #ifdef RGFW_OPENGL + if ((win->src.gfxType & RGFW_gfxNativeOpenGL) && win->src.ctx.native) { + RGFW_window_deleteContext_OpenGL(win, win->src.ctx.native); + win->src.ctx.native = NULL; + } + #endif + + RGFW_window_closePlatform(win); + + RGFW_clipboard_switch(NULL); + _RGFW->windowCount--; + if (_RGFW->windowCount == 0) RGFW_deinit(); + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, "a window was freed"); +} + +void RGFW_setQueueEvents(RGFW_bool queue) { _RGFW->queueEvents = RGFW_BOOL(queue); } + +void RGFW_eventQueueFlush(void) { _RGFW->eventLen = 0; } + +void RGFW_eventQueuePush(const RGFW_event* event) { + if (_RGFW->queueEvents == RGFW_FALSE) return; + RGFW_ASSERT(_RGFW->eventLen >= 0); + + if (_RGFW->eventLen >= RGFW_MAX_EVENTS) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEventQueue, "Event queue limit 'RGFW_MAX_EVENTS' has been reached automatically flushing queue."); + RGFW_eventQueueFlush(); + return; + } + + i32 eventTop = (_RGFW->eventBottom + _RGFW->eventLen) % RGFW_MAX_EVENTS; + _RGFW->eventLen += 1; + _RGFW->events[eventTop] = *event; } RGFW_event* RGFW_eventQueuePop(RGFW_window* win) { - RGFW_event* ev; - if (_RGFW.eventLen == 0) return NULL; + RGFW_ASSERT(_RGFW->eventLen >= 0 && _RGFW->eventLen <= RGFW_MAX_EVENTS); + RGFW_event* ev; - ev = (RGFW_event*)&_RGFW.events[_RGFW.eventIndex]; - - _RGFW.eventLen--; - if (_RGFW.eventLen >= 0 && _RGFW.eventIndex < _RGFW.eventLen) { - _RGFW.eventIndex++; - } else if (_RGFW.eventLen == 0) { - _RGFW.eventIndex = 0; - } - - if (ev->_win != win && ev->_win != NULL) { - RGFW_eventQueuePush(*ev); - return NULL; + if (_RGFW->eventLen == 0) { + return NULL; + } + + ev = &_RGFW->events[_RGFW->eventBottom]; + _RGFW->eventLen -= 1; + _RGFW->eventBottom = (_RGFW->eventBottom + 1) % RGFW_MAX_EVENTS; + + if (ev->common.win != win && ev->common.win != NULL) { + RGFW_eventQueuePush(ev); + return NULL; } - ev->droppedFilesCount = win->event.droppedFilesCount; - ev->droppedFiles = win->event.droppedFiles; return ev; } -RGFW_event* RGFW_window_checkEventCore(RGFW_window* win); -RGFW_event* RGFW_window_checkEventCore(RGFW_window* win) { +void RGFW_resetPrevState(void) { + size_t i; /*!< reset each previous state */ + for (i = 0; i < RGFW_keyLast; i++) _RGFW->keyboard[i].prev = _RGFW->keyboard[i].current; + for (i = 0; i < RGFW_mouseFinal; i++) _RGFW->mouseButtons[i].prev = _RGFW->mouseButtons[i].current; + _RGFW->scrollX = 0.0f; + _RGFW->scrollY = 0.0f; + _RGFW->vectorX = (float)0.0f; + _RGFW->vectorY = (float)0.0f; + RGFW_MEMSET(&_RGFW->windowState, 0, sizeof(_RGFW->windowState)); +} + +RGFW_bool RGFW_isKeyPressed(RGFW_key key) { + return _RGFW != NULL && _RGFW->keyboard[key].current && !_RGFW->keyboard[key].prev; +} + +RGFW_bool RGFW_isKeyDown(RGFW_key key) { + return _RGFW != NULL && _RGFW->keyboard[key].current; +} + +RGFW_bool RGFW_isKeyReleased(RGFW_key key) { + return _RGFW != NULL && !_RGFW->keyboard[key].current && _RGFW->keyboard[key].prev; +} + + +RGFW_bool RGFW_isMousePressed(RGFW_mouseButton button) { + return _RGFW != NULL && _RGFW->mouseButtons[button].current && !_RGFW->mouseButtons[button].prev; +} +RGFW_bool RGFW_isMouseDown(RGFW_mouseButton button) { + return _RGFW != NULL && _RGFW->mouseButtons[button].current; +} +RGFW_bool RGFW_isMouseReleased(RGFW_mouseButton button) { + return _RGFW != NULL && !_RGFW->mouseButtons[button].current && _RGFW->mouseButtons[button].prev; +} + +void RGFW_getMouseScroll(float* x, float* y) { + RGFW_ASSERT(_RGFW != NULL); + if (x) *x = _RGFW->scrollX; + if (y) *y = _RGFW->scrollY; +} + +void RGFW_getMouseVector(float* x, float* y) { + RGFW_ASSERT(_RGFW != NULL); + if (x) *x = _RGFW->vectorX; + if (y) *y = _RGFW->vectorY; +} + +RGFW_bool RGFW_window_didMouseLeave(RGFW_window* win) { return _RGFW->windowState.winLeave == win && _RGFW->windowState.mouseLeave; } +RGFW_bool RGFW_window_didMouseEnter(RGFW_window* win) { return _RGFW->windowState.win == win && _RGFW->windowState.mouseEnter; } +RGFW_bool RGFW_window_isMouseInside(RGFW_window* win) { return win->internal.mouseInside; } + +RGFW_bool RGFW_window_isDataDragging(RGFW_window* win) { return RGFW_window_getDataDrag(win, (i32*)NULL, (i32*)NULL); } +RGFW_bool RGFW_window_didDataDrop(RGFW_window* win) { return RGFW_window_getDataDrop(win, (const char***)NULL, (size_t*)NULL);} + + +RGFW_bool RGFW_window_getDataDrag(RGFW_window* win, i32* x, i32* y) { + if (_RGFW->windowState.win != win || _RGFW->windowState.dataDragging == RGFW_FALSE) return RGFW_FALSE; + if (x) *x = _RGFW->windowState.dropX; + if (y) *y = _RGFW->windowState.dropY; + return RGFW_TRUE; +} +RGFW_bool RGFW_window_getDataDrop(RGFW_window* win, const char*** files, size_t* count) { + if (_RGFW->windowState.win != win || _RGFW->windowState.dataDrop == RGFW_FALSE) return RGFW_FALSE; + if (files) *files = (const char**)_RGFW->files; + if (count) *count = _RGFW->windowState.filesCount; + return RGFW_TRUE; +} + +RGFW_bool RGFW_window_checkEvent(RGFW_window* win, RGFW_event* event) { + if (_RGFW->eventLen == 0 && _RGFW->polledEvents == RGFW_FALSE) { + _RGFW->queueEvents = RGFW_TRUE; + RGFW_pollEvents(); + _RGFW->polledEvents = RGFW_TRUE; + } + + if (RGFW_window_checkQueuedEvent(win, event) == RGFW_FALSE) { + _RGFW->polledEvents = RGFW_FALSE; + return RGFW_FALSE; + } + + return RGFW_TRUE; +} + +RGFW_bool RGFW_window_checkQueuedEvent(RGFW_window* win, RGFW_event* event) { RGFW_event* ev; - RGFW_ASSERT(win != NULL); - if (win->event.type == 0 && _RGFW.eventLen == 0) - RGFW_resetKeyPrev(); - - if (win->event.type == RGFW_quit && win->_flags & RGFW_windowFreeOnClose) { - static RGFW_event event; - event = win->event; - RGFW_window_close(win); - return &event; - } - - if (win->event.type != RGFW_DNDInit) win->event.type = 0; - + RGFW_ASSERT(win != NULL); + _RGFW->queueEvents = RGFW_TRUE; /* check queued events */ ev = RGFW_eventQueuePop(win); if (ev != NULL) { if (ev->type == RGFW_quit) RGFW_window_setShouldClose(win, RGFW_TRUE); - win->event = *ev; + *event = *ev; + return RGFW_TRUE; } - else return NULL; - return &win->event; + return RGFW_FALSE; } +void RGFW_setRootWindow(RGFW_window* win) { _RGFW->root = win; } +RGFW_window* RGFW_getRootWindow(void) { return _RGFW->root; } -RGFWDEF void RGFW_window_basic_init(RGFW_window* win, RGFW_rect rect, RGFW_windowFlags flags); -void RGFW_setRootWindow(RGFW_window* win) { _RGFW.root = win; } -RGFW_window* RGFW_getRootWindow(void) { return _RGFW.root; } - -/* do a basic initialization for RGFW_window, this is to standard it for each OS */ -void RGFW_window_basic_init(RGFW_window* win, RGFW_rect rect, RGFW_windowFlags flags) { - RGFW_UNUSED(flags); - if (_RGFW.windowCount == -1 || _RGFW_init == RGFW_FALSE) RGFW_init(); - _RGFW.windowCount++; - - /* rect based the requested flags */ - if (_RGFW.root == NULL) { - RGFW_setRootWindow(win); - RGFW_setTime(0); - } - - if (!(win->_flags & RGFW_WINDOW_ALLOC)) win->_flags = 0; - - /* set and init the new window's data */ - win->r = rect; - win->exitKey = RGFW_escape; - win->event.droppedFilesCount = 0; - - win->_flags = 0 | (win->_flags & RGFW_WINDOW_ALLOC); - win->_flags |= flags; - win->event.keyMod = 0; - win->_lastMousePoint.x = 0; - win->_lastMousePoint.y = 0; - - win->event.droppedFiles = (char**)RGFW_ALLOC(RGFW_MAX_PATH * RGFW_MAX_DROPS); - RGFW_ASSERT(win->event.droppedFiles != NULL); - - { - u32 i; - for (i = 0; i < RGFW_MAX_DROPS; i++) - win->event.droppedFiles[i] = (char*)(win->event.droppedFiles + RGFW_MAX_DROPS + (i * RGFW_MAX_PATH)); - } -} - -void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags flags) { - RGFW_windowFlags cmpFlags = win->_flags; - if (win->_flags & RGFW_WINDOW_INIT) cmpFlags = 0; +#ifndef RGFW_EGL +RGFW_bool RGFW_loadEGL(void) { return RGFW_FALSE; } +#endif +void RGFW_window_setFlagsInternal(RGFW_window* win, RGFW_windowFlags flags, RGFW_windowFlags cmpFlags) { #ifndef RGFW_NO_MONITOR if (flags & RGFW_windowScaleToMonitor) RGFW_window_scaleToMonitor(win); #endif if (flags & RGFW_windowCenter) RGFW_window_center(win); - if (flags & RGFW_windowCenterCursor) - RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); + if (flags & RGFW_windowCenterCursor) RGFW_window_moveMouse(win, win->x + (win->w / 2), win->y + (win->h / 2)); if (flags & RGFW_windowNoBorder) RGFW_window_setBorder(win, 0); - else RGFW_window_setBorder(win, 1); + else if (cmpFlags & RGFW_windowNoBorder) RGFW_window_setBorder(win, 1); if (flags & RGFW_windowFullscreen) RGFW_window_setFullscreen(win, RGFW_TRUE); else if (cmpFlags & RGFW_windowFullscreen) RGFW_window_setFullscreen(win, 0); if (flags & RGFW_windowMaximize) RGFW_window_maximize(win); @@ -2111,153 +3559,97 @@ void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags flags) { else if (cmpFlags & RGFW_windowHideMouse) RGFW_window_showMouse(win, 1); if (flags & RGFW_windowHide) RGFW_window_hide(win); else if (cmpFlags & RGFW_windowHide) RGFW_window_show(win); - if (flags & RGFW_windowCocoaCHDirToRes) RGFW_moveToMacOSResourceDir(); if (flags & RGFW_windowFloating) RGFW_window_setFloating(win, 1); else if (cmpFlags & RGFW_windowFloating) RGFW_window_setFloating(win, 0); if (flags & RGFW_windowFocus) RGFW_window_focus(win); if (flags & RGFW_windowNoResize) { - RGFW_window_setMaxSize(win, RGFW_AREA(win->r.w, win->r.h)); - RGFW_window_setMinSize(win, RGFW_AREA(win->r.w, win->r.h)); + RGFW_window_setMaxSize(win, win->w, win->h); + RGFW_window_setMinSize(win, win->w, win->h); } else if (cmpFlags & RGFW_windowNoResize) { - RGFW_window_setMaxSize(win, RGFW_AREA(0, 0)); - RGFW_window_setMinSize(win, RGFW_AREA(0, 0)); + RGFW_window_setMaxSize(win, 0, 0); + RGFW_window_setMinSize(win, 0, 0); } - win->_flags = flags | (win->_flags & RGFW_INTERNAL_FLAGS); + win->internal.flags = flags; } -RGFW_bool RGFW_window_opengl_isSoftware(RGFW_window* win) { - return RGFW_BOOL(win->_flags |= RGFW_windowOpenglSoftware); -} + +void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags flags) { RGFW_window_setFlagsInternal(win, flags, win->internal.flags); } RGFW_bool RGFW_window_isInFocus(RGFW_window* win) { #ifdef RGFW_WASM return RGFW_TRUE; #else - return RGFW_BOOL(win->_flags & RGFW_windowFocus); + return RGFW_BOOL(win->internal.inFocus); #endif } -void RGFW_window_initBuffer(RGFW_window* win) { - RGFW_area area = RGFW_getScreenSize(); - if ((win->_flags & RGFW_windowNoResize)) - area = RGFW_AREA(win->r.w, win->r.h); - - RGFW_window_initBufferSize(win, area); -} - -void RGFW_window_initBufferSize(RGFW_window* win, RGFW_area area) { -#if defined(RGFW_BUFFER) || defined(RGFW_OSMESA) - win->_flags |= RGFW_BUFFER_ALLOC; - #ifndef RGFW_WINDOWS - u8* buffer = (u8*)RGFW_ALLOC(area.w * area.h * 4); - RGFW_ASSERT(buffer != NULL); - - RGFW_window_initBufferPtr(win, buffer, area); - #else /* windows's bitmap allocs memory for us */ - RGFW_window_initBufferPtr(win, (u8*)NULL, area); - #endif -#else - RGFW_UNUSED(win); RGFW_UNUSED(area); -#endif -} - -#ifdef RGFW_MACOS -RGFWDEF void RGFW_window_cocoaSetLayer(RGFW_window* win, void* layer); -RGFWDEF void* RGFW_cocoaGetLayer(void); -#endif - -const char* RGFW_className = NULL; -void RGFW_setClassName(const char* name) { RGFW_className = name; } +void RGFW_setClassName(const char* name) { RGFW_init(); _RGFW->className = name; } #ifndef RGFW_X11 void RGFW_setXInstName(const char* name) { RGFW_UNUSED(name); } #endif -RGFW_keyState RGFW_mouseButtons[RGFW_mouseFinal] = { {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - -RGFW_bool RGFW_isMousePressed(RGFW_window* win, RGFW_mouseButton button) { - return RGFW_mouseButtons[button].current && (win == NULL || RGFW_window_isInFocus(win)); -} -RGFW_bool RGFW_wasMousePressed(RGFW_window* win, RGFW_mouseButton button) { - return RGFW_mouseButtons[button].prev && (win != NULL || RGFW_window_isInFocus(win)); -} -RGFW_bool RGFW_isMouseHeld(RGFW_window* win, RGFW_mouseButton button) { - return (RGFW_isMousePressed(win, button) && RGFW_wasMousePressed(win, button)); -} -RGFW_bool RGFW_isMouseReleased(RGFW_window* win, RGFW_mouseButton button) { - return (!RGFW_isMousePressed(win, button) && RGFW_wasMousePressed(win, button)); -} - -RGFW_point RGFW_window_getMousePoint(RGFW_window* win) { +RGFW_bool RGFW_window_getMouse(RGFW_window* win, i32* x, i32* y) { RGFW_ASSERT(win != NULL); - return win->_lastMousePoint; + if (x) *x = win->internal.lastMouseX; + if (y) *y = win->internal.lastMouseY; + return RGFW_TRUE; } -RGFW_bool RGFW_isPressed(RGFW_window* win, RGFW_key key) { - return RGFW_keyboard[key].current && (win == NULL || RGFW_window_isInFocus(win)); -} +RGFW_bool RGFW_window_isKeyPressed(RGFW_window* win, RGFW_key key) { return RGFW_isKeyPressed(key) && RGFW_window_isInFocus(win); } +RGFW_bool RGFW_window_isKeyDown(RGFW_window* win, RGFW_key key) { return RGFW_isKeyDown(key) && RGFW_window_isInFocus(win); } +RGFW_bool RGFW_window_isKeyReleased(RGFW_window* win, RGFW_key key) { return RGFW_isKeyReleased(key) && RGFW_window_isInFocus(win); } -RGFW_bool RGFW_wasPressed(RGFW_window* win, RGFW_key key) { - return RGFW_keyboard[key].prev && (win == NULL || RGFW_window_isInFocus(win)); -} +RGFW_bool RGFW_window_isMousePressed(RGFW_window* win, RGFW_mouseButton button) { return RGFW_isMousePressed(button) && RGFW_window_isInFocus(win); } +RGFW_bool RGFW_window_isMouseDown(RGFW_window* win, RGFW_mouseButton button) { return RGFW_isMouseDown(button) && RGFW_window_isInFocus(win); } +RGFW_bool RGFW_window_isMouseReleased(RGFW_window* win, RGFW_mouseButton button) { return RGFW_isMouseReleased(button) && RGFW_window_isInFocus(win); } -RGFW_bool RGFW_isHeld(RGFW_window* win, RGFW_key key) { - return (RGFW_isPressed(win, key) && RGFW_wasPressed(win, key)); -} -RGFW_bool RGFW_isClicked(RGFW_window* win, RGFW_key key) { - return (RGFW_wasPressed(win, key) && !RGFW_isPressed(win, key)); -} -RGFW_bool RGFW_isReleased(RGFW_window* win, RGFW_key key) { - return (!RGFW_isPressed(win, key) && RGFW_wasPressed(win, key)); -} - -void RGFW_window_makeCurrent(RGFW_window* win) { - _RGFW.current = win; -#if defined(RGFW_OPENGL) || defined(RGFW_EGL) - RGFW_window_makeCurrent_OpenGL(win); +#ifndef RGFW_X11 +void* RGFW_getDisplay_X11(void) { return NULL; } +u64 RGFW_window_getWindow_X11(RGFW_window* win) { RGFW_UNUSED(win); return 0; } #endif -} -RGFW_window* RGFW_getCurrent(void) { - return _RGFW.current; -} - -void RGFW_window_swapBuffers(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - RGFW_window_swapBuffers_software(win); -#if defined(RGFW_OPENGL) || defined(RGFW_EGL) - RGFW_window_swapBuffers_OpenGL(win); +#ifndef RGFW_WAYLAND +struct wl_display* RGFW_getDisplay_Wayland(void) { return NULL; } +struct wl_surface* RGFW_window_getWindow_Wayland(RGFW_window* win) { RGFW_UNUSED(win); return NULL; } #endif -} -RGFWDEF void RGFW_setBit(u32* data, u32 bit, RGFW_bool value); -void RGFW_setBit(u32* data, u32 bit, RGFW_bool value) { - if (value) - *data |= bit; - else if (!value && (*(data) & bit)) - *data ^= bit; +#ifndef RGFW_WINDOWS +void* RGFW_window_getHWND(RGFW_window* win) { RGFW_UNUSED(win); return NULL; } +void* RGFW_window_getHDC(RGFW_window* win) { RGFW_UNUSED(win); return NULL; } +#endif + +#ifndef RGFW_MACOS +void* RGFW_window_getView_OSX(RGFW_window* win) { RGFW_UNUSED(win); return NULL; } +void RGFW_window_setLayer_OSX(RGFW_window* win, void* layer) { RGFW_UNUSED(win); RGFW_UNUSED(layer); } +void* RGFW_getLayer_OSX(void) { return NULL; } +void* RGFW_window_getWindow_OSX(RGFW_window* win) { RGFW_UNUSED(win); return NULL; } +#endif + +void RGFW_setBit(u32* var, u32 mask, RGFW_bool set) { + if (set) *var |= mask; + else *var &= ~mask; } void RGFW_window_center(RGFW_window* win) { RGFW_ASSERT(win != NULL); - RGFW_area screenR = RGFW_getScreenSize(); - RGFW_window_move(win, RGFW_POINT((i32)(screenR.w - (u32)win->r.w) / 2, (screenR.h - (u32)win->r.h) / 2)); + RGFW_monitor mon = RGFW_window_getMonitor(win); + RGFW_window_move(win, (i32)(mon.mode.w - win->w) / 2, (mon.mode.h - win->h) / 2); } RGFW_bool RGFW_monitor_scaleToWindow(RGFW_monitor mon, RGFW_window* win) { RGFW_monitorMode mode; RGFW_ASSERT(win != NULL); - mode.area.w = (u32)win->r.w; - mode.area.h = (u32)win->r.h; + mode.w = win->w; + mode.h = win->h; return RGFW_monitor_requestMode(mon, mode, RGFW_monitorScale); } -void RGFW_splitBPP(u32 bpp, RGFW_monitorMode* mode); void RGFW_splitBPP(u32 bpp, RGFW_monitorMode* mode) { if (bpp == 32) bpp = 24; mode->red = mode->green = mode->blue = (u8)(bpp / 3); @@ -2268,21 +3660,21 @@ void RGFW_splitBPP(u32 bpp, RGFW_monitorMode* mode) { } RGFW_bool RGFW_monitorModeCompare(RGFW_monitorMode mon, RGFW_monitorMode mon2, RGFW_modeRequest request) { - return (((mon.area.w == mon2.area.w && mon.area.h == mon2.area.h) || !(request & RGFW_monitorScale)) && + return (((mon.w == mon2.w && mon.h == mon2.h) || !(request & RGFW_monitorScale)) && ((mon.refreshRate == mon2.refreshRate) || !(request & RGFW_monitorRefresh)) && ((mon.red == mon2.red && mon.green == mon2.green && mon.blue == mon2.blue) || !(request & RGFW_monitorRGB))); } RGFW_bool RGFW_window_shouldClose(RGFW_window* win) { - return (win == NULL || (win->_flags & RGFW_EVENT_QUIT)|| (win->exitKey && RGFW_isPressed(win, win->exitKey))); + return (win == NULL || win->internal.shouldClose || (win->internal.exitKey && RGFW_window_isKeyPressed(win, win->internal.exitKey))); } void RGFW_window_setShouldClose(RGFW_window* win, RGFW_bool shouldClose) { if (shouldClose) { - win->_flags |= RGFW_EVENT_QUIT; + win->internal.shouldClose = RGFW_TRUE; RGFW_windowQuitCallback(win); } else { - win->_flags &= ~(u32)RGFW_EVENT_QUIT; + win->internal.shouldClose = RGFW_FALSE; } } @@ -2292,123 +3684,169 @@ void RGFW_window_scaleToMonitor(RGFW_window* win) { if (monitor.scaleX == 0 && monitor.scaleY == 0) return; - RGFW_window_resize(win, RGFW_AREA((u32)(monitor.scaleX * (float)win->r.w), (u32)(monitor.scaleY * (float)win->r.h))); + RGFW_window_resize(win, (i32)(monitor.scaleX * (float)win->w), (i32)(monitor.scaleY * (float)win->h)); } void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor m) { - RGFW_window_move(win, RGFW_POINT(m.x + win->r.x, m.y + win->r.y)); + RGFW_window_move(win, m.x + win->x, m.y + win->y); } #endif -RGFW_bool RGFW_window_setIcon(RGFW_window* win, u8* icon, RGFW_area a, i32 channels) { - return RGFW_window_setIconEx(win, icon, a, channels, RGFW_iconBoth); +RGFW_surface* RGFW_createSurface(u8* data, i32 w, i32 h, RGFW_format format) { + RGFW_surface* surface = (RGFW_surface*)RGFW_ALLOC(sizeof(RGFW_surface)); + RGFW_MEMSET(surface, 0, sizeof(RGFW_surface)); + RGFW_createSurfacePtr(data, w, h, format, surface); + return surface; } -RGFWDEF void RGFW_captureCursor(RGFW_window* win, RGFW_rect); -RGFWDEF void RGFW_releaseCursor(RGFW_window* win); - - -RGFW_bool RGFW_window_mouseHeld(RGFW_window* win) { return RGFW_BOOL(win->_flags & RGFW_HOLD_MOUSE); } - -void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area) { - if (!area.w && !area.h) - area = RGFW_AREA(win->r.w / 2, win->r.h / 2); - - win->_flags |= RGFW_HOLD_MOUSE; - RGFW_captureCursor(win, win->r); - RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); +void RGFW_surface_free(RGFW_surface* surface) { + RGFW_surface_freePtr(surface); + RGFW_FREE(surface); } -void RGFW_window_mouseUnhold(RGFW_window* win) { - win->_flags &= ~(u32)RGFW_HOLD_MOUSE; +RGFW_nativeImage* RGFW_surface_getNativeImage(RGFW_surface* surface) { + return &surface->native; +} + +RGFW_surface* RGFW_window_createSurface(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format) { + RGFW_surface* surface = (RGFW_surface*)RGFW_ALLOC(sizeof(RGFW_surface)); + RGFW_MEMSET(surface, 0, sizeof(RGFW_surface)); + RGFW_window_createSurfacePtr(win, data, w, h, format, surface); + return surface; +} +#ifndef RGFW_X11 +RGFW_bool RGFW_window_createSurfacePtr(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { + RGFW_UNUSED(win); + return RGFW_createSurfacePtr(data, w, h, format, surface); +} +#endif + +const RGFW_colorLayout RGFW_layouts[RGFW_formatCount] = { + { 0, 1, 2, 3 }, /* RGFW_formatRGB8 */ + { 2, 1, 0, 3 }, /* RGFW_formatBGR8 */ + { 0, 1, 2, 3 }, /* RGFW_formatRGBA8 */ + { 1, 2, 3, 0 }, /* RGFW_formatARGB8 */ + { 2, 1, 0, 3 }, /* RGFW_formatBGRA8 */ + { 3, 2, 1, 0 }, /* RGFW_formatABGR8 */ +}; + + +void RGFW_copyImageData(u8* dest_data, i32 w, i32 h, RGFW_format dest_format, u8* src_data, RGFW_format src_format) { + RGFW_copyImageData64(dest_data, w, h, dest_format, src_data, src_format, RGFW_FALSE); +} + +void RGFW_copyImageData64(u8* dest_data, i32 dest_w, i32 dest_h, RGFW_format dest_format, u8* src_data, RGFW_format src_format, RGFW_bool is64bit) { + RGFW_ASSERT(dest_data && src_data); + + u32 src_channels = (src_format >= RGFW_formatRGBA8) ? 4 : 3; + u32 dest_channels = (dest_format >= RGFW_formatRGBA8) ? 4 : 3; + + u32 pixel_count = (u32)(dest_w * dest_h); + + if (src_format == dest_format) { + RGFW_MEMCPY(dest_data, src_data, pixel_count * dest_channels); + return; + } + + const RGFW_colorLayout* src_layout = &RGFW_layouts[src_format]; + const RGFW_colorLayout* dest_layout = &RGFW_layouts[dest_format]; + + u32 i, i2 = 0; + for (i = 0; i < pixel_count; i++) { + const u8* src_px = &src_data[i * src_channels]; + u8* dst_px = &dest_data[i2 * dest_channels]; + u8 rgba[4] = {0}; + rgba[0] = src_px[src_layout->r]; + rgba[1] = src_px[src_layout->g]; + rgba[2] = src_px[src_layout->b]; + rgba[3] = 255; + if (src_channels == 4) + rgba[3] = src_px[src_layout->a]; + + dst_px[dest_layout->r] = rgba[0]; + dst_px[dest_layout->g] = rgba[1]; + dst_px[dest_layout->b] = rgba[2]; + if (dest_channels == 4) + dst_px[dest_layout->a] = rgba[3]; + + i2 += 1 + is64bit; + } +} + +RGFW_monitorNode* RGFW_monitors_add(RGFW_monitor mon) { + RGFW_monitorNode* node = NULL; + if (_RGFW->monitors.freeList.head == NULL) return node; + + node = _RGFW->monitors.freeList.head; + mon = node->mon; + + _RGFW->monitors.freeList.head = node->next; + if (_RGFW->monitors.freeList.head == NULL) { + _RGFW->monitors.freeList.cur = NULL; + } + + node->next = NULL; + + if (_RGFW->monitors.list.head == NULL) { + _RGFW->monitors.list.head = node; + } else { + _RGFW->monitors.list.cur->next = node; + } + + _RGFW->monitors.list.cur = node; + + node->mon = mon; + _RGFW->monitors.count += 1; + return node; +} + +void RGFW_monitors_remove(RGFW_monitorNode* node, RGFW_monitorNode* prev) { + _RGFW->monitors.count -= 1; + + /* remove node from the list */ + if (prev != node) { + prev->next = node->next; + } else { /* node is the head */ + _RGFW->monitors.list.head = NULL; + } + + node->next = NULL; + + /* move node to the free list */ + if (_RGFW->monitors.freeList.head == NULL) { + _RGFW->monitors.freeList.head = node; + } else { + _RGFW->monitors.freeList.cur->next = node; + } + + _RGFW->monitors.freeList.cur = node; +} + +RGFW_bool RGFW_window_setIcon(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format) { + return RGFW_window_setIconEx(win, data, w, h, format, RGFW_iconBoth); +} + +void RGFW_window_holdMouse(RGFW_window* win) { + win->internal.holdMouse = RGFW_TRUE; + _RGFW->mouseOwner = win; + RGFW_captureCursor(win); + RGFW_window_moveMouse(win, win->x + (win->w / 2), win->y + (win->h / 2)); +} + +RGFW_bool RGFW_window_isHoldingMouse(RGFW_window* win) { return RGFW_BOOL(win->internal.holdMouse); } + +void RGFW_window_unholdMouse(RGFW_window* win) { + win->internal.holdMouse = RGFW_FALSE; + _RGFW->mouseOwner = NULL; RGFW_releaseCursor(win); } -u32 RGFW_checkFPS(double startTime, u32 frameCount, u32 fpsCap) { - double deltaTime = RGFW_getTime() - startTime; - if (deltaTime == 0) return 0; - - double fps = (frameCount / deltaTime); /* the numer of frames over the time it took for them to render */ - if (fpsCap && fps > fpsCap) { - double frameTime = (double)frameCount / (double)fpsCap; /* how long it should take to finish the frames */ - double sleepTime = frameTime - deltaTime; /* subtract how long it should have taken with how long it did take */ - - if (sleepTime > 0) RGFW_sleep((u32)(sleepTime * 1000)); - } - - return (u32) fps; -} - -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) -void RGFW_RGB_to_BGR(RGFW_window* win, u8* data) { - #if !defined(RGFW_BUFFER_BGR) && !defined(RGFW_OSMESA) - u32 x, y; - for (y = 0; y < (u32)win->r.h; y++) { - for (x = 0; x < (u32)win->r.w; x++) { - u32 index = (y * 4 * win->bufferSize.w) + x * 4; - - u8 red = data[index]; - data[index] = win->buffer[index + 2]; - data[index + 2] = red; - } - } - #elif defined(RGFW_OSMESA) - u32 y; - for(y = 0; y < (u32)win->r.h; y++){ - u32 index_from = (y + (win->bufferSize.h - win->r.h)) * 4 * win->bufferSize.w; - u32 index_to = y * 4 * win->bufferSize.w; - memcpy(&data[index_to], &data[index_from], 4 * win->bufferSize.w); - } - #else - RGFW_UNUSED(win); RGFW_UNUSED(data); - #endif -} -#endif - -u32 RGFW_isPressedGamepad(RGFW_window* win, u8 c, RGFW_gamepadCodes button) { - RGFW_UNUSED(win); - return RGFW_gamepadPressed[c][button].current; -} -u32 RGFW_wasPressedGamepad(RGFW_window* win, u8 c, RGFW_gamepadCodes button) { - RGFW_UNUSED(win); - return RGFW_gamepadPressed[c][button].prev; -} -u32 RGFW_isReleasedGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button) { - RGFW_UNUSED(win); - return !RGFW_isPressedGamepad(win, controller, button) && RGFW_wasPressedGamepad(win, controller, button); -} -u32 RGFW_isHeldGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button) { - RGFW_UNUSED(win); - return RGFW_isPressedGamepad(win, controller, button) && RGFW_wasPressedGamepad(win, controller, button); -} - -RGFW_point RGFW_getGamepadAxis(RGFW_window* win, u16 controller, u16 whichAxis) { - RGFW_UNUSED(win); - return RGFW_gamepadAxes[controller][whichAxis]; -} -const char* RGFW_getGamepadName(RGFW_window* win, u16 controller) { - RGFW_UNUSED(win); - return (const char*)RGFW_gamepads_name[controller]; -} - -size_t RGFW_getGamepadCount(RGFW_window* win) { - RGFW_UNUSED(win); - return RGFW_gamepadCount; -} - -RGFW_gamepadType RGFW_getGamepadType(RGFW_window* win, u16 controller) { - RGFW_UNUSED(win); - return RGFW_gamepads_type[controller]; -} - -RGFWDEF void RGFW_updateKeyMod(RGFW_window* win, RGFW_keymod mod, RGFW_bool value); void RGFW_updateKeyMod(RGFW_window* win, RGFW_keymod mod, RGFW_bool value) { - if (value) win->event.keyMod |= mod; - else win->event.keyMod &= ~mod; + if (value) win->internal.mod |= mod; + else win->internal.mod &= ~mod; } -RGFWDEF void RGFW_updateKeyModsPro(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll); -void RGFW_updateKeyModsPro(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll) { +void RGFW_updateKeyModsEx(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll) { RGFW_updateKeyMod(win, RGFW_modCapsLock, capital); RGFW_updateKeyMod(win, RGFW_modNumLock, numlock); RGFW_updateKeyMod(win, RGFW_modControl, control); @@ -2418,60 +3856,63 @@ void RGFW_updateKeyModsPro(RGFW_window* win, RGFW_bool capital, RGFW_bool numloc RGFW_updateKeyMod(win, RGFW_modScrollLock, scroll); } -RGFWDEF void RGFW_updateKeyMods(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool scroll); void RGFW_updateKeyMods(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool scroll) { - RGFW_updateKeyModsPro(win, capital, numlock, - RGFW_isPressed(win, RGFW_controlL) || RGFW_isPressed(win, RGFW_controlR), - RGFW_isPressed(win, RGFW_altL) || RGFW_isPressed(win, RGFW_altR), - RGFW_isPressed(win, RGFW_shiftL) || RGFW_isPressed(win, RGFW_shiftR), - RGFW_isPressed(win, RGFW_superL) || RGFW_isPressed(win, RGFW_superR), + RGFW_updateKeyModsEx(win, capital, numlock, + RGFW_window_isKeyDown(win, RGFW_controlL) || RGFW_window_isKeyDown(win, RGFW_controlR), + RGFW_window_isKeyDown(win, RGFW_altL) || RGFW_window_isKeyDown(win, RGFW_altR), + RGFW_window_isKeyDown(win, RGFW_shiftL) || RGFW_window_isKeyDown(win, RGFW_shiftR), + RGFW_window_isKeyDown(win, RGFW_superL) || RGFW_window_isKeyDown(win, RGFW_superR), scroll); } -RGFWDEF void RGFW_window_showMouseFlags(RGFW_window* win, RGFW_bool show); void RGFW_window_showMouseFlags(RGFW_window* win, RGFW_bool show) { - if (show && (win->_flags & RGFW_windowHideMouse)) - win->_flags ^= RGFW_windowHideMouse; - else if (!show && !(win->_flags & RGFW_windowHideMouse)) - win->_flags |= RGFW_windowHideMouse; + if (show && (win->internal.flags & RGFW_windowHideMouse)) + win->internal.flags ^= RGFW_windowHideMouse; + else if (!show && !(win->internal.flags & RGFW_windowHideMouse)) + win->internal.flags |= RGFW_windowHideMouse; } -RGFW_bool RGFW_window_mouseHidden(RGFW_window* win) { - return (RGFW_bool)RGFW_BOOL(win->_flags & RGFW_windowHideMouse); +RGFW_bool RGFW_window_isMouseHidden(RGFW_window* win) { + return (RGFW_bool)RGFW_BOOL(((RGFW_window*)win)->internal.flags & RGFW_windowHideMouse); } RGFW_bool RGFW_window_borderless(RGFW_window* win) { - return (RGFW_bool)RGFW_BOOL(win->_flags & RGFW_windowNoBorder); + return (RGFW_bool)RGFW_BOOL(win->internal.flags & RGFW_windowNoBorder); } -RGFW_bool RGFW_window_isFullscreen(RGFW_window* win){ return RGFW_BOOL(win->_flags & RGFW_windowFullscreen); } -RGFW_bool RGFW_window_allowsDND(RGFW_window* win) { return RGFW_BOOL(win->_flags & RGFW_windowAllowDND); } +RGFW_bool RGFW_window_isFullscreen(RGFW_window* win){ return RGFW_BOOL(win->internal.flags & RGFW_windowFullscreen); } +RGFW_bool RGFW_window_allowsDND(RGFW_window* win) { return RGFW_BOOL(win->internal.flags & RGFW_windowAllowDND); } void RGFW_window_focusLost(RGFW_window* win) { /* standard routines for when a window looses focus */ - _RGFW.root->_flags &= ~(u32)RGFW_windowFocus; - if ((win->_flags & RGFW_windowFullscreen)) + win->internal.inFocus = RGFW_FALSE; + if ((win->internal.flags & RGFW_windowFullscreen)) RGFW_window_minimize(win); - for (size_t key = 0; key < RGFW_keyLast; key++) { - if (RGFW_isPressed(NULL, (u8)key) == RGFW_FALSE) continue; - RGFW_keyboard[key].current = RGFW_FALSE; - u8 keyChar = RGFW_rgfwToKeyChar((u32)key); - RGFW_keyCallback(win, (u8)key, keyChar, win->event.keyMod, RGFW_FALSE); - RGFW_eventQueuePushEx(e.type = RGFW_keyReleased; - e.key = (u8)key; - e.keyChar = keyChar; - e.repeat = RGFW_FALSE; - e.keyMod = win->event.keyMod; - e._win = win); + size_t key; + for (key = 0; key < RGFW_keyLast; key++) { + if (RGFW_isKeyDown((u8)key) == RGFW_FALSE) continue; + + _RGFW->keyboard[key].current = RGFW_FALSE; + u8 sym = RGFW_rgfwToKeyChar((u32)key); + + if ((win->internal.enabledEvents & RGFW_BIT(RGFW_keyReleased))) { + RGFW_keyCallback(win, (u8)key, sym, win->internal.mod, RGFW_FALSE, RGFW_FALSE); + RGFW_eventQueuePushEx(e.type = RGFW_keyReleased; + e.key.value = (u8)key; + e.key.sym = sym; + e.key.repeat = RGFW_FALSE; + e.key.mod = win->internal.mod; + e.common.win = win); + } } - + RGFW_resetKey(); } #ifndef RGFW_WINDOWS void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow) { - RGFW_setBit(&win->_flags, RGFW_windowAllowDND, allow); + RGFW_setBit(&win->internal.flags, RGFW_windowAllowDND, allow); } #endif @@ -2486,8 +3927,8 @@ struct timespec; #if defined(RGFW_WAYLAND) || defined(RGFW_X11) || defined(RGFW_WINDOWS) void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show) { RGFW_window_showMouseFlags(win, show); - if (show == 0) - RGFW_window_setMouse(win, _RGFW.hiddenMouse); + if (show == RGFW_FALSE) + RGFW_window_setMouse(win, _RGFW->hiddenMouse); else RGFW_window_setMouseDefault(win); } @@ -2507,50 +3948,109 @@ void RGFW_moveToMacOSResourceDir(void) { } OpenGL defines start here (Normal, EGL, OSMesa) */ -#if defined(RGFW_OPENGL) || defined(RGFW_EGL) +#if defined(RGFW_OPENGL) +/* EGL, OpenGL */ +#define RGFW_DEFAULT_GL_HINTS { \ + /* Stencil */ 0, \ + /* Samples */ 0, \ + /* Stereo */ RGFW_FALSE, \ + /* AuxBuffers */ 0, \ + /* DoubleBuffer */ RGFW_TRUE, \ + /* Red */ 8, \ + /* Green */ 8, \ + /* Blue */ 8, \ + /* Alpha */ 8, \ + /* Depth */ 24, \ + /* AccumRed */ 0, \ + /* AccumGreen */ 0, \ + /* AccumBlue */ 0, \ + /* AccumAlpha */ 0, \ + /* SRGB */ RGFW_FALSE, \ + /* Robustness */ RGFW_FALSE, \ + /* Debug */ RGFW_FALSE, \ + /* NoError */ RGFW_FALSE, \ + /* ReleaseBehavior */ RGFW_glReleaseNone, \ + /* Profile */ RGFW_glCore, \ + /* Major */ 1, \ + /* Minor */ 0, \ + /* Share */ NULL, \ + /* Share_EGL */ NULL, \ + /* renderer */ RGFW_glAccelerated \ +} -#ifdef RGFW_WINDOWS - #define WIN32_LEAN_AND_MEAN - #define OEMRESOURCE - #include -#endif +RGFW_glHints RGFW_globalHints_OpenGL_SRC = RGFW_DEFAULT_GL_HINTS; +RGFW_glHints* RGFW_globalHints_OpenGL = &RGFW_globalHints_OpenGL_SRC; -#if !defined(__APPLE__) && !defined(RGFW_NO_GL_HEADER) - #include -#elif defined(__APPLE__) - #ifndef GL_SILENCE_DEPRECATION - #define GL_SILENCE_DEPRECATION - #endif - #include - #include -#endif - -/* EGL, normal OpenGL only */ -#ifndef RGFW_EGL -i32 RGFW_GL_HINTS[RGFW_glFinalHint] = {8, +void RGFW_resetGlobalHints_OpenGL(void) { +#if !defined(__cplusplus) || defined(RGFW_MACOS) + RGFW_globalHints_OpenGL_SRC = (RGFW_glHints)RGFW_DEFAULT_GL_HINTS; #else -i32 RGFW_GL_HINTS[RGFW_glFinalHint] = {0, + RGFW_globalHints_OpenGL_SRC = RGFW_DEFAULT_GL_HINTS; #endif - 0, 0, 0, 1, 8, 8, 8, 8, 24, 0, 0, 0, 0, 0, 0, 0, 0, RGFW_glReleaseNone, RGFW_glCore, 0, 0}; +} +void RGFW_setGlobalHints_OpenGL(RGFW_glHints* hints) { RGFW_globalHints_OpenGL = hints; } +RGFW_glHints* RGFW_getGlobalHints_OpenGL(void) { RGFW_init(); return RGFW_globalHints_OpenGL; } -void RGFW_setGLHint(RGFW_glHints hint, i32 value) { - if (hint < RGFW_glFinalHint && hint) RGFW_GL_HINTS[hint] = value; + +void* RGFW_glContext_getSourceContext(RGFW_glContext* ctx) { + RGFW_UNUSED(ctx); + +#ifdef RGFW_WAYLAND + if (RGFW_usingWayland()) return (void*)ctx->egl.ctx; +#endif + +#if defined(RGFW_X11) + return (void*)ctx->ctx; +#else + return NULL; +#endif +} + +RGFW_glContext* RGFW_window_createContext_OpenGL(RGFW_window* win, RGFW_glHints* hints) { + #ifdef RGFW_WAYLAND + if (RGFW_usingWayland()) { + return (RGFW_glContext*)RGFW_window_createContext_EGL(win, hints); + } + #endif + RGFW_glContext* ctx = (RGFW_glContext*)RGFW_ALLOC(sizeof(RGFW_glContext)); + if (RGFW_window_createContextPtr_OpenGL(win, ctx, hints) == RGFW_FALSE) { + RGFW_FREE(ctx); + win->src.ctx.native = NULL; + return NULL; + } + win->src.gfxType |= RGFW_gfxOwnedByRGFW; + return ctx; +} + +RGFW_glContext* RGFW_window_getContext_OpenGL(RGFW_window* win) { + if (win->src.gfxType & RGFW_windowEGL) return NULL; + return win->src.ctx.native; +} + +void RGFW_window_deleteContext_OpenGL(RGFW_window* win, RGFW_glContext* ctx) { + RGFW_window_deleteContextPtr_OpenGL(win, ctx); + if (win->src.gfxType & RGFW_gfxOwnedByRGFW) RGFW_FREE(ctx); } RGFW_bool RGFW_extensionSupportedStr(const char* extensions, const char* ext, size_t len) { const char *start = extensions; - const char *where; + const char *where; const char* terminator; - if (extensions == NULL || ext == NULL) + if (extensions == NULL || ext == NULL) { return RGFW_FALSE; + } - where = strstr(extensions, ext); + while (ext[len - 1] == '\0' && len > 3) { + len--; + } + + where = RGFW_STRSTR(extensions, ext); while (where) { - terminator = where + len; + terminator = where + len; if ((where == start || *(where - 1) == ' ') && (*terminator == ' ' || *terminator == '\0')) { - return RGFW_TRUE; + return RGFW_TRUE; } where = RGFW_STRSTR(terminator, ext); } @@ -2558,457 +4058,523 @@ RGFW_bool RGFW_extensionSupportedStr(const char* extensions, const char* ext, si return RGFW_FALSE; } -RGFW_bool RGFW_extensionSupported(const char* extension, size_t len) { +RGFWDEF RGFW_bool RGFW_extensionSupported_base(const char* extension, size_t len); +RGFW_bool RGFW_extensionSupported_base(const char* extension, size_t len) { #ifdef GL_NUM_EXTENSIONS - if (RGFW_GL_HINTS[RGFW_glMajor] >= 3) { + if (RGFW_globalHints_OpenGL->major >= 3) { i32 i; + GLint count = 0; - RGFW_proc RGFW_glGetStringi = RGFW_getProcAddress("glGetStringi"); - RGFW_proc RGFW_glGetIntegerv = RGFW_getProcAddress("RGFW_glGetIntegerv"); - if (RGFW_glGetIntegerv) + RGFW_proc RGFW_glGetStringi = RGFW_getProcAddress_OpenGL("glGetStringi"); + RGFW_proc RGFW_glGetIntegerv = RGFW_getProcAddress_OpenGL("glGetIntegerv"); + if (RGFW_glGetIntegerv) ((void(*)(GLenum, GLint*))RGFW_glGetIntegerv)(GL_NUM_EXTENSIONS, &count); for (i = 0; RGFW_glGetStringi && i < count; i++) { const char* en = ((const char* (*)(u32, u32))RGFW_glGetStringi)(GL_EXTENSIONS, (u32)i); - if (en && RGFW_STRNCMP(en, extension, len) == 0) - return RGFW_TRUE; + if (en && RGFW_STRNCMP(en, extension, len) == 0) { + return RGFW_TRUE; + } } - } else + } else #endif { - RGFW_proc RGFW_glGetString = RGFW_getProcAddress("glGetString"); - + RGFW_proc RGFW_glGetString = RGFW_getProcAddress_OpenGL("glGetString"); + #define RGFW_GL_EXTENSIONS 0x1F03 if (RGFW_glGetString) { - const char* extensions = ((const char*(*)(u32))RGFW_glGetString)(GL_EXTENSIONS); - if ((extensions != NULL) && RGFW_extensionSupportedStr(extensions, extension, len)) - return RGFW_TRUE; + const char* extensions = ((const char*(*)(u32))RGFW_glGetString)(RGFW_GL_EXTENSIONS); + + if ((extensions != NULL) && RGFW_extensionSupportedStr(extensions, extension, len)) { + return RGFW_TRUE; + } } } - - return RGFW_extensionSupportedPlatform(extension, len); + return RGFW_FALSE; } -/* OPENGL normal only (no EGL / OSMesa) */ -#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) && !defined(RGFW_CUSTOM_BACKEND) && !defined(RGFW_WASM) - -#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, 0) - #define RGFW_GL_DRAW_TYPE RGFW_OS_BASED_VALUE(GLX_RENDER_TYPE, 0x2013, 0, 0) - #define RGFW_GL_FULL_FORMAT RGFW_OS_BASED_VALUE(GLX_TRUE_COLOR, 0x2027, 0, 0) - #define RGFW_GL_RED_SIZE RGFW_OS_BASED_VALUE(GLX_RED_SIZE, 0x2015, 0, 0) - #define RGFW_GL_GREEN_SIZE RGFW_OS_BASED_VALUE(GLX_GREEN_SIZE, 0x2017, 0, 0) - #define RGFW_GL_BLUE_SIZE RGFW_OS_BASED_VALUE(GLX_BLUE_SIZE, 0x2019, 0, 0) - #define RGFW_GL_USE_RGBA RGFW_OS_BASED_VALUE(GLX_RGBA_BIT, 0x202B, 0, 0) - #define RGFW_GL_ACCUM_RED_SIZE RGFW_OS_BASED_VALUE(14, 0x201E, 0, 0) - #define RGFW_GL_ACCUM_GREEN_SIZE RGFW_OS_BASED_VALUE(15, 0x201F, 0, 0) - #define RGFW_GL_ACCUM_BLUE_SIZE RGFW_OS_BASED_VALUE(16, 0x2020, 0, 0) - #define RGFW_GL_ACCUM_ALPHA_SIZE RGFW_OS_BASED_VALUE(17, 0x2021, 0, 0) - #define RGFW_GL_SRGB RGFW_OS_BASED_VALUE(0x20b2, 0x3089, 0, 0) - #define RGFW_GL_NOERROR RGFW_OS_BASED_VALUE(0x31b3, 0x31b3, 0, 0) - #define RGFW_GL_FLAGS RGFW_OS_BASED_VALUE(GLX_CONTEXT_FLAGS_ARB, 0x2094, 0, 0) - #define RGFW_GL_RELEASE_BEHAVIOR RGFW_OS_BASED_VALUE(GLX_CONTEXT_RELEASE_BEHAVIOR_ARB, 0x2097 , 0, 0) - #define RGFW_GL_CONTEXT_RELEASE RGFW_OS_BASED_VALUE(GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB, 0x2098, 0, 0) - #define RGFW_GL_CONTEXT_NONE RGFW_OS_BASED_VALUE(GLX_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB, 0x0000, 0, 0) - #define RGFW_GL_FLAGS RGFW_OS_BASED_VALUE(GLX_CONTEXT_FLAGS_ARB, 0x2094, 0, 0) - #define RGFW_GL_DEBUG_BIT RGFW_OS_BASED_VALUE(GLX_CONTEXT_FLAGS_ARB, 0x2094, 0, 0) - #define RGFW_GL_ROBUST_BIT RGFW_OS_BASED_VALUE(GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB, 0x00000004, 0, 0) -#endif - -#ifdef RGFW_WINDOWS - #define WGL_SUPPORT_OPENGL_ARB 0x2010 - #define WGL_COLOR_BITS_ARB 0x2014 - #define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 - #define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 - #define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 - #define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 - #define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 - #define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 - #define WGL_SAMPLE_BUFFERS_ARB 0x2041 - #define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9 - #define WGL_PIXEL_TYPE_ARB 0x2013 - #define WGL_TYPE_RGBA_ARB 0x202B - - #define WGL_TRANSPARENT_ARB 0x200A -#endif - -/* The window'ing api needs to know how to render the data we (or opengl) give it - MacOS and Windows do this using a structure called a "pixel format" - X11 calls it a "Visual" - This function returns the attributes for the format we want */ -i32* RGFW_initFormatAttribs(void); -i32* RGFW_initFormatAttribs(void) { - static i32 attribs[] = { - #if defined(RGFW_X11) || defined(RGFW_WINDOWS) - RGFW_GL_RENDER_TYPE, - RGFW_GL_FULL_FORMAT, - RGFW_GL_DRAW, 1, - RGFW_GL_DRAW_TYPE , RGFW_GL_USE_RGBA, - #endif - - #ifdef RGFW_X11 - GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT, - #endif - - #ifdef RGFW_MACOS - 72, - 8, 24, - #endif - - #ifdef RGFW_WINDOWS - WGL_SUPPORT_OPENGL_ARB, 1, - WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, - WGL_COLOR_BITS_ARB, 32, - #endif - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - - size_t index = (sizeof(attribs) / sizeof(attribs[0])) - 27; - - #define RGFW_GL_ADD_ATTRIB(attrib, attVal) \ - if (attVal) { \ - attribs[index] = attrib;\ - attribs[index + 1] = attVal;\ - index += 2;\ - } - - #if defined(RGFW_MACOS) && defined(RGFW_COCOA_GRAPHICS_SWITCHING) - RGFW_GL_ADD_ATTRIB(96, kCGLPFASupportsAutomaticGraphicsSwitching); - #endif - - RGFW_GL_ADD_ATTRIB(RGFW_GL_DOUBLEBUFFER, 1); - - RGFW_GL_ADD_ATTRIB(RGFW_GL_ALPHA_SIZE, RGFW_GL_HINTS[RGFW_glAlpha]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_DEPTH_SIZE, RGFW_GL_HINTS[RGFW_glDepth]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_STENCIL_SIZE, RGFW_GL_HINTS[RGFW_glStencil]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_STEREO, RGFW_GL_HINTS[RGFW_glStereo]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_AUX_BUFFERS, RGFW_GL_HINTS[RGFW_glAuxBuffers]); - - #if defined(RGFW_X11) || defined(RGFW_WINDOWS) - RGFW_GL_ADD_ATTRIB(RGFW_GL_RED_SIZE, RGFW_GL_HINTS[RGFW_glRed]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_GREEN_SIZE, RGFW_GL_HINTS[RGFW_glBlue]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_BLUE_SIZE, RGFW_GL_HINTS[RGFW_glGreen]); - #endif - - #if defined(RGFW_X11) || defined(RGFW_WINDOWS) - RGFW_GL_ADD_ATTRIB(RGFW_GL_ACCUM_RED_SIZE, RGFW_GL_HINTS[RGFW_glAccumRed]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_ACCUM_GREEN_SIZE, RGFW_GL_HINTS[RGFW_glAccumBlue]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_ACCUM_BLUE_SIZE, RGFW_GL_HINTS[RGFW_glAccumGreen]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_ACCUM_ALPHA_SIZE, RGFW_GL_HINTS[RGFW_glAccumAlpha]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_SRGB, RGFW_GL_HINTS[RGFW_glSRGB]); - RGFW_GL_ADD_ATTRIB(RGFW_GL_NOERROR, RGFW_GL_HINTS[RGFW_glNoError]); - - if (RGFW_GL_HINTS[RGFW_glReleaseBehavior] == RGFW_releaseFlush) { - RGFW_GL_ADD_ATTRIB(RGFW_GL_RELEASE_BEHAVIOR, RGFW_GL_CONTEXT_RELEASE); - } else if (RGFW_GL_HINTS[RGFW_glReleaseBehavior] == RGFW_glReleaseNone) { - RGFW_GL_ADD_ATTRIB(RGFW_GL_RELEASE_BEHAVIOR, RGFW_GL_CONTEXT_NONE); - } - - i32 flags = 0; - if (RGFW_GL_HINTS[RGFW_glDebug]) flags |= RGFW_GL_DEBUG_BIT; - if (RGFW_GL_HINTS[RGFW_glRobustness]) flags |= RGFW_GL_ROBUST_BIT; - RGFW_GL_ADD_ATTRIB(RGFW_GL_FLAGS, flags); - #else - i32 accumSize = (i32)(RGFW_GL_HINTS[RGFW_glAccumRed] + RGFW_GL_HINTS[RGFW_glAccumGreen] + RGFW_GL_HINTS[RGFW_glAccumBlue] + RGFW_GL_HINTS[RGFW_glAccumAlpha]) / 4; - RGFW_GL_ADD_ATTRIB(14, accumSize); - #endif - - #ifndef RGFW_X11 - RGFW_GL_ADD_ATTRIB(RGFW_GL_SAMPLES, RGFW_GL_HINTS[RGFW_glSamples]); - #endif - - #ifdef RGFW_MACOS - if (_RGFW.root->_flags & RGFW_windowOpenglSoftware) { - RGFW_GL_ADD_ATTRIB(70, kCGLRendererGenericFloatID); - } else { - attribs[index] = RGFW_GL_RENDER_TYPE; - index += 1; - } - #endif - - #ifdef RGFW_MACOS - /* macOS has the surface attribs and the opengl attribs connected for some reason - maybe this is to give macOS more control to limit openGL/the opengl version? */ - - attribs[index] = 99; - attribs[index + 1] = 0x1000; - - - if (RGFW_GL_HINTS[RGFW_glMajor] >= 4 || RGFW_GL_HINTS[RGFW_glMajor] >= 3) { - attribs[index + 1] = (i32) ((RGFW_GL_HINTS[RGFW_glMajor] >= 4) ? 0x4100 : 0x3200); - } - #endif - - RGFW_GL_ADD_ATTRIB(0, 0); - - return attribs; +RGFW_bool RGFW_extensionSupported_OpenGL(const char* extension, size_t len) { + if (RGFW_extensionSupported_base(extension, len)) return RGFW_TRUE; + return RGFW_extensionSupportedPlatform_OpenGL(extension, len); } -/* EGL only (no OSMesa nor normal OPENGL) */ -#elif defined(RGFW_EGL) +void RGFW_window_makeCurrentWindow_OpenGL(RGFW_window* win) { + if (win) { + _RGFW->current = win; + } + RGFW_window_makeCurrentContext_OpenGL(win); +} + +RGFW_window* RGFW_getCurrentWindow_OpenGL(void) { return _RGFW->current; } +void RGFW_attribStack_init(RGFW_attribStack* stack, i32* attribs, size_t max) { stack->attribs = attribs; stack->count = 0; stack->max = max; } +void RGFW_attribStack_pushAttrib(RGFW_attribStack* stack, i32 attrib) { + RGFW_ASSERT(stack->count < stack->max); + stack->attribs[stack->count] = attrib; + stack->count += 1; +} +void RGFW_attribStack_pushAttribs(RGFW_attribStack* stack, i32 attrib1, i32 attrib2) { + RGFW_attribStack_pushAttrib(stack, attrib1); + RGFW_attribStack_pushAttrib(stack, attrib2); +} + +/* EGL */ +#ifdef RGFW_EGL #include -#if defined(RGFW_LINK_EGL) - typedef EGLBoolean(EGLAPIENTRY* PFN_eglInitialize)(EGLDisplay, EGLint*, EGLint*); - - PFNEGLINITIALIZEPROC eglInitializeSource; - PFNEGLGETCONFIGSPROC eglGetConfigsSource; - PFNEGLCHOOSECONFIgamepadROC eglChooseConfigSource; - PFNEGLCREATEWINDOWSURFACEPROC eglCreateWindowSurfaceSource; - PFNEGLCREATECONTEXTPROC eglCreateContextSource; - PFNEGLMAKECURRENTPROC eglMakeCurrentSource; - PFNEGLGETDISPLAYPROC eglGetDisplaySource; - PFNEGLSWAPBUFFERSPROC eglSwapBuffersSource; - PFNEGLSWAPINTERVALPROC eglSwapIntervalSource; - PFNEGLBINDAPIPROC eglBindAPISource; - PFNEGLDESTROYCONTEXTPROC eglDestroyContextSource; - PFNEGLTERMINATEPROC eglTerminateSource; - PFNEGLDESTROYSURFACEPROC eglDestroySurfaceSource; - - #define eglInitialize eglInitializeSource - #define eglGetConfigs eglGetConfigsSource - #define eglChooseConfig eglChooseConfigSource - #define eglCreateWindowSurface eglCreateWindowSurfaceSource - #define eglCreateContext eglCreateContextSource - #define eglMakeCurrent eglMakeCurrentSource - #define eglGetDisplay eglGetDisplaySource - #define eglSwapBuffers eglSwapBuffersSource - #define eglSwapInterval eglSwapIntervalSource - #define eglBindAPI eglBindAPISource - #define eglDestroyContext eglDestroyContextSource - #define eglTerminate eglTerminateSource - #define eglDestroySurface eglDestroySurfaceSource; -#endif - +PFNEGLINITIALIZEPROC RGFW_eglInitialize; +PFNEGLGETCONFIGSPROC RGFW_eglGetConfigs; +PFNEGLCHOOSECONFIGPROC RGFW_eglChooseConfig; +PFNEGLCREATEWINDOWSURFACEPROC RGFW_eglCreateWindowSurface; +PFNEGLCREATECONTEXTPROC RGFW_eglCreateContext; +PFNEGLMAKECURRENTPROC RGFW_eglMakeCurrent; +PFNEGLGETDISPLAYPROC RGFW_eglGetDisplay; +PFNEGLSWAPBUFFERSPROC RGFW_eglSwapBuffers; +PFNEGLSWAPINTERVALPROC RGFW_eglSwapInterval; +PFNEGLBINDAPIPROC RGFW_eglBindAPI; +PFNEGLDESTROYCONTEXTPROC RGFW_eglDestroyContext; +PFNEGLTERMINATEPROC RGFW_eglTerminate; +PFNEGLDESTROYSURFACEPROC RGFW_eglDestroySurface; +PFNEGLGETCURRENTCONTEXTPROC RGFW_eglGetCurrentContext; +PFNEGLGETPROCADDRESSPROC RGFW_eglGetProcAddress = NULL; +PFNEGLQUERYSTRINGPROC RGFW_eglQueryString; +PFNEGLGETCONFIGATTRIBPROC RGFW_eglGetConfigAttrib; #define EGL_SURFACE_MAJOR_VERSION_KHR 0x3098 #define EGL_SURFACE_MINOR_VERSION_KHR 0x30fb -#ifndef RGFW_GL_ADD_ATTRIB -#define RGFW_GL_ADD_ATTRIB(attrib, attVal) \ - if (attVal) { \ - attribs[index] = attrib;\ - attribs[index + 1] = attVal;\ - index += 2;\ - } +#ifdef RGFW_WINDOWS + #include +#elif defined(RGFW_MACOS) || defined(RGFW_UNIX) + #include #endif - -void RGFW_window_initOpenGL(RGFW_window* win) { -#if defined(RGFW_LINK_EGL) - eglInitializeSource = (PFNEGLINITIALIZEPROC) eglGetProcAddress("eglInitialize"); - eglGetConfigsSource = (PFNEGLGETCONFIGSPROC) eglGetProcAddress("eglGetConfigs"); - eglChooseConfigSource = (PFNEGLCHOOSECONFIgamepadROC) eglGetProcAddress("eglChooseConfig"); - eglCreateWindowSurfaceSource = (PFNEGLCREATEWINDOWSURFACEPROC) eglGetProcAddress("eglCreateWindowSurface"); - eglCreateContextSource = (PFNEGLCREATECONTEXTPROC) eglGetProcAddress("eglCreateContext"); - eglMakeCurrentSource = (PFNEGLMAKECURRENTPROC) eglGetProcAddress("eglMakeCurrent"); - eglGetDisplaySource = (PFNEGLGETDISPLAYPROC) eglGetProcAddress("eglGetDisplay"); - eglSwapBuffersSource = (PFNEGLSWAPBUFFERSPROC) eglGetProcAddress("eglSwapBuffers"); - eglSwapIntervalSource = (PFNEGLSWAPINTERVALPROC) eglGetProcAddress("eglSwapInterval"); - eglBindAPISource = (PFNEGLBINDAPIPROC) eglGetProcAddress("eglBindAPI"); - eglDestroyContextSource = (PFNEGLDESTROYCONTEXTPROC) eglGetProcAddress("eglDestroyContext"); - eglTerminateSource = (PFNEGLTERMINATEPROC) eglGetProcAddress("eglTerminate"); - eglDestroySurfaceSource = (PFNEGLDESTROYSURFACEPROC) eglGetProcAddress("eglDestroySurface"); - - RGFW_ASSERT(eglInitializeSource != NULL && - eglGetConfigsSource != NULL && - eglChooseConfigSource != NULL && - eglCreateWindowSurfaceSource != NULL && - eglCreateContextSource != NULL && - eglMakeCurrentSource != NULL && - eglGetDisplaySource != NULL && - eglSwapBuffersSource != NULL && - eglSwapIntervalsSource != NULL && - eglBindAPISource != NULL && - eglDestroyContextSource != NULL && - eglTerminateSource != NULL && - eglDestroySurfaceSource != NULL); -#endif /* RGFW_LINK_EGL */ - #ifdef RGFW_WAYLAND - if (RGFW_useWaylandBool) - win->src.eglWindow = wl_egl_window_create(win->src.surface, win->r.w, win->r.h); +#include #endif +void* RGFW_eglLibHandle = NULL; + +void* RGFW_getDisplay_EGL(void) { return _RGFW->EGL_display; } +void* RGFW_eglContext_getSourceContext(RGFW_eglContext* ctx) { return ctx->ctx; } +void* RGFW_eglContext_getSurface(RGFW_eglContext* ctx) { return ctx->surface; } +struct wl_egl_window* RGFW_eglContext_wlEGLWindow(RGFW_eglContext* ctx) { return ctx->eglWindow; } + +RGFW_bool RGFW_loadEGL(void) { + RGFW_init(); + if (RGFW_eglGetProcAddress != NULL) { + return RGFW_TRUE; + } + +#ifndef RGFW_WASM #ifdef RGFW_WINDOWS - win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.hdc); - #elif defined(RGFW_MACOS) - win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType)0); - #elif defined(RGFW_WAYLAND) - if (RGFW_useWaylandBool) - win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.wl_display); - else - #endif - #ifdef RGFW_X11 - win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.display); - #else - {} - #endif - #if !defined(RGFW_WAYLAND) && !defined(RGFW_WINDOWS) && !defined(RGFW_X11) - win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.display); + const char* libNames[] = { "libEGL.dll", "EGL.dll" }; + #elif defined(RGFW_MACOS) || defined(RGFW_UNIX) + /* Linux and macOS */ + const char* libNames[] = { + "libEGL.so.1", /* most common */ + "libEGL.so", /* fallback */ + "/System/Library/Frameworks/OpenGL.framework/OpenGL" /* fallback for older macOS EGL-like systems */ + }; #endif - EGLint major, minor; + for (size_t i = 0; i < sizeof(libNames) / sizeof(libNames[0]); ++i) { + #ifdef RGFW_WINDOWS + RGFW_eglLibHandle = (void*)LoadLibraryA(libNames[i]); + if (RGFW_eglLibHandle) { + RGFW_eglGetProcAddress = (PFNEGLGETPROCADDRESSPROC)(RGFW_proc)GetProcAddress((HMODULE)RGFW_eglLibHandle, "eglGetProcAddress"); + break; + } + #elif defined(RGFW_MACOS) || defined(RGFW_UNIX) + RGFW_eglLibHandle = dlopen(libNames[i], RTLD_LAZY | RTLD_GLOBAL); + if (RGFW_eglLibHandle) { + void* lib = dlsym(RGFW_eglLibHandle, "eglGetProcAddress"); + if (lib != NULL) RGFW_MEMCPY(&RGFW_eglGetProcAddress, &lib, sizeof(PFNEGLGETPROCADDRESSPROC)); + break; + } + #endif + } - eglInitialize(win->src.EGL_display, &major, &minor); + if (!RGFW_eglLibHandle || !RGFW_eglGetProcAddress) { + return RGFW_FALSE; + } + + RGFW_eglInitialize = (PFNEGLINITIALIZEPROC) RGFW_eglGetProcAddress("eglInitialize"); + RGFW_eglGetConfigs = (PFNEGLGETCONFIGSPROC) RGFW_eglGetProcAddress("eglGetConfigs"); + RGFW_eglChooseConfig = (PFNEGLCHOOSECONFIGPROC) RGFW_eglGetProcAddress("eglChooseConfig"); + RGFW_eglCreateWindowSurface = (PFNEGLCREATEWINDOWSURFACEPROC) RGFW_eglGetProcAddress("eglCreateWindowSurface"); + RGFW_eglCreateContext = (PFNEGLCREATECONTEXTPROC) RGFW_eglGetProcAddress("eglCreateContext"); + RGFW_eglMakeCurrent = (PFNEGLMAKECURRENTPROC) RGFW_eglGetProcAddress("eglMakeCurrent"); + RGFW_eglGetDisplay = (PFNEGLGETDISPLAYPROC) RGFW_eglGetProcAddress("eglGetDisplay"); + RGFW_eglSwapBuffers = (PFNEGLSWAPBUFFERSPROC) RGFW_eglGetProcAddress("eglSwapBuffers"); + RGFW_eglSwapInterval = (PFNEGLSWAPINTERVALPROC) RGFW_eglGetProcAddress("eglSwapInterval"); + RGFW_eglBindAPI = (PFNEGLBINDAPIPROC) RGFW_eglGetProcAddress("eglBindAPI"); + RGFW_eglDestroyContext = (PFNEGLDESTROYCONTEXTPROC) RGFW_eglGetProcAddress("eglDestroyContext"); + RGFW_eglTerminate = (PFNEGLTERMINATEPROC) RGFW_eglGetProcAddress("eglTerminate"); + RGFW_eglDestroySurface = (PFNEGLDESTROYSURFACEPROC) RGFW_eglGetProcAddress("eglDestroySurface"); + RGFW_eglQueryString = (PFNEGLQUERYSTRINGPROC) RGFW_eglGetProcAddress("eglQueryString"); + RGFW_eglGetCurrentContext = (PFNEGLGETCURRENTCONTEXTPROC) RGFW_eglGetProcAddress("eglGetCurrentContext"); + RGFW_eglGetConfigAttrib = (PFNEGLGETCONFIGATTRIBPROC) RGFW_eglGetProcAddress("eglGetConfigAttrib"); + +#else + RGFW_eglGetProcAddress = eglGetProcAddress; + RGFW_eglInitialize = (PFNEGLINITIALIZEPROC) eglInitialize; + RGFW_eglGetConfigs = (PFNEGLGETCONFIGSPROC) eglGetConfigs; + RGFW_eglChooseConfig = (PFNEGLCHOOSECONFIGPROC) eglChooseConfig; + RGFW_eglCreateWindowSurface = (PFNEGLCREATEWINDOWSURFACEPROC) eglCreateWindowSurface; + RGFW_eglCreateContext = (PFNEGLCREATECONTEXTPROC) eglCreateContext; + RGFW_eglMakeCurrent = (PFNEGLMAKECURRENTPROC) eglMakeCurrent; + RGFW_eglGetDisplay = (PFNEGLGETDISPLAYPROC) eglGetDisplay; + RGFW_eglSwapBuffers = (PFNEGLSWAPBUFFERSPROC) eglSwapBuffers; + RGFW_eglSwapInterval = (PFNEGLSWAPINTERVALPROC) eglSwapInterval; + RGFW_eglBindAPI = (PFNEGLBINDAPIPROC) eglBindAPI; + RGFW_eglDestroyContext = (PFNEGLDESTROYCONTEXTPROC) eglDestroyContext; + RGFW_eglTerminate = (PFNEGLTERMINATEPROC) eglTerminate; + RGFW_eglDestroySurface = (PFNEGLDESTROYSURFACEPROC) eglDestroySurface; + RGFW_eglQueryString = (PFNEGLQUERYSTRINGPROC) eglQueryString; + RGFW_eglGetCurrentContext = (PFNEGLGETCURRENTCONTEXTPROC) eglGetCurrentContext; + RGFW_eglGetConfigAttrib = (PFNEGLGETCONFIGATTRIBPROC)eglGetConfigAttrib; +#endif + + RGFW_bool out = RGFW_BOOL(RGFW_eglInitialize!= NULL && + RGFW_eglGetConfigs!= NULL && + RGFW_eglChooseConfig!= NULL && + RGFW_eglCreateWindowSurface!= NULL && + RGFW_eglCreateContext!= NULL && + RGFW_eglMakeCurrent!= NULL && + RGFW_eglGetDisplay!= NULL && + RGFW_eglSwapBuffers!= NULL && + RGFW_eglSwapInterval != NULL && + RGFW_eglBindAPI!= NULL && + RGFW_eglDestroyContext!= NULL && + RGFW_eglTerminate!= NULL && + RGFW_eglDestroySurface!= NULL && + RGFW_eglQueryString != NULL && + RGFW_eglGetCurrentContext != NULL && + RGFW_eglGetConfigAttrib != NULL); + + if (out) { + #ifdef RGFW_WINDOWS + HDC dc = GetDC(NULL); + _RGFW->EGL_display = RGFW_eglGetDisplay((EGLNativeDisplayType) dc); + ReleaseDC(NULL, dc); + #elif defined(RGFW_WAYLAND) + if (_RGFW->useWaylandBool) + _RGFW->EGL_display = RGFW_eglGetDisplay((EGLNativeDisplayType) _RGFW->wl_display); + else + #endif + #ifdef RGFW_X11 + _RGFW->EGL_display = RGFW_eglGetDisplay((EGLNativeDisplayType) _RGFW->display); + #else + {} + #endif + #if !defined(RGFW_WAYLAND) && !defined(RGFW_WINDOWS) && !defined(RGFW_X11) + _RGFW->EGL_display = RGFW_eglGetDisplay(EGL_DEFAULT_DISPLAY); + #endif + } + + RGFW_eglInitialize(_RGFW->EGL_display, NULL, NULL); + return out; +} + + +void RGFW_unloadEGL(void) { + if (!RGFW_eglLibHandle) return; + RGFW_eglTerminate(_RGFW->EGL_display); + #ifdef RGFW_WINDOWS + FreeLibrary((HMODULE)RGFW_eglLibHandle); + #elif defined(RGFW_MACOS) || defined(RGFW_UNIX) + dlclose(RGFW_eglLibHandle); + #endif + + RGFW_eglLibHandle = NULL; + RGFW_eglGetProcAddress = NULL; +} + +RGFW_bool RGFW_window_createContextPtr_EGL(RGFW_window* win, RGFW_eglContext* ctx, RGFW_glHints* hints) { + if (RGFW_loadEGL() == RGFW_FALSE) return RGFW_FALSE; + win->src.ctx.egl = ctx; + win->src.gfxType = RGFW_gfxEGL; + +#ifdef RGFW_WAYLAND + if (_RGFW->useWaylandBool) + win->src.ctx.egl->eglWindow = wl_egl_window_create(win->src.surface, win->w, win->h); +#endif #ifndef EGL_OPENGL_ES1_BIT #define EGL_OPENGL_ES1_BIT 0x1 #endif - EGLint egl_config[24] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RENDERABLE_TYPE, - #ifdef RGFW_OPENGL_ES1 - EGL_OPENGL_ES1_BIT, - #elif defined(RGFW_OPENGL_ES3) - EGL_OPENGL_ES3_BIT, - #elif defined(RGFW_OPENGL_ES2) - EGL_OPENGL_ES2_BIT, - #else - EGL_OPENGL_BIT, - #endif - EGL_NONE, EGL_NONE - }; + EGLint egl_config[24]; { - size_t index = 7; - EGLint* attribs = egl_config; + RGFW_attribStack stack; + RGFW_attribStack_init(&stack, egl_config, 24); - RGFW_GL_ADD_ATTRIB(EGL_RED_SIZE, RGFW_GL_HINTS[RGFW_glRed]); - RGFW_GL_ADD_ATTRIB(EGL_GREEN_SIZE, RGFW_GL_HINTS[RGFW_glBlue]); - RGFW_GL_ADD_ATTRIB(EGL_BLUE_SIZE, RGFW_GL_HINTS[RGFW_glGreen]); - RGFW_GL_ADD_ATTRIB(EGL_ALPHA_SIZE, RGFW_GL_HINTS[RGFW_glAlpha]); - RGFW_GL_ADD_ATTRIB(EGL_DEPTH_SIZE, RGFW_GL_HINTS[RGFW_glDepth]); + RGFW_attribStack_pushAttribs(&stack, EGL_SURFACE_TYPE, EGL_WINDOW_BIT); + RGFW_attribStack_pushAttrib(&stack, EGL_RENDERABLE_TYPE); - if (RGFW_GL_HINTS[RGFW_glSRGB]) - RGFW_GL_ADD_ATTRIB(0x3089, RGFW_GL_HINTS[RGFW_glSRGB]); + if (hints->profile == RGFW_glES) { + switch (hints->major) { + case 1: RGFW_attribStack_pushAttrib(&stack, EGL_OPENGL_ES1_BIT); break; + case 2: RGFW_attribStack_pushAttrib(&stack, EGL_OPENGL_ES2_BIT); break; + case 3: RGFW_attribStack_pushAttrib(&stack, EGL_OPENGL_ES3_BIT); break; + default: break; + } + } else { + RGFW_attribStack_pushAttrib(&stack, EGL_OPENGL_BIT); + } - RGFW_GL_ADD_ATTRIB(EGL_NONE, EGL_NONE); + RGFW_attribStack_pushAttribs(&stack, EGL_RED_SIZE, hints->red); + RGFW_attribStack_pushAttribs(&stack, EGL_GREEN_SIZE, hints->green); + RGFW_attribStack_pushAttribs(&stack, EGL_BLUE_SIZE, hints->blue); + RGFW_attribStack_pushAttribs(&stack, EGL_ALPHA_SIZE, hints->alpha); + RGFW_attribStack_pushAttribs(&stack, EGL_DEPTH_SIZE, hints->depth); + + RGFW_attribStack_pushAttribs(&stack, EGL_STENCIL_SIZE, hints->stencil); + if (hints->samples) { + RGFW_attribStack_pushAttribs(&stack, EGL_SAMPLE_BUFFERS, 1); + RGFW_attribStack_pushAttribs(&stack, EGL_SAMPLES, hints->samples); + } + + RGFW_attribStack_pushAttribs(&stack, EGL_NONE, EGL_NONE); } - EGLConfig config; - EGLint numConfigs; - eglChooseConfig(win->src.EGL_display, egl_config, &config, 1, &numConfigs); + EGLint numConfigs, best_config = -1, best_samples = 0; + RGFW_eglChooseConfig(_RGFW->EGL_display, egl_config, NULL, 0, &numConfigs); + EGLConfig* configs = (EGLConfig*)RGFW_ALLOC(sizeof(EGLConfig) * (u32)numConfigs); + + RGFW_eglChooseConfig(_RGFW->EGL_display, egl_config, configs, numConfigs, &numConfigs); + +#ifdef RGFW_X11 + RGFW_bool transparent = (win->internal.flags & RGFW_windowTransparent); + EGLint best_depth = 0; +#endif + + for (EGLint i = 0; i < numConfigs; i++) { + EGLint visual_id = 0; + EGLint samples = 0; + + RGFW_eglGetConfigAttrib(_RGFW->EGL_display, configs[i], EGL_NATIVE_VISUAL_ID, &visual_id); + RGFW_eglGetConfigAttrib(_RGFW->EGL_display, configs[i], EGL_SAMPLES, &samples); + + if (best_config == -1) best_config = i; + +#ifdef RGFW_X11 + if (_RGFW->useWaylandBool == RGFW_FALSE) { + XVisualInfo vinfo_template; + vinfo_template.visualid = (VisualID)visual_id; + + int num_visuals = 0; + XVisualInfo* vi = XGetVisualInfo(_RGFW->display, VisualIDMask, &vinfo_template, &num_visuals); + if (!vi) continue; + if ((!transparent || vi->depth == 32) && best_depth == 0) { + best_config = i; + best_depth = vi->depth; + } + + if ((!(transparent) || vi->depth == 32) && (samples <= hints->samples && samples > best_samples)) { + best_depth = vi->depth; + best_config = i; + best_samples = samples; + XFree(vi); + continue; + } + } +#endif + + if (samples <= hints->samples && samples > best_samples) { + best_config = i; + best_samples = samples; + } + } + + EGLConfig config = configs[best_config]; + RGFW_FREE(configs); +#ifdef RGFW_X11 + if (_RGFW->useWaylandBool == RGFW_FALSE) { + /* This is required so that way the user can create their own OpenGL context after RGFW_createWindow is used */ + XVisualInfo* result; + XVisualInfo desired; + EGLint visualID = 0, count = 0; + + RGFW_eglGetConfigAttrib(_RGFW->EGL_display, config, EGL_NATIVE_VISUAL_ID, &visualID); + if (visualID) { + desired.visualid = (VisualID)visualID; + result = XGetVisualInfo(_RGFW->display, VisualIDMask, &desired, &count); + } else RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEGLContext, "Failed to fetch a valid EGL VisualID"); + + if (result == NULL || count == 0) { + if (win->src.window == 0) { + /* try to create a EGL context anyway (this will work if you're not using a NVidia driver) */ + win->internal.flags &= ~(u32)RGFW_windowEGL; + RGFW_createWindowPlatform("", win->internal.flags, win); + } + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEGLContext, "Failed to find a valid visual for the EGL config"); + } else { + if (win->src.window) RGFW_window_closePlatform(win); + RGFW_XCreateWindow(*result, "", win->internal.flags, win); + XFree(result); + } + } +#endif + + EGLint surf_attribs[9]; + + { + RGFW_attribStack stack; + RGFW_attribStack_init(&stack, surf_attribs, 9); + + const char present_opaque_str[] = "EGL_EXT_present_opaque"; + RGFW_bool opaque_extension_Found = RGFW_extensionSupportedPlatform_EGL(present_opaque_str, sizeof(present_opaque_str)); + + #ifndef EGL_PRESENT_OPAQUE_EXT + #define EGL_PRESENT_OPAQUE_EXT 0x31df + #endif + + #ifndef EGL_GL_COLORSPACE_KHR + #define EGL_GL_COLORSPACE_KHR 0x309D + #ifndef EGL_GL_COLORSPACE_SRGB_KHR + #define EGL_GL_COLORSPACE_SRGB_KHR 0x3089 + #endif + #endif + + const char gl_colorspace_str[] = "EGL_KHR_gl_colorspace"; + RGFW_bool gl_colorspace_Found = RGFW_extensionSupportedPlatform_EGL(gl_colorspace_str, sizeof(gl_colorspace_str)); + + if (hints->sRGB && gl_colorspace_Found) { + RGFW_attribStack_pushAttribs(&stack, EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR); + } + + if (!(win->internal.flags & RGFW_windowTransparent) && opaque_extension_Found) + RGFW_attribStack_pushAttribs(&stack, EGL_PRESENT_OPAQUE_EXT, EGL_TRUE); + + if (hints->doubleBuffer == 0) { + RGFW_attribStack_pushAttribs(&stack, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER); + } + + RGFW_attribStack_pushAttribs(&stack, EGL_NONE, EGL_NONE); + } #if defined(RGFW_MACOS) - void* layer = RGFW_cocoaGetLayer(); + void* layer = RGFW_getLayer_OSX(); - RGFW_window_cocoaSetLayer(win, layer); + RGFW_window_setLayer_OSX(win, layer); - win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) layer, NULL); + win->src.ctx.egl->surface = RGFW_eglCreateWindowSurface(_RGFW->EGL_display, config, (EGLNativeWindowType) layer, surf_attribs); #elif defined(RGFW_WINDOWS) - win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.window, NULL); + win->src.ctx.egl->surface = RGFW_eglCreateWindowSurface(_RGFW->EGL_display, config, (EGLNativeWindowType) win->src.window, surf_attribs); #elif defined(RGFW_WAYLAND) - if (RGFW_useWaylandBool) - win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.eglWindow, NULL); + if (_RGFW->useWaylandBool) + win->src.ctx.egl->surface = RGFW_eglCreateWindowSurface(_RGFW->EGL_display, config, (EGLNativeWindowType) win->src.ctx.egl->eglWindow, surf_attribs); else #endif #ifdef RGFW_X11 - win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.window, NULL); + win->src.ctx.egl->surface = RGFW_eglCreateWindowSurface(_RGFW->EGL_display, config, (EGLNativeWindowType) win->src.window, surf_attribs); #else {} #endif - #if !defined(RGFW_X11) && !defined(RGFW_WAYLAND) && !defined(RGFW_MACOS) - win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.window, NULL); + #ifdef RGFW_WASM + win->src.ctx.egl->surface = eglCreateWindowSurface(_RGFW->EGL_display, config, 0, 0); #endif - EGLint attribs[12]; - size_t index = 0; - -#ifdef RGFW_OPENGL_ES1 - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_CLIENT_VERSION, 1); -#elif defined(RGFW_OPENGL_ES2) - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_CLIENT_VERSION, 2); -#elif defined(RGFW_OPENGL_ES3) - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_CLIENT_VERSION, 3); -#endif - - RGFW_GL_ADD_ATTRIB(EGL_STENCIL_SIZE, RGFW_GL_HINTS[RGFW_glStencil]); - RGFW_GL_ADD_ATTRIB(EGL_SAMPLES, RGFW_GL_HINTS[RGFW_glSamples]); - - if (RGFW_GL_HINTS[RGFW_glDoubleBuffer] == 0) - RGFW_GL_ADD_ATTRIB(EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER); - - if (RGFW_GL_HINTS[RGFW_glMajor]) { - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_MAJOR_VERSION, RGFW_GL_HINTS[RGFW_glMajor]); - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_MINOR_VERSION, RGFW_GL_HINTS[RGFW_glMinor]); - - if (RGFW_GL_HINTS[RGFW_glProfile] == RGFW_glCore) { - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT); - } - else { - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT); - } + if (win->src.ctx.egl->surface == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEGLContext, "Failed to create an EGL surface."); + return RGFW_FALSE; } - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_ROBUST_ACCESS, RGFW_GL_HINTS[RGFW_glRobustness]); - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_DEBUG, RGFW_GL_HINTS[RGFW_glDebug]); - if (RGFW_GL_HINTS[RGFW_glReleaseBehavior] == RGFW_releaseFlush) { - RGFW_GL_ADD_ATTRIB(0x2097, 0x2098); - } else { - RGFW_GL_ADD_ATTRIB(0x2096, 0x0000); + EGLint attribs[20]; + { + RGFW_attribStack stack; + RGFW_attribStack_init(&stack, attribs, 20); + + if (hints->major || hints->minor) { + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_MAJOR_VERSION, hints->major); + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_MINOR_VERSION, hints->minor); + } + + if (hints->profile == RGFW_glCore) { + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT); + } else if (hints->profile == RGFW_glCompatibility) { + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT); + } + + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_OPENGL_ROBUST_ACCESS, hints->robustness); + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_OPENGL_DEBUG, hints->debug); + + #ifndef EGL_CONTEXT_RELEASE_BEHAVIOR_KHR + #define EGL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x2097 + #endif + + #ifndef EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR + #define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098 + #endif + + if (hints->releaseBehavior == RGFW_glReleaseFlush) { + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_RELEASE_BEHAVIOR_KHR, EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR); + } else { + RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_RELEASE_BEHAVIOR_KHR, 0x0000); + } + + RGFW_attribStack_pushAttribs(&stack, EGL_NONE, EGL_NONE); } - RGFW_GL_ADD_ATTRIB(EGL_NONE, EGL_NONE); + if (hints->profile == RGFW_glES) + RGFW_eglBindAPI(EGL_OPENGL_ES_API); + else + RGFW_eglBindAPI(EGL_OPENGL_API); - #if defined(RGFW_OPENGL_ES1) || defined(RGFW_OPENGL_ES2) || defined(RGFW_OPENGL_ES3) - eglBindAPI(EGL_OPENGL_ES_API); - #else - eglBindAPI(EGL_OPENGL_API); + win->src.ctx.egl->ctx = RGFW_eglCreateContext(_RGFW->EGL_display, config, hints->shareEGL, attribs); + + if (win->src.ctx.egl->ctx == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEGLContext, "Failed to create an EGL context."); + return RGFW_FALSE; + } + + RGFW_eglMakeCurrent(_RGFW->EGL_display, win->src.ctx.egl->surface, win->src.ctx.egl->surface, win->src.ctx.egl->ctx); + RGFW_eglSwapBuffers(_RGFW->EGL_display, win->src.ctx.egl->surface); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "EGL context initalized."); + return RGFW_TRUE; +} + +RGFW_eglContext* RGFW_window_getContext_EGL(RGFW_window* win) { + if (win->src.gfxType == RGFW_windowOpenGL) return NULL; + return win->src.ctx.egl; +} + +void RGFW_window_deleteContextPtr_EGL(RGFW_window* win, RGFW_eglContext* ctx) { + if (_RGFW->EGL_display == NULL) return; + + RGFW_eglDestroySurface(_RGFW->EGL_display, ctx->surface); + RGFW_eglDestroyContext(_RGFW->EGL_display, ctx->ctx); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "EGL context freed"); + #ifdef RGFW_WAYLAND + if (_RGFW->useWaylandBool == RGFW_FALSE) return; + wl_egl_window_destroy(win->src.ctx.egl->eglWindow); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "EGL window context freed"); #endif - - win->src.EGL_context = eglCreateContext(win->src.EGL_display, config, EGL_NO_CONTEXT, attribs); - - if (win->src.EGL_context == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEGLContext, RGFW_DEBUG_CTX(win, 0), "failed to create an EGL opengl context"); - return; - } - - eglMakeCurrent(win->src.EGL_display, win->src.EGL_surface, win->src.EGL_surface, win->src.EGL_context); - eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "EGL opengl context initalized"); + win->src.ctx.egl = NULL; } -void RGFW_window_freeOpenGL(RGFW_window* win) { - if (win->src.EGL_display == NULL) return; - - eglDestroySurface(win->src.EGL_display, win->src.EGL_surface); - eglDestroyContext(win->src.EGL_display, win->src.EGL_context); - eglTerminate(win->src.EGL_display); - win->src.EGL_display = NULL; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "EGL opengl context freed"); -} - -void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { - if (win == NULL) - eglMakeCurrent(_RGFW.root->src.EGL_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +void RGFW_window_makeCurrentContext_EGL(RGFW_window* win) { if (win) RGFW_ASSERT(win->src.ctx.egl); + if (win == NULL) + RGFW_eglMakeCurrent(_RGFW->EGL_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); else { - eglMakeCurrent(win->src.EGL_display, win->src.EGL_surface, win->src.EGL_surface, win->src.EGL_context); + RGFW_eglMakeCurrent(_RGFW->EGL_display, win->src.ctx.egl->surface, win->src.ctx.egl->surface, win->src.ctx.egl->ctx); } } -void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); } +void RGFW_window_swapBuffers_EGL(RGFW_window* win) { + if (RGFW_eglSwapBuffers) + RGFW_eglSwapBuffers(_RGFW->EGL_display, win->src.ctx.egl->surface); + else RGFW_window_swapBuffers_OpenGL(win); +} -void* RGFW_getCurrent_OpenGL(void) { return eglGetCurrentContext(); } +void* RGFW_getCurrentContext_EGL(void) { + return RGFW_eglGetCurrentContext(); +} -#ifdef RGFW_APPLE -void* RGFWnsglFramework = NULL; -#elif defined(RGFW_WINDOWS) -HMODULE RGFW_wgl_dll = NULL; -#endif - -RGFW_proc RGFW_getProcAddress(const char* procname) { +RGFW_proc RGFW_getProcAddress_EGL(const char* procname) { #if defined(RGFW_WINDOWS) RGFW_proc proc = (RGFW_proc) GetProcAddress(RGFW_wgl_dll, procname); @@ -3016,19 +4582,46 @@ RGFW_proc RGFW_getProcAddress(const char* procname) { return proc; #endif - return (RGFW_proc) eglGetProcAddress(procname); + return (RGFW_proc) RGFW_eglGetProcAddress(procname); } -RGFW_bool RGFW_extensionSupportedPlatform(const char* extension, size_t len) { - const char* extensions = eglQueryString(_RGFW.root->src.EGL_display, EGL_EXTENSIONS); - return extensions != NULL && RGFW_extensionSupportedStr(extensions, extension, len); +RGFW_bool RGFW_extensionSupportedPlatform_EGL(const char* extension, size_t len) { + if (RGFW_loadEGL() == RGFW_FALSE) return RGFW_FALSE; + const char* extensions = RGFW_eglQueryString(_RGFW->EGL_display, EGL_EXTENSIONS); + return extensions != NULL && RGFW_extensionSupportedStr(extensions, extension, len); } -void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { +void RGFW_window_swapInterval_EGL(RGFW_window* win, i32 swapInterval) { RGFW_ASSERT(win != NULL); + RGFW_eglSwapInterval(_RGFW->EGL_display, swapInterval); +} - eglSwapInterval(win->src.EGL_display, swapInterval); +RGFW_bool RGFW_extensionSupported_EGL(const char* extension, size_t len) { + if (RGFW_extensionSupported_base(extension, len)) return RGFW_TRUE; + return RGFW_extensionSupportedPlatform_EGL(extension, len); +} +void RGFW_window_makeCurrentWindow_EGL(RGFW_window* win) { + _RGFW->current = win; + RGFW_window_makeCurrentContext_EGL(win); +} + +RGFW_window* RGFW_getCurrentWindow_EGL(void) { return _RGFW->current; } + +RGFW_eglContext* RGFW_window_createContext_EGL(RGFW_window* win, RGFW_glHints* hints) { + RGFW_eglContext* ctx = (RGFW_eglContext*)RGFW_ALLOC(sizeof(RGFW_eglContext)); + if (RGFW_window_createContextPtr_EGL(win, ctx, hints) == RGFW_FALSE) { + RGFW_FREE(ctx); + win->src.ctx.egl = NULL; + return NULL; + } + win->src.gfxType |= RGFW_gfxOwnedByRGFW; + return ctx; +} + +void RGFW_window_deleteContext_EGL(RGFW_window* win, RGFW_eglContext* ctx) { + RGFW_window_deleteContextPtr_EGL(win, ctx); + if (win->src.gfxType & RGFW_gfxOwnedByRGFW) RGFW_FREE(ctx); } #endif /* RGFW_EGL */ @@ -3046,7 +4639,7 @@ void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { #include #endif -const char** RGFW_getVKRequiredInstanceExtensions(size_t* count) { +const char** RGFW_getRequiredInstanceExtensions_Vulkan(size_t* count) { static const char* arr[2] = {VK_KHR_SURFACE_EXTENSION_NAME}; arr[1] = RGFW_VK_SURFACE; if (count != NULL) *count = 2; @@ -3054,20 +4647,20 @@ const char** RGFW_getVKRequiredInstanceExtensions(size_t* count) { return (const char**)arr; } -VkResult RGFW_window_createVKSurface(RGFW_window* win, VkInstance instance, VkSurfaceKHR* surface) { +VkResult RGFW_window_createSurface_Vulkan(RGFW_window* win, VkInstance instance, VkSurfaceKHR* surface) { RGFW_ASSERT(win != NULL); RGFW_ASSERT(instance); RGFW_ASSERT(surface != NULL); *surface = VK_NULL_HANDLE; #ifdef RGFW_X11 - RGFW_GOTO_WAYLAND(0); - VkXlibSurfaceCreateInfoKHR x11 = { VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, 0, 0, (Display*) win->src.display, (Window) win->src.window }; + + VkXlibSurfaceCreateInfoKHR x11 = { VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, 0, 0, (Display*) _RGFW->display, (Window) win->src.window }; return vkCreateXlibSurfaceKHR(instance, &x11, NULL, surface); #endif #if defined(RGFW_WAYLAND) -RGFW_WAYLAND_LABEL - VkWaylandSurfaceCreateInfoKHR wayland = { VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, 0, 0, (struct wl_display*) win->src.wl_display, (struct wl_surface*) win->src.surface }; + + VkWaylandSurfaceCreateInfoKHR wayland = { VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, 0, 0, (struct wl_display*) _RGFW->wl_display, (struct wl_surface*) win->src.surface }; return vkCreateWaylandSurfaceKHR(instance, &wayland, NULL, surface); #elif defined(RGFW_WINDOWS) VkWin32SurfaceCreateInfoKHR win32 = { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, 0, 0, GetModuleHandle(NULL), (HWND)win->src.window }; @@ -3075,28 +4668,24 @@ RGFW_WAYLAND_LABEL return vkCreateWin32SurfaceKHR(instance, &win32, NULL, surface); #elif defined(RGFW_MACOS) && !defined(RGFW_MACOS_X11) void* contentView = ((void* (*)(id, SEL))objc_msgSend)((id)win->src.window, sel_getUid("contentView")); - VkMacOSSurfaceCreateFlagsMVK macos = { VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK, 0, 0, win->src.display, (void*)contentView }; - + VkMacOSSurfaceCreateSurfaceMVK macos = { VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK, 0, 0, 0, (void*)contentView }; return vkCreateMacOSSurfaceMVK(instance, &macos, NULL, surface); #endif } -RGFW_bool RGFW_getVKPresentationSupport(VkInstance instance, VkPhysicalDevice physicalDevice, u32 queueFamilyIndex) { +RGFW_bool RGFW_getPresentationSupport_Vulkan(VkInstance instance, VkPhysicalDevice physicalDevice, u32 queueFamilyIndex) { RGFW_ASSERT(instance); - if (_RGFW.windowCount == -1 || _RGFW_init == RGFW_FALSE) RGFW_init(); + if (_RGFW == NULL) RGFW_init(); #ifdef RGFW_X11 - RGFW_GOTO_WAYLAND(0); - Visual* visual = DefaultVisual(_RGFW.display, DefaultScreen(_RGFW.display)); - if (_RGFW.root) - visual = _RGFW.root->src.visual.visual; - RGFW_bool out = vkGetPhysicalDeviceXlibPresentationSupportKHR(physicalDevice, queueFamilyIndex, _RGFW.display, XVisualIDFromVisual(visual)); + Visual* visual = DefaultVisual(_RGFW->display, DefaultScreen(_RGFW->display)); + RGFW_bool out = vkGetPhysicalDeviceXlibPresentationSupportKHR(physicalDevice, queueFamilyIndex, _RGFW->display, XVisualIDFromVisual(visual)); return out; #endif #if defined(RGFW_WAYLAND) -RGFW_WAYLAND_LABEL - RGFW_bool wlout = vkGetPhysicalDeviceWaylandPresentationSupportKHR(physicalDevice, queueFamilyIndex, _RGFW.wl_display); + + RGFW_bool wlout = vkGetPhysicalDeviceWaylandPresentationSupportKHR(physicalDevice, queueFamilyIndex, _RGFW->wl_display); return wlout; #elif defined(RGFW_WINDOWS) #elif defined(RGFW_MACOS) && !defined(RGFW_MACOS_X11) @@ -3109,1341 +4698,138 @@ RGFW_WAYLAND_LABEL This is where OS specific stuff starts */ - -#if (defined(RGFW_WAYLAND) || defined(RGFW_X11)) && !defined(RGFW_NO_LINUX) - int RGFW_eventWait_forceStop[] = {0, 0, 0}; /* for wait events */ - - #if defined(__linux__) - #include - #include - #include - #include - - u32 RGFW_linux_updateGamepad(RGFW_window* win); - u32 RGFW_linux_updateGamepad(RGFW_window* win) { - /* check for new gamepads */ - static const char* str[] = {"/dev/input/js0", "/dev/input/js1", "/dev/input/js2", "/dev/input/js3", "/dev/input/js4", "/dev/input/js5"}; - static u8 RGFW_rawGamepads[6]; - { - u16 i; - for (i = 0; i < 6; i++) { - u16 index = RGFW_gamepadCount; - if (RGFW_rawGamepads[i]) { - struct input_id device_info; - if (ioctl(RGFW_rawGamepads[i], EVIOCGID, &device_info) == -2) { - if (errno == ENODEV) { - RGFW_rawGamepads[i] = 0; - } - } - continue; - } - - i32 js = open(str[i], O_RDONLY); - - if (js <= 0) - break; - - if (RGFW_gamepadCount >= 4) { - close(js); - break; - } - - RGFW_rawGamepads[i] = 1; - - int axes, buttons; - if (ioctl(js, JSIOCGAXES, &axes) < 0 || ioctl(js, JSIOCGBUTTONS, &buttons) < 0) { - close(js); - continue; - } - - if (buttons <= 5 || buttons >= 30) { - close(js); - continue; - } - - RGFW_gamepadCount++; - - RGFW_gamepads[index] = js; - - ioctl(js, JSIOCGNAME(sizeof(RGFW_gamepads_name[index])), RGFW_gamepads_name[index]); - RGFW_gamepads_name[index][sizeof(RGFW_gamepads_name[index]) - 1] = 0; - - u8 j; - for (j = 0; j < 16; j++) { - RGFW_gamepadPressed[index][j].prev = 0; - RGFW_gamepadPressed[index][j].current = 0; - } - - win->event.type = RGFW_gamepadConnected; - - RGFW_gamepads_type[index] = RGFW_gamepadUnknown; - if (RGFW_STRSTR(RGFW_gamepads_name[index], "Microsoft") || RGFW_STRSTR(RGFW_gamepads_name[index], "X-Box")) - RGFW_gamepads_type[index] = RGFW_gamepadMicrosoft; - else if (RGFW_STRSTR(RGFW_gamepads_name[index], "PlayStation") || RGFW_STRSTR(RGFW_gamepads_name[index], "PS3") || RGFW_STRSTR(RGFW_gamepads_name[index], "PS4") || RGFW_STRSTR(RGFW_gamepads_name[index], "PS5")) - RGFW_gamepads_type[index] = RGFW_gamepadSony; - else if (RGFW_STRSTR(RGFW_gamepads_name[index], "Nintendo")) - RGFW_gamepads_type[index] = RGFW_gamepadNintendo; - else if (RGFW_STRSTR(RGFW_gamepads_name[index], "Logitech")) - RGFW_gamepads_type[index] = RGFW_gamepadLogitech; - - win->event.gamepad = index; - RGFW_gamepadCallback(win, index, 1); - return 1; - } - } - /* check gamepad events */ - u8 i; - - for (i = 0; i < RGFW_gamepadCount; i++) { - struct js_event e; - if (RGFW_gamepads[i] == 0) - continue; - - i32 flags = fcntl(RGFW_gamepads[i], F_GETFL, 0); - fcntl(RGFW_gamepads[i], F_SETFL, flags | O_NONBLOCK); - - ssize_t bytes; - while ((bytes = read(RGFW_gamepads[i], &e, sizeof(e))) > 0) { - switch (e.type) { - case JS_EVENT_BUTTON: { - size_t typeIndex = 0; - if (RGFW_gamepads_type[i] == RGFW_gamepadMicrosoft) typeIndex = 1; - else if (RGFW_gamepads_type[i] == RGFW_gamepadLogitech) typeIndex = 2; - - win->event.type = e.value ? RGFW_gamepadButtonPressed : RGFW_gamepadButtonReleased; - u8 RGFW_linux2RGFW[3][RGFW_gamepadR3 + 8] = {{ /* ps */ - RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadY, RGFW_gamepadX, RGFW_gamepadL1, RGFW_gamepadR1, RGFW_gamepadL2, RGFW_gamepadR2, - RGFW_gamepadSelect, RGFW_gamepadStart, RGFW_gamepadHome, RGFW_gamepadL3, RGFW_gamepadR3, RGFW_gamepadUp, RGFW_gamepadDown, RGFW_gamepadLeft, RGFW_gamepadRight, - },{ /* xbox */ - RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadX, RGFW_gamepadY, RGFW_gamepadL1, RGFW_gamepadR1, RGFW_gamepadSelect, RGFW_gamepadStart, - RGFW_gamepadHome, RGFW_gamepadL3, RGFW_gamepadR3, 255, 255, RGFW_gamepadUp, RGFW_gamepadDown, RGFW_gamepadLeft, RGFW_gamepadRight - },{ /* Logitech */ - RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadX, RGFW_gamepadY, RGFW_gamepadL1, RGFW_gamepadR1, RGFW_gamepadL2, RGFW_gamepadR2, - RGFW_gamepadSelect, RGFW_gamepadStart, RGFW_gamepadHome, RGFW_gamepadL3, RGFW_gamepadR3, RGFW_gamepadUp, RGFW_gamepadDown, RGFW_gamepadLeft, RGFW_gamepadRight - } - }; - - win->event.button = RGFW_linux2RGFW[typeIndex][e.number]; - win->event.gamepad = i; - if (win->event.button == 255) break; - - RGFW_gamepadPressed[i][win->event.button].prev = RGFW_gamepadPressed[i][win->event.button].current; - RGFW_gamepadPressed[i][win->event.button].current = RGFW_BOOL(e.value); - RGFW_gamepadButtonCallback(win, i, win->event.button, RGFW_BOOL(e.value)); - - return 1; - } - case JS_EVENT_AXIS: { - size_t axis = e.number / 2; - if (axis == 2) axis = 1; - - ioctl(RGFW_gamepads[i], JSIOCGAXES, &win->event.axisesCount); - win->event.axisesCount = 2; - - if (axis < 3) { - if (e.number == 0 || e.number == 3) - RGFW_gamepadAxes[i][axis].x = (i32)((e.value / 32767.0f) * 100); - else if (e.number == 1 || e.number == 4) { - RGFW_gamepadAxes[i][axis].y = (i32)((e.value / 32767.0f) * 100); - } - } - - win->event.axis[axis] = RGFW_gamepadAxes[i][axis]; - win->event.type = RGFW_gamepadAxisMove; - win->event.gamepad = i; - win->event.whichAxis = (u8)axis; - RGFW_gamepadAxisCallback(win, i, win->event.axis, win->event.axisesCount, win->event.whichAxis); - return 1; - } - default: break; - } - } - if (bytes == -1 && errno == ENODEV) { - RGFW_gamepadCount--; - close(RGFW_gamepads[i]); - RGFW_gamepads[i] = 0; - - win->event.type = RGFW_gamepadDisconnected; - win->event.gamepad = i; - RGFW_gamepadCallback(win, i, 0); - return 1; - } - } - return 0; - } - - #endif -#endif - - - -/* - - Start of Wayland defines - - -*/ - -#ifdef RGFW_WAYLAND -/* -Wayland TODO: (out of date) -- fix RGFW_keyPressed lock state - - RGFW_windowMoved, the window was moved (by the user) - RGFW_windowResized the window was resized (by the user), [on WASM this means the browser was resized] - RGFW_windowRefresh The window content needs to be refreshed - - RGFW_DND a file has been dropped into the window - RGFW_DNDInit - -- window args: - #define RGFW_windowNoResize the window cannot be resized by the user - #define RGFW_windowAllowDND the window supports drag and drop - #define RGFW_scaleToMonitor scale the window to the screen - -- other missing functions functions ("TODO wayland") (~30 functions) -- fix buffer rendering weird behavior -*/ -#include -#include -#include -#include -#include -#include -#include -#include - -RGFW_window* RGFW_key_win = NULL; - -/* wayland global garbage (wayland bad, X11 is fine (ish) (not really)) */ -#include "xdg-shell.h" -#include "xdg-decoration-unstable-v1.h" - -struct xkb_context *xkb_context; -struct xkb_keymap *keymap = NULL; -struct xkb_state *xkb_state = NULL; -enum zxdg_toplevel_decoration_v1_mode client_preferred_mode, RGFW_current_mode; -struct zxdg_decoration_manager_v1 *decoration_manager = NULL; - -struct wl_cursor_theme* RGFW_wl_cursor_theme = NULL; -struct wl_surface* RGFW_cursor_surface = NULL; -struct wl_cursor_image* RGFW_cursor_image = NULL; - -void xdg_wm_base_ping_handler(void *data, - struct xdg_wm_base *wm_base, uint32_t serial) -{ - RGFW_UNUSED(data); - xdg_wm_base_pong(wm_base, serial); -} - -const struct xdg_wm_base_listener xdg_wm_base_listener = { - .ping = xdg_wm_base_ping_handler, -}; - -RGFW_bool RGFW_wl_configured = 0; - -void xdg_surface_configure_handler(void *data, - struct xdg_surface *xdg_surface, uint32_t serial) -{ - RGFW_UNUSED(data); - xdg_surface_ack_configure(xdg_surface, serial); - RGFW_wl_configured = 1; -} - -const struct xdg_surface_listener xdg_surface_listener = { - .configure = xdg_surface_configure_handler, -}; - -void xdg_toplevel_configure_handler(void *data, - struct xdg_toplevel *toplevel, int32_t width, int32_t height, - struct wl_array *states) -{ - RGFW_UNUSED(data); RGFW_UNUSED(toplevel); RGFW_UNUSED(states); - RGFW_UNUSED(width); RGFW_UNUSED(height); -} - -void xdg_toplevel_close_handler(void *data, - struct xdg_toplevel *toplevel) -{ - RGFW_UNUSED(data); - RGFW_window* win = (RGFW_window*)xdg_toplevel_get_user_data(toplevel); - if (win == NULL) - win = RGFW_key_win; - - RGFW_eventQueuePushEx(e.type = RGFW_quit; e._win = win); - RGFW_windowQuitCallback(win); -} - -void shm_format_handler(void *data, - struct wl_shm *shm, uint32_t format) -{ - RGFW_UNUSED(data); RGFW_UNUSED(shm); RGFW_UNUSED(format); -} - -const struct wl_shm_listener shm_listener = { - .format = shm_format_handler, -}; - -const struct xdg_toplevel_listener xdg_toplevel_listener = { - .configure = xdg_toplevel_configure_handler, - .close = xdg_toplevel_close_handler, -}; - -RGFW_window* RGFW_mouse_win = NULL; - -void pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { - RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(serial); RGFW_UNUSED(surface_x); RGFW_UNUSED(surface_y); - RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); - RGFW_mouse_win = win; - - RGFW_eventQueuePushEx(e.type = RGFW_mouseEnter; - e.point = RGFW_POINT(wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)); - e._win = win); - - RGFW_mouseNotifyCallback(win, win->event.point, RGFW_TRUE); -} -void pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { - RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(serial); RGFW_UNUSED(surface); - RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); - if (RGFW_mouse_win == win) - RGFW_mouse_win = NULL; - - RGFW_eventQueuePushEx(e.type = RGFW_mouseLeave; - e.point = win->event.point; - e._win = win); - - RGFW_mouseNotifyCallback(win, win->event.point, RGFW_FALSE); -} -void pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y) { - RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(x); RGFW_UNUSED(y); - - RGFW_ASSERT(RGFW_mouse_win != NULL); - RGFW_eventQueuePushEx(e.type = RGFW_mousePosChanged; - e.point = RGFW_POINT(wl_fixed_to_double(x), wl_fixed_to_double(y)); - e._win = RGFW_mouse_win); - - RGFW_mousePosCallback(RGFW_mouse_win, RGFW_POINT(wl_fixed_to_double(x), wl_fixed_to_double(y)), RGFW_mouse_win->event.vector); -} -void pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { - RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(serial); - RGFW_ASSERT(RGFW_mouse_win != NULL); - - u32 b = (button - 0x110); - - /* flip right and middle button codes */ - if (b == 1) b = 2; - else if (b == 2) b = 1; - - RGFW_mouseButtons[b].prev = RGFW_mouseButtons[b].current; - RGFW_mouseButtons[b].current = RGFW_BOOL(state); - - RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonReleased - RGFW_BOOL(state); - e.point = RGFW_mouse_win->event.point; - e.button = (u8)b; - e._win = RGFW_mouse_win); - RGFW_mouseButtonCallback(RGFW_mouse_win, (u8)b, 0, RGFW_BOOL(state)); -} -void pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { - RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(axis); - RGFW_ASSERT(RGFW_mouse_win != NULL); - - double scroll = - wl_fixed_to_double(value); - - RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonPressed; - e.point = RGFW_mouse_win->event.point; - e.button = RGFW_mouseScrollUp + (scroll < 0); - e.scroll = scroll; - e._win = RGFW_mouse_win); - - RGFW_mouseButtonCallback(RGFW_mouse_win, RGFW_mouseScrollUp + (scroll < 0), scroll, 1); -} - -void RGFW_doNothing(void) { } - -void keyboard_keymap (void *data, struct wl_keyboard *keyboard, uint32_t format, int32_t fd, uint32_t size) { - RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(format); - - char *keymap_string = mmap (NULL, size, PROT_READ, MAP_SHARED, fd, 0); - xkb_keymap_unref (keymap); - keymap = xkb_keymap_new_from_string (xkb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); - - munmap (keymap_string, size); - close (fd); - xkb_state_unref (xkb_state); - xkb_state = xkb_state_new (keymap); -} -void keyboard_enter (void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { - RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(keys); - - RGFW_key_win = (RGFW_window*)wl_surface_get_user_data(surface); - - RGFW_key_win->_flags |= RGFW_windowFocus; - RGFW_eventQueuePushEx(e.type = RGFW_focusIn; e._win = RGFW_key_win); - RGFW_focusCallback(RGFW_key_win, RGFW_TRUE); - - if ((RGFW_key_win->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(RGFW_key_win, RGFW_AREA(RGFW_key_win->r.w, RGFW_key_win->r.h)); -} -void keyboard_leave (void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { - RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); - - RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); - if (RGFW_key_win == win) - RGFW_key_win = NULL; - - RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e._win = win); - RGFW_focusCallback(win, RGFW_FALSE); - RGFW_window_focusLost(win); -} -void keyboard_key (void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { - RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); - - if (RGFW_key_win == NULL) return; - - xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, key + 8); - - u32 RGFWkey = RGFW_apiKeyToRGFW(key + 8); - RGFW_keyboard[RGFWkey].prev = RGFW_keyboard[RGFWkey].current; - RGFW_keyboard[RGFWkey].current = RGFW_BOOL(state); - - RGFW_eventQueuePushEx(e.type = (u8)(RGFW_keyPressed + state); - e.key = (u8)RGFWkey; - e.keyChar = (u8)keysym; - e.repeat = RGFW_isHeld(RGFW_key_win, (u8)RGFWkey); - e._win = RGFW_key_win); - - RGFW_updateKeyMods(RGFW_key_win, RGFW_BOOL(xkb_keymap_mod_get_index(keymap, "Lock")), RGFW_BOOL(xkb_keymap_mod_get_index(keymap, "Mod2")), RGFW_BOOL(xkb_keymap_mod_get_index(keymap, "ScrollLock"))); - RGFW_keyCallback(RGFW_key_win, (u8)RGFWkey, (u8)keysym, RGFW_key_win->event.keyMod, RGFW_BOOL(state)); -} -void keyboard_modifiers (void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { - RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); - xkb_state_update_mask (xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); -} -struct wl_keyboard_listener keyboard_listener = {&keyboard_keymap, &keyboard_enter, &keyboard_leave, &keyboard_key, &keyboard_modifiers, (void (*)(void *, struct wl_keyboard *, -int, int))&RGFW_doNothing}; - -void seat_capabilities (void *data, struct wl_seat *seat, uint32_t capabilities) { - RGFW_UNUSED(data); - static struct wl_pointer_listener pointer_listener = {&pointer_enter, &pointer_leave, &pointer_motion, &pointer_button, &pointer_axis, (void (*)(void *, struct wl_pointer *))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t, uint32_t))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t, int32_t))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t, int32_t))&RGFW_doNothing, (void (*)(void*, struct wl_pointer*, uint32_t, uint32_t))&RGFW_doNothing}; - - if (capabilities & WL_SEAT_CAPABILITY_POINTER) { - struct wl_pointer *pointer = wl_seat_get_pointer (seat); - wl_pointer_add_listener (pointer, &pointer_listener, NULL); - } - if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { - struct wl_keyboard *keyboard = wl_seat_get_keyboard (seat); - wl_keyboard_add_listener (keyboard, &keyboard_listener, NULL); - } -} -struct wl_seat_listener seat_listener = {&seat_capabilities, (void (*)(void *, struct wl_seat *, const char *))&RGFW_doNothing}; - -void wl_global_registry_handler(void *data, - struct wl_registry *registry, uint32_t id, const char *interface, - uint32_t version) -{ - RGFW_window* win = (RGFW_window*)data; - RGFW_UNUSED(version); - if (RGFW_STRNCMP(interface, "wl_compositor", 16) == 0) { - win->src.compositor = wl_registry_bind(registry, - id, &wl_compositor_interface, 4); - } else if (RGFW_STRNCMP(interface, "xdg_wm_base", 12) == 0) { - win->src.xdg_wm_base = wl_registry_bind(registry, - id, &xdg_wm_base_interface, 1); - } else if (RGFW_STRNCMP(interface, zxdg_decoration_manager_v1_interface.name, 255) == 0) { - decoration_manager = wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1); - } else if (RGFW_STRNCMP(interface, "wl_shm", 7) == 0) { - win->src.shm = wl_registry_bind(registry, - id, &wl_shm_interface, 1); - wl_shm_add_listener(win->src.shm, &shm_listener, NULL); - } else if (RGFW_STRNCMP(interface,"wl_seat", 8) == 0) { - win->src.seat = wl_registry_bind(registry, id, &wl_seat_interface, 1); - wl_seat_add_listener(win->src.seat, &seat_listener, NULL); - } -} - -void wl_global_registry_remove(void *data, struct wl_registry *registry, uint32_t name) { RGFW_UNUSED(data); RGFW_UNUSED(registry); RGFW_UNUSED(name); } -const struct wl_registry_listener registry_listener = { - .global = wl_global_registry_handler, - .global_remove = wl_global_registry_remove, -}; - -void decoration_handle_configure(void *data, - struct zxdg_toplevel_decoration_v1 *decoration, - enum zxdg_toplevel_decoration_v1_mode mode) { - RGFW_UNUSED(data); RGFW_UNUSED(decoration); - RGFW_current_mode = mode; -} - -const struct zxdg_toplevel_decoration_v1_listener decoration_listener = { - .configure = decoration_handle_configure, -}; - -void randname(char *buf) { - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - long r = ts.tv_nsec; - - int i; - for (i = 0; i < 6; ++i) { - buf[i] = (char)('A'+(r&15)+(r&16)*2); - r >>= 5; - } -} - -size_t wl_stringlen(char* name) { - size_t i = 0; - while (name[i]) { i++; } - return i; -} - -int anonymous_shm_open(void) { - char name[] = "/RGFW-wayland-XXXXXX"; - int retries = 100; - - do { - randname(name + wl_stringlen(name) - 6); - - --retries; - /* shm_open guarantees that O_CLOEXEC is set */ - int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); - if (fd >= 0) { - shm_unlink(name); - return fd; - } - } while (retries > 0 && errno == EEXIST); - - return -1; -} - -int create_shm_file(off_t size) { - int fd = anonymous_shm_open(); - if (fd < 0) { - return fd; - } - - if (ftruncate(fd, size) < 0) { - close(fd); - return -1; - } - - return fd; -} - -void wl_surface_frame_done(void *data, struct wl_callback *cb, uint32_t time) { - RGFW_UNUSED(data); RGFW_UNUSED(cb); RGFW_UNUSED(time); - - #ifdef RGFW_BUFFER - RGFW_window* win = (RGFW_window*)data; - wl_surface_attach(win->src.surface, win->src.wl_buffer, 0, 0); - wl_surface_damage_buffer(win->src.surface, 0, 0, win->r.w, win->r.h); - wl_surface_commit(win->src.surface); - #endif -} - -const struct wl_callback_listener wl_surface_frame_listener = { - .done = wl_surface_frame_done, -}; -#endif /* RGFW_WAYLAND */ -/* - End of Wayland defines -*/ - -/* - - -Start of Linux / Unix defines - - -*/ +/* start of unix (wayland or X11 (unix) ) defines */ #ifdef RGFW_UNIX -#if !defined(RGFW_NO_X11_CURSOR) && defined(RGFW_X11) -#include -#endif - -#include - -#ifndef RGFW_NO_DPI -#include -#include -#endif - -#include -#include -#include +#include +#include #include -#include /* for converting keycode to string */ -#include /* for hiding */ -#include -#include -#include +void RGFW_stopCheckEvents(void) { -#include /* for data limits (mainly used in drag and drop functions) */ -#include - -/* atoms needed for drag and drop */ -Atom XdndAware, XtextPlain, XtextUriList; -Atom RGFW_XUTF8_STRING = 0; - -Atom wm_delete_window = 0, RGFW_XCLIPBOARD = 0; - -#if defined(RGFW_X11) && !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) - typedef XcursorImage* (*PFN_XcursorImageCreate)(int, int); - typedef void (*PFN_XcursorImageDestroy)(XcursorImage*); - typedef Cursor(*PFN_XcursorImageLoadCursor)(Display*, const XcursorImage*); -#endif -#if defined(RGFW_OPENGL) && defined(RGFW_X11) - typedef GLXContext(*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); -#endif - -#if !defined(RGFW_NO_X11_XI_PRELOAD) && defined(RGFW_X11) - typedef int (* PFN_XISelectEvents)(Display*,Window,XIEventMask*,int); - PFN_XISelectEvents XISelectEventsSRC = NULL; - #define XISelectEvents XISelectEventsSRC - - void* X11Xihandle = NULL; -#endif - -#if !defined(RGFW_NO_X11_EXT_PRELOAD) && defined(RGFW_X11) - typedef void (* PFN_XSyncIntToValue)(XSyncValue*, int); - PFN_XSyncIntToValue XSyncIntToValueSRC = NULL; - #define XSyncIntToValue XSyncIntToValueSRC - - typedef Status (* PFN_XSyncSetCounter)(Display*, XSyncCounter, XSyncValue); - PFN_XSyncSetCounter XSyncSetCounterSRC = NULL; - #define XSyncSetCounter XSyncSetCounterSRC - - typedef XSyncCounter (* PFN_XSyncCreateCounter)(Display*, XSyncValue); - PFN_XSyncCreateCounter XSyncCreateCounterSRC = NULL; - #define XSyncCreateCounter XSyncCreateCounterSRC - - typedef void (* PFN_XShapeCombineMask)(Display*,Window,int,int,int,Pixmap,int); - PFN_XShapeCombineMask XShapeCombineMaskSRC; - #define XShapeCombineMask XShapeCombineMaskSRC - - typedef void (* PFN_XShapeCombineRegion)(Display*,Window,int,int,int,Region,int); - PFN_XShapeCombineRegion XShapeCombineRegionSRC; - #define XShapeCombineRegion XShapeCombineRegionSRC - void* X11XEXThandle = NULL; -#endif - -#if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) && defined(RGFW_X11) - PFN_XcursorImageLoadCursor XcursorImageLoadCursorSRC = NULL; - PFN_XcursorImageCreate XcursorImageCreateSRC = NULL; - PFN_XcursorImageDestroy XcursorImageDestroySRC = NULL; - - #define XcursorImageLoadCursor XcursorImageLoadCursorSRC - #define XcursorImageCreate XcursorImageCreateSRC - #define XcursorImageDestroy XcursorImageDestroySRC - - void* X11Cursorhandle = NULL; -#endif - -#ifdef RGFW_X11 -const char* RGFW_instName = NULL; -void RGFW_setXInstName(const char* name) { RGFW_instName = name; } -#endif - -#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) -RGFW_bool RGFW_extensionSupportedPlatform(const char * extension, size_t len) { - const char* extensions = glXQueryExtensionsString(_RGFW.display, XDefaultScreen(_RGFW.display)); - return (extensions != NULL) && RGFW_extensionSupportedStr(extensions, extension, len); -} -RGFW_proc RGFW_getProcAddress(const char* procname) { return (RGFW_proc) glXGetProcAddress((GLubyte*) procname); } -#endif - -void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area) { - RGFW_GOTO_WAYLAND(0); - -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - win->buffer = (u8*)buffer; - win->bufferSize = area; - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoBuffer, RGFW_DEBUG_CTX(win, 0), "createing a 4 channel buffer"); - #ifdef RGFW_X11 - #ifdef RGFW_OSMESA - win->src.ctx = OSMesaCreateContext(OSMESA_BGRA, NULL); - OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); - OSMesaPixelStore(OSMESA_Y_UP, 0); - #endif - - win->src.bitmap = XCreateImage( - win->src.display, win->src.visual.visual, (u32)win->src.visual.depth, - ZPixmap, 0, NULL, area.w, area.h, 32, 0 - ); - #endif - #ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL {} - u32 size = (u32)(win->r.w * win->r.h * 4); - int fd = create_shm_file(size); - if (fd < 0) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, RGFW_DEBUG_CTX(win, (u32)fd),"Failed to create a buffer."); - exit(1); - } - - win->src.buffer = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (win->src.buffer == MAP_FAILED) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, RGFW_DEBUG_CTX(win, 0), "mmap failed!"); - close(fd); - exit(1); - } - - win->_flags |= RGFW_BUFFER_ALLOC; - - struct wl_shm_pool* pool = wl_shm_create_pool(win->src.shm, fd, (i32)size); - win->src.wl_buffer = wl_shm_pool_create_buffer(pool, 0, win->r.w, win->r.h, win->r.w * 4, - WL_SHM_FORMAT_ARGB8888); - wl_shm_pool_destroy(pool); - - close(fd); - - wl_surface_attach(win->src.surface, win->src.wl_buffer, 0, 0); - wl_surface_commit(win->src.surface); - - u8 color[] = {0x00, 0x00, 0x00, 0xFF}; - - size_t i; - for (i = 0; i < area.w * area.h * 4; i += 4) { - RGFW_MEMCPY(&win->buffer[i], color, 4); - } - - RGFW_MEMCPY(win->src.buffer, win->buffer, (size_t)(win->r.w * win->r.h * 4)); - - #if defined(RGFW_OSMESA) - win->src.ctx = OSMesaCreateContext(OSMESA_BGRA, NULL); - OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); - OSMesaPixelStore(OSMESA_Y_UP, 0); - #endif - #endif -#else - #ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL{} - #endif - - RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); -#endif + _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; + } } -#define RGFW_LOAD_ATOM(name) \ - static Atom name = 0; \ - if (name == 0) name = XInternAtom(_RGFW.display, #name, False); +RGFWDEF u64 RGFW_linux_getTimeNS(i32 clock); +u64 RGFW_linux_getTimeNS(i32 clock) { + struct timespec ts; + const u64 scale_factor = 1000000000; + clock_gettime(clock, &ts); + return (u64)ts.tv_sec * scale_factor + (u64)ts.tv_nsec; +} -void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { - RGFW_setBit(&win->_flags, RGFW_windowNoBorder, !border); +void RGFW_waitForEvent(i32 waitMS) { + if (waitMS == 0) return; - RGFW_GOTO_WAYLAND(0); - #ifdef RGFW_X11 - RGFW_LOAD_ATOM(_MOTIF_WM_HINTS); - - struct __x11WindowHints { - unsigned long flags, functions, decorations, status; - long input_mode; - } hints; - hints.flags = 2; - hints.decorations = border; - - XChangeProperty(win->src.display, win->src.window, _MOTIF_WM_HINTS, _MOTIF_WM_HINTS, 32, - PropModeReplace, (u8*)&hints, 5 - ); - - if (RGFW_window_isHidden(win) == 0) { - RGFW_window_hide(win); - RGFW_window_show(win); + 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); + } } - #endif - #ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - RGFW_UNUSED(win); RGFW_UNUSED(border); - #endif -} + struct pollfd fds[2]; + fds[0].fd = 0; + fds[0].events = POLLIN; + fds[0].revents = 0; + fds[1].fd = _RGFW->eventWait_forceStop[0]; + fds[1].events = POLLIN; + fds[1].revents = 0; -void RGFW_releaseCursor(RGFW_window* win) { -RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - XUngrabPointer(win->src.display, CurrentTime); - /* disable raw input */ - unsigned char mask[] = { 0 }; - XIEventMask em; - em.deviceid = XIAllMasterDevices; - em.mask_len = sizeof(mask); - em.mask = mask; + if (RGFW_usingWayland()) { + #ifdef RGFW_WAYLAND + fds[0].fd = wl_display_get_fd(_RGFW->wl_display); - XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - RGFW_UNUSED(win); -#endif -} - -void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { -RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - /* enable raw input */ - unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; - XISetMask(mask, XI_RawMotion); - - XIEventMask em; - em.deviceid = XIAllMasterDevices; - em.mask_len = sizeof(mask); - em.mask = mask; - - XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); - - XGrabPointer(win->src.display, win->src.window, True, PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); - RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (i32)(r.w / 2), win->r.y + (i32)(r.h / 2))); -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - RGFW_UNUSED(win); RGFW_UNUSED(r); -#endif -} - -#define RGFW_LOAD_LIBRARY(x, lib) if (x == NULL) x = dlopen(lib, RTLD_LAZY | RTLD_LOCAL) -#define RGFW_PROC_DEF(proc, name) if (name##SRC == NULL && proc != NULL) { \ - void* ptr = dlsym(proc, #name); \ - if (ptr != NULL) memcpy(&name##SRC, &ptr, sizeof(PFN_##name)); \ -} - -#ifdef RGFW_X11 -void RGFW_window_getVisual(RGFW_window* win) { -#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) - i32* visual_attribs = RGFW_initFormatAttribs(); - i32 fbcount; - GLXFBConfig* fbc = glXChooseFBConfig(win->src.display, DefaultScreen(win->src.display), visual_attribs, &fbcount); - - i32 best_fbc = -1; - i32 best_depth = 0; - i32 best_samples = 0; - - if (fbcount == 0) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to find any valid GLX visual configs"); - return; - } - - i32 i; - for (i = 0; i < fbcount; i++) { - XVisualInfo* vi = glXGetVisualFromFBConfig(win->src.display, fbc[i]); - if (vi == NULL) - continue; - - i32 samp_buf, samples; - glXGetFBConfigAttrib(win->src.display, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf); - glXGetFBConfigAttrib(win->src.display, fbc[i], GLX_SAMPLES, &samples); - - if (best_fbc == -1) best_fbc = i; - if ((!(win->_flags & RGFW_windowTransparent) || vi->depth == 32) && best_depth == 0) { - best_fbc = i; - best_depth = vi->depth; + /* empty the queue */ + while (wl_display_prepare_read(_RGFW->wl_display) != 0) { + /* error occured when dispatching the queue */ + if (wl_display_dispatch_pending(_RGFW->wl_display) == -1) { + return; } - if ((!(win->_flags & RGFW_windowTransparent) || vi->depth == 32) && samples <= RGFW_GL_HINTS[RGFW_glSamples] && samples > best_samples) { - best_fbc = i; - best_depth = vi->depth; - best_samples = samples; + } + + /* send any pending requests to the compositor */ + while (wl_display_flush(_RGFW->wl_display) == -1) { + + /* queue is full dispatch them */ + if (errno == EAGAIN) { + if (wl_display_dispatch_pending(_RGFW->wl_display) == -1) { + return; + } + } else { + return; } - XFree(vi); } - - if (best_fbc == -1) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to get a valid GLX visual"); - return; - } - - win->src.bestFbc = fbc[best_fbc]; - XVisualInfo* vi = glXGetVisualFromFBConfig(win->src.display, win->src.bestFbc); - if (vi->depth != 32 && (win->_flags & RGFW_windowTransparent)) - RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, RGFW_DEBUG_CTX(win, 0), "Failed to to find a matching visual with a 32-bit depth"); - - if (best_samples < RGFW_GL_HINTS[RGFW_glSamples]) - RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, RGFW_DEBUG_CTX(win, 0), "Failed to load matching sampiling"); - - int configCaveat; - if (glXGetFBConfigAttrib(win->src.display, win->src.bestFbc, GLX_CONFIG_CAVEAT, &configCaveat) == Success && - configCaveat == GLX_SLOW_CONFIG) { - win->_flags |= RGFW_windowOpenglSoftware; - } - - XFree(fbc); - win->src.visual = *vi; - XFree(vi); -#else - win->src.visual.visual = DefaultVisual(win->src.display, DefaultScreen(win->src.display)); - win->src.visual.depth = DefaultDepth(win->src.display, DefaultScreen(win->src.display)); - if (win->_flags & RGFW_windowTransparent) { - XMatchVisualInfo(win->src.display, DefaultScreen(win->src.display), 32, TrueColor, &win->src.visual); /*!< for RGBA backgrounds */ - if (win->src.visual.depth != 32) - RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, RGFW_DEBUG_CTX(win, 0), "Failed to load a 32-bit depth"); - } -#endif -} -#endif -#ifndef RGFW_EGL -void RGFW_window_initOpenGL(RGFW_window* win) { -#ifdef RGFW_OPENGL - i32 context_attribs[7] = { 0, 0, 0, 0, 0, 0, 0 }; - context_attribs[0] = GLX_CONTEXT_PROFILE_MASK_ARB; - if (RGFW_GL_HINTS[RGFW_glProfile] == RGFW_glCore) - context_attribs[1] = GLX_CONTEXT_CORE_PROFILE_BIT_ARB; - else - context_attribs[1] = GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; - - if (RGFW_GL_HINTS[RGFW_glMinor] || RGFW_GL_HINTS[RGFW_glMajor]) { - context_attribs[2] = GLX_CONTEXT_MAJOR_VERSION_ARB; - context_attribs[3] = RGFW_GL_HINTS[RGFW_glMajor]; - context_attribs[4] = GLX_CONTEXT_MINOR_VERSION_ARB; - context_attribs[5] = RGFW_GL_HINTS[RGFW_glMinor]; - } - - glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0; - glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc) - glXGetProcAddressARB((GLubyte*) "glXCreateContextAttribsARB"); - - GLXContext ctx = NULL; - if (_RGFW.root != NULL && _RGFW.root != win) { - ctx = _RGFW.root->src.ctx; - RGFW_window_makeCurrent_OpenGL(_RGFW.root); - } - - if (glXCreateContextAttribsARB == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "failed to load proc address 'glXCreateContextAttribsARB', loading a generic opengl context"); - win->src.ctx = glXCreateContext(win->src.display, &win->src.visual, ctx, True); - } - else { - win->src.ctx = glXCreateContextAttribsARB(win->src.display, win->src.bestFbc, ctx, True, context_attribs); - XSync(win->src.display, False); - if (win->src.ctx == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "failed to create an opengl context with AttribsARB, loading a generic opengl context"); - win->src.ctx = glXCreateContext(win->src.display, &win->src.visual, ctx, True); - } - } - - glXMakeCurrent(win->src.display, (Drawable) win->src.window, (GLXContext) win->src.ctx); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context initalized"); -#else - RGFW_UNUSED(win); -#endif -} - -void RGFW_window_freeOpenGL(RGFW_window* win) { -#ifdef RGFW_OPENGL - if (win->src.ctx == NULL) return; - glXDestroyContext(win->src.display, win->src.ctx); - win->src.ctx = NULL; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context freed"); -#else -RGFW_UNUSED(win); -#endif -} -#endif - - -i32 RGFW_init(void) { - RGFW_GOTO_WAYLAND(1); -#if defined(RGFW_C89) || defined(__cplusplus) - if (_RGFW_init) return 0; - _RGFW_init = RGFW_TRUE; - _RGFW.root = NULL; _RGFW.current = NULL; _RGFW.windowCount = -1; _RGFW.eventLen = 0; _RGFW.eventIndex = 0; -#endif - -#ifdef RGFW_X11 - if (_RGFW.windowCount != -1) return 0; - #ifdef RGFW_USE_XDL - XDL_init(); - #endif - - #if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) - #if defined(__CYGWIN__) - RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor-1.so"); - #elif defined(__OpenBSD__) || defined(__NetBSD__) - RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor.so"); - #else - RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor.so.1"); - #endif - RGFW_PROC_DEF(X11Cursorhandle, XcursorImageCreate); - RGFW_PROC_DEF(X11Cursorhandle, XcursorImageDestroy); - RGFW_PROC_DEF(X11Cursorhandle, XcursorImageLoadCursor); - #endif - - #if !defined(RGFW_NO_X11_XI_PRELOAD) - #if defined(__CYGWIN__) - RGFW_LOAD_LIBRARY(X11Xihandle, "libXi-6.so"); - #elif defined(__OpenBSD__) || defined(__NetBSD__) - RGFW_LOAD_LIBRARY(X11Xihandle, "libXi.so"); - #else - RGFW_LOAD_LIBRARY(X11Xihandle, "libXi.so.6"); - #endif - RGFW_PROC_DEF(X11Xihandle, XISelectEvents); - #endif - - #if !defined(RGFW_NO_X11_EXT_PRELOAD) - #if defined(__CYGWIN__) - RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext-6.so"); - #elif defined(__OpenBSD__) || defined(__NetBSD__) - RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext.so"); - #else - RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext.so.6"); - #endif - RGFW_PROC_DEF(X11XEXThandle, XSyncCreateCounter); - RGFW_PROC_DEF(X11XEXThandle, XSyncIntToValue); - RGFW_PROC_DEF(X11XEXThandle, XSyncSetCounter); - RGFW_PROC_DEF(X11XEXThandle, XShapeCombineRegion); - RGFW_PROC_DEF(X11XEXThandle, XShapeCombineMask); - #endif - - XInitThreads(); /*!< init X11 threading */ - _RGFW.display = XOpenDisplay(0); - XSetWindowAttributes wa; - RGFW_MEMSET(&wa, 0, sizeof(wa)); - wa.event_mask = PropertyChangeMask; - _RGFW.helperWindow = XCreateWindow(_RGFW.display, XDefaultRootWindow(_RGFW.display), 0, 0, 1, 1, 0, 0, - InputOnly, DefaultVisual(_RGFW.display, DefaultScreen(_RGFW.display)), CWEventMask, &wa); - - _RGFW.windowCount = 0; - u8 RGFW_blk[] = { 0, 0, 0, 0 }; - _RGFW.hiddenMouse = RGFW_loadMouse(RGFW_blk, RGFW_AREA(1, 1), 4); - _RGFW.clipboard = NULL; - - XkbComponentNamesRec rec; - XkbDescPtr desc = XkbGetMap(_RGFW.display, 0, XkbUseCoreKbd); - XkbDescPtr evdesc; - u8 old[sizeof(RGFW_keycodes) / sizeof(RGFW_keycodes[0])]; - - XkbGetNames(_RGFW.display, XkbKeyNamesMask, desc); - - RGFW_MEMSET(&rec, 0, sizeof(rec)); - rec.keycodes = (char*)"evdev"; - evdesc = XkbGetKeyboardByName(_RGFW.display, XkbUseCoreKbd, &rec, XkbGBN_KeyNamesMask, XkbGBN_KeyNamesMask, False); - /* memo: RGFW_keycodes[x11 keycode] = rgfw keycode */ - if(evdesc != NULL && desc != NULL){ - for(int i = 0; i < (int)sizeof(RGFW_keycodes) / (int)sizeof(RGFW_keycodes[0]); i++){ - old[i] = RGFW_keycodes[i]; - RGFW_keycodes[i] = 0; - } - for(int i = evdesc->min_key_code; i <= evdesc->max_key_code; i++){ - for(int j = desc->min_key_code; j <= desc->max_key_code; j++){ - if(strncmp(evdesc->names->keys[i].name, desc->names->keys[j].name, XkbKeyNameLength) == 0){ - RGFW_keycodes[j] = old[i]; - break; - } - } - } - XkbFreeKeyboard(desc, 0, True); - XkbFreeKeyboard(evdesc, 0, True); - } -#endif -#ifdef RGFW_WAYLAND -RGFW_WAYLAND_LABEL - _RGFW.wl_display = wl_display_connect(NULL); -#endif - _RGFW.windowCount = 0; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context initialized"); - return 0; -} - - -RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { - RGFW_window_basic_init(win, rect, flags); - -#ifdef RGFW_WAYLAND - win->src.compositor = NULL; -#endif - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - i64 event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | FocusChangeMask | LeaveWindowMask | EnterWindowMask | ExposureMask; /*!< X11 events accepted */ - - win->src.display = XOpenDisplay(NULL); - RGFW_window_getVisual(win); - - /* make X window attrubutes */ - XSetWindowAttributes swa; - RGFW_MEMSET(&swa, 0, sizeof(swa)); - - Colormap cmap; - swa.colormap = cmap = XCreateColormap(win->src.display, - DefaultRootWindow(win->src.display), - win->src.visual.visual, AllocNone); - swa.event_mask = event_mask; - - /* create the window */ - win->src.window = XCreateWindow(win->src.display, DefaultRootWindow(win->src.display), win->r.x, win->r.y, (u32)win->r.w, (u32)win->r.h, - 0, win->src.visual.depth, InputOutput, win->src.visual.visual, - CWColormap | CWBorderPixel | CWEventMask, &swa); - - XFreeColors(win->src.display, cmap, NULL, 0, 0); - - win->src.gc = XCreateGC(win->src.display, win->src.window, 0, NULL); - - /* In your .desktop app, if you set the property - StartupWMClass=RGFW that will assoicate the launcher icon - with your application - robrohan */ - if (RGFW_className == NULL) - RGFW_className = (char*)name; - - XClassHint hint; - hint.res_class = (char*)RGFW_className; - if (RGFW_instName == NULL) hint.res_name = (char*)name; - else hint.res_name = (char*)RGFW_instName; - XSetClassHint(win->src.display, win->src.window, &hint); - - #ifndef RGFW_NO_MONITOR - if (flags & RGFW_windowScaleToMonitor) - RGFW_window_scaleToMonitor(win); - #endif - XSelectInput(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(win->src.display, "WM_DELETE_WINDOW", False); - RGFW_XUTF8_STRING = XInternAtom(win->src.display, "UTF8_STRING", False); - RGFW_XCLIPBOARD = XInternAtom(win->src.display, "CLIPBOARD", False); - } - - XSetWMProtocols(win->src.display, (Drawable) win->src.window, &wm_delete_window, 1); - /* set the background */ - RGFW_window_setName(win, name); - - XMoveWindow(win->src.display, (Drawable) win->src.window, win->r.x, win->r.y); /*!< move the window to it's proper cords */ - - if (flags & RGFW_windowAllowDND) { /* init drag and drop atoms and turn on drag and drop for this window */ - win->_flags |= RGFW_windowAllowDND; - - /* actions */ - XtextUriList = XInternAtom(win->src.display, "text/uri-list", False); - XtextPlain = XInternAtom(win->src.display, "text/plain", False); - XdndAware = XInternAtom(win->src.display, "XdndAware", False); - const u8 version = 5; - - XChangeProperty(win->src.display, win->src.window, - XdndAware, 4, 32, - PropModeReplace, &version, 1); /*!< turns on drag and drop */ - } - -#ifdef RGFW_ADVANCED_SMOOTH_RESIZE - RGFW_LOAD_ATOM(_NET_WM_SYNC_REQUEST_COUNTER) - RGFW_LOAD_ATOM(_NET_WM_SYNC_REQUEST) - Atom protcols[2] = {_NET_WM_SYNC_REQUEST, wm_delete_window}; - XSetWMProtocols(win->src.display, win->src.window, protcols, 2); - - XSyncValue initial_value; - XSyncIntToValue(&initial_value, 0); - win->src.counter = XSyncCreateCounter(win->src.display, initial_value); - - XChangeProperty(win->src.display, win->src.window, _NET_WM_SYNC_REQUEST_COUNTER, XA_CARDINAL, 32, PropModeReplace, (uint8_t*)&win->src.counter, 1); -#endif - - if ((flags & RGFW_windowNoInitAPI) == 0) { - RGFW_window_initOpenGL(win); - RGFW_window_initBuffer(win); - } - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); - RGFW_window_setMouseDefault(win); - RGFW_window_setFlags(win, flags); - - win->src.r = win->r; - - RGFW_window_show(win); - return win; /*return newly created window */ -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningWayland, RGFW_DEBUG_CTX(win, 0), "RGFW Wayland support is experimental"); - - win->src.wl_display = _RGFW.wl_display; - if (win->src.wl_display == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errWayland, RGFW_DEBUG_CTX(win, 0), "Failed to load Wayland display"); + #endif + } else { #ifdef RGFW_X11 - RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningWayland, RGFW_DEBUG_CTX(win, 0), "Falling back to X11"); - RGFW_useWayland(0); - return RGFW_createWindowPtr(name, rect, flags, win); + fds[0].fd = ConnectionNumber(_RGFW->display); #endif - return NULL; } + i32 clock = 0; + #if defined(_POSIX_MONOTONIC_CLOCK) + struct timespec ts; + RGFW_MEMSET(&ts, 0, sizeof(struct timespec)); - #ifdef RGFW_X11 - win->src.display = _RGFW.display; - win->src.window = _RGFW.helperWindow; - XMapWindow(_RGFW.display, win->src.window); - XFlush(win->src.display); - if (wm_delete_window == 0) { - wm_delete_window = XInternAtom(win->src.display, "WM_DELETE_WINDOW", False); - RGFW_XUTF8_STRING = XInternAtom(win->src.display, "UTF8_STRING", False); - RGFW_XCLIPBOARD = XInternAtom(win->src.display, "CLIPBOARD", False); - } + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) + clock = CLOCK_MONOTONIC; + #else + clock = CLOCK_REALTIME; #endif - struct wl_registry *registry = wl_display_get_registry(win->src.wl_display); - wl_registry_add_listener(registry, ®istry_listener, win); + u64 start = RGFW_linux_getTimeNS(clock); + if (RGFW_usingWayland()) { + #ifdef RGFW_WAYLAND + while (wl_display_dispatch_pending(_RGFW->wl_display) == 0) { + if (poll(fds, 1, waitMS) <= 0) { + wl_display_cancel_read(_RGFW->wl_display); + break; + } else { + if (wl_display_read_events(_RGFW->wl_display) == -1) + return; + } - wl_display_roundtrip(win->src.wl_display); - wl_display_dispatch(win->src.wl_display); - - if (win->src.compositor == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errWayland, RGFW_DEBUG_CTX(win, 0), "Can't find compositor."); - return NULL; - } - - if (RGFW_wl_cursor_theme == NULL) { - RGFW_wl_cursor_theme = wl_cursor_theme_load(NULL, 24, win->src.shm); - RGFW_cursor_surface = wl_compositor_create_surface(win->src.compositor); - - struct wl_cursor* cursor = wl_cursor_theme_get_cursor(RGFW_wl_cursor_theme, "left_ptr"); - RGFW_cursor_image = cursor->images[0]; - struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(RGFW_cursor_image); - - wl_surface_attach(RGFW_cursor_surface, cursor_buffer, 0, 0); - wl_surface_commit(RGFW_cursor_surface); - } - - xdg_wm_base_add_listener(win->src.xdg_wm_base, &xdg_wm_base_listener, NULL); - - xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - - win->src.surface = wl_compositor_create_surface(win->src.compositor); - wl_surface_set_user_data(win->src.surface, win); - - win->src.xdg_surface = xdg_wm_base_get_xdg_surface(win->src.xdg_wm_base, win->src.surface); - xdg_surface_add_listener(win->src.xdg_surface, &xdg_surface_listener, NULL); - - xdg_wm_base_set_user_data(win->src.xdg_wm_base, win); - - win->src.xdg_toplevel = xdg_surface_get_toplevel(win->src.xdg_surface); - xdg_toplevel_set_user_data(win->src.xdg_toplevel, win); - xdg_toplevel_add_listener(win->src.xdg_toplevel, &xdg_toplevel_listener, NULL); - - xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->r.w, win->r.h); - - if (!(flags & RGFW_windowNoBorder)) { - win->src.decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( - decoration_manager, win->src.xdg_toplevel); - } - - wl_display_roundtrip(win->src.wl_display); - - wl_surface_commit(win->src.surface); - RGFW_window_show(win); - - /* wait for the surface to be configured */ - while (wl_display_dispatch(win->src.wl_display) != -1 && !RGFW_wl_configured) { } - - if ((flags & RGFW_windowNoInitAPI) == 0) { - RGFW_window_initOpenGL(win); - RGFW_window_initBuffer(win); - } - struct wl_callback* callback = wl_surface_frame(win->src.surface); - wl_callback_add_listener(callback, &wl_surface_frame_listener, win); - wl_surface_commit(win->src.surface); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); - - #ifndef RGFW_NO_MONITOR - if (flags & RGFW_windowScaleToMonitor) - RGFW_window_scaleToMonitor(win); - #endif - - RGFW_window_setName(win, name); - RGFW_window_setMouseDefault(win); - RGFW_window_setFlags(win, flags); - return win; /* return newly created window */ -#endif -} - -RGFW_area RGFW_getScreenSize(void) { - RGFW_GOTO_WAYLAND(1); - RGFW_init(); - - #ifdef RGFW_X11 - Screen* scrn = DefaultScreenOfDisplay(_RGFW.display); - return RGFW_AREA(scrn->width, scrn->height); - #endif - #ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL return RGFW_AREA(_RGFW.root->r.w, _RGFW.root->r.h); /* TODO */ - #endif -} - -RGFW_point RGFW_getGlobalMousePoint(void) { - RGFW_init(); - RGFW_point RGFWMouse = RGFW_POINT(0, 0); - RGFW_GOTO_WAYLAND(1); -#ifdef RGFW_X11 - i32 x, y; - u32 z; - Window window1, window2; - XQueryPointer(_RGFW.display, XDefaultRootWindow(_RGFW.display), &window1, &window2, &RGFWMouse.x, &RGFWMouse.y, &x, &y, &z); - return RGFWMouse; -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - return RGFWMouse; -#endif -} - -RGFWDEF void RGFW_XHandleClipboardSelection(XEvent* event); -void RGFW_XHandleClipboardSelection(XEvent* event) { RGFW_UNUSED(event); -#ifdef RGFW_X11 - RGFW_LOAD_ATOM(ATOM_PAIR); - RGFW_LOAD_ATOM(MULTIPLE); - RGFW_LOAD_ATOM(TARGETS); - RGFW_LOAD_ATOM(SAVE_TARGETS); - - const XSelectionRequestEvent* request = &event->xselectionrequest; - const Atom formats[] = { RGFW_XUTF8_STRING, XA_STRING }; - const int formatCount = sizeof(formats) / sizeof(formats[0]); - - if (request->target == TARGETS) { - const Atom targets[] = { TARGETS, MULTIPLE, RGFW_XUTF8_STRING, XA_STRING }; - - XChangeProperty(_RGFW.display, request->requestor, request->property, - XA_ATOM, 32, PropModeReplace, (u8*) targets, sizeof(targets) / sizeof(Atom)); - } else if (request->target == MULTIPLE) { - Atom* targets = NULL; - - Atom actualType = 0; - int actualFormat = 0; - unsigned long count = 0, bytesAfter = 0; - - XGetWindowProperty(_RGFW.display, request->requestor, request->property, 0, LONG_MAX, - False, ATOM_PAIR, &actualType, &actualFormat, &count, &bytesAfter, (u8**) &targets); - - unsigned long i; - for (i = 0; i < (u32)count; i += 2) { - if (targets[i] == RGFW_XUTF8_STRING || targets[i] == XA_STRING) - XChangeProperty(_RGFW.display, request->requestor, targets[i + 1], targets[i], - 8, PropModeReplace, (const unsigned char *)_RGFW.clipboard, (i32)_RGFW.clipboard_len); - else - targets[i + 1] = None; + if (waitMS != RGFW_eventWaitNext) { + waitMS -= (i32)(RGFW_linux_getTimeNS(clock) - start) / (i32)1e+6; + } } - XChangeProperty(_RGFW.display, - request->requestor, request->property, ATOM_PAIR, 32, - PropModeReplace, (u8*) targets, (i32)count); - - XFlush(_RGFW.display); - XFree(targets); - } else if (request->target == SAVE_TARGETS) - XChangeProperty(_RGFW.display, request->requestor, request->property, 0, 32, PropModeReplace, NULL, 0); - else { - int i; - for (i = 0; i < formatCount; i++) { - if (request->target != formats[i]) - continue; - XChangeProperty(_RGFW.display, request->requestor, request->property, request->target, - 8, PropModeReplace, (u8*) _RGFW.clipboard, (i32)_RGFW.clipboard_len); + /* queue contains events from read, dispatch them */ + if (wl_display_dispatch_pending(_RGFW->wl_display) == -1) { + return; } + #endif + } else { + #ifdef RGFW_X11 + while (XPending(_RGFW->display) == 0) { + if (poll(fds, 1, waitMS) <= 0) + break; + + if (waitMS != RGFW_eventWaitNext) { + waitMS -= (i32)(RGFW_linux_getTimeNS(clock) - start) / (i32)1e+6; + } + } + #endif } - XEvent reply = { SelectionNotify }; - reply.xselection.property = request->property; - reply.xselection.display = request->display; - reply.xselection.requestor = request->requestor; - reply.xselection.selection = request->selection; - reply.xselection.target = request->target; - reply.xselection.time = request->time; + /* drain any data in the stop request */ + if (_RGFW->eventWait_forceStop[2]) { + char data[64]; + RGFW_MEMSET(data, 0, sizeof(data)); + (void)!read(_RGFW->eventWait_forceStop[0], data, sizeof(data)); - XSendEvent(_RGFW.display, request->requestor, False, 0, &reply); -#endif + _RGFW->eventWait_forceStop[2] = 0; + } } char* RGFW_strtok(char* str, const char* delimStr); @@ -4496,19 +4882,678 @@ char* RGFW_strtok(char* str, const char* delimStr) { return token_start; } +#ifdef RGFW_X11 +RGFWDEF i32 RGFW_initPlatform_X11(void); +RGFWDEF void RGFW_deinitPlatform_X11(void); +#endif +#ifdef RGFW_WAYLAND +RGFWDEF i32 RGFW_initPlatform_Wayland(void); +RGFWDEF void RGFW_deinitPlatform_Wayland(void); +#endif + +RGFWDEF void RGFW_load_X11(void); +RGFWDEF void RGFW_load_Wayland(void); + +#if !defined(RGFW_X11) || !defined(RGFW_WAYLAND) +void RGFW_load_X11(void) { } +void RGFW_load_Wayland(void) { } +#endif + +/* + * Sadly we have to use magic linux keycodes + * We can't use X11 functions, because that breaks Wayland, but they use the same keycodes so there's no use redeffing them + * We can't use linux enums, because the headers don't exist on BSD + */ +void RGFW_initKeycodesPlatform(void) { + _RGFW->keycodes[49] = RGFW_backtick; + _RGFW->keycodes[19] = RGFW_0; + _RGFW->keycodes[10] = RGFW_1; + _RGFW->keycodes[11] = RGFW_2; + _RGFW->keycodes[12] = RGFW_3; + _RGFW->keycodes[13] = RGFW_4; + _RGFW->keycodes[14] = RGFW_5; + _RGFW->keycodes[15] = RGFW_6; + _RGFW->keycodes[16] = RGFW_7; + _RGFW->keycodes[17] = RGFW_8; + _RGFW->keycodes[18] = RGFW_9; + _RGFW->keycodes[65] = RGFW_space; + _RGFW->keycodes[38] = RGFW_a; + _RGFW->keycodes[56] = RGFW_b; + _RGFW->keycodes[54] = RGFW_c; + _RGFW->keycodes[40] = RGFW_d; + _RGFW->keycodes[26] = RGFW_e; + _RGFW->keycodes[41] = RGFW_f; + _RGFW->keycodes[42] = RGFW_g; + _RGFW->keycodes[43] = RGFW_h; + _RGFW->keycodes[31] = RGFW_i; + _RGFW->keycodes[44] = RGFW_j; + _RGFW->keycodes[45] = RGFW_k; + _RGFW->keycodes[46] = RGFW_l; + _RGFW->keycodes[58] = RGFW_m; + _RGFW->keycodes[57] = RGFW_n; + _RGFW->keycodes[32] = RGFW_o; + _RGFW->keycodes[33] = RGFW_p; + _RGFW->keycodes[24] = RGFW_q; + _RGFW->keycodes[27] = RGFW_r; + _RGFW->keycodes[39] = RGFW_s; + _RGFW->keycodes[28] = RGFW_t; + _RGFW->keycodes[30] = RGFW_u; + _RGFW->keycodes[55] = RGFW_v; + _RGFW->keycodes[25] = RGFW_w; + _RGFW->keycodes[53] = RGFW_x; + _RGFW->keycodes[29] = RGFW_y; + _RGFW->keycodes[52] = RGFW_z; + _RGFW->keycodes[60] = RGFW_period; + _RGFW->keycodes[59] = RGFW_comma; + _RGFW->keycodes[61] = RGFW_slash; + _RGFW->keycodes[34] = RGFW_bracket; + _RGFW->keycodes[35] = RGFW_closeBracket; + _RGFW->keycodes[47] = RGFW_semicolon; + _RGFW->keycodes[48] = RGFW_apostrophe; + _RGFW->keycodes[51] = RGFW_backSlash; + _RGFW->keycodes[36] = RGFW_return; + _RGFW->keycodes[119] = RGFW_delete; + _RGFW->keycodes[77] = RGFW_numLock; + _RGFW->keycodes[106] = RGFW_kpSlash; + _RGFW->keycodes[63] = RGFW_kpMultiply; + _RGFW->keycodes[86] = RGFW_kpPlus; + _RGFW->keycodes[82] = RGFW_kpMinus; + _RGFW->keycodes[87] = RGFW_kp1; + _RGFW->keycodes[88] = RGFW_kp2; + _RGFW->keycodes[89] = RGFW_kp3; + _RGFW->keycodes[83] = RGFW_kp4; + _RGFW->keycodes[84] = RGFW_kp5; + _RGFW->keycodes[85] = RGFW_kp6; + _RGFW->keycodes[81] = RGFW_kp9; + _RGFW->keycodes[90] = RGFW_kp0; + _RGFW->keycodes[91] = RGFW_kpPeriod; + _RGFW->keycodes[104] = RGFW_kpReturn; + _RGFW->keycodes[20] = RGFW_minus; + _RGFW->keycodes[21] = RGFW_equals; + _RGFW->keycodes[22] = RGFW_backSpace; + _RGFW->keycodes[23] = RGFW_tab; + _RGFW->keycodes[66] = RGFW_capsLock; + _RGFW->keycodes[50] = RGFW_shiftL; + _RGFW->keycodes[37] = RGFW_controlL; + _RGFW->keycodes[64] = RGFW_altL; + _RGFW->keycodes[133] = RGFW_superL; + _RGFW->keycodes[105] = RGFW_controlR; + _RGFW->keycodes[134] = RGFW_superR; + _RGFW->keycodes[62] = RGFW_shiftR; + _RGFW->keycodes[108] = RGFW_altR; + _RGFW->keycodes[67] = RGFW_F1; + _RGFW->keycodes[68] = RGFW_F2; + _RGFW->keycodes[69] = RGFW_F3; + _RGFW->keycodes[70] = RGFW_F4; + _RGFW->keycodes[71] = RGFW_F5; + _RGFW->keycodes[72] = RGFW_F6; + _RGFW->keycodes[73] = RGFW_F7; + _RGFW->keycodes[74] = RGFW_F8; + _RGFW->keycodes[75] = RGFW_F9; + _RGFW->keycodes[76] = RGFW_F10; + _RGFW->keycodes[95] = RGFW_F11; + _RGFW->keycodes[96] = RGFW_F12; + _RGFW->keycodes[111] = RGFW_up; + _RGFW->keycodes[116] = RGFW_down; + _RGFW->keycodes[113] = RGFW_left; + _RGFW->keycodes[114] = RGFW_right; + _RGFW->keycodes[118] = RGFW_insert; + _RGFW->keycodes[115] = RGFW_end; + _RGFW->keycodes[112] = RGFW_pageUp; + _RGFW->keycodes[117] = RGFW_pageDown; + _RGFW->keycodes[9] = RGFW_escape; + _RGFW->keycodes[110] = RGFW_home; + _RGFW->keycodes[78] = RGFW_scrollLock; + _RGFW->keycodes[107] = RGFW_printScreen; + _RGFW->keycodes[128] = RGFW_pause; + _RGFW->keycodes[191] = RGFW_F13; + _RGFW->keycodes[192] = RGFW_F14; + _RGFW->keycodes[193] = RGFW_F15; + _RGFW->keycodes[194] = RGFW_F16; + _RGFW->keycodes[195] = RGFW_F17; + _RGFW->keycodes[196] = RGFW_F18; + _RGFW->keycodes[197] = RGFW_F19; + _RGFW->keycodes[198] = RGFW_F20; + _RGFW->keycodes[199] = RGFW_F21; + _RGFW->keycodes[200] = RGFW_F22; + _RGFW->keycodes[201] = RGFW_F23; + _RGFW->keycodes[202] = RGFW_F24; + _RGFW->keycodes[203] = RGFW_F25; + _RGFW->keycodes[142] = RGFW_kpEqual; + _RGFW->keycodes[161] = RGFW_world1; /* non-US key #1 */ + _RGFW->keycodes[162] = RGFW_world2; /* non-US key #2 */ +} + +i32 RGFW_initPlatform(void) { +#ifdef RGFW_WAYLAND + RGFW_load_Wayland(); + i32 ret = RGFW_initPlatform_Wayland(); + if (ret == 0) { + return 0; + } else { + #ifdef RGFW_X11 + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningWayland, "Falling back to X11"); + RGFW_useWayland(0); + #else + return ret; + #endif + } +#endif +#ifdef RGFW_X11 + RGFW_load_X11(); + return RGFW_initPlatform_X11(); +#else + return 0; +#endif +} + + +void RGFW_deinitPlatform(void) { + if (_RGFW->eventWait_forceStop[0] || _RGFW->eventWait_forceStop[1]){ + close(_RGFW->eventWait_forceStop[0]); + close(_RGFW->eventWait_forceStop[1]); + } +#ifdef RGFW_WAYLAND + if (_RGFW->useWaylandBool) { + RGFW_deinitPlatform_Wayland(); + return; + } +#endif +#ifdef RGFW_X11 + RGFW_deinitPlatform_X11(); +#endif +} + +#endif /* end of wayland or X11 defines */ + + +/* + + +Start of Linux / Unix defines + + +*/ + +#ifdef RGFW_X11 +#ifdef RGFW_WAYLAND +#define RGFW_FUNC(func) func##_X11 +#else +#define RGFW_FUNC(func) func +#endif + +#include +#include + +#include /* for data limits (mainly used in drag and drop functions) */ +#include + +void RGFW_setXInstName(const char* name) { _RGFW->instName = name; } +#if !defined(RGFW_NO_X11_CURSOR) && defined(RGFW_X11) + #include +#endif + +#ifndef RGFW_NO_DPI + #include + #include +#endif + +#include +#include +#include + +#include /* for converting keycode to string */ +#include /* for hiding */ +#include +#include +#include + +#ifdef RGFW_OPENGL + #ifndef __gl_h_ + #define __gl_h_ + #define RGFW_gl_ndef + #define GLubyte unsigned char + #define GLenum unsigned int + #define GLint int + #define GLuint unsigned int + #define GLsizei int + #define GLfloat float + #define GLvoid void + #define GLbitfield unsigned int + #define GLintptr ptrdiff_t + #define GLsizeiptr ptrdiff_t + #define GLboolean unsigned char + #endif + + #include /* GLX defs, xlib.h, gl.h */ + #ifndef GLX_MESA_swap_control + #define GLX_MESA_swap_control + #endif + + #ifdef RGFW_gl_ndef + #undef __gl_h_ + #undef GLubyte + #undef GLenum + #undef GLint + #undef GLuint + #undef GLsizei + #undef GLfloat + #undef GLvoid + #undef GLbitfield + #undef GLintptr + #undef GLsizeiptr + #undef GLboolean + #endif + typedef GLXContext(*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); +#endif + +/* atoms needed for drag and drop */ +#if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) + typedef XcursorImage* (*PFN_XcursorImageCreate)(int, int); + typedef void (*PFN_XcursorImageDestroy)(XcursorImage*); + typedef Cursor(*PFN_XcursorImageLoadCursor)(Display*, const XcursorImage*); +#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_EXT_PRELOAD) + typedef void (* PFN_XSyncIntToValue)(XSyncValue*, int); + PFN_XSyncIntToValue XSyncIntToValueSRC = NULL; + #define XSyncIntToValue XSyncIntToValueSRC + + typedef Status (* PFN_XSyncSetCounter)(Display*, XSyncCounter, XSyncValue); + PFN_XSyncSetCounter XSyncSetCounterSRC = NULL; + #define XSyncSetCounter XSyncSetCounterSRC + + typedef XSyncCounter (* PFN_XSyncCreateCounter)(Display*, XSyncValue); + PFN_XSyncCreateCounter XSyncCreateCounterSRC = NULL; + #define XSyncCreateCounter XSyncCreateCounterSRC + + typedef void (* PFN_XShapeCombineMask)(Display*,Window,int,int,int,Pixmap,int); + PFN_XShapeCombineMask XShapeCombineMaskSRC; + #define XShapeCombineMask XShapeCombineMaskSRC + + typedef void (* PFN_XShapeCombineRegion)(Display*,Window,int,int,int,Region,int); + PFN_XShapeCombineRegion XShapeCombineRegionSRC; + #define XShapeCombineRegion XShapeCombineRegionSRC + void* X11XEXThandle = NULL; +#endif + +#if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) + PFN_XcursorImageLoadCursor XcursorImageLoadCursorSRC = NULL; + PFN_XcursorImageCreate XcursorImageCreateSRC = NULL; + PFN_XcursorImageDestroy XcursorImageDestroySRC = NULL; + + #define XcursorImageLoadCursor XcursorImageLoadCursorSRC + #define XcursorImageCreate XcursorImageCreateSRC + #define XcursorImageDestroy XcursorImageDestroySRC + + void* X11Cursorhandle = NULL; +#endif + +void* RGFW_getDisplay_X11(void) { return _RGFW->display; } +u64 RGFW_window_getWindow_X11(RGFW_window* win) { return (u64)win->src.window; } + +RGFWDEF RGFW_format RGFW_XImage_getFormat(XImage* image); +RGFW_format RGFW_XImage_getFormat(XImage* image) { + switch (image->bits_per_pixel) { + case 24: + if (image->red_mask == 0xFF0000 && image->green_mask == 0x00FF00 && image->blue_mask == 0x0000FF) + return RGFW_formatRGB8; + if (image->red_mask == 0x0000FF && image->green_mask == 0x00FF00 && image->blue_mask == 0xFF0000) + return RGFW_formatBGR8; + break; + case 32: + if (image->red_mask == 0x00FF0000 && image->green_mask == 0x0000FF00 && image->blue_mask == 0x000000FF) + return RGFW_formatBGRA8; + if (image->red_mask == 0x000000FF && image->green_mask == 0x0000FF00 && image->blue_mask == 0x00FF0000) + return RGFW_formatRGBA8; + if (image->red_mask == 0x0000FF00 && image->green_mask == 0x00FF0000 && image->blue_mask == 0xFF000000) + return RGFW_formatABGR8; + if (image->red_mask == 0x00FF0000 && image->green_mask == 0x0000FF00 && image->blue_mask == 0x000000FF) + return RGFW_formatARGB8; /* ambiguous without alpha */ + break; + } + return RGFW_formatARGB8; +} + +RGFW_bool RGFW_window_createSurfacePtr(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { + RGFW_ASSERT(surface != NULL); + surface->data = data; + surface->w = w; + surface->h = h; + surface->format = format; + + XWindowAttributes attrs; + if (XGetWindowAttributes(_RGFW->display, win->src.window, &attrs) == 0) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, "Failed to get window attributes."); + return RGFW_FALSE; + } + + surface->native.bitmap = XCreateImage(_RGFW->display, attrs.visual, (u32)attrs.depth, + ZPixmap, 0, NULL, (u32)surface->w, (u32)surface->h, 32, 0); + + surface->native.buffer = (u8*)RGFW_ALLOC((size_t)(w * h * 4)); + surface->native.format = RGFW_XImage_getFormat(surface->native.bitmap); + + if (surface->native.bitmap == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, "Failed to create XImage."); + return RGFW_FALSE; + } + + surface->native.format = RGFW_formatBGRA8; + return RGFW_TRUE; +} + +RGFW_bool RGFW_FUNC(RGFW_createSurfacePtr) (u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { + return RGFW_window_createSurfacePtr(_RGFW->root, data, w, h, format, surface); +} + +void RGFW_FUNC(RGFW_window_blitSurface) (RGFW_window* win, RGFW_surface* surface) { + RGFW_ASSERT(surface != NULL); + surface->native.bitmap->data = (char*)surface->native.buffer; + RGFW_copyImageData((u8*)surface->native.buffer, surface->w, RGFW_MIN(win->h, surface->h), surface->native.format, surface->data, surface->format); + + XPutImage(_RGFW->display, win->src.window, win->src.gc, surface->native.bitmap, 0, 0, 0, 0, (u32)RGFW_MIN(win->w, surface->w), (u32)RGFW_MIN(win->h, surface->h)); + surface->native.bitmap->data = NULL; + return; +} + +void RGFW_FUNC(RGFW_surface_freePtr) (RGFW_surface* surface) { + RGFW_ASSERT(surface != NULL); + RGFW_FREE(surface->native.buffer); + XDestroyImage(surface->native.bitmap); + return; +} + +#define RGFW_LOAD_ATOM(name) \ + static Atom name = 0; \ + if (name == 0) name = XInternAtom(_RGFW->display, #name, False); + +void RGFW_FUNC(RGFW_window_setBorder) (RGFW_window* win, RGFW_bool border) { + RGFW_setBit(&win->internal.flags, RGFW_windowNoBorder, !border); + RGFW_LOAD_ATOM(_MOTIF_WM_HINTS); + + struct __x11WindowHints { + unsigned long flags, functions, decorations, status; + long input_mode; + } hints; + hints.flags = 2; + hints.decorations = border; + + XChangeProperty(_RGFW->display, win->src.window, _MOTIF_WM_HINTS, _MOTIF_WM_HINTS, 32, PropModeReplace, (u8*)&hints, 5); + + if (RGFW_window_isHidden(win) == 0) { + RGFW_window_hide(win); + RGFW_window_show(win); + } +} + +void RGFW_FUNC(RGFW_releaseCursor) (RGFW_window* win) { + RGFW_UNUSED(win); + XUngrabPointer(_RGFW->display, CurrentTime); + + /* disable raw input */ + unsigned char mask[] = { 0 }; + XIEventMask em; + em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; + + XISelectEvents(_RGFW->display, XDefaultRootWindow(_RGFW->display), &em, 1); +} + +void RGFW_FUNC(RGFW_captureCursor) (RGFW_window* win) { + /* enable raw input */ + unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; + XISetMask(mask, XI_RawMotion); + + XIEventMask em; + em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; + + XISelectEvents(_RGFW->display, XDefaultRootWindow(_RGFW->display), &em, 1); + + unsigned int event_mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask; + XGrabPointer(_RGFW->display, win->src.window, False, event_mask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); + RGFW_window_moveMouse(win, win->x + (i32)(win->w / 2), win->y + (i32)(win->h / 2)); +} + +#define RGFW_LOAD_LIBRARY(x, lib) if (x == NULL) x = dlopen(lib, RTLD_LAZY | RTLD_LOCAL) +#define RGFW_PROC_DEF(proc, name) if (name##SRC == NULL && proc != NULL) { \ + void* ptr = dlsym(proc, #name); \ + if (ptr != NULL) RGFW_MEMCPY(&name##SRC, &ptr, sizeof(PFN_##name)); \ +} + +RGFWDEF void RGFW_window_getVisual(XVisualInfo* visual, RGFW_bool transparent); +void RGFW_window_getVisual(XVisualInfo* visual, RGFW_bool transparent) { + visual->visual = DefaultVisual(_RGFW->display, DefaultScreen(_RGFW->display)); + visual->depth = DefaultDepth(_RGFW->display, DefaultScreen(_RGFW->display)); + if (transparent) { + XMatchVisualInfo(_RGFW->display, DefaultScreen(_RGFW->display), 32, TrueColor, visual); /*!< for RGBA backgrounds */ + if (visual->depth != 32) + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, "Failed to load a 32-bit depth."); + } +} + +RGFWDEF int RGFW_XErrorHandler(Display* display, XErrorEvent* ev); +int RGFW_XErrorHandler(Display* display, XErrorEvent* ev) { + char errorText[512]; + XGetErrorText(display, ev->error_code, errorText, sizeof(errorText)); + + char buf[1024]; + RGFW_SNPRINTF(buf, sizeof(buf), "[X Error] %s\n Error code: %d\n Request code: %d\n Minor code: %d\n Serial: %lu\n", + errorText, + ev->error_code, ev->request_code, ev->minor_code, ev->serial); + + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errX11, buf); + _RGFW->x11Error = ev; + return 0; +} + +void RGFW_XCreateWindow (XVisualInfo visual, const char* name, RGFW_windowFlags flags, RGFW_window* win) { + i64 event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | FocusChangeMask | + LeaveWindowMask | EnterWindowMask | ExposureMask | VisibilityChangeMask | PropertyChangeMask; + + /* make X window attrubutes */ + XSetWindowAttributes swa; + RGFW_MEMSET(&swa, 0, sizeof(swa)); + + win->src.parent = DefaultRootWindow(_RGFW->display); + + Colormap cmap; + swa.colormap = cmap = XCreateColormap(_RGFW->display, + win->src.parent, + visual.visual, AllocNone); + swa.event_mask = event_mask; + swa.background_pixmap = None; + + /* create the window */ + win->src.window = XCreateWindow(_RGFW->display, win->src.parent, win->x, win->y, (u32)win->w, (u32)win->h, + 0, visual.depth, InputOutput, visual.visual, + CWBorderPixel | CWColormap | CWEventMask, &swa); + + XFreeColors(_RGFW->display, cmap, NULL, 0, 0); + + XSaveContext(_RGFW->display, win->src.window, _RGFW->context, (XPointer)win); + + win->src.gc = XCreateGC(_RGFW->display, win->src.window, 0, NULL); + + /* In your .desktop app, if you set the property + StartupWMClass=RGFW that will assoicate the launcher icon + with your application - robrohan */ + if (_RGFW->className == NULL) + _RGFW->className = (char*)name; + + XClassHint hint; + hint.res_class = (char*)_RGFW->className; + if (_RGFW->instName == NULL) hint.res_name = (char*)name; + else hint.res_name = (char*)_RGFW->instName; + XSetClassHint(_RGFW->display, win->src.window, &hint); + + #ifndef RGFW_NO_MONITOR + if (flags & RGFW_windowScaleToMonitor) + RGFW_window_scaleToMonitor(win); + #endif + XSelectInput(_RGFW->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 */ + RGFW_LOAD_ATOM(WM_DELETE_WINDOW); + XSetWMProtocols(_RGFW->display, (Drawable) win->src.window, &WM_DELETE_WINDOW, 1); + /* set the background */ + RGFW_window_setName(win, name); + + XMoveWindow(_RGFW->display, (Drawable) win->src.window, win->x, win->y); /*!< move the window to it's proper cords */ + + if (flags & RGFW_windowAllowDND) { /* init drag and drop atoms and turn on drag and drop for this window */ + win->internal.flags |= RGFW_windowAllowDND; + + /* actions */ + Atom XdndAware = XInternAtom(_RGFW->display, "XdndAware", False); + const u8 version = 5; + + XChangeProperty(_RGFW->display, win->src.window, + XdndAware, 4, 32, + PropModeReplace, &version, 1); /*!< turns on drag and drop */ + } + +#ifdef RGFW_ADVANCED_SMOOTH_RESIZE + RGFW_LOAD_ATOM(_NET_WM_SYNC_REQUEST_COUNTER) + RGFW_LOAD_ATOM(_NET_WM_SYNC_REQUEST) + + Atom protcols[2] = {_NET_WM_SYNC_REQUEST, WM_DELETE_WINDOW}; + XSetWMProtocols(_RGFW->display, win->src.window, protcols, 2); + + XSyncValue initial_value; + XSyncIntToValue(&initial_value, 0); + win->src.counter = XSyncCreateCounter(_RGFW->display, initial_value); + + XChangeProperty(_RGFW->display, win->src.window, _NET_WM_SYNC_REQUEST_COUNTER, XA_CARDINAL, 32, PropModeReplace, (uint8_t*)&win->src.counter, 1); +#endif + + win->src.x = win->x; + win->src.y = win->y; + win->src.w = win->w; + win->src.h = win->h; + + XSetWindowBackground(_RGFW->display, win->src.window, None); + XClearWindow(_RGFW->display, win->src.window); + + /* stupid hack to make resizing the window less bad */ + XSetWindowBackgroundPixmap(_RGFW->display, win->src.window, None); +} + +RGFW_window* RGFW_FUNC(RGFW_createWindowPlatform) (const char* name, RGFW_windowFlags flags, RGFW_window* win) { + if ((flags & RGFW_windowOpenGL) || (flags & RGFW_windowEGL)) { + win->src.window = 0; + return win; + } + + XVisualInfo visual; + RGFW_window_getVisual(&visual, RGFW_BOOL(win->internal.flags & RGFW_windowTransparent)); + RGFW_XCreateWindow(visual, name, flags, win); + return win; /*return newly created window */ +} + +RGFW_bool RGFW_FUNC(RGFW_getGlobalMouse) (i32* fX, i32* fY) { + RGFW_init(); + i32 x, y; + u32 z; + Window window1, window2; + XQueryPointer(_RGFW->display, XDefaultRootWindow(_RGFW->display), &window1, &window2, fX, fY, &x, &y, &z); + return RGFW_TRUE; +} + +RGFWDEF void RGFW_XHandleClipboardSelection(XEvent* event); +void RGFW_XHandleClipboardSelection(XEvent* event) { RGFW_UNUSED(event); + RGFW_LOAD_ATOM(ATOM_PAIR); + RGFW_LOAD_ATOM(MULTIPLE); + RGFW_LOAD_ATOM(TARGETS); + RGFW_LOAD_ATOM(SAVE_TARGETS); + RGFW_LOAD_ATOM(UTF8_STRING); + + const XSelectionRequestEvent* request = &event->xselectionrequest; + Atom formats[2] = {0}; + formats[0] = UTF8_STRING; + formats[1] = XA_STRING; + const int formatCount = sizeof(formats) / sizeof(formats[0]); + + if (request->target == TARGETS) { + Atom targets[4] = {0}; + targets[0] = TARGETS; + targets[1] = MULTIPLE; + targets[2] = UTF8_STRING; + targets[3] = XA_STRING; + + XChangeProperty(_RGFW->display, request->requestor, request->property, + XA_ATOM, 32, PropModeReplace, (u8*) targets, sizeof(targets) / sizeof(Atom)); + } else if (request->target == MULTIPLE) { + Atom* targets = NULL; + + Atom actualType = 0; + int actualFormat = 0; + unsigned long count = 0, bytesAfter = 0; + + XGetWindowProperty(_RGFW->display, request->requestor, request->property, 0, LONG_MAX, + False, ATOM_PAIR, &actualType, &actualFormat, &count, &bytesAfter, (u8**) &targets); + + unsigned long i; + for (i = 0; i < (u32)count; i += 2) { + if (targets[i] == UTF8_STRING || targets[i] == XA_STRING) + XChangeProperty(_RGFW->display, request->requestor, targets[i + 1], targets[i], + 8, PropModeReplace, (const unsigned char *)_RGFW->clipboard, (i32)_RGFW->clipboard_len); + else + targets[i + 1] = None; + } + + XChangeProperty(_RGFW->display, + request->requestor, request->property, ATOM_PAIR, 32, + PropModeReplace, (u8*) targets, (i32)count); + + XFlush(_RGFW->display); + XFree(targets); + } else if (request->target == SAVE_TARGETS) + XChangeProperty(_RGFW->display, request->requestor, request->property, 0, 32, PropModeReplace, NULL, 0); + else { + int i; + for (i = 0; i < formatCount; i++) { + if (request->target != formats[i]) + continue; + XChangeProperty(_RGFW->display, request->requestor, request->property, request->target, + 8, PropModeReplace, (u8*) _RGFW->clipboard, (i32)_RGFW->clipboard_len); + } + } + + XEvent reply = { SelectionNotify }; + reply.xselection.property = request->property; + reply.xselection.display = request->display; + reply.xselection.requestor = request->requestor; + reply.xselection.selection = request->selection; + reply.xselection.target = request->target; + reply.xselection.time = request->time; + + XSendEvent(_RGFW->display, request->requestor, False, 0, &reply); + XFlush(_RGFW->display); +} + i32 RGFW_XHandleClipboardSelectionHelper(void); - -u8 RGFW_rgfwToKeyChar(u32 key) { +u8 RGFW_FUNC(RGFW_rgfwToKeyChar) (u32 key) { u32 keycode = RGFW_rgfwToApiKey(key); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - Window root = DefaultRootWindow(_RGFW.display); + + Window root = DefaultRootWindow(_RGFW->display); Window ret_root, ret_child; int root_x, root_y, win_x, win_y; unsigned int mask; - XQueryPointer(_RGFW.display, root, &ret_root, &ret_child, &root_x, &root_y, &win_x, &win_y, &mask); - KeySym sym = (KeySym)XkbKeycodeToKeysym(_RGFW.display, (KeyCode)keycode, 0, (KeyCode)mask & ShiftMask ? 1 : 0); + XQueryPointer(_RGFW->display, root, &ret_root, &ret_child, &root_x, &root_y, &win_x, &win_y, &mask); + KeySym sym = (KeySym)XkbKeycodeToKeysym(_RGFW->display, (KeyCode)keycode, 0, (KeyCode)mask & ShiftMask ? 1 : 0); if ((mask & LockMask) && sym >= XK_a && sym <= XK_z) sym = (mask & ShiftMask) ? sym + 32 : sym - 32; @@ -4516,25 +5561,10 @@ u8 RGFW_rgfwToKeyChar(u32 key) { sym = 0; return (u8)sym; -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL RGFW_UNUSED(keycode); - return (u8)key; -#endif } -RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { - RGFW_XHandleClipboardSelectionHelper(); - - if (win == NULL || ((win->_flags & RGFW_windowFreeOnClose) && (win->_flags & RGFW_EVENT_QUIT))) return NULL; - RGFW_event* ev = RGFW_window_checkEventCore(win); - if (ev) return ev; - - #if defined(__linux__) && !defined(RGFW_NO_LINUX) - if (RGFW_linux_updateGamepad(win)) return &win->event; - #endif - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 +RGFWDEF void RGFW_XHandleEvent(void); +void RGFW_XHandleEvent(void) { RGFW_LOAD_ATOM(XdndTypeList); RGFW_LOAD_ATOM(XdndSelection); RGFW_LOAD_ATOM(XdndEnter); @@ -4546,20 +5576,8 @@ RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { RGFW_LOAD_ATOM(XdndActionCopy); RGFW_LOAD_ATOM(_NET_WM_SYNC_REQUEST); RGFW_LOAD_ATOM(WM_PROTOCOLS); - XPending(win->src.display); - - XEvent E; /*!< raw X11 event */ - - /* if there is no unread qued events, get a new one */ - if ((QLength(win->src.display) || XEventsQueued(win->src.display, QueuedAlready) + XEventsQueued(win->src.display, QueuedAfterReading)) - && win->event.type != RGFW_quit - ) - XNextEvent(win->src.display, &E); - else { - return NULL; - } - - win->event.type = 0; + RGFW_LOAD_ATOM(WM_STATE); + RGFW_LOAD_ATOM(_NET_WM_STATE); /* xdnd data */ static Window source = 0; @@ -4567,550 +5585,641 @@ RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { static i32 format = 0; XEvent reply = { ClientMessage }; + XEvent E; + RGFW_event event; + RGFW_MEMSET(&event, 0, sizeof(event)); + XNextEvent(_RGFW->display, &E); switch (E.type) { - case KeyPress: - case KeyRelease: { - win->event.repeat = RGFW_FALSE; - /* check if it's a real key release */ - if (E.type == KeyRelease && XEventsQueued(win->src.display, QueuedAfterReading)) { /* get next event if there is one */ - XEvent NE; - XPeekEvent(win->src.display, &NE); + case SelectionRequest: + RGFW_XHandleClipboardSelection(&E); + return; + case GenericEvent: { + RGFW_window* win = _RGFW->mouseOwner; + if (win == NULL) return; + if (!(win->internal.enabledEvents & RGFW_BIT(RGFW_mousePosChanged))) return; - if (E.xkey.time == NE.xkey.time && E.xkey.keycode == NE.xkey.keycode) /* check if the current and next are both the same */ - win->event.repeat = RGFW_TRUE; - } - - /* set event key data */ - win->event.key = (u8)RGFW_apiKeyToRGFW(E.xkey.keycode); - win->event.keyChar = (u8)RGFW_rgfwToKeyChar(win->event.key); - - RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; - - /* get keystate data */ - win->event.type = (E.type == KeyPress) ? RGFW_keyPressed : RGFW_keyReleased; - - XKeyboardState keystate; - XGetKeyboardControl(win->src.display, &keystate); - - RGFW_keyboard[win->event.key].current = (E.type == KeyPress); - - XkbStateRec state; - XkbGetState(win->src.display, XkbUseCoreKbd, &state); - RGFW_updateKeyMods(win, (state.locked_mods & LockMask), (state.locked_mods & Mod2Mask), (state.locked_mods & Mod3Mask)); - - RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, (E.type == KeyPress)); - break; - } - case ButtonPress: - case ButtonRelease: - if (E.xbutton.button > RGFW_mouseFinal) { /* skip this event */ - XFlush(win->src.display); - return RGFW_window_checkEvent(win); - } - - win->event.type = RGFW_mouseButtonPressed + (E.type == ButtonRelease); /* the events match */ - win->event.button = (u8)(E.xbutton.button - 1); - switch(win->event.button) { - case RGFW_mouseScrollUp: - win->event.scroll = 1; - break; - case RGFW_mouseScrollDown: - win->event.scroll = -1; - break; - default: break; - } - - RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; - - if (win->event.repeat == RGFW_FALSE) - win->event.repeat = RGFW_isPressed(win, win->event.key); - - 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; - - win->event.vector.x = win->event.point.x - win->_lastMousePoint.x; - win->event.vector.y = win->event.point.y - win->_lastMousePoint.y; - win->_lastMousePoint = win->event.point; - - win->event.type = RGFW_mousePosChanged; - RGFW_mousePosCallback(win, win->event.point, win->event.vector); - break; - - case GenericEvent: { - /* MotionNotify is used for mouse events if the mouse isn't held */ - if (!(win->_flags & 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; + /* MotionNotify is used for mouse events if the mouse isn't held */ + if (!(win->internal.holdMouse)) { + XFreeEventData(_RGFW->display, &E.xcookie); + return; } - 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.vector = RGFW_POINT((i32)deltaX, (i32)deltaY); - win->event.point.x = win->_lastMousePoint.x + win->event.vector.x; - win->event.point.y = win->_lastMousePoint.y + win->event.vector.y; - win->_lastMousePoint = win->event.point; - - RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); - - win->event.type = RGFW_mousePosChanged; - RGFW_mousePosCallback(win, win->event.point, win->event.vector); - } - - XFreeEventData(win->src.display, &E.xcookie); - break; - } - - case Expose: { - win->event.type = RGFW_windowRefresh; - RGFW_windowRefreshCallback(win); - -#ifdef RGFW_ADVANCED_SMOOTH_RESIZE - XSyncValue value; - XSyncIntToValue(&value, (i32)win->src.counter_value); - XSyncSetCounter(win->src.display, win->src.counter, value); -#endif - break; - } - case MapNotify: case UnmapNotify: RGFW_window_checkMode(win); break; - case ClientMessage: { - /* if the client closed the window */ - if (E.xclient.data.l[0] == (long)wm_delete_window) { - win->event.type = RGFW_quit; - RGFW_window_setShouldClose(win, RGFW_TRUE); - RGFW_windowQuitCallback(win); - break; - } -#ifdef RGFW_ADVANCED_SMOOTH_RESIZE - if (E.xclient.message_type == WM_PROTOCOLS && (Atom)E.xclient.data.l[0] == _NET_WM_SYNC_REQUEST) { - RGFW_windowRefreshCallback(win); - win->src.counter_value = 0; - win->src.counter_value |= E.xclient.data.l[2]; - win->src.counter_value |= (E.xclient.data.l[3] << 32); - - XSyncValue value; - XSyncIntToValue(&value, (i32)win->src.counter_value); - XSyncSetCounter(win->src.display, win->src.counter, value); - break; - } -#endif - if ((win->_flags & RGFW_windowAllowDND) == 0) - break; - - reply.xclient.window = source; - reply.xclient.format = 32; - reply.xclient.data.l[0] = (long)win->src.window; - reply.xclient.data.l[1] = 0; - reply.xclient.data.l[2] = None; - - if (E.xclient.message_type == XdndEnter) { - if (version > 5) - break; - - unsigned long count; - Atom* formats; - Atom real_formats[6]; - Bool list = E.xclient.data.l[1] & 1; - - source = (unsigned long int)E.xclient.data.l[0]; - version = E.xclient.data.l[1] >> 24; - format = None; - if (list) { - Atom actualType; - i32 actualFormat; - unsigned long bytesAfter; - - XGetWindowProperty( - win->src.display, source, XdndTypeList, - 0, LONG_MAX, False, 4, - &actualType, &actualFormat, &count, &bytesAfter, (u8**)&formats - ); - } else { - count = 0; - - size_t i; - for (i = 2; i < 5; i++) { - if (E.xclient.data.l[i] != None) { - real_formats[count] = (unsigned long int)E.xclient.data.l[i]; - count += 1; - } + XGetEventData(_RGFW->display, &E.xcookie); + if (E.xcookie.evtype == XI_RawMotion) { + XIRawEvent *raw = (XIRawEvent *)E.xcookie.data; + if (raw->valuators.mask_len == 0) { + XFreeEventData(_RGFW->display, &E.xcookie); + return; } - formats = real_formats; + 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]; + + event.mouse.vecX = (float)deltaX; + event.mouse.vecY = (float)deltaY; + _RGFW->vectorX = (float)event.mouse.vecX; + _RGFW->vectorY = (float)event.mouse.vecY; + event.mouse.x = win->internal.lastMouseX + (i32)event.mouse.vecX; + event.mouse.y = win->internal.lastMouseY + (i32)event.mouse.vecY; + win->internal.lastMouseX = event.mouse.x; + win->internal.lastMouseY = event.mouse.y; + RGFW_window_moveMouse(win, win->x + (win->w / 2), win->y + (win->h / 2)); + + event.type = RGFW_mousePosChanged; + RGFW_mousePosCallback(win, event.mouse.x, event.mouse.y, (float)event.mouse.vecX, (float)event.mouse.vecY); } - size_t i; - for (i = 0; i < count; i++) { - if (formats[i] == XtextUriList || formats[i] == XtextPlain) { - format = (int)formats[i]; + XFreeEventData(_RGFW->display, &E.xcookie); + if (event.type) + RGFW_eventQueuePush(&event); + return; + } + } + + RGFW_window* win = NULL; + if (XFindContext(_RGFW->display, E.xany.window, _RGFW->context, (XPointer*) &win) != 0) { + return; + } + + event.common.win = win; + + /* + Repeated key presses are sent as a release followed by another press at the same time. + We want to convert that into a single key press event with the repeat flag set + */ + if (E.type == KeyRelease && XEventsQueued(_RGFW->display, QueuedAfterReading)) { + XEvent NE; + XPeekEvent(_RGFW->display, &NE); + if (NE.type == KeyPress && E.xkey.time == NE.xkey.time && E.xkey.keycode == NE.xkey.keycode) { + /* Use the next KeyPress event */ + XNextEvent(_RGFW->display, &E); + event.key.repeat = RGFW_TRUE; + } + } + + switch (E.type) { + case KeyPress: { + if (!(win->internal.enabledEvents & RGFW_keyPressedFlag)) return; + event.type = RGFW_keyPressed; + event.key.value = (u8)RGFW_apiKeyToRGFW(E.xkey.keycode); + event.key.sym = (u8)RGFW_rgfwToKeyChar(event.key.value); + + _RGFW->keyboard[event.key.value].prev = _RGFW->keyboard[event.key.value].current; + _RGFW->keyboard[event.key.value].current = RGFW_TRUE; + + XkbStateRec state; + XkbGetState(_RGFW->display, XkbUseCoreKbd, &state); + RGFW_updateKeyMods(win, (state.locked_mods & LockMask), (state.locked_mods & Mod2Mask), (state.locked_mods & Mod3Mask)); + + RGFW_keyCallback(win, event.key.value, event.key.sym, win->internal.mod, event.key.repeat, RGFW_TRUE); + break; + } + case KeyRelease: { + if (!(win->internal.enabledEvents & RGFW_keyReleasedFlag)) return; + + event.type = RGFW_keyReleased; + event.key.value = (u8)RGFW_apiKeyToRGFW(E.xkey.keycode); + event.key.sym = (u8)RGFW_rgfwToKeyChar(event.key.value); + + /* get keystate data */ + _RGFW->keyboard[event.key.value].prev = _RGFW->keyboard[event.key.value].current; + _RGFW->keyboard[event.key.value].current = RGFW_FALSE; + + XkbStateRec state; + XkbGetState(_RGFW->display, XkbUseCoreKbd, &state); + RGFW_updateKeyMods(win, (state.locked_mods & LockMask), (state.locked_mods & Mod2Mask), (state.locked_mods & Mod3Mask)); + + RGFW_keyCallback(win, event.key.value, event.key.sym, win->internal.mod, event.key.repeat, RGFW_FALSE); + break; + } + case ButtonPress: + if (E.xbutton.button >= Button4 && E.xbutton.button <= 7) { + if (!(win->internal.enabledEvents & RGFW_mouseScrollFlag)) return; + event.type = RGFW_mouseScroll; + } else { + if (!(win->internal.enabledEvents & RGFW_mouseButtonPressedFlag) || E.xbutton.button > RGFW_mouseFinal) return; + event.type = RGFW_mouseButtonPressed; + } + + switch(E.xbutton.button) { + case Button1: event.button.value = RGFW_mouseLeft; break; + case Button2: event.button.value = RGFW_mouseMiddle; break; + case Button3: event.button.value = RGFW_mouseRight; break; + case Button4: event.scroll.y = 1.0; break; + case Button5: event.scroll.y = -1.0; break; + case 6: event.scroll.x = 1.0f; break; + case 7: event.scroll.x = -1.0f; break; + default: + event.button.value = (u8)E.xbutton.button - Button1 - 4; + break; + } + + if (event.type == RGFW_mouseScroll) { + _RGFW->scrollX = event.scroll.x; + _RGFW->scrollY = event.scroll.y; + RGFW_mouseScrollCallback(win, event.scroll.x, event.scroll.y); + break; + } + + _RGFW->mouseButtons[event.button.value].prev = _RGFW->mouseButtons[event.button.value].current; + _RGFW->mouseButtons[event.button.value].current = RGFW_TRUE; + RGFW_mouseButtonCallback(win, event.button.value, RGFW_TRUE); + break; + case ButtonRelease: + if (E.xbutton.button >= Button4 && E.xbutton.button <= 7) break; + if (!(win->internal.enabledEvents & RGFW_mouseButtonReleasedFlag) || E.xbutton.button > RGFW_mouseFinal) return; + event.type = RGFW_mouseButtonReleased; + switch(E.xbutton.button) { + case Button1: event.button.value = RGFW_mouseLeft; break; + case Button2: event.button.value = RGFW_mouseMiddle; break; + case Button3: event.button.value = RGFW_mouseRight; break; + default: + event.button.value = (u8)E.xbutton.button - Button1 - 4; + break; + } + + _RGFW->mouseButtons[event.button.value].prev = _RGFW->mouseButtons[event.button.value].current; + _RGFW->mouseButtons[event.button.value].current = RGFW_FALSE; + RGFW_mouseButtonCallback(win, event.button.value, RGFW_FALSE); + break; + case MotionNotify: + if (win->internal.holdMouse) return; + if (!(win->internal.enabledEvents & RGFW_mousePosChangedFlag)) return; + event.mouse.x = E.xmotion.x; + event.mouse.y = E.xmotion.y; + + event.mouse.vecX = (float)(event.mouse.x - win->internal.lastMouseX); + event.mouse.vecY = (float)(event.mouse.y - win->internal.lastMouseY); + _RGFW->vectorX = event.mouse.vecX; + _RGFW->vectorY = event.mouse.vecY; + win->internal.lastMouseX = event.mouse.x; + win->internal.lastMouseY = event.mouse.y; + event.type = RGFW_mousePosChanged; + RGFW_mousePosCallback(win, event.mouse.x, event.mouse.y, (float)event.mouse.vecX, (float)event.mouse.vecY); + break; + + case Expose: { + if (!(win->internal.enabledEvents & RGFW_windowRefreshFlag)) return; + event.type = RGFW_windowRefresh; + RGFW_windowRefreshCallback(win); + +#ifdef RGFW_ADVANCED_SMOOTH_RESIZE + XSyncValue value; + XSyncIntToValue(&value, (i32)win->src.counter_value); + XSyncSetCounter(_RGFW->display, win->src.counter, value); +#endif + break; + } + + case PropertyNotify: + if (E.xproperty.state != PropertyNewValue) break; + + if (E.xproperty.atom == WM_STATE) { + if (RGFW_window_isMinimized(win) && !(win->internal.flags & RGFW_windowMinimized)) { + win->internal.flags |= RGFW_windowMinimize; + RGFW_eventQueuePushEx(e.type = RGFW_windowMinimized; e.common.win = win); + RGFW_windowMinimizedCallback(win); + break; + } + } else if (E.xproperty.atom == _NET_WM_STATE) { + if (!(win->internal.flags & RGFW_windowMaximize)) { + win->internal.flags |= RGFW_windowMaximize; + RGFW_eventQueuePushEx(e.type = RGFW_windowMaximized; e.common.win = win); + RGFW_windowMaximizedCallback(win, win->x, win->y, win->w, win->h); break; } } - if (list) { - XFree(formats); + RGFW_window_checkMode(win); + break; + case MapNotify: case UnmapNotify: RGFW_window_checkMode(win); break; + case ClientMessage: { + RGFW_LOAD_ATOM(WM_DELETE_WINDOW); + /* if the client closed the window */ + if (E.xclient.data.l[0] == (long)WM_DELETE_WINDOW) { + event.type = RGFW_quit; + RGFW_window_setShouldClose(win, RGFW_TRUE); + RGFW_windowQuitCallback(win); + break; + } +#ifdef RGFW_ADVANCED_SMOOTH_RESIZE + if (E.xclient.message_type == WM_PROTOCOLS && (Atom)E.xclient.data.l[0] == _NET_WM_SYNC_REQUEST) { + RGFW_windowRefreshCallback(win); + win->src.counter_value = 0; + win->src.counter_value |= E.xclient.data.l[2]; + win->src.counter_value |= (E.xclient.data.l[3] << 32); + + XSyncValue value; + XSyncIntToValue(&value, (i32)win->src.counter_value); + XSyncSetCounter(_RGFW->display, win->src.counter, value); + break; + } +#endif + if ((win->internal.flags & RGFW_windowAllowDND) == 0) + return; + + reply.xclient.window = source; + reply.xclient.format = 32; + reply.xclient.data.l[0] = (long)win->src.window; + reply.xclient.data.l[1] = 0; + reply.xclient.data.l[2] = None; + + if (E.xclient.message_type == XdndEnter) { + if (version > 5) + break; + + unsigned long count; + Atom* formats; + Atom real_formats[6]; + Bool list = E.xclient.data.l[1] & 1; + + source = (unsigned long int)E.xclient.data.l[0]; + version = E.xclient.data.l[1] >> 24; + format = None; + if (list) { + Atom actualType; + i32 actualFormat; + unsigned long bytesAfter; + + XGetWindowProperty( + _RGFW->display, source, XdndTypeList, + 0, LONG_MAX, False, 4, + &actualType, &actualFormat, &count, &bytesAfter, (u8**)&formats + ); + } else { + count = 0; + + size_t i; + for (i = 2; i < 5; i++) { + if (E.xclient.data.l[i] != None) { + real_formats[count] = (unsigned long int)E.xclient.data.l[i]; + count += 1; + } + } + + formats = real_formats; + } + + Atom XtextPlain = XInternAtom(_RGFW->display, "text/plain", False); + Atom XtextUriList = XInternAtom(_RGFW->display, "text/uri-list", False); + + size_t i; + for (i = 0; i < count; i++) { + if (formats[i] == XtextUriList || formats[i] == XtextPlain) { + format = (int)formats[i]; + break; + } + } + + if (list) { + XFree(formats); + } + + break; } - break; - } + if (E.xclient.message_type == XdndPosition) { + const i32 xabs = (E.xclient.data.l[2] >> 16) & 0xffff; + const i32 yabs = (E.xclient.data.l[2]) & 0xffff; + Window dummy; + i32 xpos, ypos; - if (E.xclient.message_type == XdndPosition) { - const i32 xabs = (E.xclient.data.l[2] >> 16) & 0xffff; - const i32 yabs = (E.xclient.data.l[2]) & 0xffff; - Window dummy; - i32 xpos, ypos; + if (version > 5) + break; + + XTranslateCoordinates( + _RGFW->display, XDefaultRootWindow(_RGFW->display), win->src.window, + xabs, yabs, &xpos, &ypos, &dummy + ); + + event.drag.x = xpos; + event.drag.y = ypos; + + reply.xclient.window = source; + reply.xclient.message_type = XdndStatus; + + if (format) { + reply.xclient.data.l[1] = 1; + if (version >= 2) + reply.xclient.data.l[4] = (long)XdndActionCopy; + } + + XSendEvent(_RGFW->display, source, False, NoEventMask, &reply); + XFlush(_RGFW->display); + break; + } + if (E.xclient.message_type != XdndDrop) + break; if (version > 5) break; - XTranslateCoordinates( - win->src.display, XDefaultRootWindow(win->src.display), win->src.window, - xabs, yabs, &xpos, &ypos, &dummy - ); - - win->event.point.x = xpos; - win->event.point.y = ypos; - - reply.xclient.window = source; - reply.xclient.message_type = XdndStatus; + event.type = RGFW_dataDrag; if (format) { - reply.xclient.data.l[1] = 1; - if (version >= 2) - reply.xclient.data.l[4] = (long)XdndActionCopy; + Time time = (version >= 1) + ? (Time)E.xclient.data.l[2] + : CurrentTime; + + XConvertSelection( + _RGFW->display, XdndSelection, (Atom)format, + XdndSelection, win->src.window, time + ); + } else if (version >= 2) { + XEvent new_reply = { ClientMessage }; + + XSendEvent(_RGFW->display, source, False, NoEventMask, &new_reply); + XFlush(_RGFW->display); } - XSendEvent(win->src.display, source, False, NoEventMask, &reply); - XFlush(win->src.display); - break; - } - if (E.xclient.message_type != XdndDrop) - break; + _RGFW->windowState.win = win; + _RGFW->windowState.dataDragging = RGFW_TRUE; + _RGFW->windowState.dropX = event.drag.x; + _RGFW->windowState.dropY = event.drag.y; - if (version > 5) - break; + if (win->internal.enabledEvents & RGFW_dataDragFlag) return; + RGFW_dataDragCallback(win, event.drag.x, event.drag.y); + } break; + case SelectionNotify: { + /* this is only for checking for xdnd drops */ + if (!(win->internal.enabledEvents & RGFW_dataDropFlag) || E.xselection.property != XdndSelection || !(win->internal.flags & RGFW_windowAllowDND)) + return; + char* data; + unsigned long result; - size_t i; - for (i = 0; i < win->event.droppedFilesCount; i++) - win->event.droppedFiles[i][0] = '\0'; + Atom actualType; + i32 actualFormat; + unsigned long bytesAfter; - win->event.droppedFilesCount = 0; + XGetWindowProperty(_RGFW->display, E.xselection.requestor, E.xselection.property, 0, LONG_MAX, False, E.xselection.target, &actualType, &actualFormat, &result, &bytesAfter, (u8**) &data); + if (result == 0) + break; - win->event.type = RGFW_DNDInit; + const char* prefix = (const char*)"file://"; - if (format) { - Time time = (version >= 1) - ? (Time)E.xclient.data.l[2] - : CurrentTime; + char* line; - XConvertSelection( - win->src.display, XdndSelection, (Atom)format, - XdndSelection, win->src.window, time - ); - } else if (version >= 2) { - XEvent new_reply = { ClientMessage }; + event.drop.files = _RGFW->files; + event.drop.count = 0; + event.type = RGFW_dataDrop; - XSendEvent(win->src.display, source, False, NoEventMask, &new_reply); - XFlush(win->src.display); - } + while ((line = (char*)RGFW_strtok(data, "\r\n"))) { + char path[RGFW_MAX_PATH]; - RGFW_dndInitCallback(win, win->event.point); - } break; - case SelectionRequest: - RGFW_XHandleClipboardSelection(&E); - XFlush(win->src.display); - return RGFW_window_checkEvent(win); - case SelectionNotify: { - /* this is only for checking for xdnd drops */ - if (E.xselection.property != XdndSelection || !(win->_flags & RGFW_windowAllowDND)) - break; - char* data; - unsigned long result; + data = NULL; - Atom actualType; - i32 actualFormat; - unsigned long bytesAfter; + if (line[0] == '#') + continue; - XGetWindowProperty(win->src.display, E.xselection.requestor, E.xselection.property, 0, LONG_MAX, False, E.xselection.target, &actualType, &actualFormat, &result, &bytesAfter, (u8**) &data); + char* l; + for (l = line; 1; l++) { + if ((l - line) > 7) + break; + else if (*l != prefix[(l - line)]) + break; + else if (*l == '\0' && prefix[(l - line)] == '\0') { + line += 7; + while (*line != '/') + line++; + break; + } else if (*l == '\0') + break; + } - if (result == 0) - break; + event.drop.count++; - const char* prefix = (const char*)"file://"; - - char* line; - - win->event.droppedFilesCount = 0; - win->event.type = RGFW_DND; - - while ((line = (char*)RGFW_strtok(data, "\r\n"))) { - char path[RGFW_MAX_PATH]; - - data = NULL; - - if (line[0] == '#') - continue; - - char* l; - for (l = line; 1; l++) { - if ((l - line) > 7) - break; - else if (*l != prefix[(l - line)]) - break; - else if (*l == '\0' && prefix[(l - line)] == '\0') { - line += 7; - while (*line != '/') - line++; - break; - } else if (*l == '\0') - break; - } - - win->event.droppedFilesCount++; - - size_t index = 0; - while (*line) { - if (line[0] == '%' && line[1] && line[2]) { - const char digits[3] = { line[1], line[2], '\0' }; - path[index] = (char) RGFW_STRTOL(digits, NULL, 16); - line += 2; - } else + size_t index = 0; + while (*line) { + if (line[0] == '%' && line[1] && line[2]) { + char digits[3] = {0}; + digits[0] = line[1]; + digits[1] = line[2]; + digits[2] = '\0'; + path[index] = (char) RGFW_STRTOL(digits, NULL, 16); + line += 2; + } else path[index] = *line; - index++; - line++; + index++; + line++; + } + path[index] = '\0'; + RGFW_MEMCPY(event.drop.files[event.drop.count - 1], path, index + 1); } - path[index] = '\0'; - RGFW_MEMCPY(win->event.droppedFiles[win->event.droppedFilesCount - 1], path, index + 1); + + _RGFW->windowState.win = win; + _RGFW->windowState.dataDrop = RGFW_TRUE; + _RGFW->windowState.filesCount = event.drop.count; + + RGFW_dataDropCallback(win, event.drop.files, event.drop.count); + if (data) + XFree(data); + + if (version >= 2) { + XEvent new_reply = { ClientMessage }; + new_reply.xclient.window = source; + new_reply.xclient.message_type = XdndFinished; + new_reply.xclient.format = 32; + new_reply.xclient.data.l[1] = (long int)result; + new_reply.xclient.data.l[2] = (long int)XdndActionCopy; + XSendEvent(_RGFW->display, source, False, NoEventMask, &new_reply); + XFlush(_RGFW->display); + } + break; } + case FocusIn: + if ((win->internal.flags & RGFW_windowFullscreen)) + XMapRaised(_RGFW->display, win->src.window); + if ((win->internal.holdMouse)) RGFW_window_holdMouse(win); - RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); - if (data) - XFree(data); + if (!(win->internal.enabledEvents & RGFW_focusInFlag)) return; + win->internal.inFocus = RGFW_TRUE; + event.type = RGFW_focusIn; + RGFW_focusCallback(win, 1); - if (version >= 2) { - XEvent new_reply = { ClientMessage }; - new_reply.xclient.window = source; - new_reply.xclient.message_type = XdndFinished; - new_reply.xclient.format = 32; - new_reply.xclient.data.l[1] = (long int)result; - new_reply.xclient.data.l[2] = (long int)XdndActionCopy; - XSendEvent(win->src.display, source, False, NoEventMask, &new_reply); - XFlush(win->src.display); - } - break; - } - case FocusIn: - if ((win->_flags & RGFW_windowFullscreen)) - XMapRaised(win->src.display, win->src.window); + break; + case FocusOut: + if (!(win->internal.enabledEvents & RGFW_focusOutFlag)) return; + event.type = RGFW_focusOut; + RGFW_focusCallback(win, 0); + RGFW_window_focusLost(win); + break; + case EnterNotify: { + win->internal.mouseInside = RGFW_TRUE; + _RGFW->windowState.win = win; + _RGFW->windowState.mouseEnter = RGFW_TRUE; - win->_flags |= RGFW_windowFocus; - win->event.type = RGFW_focusIn; - RGFW_focusCallback(win, 1); - - - if ((win->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(win, RGFW_AREA(win->r.w, win->r.h)); - break; - case FocusOut: - win->event.type = RGFW_focusOut; - RGFW_focusCallback(win, 0); - RGFW_window_focusLost(win); - break; - case PropertyNotify: RGFW_window_checkMode(win); 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 */ - RGFW_window_checkMode(win); - if (E.xconfigure.width != win->src.r.w || E.xconfigure.height != win->src.r.h) { - win->event.type = RGFW_windowResized; - win->src.r = win->r = RGFW_RECT(win->src.r.x, win->src.r.y, E.xconfigure.width, E.xconfigure.height); - RGFW_windowResizedCallback(win, win->r); + if (!(win->internal.enabledEvents & RGFW_mouseEnterFlag)) return; + event.type = RGFW_mouseEnter; + event.mouse.x = E.xcrossing.x; + event.mouse.y = E.xcrossing.y; + RGFW_mouseNotifyCallback(win, event.mouse.x, event.mouse.y, 1); break; } - /* detect move */ - if (E.xconfigure.x != win->src.r.x || E.xconfigure.y != win->src.r.y) { - win->event.type = RGFW_windowMoved; - win->src.r = win->r = RGFW_RECT(E.xconfigure.x, E.xconfigure.y, win->src.r.w, win->src.r.h); - RGFW_windowMovedCallback(win, win->r); + case LeaveNotify: { + win->internal.mouseInside = RGFW_FALSE; + _RGFW->windowState.winLeave = win; + _RGFW->windowState.mouseLeave = RGFW_TRUE; + if (!(win->internal.enabledEvents & RGFW_mouseLeaveFlag)) return; + event.type = RGFW_mouseLeave; + RGFW_mouseNotifyCallback(win, event.mouse.x, event.mouse.y, 0); break; } + case ReparentNotify: + win->src.parent = E.xreparent.parent; + break; + case ConfigureNotify: { + /* detect resize */ + RGFW_window_checkMode(win); + if (E.xconfigure.width != win->src.w || E.xconfigure.height != win->src.h) { + win->src.w = win->w = E.xconfigure.width; + win->src.h = win->h = E.xconfigure.height; - break; - } - default: - XFlush(win->src.display); - return RGFW_window_checkEvent(win); - } - XFlush(win->src.display); - if (win->event.type) return &win->event; - else return NULL; -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - if ((win->_flags & RGFW_windowHide) == 0) - wl_display_roundtrip(win->src.wl_display); - return NULL; -#endif -} - -void RGFW_window_move(RGFW_window* win, RGFW_point v) { - RGFW_ASSERT(win != NULL); - win->r.x = v.x; - win->r.y = v.y; - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - XMoveWindow(win->src.display, win->src.window, v.x, v.y); -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - RGFW_ASSERT(win != NULL); - - if (win->src.compositor) { - struct wl_pointer *pointer = wl_seat_get_pointer(win->src.seat); - if (!pointer) { - return; + if (!(win->internal.enabledEvents & RGFW_windowResizedFlag)) return; + event.type = RGFW_windowResized; + RGFW_windowResizedCallback(win, win->w, win->h); + RGFW_eventQueuePush(&event); } - wl_display_flush(win->src.wl_display); + i32 x = E.xconfigure.x; + i32 y = E.xconfigure.y; + + /* + if the event came from the server and we're not a direct child of the root window then + we're using local coords which need to be translated into screen coords + */ + Window root = DefaultRootWindow(_RGFW->display); + if (E.xany.send_event == 0 && win->src.parent != root) { + Window dummy = 0; + XTranslateCoordinates(_RGFW->display, win->src.parent, root, x, y, &x, &y, &dummy); + } + + /* detect move */ + if (E.xconfigure.x != win->src.x || E.xconfigure.y != win->src.y) { + win->src.x = win->x = E.xconfigure.x; + win->src.y = win->y = E.xconfigure.y; + + if (!(win->internal.enabledEvents & RGFW_windowMovedFlag)) return; + event.type = RGFW_windowMoved; + RGFW_windowMovedCallback(win, win->x, win->y); + RGFW_eventQueuePush(&event); + } + return; + } + default: + break; } -#endif + + if (event.type) { + RGFW_eventQueuePush(&event); + } + + XFlush(_RGFW->display); +} + +void RGFW_FUNC(RGFW_pollEvents) (void) { + RGFW_resetPrevState(); + + XPending(_RGFW->display); + /* if there is no unread queued events, get a new one */ + while ((QLength(_RGFW->display) || XEventsQueued(_RGFW->display, QueuedAlready) + XEventsQueued(_RGFW->display, QueuedAfterReading))) { + RGFW_XHandleEvent(); + } +} + +void RGFW_FUNC(RGFW_window_move) (RGFW_window* win, i32 x, i32 y) { + RGFW_ASSERT(win != NULL); + win->x = x; + win->y = y; + + XMoveWindow(_RGFW->display, win->src.window, x, y); + return; } -void RGFW_window_resize(RGFW_window* win, RGFW_area a) { +void RGFW_FUNC(RGFW_window_resize) (RGFW_window* win, i32 w, i32 h) { RGFW_ASSERT(win != NULL); - win->r.w = (i32)a.w; - win->r.h = (i32)a.h; - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - XResizeWindow(win->src.display, win->src.window, a.w, a.h); + win->w = (i32)w; + win->h = (i32)h; - if ((win->_flags & RGFW_windowNoResize)) { + XResizeWindow(_RGFW->display, win->src.window, (u32)w, (u32)h); + + if ((win->internal.flags & RGFW_windowNoResize)) { XSizeHints sh; sh.flags = (1L << 4) | (1L << 5); - sh.min_width = sh.max_width = (i32)a.w; - sh.min_height = sh.max_height = (i32)a.h; + sh.min_width = sh.max_width = (i32)w; + sh.min_height = sh.max_height = (i32)h; - XSetWMSizeHints(win->src.display, (Drawable) win->src.window, &sh, XA_WM_NORMAL_HINTS); + XSetWMSizeHints(_RGFW->display, (Drawable) win->src.window, &sh, XA_WM_NORMAL_HINTS); } -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - if (win->src.compositor) { - xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->r.w, win->r.h); - #ifdef RGFW_OPENGL - wl_egl_window_resize(win->src.eglWindow, (i32)a.w, (i32)a.h, 0, 0); - #endif - } -#endif + return; } -void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { +void RGFW_FUNC(RGFW_window_setAspectRatio) (RGFW_window* win, i32 w, i32 h) { RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); - if (a.w == 0 && a.h == 0) + + if (w == 0 && h == 0) return; -#ifdef RGFW_X11 XSizeHints hints; long flags; - XGetWMNormalHints(win->src.display, win->src.window, &hints, &flags); + XGetWMNormalHints(_RGFW->display, win->src.window, &hints, &flags); hints.flags |= PAspect; - hints.min_aspect.x = hints.max_aspect.x = (i32)a.w; - hints.min_aspect.y = hints.max_aspect.y = (i32)a.h; + hints.min_aspect.x = hints.max_aspect.x = (i32)w; + hints.min_aspect.y = hints.max_aspect.y = (i32)h; - XSetWMNormalHints(win->src.display, win->src.window, &hints); + XSetWMNormalHints(_RGFW->display, win->src.window, &hints); return; -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL -#endif } -void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { +void RGFW_FUNC(RGFW_window_setMinSize) (RGFW_window* win, i32 w, i32 h) { RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 + long flags; XSizeHints hints; RGFW_MEMSET(&hints, 0, sizeof(XSizeHints)); - XGetWMNormalHints(win->src.display, win->src.window, &hints, &flags); + XGetWMNormalHints(_RGFW->display, win->src.window, &hints, &flags); hints.flags |= PMinSize; - hints.min_width = (i32)a.w; - hints.min_height = (i32)a.h; + hints.min_width = (i32)w; + hints.min_height = (i32)h; - XSetWMNormalHints(win->src.display, win->src.window, &hints); + XSetWMNormalHints(_RGFW->display, win->src.window, &hints); return; -#endif -#ifdef RGFW_WAYLAND -RGFW_WAYLAND_LABEL RGFW_UNUSED(a); -#endif } -void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { +void RGFW_FUNC(RGFW_window_setMaxSize) (RGFW_window* win, i32 w, i32 h) { RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 + long flags; XSizeHints hints; RGFW_MEMSET(&hints, 0, sizeof(XSizeHints)); - XGetWMNormalHints(win->src.display, win->src.window, &hints, &flags); + XGetWMNormalHints(_RGFW->display, win->src.window, &hints, &flags); hints.flags |= PMaxSize; - hints.max_width = (i32)a.w; - hints.max_height = (i32)a.h; + hints.max_width = (i32)w; + hints.max_height = (i32)h; - XSetWMNormalHints(win->src.display, win->src.window, &hints); -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL RGFW_UNUSED(a); -#endif + XSetWMNormalHints(_RGFW->display, win->src.window, &hints); + return; } -#ifdef RGFW_X11 void RGFW_toggleXMaximized(RGFW_window* win, RGFW_bool maximized); void RGFW_toggleXMaximized(RGFW_window* win, RGFW_bool maximized) { RGFW_ASSERT(win != NULL); @@ -5129,52 +6238,36 @@ void RGFW_toggleXMaximized(RGFW_window* win, RGFW_bool maximized) { xev.xclient.data.l[3] = 0; xev.xclient.data.l[4] = 0; - XSendEvent(win->src.display, DefaultRootWindow(win->src.display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XSendEvent(_RGFW->display, DefaultRootWindow(_RGFW->display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); } -#endif -void RGFW_window_maximize(RGFW_window* win) { - win->_oldRect = win->r; - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 +void RGFW_FUNC(RGFW_window_maximize) (RGFW_window* win) { + win->internal.oldX = win->x; + win->internal.oldY = win->y; + win->internal.oldW = win->w; + win->internal.oldH = win->h; + RGFW_toggleXMaximized(win, 1); return; -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - return; -#endif } -void RGFW_window_focus(RGFW_window* win) { +void RGFW_FUNC(RGFW_window_focus) (RGFW_window* win) { RGFW_ASSERT(win); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 + XWindowAttributes attr; - XGetWindowAttributes(win->src.display, win->src.window, &attr); + XGetWindowAttributes(_RGFW->display, win->src.window, &attr); if (attr.map_state != IsViewable) return; - XSetInputFocus(win->src.display, win->src.window, RevertToPointerRoot, CurrentTime); - XFlush(win->src.display); -#endif -#ifdef RGFW_WAYLAND -RGFW_WAYLAND_LABEL; -#endif + XSetInputFocus(_RGFW->display, win->src.window, RevertToPointerRoot, CurrentTime); + XFlush(_RGFW->display); } -void RGFW_window_raise(RGFW_window* win) { +void RGFW_FUNC(RGFW_window_raise) (RGFW_window* win) { RGFW_ASSERT(win); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - XRaiseWindow(win->src.display, win->src.window); - XMapRaised(win->src.display, win->src.window); -#endif -#ifdef RGFW_WAYLAND -RGFW_WAYLAND_LABEL; -#endif + XRaiseWindow(_RGFW->display, win->src.window); + XMapRaised(_RGFW->display, win->src.window); } -#ifdef RGFW_X11 void RGFW_window_setXAtom(RGFW_window* win, Atom netAtom, RGFW_bool fullscreen); void RGFW_window_setXAtom(RGFW_window* win, Atom netAtom, RGFW_bool fullscreen) { RGFW_ASSERT(win != NULL); @@ -5191,94 +6284,66 @@ void RGFW_window_setXAtom(RGFW_window* win, Atom netAtom, RGFW_bool fullscreen) xev.xclient.data.l[1] = (long int)netAtom; xev.xclient.data.l[2] = 0; - XSendEvent(win->src.display, DefaultRootWindow(win->src.display), False, SubstructureNotifyMask | SubstructureRedirectMask, &xev); + XSendEvent(_RGFW->display, DefaultRootWindow(_RGFW->display), False, SubstructureNotifyMask | SubstructureRedirectMask, &xev); } -#endif -void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { +void RGFW_FUNC(RGFW_window_setFullscreen)(RGFW_window* win, RGFW_bool fullscreen) { RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); + if (fullscreen) { - win->_flags |= RGFW_windowFullscreen; - win->_oldRect = win->r; + win->internal.flags |= RGFW_windowFullscreen; + win->internal.oldX = win->x; + win->internal.oldY = win->y; + win->internal.oldW = win->w; + win->internal.oldH = win->h; } - else win->_flags &= ~(u32)RGFW_windowFullscreen; -#ifdef RGFW_X11 + else win->internal.flags &= ~(u32)RGFW_windowFullscreen; RGFW_LOAD_ATOM(_NET_WM_STATE_FULLSCREEN); RGFW_window_setXAtom(win, _NET_WM_STATE_FULLSCREEN, fullscreen); - XRaiseWindow(win->src.display, win->src.window); - XMapRaised(win->src.display, win->src.window); -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL; -#endif + XRaiseWindow(_RGFW->display, win->src.window); + XMapRaised(_RGFW->display, win->src.window); } -void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { +void RGFW_FUNC(RGFW_window_setFloating)(RGFW_window* win, RGFW_bool floating) { RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 RGFW_LOAD_ATOM(_NET_WM_STATE_ABOVE); RGFW_window_setXAtom(win, _NET_WM_STATE_ABOVE, floating); -#endif -#ifdef RGFW_WAYLAND -RGFW_WAYLAND_LABEL RGFW_UNUSED(floating); -#endif } -void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { +void RGFW_FUNC(RGFW_window_setOpacity)(RGFW_window* win, u8 opacity) { RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 const u32 value = (u32) (0xffffffffu * (double) opacity); RGFW_LOAD_ATOM(NET_WM_WINDOW_OPACITY); - XChangeProperty(win->src.display, win->src.window, + XChangeProperty(_RGFW->display, win->src.window, NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) &value, 1); -#endif -#ifdef RGFW_WAYLAND -RGFW_WAYLAND_LABEL RGFW_UNUSED(opacity); -#endif } -void RGFW_window_minimize(RGFW_window* win) { +void RGFW_FUNC(RGFW_window_minimize)(RGFW_window* win) { RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); + if (RGFW_window_isMaximized(win)) return; - win->_oldRect = win->r; -#ifdef RGFW_X11 - XIconifyWindow(win->src.display, win->src.window, DefaultScreen(win->src.display)); - XFlush(win->src.display); -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL; -#endif + win->internal.oldX = win->x; + win->internal.oldY = win->y; + win->internal.oldW = win->w; + win->internal.oldH = win->h; + XIconifyWindow(_RGFW->display, win->src.window, DefaultScreen(_RGFW->display)); + XFlush(_RGFW->display); } -void RGFW_window_restore(RGFW_window* win) { +void RGFW_FUNC(RGFW_window_restore)(RGFW_window* win) { RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - RGFW_toggleXMaximized(win, 0); -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL -#endif - win->r = win->_oldRect; - RGFW_window_move(win, RGFW_POINT(win->r.x, win->r.y)); - RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h)); - + RGFW_toggleXMaximized(win, RGFW_FALSE); + RGFW_window_move(win, win->internal.oldX, win->internal.oldY); + RGFW_window_resize(win, win->internal.oldW, win->internal.oldH); + RGFW_window_show(win); -#ifdef RGFW_X11 - XFlush(win->src.display); -#endif + XFlush(_RGFW->display); } -RGFW_bool RGFW_window_isFloating(RGFW_window* win) { - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 +RGFW_bool RGFW_FUNC(RGFW_window_isFloating)(RGFW_window* win) { RGFW_LOAD_ATOM(_NET_WM_STATE); RGFW_LOAD_ATOM(_NET_WM_STATE_ABOVE); @@ -5287,7 +6352,7 @@ RGFW_bool RGFW_window_isFloating(RGFW_window* win) { unsigned long nitems, bytes_after; Atom* prop_return = NULL; - int status = XGetWindowProperty(win->src.display, win->src.window, _NET_WM_STATE, 0, (~0L), False, XA_ATOM, + int status = XGetWindowProperty(_RGFW->display, win->src.window, _NET_WM_STATE, 0, (~0L), False, XA_ATOM, &actual_type, &actual_format, &nitems, &bytes_after, (unsigned char **)&prop_return); @@ -5300,226 +6365,148 @@ RGFW_bool RGFW_window_isFloating(RGFW_window* win) { if (prop_return) XFree(prop_return); -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL RGFW_UNUSED(win); -#endif return RGFW_FALSE; } -void RGFW_window_setName(RGFW_window* win, const char* name) { +void RGFW_FUNC(RGFW_window_setName)(RGFW_window* win, const char* name) { RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); - #ifdef RGFW_X11 - XStoreName(win->src.display, win->src.window, name); - RGFW_LOAD_ATOM(_NET_WM_NAME); + XStoreName(_RGFW->display, win->src.window, name); + + RGFW_LOAD_ATOM(_NET_WM_NAME); RGFW_LOAD_ATOM(UTF8_STRING); char buf[256]; RGFW_MEMSET(buf, 0, sizeof(buf)); RGFW_STRNCPY(buf, name, sizeof(buf) - 1); XChangeProperty( - win->src.display, win->src.window, _NET_WM_NAME, RGFW_XUTF8_STRING, + _RGFW->display, win->src.window, _NET_WM_NAME, UTF8_STRING, 8, PropModeReplace, (u8*)buf, sizeof(buf) ); - #endif - #ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - if (win->src.compositor) - xdg_toplevel_set_title(win->src.xdg_toplevel, name); - #endif } #ifndef RGFW_NO_PASSTHROUGH -void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { +void RGFW_FUNC(RGFW_window_setMousePassthrough) (RGFW_window* win, RGFW_bool passthrough) { RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 if (passthrough) { Region region = XCreateRegion(); - XShapeCombineRegion(win->src.display, win->src.window, ShapeInput, 0, 0, region, ShapeSet); + XShapeCombineRegion(_RGFW->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 -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL RGFW_UNUSED(passthrough); -#endif + XShapeCombineMask(_RGFW->display, win->src.window, ShapeInput, 0, 0, None, ShapeSet); } #endif /* RGFW_NO_PASSTHROUGH */ -RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* icon, RGFW_area a, i32 channels, u8 type) { +RGFW_bool RGFW_FUNC(RGFW_window_setIconEx) (RGFW_window* win, u8* data_src, i32 w, i32 h, RGFW_format format, RGFW_icon type) { + Atom _NET_WM_ICON = XInternAtom(_RGFW->display, "_NET_WM_ICON", False); RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - RGFW_LOAD_ATOM(_NET_WM_ICON); - if (icon == NULL || (channels != 3 && channels != 4)) { + if (data_src == NULL) { RGFW_bool res = (RGFW_bool)XChangeProperty( - win->src.display, win->src.window, _NET_WM_ICON, XA_CARDINAL, 32, + _RGFW->display, win->src.window, _NET_WM_ICON, XA_CARDINAL, 32, PropModeReplace, (u8*)NULL, 0 ); return res; } - i32 count = (i32)(2 + (a.w * a.h)); + i32 count = (i32)(2 + (w * h)); unsigned long* data = (unsigned long*) RGFW_ALLOC((u32)count * sizeof(unsigned long)); RGFW_ASSERT(data != NULL); - data[0] = (unsigned long)a.w; - data[1] = (unsigned long)a.h; - - unsigned long* target = &data[2]; - u32 x, y; - - for (x = 0; x < a.w; x++) { - for (y = 0; y < a.h; y++) { - size_t i = y * a.w + x; - u32 alpha = (channels == 4) ? icon[i * 4 + 3] : 0xFF; - - target[i] = (unsigned long)((icon[i * 4 + 0]) << 16) | - (unsigned long)((icon[i * 4 + 1]) << 8) | - (unsigned long)((icon[i * 4 + 2]) << 0) | - (unsigned long)(alpha << 24); - } - } + RGFW_MEMSET(data, 0, (u32)count * sizeof(unsigned long)); + data[0] = (unsigned long)w; + data[1] = (unsigned long)h; + RGFW_copyImageData64((u8*)&data[2], w, h, RGFW_formatBGRA8, data_src, format, RGFW_TRUE); RGFW_bool res = RGFW_TRUE; if (type & RGFW_iconTaskbar) { res = (RGFW_bool)XChangeProperty( - win->src.display, win->src.window, _NET_WM_ICON, XA_CARDINAL, 32, + _RGFW->display, win->src.window, _NET_WM_ICON, XA_CARDINAL, 32, PropModeReplace, (u8*)data, count ); } + RGFW_copyImageData64((u8*)&data[2], w, h, RGFW_formatBGRA8, data_src, format, RGFW_FALSE); + if (type & RGFW_iconWindow) { XWMHints wm_hints; wm_hints.flags = IconPixmapHint; - i32 depth = DefaultDepth(win->src.display, DefaultScreen(win->src.display)); - XImage *image = XCreateImage(win->src.display, DefaultVisual(win->src.display, DefaultScreen(win->src.display)), - (u32)depth, ZPixmap, 0, (char *)target, a.w, a.h, 32, 0); + i32 depth = DefaultDepth(_RGFW->display, DefaultScreen(_RGFW->display)); + XImage *image = XCreateImage(_RGFW->display, DefaultVisual(_RGFW->display, DefaultScreen(_RGFW->display)), + (u32)depth, ZPixmap, 0, (char *)&data[2], (u32)w, (u32)h, 32, 0); - wm_hints.icon_pixmap = XCreatePixmap(win->src.display, win->src.window, a.w, a.h, (u32)depth); - XPutImage(win->src.display, wm_hints.icon_pixmap, DefaultGC(win->src.display, DefaultScreen(win->src.display)), image, 0, 0, 0, 0, a.w, a.h); + wm_hints.icon_pixmap = XCreatePixmap(_RGFW->display, win->src.window, (u32)w, (u32)h, (u32)depth); + XPutImage(_RGFW->display, wm_hints.icon_pixmap, DefaultGC(_RGFW->display, DefaultScreen(_RGFW->display)), image, 0, 0, 0, 0, (u32)w, (u32)h); image->data = NULL; XDestroyImage(image); - XSetWMHints(win->src.display, win->src.window, &wm_hints); + XSetWMHints(_RGFW->display, win->src.window, &wm_hints); } RGFW_FREE(data); - XFlush(win->src.display); + XFlush(_RGFW->display); return RGFW_BOOL(res); -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL RGFW_UNUSED(icon); RGFW_UNUSED(a); RGFW_UNUSED(channels); RGFW_UNUSED(type); - return RGFW_FALSE; -#endif } -RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { - RGFW_ASSERT(icon); - RGFW_ASSERT(channels == 3 || channels == 4); - RGFW_GOTO_WAYLAND(0); - -#ifdef RGFW_X11 +RGFW_mouse* RGFW_FUNC(RGFW_loadMouse) (u8* data, i32 w, i32 h, RGFW_format format) { + RGFW_ASSERT(data); #ifndef RGFW_NO_X11_CURSOR RGFW_init(); - XcursorImage* native = XcursorImageCreate((i32)a.w, (i32)a.h); + XcursorImage* native = XcursorImageCreate((i32)w, (i32)h); native->xhot = 0; native->yhot = 0; + RGFW_MEMSET(native->pixels, 0, (u32)(w * h * 4)); + RGFW_copyImageData((u8*)native->pixels, w, h, RGFW_formatBGRA8, data, format); - XcursorPixel* target = native->pixels; - size_t x, y; - for (x = 0; x < a.w; x++) { - for (y = 0; y < a.h; y++) { - size_t i = y * a.w + x; - u32 alpha = (channels == 4) ? icon[i * 4 + 3] : 0xFF; - - target[i] = (u32)((icon[i * 4 + 0]) << 16) - | (u32)((icon[i * 4 + 1]) << 8) - | (u32)((icon[i * 4 + 2]) << 0) - | (u32)(alpha << 24); - } - } - - Cursor cursor = XcursorImageLoadCursor(_RGFW.display, native); + Cursor cursor = XcursorImageLoadCursor(_RGFW->display, native); XcursorImageDestroy(native); return (void*)cursor; #else - RGFW_UNUSED(image); RGFW_UNUSED(a.w); RGFW_UNUSED(channels); + RGFW_UNUSED(data); RGFW_UNUSED(w); RGFW_UNUSED(h); RGFW_UNUSED(format); return NULL; #endif -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - RGFW_UNUSED(icon); RGFW_UNUSED(a); RGFW_UNUSED(channels); - return NULL; /* TODO */ -#endif } -void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { -RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 +void RGFW_FUNC(RGFW_window_setMouse)(RGFW_window* win, RGFW_mouse* mouse) { RGFW_ASSERT(win && mouse); - XDefineCursor(win->src.display, win->src.window, (Cursor)mouse); -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - RGFW_UNUSED(win); RGFW_UNUSED(mouse); -#endif + XDefineCursor(_RGFW->display, win->src.window, (Cursor)mouse); } -void RGFW_freeMouse(RGFW_mouse* mouse) { -RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 +void RGFW_FUNC(RGFW_freeMouse)(RGFW_mouse* mouse) { RGFW_ASSERT(mouse); - XFreeCursor(_RGFW.display, (Cursor)mouse); -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - RGFW_UNUSED(mouse); -#endif + XFreeCursor(_RGFW->display, (Cursor)mouse); } -void RGFW_window_moveMouse(RGFW_window* win, RGFW_point p) { -RGFW_GOTO_WAYLAND(1); -#ifdef RGFW_X11 +void RGFW_FUNC(RGFW_window_moveMouse)(RGFW_window* win, i32 x, i32 y) { RGFW_ASSERT(win != NULL); XEvent event; - XQueryPointer(win->src.display, DefaultRootWindow(win->src.display), + XQueryPointer(_RGFW->display, DefaultRootWindow(_RGFW->display), &event.xbutton.root, &event.xbutton.window, &event.xbutton.x_root, &event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y, &event.xbutton.state); - win->_lastMousePoint = RGFW_POINT(p.x - win->r.x, p.y - win->r.y); - if (event.xbutton.x == p.x && event.xbutton.y == p.y) + win->internal.lastMouseX = x - win->x; + win->internal.lastMouseY = y - win->y; + if (event.xbutton.x == x && event.xbutton.y == y) return; - XWarpPointer(win->src.display, None, win->src.window, 0, 0, 0, 0, (int) p.x - win->r.x, (int) p.y - win->r.y); -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - RGFW_UNUSED(win); RGFW_UNUSED(p); -#endif + XWarpPointer(_RGFW->display, None, win->src.window, 0, 0, 0, 0, (int) x - win->x, (int) y - win->y); } -RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { +RGFW_bool RGFW_FUNC(RGFW_window_setMouseDefault) (RGFW_window* win) { return RGFW_window_setMouseStandard(win, RGFW_mouseArrow); } -RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { +RGFW_bool RGFW_FUNC(RGFW_window_setMouseStandard) (RGFW_window* win, u8 mouse) { RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 + static const u8 mouseIconSrc[16] = { 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}; if (mouse > (sizeof(mouseIconSrc) / sizeof(u8))) @@ -5527,63 +6514,33 @@ RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { mouse = mouseIconSrc[mouse]; - Cursor cursor = XCreateFontCursor(win->src.display, mouse); - XDefineCursor(win->src.display, win->src.window, (Cursor) cursor); - - XFreeCursor(win->src.display, (Cursor) cursor); + Cursor cursor = XCreateFontCursor(_RGFW->display, mouse); + XDefineCursor(_RGFW->display, win->src.window, (Cursor) cursor); + XFreeCursor(_RGFW->display, (Cursor) cursor); return RGFW_TRUE; -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL { } - static const char* iconStrings[16] = { "left_ptr", "left_ptr", "text", "cross", "pointer", "e-resize", "n-resize", "nw-resize", "ne-resize", "all-resize", "not-allowed" }; - - struct wl_cursor* wlcursor = wl_cursor_theme_get_cursor(RGFW_wl_cursor_theme, iconStrings[mouse]); - RGFW_cursor_image = wlcursor->images[0]; - struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(RGFW_cursor_image); - - wl_surface_attach(RGFW_cursor_surface, cursor_buffer, 0, 0); - wl_surface_commit(RGFW_cursor_surface); - return RGFW_TRUE; - -#endif } -void RGFW_window_hide(RGFW_window* win) { - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - XUnmapWindow(win->src.display, win->src.window); -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - wl_surface_attach(win->src.surface, NULL, 0, 0); - wl_surface_commit(win->src.surface); - win->_flags |= RGFW_windowHide; -#endif +void RGFW_FUNC(RGFW_window_hide)(RGFW_window* win) { + XUnmapWindow(_RGFW->display, win->src.window); } -void RGFW_window_show(RGFW_window* win) { - win->_flags &= ~(u32)RGFW_windowHide; - if (win->_flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - XMapWindow(win->src.display, win->src.window); -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - /* wl_surface_attach(win->src.surface, win->rc., 0, 0); */ - wl_surface_commit(win->src.surface); -#endif +void RGFW_FUNC(RGFW_window_show) (RGFW_window* win) { + win->internal.flags &= ~(u32)RGFW_windowHide; + if (win->internal.flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); + + XMapWindow(_RGFW->display, win->src.window); + RGFW_window_move(win, win->x, win->y); + return; } -RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { - RGFW_GOTO_WAYLAND(1); -#ifdef RGFW_X11 +RGFW_ssize_t RGFW_FUNC(RGFW_readClipboardPtr)(char* str, size_t strCapacity) { RGFW_init(); - if (XGetSelectionOwner(_RGFW.display, RGFW_XCLIPBOARD) == _RGFW.helperWindow) { + RGFW_LOAD_ATOM(XSEL_DATA); RGFW_LOAD_ATOM(UTF8_STRING); RGFW_LOAD_ATOM(CLIPBOARD); + if (XGetSelectionOwner(_RGFW->display, CLIPBOARD) == _RGFW->helperWindow) { if (str != NULL) - RGFW_STRNCPY(str, _RGFW.clipboard, _RGFW.clipboard_len - 1); - _RGFW.clipboard[_RGFW.clipboard_len - 1] = '\0'; - return (RGFW_ssize_t)_RGFW.clipboard_len - 1; + RGFW_STRNCPY(str, _RGFW->clipboard, _RGFW->clipboard_len - 1); + _RGFW->clipboard[_RGFW->clipboard_len - 1] = '\0'; + return (RGFW_ssize_t)_RGFW->clipboard_len - 1; } XEvent event; @@ -5592,15 +6549,13 @@ RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { char* data; Atom target; - RGFW_LOAD_ATOM(XSEL_DATA); - - XConvertSelection(_RGFW.display, RGFW_XCLIPBOARD, RGFW_XUTF8_STRING, XSEL_DATA, _RGFW.helperWindow, CurrentTime); - XSync(_RGFW.display, 0); + XConvertSelection(_RGFW->display, CLIPBOARD, UTF8_STRING, XSEL_DATA, _RGFW->helperWindow, CurrentTime); + XSync(_RGFW->display, 0); while (1) { - XNextEvent(_RGFW.display, &event); + XNextEvent(_RGFW->display, &event); if (event.type != SelectionNotify) continue; - if (event.xselection.selection != RGFW_XCLIPBOARD || event.xselection.property == 0) + if (event.xselection.selection != CLIPBOARD || event.xselection.property == 0) return -1; break; } @@ -5613,7 +6568,7 @@ RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { if (sizeN > strCapacity && str != NULL) size = -1; - if ((target == RGFW_XUTF8_STRING || target == XA_STRING) && str != NULL) { + if ((target == UTF8_STRING || target == XA_STRING) && str != NULL) { RGFW_MEMCPY(str, data, sizeN); str[sizeN] = '\0'; XFree(data); @@ -5623,22 +6578,16 @@ RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { size = (RGFW_ssize_t)sizeN; return size; - #endif - #if defined(RGFW_WAYLAND) - RGFW_WAYLAND_LABEL RGFW_UNUSED(str); RGFW_UNUSED(strCapacity); - return 0; - #endif } i32 RGFW_XHandleClipboardSelectionHelper(void) { -#ifdef RGFW_X11 RGFW_LOAD_ATOM(SAVE_TARGETS); XEvent event; - XPending(_RGFW.display); + XPending(_RGFW->display); - if (QLength(_RGFW.display) || XEventsQueued(_RGFW.display, QueuedAlready) + XEventsQueued(_RGFW.display, QueuedAfterReading)) - XNextEvent(_RGFW.display, &event); + if (QLength(_RGFW->display) || XEventsQueued(_RGFW->display, QueuedAlready) + XEventsQueued(_RGFW->display, QueuedAfterReading)) + XNextEvent(_RGFW->display, &event); else return 0; @@ -5654,60 +6603,41 @@ i32 RGFW_XHandleClipboardSelectionHelper(void) { } return 0; -#else - return 1; -#endif } -void RGFW_writeClipboard(const char* text, u32 textLen) { - RGFW_GOTO_WAYLAND(1); - #ifdef RGFW_X11 - RGFW_LOAD_ATOM(SAVE_TARGETS); +void RGFW_FUNC(RGFW_writeClipboard)(const char* text, u32 textLen) { + RGFW_LOAD_ATOM(SAVE_TARGETS); RGFW_LOAD_ATOM(CLIPBOARD); RGFW_init(); /* request ownership of the clipboard section and request to convert it, this means its our job to convert it */ - XSetSelectionOwner(_RGFW.display, RGFW_XCLIPBOARD, _RGFW.helperWindow, CurrentTime); - if (XGetSelectionOwner(_RGFW.display, RGFW_XCLIPBOARD) != _RGFW.helperWindow) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, RGFW_DEBUG_CTX(_RGFW.root, 0), "X11 failed to become owner of clipboard selection"); + XSetSelectionOwner(_RGFW->display, CLIPBOARD, _RGFW->helperWindow, CurrentTime); + if (XGetSelectionOwner(_RGFW->display, CLIPBOARD) != _RGFW->helperWindow) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, "X11 failed to become owner of clipboard selection"); return; } - if (_RGFW.clipboard) - RGFW_FREE(_RGFW.clipboard); + if (_RGFW->clipboard) + RGFW_FREE(_RGFW->clipboard); - _RGFW.clipboard = (char*)RGFW_ALLOC(textLen); - RGFW_ASSERT(_RGFW.clipboard != NULL); + _RGFW->clipboard = (char*)RGFW_ALLOC(textLen); + RGFW_ASSERT(_RGFW->clipboard != NULL); - RGFW_STRNCPY(_RGFW.clipboard, text, textLen - 1); - _RGFW.clipboard[textLen - 1] = '\0'; - _RGFW.clipboard_len = textLen; - #endif - #ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - RGFW_UNUSED(text); RGFW_UNUSED(textLen); - #endif + RGFW_STRNCPY(_RGFW->clipboard, text, textLen - 1); + _RGFW->clipboard[textLen - 1] = '\0'; + _RGFW->clipboard_len = textLen; + return; } -RGFW_bool RGFW_window_isHidden(RGFW_window* win) { +RGFW_bool RGFW_FUNC(RGFW_window_isHidden)(RGFW_window* win) { RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 - XWindowAttributes windowAttributes; - XGetWindowAttributes(win->src.display, win->src.window, &windowAttributes); + XGetWindowAttributes(_RGFW->display, win->src.window, &windowAttributes); return (windowAttributes.map_state == IsUnmapped && !RGFW_window_isMinimized(win)); -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - return RGFW_FALSE; -#endif } -RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { +RGFW_bool RGFW_FUNC(RGFW_window_isMinimized)(RGFW_window* win) { RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 RGFW_LOAD_ATOM(WM_STATE); Atom actual_type; @@ -5715,7 +6645,7 @@ RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { unsigned long nitems, bytes_after; unsigned char* prop_data; - i32 status = XGetWindowProperty(win->src.display, win->src.window, WM_STATE, 0, 2, False, + i32 status = XGetWindowProperty(_RGFW->display, win->src.window, WM_STATE, 0, 2, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop_data); @@ -5728,19 +6658,12 @@ RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { XFree(prop_data); XWindowAttributes windowAttributes; - XGetWindowAttributes(win->src.display, win->src.window, &windowAttributes); + XGetWindowAttributes(_RGFW->display, win->src.window, &windowAttributes); return windowAttributes.map_state != IsViewable; -#endif -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - return RGFW_FALSE; -#endif } -RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { +RGFW_bool RGFW_FUNC(RGFW_window_isMaximized)(RGFW_window* win) { RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); -#ifdef RGFW_X11 RGFW_LOAD_ATOM(_NET_WM_STATE); RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_VERT); RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ); @@ -5750,7 +6673,7 @@ RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { unsigned long nitems, bytes_after; unsigned char* prop_data; - i32 status = XGetWindowProperty(win->src.display, win->src.window, _NET_WM_STATE, 0, 1024, False, + i32 status = XGetWindowProperty(_RGFW->display, win->src.window, _NET_WM_STATE, 0, 1024, False, XA_ATOM, &actual_type, &actual_format, &nitems, &bytes_after, &prop_data); @@ -5772,23 +6695,10 @@ RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { if (prop_data != NULL) XFree(prop_data); -#endif -#ifdef RGFW_WAYLAND -RGFW_WAYLAND_LABEL; -#endif + return RGFW_FALSE; } -#ifndef RGFW_NO_DPI -u32 RGFW_XCalculateRefreshRate(XRRModeInfo mi); -u32 RGFW_XCalculateRefreshRate(XRRModeInfo mi) { - if (mi.hTotal == 0 || mi.vTotal == 0) return 0; - return (u32) RGFW_ROUND((double) mi.dotClock / ((double) mi.hTotal * (double) mi.vTotal)); -} -#endif - - -#ifdef RGFW_X11 static float XGetSystemContentDPI(Display* display, i32 screen) { float dpi = 96.0f; @@ -5812,49 +6722,53 @@ static float XGetSystemContentDPI(Display* display, i32 screen) { return dpi; } -#endif RGFW_monitor RGFW_XCreateMonitor(i32 screen); RGFW_monitor RGFW_XCreateMonitor(i32 screen) { RGFW_monitor monitor; RGFW_init(); - RGFW_GOTO_WAYLAND(1); -#ifdef RGFW_X11 - Display* display = _RGFW.display; + Display* display = _RGFW->display; if (screen == -1) screen = DefaultScreen(display); Screen* scrn = DefaultScreenOfDisplay(display); - RGFW_area size = RGFW_AREA(scrn->width, scrn->height); monitor.x = 0; monitor.y = 0; - monitor.mode.area = RGFW_AREA(size.w, size.h); + monitor.mode.w = scrn->width; + monitor.mode.h = scrn->height; monitor.physW = (float)DisplayWidthMM(display, screen) / 25.4f; monitor.physH = (float)DisplayHeightMM(display, screen) / 25.4f; - RGFW_splitBPP((u32)DefaultDepth(display, DefaultScreen(display)), &monitor.mode); + RGFW_splitBPP((u32)DefaultDepth(display, screen), &monitor.mode); char* name = XDisplayName((const char*)display); RGFW_STRNCPY(monitor.name, name, sizeof(monitor.name) - 1); monitor.name[sizeof(monitor.name) - 1] = '\0'; float dpi = XGetSystemContentDPI(display, screen); - monitor.pixelRatio = dpi >= 192.0f ? 2 : 1; + monitor.pixelRatio = dpi >= 192.0f ? 2 : 1.0f; monitor.scaleX = (float) (dpi) / 96.0f; monitor.scaleY = (float) (dpi) / 96.0f; #ifndef RGFW_NO_DPI - XRRScreenResources* sr = XRRGetScreenResourcesCurrent(display, RootWindow(display, screen)); - monitor.mode.refreshRate = RGFW_XCalculateRefreshRate(sr->modes[screen]); + XRRCrtcInfo* ci = NULL; + XRRScreenResources* sr = NULL; - XRRCrtcInfo* ci = NULL; + { + XRRScreenConfiguration* conf = XRRGetScreenInfo(display, RootWindow(display, screen)); + monitor.mode.refreshRate = (u32)XRRConfigCurrentRate(conf); + + sr = XRRGetScreenResourcesCurrent(display, RootWindow(display, screen)); int crtc = screen; if (sr->ncrtc > crtc) { ci = XRRGetCrtcInfo(display, sr, sr->crtcs[crtc]); } + + XRRFreeScreenConfigInfo(conf); + } #endif #ifndef RGFW_NO_DPI @@ -5862,7 +6776,7 @@ RGFW_monitor RGFW_XCreateMonitor(i32 screen) { if (info == NULL || ci == NULL) { XRRFreeScreenResources(sr); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, "monitor found"); return monitor; } @@ -5873,18 +6787,21 @@ RGFW_monitor RGFW_XCreateMonitor(i32 screen) { RGFW_STRNCPY(monitor.name, info->name, sizeof(monitor.name) - 1); monitor.name[sizeof(monitor.name) - 1] = '\0'; - if ((u8)physW && (u8)physH) { - monitor.physW = physW; - monitor.physH = physH; - } + XRRFreeOutputInfo(info); + info = NULL; - monitor.x = ci->x; - monitor.y = ci->y; + if (physW > 0.0f && physH > 0.0f) { + monitor.physW = physW; + monitor.physH = physH; + } - if (ci->width && ci->height) { - monitor.mode.area.w = (u32)ci->width; - monitor.mode.area.h = (u32)ci->height; - } + monitor.x = ci->x; + monitor.y = ci->y; + + if (ci->width && ci->height) { + monitor.mode.w = (i32)ci->width; + monitor.mode.h = (i32)ci->height; + } #endif #ifndef RGFW_NO_DPI @@ -5892,24 +6809,15 @@ RGFW_monitor RGFW_XCreateMonitor(i32 screen) { XRRFreeScreenResources(sr); #endif - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, "monitor found"); return monitor; -#endif -#ifdef RGFW_WAYLAND -RGFW_WAYLAND_LABEL RGFW_UNUSED(screen); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); - return monitor; -#endif } -RGFW_monitor* RGFW_getMonitors(size_t* len) { +RGFW_monitor* RGFW_FUNC(RGFW_getMonitors)(size_t* len) { static RGFW_monitor monitors[7]; - - RGFW_GOTO_WAYLAND(1); - #ifdef RGFW_X11 RGFW_init(); - Display* display = _RGFW.display; + Display* display = _RGFW->display; i32 max = ScreenCount(display); i32 i; @@ -5919,52 +6827,41 @@ RGFW_monitor* RGFW_getMonitors(size_t* len) { if (len != NULL) *len = (size_t)((max <= 6) ? (max) : (6)); return monitors; - #endif - #ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL RGFW_UNUSED(len); - return monitors; /* TODO WAYLAND */ - #endif } -RGFW_monitor RGFW_getPrimaryMonitor(void) { - RGFW_GOTO_WAYLAND(1); - #ifdef RGFW_X11 +RGFW_monitor RGFW_FUNC(RGFW_getPrimaryMonitor)(void) { return RGFW_XCreateMonitor(-1); - #endif - #ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL return (RGFW_monitor){ 0 }; /* TODO WAYLAND */ - #endif } -RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { - RGFW_GOTO_WAYLAND(1); -#ifdef RGFW_X11 +RGFW_bool RGFW_FUNC(RGFW_monitor_requestMode)(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { #ifndef RGFW_NO_DPI RGFW_init(); - XRRScreenResources* screenRes = XRRGetScreenResources(_RGFW.display, DefaultRootWindow(_RGFW.display)); + XRRScreenConfiguration *conf = XRRGetScreenInfo(_RGFW->display, DefaultRootWindow(_RGFW->display)); + XRRScreenResources* screenRes = XRRGetScreenResources(_RGFW->display, DefaultRootWindow(_RGFW->display)); if (screenRes == NULL) return RGFW_FALSE; int i; for (i = 0; i < screenRes->ncrtc; i++) { - XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(_RGFW.display, screenRes, screenRes->crtcs[i]); + XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(_RGFW->display, screenRes, screenRes->crtcs[i]); if (!crtcInfo) continue; - if (mon.x == crtcInfo->x && mon.y == crtcInfo->y && (u32)mon.mode.area.w == crtcInfo->width && (u32)mon.mode.area.h == crtcInfo->height) { + if (mon.x == crtcInfo->x && mon.y == crtcInfo->y && (u32)mon.mode.w == crtcInfo->width && (u32)mon.mode.h == crtcInfo->height) { RRMode rmode = None; int index; for (index = 0; index < screenRes->nmode; index++) { RGFW_monitorMode foundMode; - foundMode.area = RGFW_AREA(screenRes->modes[index].width, screenRes->modes[index].height); - foundMode.refreshRate = RGFW_XCalculateRefreshRate(screenRes->modes[index]); - RGFW_splitBPP((u32)DefaultDepth(_RGFW.display, DefaultScreen(_RGFW.display)), &foundMode); + foundMode.w = (i32)screenRes->modes[index].width; + foundMode.h = (i32)screenRes->modes[index].height; + foundMode.refreshRate = (u32)XRRConfigCurrentRate(conf); + RGFW_splitBPP((u32)DefaultDepth(_RGFW->display, DefaultScreen(_RGFW->display)), &foundMode); if (RGFW_monitorModeCompare(mode, foundMode, request)) { rmode = screenRes->modes[index].id; RROutput output = screenRes->outputs[i]; - XRROutputInfo* info = XRRGetOutputInfo(_RGFW.display, screenRes, output); + XRROutputInfo* info = XRRGetOutputInfo(_RGFW->display, screenRes, output); if (info) { - XRRSetCrtcConfig(_RGFW.display, screenRes, screenRes->crtcs[i], + XRRSetCrtcConfig(_RGFW->display, screenRes, screenRes->crtcs[i], CurrentTime, 0, 0, rmode, RR_Rotate_0, &output, 1); XRRFreeOutputInfo(info); XRRFreeCrtcInfo(crtcInfo); @@ -5983,144 +6880,394 @@ RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW } XRRFreeScreenResources(screenRes); - return RGFW_FALSE; - #endif -#endif -#ifdef RGFW_WAYLAND -RGFW_WAYLAND_LABEL RGFW_UNUSED(mon); RGFW_UNUSED(mode); RGFW_UNUSED(request); + XRRFreeScreenConfigInfo(conf); #endif return RGFW_FALSE; } -RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { +RGFW_monitor RGFW_FUNC(RGFW_window_getMonitor) (RGFW_window* win) { RGFW_monitor mon; RGFW_MEMSET(&mon, 0, sizeof(mon)); RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(1); -#ifdef RGFW_X11 + XWindowAttributes attrs; - if (!XGetWindowAttributes(win->src.display, win->src.window, &attrs)) { + if (!XGetWindowAttributes(_RGFW->display, win->src.window, &attrs)) { return mon; } i32 i; - for (i = 0; i < ScreenCount(win->src.display) && i < 6; i++) { - Screen* screen = ScreenOfDisplay(win->src.display, i); + for (i = 0; i < ScreenCount(_RGFW->display) && i < 6; i++) { + Screen* screen = ScreenOfDisplay(_RGFW->display, i); if (attrs.x >= 0 && attrs.x < XWidthOfScreen(screen) && attrs.y >= 0 && attrs.y < XHeightOfScreen(screen)) return RGFW_XCreateMonitor(i); } -#endif -#ifdef RGFW_WAYLAND -RGFW_WAYLAND_LABEL -#endif return mon; } -#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) -void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { +#ifdef RGFW_OPENGL +RGFW_bool RGFW_FUNC(RGFW_window_createContextPtr_OpenGL) (RGFW_window* win, RGFW_glContext* context, RGFW_glHints* hints) { + /* for checking extensions later */ + const char sRGBARBstr[] = "GLX_ARB_framebuffer_sRGB"; + const char sRGBEXTstr[] = "GLX_EXT_framebuffer_sRGB"; + const char noErorrStr[] = "GLX_ARB_create_context_no_error"; + const char flushStr[] = "GLX_ARB_context_flush_control"; + const char robustStr[] = "GLX_ARB_create_context_robustness"; + + /* basic RGFW int */ + win->src.ctx.native = context; + win->src.gfxType = RGFW_gfxNativeOpenGL; + /* This is required so that way the user can create their own OpenGL context after RGFW_createWindow is used */ + if (win->src.window) RGFW_window_closePlatform(win); + + RGFW_bool transparent = (win->internal.flags & RGFW_windowTransparent); + + /* start by creating a GLX config / X11 Viusal */ + XVisualInfo visual; + GLXFBConfig bestFbc; + + i32 visual_attribs[40]; + RGFW_attribStack stack; + RGFW_attribStack_init(&stack, visual_attribs, 40); + RGFW_attribStack_pushAttribs(&stack, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR); + RGFW_attribStack_pushAttribs(&stack, GLX_X_RENDERABLE, 1); + RGFW_attribStack_pushAttribs(&stack, GLX_RENDER_TYPE, GLX_RGBA_BIT); + RGFW_attribStack_pushAttribs(&stack, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT); + RGFW_attribStack_pushAttribs(&stack, GLX_DOUBLEBUFFER, 1); + RGFW_attribStack_pushAttribs(&stack, GLX_ALPHA_SIZE, hints->alpha); + RGFW_attribStack_pushAttribs(&stack, GLX_DEPTH_SIZE, hints->depth); + RGFW_attribStack_pushAttribs(&stack, GLX_STENCIL_SIZE, hints->stencil); + RGFW_attribStack_pushAttribs(&stack, GLX_STEREO, hints->stereo); + RGFW_attribStack_pushAttribs(&stack, GLX_AUX_BUFFERS, hints->auxBuffers); + RGFW_attribStack_pushAttribs(&stack, GLX_RED_SIZE, hints->red); + RGFW_attribStack_pushAttribs(&stack, GLX_GREEN_SIZE, hints->green); + RGFW_attribStack_pushAttribs(&stack, GLX_BLUE_SIZE, hints->blue); + RGFW_attribStack_pushAttribs(&stack, GLX_ACCUM_RED_SIZE, hints->accumRed); + RGFW_attribStack_pushAttribs(&stack, GLX_ACCUM_GREEN_SIZE, hints->accumGreen); + RGFW_attribStack_pushAttribs(&stack, GLX_ACCUM_BLUE_SIZE, hints->accumBlue); + RGFW_attribStack_pushAttribs(&stack, GLX_ACCUM_ALPHA_SIZE, hints->accumAlpha); + + if (hints->sRGB) { + if (RGFW_extensionSupportedPlatform_OpenGL(sRGBARBstr, sizeof(sRGBARBstr))) + RGFW_attribStack_pushAttribs(&stack, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, hints->sRGB); + if (RGFW_extensionSupportedPlatform_OpenGL(sRGBEXTstr, sizeof(sRGBEXTstr))) + RGFW_attribStack_pushAttribs(&stack, GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, hints->sRGB); + } + + RGFW_attribStack_pushAttribs(&stack, 0, 0); + + /* find the configs */ + i32 fbcount; + GLXFBConfig* fbc = glXChooseFBConfig(_RGFW->display, DefaultScreen(_RGFW->display), visual_attribs, &fbcount); + + i32 best_fbc = -1; + i32 best_depth = 0; + i32 best_samples = 0; + + if (fbcount == 0) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to find any valid GLX visual configs."); + return 0; + } + + /* search through all found configs to find the best match */ + i32 i; + for (i = 0; i < fbcount; i++) { + XVisualInfo* vi = glXGetVisualFromFBConfig(_RGFW->display, fbc[i]); + if (vi == NULL) + continue; + + i32 samp_buf, samples; + glXGetFBConfigAttrib(_RGFW->display, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf); + glXGetFBConfigAttrib(_RGFW->display, fbc[i], GLX_SAMPLES, &samples); + + if (best_fbc == -1) best_fbc = i; + if ((!(transparent) || vi->depth == 32) && best_depth == 0) { + best_fbc = i; + best_depth = vi->depth; + } + if ((!(transparent) || vi->depth == 32) && samples <= hints->samples && samples > best_samples) { + best_fbc = i; + best_depth = vi->depth; + best_samples = samples; + } + XFree(vi); + } + + if (best_fbc == -1) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to get a valid GLX visual."); + return 0; + } + + /* we found a config */ + bestFbc = fbc[best_fbc]; + XVisualInfo* vi = glXGetVisualFromFBConfig(_RGFW->display, bestFbc); + if (vi->depth != 32 && transparent) + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, "Failed to to find a matching visual with a 32-bit depth."); + + if (best_samples < hints->samples) + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, "Failed to load a matching sample count."); + + XFree(fbc); + visual = *vi; + XFree(vi); + + /* use the visual to create a new window */ + RGFW_XCreateWindow(visual, "", win->internal.flags, win); + + /* create the actual OpenGL context */ + i32 context_attribs[40]; + RGFW_attribStack_init(&stack, context_attribs, 40); + + i32 mask = 0; + switch (hints->profile) { + case RGFW_glES: mask |= GLX_CONTEXT_ES_PROFILE_BIT_EXT; break; + case RGFW_glCompatibility: mask |= GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; break; + case RGFW_glCore: mask |= GLX_CONTEXT_CORE_PROFILE_BIT_ARB; break; + default: mask |= GLX_CONTEXT_CORE_PROFILE_BIT_ARB; break; + } + + RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_PROFILE_MASK_ARB, mask); + + if (hints->minor || hints->major) { + RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_MAJOR_VERSION_ARB, hints->major); + RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_MINOR_VERSION_ARB, hints->minor); + } + + + if (RGFW_extensionSupportedPlatform_OpenGL(flushStr, sizeof(flushStr))) { + if (hints->releaseBehavior == RGFW_glReleaseFlush) { + RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_RELEASE_BEHAVIOR_ARB, GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB); + } else if (hints->releaseBehavior == RGFW_glReleaseNone) { + RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_RELEASE_BEHAVIOR_ARB, GLX_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB); + } + } + + i32 flags = 0; + if (hints->debug) flags |= GLX_CONTEXT_DEBUG_BIT_ARB; + if (hints->robustness && RGFW_extensionSupportedPlatform_OpenGL(robustStr, sizeof(robustStr))) flags |= GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB; + if (flags) { + RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_FLAGS_ARB, flags); + } + + if (RGFW_extensionSupportedPlatform_OpenGL(noErorrStr, sizeof(noErorrStr))) { + RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_OPENGL_NO_ERROR_ARB, hints->noError); + } + + RGFW_attribStack_pushAttribs(&stack, 0, 0); + + /* create the context */ + glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0; + char str[] = "glXCreateContextAttribsARB"; + glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)glXGetProcAddressARB((u8*) str); + + GLXContext ctx = NULL; + if (hints->share) { + ctx = hints->share->ctx; + } + + if (glXCreateContextAttribsARB == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to load proc address 'glXCreateContextAttribsARB', loading a generic OpenGL context."); + win->src.ctx.native->ctx = glXCreateContext(_RGFW->display, &visual, ctx, True); + } else { + _RGFW->x11Error = NULL; + win->src.ctx.native->ctx = glXCreateContextAttribsARB(_RGFW->display, bestFbc, ctx, True, context_attribs); + if (_RGFW->x11Error || win->src.ctx.native->ctx == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to create an OpenGL context with AttribsARB, loading a generic OpenGL context."); + win->src.ctx.native->ctx = glXCreateContext(_RGFW->display, &visual, ctx, True); + } + } + + #ifndef RGFW_NO_GLXWINDOW + win->src.ctx.native->window = glXCreateWindow(_RGFW->display, bestFbc, win->src.window, NULL); + #else + win->src.ctx.native->window = win->src.window; + #endif + + glXMakeCurrent(_RGFW->display, (Drawable)win->src.ctx.native->window, (GLXContext)win->src.ctx.native->ctx); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context initalized."); + + return RGFW_TRUE; +} + +void RGFW_FUNC(RGFW_window_deleteContextPtr_OpenGL) (RGFW_window* win, RGFW_glContext* ctx) { + #ifndef RGFW_NO_GLXWINDOW + if (win->src.ctx.native->window != win->src.window) { + glXDestroyWindow(_RGFW->display, win->src.ctx.native->window); + } + #endif + + glXDestroyContext(_RGFW->display, ctx->ctx); + win->src.ctx.native = NULL; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context freed."); +} + +RGFW_bool RGFW_FUNC(RGFW_extensionSupportedPlatform_OpenGL)(const char * extension, size_t len) { + RGFW_init(); + const char* extensions = glXQueryExtensionsString(_RGFW->display, XDefaultScreen(_RGFW->display)); + return (extensions != NULL) && RGFW_extensionSupportedStr(extensions, extension, len); +} + +RGFW_proc RGFW_FUNC(RGFW_getProcAddress_OpenGL)(const char* procname) { return glXGetProcAddress((u8*) procname); } + +void RGFW_FUNC(RGFW_window_makeCurrentContext_OpenGL) (RGFW_window* win) { if (win) RGFW_ASSERT(win->src.ctx.native); if (win == NULL) glXMakeCurrent(NULL, (Drawable)NULL, (GLXContext) NULL); else - glXMakeCurrent(win->src.display, (Drawable) win->src.window, (GLXContext) win->src.ctx); + glXMakeCurrent(_RGFW->display, (Drawable)win->src.ctx.native->window, (GLXContext) win->src.ctx.native->ctx); + return; } -void* RGFW_getCurrent_OpenGL(void) { return glXGetCurrentContext(); } -void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { glXSwapBuffers(win->src.display, win->src.window); } -#endif +void* RGFW_FUNC(RGFW_getCurrentContext_OpenGL) (void) { return glXGetCurrentContext(); } +void RGFW_FUNC(RGFW_window_swapBuffers_OpenGL) (RGFW_window* win) { RGFW_ASSERT(win->src.ctx.native); glXSwapBuffers(_RGFW->display, win->src.ctx.native->window); } -void RGFW_window_swapBuffers_software(RGFW_window* win) { +void RGFW_FUNC(RGFW_window_swapInterval_OpenGL) (RGFW_window* win, i32 swapInterval) { RGFW_ASSERT(win != NULL); - RGFW_GOTO_WAYLAND(0); -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - #ifdef RGFW_X11 - win->src.bitmap->data = (char*) win->buffer; - RGFW_RGB_to_BGR(win, (u8*)win->src.bitmap->data); - XPutImage(win->src.display, win->src.window, win->src.gc, win->src.bitmap, 0, 0, 0, 0, win->bufferSize.w, win->bufferSize.h); - win->src.bitmap->data = NULL; - return; - #endif - #ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - #if !defined(RGFW_BUFFER_BGR) && !defined(RGFW_OSMESA) - RGFW_RGB_to_BGR(win, win->src.buffer); - #else - size_t y; - for (y = 0; y < win->r.h; y++) { - u32 index = (y * 4 * win->r.w); - u32 index2 = (y * 4 * win->bufferSize.w); - RGFW_MEMCPY(&win->src.buffer[index], &win->buffer[index2], win->r.w * 4); - } - #endif + /* cached pfn to avoid calling glXGetProcAddress more than once */ + static PFNGLXSWAPINTERVALEXTPROC pfn = NULL; + static int (*pfn2)(int) = NULL; - wl_surface_frame_done(win, NULL, 0); - wl_surface_commit(win->src.surface); - #endif -#else -#ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL -#endif - RGFW_UNUSED(win); -#endif -} - -#if !defined(RGFW_EGL) - -void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { - RGFW_ASSERT(win != NULL); - - #if defined(RGFW_OPENGL) - // cached pfn to avoid calling glXGetProcAddress more than once - static PFNGLXSWAPINTERVALEXTPROC pfn = (PFNGLXSWAPINTERVALEXTPROC)123; - static int (*pfn2)(int) = NULL; - - if (pfn == (PFNGLXSWAPINTERVALEXTPROC)123) { - pfn = ((PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddress((GLubyte*) "glXSwapIntervalEXT")); + if (pfn == NULL) { + u8 str[] = "glXSwapIntervalEXT"; + pfn = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddress(str); if (pfn == NULL) { - const char* array[] = {"GLX_MESA_swap_control", "GLX_SGI_swap_control"}; - u32 i; - for (i = 0; i < sizeof(array) / sizeof(char*) && pfn2 == NULL; i++) - pfn2 = ((int(*)(int))glXGetProcAddress((GLubyte*) array[i])); + pfn = (PFNGLXSWAPINTERVALEXTPROC)1; + const char* array[] = {"GLX_MESA_swap_control", "GLX_SGI_swap_control"}; - if (pfn2 != NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to load swap interval function, fallingback to the native swapinterval function"); - } else { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to load swap interval function"); - } - } - } - if (pfn != NULL) - pfn(win->src.display, win->src.window, swapInterval); - else if (pfn2 != NULL) { - pfn2(swapInterval); - } - #else - RGFW_UNUSED(swapInterval); - #endif + size_t i; + for (i = 0; i < sizeof(array) / sizeof(char*) && pfn2 == NULL; i++) { + pfn2 = (int(*)(int))glXGetProcAddress((u8*)array[i]); + } + + if (pfn2 != NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to load swap interval function, fallingback to the native swapinterval function"); + } else { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to load swap interval function"); + } + } + } + + if (pfn != (PFNGLXSWAPINTERVALEXTPROC)1) { + pfn(_RGFW->display, win->src.ctx.native->window, swapInterval); + } + else if (pfn2 != NULL) { + pfn2(swapInterval); + } } -#endif +#endif /* RGFW_OPENGL */ -void RGFW_deinit(void) { - if (_RGFW.windowCount == -1 || _RGFW_init == RGFW_FALSE) return; +i32 RGFW_initPlatform_X11(void) { + #ifdef RGFW_USE_XDL + XDL_init(); + #endif + + #if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) + #if defined(__CYGWIN__) + RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor-1.so"); + #elif defined(__OpenBSD__) || defined(__NetBSD__) + RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor.so"); + #else + RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor.so.1"); + #endif + RGFW_PROC_DEF(X11Cursorhandle, XcursorImageCreate); + RGFW_PROC_DEF(X11Cursorhandle, XcursorImageDestroy); + RGFW_PROC_DEF(X11Cursorhandle, XcursorImageLoadCursor); + #endif + + #if !defined(RGFW_NO_X11_XI_PRELOAD) + #if defined(__CYGWIN__) + RGFW_LOAD_LIBRARY(X11Xihandle, "libXi-6.so"); + #elif defined(__OpenBSD__) || defined(__NetBSD__) + RGFW_LOAD_LIBRARY(X11Xihandle, "libXi.so"); + #else + RGFW_LOAD_LIBRARY(X11Xihandle, "libXi.so.6"); + #endif + RGFW_PROC_DEF(X11Xihandle, XISelectEvents); + #endif + + #if !defined(RGFW_NO_X11_EXT_PRELOAD) + #if defined(__CYGWIN__) + RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext-6.so"); + #elif defined(__OpenBSD__) || defined(__NetBSD__) + RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext.so"); + #else + RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext.so.6"); + #endif + RGFW_PROC_DEF(X11XEXThandle, XSyncCreateCounter); + RGFW_PROC_DEF(X11XEXThandle, XSyncIntToValue); + RGFW_PROC_DEF(X11XEXThandle, XSyncSetCounter); + RGFW_PROC_DEF(X11XEXThandle, XShapeCombineRegion); + RGFW_PROC_DEF(X11XEXThandle, XShapeCombineMask); + #endif + + XInitThreads(); /*!< init X11 threading */ + _RGFW->display = XOpenDisplay(0); + _RGFW->context = XUniqueContext(); + + XSetWindowAttributes wa; + RGFW_MEMSET(&wa, 0, sizeof(wa)); + wa.event_mask = PropertyChangeMask; + _RGFW->helperWindow = XCreateWindow(_RGFW->display, XDefaultRootWindow(_RGFW->display), 0, 0, 1, 1, 0, 0, + InputOnly, DefaultVisual(_RGFW->display, DefaultScreen(_RGFW->display)), CWEventMask, &wa); + + u8 RGFW_blk[] = { 0, 0, 0, 0 }; + _RGFW->hiddenMouse = RGFW_loadMouse(RGFW_blk, 1, 1, RGFW_formatRGBA8); + _RGFW->clipboard = NULL; + + XkbComponentNamesRec rec; + XkbDescPtr desc = XkbGetMap(_RGFW->display, 0, XkbUseCoreKbd); + XkbDescPtr evdesc; + XSetErrorHandler(RGFW_XErrorHandler); + u8 old[256]; + + XkbGetNames(_RGFW->display, XkbKeyNamesMask, desc); + + RGFW_MEMSET(&rec, 0, sizeof(rec)); + char evdev[] = "evdev"; + rec.keycodes = evdev; + evdesc = XkbGetKeyboardByName(_RGFW->display, XkbUseCoreKbd, &rec, XkbGBN_KeyNamesMask, XkbGBN_KeyNamesMask, False); + /* memo: RGFW_keycodes[x11 keycode] = rgfw keycode */ + if(evdesc != NULL && desc != NULL) { + int i, j; + for(i = 0; i < (int)sizeof(old); i++){ + old[i] = _RGFW->keycodes[i]; + _RGFW->keycodes[i] = 0; + } + for(i = evdesc->min_key_code; i <= evdesc->max_key_code; i++){ + for(j = desc->min_key_code; j <= desc->max_key_code; j++){ + if(RGFW_STRNCMP(evdesc->names->keys[i].name, desc->names->keys[j].name, XkbKeyNameLength) == 0){ + _RGFW->keycodes[j] = old[i]; + break; + } + } + } + XkbFreeKeyboard(desc, 0, True); + XkbFreeKeyboard(evdesc, 0, True); + } + return 0; +} + +void RGFW_deinitPlatform_X11(void) { #define RGFW_FREE_LIBRARY(x) if (x != NULL) dlclose(x); x = NULL; -#ifdef RGFW_X11 /* to save the clipboard on the x server after the window is closed */ - RGFW_LOAD_ATOM(CLIPBOARD_MANAGER); + RGFW_LOAD_ATOM(CLIPBOARD_MANAGER); RGFW_LOAD_ATOM(CLIPBOARD); RGFW_LOAD_ATOM(SAVE_TARGETS); - if (XGetSelectionOwner(_RGFW.display, RGFW_XCLIPBOARD) == _RGFW.helperWindow) { - XConvertSelection(_RGFW.display, CLIPBOARD_MANAGER, SAVE_TARGETS, None, _RGFW.helperWindow, CurrentTime); + if (XGetSelectionOwner(_RGFW->display, CLIPBOARD) == _RGFW->helperWindow) { + XConvertSelection(_RGFW->display, CLIPBOARD_MANAGER, SAVE_TARGETS, None, _RGFW->helperWindow, CurrentTime); while (RGFW_XHandleClipboardSelectionHelper()); } - if (_RGFW.clipboard) { - RGFW_FREE(_RGFW.clipboard); - _RGFW.clipboard = NULL; + if (_RGFW->clipboard) { + RGFW_FREE(_RGFW->clipboard); + _RGFW->clipboard = NULL; } - RGFW_freeMouse(_RGFW.hiddenMouse); + if (_RGFW->hiddenMouse) { + RGFW_freeMouse(_RGFW->hiddenMouse); + _RGFW->hiddenMouse = NULL; + } - XDestroyWindow(_RGFW.display, (Drawable) _RGFW.helperWindow); /*!< close the window */ - XCloseDisplay(_RGFW.display); /*!< kill connection to the x server */ + XDestroyWindow(_RGFW->display, (Drawable) _RGFW->helperWindow); /*!< close the window */ + XCloseDisplay(_RGFW->display); /*!< kill connection to the x server */ #if !defined(RGFW_NO_X11_CURSOR_PRELOAD) && !defined(RGFW_NO_X11_CURSOR) RGFW_FREE_LIBRARY(X11Cursorhandle); @@ -6136,210 +7283,1592 @@ void RGFW_deinit(void) { #if !defined(RGFW_NO_X11_EXT_PRELOAD) RGFW_FREE_LIBRARY(X11XEXThandle); #endif -#endif -#ifdef RGFW_WAYLAND - wl_display_disconnect(_RGFW.wl_display); -#endif - #ifndef RGFW_NO_LINUX - 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_gamepadCount; i++) { - if(RGFW_gamepads[i]) - close(RGFW_gamepads[i]); - } - #endif - - _RGFW.root = NULL; - _RGFW.windowCount = -1; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context deinitialized"); } -void RGFW_window_close(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - if ((win->_flags & RGFW_windowNoInitAPI) == 0) RGFW_window_freeOpenGL(win); +void RGFW_FUNC(RGFW_window_closePlatform)(RGFW_window* win) { + if (win->internal.holdMouse) + XUngrabPointer(_RGFW->display, CurrentTime); - RGFW_GOTO_WAYLAND(0); - #ifdef RGFW_X11 - /* ungrab pointer if it was grabbed */ - if (win->_flags & RGFW_HOLD_MOUSE) - XUngrabPointer(win->src.display, CurrentTime); - - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - if (win->buffer != NULL) { - if ((win->_flags & RGFW_BUFFER_ALLOC)) - RGFW_FREE(win->buffer); - XDestroyImage((XImage*) win->src.bitmap); - } - #endif - - XFreeGC(win->src.display, win->src.gc); - XDestroyWindow(win->src.display, (Drawable) win->src.window); /*!< close the window */ - win->src.window = 0; - XCloseDisplay(win->src.display); - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a window was freed"); - _RGFW.windowCount--; - if (_RGFW.windowCount == 0) RGFW_deinit(); - - RGFW_clipboard_switch(NULL); - RGFW_FREE(win->event.droppedFiles); - if ((win->_flags & RGFW_WINDOW_ALLOC)) { - RGFW_FREE(win); - win = NULL; - } + XFreeGC(_RGFW->display, win->src.gc); + XDeleteContext(_RGFW->display, win->src.window, _RGFW->context); + XDestroyWindow(_RGFW->display, (Drawable) win->src.window); /*!< close the window */ return; - #endif - - #ifdef RGFW_WAYLAND - RGFW_WAYLAND_LABEL - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a window was freed"); - - xdg_toplevel_destroy(win->src.xdg_toplevel); - xdg_surface_destroy(win->src.xdg_surface); - wl_surface_destroy(win->src.surface); - - _RGFW.windowCount--; - if (_RGFW.windowCount == 0) RGFW_deinit(); - - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - wl_buffer_destroy(win->src.wl_buffer); - if ((win->_flags & RGFW_BUFFER_ALLOC)) - RGFW_FREE(win->buffer); - - munmap(win->src.buffer, (size_t)(win->r.w * win->r.h * 4)); - #endif - - RGFW_clipboard_switch(NULL); - RGFW_FREE(win->event.droppedFiles); - if ((win->_flags & RGFW_WINDOW_ALLOC)) { - RGFW_FREE(win); - win = NULL; - } - #endif } +#ifdef RGFW_WEBGPU +WGPUSurface RGFW_FUNC(RGFW_window_createSurface_WebGPU) (RGFW_window* window, WGPUInstance instance) { + WGPUSurfaceDescriptor surfaceDesc = {0}; + WGPUSurfaceSourceXlibWindow fromXlib = {0}; + fromXlib.chain.sType = WGPUSType_SurfaceSourceXlibWindow; + fromXlib.display = _RGFW->display; + fromXlib.window = window->src.window; + surfaceDesc.nextInChain = (WGPUChainedStruct*)&fromXlib.chain; + return wgpuInstanceCreateSurface(instance, &surfaceDesc); +} +#endif + +#endif /* End of X11 linux / wayland / unix defines */ -#include -#include -#include +/* -void RGFW_stopCheckEvents(void) { + Start of Wayland defayland +*/ - RGFW_eventWait_forceStop[2] = 1; - while (1) { - const char byte = 0; - const ssize_t result = write(RGFW_eventWait_forceStop[1], &byte, 1); - if (result == 1 || result == -1) - break; - } -} - -void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { - if (waitMS == 0) return; - - u8 i; - if (RGFW_eventWait_forceStop[0] == 0 || RGFW_eventWait_forceStop[1] == 0) { - if (pipe(RGFW_eventWait_forceStop) != -1) { - fcntl(RGFW_eventWait_forceStop[0], F_GETFL, 0); - fcntl(RGFW_eventWait_forceStop[0], F_GETFD, 0); - fcntl(RGFW_eventWait_forceStop[1], F_GETFL, 0); - fcntl(RGFW_eventWait_forceStop[1], F_GETFD, 0); - } - } - - struct pollfd fds[] = { - #ifdef RGFW_WAYLAND - { wl_display_get_fd(win->src.wl_display), POLLIN, 0 }, - #else - { ConnectionNumber(win->src.display), POLLIN, 0 }, - #endif - #ifdef RGFW_X11 - { ConnectionNumber(_RGFW.display), POLLIN, 0 }, - #endif - { RGFW_eventWait_forceStop[0], POLLIN, 0 }, - #if defined(__linux__) - { -1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0} - #endif - }; - - u8 index = 2; +#ifdef RGFW_WAYLAND #ifdef RGFW_X11 - index++; +#undef RGFW_FUNC /* remove previous define */ +#define RGFW_FUNC(func) func##_Wayland +#else +#define RGFW_FUNC(func) func #endif - #if defined(__linux__) || defined(__NetBSD__) - for (i = 0; i < RGFW_gamepadCount; i++) { - if (RGFW_gamepads[i] == 0) - continue; +/* +Wayland TODO: (out of date) +- fix RGFW_keyPressed lock state - fds[index].fd = RGFW_gamepads[i]; - index++; + RGFW_windowMoved, the window was moved (by the user) + RGFW_windowRefresh The window content needs to be refreshed + + RGFW_dataDrop a file has been dropped into the window + RGFW_dataDrag + +- window args: + #define RGFW_windowNoResize the window cannot be resized by the user + #define RGFW_windowAllowDND the window supports drag and drop + #define RGFW_scaleToMonitor scale the window to the screen + +- other missing functions functions ("TODO wayland") (~30 functions) +- fix buffer rendering weird behavior +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct wl_display* RGFW_getDisplay_Wayland(void) { return _RGFW->wl_display; } +struct wl_surface* RGFW_window_getWindow_Wayland(RGFW_window* win) { return win->src.surface; } + + +/* wayland global garbage (wayland bad, X11 is fine (ish) (not really)) */ +#include "xdg-shell.h" +#include "xdg-toplevel-icon-v1.h" +#include "xdg-decoration-unstable-v1.h" +#include "relative-pointer-unstable-v1.h" +#include "pointer-constraints-unstable-v1.h" +#include "xdg-output-unstable-v1.h" + + +void RGFW_toggleWaylandMaximized(RGFW_window* win, RGFW_bool maximized); + +static void RGFW_wl_setOpaque(RGFW_window* win) { + struct wl_region* wl_region = wl_compositor_create_region(_RGFW->compositor); + + if (!wl_region) return; /* return if no region was created */ + + wl_region_add(wl_region, 0, 0, win->w, win->h); + wl_surface_set_opaque_region(win->src.surface, wl_region); + wl_region_destroy(wl_region); + +} + +static void RGFW_wl_xdg_wm_base_ping_handler(void* data, struct xdg_wm_base* wm_base, + u32 serial) { + RGFW_UNUSED(data); + xdg_wm_base_pong(wm_base, serial); +} +static void RGFW_wl_xdg_surface_configure_handler(void* data, struct xdg_surface* xdg_surface, + u32 serial) { + + xdg_surface_ack_configure(xdg_surface, serial); + + RGFW_window* win = (RGFW_window*)data; + + if (win == NULL) { + win = _RGFW->kbOwner; + if (win == NULL) + return; + } + + /* useful for libdecor */ + if (win->src.activated != win->src.pending_activated) { + win->src.activated = win->src.pending_activated; + } + + if (win->src.maximized != win->src.pending_maximized) { + RGFW_toggleWaylandMaximized(win, win->src.pending_maximized); + + RGFW_window_checkMode(win); + } + + + if (win->src.resizing) { + + /* Do not create a resize event if the window is maximized */ + if (!win->src.maximized && win->internal.enabledEvents & RGFW_windowResizedFlag) { + RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e.common.win = win); + RGFW_windowResizedCallback(win, win->w, win->h); + } + RGFW_window_resize(win, win->w, win->h); + if (!(win->internal.flags & RGFW_windowTransparent)) { + RGFW_wl_setOpaque(win); } - #endif - - - u64 start = RGFW_getTimeNS(); - - - #ifdef RGFW_WAYLAND - while (wl_display_dispatch(win->src.wl_display) <= 0 - #else - while (XPending(win->src.display) == 0 - #endif - #ifdef RGFW_X11 - && XPending(_RGFW.display) == 0 - #endif - ) { - if (poll(fds, index, waitMS) <= 0) - break; - - if (waitMS != RGFW_eventWaitNext) - waitMS -= (i32)(RGFW_getTimeNS() - start) / (i32)1e+6; } - /* drain any data in the stop request */ - if (RGFW_eventWait_forceStop[2]) { - char data[64]; - (void)!read(RGFW_eventWait_forceStop[0], data, sizeof(data)); +} - RGFW_eventWait_forceStop[2] = 0; +static void RGFW_wl_xdg_toplevel_configure_handler(void* data, struct xdg_toplevel* toplevel, + i32 width, i32 height, struct wl_array* states) { + + RGFW_UNUSED(toplevel); + RGFW_window* win = (RGFW_window*)data; + + + win->src.pending_activated = RGFW_FALSE; + win->src.pending_maximized = RGFW_FALSE; + win->src.resizing = RGFW_FALSE; + + + enum xdg_toplevel_state* state; + wl_array_for_each(state, states) { + switch (*state) { + case XDG_TOPLEVEL_STATE_ACTIVATED: + win->src.pending_activated = RGFW_TRUE; + break; + case XDG_TOPLEVEL_STATE_MAXIMIZED: + win->src.pending_maximized = RGFW_TRUE; + break; + default: + break; + } + + } + /* if width and height are not zero and are not the same as the window */ + /* the window is resizing so update the values */ + if ((width && height) && (win->w != width || win->h != height)) { + win->src.resizing = RGFW_TRUE; + win->src.w = win->w = width; + win->src.h = win->h = height; } } -i32 RGFW_getClock(void); -i32 RGFW_getClock(void) { - static i32 clock = -1; - if (clock != -1) return clock; +static void RGFW_wl_xdg_toplevel_close_handler(void* data, struct xdg_toplevel *toplevel) { + RGFW_UNUSED(toplevel); + RGFW_window* win = (RGFW_window*)data; - #if defined(_POSIX_MONOTONIC_CLOCK) - struct timespec ts; - if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) - clock = CLOCK_MONOTONIC; - #else - clock = CLOCK_REALTIME; - #endif - - return clock; + if (!win->internal.shouldClose) { + RGFW_eventQueuePushEx(e.type = RGFW_quit; e.common.win = win); + RGFW_window_setShouldClose(win, RGFW_TRUE); + RGFW_windowQuitCallback(win); + } } -u64 RGFW_getTimerFreq(void) { return 1000000000LLU; } -u64 RGFW_getTimerValue(void) { +static void RGFW_wl_xdg_decoration_configure_handler(void* data, + struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, u32 mode) { + RGFW_window* win = (RGFW_window*)data; RGFW_UNUSED(zxdg_toplevel_decoration_v1); + + /* this is expected to run once */ + /* set the decoration mode set by earlier request */ + if (mode != win->src.decoration_mode) { + win->src.decoration_mode = mode; + } +} + +static void RGFW_wl_shm_format_handler(void* data, struct wl_shm *shm, u32 format) { + RGFW_UNUSED(data); RGFW_UNUSED(shm); RGFW_UNUSED(format); +} + +static void RGFW_wl_relative_pointer_motion(void *data, struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1, + u32 time_hi, u32 time_lo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel) { + + RGFW_UNUSED(zwp_relative_pointer_v1); RGFW_UNUSED(time_hi); RGFW_UNUSED(time_lo); + RGFW_UNUSED(dx_unaccel); RGFW_UNUSED(dy_unaccel); + + RGFW_info* RGFW = (RGFW_info*)data; + RGFW_window* win = RGFW->mouseOwner; + + RGFW_ASSERT(win); + + float vecX = (float)wl_fixed_to_double(dx); + float vecY = (float)wl_fixed_to_double(dy); + + RGFW_eventQueuePushEx(e.type = RGFW_mousePosChanged; + e.mouse.x = win->internal.lastMouseX; + e.mouse.y = win->internal.lastMouseY; + e.mouse.vecX = vecX; + e.mouse.vecY = vecY; + e.common.win = win); + + RGFW->vectorX = vecX; + RGFW->vectorY = vecY; + RGFW_mousePosCallback(win, win->internal.lastMouseX, win->internal.lastMouseY, vecX, vecY); +} + +static void RGFW_wl_pointer_locked(void *data, struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1) { + RGFW_UNUSED(zwp_locked_pointer_v1); + RGFW_info* RGFW = (RGFW_info*)data; + RGFW_window* win = RGFW->mouseOwner; + + win->internal.lastMouseX = win->w / 2; + win->internal.lastMouseY = win->h / 2; + zwp_locked_pointer_v1_set_cursor_position_hint(win->src.locked_pointer, wl_fixed_from_int((win->w / 2)), wl_fixed_from_int((win->h / 2))); + wl_pointer_set_cursor(RGFW->wl_pointer, RGFW->mouse_enter_serial, NULL, 0, 0); /* draw no cursor */ +} + +static void RGFW_wl_pointer_enter(void* data, struct wl_pointer* pointer, u32 serial, + struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { + RGFW_info* RGFW = (RGFW_info*)data; + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + + /* save when the pointer is locked or using default cursor */ + RGFW->mouse_enter_serial = serial; + win->internal.mouseInside = RGFW_TRUE; + RGFW->windowState.win = win; + RGFW->windowState.mouseEnter = RGFW_TRUE; + + RGFW->mouseOwner = win; + + /* set the cursor */ + if (win->src.using_custom_cursor) { + wl_pointer_set_cursor(pointer, serial, win->src.custom_cursor_surface, 0, 0); + } + else { + RGFW_window_setMouseDefault(win); + } + + if (!(win->internal.enabledEvents & RGFW_mouseEnterFlag)) return; + + i32 x = (i32)wl_fixed_to_double(surface_x); + i32 y = (i32)wl_fixed_to_double(surface_y); + + RGFW_eventQueuePushEx(e.type = RGFW_mouseEnter; + e.mouse.x = x; + e.mouse.y = y; + e.common.win = win); + + win->internal.lastMouseX = x; + win->internal.lastMouseY = y; + + RGFW_mouseNotifyCallback(win, x, y, RGFW_TRUE); +} + +static void RGFW_wl_pointer_leave(void* data, struct wl_pointer *pointer, u32 serial, struct wl_surface *surface) { + RGFW_UNUSED(pointer); RGFW_UNUSED(serial); + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + RGFW_info* RGFW = (RGFW_info*)data; + if (RGFW->mouseOwner == win) + RGFW->mouseOwner = NULL; + + win->internal.mouseInside = RGFW_FALSE; + RGFW->windowState.winLeave = win; + RGFW->windowState.mouseLeave = RGFW_TRUE; + + if (!(win->internal.enabledEvents & RGFW_mouseLeaveFlag)) return; + + RGFW_eventQueuePushEx(e.type = RGFW_mouseLeave; + e.mouse.x = win->internal.lastMouseX; + e.mouse.y = win->internal.lastMouseY; + e.common.win = win); + + RGFW_mouseNotifyCallback(win, win->internal.lastMouseX, win->internal.lastMouseY, RGFW_FALSE); +} + +static void RGFW_wl_pointer_motion(void* data, struct wl_pointer *pointer, u32 time, wl_fixed_t x, wl_fixed_t y) { + RGFW_UNUSED(pointer); RGFW_UNUSED(time); + + RGFW_info* RGFW = (RGFW_info*)data; + RGFW_ASSERT(RGFW->mouseOwner != NULL); + + RGFW_window* win = RGFW->mouseOwner; + + if (!(win->internal.enabledEvents & RGFW_mousePosChangedFlag)) return; + + i32 convertedX = (i32)wl_fixed_to_double(x); + i32 convertedY = (i32)wl_fixed_to_double(y); + float newVecX = (float)(convertedX - win->internal.lastMouseX); + float newVecY = (float)(convertedY - win->internal.lastMouseY); + + RGFW_eventQueuePushEx(e.type = RGFW_mousePosChanged; + e.mouse.x = convertedX; + e.mouse.y = convertedY; + e.mouse.vecX = newVecX; + e.mouse.vecY = newVecY; + e.common.win = win); + + RGFW->vectorX = newVecX; + RGFW->vectorY = newVecY; + win->internal.lastMouseX = convertedX; + win->internal.lastMouseY = convertedY; + RGFW_mousePosCallback(win, convertedX, convertedY, newVecX, newVecY); +} + +static void RGFW_wl_pointer_button(void* data, struct wl_pointer *pointer, u32 serial, u32 time, u32 button, u32 state) { + RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(serial); + RGFW_info* RGFW = (RGFW_info*)data; + + RGFW_ASSERT(RGFW->mouseOwner != NULL); + RGFW_window* win = RGFW->mouseOwner; + + if (!(win->internal.enabledEvents & (RGFW_BIT(RGFW_mouseButtonReleased - RGFW_BOOL(state))))) return; + u32 b = (button - 0x110); + + /* flip right and middle button codes */ + if (b == 1) b = 2; + else if (b == 2) b = 1; + + RGFW->mouseButtons[b].prev = RGFW->mouseButtons[b].current; + RGFW->mouseButtons[b].current = RGFW_BOOL(state); + + RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonReleased - RGFW_BOOL(state); + e.button.value = (u8)b; + e.common.win = win); + RGFW_mouseButtonCallback(win, (u8)b, RGFW_BOOL(state)); +} + +static void RGFW_wl_pointer_axis(void* data, struct wl_pointer *pointer, u32 time, u32 axis, wl_fixed_t value) { + RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(axis); + + RGFW_info* RGFW = (RGFW_info*)data; + RGFW_ASSERT(RGFW->mouseOwner != NULL); + RGFW_window* win = RGFW->mouseOwner; + + float scrollX = 0.0; + float scrollY = 0.0; + + if (!(win->internal.enabledEvents & (RGFW_BIT(RGFW_mouseScroll)))) return; + + if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) + scrollX = (float)(-wl_fixed_to_double(value) / 10.0); + else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) + scrollY = (float)(-wl_fixed_to_double(value) / 10.0); + + + RGFW->scrollX = (float)scrollX; + RGFW->scrollY = (float)scrollY; + RGFW_mouseScrollCallback(win, scrollX, scrollY); + RGFW_eventQueuePushEx(e.type = RGFW_mouseScroll; + e.scroll.x = scrollX; + e.scroll.y = scrollY; + e.common.win = win); +} + + +static void RGFW_doNothing(void) { } + +static void RGFW_wl_keyboard_keymap(void* data, struct wl_keyboard *keyboard, u32 format, i32 fd, u32 size) { + RGFW_UNUSED(keyboard); RGFW_UNUSED(format); + RGFW_info* RGFW = (RGFW_info*)data; + + char *keymap_string = mmap (NULL, size, PROT_READ, MAP_SHARED, fd, 0); + xkb_keymap_unref(RGFW->keymap); + RGFW->keymap = xkb_keymap_new_from_string(RGFW->xkb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + + munmap(keymap_string, size); + close(fd); + xkb_state_unref(RGFW->xkb_state); + RGFW->xkb_state = xkb_state_new(RGFW->keymap); +} + +static void RGFW_wl_keyboard_enter(void* data, struct wl_keyboard *keyboard, u32 serial, struct wl_surface *surface, struct wl_array *keys) { + RGFW_UNUSED(keyboard); RGFW_UNUSED(keys); + + RGFW_info* RGFW = (RGFW_info*)data; + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + RGFW->kbOwner = win; + + // this is to prevent race conditions + if (RGFW->data_device != NULL && win->src.data_source != NULL) { + wl_data_device_set_selection(RGFW->data_device, win->src.data_source, serial); + } + if (!(win->internal.enabledEvents & RGFW_focusInFlag)) return; + + /* is set when RGFW_window_minimize is called; if the minimize button is */ + /* pressed this flag is not set since there is no event to listen for */ + if (win->src.minimized == RGFW_TRUE) win->src.minimized = RGFW_FALSE; + + win->internal.inFocus = RGFW_TRUE; + RGFW_eventQueuePushEx(e.type = RGFW_focusIn; e.common.win = win); + RGFW_focusCallback(win, RGFW_TRUE); + + if ((win->internal.holdMouse)) RGFW_window_holdMouse(win); +} + +static void RGFW_wl_keyboard_leave(void* data, struct wl_keyboard *keyboard, u32 serial, struct wl_surface *surface) { + RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); + + RGFW_info* RGFW = (RGFW_info*)data; + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + if (RGFW->kbOwner == win) + RGFW->kbOwner = NULL; + + if (!(win->internal.enabledEvents & RGFW_focusOutFlag)) return; + + RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e.common.win = win); + RGFW_focusCallback(win, RGFW_FALSE); + RGFW_window_focusLost(win); +} + +static void RGFW_wl_keyboard_key(void* data, struct wl_keyboard *keyboard, u32 serial, u32 time, u32 key, u32 state) { + RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); + + RGFW_info* RGFW = (RGFW_info*)data; + if (RGFW->kbOwner == NULL) return; + + RGFW_window *RGFW_key_win = RGFW->kbOwner; + if (!(RGFW_key_win->internal.enabledEvents & (RGFW_BIT(RGFW_keyPressed + state)))) return; + + xkb_keysym_t keysym = xkb_state_key_get_one_sym(RGFW->xkb_state, key + 8); + + u32 RGFWkey = RGFW_apiKeyToRGFW(key + 8); + RGFW->keyboard[RGFWkey].prev = RGFW->keyboard[RGFWkey].current; + RGFW->keyboard[RGFWkey].current = RGFW_BOOL(state); + + RGFW_eventQueuePushEx(e.type = (u8)(RGFW_keyPressed + state); + e.key.value = (u8)RGFWkey; + e.key.sym = (u8)keysym; + e.key.repeat = RGFW_window_isKeyDown(RGFW_key_win, (u8)RGFWkey); + e.common.win = RGFW_key_win); + + RGFW_updateKeyMods(RGFW_key_win, RGFW_BOOL(xkb_keymap_mod_get_index(RGFW->keymap, "Lock")), RGFW_BOOL(xkb_keymap_mod_get_index(RGFW->keymap, "Mod2")), RGFW_BOOL(xkb_keymap_mod_get_index(RGFW->keymap, "ScrollLock"))); + RGFW_keyCallback(RGFW_key_win, (u8)RGFWkey, (u8)keysym, RGFW_key_win->internal.mod, RGFW_window_isKeyDown(RGFW_key_win, (u8)RGFWkey), RGFW_BOOL(state)); +} + +static void RGFW_wl_keyboard_modifiers(void* data, struct wl_keyboard *keyboard, u32 serial, u32 mods_depressed, u32 mods_latched, u32 mods_locked, u32 group) { + RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); + RGFW_info* RGFW = (RGFW_info*)data; + xkb_state_update_mask(RGFW->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); +} + +static void RGFW_wl_seat_capabilities(void* data, struct wl_seat *seat, u32 capabilities) { + RGFW_info* RGFW = (RGFW_info*)data; + static struct wl_pointer_listener pointer_listener; + RGFW_MEMSET(&pointer_listener, 0, sizeof(pointer_listener)); + pointer_listener.enter = &RGFW_wl_pointer_enter; + pointer_listener.leave = &RGFW_wl_pointer_leave; + pointer_listener.motion = &RGFW_wl_pointer_motion; + pointer_listener.button = &RGFW_wl_pointer_button; + pointer_listener.axis = &RGFW_wl_pointer_axis; + + static struct wl_keyboard_listener keyboard_listener; + RGFW_MEMSET(&keyboard_listener, 0, sizeof(keyboard_listener)); + keyboard_listener.keymap = &RGFW_wl_keyboard_keymap; + keyboard_listener.enter = &RGFW_wl_keyboard_enter; + keyboard_listener.leave = &RGFW_wl_keyboard_leave; + keyboard_listener.key = &RGFW_wl_keyboard_key; + keyboard_listener.modifiers = &RGFW_wl_keyboard_modifiers; + + if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && !RGFW->wl_pointer) { + RGFW->wl_pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(RGFW->wl_pointer, &pointer_listener, RGFW); + } + if ((capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && !RGFW->wl_keyboard) { + RGFW->wl_keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(RGFW->wl_keyboard, &keyboard_listener, RGFW); + } + + if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && RGFW->wl_pointer) { + wl_pointer_destroy(RGFW->wl_pointer); + } + if (!(capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && RGFW->wl_keyboard) { + wl_keyboard_destroy(RGFW->wl_keyboard); + } +} + +static void RGFW_wl_output_set_geometry(void *data, struct wl_output *wl_output, + int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, + int32_t subpixel, const char *make, const char *model, int32_t transform) { + + RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; + monitor->x = x; + monitor->y = y; + + monitor->physW = (float)physical_width / 25.4f; + monitor->physH = (float)physical_height / 25.4f; + + RGFW_UNUSED(wl_output); + RGFW_UNUSED(subpixel); + RGFW_UNUSED(make); + RGFW_UNUSED(model); + RGFW_UNUSED(transform); +} + +static void RGFW_wl_output_set_mode(void *data, struct wl_output *wl_output, uint32_t flags, + int32_t width, int32_t height, int32_t refresh) { + + RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; + + monitor->mode.w = width; + monitor->mode.h = height; + monitor->mode.refreshRate = (u32)RGFW_ROUND( ((float)refresh / 1000) ); + RGFW_UNUSED(width); + RGFW_UNUSED(height); + RGFW_UNUSED(wl_output); + RGFW_UNUSED(flags); +} + +static void RGFW_wl_output_set_scale(void *data, struct wl_output *wl_output, int32_t factor) { + /* this is for pixelRatio */ + RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; + + monitor->pixelRatio = (float)factor; + RGFW_UNUSED(wl_output); +} + +static void RGFW_wl_output_set_name(void *data, struct wl_output *wl_output, const char *name) { + RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; + + RGFW_STRNCPY(monitor->name, name, sizeof(monitor->name) - 1); + monitor->name[sizeof(monitor->name) - 1] = '\0'; + + RGFW_UNUSED(wl_output); + +} + +static void RGFW_xdg_output_logical_pos(void *data, struct zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y) { + RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; + monitor->x = x; + monitor->y = y; + RGFW_UNUSED(zxdg_output_v1); +} + +static void RGFW_xdg_output_logical_size(void *data, struct zxdg_output_v1 *zxdg_output_v1, int32_t width, int32_t height) { + RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; + + float mon_float_width = (float) monitor->mode.w; + float mon_float_height = (float) monitor->mode.h; + + monitor->scaleX = (mon_float_width / (float) width); + monitor->scaleY = (mon_float_height / (float) height); + + /* under xwayland the monitor changes w & h when compositor scales it */ + monitor->mode.w = width; + monitor->mode.h = height; + RGFW_UNUSED(zxdg_output_v1); +} + +static void RGFW_wl_create_outputs(struct wl_registry *const registry, uint32_t id) { + struct wl_output *output = wl_registry_bind(registry, id, &wl_output_interface, wl_display_get_version(_RGFW->wl_display) < 4 ? 3 : 4); + RGFW_monitorNode* node; + RGFW_monitor mon; + + if (!output) return; + + char RGFW_mon_default_name[10]; + + RGFW_SNPRINTF(RGFW_mon_default_name, sizeof(RGFW_mon_default_name), "monitor-%li", _RGFW->monitors.count); + RGFW_STRNCPY(mon.name, RGFW_mon_default_name, sizeof(mon.name) - 1); + mon.name[sizeof(mon.name) - 1] = '\0'; + + /* set in case compositor does not send one */ + /* or no xdg_output support */ + mon.scaleY = mon.scaleX = mon.pixelRatio = 1.0f; + + node = RGFW_monitors_add(mon); + if (node == NULL) return; + + node->id = id; + node->output = output; + + static const struct wl_output_listener wl_output_listener = { + .geometry = RGFW_wl_output_set_geometry, + .mode = RGFW_wl_output_set_mode, + .done = (void (*)(void *,struct wl_output *))&RGFW_doNothing, + .scale = RGFW_wl_output_set_scale, + .name = RGFW_wl_output_set_name, + .description = (void (*)(void *, struct wl_output *, const char *))&RGFW_doNothing + }; + + /* the wl_output will have a reference to the node */ + wl_output_set_user_data(output, node); + + /* pass the monitor so we can access it in the callback functions */ + wl_output_add_listener(output, &wl_output_listener, node); + + if (!_RGFW->xdg_output_manager) return; /* compositor does not support it */ + + static const struct zxdg_output_v1_listener xdg_output_listener = { + .name = (void (*)(void *,struct zxdg_output_v1 *, const char *))&RGFW_doNothing, + .done = (void (*)(void *,struct zxdg_output_v1 *))&RGFW_doNothing, + .description = (void (*)(void *,struct zxdg_output_v1 *, const char *))&RGFW_doNothing, + .logical_position = RGFW_xdg_output_logical_pos, + .logical_size = RGFW_xdg_output_logical_size + }; + + node->xdg_output = zxdg_output_manager_v1_get_xdg_output(_RGFW->xdg_output_manager, node->output); + zxdg_output_v1_add_listener(node->xdg_output, &xdg_output_listener, node); +} + +static void RGFW_wl_surface_enter(void *data, struct wl_surface *wl_surface, struct wl_output *output) { + RGFW_UNUSED(wl_surface); + + RGFW_window* win = (RGFW_window*)data; + RGFW_monitorNode* node = wl_output_get_user_data(output); + win->src.active_monitor = node->mon; + + #ifndef RGFW_NO_MONITOR + if (win->internal.flags & RGFW_windowScaleToMonitor) + RGFW_window_scaleToMonitor(win); + #endif +} + +static void RGFW_wl_data_source_send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, int32_t fd) { + RGFW_UNUSED(data); RGFW_UNUSED(wl_data_source); + + // a client can accept our clipboard + if (RGFW_STRNCMP(mime_type, "text/plain;charset=utf-8", 25) == 0) { + // do not write \0 + write(fd, _RGFW->clipboard, _RGFW->clipboard_len - 1); + } + + close(fd); +} + +static void RGFW_wl_data_source_cancelled(void *data, struct wl_data_source *wl_data_source) { + + RGFW_info* RGFW = (RGFW_info*)data; + + if (RGFW->kbOwner->src.data_source == wl_data_source) { + RGFW->kbOwner->src.data_source = NULL; + } + + wl_data_source_destroy(wl_data_source); + +} + +static void RGFW_wl_data_device_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *wl_data_offer) { + + RGFW_UNUSED(data); RGFW_UNUSED(wl_data_device); + static const struct wl_data_offer_listener wl_data_offer_listener = { + .offer = (void (*)(void *data, struct wl_data_offer *wl_data_offer, const char *))RGFW_doNothing, + .source_actions = (void (*)(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action))RGFW_doNothing, + .action = (void (*)(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action))RGFW_doNothing + }; + wl_data_offer_add_listener(wl_data_offer, &wl_data_offer_listener, NULL); +} + +static void RGFW_wl_data_device_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *wl_data_offer) { + RGFW_UNUSED(data); RGFW_UNUSED(wl_data_device); + /* Clipboard is empty */ + if (wl_data_offer == NULL) { + return; + } + + int pfds[2]; + pipe(pfds); + + wl_data_offer_receive(wl_data_offer, "text/plain;charset=utf-8", pfds[1]); + close(pfds[1]); + + wl_display_roundtrip(_RGFW->wl_display); + + char buf[1024]; + + ssize_t n = read(pfds[0], buf, sizeof(buf)); + + _RGFW->clipboard = (char*)RGFW_ALLOC((size_t)n); + RGFW_ASSERT(_RGFW->clipboard != NULL); + RGFW_STRNCPY(_RGFW->clipboard, buf, (size_t)n); + + _RGFW->clipboard_len = (size_t)n + 1; + + close(pfds[0]); + + wl_data_offer_destroy(wl_data_offer); + +} + +static void RGFW_wl_global_registry_handler(void* data, struct wl_registry *registry, u32 id, const char *interface, u32 version) { + + static struct wl_seat_listener seat_listener = {&RGFW_wl_seat_capabilities, (void (*)(void *, struct wl_seat *, const char *))&RGFW_doNothing}; + static const struct wl_shm_listener shm_listener = { .format = RGFW_wl_shm_format_handler }; + + RGFW_info* RGFW = (RGFW_info*)data; + RGFW_UNUSED(version); + + if (RGFW_STRNCMP(interface, "wl_compositor", 16) == 0) { + RGFW->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 4); + } else if (RGFW_STRNCMP(interface, "xdg_wm_base", 12) == 0) { + RGFW->xdg_wm_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1); + } else if (RGFW_STRNCMP(interface, zxdg_decoration_manager_v1_interface.name, 255) == 0) { + RGFW->decoration_manager = wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1); + } else if (RGFW_STRNCMP(interface, zwp_pointer_constraints_v1_interface.name, 255) == 0) { + RGFW->constraint_manager = wl_registry_bind(registry, id, &zwp_pointer_constraints_v1_interface, 1); + } else if (RGFW_STRNCMP(interface, zwp_relative_pointer_manager_v1_interface.name, 255) == 0) { + RGFW->relative_pointer_manager = wl_registry_bind(registry, id, &zwp_relative_pointer_manager_v1_interface, 1); + } else if (RGFW_STRNCMP(interface, xdg_toplevel_icon_manager_v1_interface.name, 255) == 0) { + RGFW->icon_manager = wl_registry_bind(registry, id, &xdg_toplevel_icon_manager_v1_interface, 1); + } else if (RGFW_STRNCMP(interface, "wl_shm", 7) == 0) { + RGFW->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); + wl_shm_add_listener(RGFW->shm, &shm_listener, RGFW); + } else if (RGFW_STRNCMP(interface,"wl_seat", 8) == 0) { + RGFW->seat = wl_registry_bind(registry, id, &wl_seat_interface, 1); + wl_seat_add_listener(RGFW->seat, &seat_listener, RGFW); + } else if (RGFW_STRNCMP(interface, zxdg_output_manager_v1_interface.name, 255) == 0) { + RGFW->xdg_output_manager = wl_registry_bind(registry, id, &zxdg_output_manager_v1_interface, 1); + } else if (RGFW_STRNCMP(interface,"wl_output", 10) == 0) { + RGFW_wl_create_outputs(registry, id); + } else if (RGFW_STRNCMP(interface,"wl_data_device_manager", 23) == 0) { + RGFW->data_device_manager = wl_registry_bind(registry, id, &wl_data_device_manager_interface, 1); + } +} + +static void RGFW_wl_global_registry_remove(void* data, struct wl_registry *registry, u32 id) { + RGFW_UNUSED(data); RGFW_UNUSED(registry); + RGFW_info* RGFW = (RGFW_info*)data; + RGFW_monitorNode* prev = RGFW->monitors.list.head; + RGFW_monitorNode* node = NULL; + if (prev == NULL) return; + + if (prev->id != id) { + /* find the first node that has a matching id */ + while(prev->next != NULL && prev->next->id != id) { + prev = prev->next; + } + + if (prev->next == NULL) return; + node = prev->next; + } else { + node = prev; + } + + if (node->output) { + wl_output_destroy(node->output); + } + + if (node->xdg_output) { + zxdg_output_v1_destroy(node->xdg_output); + } + + RGFW_monitors_remove(node, prev); +} + +static void RGFW_wl_randname(char *buf) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); - return (u64)ts.tv_sec * RGFW_getTimerFreq() + (u64)ts.tv_nsec; + long r = ts.tv_nsec; + + int i; + for (i = 0; i < 6; ++i) { + buf[i] = (char)('A'+(r&15)+(r&16)*2); + r >>= 5; + } +} + +static size_t RGFW_wl_stringlen(char* name) { + size_t i = 0; + while (name[i]) { i++; } + return i; +} + +static int RGFW_wl_anonymous_shm_open(void) { + char name[] = "/RGFW-wayland-XXXXXX"; + int retries = 100; + + do { + RGFW_wl_randname(name + RGFW_wl_stringlen(name) - 6); + + --retries; + /* shm_open guarantees that O_CLOEXEC is set */ + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + + return -1; +} + +static int RGFW_wl_create_shm_file(off_t size) { + int fd = RGFW_wl_anonymous_shm_open(); + if (fd < 0) { + return fd; + } + + if (ftruncate(fd, size) < 0) { + close(fd); + return -1; + } + + return fd; +} + +i32 RGFW_initPlatform_Wayland(void) { + _RGFW->wl_display = wl_display_connect(NULL); + if (_RGFW->wl_display == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errWayland, "Failed to load Wayland display"); + return -1; + } + + _RGFW->compositor = NULL; + static const struct wl_registry_listener registry_listener = { + .global = RGFW_wl_global_registry_handler, + .global_remove = RGFW_wl_global_registry_remove, + }; + + _RGFW->registry = wl_display_get_registry(_RGFW->wl_display); + wl_registry_add_listener(_RGFW->registry, ®istry_listener, _RGFW); + + wl_display_roundtrip(_RGFW->wl_display); /* bind to globals */ + + if (_RGFW->compositor == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errWayland, "Can't find compositor."); + return 1; + } + + if (_RGFW->wl_cursor_theme == NULL) { + _RGFW->wl_cursor_theme = wl_cursor_theme_load(NULL, 24, _RGFW->shm); + _RGFW->cursor_surface = wl_compositor_create_surface(_RGFW->compositor); + } + + u8 RGFW_blk[] = { 0, 0, 0, 0 }; + _RGFW->hiddenMouse = RGFW_loadMouse(RGFW_blk, 1, 1, RGFW_formatRGBA8); + + static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = RGFW_wl_xdg_wm_base_ping_handler, + }; + + xdg_wm_base_add_listener(_RGFW->xdg_wm_base, &xdg_wm_base_listener, NULL); + + _RGFW->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + static const struct wl_data_device_listener wl_data_device_listener = { + .data_offer = RGFW_wl_data_device_data_offer, + .enter = (void (*)(void *, struct wl_data_device *, u32, struct wl_surface*, wl_fixed_t, wl_fixed_t, struct wl_data_offer *))&RGFW_doNothing, + .leave = (void (*)(void *, struct wl_data_device *))&RGFW_doNothing, + .motion = (void (*)(void *, struct wl_data_device *, u32, wl_fixed_t, wl_fixed_t))&RGFW_doNothing, + .drop = (void (*)(void *, struct wl_data_device *))&RGFW_doNothing, + .selection = RGFW_wl_data_device_selection + }; + + if (_RGFW->seat && _RGFW->data_device_manager) { + _RGFW->data_device = wl_data_device_manager_get_data_device(_RGFW->data_device_manager, _RGFW->seat); + wl_data_device_add_listener(_RGFW->data_device, &wl_data_device_listener, NULL); + } + + return 0; +} + +void RGFW_deinitPlatform_Wayland(void) { + if (_RGFW->clipboard) { + RGFW_FREE(_RGFW->clipboard); + _RGFW->clipboard = NULL; + } + + if (_RGFW->wl_pointer) { + wl_pointer_destroy(_RGFW->wl_pointer); + } + if (_RGFW->wl_keyboard) { + wl_keyboard_destroy(_RGFW->wl_keyboard); + } + + wl_registry_destroy(_RGFW->registry); + if (_RGFW->decoration_manager != NULL) + zxdg_decoration_manager_v1_destroy(_RGFW->decoration_manager); + if (_RGFW->relative_pointer_manager != NULL) { + zwp_relative_pointer_manager_v1_destroy(_RGFW->relative_pointer_manager); + } + + if (_RGFW->relative_pointer) { + zwp_relative_pointer_v1_destroy(_RGFW->relative_pointer); + } + + if (_RGFW->constraint_manager != NULL) { + zwp_pointer_constraints_v1_destroy(_RGFW->constraint_manager); + } + + if (_RGFW->xdg_output_manager != NULL) + if (_RGFW->icon_manager != NULL) { + xdg_toplevel_icon_manager_v1_destroy(_RGFW->icon_manager); + } + + if (_RGFW->xdg_output_manager) { + zxdg_output_manager_v1_destroy(_RGFW->xdg_output_manager); + } + + if (_RGFW->data_device_manager) { + wl_data_device_manager_destroy(_RGFW->data_device_manager); + } + + if (_RGFW->data_device) { + wl_data_device_destroy(_RGFW->data_device); + } + + if (_RGFW->wl_cursor_theme != NULL) { + wl_cursor_theme_destroy(_RGFW->wl_cursor_theme); + } + + RGFW_freeMouse(_RGFW->hiddenMouse); + + RGFW_monitorNode* node = _RGFW->monitors.list.head; + + while (node != NULL) { + if (node->output) { + wl_output_destroy(node->output); + } + + if (node->xdg_output) { + zxdg_output_v1_destroy(node->xdg_output); + } + + _RGFW->monitors.count -= 1; + node = node->next; + + } + + wl_surface_destroy(_RGFW->cursor_surface); + wl_shm_destroy(_RGFW->shm); + wl_seat_release(_RGFW->seat); + xdg_wm_base_destroy(_RGFW->xdg_wm_base); + wl_compositor_destroy(_RGFW->compositor); + wl_display_disconnect(_RGFW->wl_display); +} + +RGFW_bool RGFW_FUNC(RGFW_createSurfacePtr) (u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { + RGFW_ASSERT(surface != NULL); + surface->data = data; + surface->w = w; + surface->h = h; + surface->format = format; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoBuffer, "Creating a 4 channel buffer"); + + u32 size = (u32)(surface->w * surface->h * 4); + int fd = RGFW_wl_create_shm_file(size); + if (fd < 0) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, "Failed to create a buffer."); + return RGFW_FALSE; + } + + surface->native.buffer = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (surface->native.buffer == MAP_FAILED) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, "mmap failed."); + return RGFW_FALSE; + } + + struct wl_shm_pool* pool = wl_shm_create_pool(_RGFW->shm, fd, (i32)size); + surface->native.wl_buffer = wl_shm_pool_create_buffer(pool, 0, (i32)surface->w, (i32)surface->h, (i32)surface->w * 4, WL_SHM_FORMAT_ARGB8888); + wl_shm_pool_destroy(pool); + + close(fd); + + surface->native.format = RGFW_formatBGRA8; + return RGFW_TRUE; +} + +void RGFW_FUNC(RGFW_window_blitSurface) (RGFW_window* win, RGFW_surface* surface) { + RGFW_ASSERT(surface != NULL); + RGFW_copyImageData(surface->native.buffer, win->w, RGFW_MIN(win->h, surface->h), surface->native.format, surface->data, surface->format); + + wl_surface_attach(win->src.surface, surface->native.wl_buffer, 0, 0); + wl_surface_damage(win->src.surface, 0, 0, RGFW_MIN(win->w, surface->w), RGFW_MIN(win->h, surface->h)); + wl_surface_commit(win->src.surface); +} + +void RGFW_FUNC(RGFW_surface_freePtr) (RGFW_surface* surface) { + RGFW_ASSERT(surface != NULL); + wl_buffer_destroy(surface->native.wl_buffer); + munmap(surface->native.buffer, (size_t)(surface->w * surface->h * 4)); +} + +void RGFW_FUNC(RGFW_window_setBorder) (RGFW_window* win, RGFW_bool border) { + RGFW_setBit(&win->internal.flags, RGFW_windowNoBorder, !border); + + /* for now just toggle between SSD & CSD depending on the bool */ + if (_RGFW->decoration_manager != NULL) { + zxdg_toplevel_decoration_v1_set_mode(win->src.decoration, (border ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE)); + } +} + +void RGFW_FUNC(RGFW_releaseCursor) (RGFW_window* win) { + RGFW_ASSERT(win); + /* compositor has no support or window is not locked do nothing */ + if (_RGFW->constraint_manager == NULL || _RGFW->relative_pointer_manager == NULL) return; + + if (win->src.locked_pointer != NULL) { + zwp_locked_pointer_v1_destroy(win->src.locked_pointer); + win->src.locked_pointer = NULL; + } + if (_RGFW->relative_pointer != NULL) { + zwp_relative_pointer_v1_destroy(_RGFW->relative_pointer); + _RGFW->relative_pointer = NULL; + } + + _RGFW->mouseOwner = win; /* unhold mouse sets this to null; set it back */ +} + +void RGFW_FUNC(RGFW_captureCursor) (RGFW_window* win) { + RGFW_ASSERT(win); + /* compositor has no support or window already is locked do nothing */ + if (_RGFW->constraint_manager == NULL || _RGFW->relative_pointer_manager == NULL) return; + + if (_RGFW->relative_pointer == NULL) { + _RGFW->relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(_RGFW->relative_pointer_manager, _RGFW->wl_pointer); + + static const struct zwp_relative_pointer_v1_listener relative_motion_listener = { + .relative_motion = RGFW_wl_relative_pointer_motion + }; + + zwp_relative_pointer_v1_add_listener(_RGFW->relative_pointer, &relative_motion_listener, _RGFW); + } + + if (win->src.locked_pointer == NULL) { + win->src.locked_pointer = zwp_pointer_constraints_v1_lock_pointer(_RGFW->constraint_manager, win->src.surface, _RGFW->wl_pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + + static const struct zwp_locked_pointer_v1_listener locked_listener = { + .locked = RGFW_wl_pointer_locked, + .unlocked = (void (*)(void *, struct zwp_locked_pointer_v1 *))RGFW_doNothing + }; + + zwp_locked_pointer_v1_add_listener(win->src.locked_pointer, &locked_listener, _RGFW); + } +} + +RGFW_window* RGFW_FUNC(RGFW_createWindowPlatform) (const char* name, RGFW_windowFlags flags, RGFW_window* win) { + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningWayland, "RGFW Wayland support is experimental"); + + static const struct xdg_surface_listener xdg_surface_listener = { + .configure = RGFW_wl_xdg_surface_configure_handler, + }; + + static const struct wl_surface_listener wl_surface_listener = { + .enter = RGFW_wl_surface_enter, + .leave = (void (*)(void *, struct wl_surface *, struct wl_output *))&RGFW_doNothing, + .preferred_buffer_scale = (void (*)(void *, struct wl_surface *, i32))&RGFW_doNothing, + .preferred_buffer_transform = (void (*)(void *, struct wl_surface *, u32))&RGFW_doNothing + }; + + win->src.surface = wl_compositor_create_surface(_RGFW->compositor); + wl_surface_add_listener(win->src.surface, &wl_surface_listener, win); + + /* create a surface for a custom cursor */ + win->src.custom_cursor_surface = wl_compositor_create_surface(_RGFW->compositor); + + win->src.xdg_surface = xdg_wm_base_get_xdg_surface(_RGFW->xdg_wm_base, win->src.surface); + xdg_surface_add_listener(win->src.xdg_surface, &xdg_surface_listener, win); + + xdg_wm_base_set_user_data(_RGFW->xdg_wm_base, win); + + win->src.xdg_toplevel = xdg_surface_get_toplevel(win->src.xdg_surface); + + xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->w, win->h); + + if (!(win->internal.flags & RGFW_windowTransparent)) { /* no transparency */ + RGFW_wl_setOpaque(win); + } + + static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = RGFW_wl_xdg_toplevel_configure_handler, + .close = RGFW_wl_xdg_toplevel_close_handler, + }; + + xdg_toplevel_add_listener(win->src.xdg_toplevel, &xdg_toplevel_listener, win); + + /* compositor supports both SSD & CSD + So choose accordingly + */ + if (_RGFW->decoration_manager) { + u32 decoration_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; + win->src.decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( + _RGFW->decoration_manager, win->src.xdg_toplevel); + + static const struct zxdg_toplevel_decoration_v1_listener xdg_decoration_listener = { + .configure = RGFW_wl_xdg_decoration_configure_handler + }; + + zxdg_toplevel_decoration_v1_add_listener(win->src.decoration, &xdg_decoration_listener, win); + + /* we want no decorations */ + if ((flags & RGFW_windowNoBorder)) { + decoration_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; + } + + zxdg_toplevel_decoration_v1_set_mode(win->src.decoration, decoration_mode); + + /* no xdg_decoration support */ + } else if (!(flags & RGFW_windowNoBorder)) { + /* TODO, some fallback */ + #ifdef RGFW_LIBDECOR + static struct libdecor_interface interface = { + .error = NULL, + }; + + static struct libdecor_frame_interface frameInterface = {0}; /*= { + RGFW_wl_handle_configure, + RGFW_wl_handle_close, + RGFW_wl_handle_commit, + RGFW_wl_handle_dismiss_popup, + };*/ + + win->src.decorContext = libdecor_new(_RGFW->wl_display, &interface); + if (win->src.decorContext) { + struct libdecor_frame *frame = libdecor_decorate(win->src.decorContext, win->src.surface, &frameInterface, win); + if (!frame) { + libdecor_unref(win->src.decorContext); + win->src.decorContext = NULL; + } else { + libdecor_frame_set_app_id(frame, "my-libdecor-app"); + libdecor_frame_set_title(frame, "My Libdecor Window"); + } + } + #endif + } + + if (_RGFW->icon_manager != NULL) { + /* set the default wayland icon */ + xdg_toplevel_icon_manager_v1_set_icon(_RGFW->icon_manager, win->src.xdg_toplevel, NULL); + } + + wl_surface_commit(win->src.surface); + wl_display_dispatch(_RGFW->wl_display); + RGFW_UNUSED(name); + + return win; +} + +RGFW_bool RGFW_FUNC(RGFW_getGlobalMouse) (i32* x, i32* y) { + RGFW_init(); + if (x) *x = 0; + if (y) *y = 0; + return RGFW_FALSE; +} + +u8 RGFW_FUNC(RGFW_rgfwToKeyChar)(u32 key) { + return (u8)key; +} + +void RGFW_FUNC(RGFW_pollEvents) (void) { + RGFW_resetPrevState(); + + /* send buffered requests to compositor */ + while (wl_display_flush(_RGFW->wl_display) == -1) { + /* compositor not responding to new requests */ + /* so let's dispatch some events so the compositor responds */ + if (errno == EAGAIN) { + if (wl_display_dispatch_pending(_RGFW->wl_display) == -1) { + return; + } + } else { + return; + } + } + + /* read the events; if empty this reads from the */ + /* wayland file descriptor */ + if (wl_display_dispatch(_RGFW->wl_display) == -1) { + return; + } + +} + +void RGFW_FUNC(RGFW_window_move) (RGFW_window* win, i32 x, i32 y) { + RGFW_ASSERT(win != NULL); + win->x = x; + win->y = y; } -#endif /* end of wayland or X11 defines */ +void RGFW_FUNC(RGFW_window_resize) (RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + win->w = w; + win->h = h; + if (_RGFW->compositor) { + xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->w, win->h); + #ifdef RGFW_OPENGL + if (win->src.ctx.egl) + wl_egl_window_resize(win->src.ctx.egl->eglWindow, (i32)w, (i32)h, 0, 0); + #endif + } +} + +void RGFW_FUNC(RGFW_window_setAspectRatio) (RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + + if (w == 0 && h == 0) + return; + xdg_toplevel_set_max_size(win->src.xdg_toplevel, (i32)w, (i32)h); +} + +void RGFW_FUNC(RGFW_window_setMinSize) (RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + xdg_toplevel_set_min_size(win->src.xdg_toplevel, w, h); +} + +void RGFW_FUNC(RGFW_window_setMaxSize) (RGFW_window* win, i32 w, i32 h) { + RGFW_ASSERT(win != NULL); + xdg_toplevel_set_max_size(win->src.xdg_toplevel, w, h); +} + +void RGFW_toggleWaylandMaximized(RGFW_window* win, RGFW_bool maximized) { + win->src.maximized = maximized; + if (maximized) { + xdg_toplevel_set_maximized(win->src.xdg_toplevel); + } else { + xdg_toplevel_unset_maximized(win->src.xdg_toplevel); + } +} + +void RGFW_FUNC(RGFW_window_maximize) (RGFW_window* win) { + win->internal.oldX = win->x; + win->internal.oldY = win->y; + win->internal.oldW = win->w; + win->internal.oldH = win->h; + RGFW_toggleWaylandMaximized(win, 1); + return; +} + +void RGFW_FUNC(RGFW_window_focus)(RGFW_window* win) { + RGFW_ASSERT(win); +} + +void RGFW_FUNC(RGFW_window_raise)(RGFW_window* win) { + RGFW_ASSERT(win); +} + +void RGFW_FUNC(RGFW_window_setFullscreen)(RGFW_window* win, RGFW_bool fullscreen) { + RGFW_ASSERT(win != NULL); + if (fullscreen) { + + win->internal.flags |= RGFW_windowFullscreen; + win->internal.oldX = win->x; + win->internal.oldY = win->y; + win->internal.oldW = win->w; + win->internal.oldH = win->h; + xdg_toplevel_set_fullscreen(win->src.xdg_toplevel, NULL); /* let the compositor decide */ + } else { + win->internal.flags &= ~(u32)RGFW_windowFullscreen; + xdg_toplevel_unset_fullscreen(win->src.xdg_toplevel); + } + +} + +void RGFW_FUNC(RGFW_window_setFloating) (RGFW_window* win, RGFW_bool floating) { + RGFW_ASSERT(win != NULL); + RGFW_UNUSED(floating); +} + +void RGFW_FUNC(RGFW_window_setOpacity) (RGFW_window* win, u8 opacity) { + RGFW_ASSERT(win != NULL); + RGFW_UNUSED(opacity); +} + +void RGFW_FUNC(RGFW_window_minimize)(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + if (RGFW_window_isMaximized(win)) return; + win->internal.oldX = win->x; + win->internal.oldY = win->y; + win->internal.oldW = win->w; + win->internal.oldH = win->h; + win->src.minimized = RGFW_TRUE; + xdg_toplevel_set_minimized(win->src.xdg_toplevel); +} + +void RGFW_FUNC(RGFW_window_restore)(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_toggleWaylandMaximized(win, RGFW_FALSE); + + RGFW_window_move(win, win->internal.oldX, win->internal.oldY); + RGFW_window_resize(win, win->internal.oldW, win->internal.oldH); + + RGFW_window_show(win); + RGFW_window_move(win, win->internal.oldX, win->internal.oldY); + RGFW_window_resize(win, win->internal.oldW, win->internal.oldH); + + RGFW_window_show(win); +} + +RGFW_bool RGFW_FUNC(RGFW_window_isFloating)(RGFW_window* win) { + return (!RGFW_window_isFullscreen(win) && !RGFW_window_isMaximized(win)); +} + +void RGFW_FUNC(RGFW_window_setName) (RGFW_window* win, const char* name) { + RGFW_ASSERT(win != NULL); + if (_RGFW->compositor) + xdg_toplevel_set_title(win->src.xdg_toplevel, name); +} + +#ifndef RGFW_NO_PASSTHROUGH +void RGFW_FUNC(RGFW_window_setMousePassthrough) (RGFW_window* win, RGFW_bool passthrough) { + RGFW_ASSERT(win != NULL); + RGFW_UNUSED(passthrough); +} +#endif /* RGFW_NO_PASSTHROUGH */ + +RGFW_bool RGFW_FUNC(RGFW_window_setIconEx) (RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_icon type) { + RGFW_ASSERT(win != NULL); + RGFW_UNUSED(type); + + if (_RGFW->icon_manager == NULL || w != h) return RGFW_FALSE; + + if (win->src.icon) { + xdg_toplevel_icon_v1_destroy(win->src.icon); + win->src.icon= NULL; + } + + RGFW_surface* surface = RGFW_createSurface(data, w, h, format); + + if (surface == NULL) return RGFW_FALSE; + + RGFW_copyImageData(surface->native.buffer, RGFW_MIN(w, surface->w), RGFW_MIN(h, surface->h), surface->native.format, surface->data, surface->format); + + win->src.icon = xdg_toplevel_icon_manager_v1_create_icon(_RGFW->icon_manager); + xdg_toplevel_icon_v1_add_buffer(win->src.icon, surface->native.wl_buffer, 1); + xdg_toplevel_icon_manager_v1_set_icon(_RGFW->icon_manager, win->src.xdg_toplevel, win->src.icon); + + RGFW_surface_free(surface); + return RGFW_TRUE; +} + +RGFW_mouse* RGFW_FUNC(RGFW_loadMouse)(u8* data, i32 w, i32 h, RGFW_format format) { + + RGFW_surface *mouse_surface = RGFW_createSurface(data, w, h, format); + + if (mouse_surface == NULL) return NULL; + + RGFW_copyImageData(mouse_surface->native.buffer, RGFW_MIN(w, mouse_surface->w), RGFW_MIN(h, mouse_surface->h), mouse_surface->native.format, mouse_surface->data, mouse_surface->format); + + return (void*) mouse_surface; +} + +void RGFW_FUNC(RGFW_window_setMouse)(RGFW_window* win, RGFW_mouse* mouse) { + RGFW_ASSERT(win); RGFW_ASSERT(mouse); + RGFW_surface *mouse_surface = (RGFW_surface*)mouse; + + win->src.using_custom_cursor = RGFW_TRUE; + + struct wl_buffer *mouse_buffer = mouse_surface->native.wl_buffer; + + wl_surface_attach(win->src.custom_cursor_surface, mouse_buffer, 0, 0); + wl_surface_damage(win->src.custom_cursor_surface, 0, 0, mouse_surface->w, mouse_surface->h); + wl_surface_commit(win->src.custom_cursor_surface); + +} + +void RGFW_FUNC(RGFW_freeMouse)(RGFW_mouse* mouse) { + if (mouse != NULL) { + RGFW_surface_free((RGFW_surface*)mouse); + } +} + +void RGFW_FUNC(RGFW_window_moveMouse)(RGFW_window* win, i32 x, i32 y) { + RGFW_UNUSED(win); RGFW_UNUSED(x); RGFW_UNUSED(y); +} + +RGFW_bool RGFW_FUNC(RGFW_window_setMouseDefault)(RGFW_window* win) { + return RGFW_window_setMouseStandard(win, RGFW_mouseArrow); +} + +RGFW_bool RGFW_FUNC(RGFW_window_setMouseStandard)(RGFW_window* win, u8 mouse) { + RGFW_ASSERT(win != NULL); + static const char* iconStrings[16] = { "arrow", "left_ptr", "xterm", "crosshair", "hand2", "sb_h_double_arrow", "sb_v_double_arrow", "bottom_left_corner", "bottom_right_corner", "fleur", "forbidden" }; + + win->src.using_custom_cursor = RGFW_FALSE; + + if (mouse > RGFW_mouseIconCount - 1) return RGFW_FALSE; + + struct wl_cursor* wlcursor = wl_cursor_theme_get_cursor(_RGFW->wl_cursor_theme, iconStrings[mouse]); + struct wl_cursor_image* cursor_image = wlcursor->images[0]; + struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(cursor_image); + wl_pointer_set_cursor(_RGFW->wl_pointer, _RGFW->mouse_enter_serial, _RGFW->cursor_surface, (i32)cursor_image->hotspot_x, (i32)cursor_image->hotspot_y); + wl_surface_attach(_RGFW->cursor_surface, cursor_buffer, 0, 0); + wl_surface_damage(_RGFW->cursor_surface, 0, 0, (i32)cursor_image->width, (i32)cursor_image->height); + wl_surface_commit(_RGFW->cursor_surface); + return RGFW_TRUE; +} + +void RGFW_FUNC(RGFW_window_hide) (RGFW_window* win) { + wl_surface_attach(win->src.surface, NULL, 0, 0); + wl_surface_commit(win->src.surface); + win->internal.flags |= RGFW_windowHide; +} + +void RGFW_FUNC(RGFW_window_show) (RGFW_window* win) { + win->internal.flags &= ~(u32)RGFW_windowHide; + if (win->internal.flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); + /* wl_surface_attach(win->src.surface, win->x, win->y, win->w, win->h, 0, 0); */ + wl_surface_commit(win->src.surface); +} + +RGFW_ssize_t RGFW_FUNC(RGFW_readClipboardPtr) (char* str, size_t strCapacity) { + + RGFW_UNUSED(strCapacity); + + if (str != NULL) + RGFW_STRNCPY(str, _RGFW->clipboard, _RGFW->clipboard_len - 1); + _RGFW->clipboard[_RGFW->clipboard_len - 1] = '\0'; + return (RGFW_ssize_t)_RGFW->clipboard_len - 1; +} + +void RGFW_FUNC(RGFW_writeClipboard) (const char* text, u32 textLen) { + + // compositor does not support wl_data_device_manager + // clients cannot read rgfw's clipboard + if (_RGFW->data_device_manager == NULL) return; + // clear the clipboard + if (_RGFW->clipboard) + RGFW_FREE(_RGFW->clipboard); + + // set the contents + _RGFW->clipboard = (char*)RGFW_ALLOC(textLen); + RGFW_ASSERT(_RGFW->clipboard != NULL); + RGFW_STRNCPY(_RGFW->clipboard, text, textLen - 1); + _RGFW->clipboard[textLen - 1] = '\0'; + _RGFW->clipboard_len = textLen; + + // means we already wrote to the clipboard + // so destroy it to create a new one + RGFW_window* win = _RGFW->kbOwner; + + if (win->src.data_source != NULL) { + wl_data_source_destroy(win->src.data_source); + win->src.data_source = NULL; + } + + // advertise to other clients that we offer text + win->src.data_source = wl_data_device_manager_create_data_source(_RGFW->data_device_manager); + + // basic error checking + if (win->src.data_source == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, "Could not create clipboard data source"); + return; + } + wl_data_source_offer(win->src.data_source , "text/plain;charset=utf-8"); + + // needed RGFW_doNothing because wayland will call the functions + // if not set they are random data that lead to a crash + static const struct wl_data_source_listener data_source_listener = { + .target = (void (*)(void *, struct wl_data_source *, const char *))&RGFW_doNothing, + .action = (void (*)(void *, struct wl_data_source *, u32))&RGFW_doNothing, + .dnd_drop_performed = (void (*)(void *, struct wl_data_source *))&RGFW_doNothing, + .dnd_finished = (void (*)(void *, struct wl_data_source *))&RGFW_doNothing, + .send = RGFW_wl_data_source_send, + .cancelled = RGFW_wl_data_source_cancelled + }; + + wl_data_source_add_listener(win->src.data_source, &data_source_listener, _RGFW); + +} + +RGFW_bool RGFW_FUNC(RGFW_window_isHidden) (RGFW_window* win) { + RGFW_ASSERT(win != NULL); + return RGFW_FALSE; +} + +RGFW_bool RGFW_FUNC(RGFW_window_isMinimized) (RGFW_window* win) { + RGFW_ASSERT(win != NULL); + return win->src.minimized; +} + +RGFW_bool RGFW_FUNC(RGFW_window_isMaximized) (RGFW_window* win) { + RGFW_ASSERT(win != NULL); + return win->src.maximized; +} + +RGFW_monitor* RGFW_FUNC(RGFW_getMonitors) (size_t* len) { + static RGFW_monitor monitors[RGFW_MAX_MONITORS]; + RGFW_init(); + if (len != NULL) { + *len = _RGFW->monitors.count; + } + + u8 i = 0; + RGFW_monitorNode* cur_node = _RGFW->monitors.list.head; + while (cur_node != NULL) { + monitors[i] = cur_node->mon; + ++i; + cur_node = cur_node->next; + } + return monitors; +} + +RGFW_monitor RGFW_FUNC(RGFW_getPrimaryMonitor) (void) { + return _RGFW->monitors.list.head->mon; +} + +RGFW_bool RGFW_FUNC(RGFW_monitor_requestMode) (RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { + RGFW_UNUSED(mon); RGFW_UNUSED(mode); RGFW_UNUSED(request); + return RGFW_FALSE; +} + +RGFW_monitor RGFW_FUNC(RGFW_window_getMonitor) (RGFW_window* win) { + RGFW_ASSERT(win); + return win->src.active_monitor; +} + +#ifdef RGFW_OPENGL +RGFW_bool RGFW_FUNC(RGFW_extensionSupportedPlatform_OpenGL) (const char * extension, size_t len) { return RGFW_extensionSupportedPlatform_EGL(extension, len); } +RGFW_proc RGFW_FUNC(RGFW_getProcAddress_OpenGL) (const char* procname) { return RGFW_getProcAddress_EGL(procname); } + + +RGFW_bool RGFW_FUNC(RGFW_window_createContextPtr_OpenGL)(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { + RGFW_bool out = RGFW_window_createContextPtr_EGL(win, &ctx->egl, hints); + win->src.gfxType = RGFW_gfxNativeOpenGL; + return out; +} +void RGFW_FUNC(RGFW_window_deleteContextPtr_OpenGL) (RGFW_window* win, RGFW_glContext* ctx) { RGFW_window_deleteContextPtr_EGL(win, &ctx->egl); win->src.ctx.native = NULL; } + +void RGFW_FUNC(RGFW_window_makeCurrentContext_OpenGL) (RGFW_window* win) { RGFW_window_makeCurrentContext_EGL(win); } +void* RGFW_FUNC(RGFW_getCurrentContext_OpenGL) (void) { return RGFW_getCurrentContext_EGL(); } +void RGFW_FUNC(RGFW_window_swapBuffers_OpenGL) (RGFW_window* win) { RGFW_window_swapBuffers_EGL(win); } +void RGFW_FUNC(RGFW_window_swapInterval_OpenGL) (RGFW_window* win, i32 swapInterval) { RGFW_window_swapInterval_EGL(win, swapInterval); } +#endif /* RGFW_OPENGL */ + +void RGFW_FUNC(RGFW_window_closePlatform)(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, "a window was freed"); + #ifdef RGFW_LIBDECOR + if (win->src.decorContext) + libdecor_unref(win->src.decorContext); + #endif + + if (win->src.decoration) { + zxdg_toplevel_decoration_v1_destroy(win->src.decoration); + } + + if (win->src.xdg_toplevel) { + xdg_toplevel_destroy(win->src.xdg_toplevel); + } + + wl_surface_destroy(win->src.custom_cursor_surface); + + if (win->src.locked_pointer) { + zwp_locked_pointer_v1_destroy(win->src.locked_pointer); + } + + if (win->src.icon) { + xdg_toplevel_icon_v1_destroy(win->src.icon); + } + + xdg_surface_destroy(win->src.xdg_surface); + wl_surface_destroy(win->src.surface); +} + +#ifdef RGFW_WEBGPU +WGPUSurface RGFW_FUNC(RGFW_window_createSurface_WebGPU) (RGFW_window* window, WGPUInstance instance) { + WGPUSurfaceDescriptor surfaceDesc = {0}; + WGPUSurfaceSourceWaylandSurface fromWl = {0}; + fromWl.chain.sType = WGPUSType_SurfaceSourceWaylandSurface; + fromWl.display = _RGFW->wl_display; + fromWl.surface = window->src.surface; + + surfaceDesc.nextInChain = (WGPUChainedStruct*)&fromWl.chain; + return wgpuInstanceCreateSurface(instance, &surfaceDesc); +} +#endif + + + +#endif /* RGFW_WAYLAND */ +/* + End of Wayland defines +*/ /* @@ -6353,7 +8882,22 @@ u64 RGFW_getTimerValue(void) { #define OEMRESOURCE #include -#include +#ifndef OCR_NORMAL +#define OCR_NORMAL 32512 +#define OCR_IBEAM 32513 +#define OCR_WAIT 32514 +#define OCR_CROSS 32515 +#define OCR_UP 32516 +#define OCR_SIZENWSE 32642 +#define OCR_SIZENESW 32643 +#define OCR_SIZEWE 32644 +#define OCR_SIZENS 32645 +#define OCR_SIZEALL 32646 +#define OCR_NO 32648 +#define OCR_HAND 32649 +#define OCR_APPSTARTING 32650 +#endif + #include #include #include @@ -6365,19 +8909,7 @@ u64 RGFW_getTimerValue(void) { #define WM_DPICHANGED 0x02E0 #endif -#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 - - HMODULE RGFW_XInput_dll = NULL; -#endif - -char* RGFW_createUTF8FromWideStringWin32(const WCHAR* source); +RGFW_bool RGFW_createUTF8FromWideStringWin32(const WCHAR* source, char* out, size_t max); #define GL_FRONT 0x0404 #define GL_BACK 0x0405 @@ -6388,16 +8920,11 @@ typedef int (*PFN_wglGetSwapIntervalEXT)(void); PFN_wglGetSwapIntervalEXT wglGetSwapIntervalEXTSrc = NULL; #define wglGetSwapIntervalEXT wglGetSwapIntervalEXTSrc - -void* RGFWgamepadApi = NULL; - /* these two wgl functions need to be preloaded */ typedef HGLRC (WINAPI *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hdc, HGLRC hglrc, const int *attribList); PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL; -#ifndef RGFW_EGL - HMODULE RGFW_wgl_dll = NULL; -#endif +HMODULE RGFW_wgl_dll = NULL; #ifndef RGFW_NO_LOAD_WGL typedef HGLRC(WINAPI* PFN_wglCreateContext)(HDC); @@ -6425,28 +8952,11 @@ PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL; #define wglShareLists wglShareListsSRC #endif -#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) -RGFW_bool RGFW_extensionSupportedPlatform(const char * extension, size_t len) { - const char* extensions = NULL; +void* RGFW_window_getHWND(RGFW_window* win) { return win->src.window; } +void* RGFW_window_getHDC(RGFW_window* win) { return win->src.hdc; } - RGFW_proc proc = RGFW_getProcAddress("wglGetExtensionsStringARB"); - RGFW_proc proc2 = RGFW_getProcAddress("wglGetExtensionsStringEXT"); - - if (proc) - extensions = ((const char* (*)(HDC))proc)(wglGetCurrentDC()); - else if (proc2) - extensions = ((const char*(*)(void))proc2)(); - - return extensions != NULL && RGFW_extensionSupportedStr(extensions, extension, len); -} - -RGFW_proc RGFW_getProcAddress(const char* procname) { - RGFW_proc proc = (RGFW_proc)wglGetProcAddress(procname); - if (proc) - return proc; - - return (RGFW_proc) GetProcAddress(RGFW_wgl_dll, procname); -} +#ifdef RGFW_OPENGL +RGFWDEF void RGFW_win32_loadOpenGLFuncs(HWND dummyWin); typedef HRESULT (APIENTRY* PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc, const int* piAttribIList, const FLOAT* pfAttribFList, UINT nMaxFormats, int* piFormats, UINT* nNumFormats); PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = NULL; @@ -6457,13 +8967,15 @@ PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; #ifndef RGFW_NO_DWM HMODULE RGFW_dwm_dll = NULL; +#ifndef _DWMAPI_H_ typedef struct { DWORD dwFlags; int fEnable; HRGN hRgnBlur; int fTransitionOnMaximized;} DWM_BLURBEHIND; +#endif typedef HRESULT (WINAPI * PFN_DwmEnableBlurBehindWindow)(HWND, const DWM_BLURBEHIND*); PFN_DwmEnableBlurBehindWindow DwmEnableBlurBehindWindowSRC = NULL; #endif void RGFW_win32_makeWindowTransparent(RGFW_window* win); void RGFW_win32_makeWindowTransparent(RGFW_window* win) { - if (!(win->_flags & RGFW_windowTransparent)) return; + if (!(win->internal.flags & RGFW_windowTransparent)) return; #ifndef RGFW_NO_DWM if (DwmEnableBlurBehindWindowSRC != NULL) { @@ -6486,49 +8998,55 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) RGFW_window* win = (RGFW_window*)GetPropW(hWnd, L"RGFW"); if (win == NULL) return DefWindowProcW(hWnd, message, wParam, lParam); + static BYTE keyboardState[256]; + GetKeyboardState(keyboardState); + + RGFW_event event; + RGFW_MEMSET(&event, 0, sizeof(event)); + event.common.win = win; + RECT windowRect; GetWindowRect(hWnd, &windowRect); switch (message) { case WM_CLOSE: case WM_QUIT: - RGFW_eventQueuePushEx(e.type = RGFW_quit; e._win = win); + RGFW_window_setShouldClose(win, RGFW_TRUE); RGFW_windowQuitCallback(win); + RGFW_eventQueuePushEx(e.type = RGFW_quit; e.common.win = win); return 0; case WM_ACTIVATE: { RGFW_bool inFocus = RGFW_BOOL(LOWORD(wParam) != WA_INACTIVE); - if (inFocus) win->_flags |= RGFW_windowFocus; - else win->_flags &= ~ (u32)RGFW_windowFocus; - RGFW_eventQueuePushEx(e.type = (RGFW_eventType)((u8)RGFW_focusOut - inFocus); e._win = win); - RGFW_focusCallback(win, inFocus); - RGFW_window_focusLost(win); - - if ((win->_flags & RGFW_windowFullscreen) == 0) - return DefWindowProcW(hWnd, message, wParam, lParam); - - win->_flags &= ~(u32)RGFW_EVENT_PASSED; - if (inFocus == RGFW_FALSE) RGFW_window_minimize(win); - else RGFW_window_setFullscreen(win, 1); + win->internal.inFocus = RGFW_BOOL(inFocus); + if ((win->internal.enabledEvents & (RGFW_BIT(RGFW_focusIn - inFocus)))) { + RGFW_eventQueuePushEx(e.type = (RGFW_eventType)((u8)RGFW_focusOut - inFocus); e.common.win = win); + RGFW_focusCallback(win, inFocus); + } + if (inFocus == RGFW_FALSE) RGFW_window_focusLost(win); + if ((win->internal.flags & RGFW_windowFullscreen) && inFocus == RGFW_TRUE) + RGFW_window_setFullscreen(win, 1); return DefWindowProcW(hWnd, message, wParam, lParam); } case WM_MOVE: - win->r.x = windowRect.left; - win->r.y = windowRect.top; - RGFW_eventQueuePushEx(e.type = RGFW_windowMoved; e._win = win); - RGFW_windowMovedCallback(win, win->r); + win->x = windowRect.left; + win->y = windowRect.top; + + if (!(win->internal.enabledEvents & RGFW_windowMovedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam);; + RGFW_eventQueuePushEx(e.type = RGFW_windowMoved; e.common.win = win); + RGFW_windowMovedCallback(win, win->x, win->y); return DefWindowProcW(hWnd, message, wParam, lParam); case WM_SIZE: { - if (win->src.aspectRatio.w != 0 && win->src.aspectRatio.h != 0) { - double aspectRatio = (double)win->src.aspectRatio.w / win->src.aspectRatio.h; + if (win->src.aspectRatioW != 0 && win->src.aspectRatioH != 0) { + double aspectRatio = (double)win->src.aspectRatioW / win->src.aspectRatioH; int width = windowRect.right - windowRect.left; int height = windowRect.bottom - windowRect.top; int newHeight = (int)(width / aspectRatio); int newWidth = (int)(height * aspectRatio); - if (win->r.w > windowRect.right - windowRect.left || - win->r.h > (i32)((u32)(windowRect.bottom - windowRect.top) - win->src.hOffset)) + if (win->w > (i32)((windowRect.right - windowRect.left) - win->src.offsetW) || + win->h > (i32)((windowRect.bottom - windowRect.top) - win->src.offsetH)) { if (newHeight > height) windowRect.right = windowRect.left + newWidth; else windowRect.bottom = windowRect.top + newHeight; @@ -6537,43 +9055,47 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) else windowRect.bottom = windowRect.top + newHeight; } - RGFW_window_resize(win, RGFW_AREA((windowRect.right - windowRect.left), - (u32)(windowRect.bottom - windowRect.top) - (u32)win->src.hOffset)); + RGFW_window_resize(win, (windowRect.right - windowRect.left) - win->src.offsetW, + (windowRect.bottom - windowRect.top) - win->src.offsetH); } - win->r.w = windowRect.right - windowRect.left; - win->r.h = (windowRect.bottom - windowRect.top) - (i32)win->src.hOffset; - RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e._win = win); - RGFW_windowResizedCallback(win, win->r); + win->w = (windowRect.right - windowRect.left) - (i32)win->src.offsetW; + win->h = (windowRect.bottom - windowRect.top) - (i32)win->src.offsetH; + if (!(win->internal.enabledEvents & RGFW_windowResizedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam);; + RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e.common.win = win); + RGFW_windowResizedCallback(win, win->w, win->h); RGFW_window_checkMode(win); return DefWindowProcW(hWnd, message, wParam, lParam); } #ifndef RGFW_NO_MONITOR case WM_DPICHANGED: { - if (win->_flags & RGFW_windowScaleToMonitor) RGFW_window_scaleToMonitor(win); + if (win->internal.flags & RGFW_windowScaleToMonitor) RGFW_window_scaleToMonitor(win); const float scaleX = HIWORD(wParam) / (float) 96; const float scaleY = LOWORD(wParam) / (float) 96; + + if (!(win->internal.enabledEvents & RGFW_scaleUpdatedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam);; RGFW_scaleUpdatedCallback(win, scaleX, scaleY); - RGFW_eventQueuePushEx(e.type = RGFW_scaleUpdated; e.scaleX = scaleX; e.scaleY = scaleY; e._win = win); + RGFW_eventQueuePushEx(e.type = RGFW_scaleUpdated; e.scale.x = scaleX; e.scale.y = scaleY; e.common.win = win); return DefWindowProcW(hWnd, message, wParam, lParam); } #endif case WM_GETMINMAXINFO: { MINMAXINFO* mmi = (MINMAXINFO*) lParam; - mmi->ptMinTrackSize.x = (LONG)win->src.minSize.w; - mmi->ptMinTrackSize.y = (LONG)(win->src.minSize.h + win->src.hOffset); - if (win->src.maxSize.w == 0 && win->src.maxSize.h == 0) + mmi->ptMinTrackSize.x = (LONG)(win->src.minSizeW + win->src.offsetW); + mmi->ptMinTrackSize.y = (LONG)(win->src.minSizeH + win->src.offsetH); + if (win->src.maxSizeW == 0 && win->src.maxSizeH == 0) return DefWindowProcW(hWnd, message, wParam, lParam); - mmi->ptMaxTrackSize.x = (LONG)win->src.maxSize.w; - mmi->ptMaxTrackSize.y = (LONG)(win->src.maxSize.h + win->src.hOffset); + mmi->ptMaxTrackSize.x = (LONG)(win->src.maxSizeW + win->src.offsetW); + mmi->ptMaxTrackSize.y = (LONG)(win->src.maxSizeH + win->src.offsetH); return DefWindowProcW(hWnd, message, wParam, lParam); } case WM_PAINT: { + if (!(win->internal.enabledEvents & RGFW_windowRefreshFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); PAINTSTRUCT ps; BeginPaint(hWnd, &ps); - RGFW_eventQueuePushEx(e.type = RGFW_windowRefresh; e._win = win); + RGFW_eventQueuePushEx(e.type = RGFW_windowRefresh; e.common.win = win); RGFW_windowRefreshCallback(win); EndPaint(hWnd, &ps); @@ -6589,7 +9111,9 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) #ifdef RGFW_ADVANCED_SMOOTH_RESIZE case WM_ENTERSIZEMOVE: SetTimer(win->src.window, 1, USER_TIMER_MINIMUM, NULL); break; case WM_EXITSIZEMOVE: KillTimer(win->src.window, 1); break; - case WM_TIMER: RGFW_windowRefreshCallback(win); break; + case WM_TIMER: + if (!(win->internal.enabledEvents & RGFW_windowRefreshFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); + RGFW_windowRefreshCallback(win); break; #endif case WM_NCLBUTTONDOWN: { /* workaround for half-second pause when starting to move window @@ -6600,11 +9124,272 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) break; ScreenToClient(win->src.window, &point); - PostMessage(win->src.window, WM_MOUSEMOVE, 0, ((uint32_t)point.x)|(((uint32_t)point.y) << 16)); + PostMessage(win->src.window, WM_MOUSEMOVE, 0, (u32)(point.x)|((u32)(point.y) << 16)); break; } + case WM_MOUSELEAVE: + win->internal.mouseInside = RGFW_FALSE; + _RGFW->windowState.winLeave = win; + _RGFW->windowState.mouseLeave = RGFW_TRUE; + if (!(win->internal.enabledEvents & RGFW_mouseLeaveFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); + event.type = RGFW_mouseLeave; + RGFW_window_getMouse(win, &event.mouse.x, &event.mouse.y); + RGFW_mouseNotifyCallback(win, event.mouse.x, event.mouse.y, 0); + break; + case WM_SYSKEYUP: case WM_KEYUP: { + if (!(win->internal.enabledEvents & RGFW_keyReleasedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); + i32 scancode = (HIWORD(lParam) & (KF_EXTENDED | 0xff)); + if (scancode == 0) + scancode = (i32)MapVirtualKeyW((UINT)wParam, MAPVK_VK_TO_VSC); + + switch (scancode) { + case 0x54: scancode = 0x137; break; /* Alt+PrtS */ + case 0x146: scancode = 0x45; break; /* Ctrl+Pause */ + case 0x136: scancode = 0x36; break; /* CJK IME sets the extended bit for right Shift */ + default: break; + } + + event.key.value = (u8)RGFW_apiKeyToRGFW((u32) scancode); + + if (wParam == VK_CONTROL) { + if (HIWORD(lParam) & KF_EXTENDED) + event.key.value = RGFW_controlR; + else event.key.value = RGFW_controlL; + } + + wchar_t charBuffer; + ToUnicodeEx((UINT)wParam, (UINT)scancode, keyboardState, (wchar_t*)&charBuffer, 1, 0, NULL); + + event.key.sym = (u8)charBuffer; + + _RGFW->keyboard[event.key.value].prev = _RGFW->keyboard[event.key.value].current; + event.type = RGFW_keyReleased; + event.key.repeat = ((lParam & 0x40000000) != 0) || RGFW_window_isKeyDown(win, event.key.value); + _RGFW->keyboard[event.key.value].current = 0; + + RGFW_updateKeyMods(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001), (GetKeyState(VK_SCROLL) & 0x0001)); + event.key.mod = win->internal.mod; + + RGFW_keyCallback(win, event.key.value, event.key.sym, event.key.mod, event.key.repeat,0); + break; + } + case WM_SYSKEYDOWN: case WM_KEYDOWN: { + if (!(win->internal.enabledEvents & RGFW_keyPressedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); + i32 scancode = (HIWORD(lParam) & (KF_EXTENDED | 0xff)); + if (scancode == 0) + scancode = (i32)MapVirtualKeyW((u32)wParam, MAPVK_VK_TO_VSC); + + switch (scancode) { + case 0x54: scancode = 0x137; break; /* Alt+PrtS */ + case 0x146: scancode = 0x45; break; /* Ctrl+Pause */ + case 0x136: scancode = 0x36; break; /* CJK IME sets the extended bit for right Shift */ + default: break; + } + + event.key.value = (u8)RGFW_apiKeyToRGFW((u32) scancode); + if (wParam == VK_CONTROL) { + if (HIWORD(lParam) & KF_EXTENDED) + event.key.value = RGFW_controlR; + else event.key.value = RGFW_controlL; + } + + wchar_t charBuffer; + ToUnicodeEx((UINT)wParam, (UINT)scancode, keyboardState, &charBuffer, 1, 0, NULL); + event.key.sym = (u8)charBuffer; + + _RGFW->keyboard[event.key.value].prev = _RGFW->keyboard[event.key.value].current; + event.type = RGFW_keyPressed; + event.key.repeat = ((lParam & 0x40000000) != 0) || RGFW_window_isKeyDown(win, event.key.value); + _RGFW->keyboard[event.key.value].current = 1; + + RGFW_updateKeyMods(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001), (GetKeyState(VK_SCROLL) & 0x0001)); + event.key.mod = win->internal.mod; + + RGFW_keyCallback(win, event.key.value, event.key.sym, event.key.mod, event.key.repeat, 1); + break; + } + case WM_MOUSEMOVE: { + if (!(win->internal.enabledEvents & RGFW_mousePosChangedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); + if ((win->internal.holdMouse)) + break; + + + event.mouse.x = GET_X_LPARAM(lParam); + event.mouse.y = GET_Y_LPARAM(lParam); + event.mouse.vecX = (float)(event.mouse.x - win->internal.lastMouseX); + event.mouse.vecY = (float)(event.mouse.y - win->internal.lastMouseY); + _RGFW->vectorX = event.mouse.vecX; + _RGFW->vectorY = event.mouse.vecY; + + RGFW_mousePosCallback(win, event.mouse.x, event.mouse.y, event.mouse.vecX, event.mouse.vecY); + + if (win->internal.mouseInside == RGFW_FALSE) { + win->internal.mouseInside = RGFW_TRUE; + _RGFW->windowState.win = win; + _RGFW->windowState.mouseEnter = RGFW_TRUE; + event.type = RGFW_mouseEnter; + RGFW_mouseNotifyCallback(win, event.mouse.x, event.mouse.y, 1); + RGFW_eventQueuePush(&event); + } + + event.type = RGFW_mousePosChanged; + win->internal.lastMouseX = event.mouse.x; + win->internal.lastMouseY = event.mouse.y; + break; + } + case WM_INPUT: { + if (!(win->internal.enabledEvents & RGFW_mousePosChangedFlag) || !(win->internal.holdMouse)) return DefWindowProcW(hWnd, message, wParam, lParam); + unsigned size = sizeof(RAWINPUT); + static RAWINPUT raw; + + GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &size, sizeof(RAWINPUTHEADER)); + + if (raw.header.dwType != RIM_TYPEMOUSE || (raw.data.mouse.lLastX == 0 && raw.data.mouse.lLastY == 0) ) + break; + + if (raw.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { + POINT pos = {0, 0}; + int width, height; + + if (raw.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) { + pos.x += GetSystemMetrics(SM_XVIRTUALSCREEN); + pos.y += GetSystemMetrics(SM_YVIRTUALSCREEN); + width = GetSystemMetrics(SM_CXVIRTUALSCREEN); + height = GetSystemMetrics(SM_CYVIRTUALSCREEN); + } + else { + width = GetSystemMetrics(SM_CXSCREEN); + height = GetSystemMetrics(SM_CYSCREEN); + } + + pos.x += (int) (((float)raw.data.mouse.lLastX / 65535.f) * (float)width); + pos.y += (int) (((float)raw.data.mouse.lLastY / 65535.f) * (float)height); + ScreenToClient(win->src.window, &pos); + + event.mouse.vecX = (float)(pos.x - win->internal.lastMouseX); + event.mouse.vecY = (float)(pos.y - win->internal.lastMouseY); + } else { + event.mouse.vecX = (float)(raw.data.mouse.lLastX); + event.mouse.vecY = (float)(raw.data.mouse.lLastY); + } + + event.type = RGFW_mousePosChanged; + win->internal.lastMouseX += (i32)event.mouse.vecX; + win->internal.lastMouseY += (i32)event.mouse.vecY; + _RGFW->vectorX = event.mouse.vecX; + _RGFW->vectorY = event.mouse.vecY; + event.mouse.x = win->internal.lastMouseX; + event.mouse.y = win->internal.lastMouseY; + RGFW_mousePosCallback(win, event.mouse.x, event.mouse.y, event.mouse.vecX, event.mouse.vecY); + break; + } + case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: case WM_XBUTTONDOWN: + if (!(win->internal.enabledEvents & RGFW_mouseButtonPressedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); + if (message == WM_XBUTTONDOWN) + event.button.value = RGFW_mouseMisc1 + (GET_XBUTTON_WPARAM(wParam) == XBUTTON2); + else event.button.value = (message == WM_LBUTTONDOWN) ? (u8)RGFW_mouseLeft : + (message == WM_RBUTTONDOWN) ? (u8)RGFW_mouseRight : (u8)RGFW_mouseMiddle; + + event.type = RGFW_mouseButtonPressed; + _RGFW->mouseButtons[event.button.value].prev = _RGFW->mouseButtons[event.button.value].current; + _RGFW->mouseButtons[event.button.value].current = 1; + RGFW_mouseButtonCallback(win, event.button.value, 1); + break; + case WM_LBUTTONUP: case WM_RBUTTONUP: case WM_MBUTTONUP: case WM_XBUTTONUP: + if (!(win->internal.enabledEvents & RGFW_mouseButtonReleasedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); + if (message == WM_XBUTTONUP) + event.button.value = RGFW_mouseMisc1 + (GET_XBUTTON_WPARAM(wParam) == XBUTTON2); + else event.button.value = (message == WM_LBUTTONUP) ? (u8)RGFW_mouseLeft : + (message == WM_RBUTTONUP) ? (u8)RGFW_mouseRight : (u8)RGFW_mouseMiddle; + event.type = RGFW_mouseButtonReleased; + _RGFW->mouseButtons[event.button.value].prev = _RGFW->mouseButtons[event.button.value].current; + _RGFW->mouseButtons[event.button.value].current = 0; + RGFW_mouseButtonCallback(win, event.button.value, 0); + break; + case WM_MOUSEWHEEL: + if (!(win->internal.enabledEvents & RGFW_mouseScrollFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); + + event.type = RGFW_mouseScroll; + event.scroll.x = 0.0f; + event.scroll.y = (float)((i16) HIWORD(wParam) / (double) WHEEL_DELTA); + _RGFW->scrollX = event.scroll.x; + _RGFW->scrollY = event.scroll.y; + + RGFW_mouseScrollCallback(win, event.scroll.x, event.scroll.y); + break; + case 0x020E: /* WM_MOUSEHWHEEL */ + if (!(win->internal.enabledEvents & RGFW_mouseScrollFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); + + event.type = RGFW_mouseScroll; + event.scroll.x = -(float)((i16) HIWORD(wParam) / (double) WHEEL_DELTA); + event.scroll.y = (float)0.0f; + _RGFW->scrollX = event.scroll.x; + _RGFW->scrollY = event.scroll.y; + + RGFW_mouseScrollCallback(win, event.scroll.x, event.scroll.y); + break; + case WM_DROPFILES: { + event.type = RGFW_dataDrag; + + HDROP drop = (HDROP) wParam; + POINT pt; + + /* Move the mouse to the position of the drop */ + DragQueryPoint(drop, &pt); + + event.drag.x = pt.x; + event.drag.y = pt.y; + + _RGFW->windowState.win = win; + _RGFW->windowState.dataDragging = RGFW_TRUE; + _RGFW->windowState.dropX = event.drag.x; + _RGFW->windowState.dropY = event.drag.y; + + if ((win->internal.enabledEvents & RGFW_dataDrag)) { + RGFW_dataDragCallback(win, event.drag.x, event.drag.y); + RGFW_eventQueuePush(&event); + } + + if (!(win->internal.enabledEvents & RGFW_dataDrop)) return DefWindowProcW(hWnd, message, wParam, lParam); + event.type = 0; + event.type = RGFW_dataDrop; + event.drop.files = _RGFW->files; + event.drop.count = 0; + event.drop.count = DragQueryFileW(drop, 0xffffffff, NULL, 0); + + u32 i; + for (i = 0; i < event.drop.count; i++) { + UINT length = DragQueryFileW(drop, i, NULL, 0); + if (length == 0) + continue; + + WCHAR buffer[RGFW_MAX_PATH * 2]; + if (length > (RGFW_MAX_PATH * 2) - 1) + length = RGFW_MAX_PATH * 2; + + DragQueryFileW(drop, i, buffer, length + 1); + + RGFW_createUTF8FromWideStringWin32(buffer, event.drop.files[i], RGFW_MAX_PATH); + + event.drop.files[i][RGFW_MAX_PATH - 1] = '\0'; + event.common.win = win; + } + + DragFinish(drop); + + _RGFW->windowState.win = win; + _RGFW->windowState.dataDrop = RGFW_TRUE; + _RGFW->windowState.filesCount = event.drop.count; + RGFW_dataDropCallback(win, event.drop.files, event.drop.count); + break; + } default: break; } + + if (event.type) { + RGFW_eventQueuePush(&event); + } + return DefWindowProcW(hWnd, message, wParam, lParam); } @@ -6631,58 +9416,50 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) RGFW_ASSERT(name##SRC != NULL); \ } -#ifndef RGFW_NO_XINPUT -void RGFW_loadXInput(void); -void RGFW_loadXInput(void) { - u32 i; - static const char* names[] = {"xinput1_4.dll", "xinput9_1_0.dll", "xinput1_2.dll", "xinput1_1.dll"}; - - for (i = 0; i < sizeof(names) / sizeof(const char*) && (XInputGetStateSRC == NULL || XInputGetKeystrokeSRC != NULL); i++) { - RGFW_XInput_dll = LoadLibraryA(names[i]); - RGFW_PROC_DEF(RGFW_XInput_dll, XInputGetState); - RGFW_PROC_DEF(RGFW_XInput_dll, XInputGetKeystroke); - } - - if (XInputGetStateSRC == NULL) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errFailedFuncLoad, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to load XInputGetState"); - if (XInputGetKeystrokeSRC == NULL) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errFailedFuncLoad, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to load XInputGetKeystroke"); -} -#endif - -void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area){ -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - win->buffer = buffer; - win->bufferSize = area; +RGFW_bool RGFW_createSurfacePtr(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { + RGFW_ASSERT(surface != NULL); + surface->data = data; + surface->w = w; + surface->h = h; + surface->format = format; BITMAPV5HEADER bi; ZeroMemory(&bi, sizeof(bi)); bi.bV5Size = sizeof(bi); - bi.bV5Width = (i32)area.w; - bi.bV5Height = -((LONG) area.h); + bi.bV5Width = (i32)w; + bi.bV5Height = -((LONG) h); bi.bV5Planes = 1; - bi.bV5BitCount = 32; + bi.bV5BitCount = (format >= RGFW_formatRGBA8) ? 32 : 24; bi.bV5Compression = BI_RGB; - win->src.bitmap = CreateDIBSection(win->src.hdc, + surface->native.bitmap = CreateDIBSection(_RGFW->root->src.hdc, (BITMAPINFO*) &bi, DIB_RGB_COLORS, - (void**) &win->src.bitmapBits, + (void**) &surface->native.bitmapBits, NULL, (DWORD) 0); - if (win->buffer == NULL) - win->buffer = win->src.bitmapBits; + surface->native.format = (format >= RGFW_formatRGBA8) ? RGFW_formatBGRA8 : RGFW_formatBGR8; - win->src.hdcMem = CreateCompatibleDC(win->src.hdc); - SelectObject(win->src.hdcMem, win->src.bitmap); + if (surface->native.bitmap == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, "Failed to create DIB section."); + return RGFW_FALSE; + } - #if defined(RGFW_OSMESA) - win->src.ctx = OSMesaCreateContext(OSMESA_BGRA, NULL); - OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); - OSMesaPixelStore(OSMESA_Y_UP, 0); - #endif - #else - RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); /*!< if buffer rendering is not being used */ - #endif + surface->native.hdcMem = CreateCompatibleDC(_RGFW->root->src.hdc); + SelectObject(surface->native.hdcMem, surface->native.bitmap); + + return RGFW_TRUE; +} + +void RGFW_surface_freePtr(RGFW_surface* surface) { + RGFW_ASSERT(surface != NULL); + + DeleteDC(surface->native.hdcMem); + DeleteObject(surface->native.bitmap); +} + +void RGFW_window_blitSurface(RGFW_window* win, RGFW_surface* surface) { + RGFW_copyImageData(surface->native.bitmapBits, surface->w, RGFW_MIN(win->h, surface->h), surface->native.format, surface->data, surface->format); + BitBlt(win->src.hdc, 0, 0, RGFW_MIN(win->w, surface->w), RGFW_MIN(win->h, surface->h), surface->native.hdcMem, 0, 0, SRCCOPY); } void RGFW_releaseCursor(RGFW_window* win) { @@ -6692,8 +9469,8 @@ void RGFW_releaseCursor(RGFW_window* win) { RegisterRawInputDevices(&id, 1, sizeof(id)); } -void RGFW_captureCursor(RGFW_window* win, RGFW_rect rect) { - RGFW_UNUSED(win); RGFW_UNUSED(rect); +void RGFW_captureCursor(RGFW_window* win) { + RGFW_UNUSED(win); RECT clipRect; GetClientRect(win->src.window, &clipRect); @@ -6708,13 +9485,13 @@ void RGFW_captureCursor(RGFW_window* win, RGFW_rect rect) { #define RGFW_LOAD_LIBRARY(x, lib) if (x == NULL) { x = LoadLibraryA(lib); RGFW_ASSERT(x != NULL); } #ifdef RGFW_DIRECTX -int RGFW_window_createDXSwapChain(RGFW_window* win, IDXGIFactory* pFactory, IUnknown* pDevice, IDXGISwapChain** swapchain) { +int RGFW_window_createSwapChain_DirectX(RGFW_window* win, IDXGIFactory* pFactory, IUnknown* pDevice, IDXGISwapChain** swapchain) { RGFW_ASSERT(win && pFactory && pDevice && swapchain); static DXGI_SWAP_CHAIN_DESC swapChainDesc = { 0 }; swapChainDesc.BufferCount = 2; - swapChainDesc.BufferDesc.Width = win->r.w; - swapChainDesc.BufferDesc.Height = win->r.h; + swapChainDesc.BufferDesc.Width = win->w; + swapChainDesc.BufferDesc.Height = win->h; swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapChainDesc.OutputWindow = (HWND)win->src.window; @@ -6725,7 +9502,7 @@ int RGFW_window_createDXSwapChain(RGFW_window* win, IDXGIFactory* pFactory, IUnk HRESULT hr = pFactory->lpVtbl->CreateSwapChain(pFactory, (IUnknown*)pDevice, &swapChainDesc, swapchain); if (FAILED(hr)) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errDirectXContext, RGFW_DEBUG_CTX(win, hr), "Failed to create DirectX swap chain!"); + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errDirectXContext, "Failed to create DirectX swap chain!"); return -2; } @@ -6733,143 +9510,130 @@ int RGFW_window_createDXSwapChain(RGFW_window* win, IDXGIFactory* pFactory, IUnk } #endif -void RGFW_win32_loadOpenGLFuncs(HWND dummyWin); -void RGFW_win32_loadOpenGLFuncs(HWND dummyWin) { -#ifdef RGFW_OPENGL - if (wglSwapIntervalEXT != NULL && wglChoosePixelFormatARB != NULL && wglChoosePixelFormatARB != NULL) - return; - - HDC dummy_dc = GetDC(dummyWin); - u32 pfd_flags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; - - PIXELFORMATDESCRIPTOR pfd = {sizeof(pfd), 1, pfd_flags, PFD_TYPE_RGBA, 32, 8, PFD_MAIN_PLANE, 32, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 32, 8, 0, PFD_MAIN_PLANE, 0, 0, 0, 0}; - - int dummy_pixel_format = ChoosePixelFormat(dummy_dc, &pfd); - SetPixelFormat(dummy_dc, dummy_pixel_format, &pfd); - - HGLRC dummy_context = wglCreateContext(dummy_dc); - wglMakeCurrent(dummy_dc, dummy_context); - - wglCreateContextAttribsARB = ((PFNWGLCREATECONTEXTATTRIBSARBPROC(WINAPI *)(const char*)) wglGetProcAddress)("wglCreateContextAttribsARB"); - wglChoosePixelFormatARB = ((PFNWGLCHOOSEPIXELFORMATARBPROC(WINAPI *)(const char*)) wglGetProcAddress)("wglChoosePixelFormatARB"); - - wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)(RGFW_proc)wglGetProcAddress("wglSwapIntervalEXT"); - if (wglSwapIntervalEXT == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to load swap interval function"); - } - - wglMakeCurrent(dummy_dc, 0); - wglDeleteContext(dummy_context); - ReleaseDC(dummyWin, dummy_dc); -#else - RGFW_UNUSED(dummyWin); -#endif +/* we're doing it with magic numbers because some keys are missing */ +void RGFW_initKeycodesPlatform(void) { + _RGFW->keycodes[0x00B] = RGFW_0; + _RGFW->keycodes[0x002] = RGFW_1; + _RGFW->keycodes[0x003] = RGFW_2; + _RGFW->keycodes[0x004] = RGFW_3; + _RGFW->keycodes[0x005] = RGFW_4; + _RGFW->keycodes[0x006] = RGFW_5; + _RGFW->keycodes[0x007] = RGFW_6; + _RGFW->keycodes[0x008] = RGFW_7; + _RGFW->keycodes[0x009] = RGFW_8; + _RGFW->keycodes[0x00A] = RGFW_9; + _RGFW->keycodes[0x01E] = RGFW_a; + _RGFW->keycodes[0x030] = RGFW_b; + _RGFW->keycodes[0x02E] = RGFW_c; + _RGFW->keycodes[0x020] = RGFW_d; + _RGFW->keycodes[0x012] = RGFW_e; + _RGFW->keycodes[0x021] = RGFW_f; + _RGFW->keycodes[0x022] = RGFW_g; + _RGFW->keycodes[0x023] = RGFW_h; + _RGFW->keycodes[0x017] = RGFW_i; + _RGFW->keycodes[0x024] = RGFW_j; + _RGFW->keycodes[0x025] = RGFW_k; + _RGFW->keycodes[0x026] = RGFW_l; + _RGFW->keycodes[0x032] = RGFW_m; + _RGFW->keycodes[0x031] = RGFW_n; + _RGFW->keycodes[0x018] = RGFW_o; + _RGFW->keycodes[0x019] = RGFW_p; + _RGFW->keycodes[0x010] = RGFW_q; + _RGFW->keycodes[0x013] = RGFW_r; + _RGFW->keycodes[0x01F] = RGFW_s; + _RGFW->keycodes[0x014] = RGFW_t; + _RGFW->keycodes[0x016] = RGFW_u; + _RGFW->keycodes[0x02F] = RGFW_v; + _RGFW->keycodes[0x011] = RGFW_w; + _RGFW->keycodes[0x02D] = RGFW_x; + _RGFW->keycodes[0x015] = RGFW_y; + _RGFW->keycodes[0x02C] = RGFW_z; + _RGFW->keycodes[0x028] = RGFW_apostrophe; + _RGFW->keycodes[0x02B] = RGFW_backSlash; + _RGFW->keycodes[0x033] = RGFW_comma; + _RGFW->keycodes[0x00D] = RGFW_equals; + _RGFW->keycodes[0x029] = RGFW_backtick; + _RGFW->keycodes[0x01A] = RGFW_bracket; + _RGFW->keycodes[0x00C] = RGFW_minus; + _RGFW->keycodes[0x034] = RGFW_period; + _RGFW->keycodes[0x01B] = RGFW_closeBracket; + _RGFW->keycodes[0x027] = RGFW_semicolon; + _RGFW->keycodes[0x035] = RGFW_slash; + _RGFW->keycodes[0x056] = RGFW_world2; + _RGFW->keycodes[0x00E] = RGFW_backSpace; + _RGFW->keycodes[0x153] = RGFW_delete; + _RGFW->keycodes[0x14F] = RGFW_end; + _RGFW->keycodes[0x01C] = RGFW_enter; + _RGFW->keycodes[0x001] = RGFW_escape; + _RGFW->keycodes[0x147] = RGFW_home; + _RGFW->keycodes[0x152] = RGFW_insert; + _RGFW->keycodes[0x15D] = RGFW_menu; + _RGFW->keycodes[0x151] = RGFW_pageDown; + _RGFW->keycodes[0x149] = RGFW_pageUp; + _RGFW->keycodes[0x045] = RGFW_pause; + _RGFW->keycodes[0x039] = RGFW_space; + _RGFW->keycodes[0x00F] = RGFW_tab; + _RGFW->keycodes[0x03A] = RGFW_capsLock; + _RGFW->keycodes[0x145] = RGFW_numLock; + _RGFW->keycodes[0x046] = RGFW_scrollLock; + _RGFW->keycodes[0x03B] = RGFW_F1; + _RGFW->keycodes[0x03C] = RGFW_F2; + _RGFW->keycodes[0x03D] = RGFW_F3; + _RGFW->keycodes[0x03E] = RGFW_F4; + _RGFW->keycodes[0x03F] = RGFW_F5; + _RGFW->keycodes[0x040] = RGFW_F6; + _RGFW->keycodes[0x041] = RGFW_F7; + _RGFW->keycodes[0x042] = RGFW_F8; + _RGFW->keycodes[0x043] = RGFW_F9; + _RGFW->keycodes[0x044] = RGFW_F10; + _RGFW->keycodes[0x057] = RGFW_F11; + _RGFW->keycodes[0x058] = RGFW_F12; + _RGFW->keycodes[0x064] = RGFW_F13; + _RGFW->keycodes[0x065] = RGFW_F14; + _RGFW->keycodes[0x066] = RGFW_F15; + _RGFW->keycodes[0x067] = RGFW_F16; + _RGFW->keycodes[0x068] = RGFW_F17; + _RGFW->keycodes[0x069] = RGFW_F18; + _RGFW->keycodes[0x06A] = RGFW_F19; + _RGFW->keycodes[0x06B] = RGFW_F20; + _RGFW->keycodes[0x06C] = RGFW_F21; + _RGFW->keycodes[0x06D] = RGFW_F22; + _RGFW->keycodes[0x06E] = RGFW_F23; + _RGFW->keycodes[0x076] = RGFW_F24; + _RGFW->keycodes[0x038] = RGFW_altL; + _RGFW->keycodes[0x01D] = RGFW_controlL; + _RGFW->keycodes[0x02A] = RGFW_shiftL; + _RGFW->keycodes[0x15B] = RGFW_superL; + _RGFW->keycodes[0x137] = RGFW_printScreen; + _RGFW->keycodes[0x138] = RGFW_altR; + _RGFW->keycodes[0x11D] = RGFW_controlR; + _RGFW->keycodes[0x036] = RGFW_shiftR; + _RGFW->keycodes[0x15C] = RGFW_superR; + _RGFW->keycodes[0x150] = RGFW_down; + _RGFW->keycodes[0x14B] = RGFW_left; + _RGFW->keycodes[0x14D] = RGFW_right; + _RGFW->keycodes[0x148] = RGFW_up; + _RGFW->keycodes[0x052] = RGFW_kp0; + _RGFW->keycodes[0x04F] = RGFW_kp1; + _RGFW->keycodes[0x050] = RGFW_kp2; + _RGFW->keycodes[0x051] = RGFW_kp3; + _RGFW->keycodes[0x04B] = RGFW_kp4; + _RGFW->keycodes[0x04C] = RGFW_kp5; + _RGFW->keycodes[0x04D] = RGFW_kp6; + _RGFW->keycodes[0x047] = RGFW_kp7; + _RGFW->keycodes[0x048] = RGFW_kp8; + _RGFW->keycodes[0x049] = RGFW_kp9; + _RGFW->keycodes[0x04E] = RGFW_kpPlus; + _RGFW->keycodes[0x053] = RGFW_kpPeriod; + _RGFW->keycodes[0x135] = RGFW_kpSlash; + _RGFW->keycodes[0x11C] = RGFW_kpReturn; + _RGFW->keycodes[0x059] = RGFW_kpEqual; + _RGFW->keycodes[0x037] = RGFW_kpMultiply; + _RGFW->keycodes[0x04A] = RGFW_kpMinus; } -#ifndef RGFW_EGL -void RGFW_window_initOpenGL(RGFW_window* win) { -#ifdef RGFW_OPENGL - PIXELFORMATDESCRIPTOR pfd; - pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); - pfd.nVersion = 1; - pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; - pfd.iPixelType = PFD_TYPE_RGBA; - pfd.iLayerType = PFD_MAIN_PLANE; - pfd.cColorBits = 32; - pfd.cAlphaBits = 8; - pfd.cDepthBits = 24; - pfd.cStencilBits = (BYTE)RGFW_GL_HINTS[RGFW_glStencil]; - pfd.cAuxBuffers = (BYTE)RGFW_GL_HINTS[RGFW_glAuxBuffers]; - if (RGFW_GL_HINTS[RGFW_glStereo]) pfd.dwFlags |= PFD_STEREO; - - /* try to create the pixel format we want for opengl and then try to create an opengl context for the specified version */ - if (win->_flags & RGFW_windowOpenglSoftware) - pfd.dwFlags |= PFD_GENERIC_FORMAT | PFD_GENERIC_ACCELERATED; - - /* get pixel format, default to a basic pixel format */ - int pixel_format = ChoosePixelFormat(win->src.hdc, &pfd); - if (wglChoosePixelFormatARB != NULL) { - i32* pixel_format_attribs = (i32*)RGFW_initFormatAttribs(); - - int new_pixel_format; - UINT num_formats; - wglChoosePixelFormatARB(win->src.hdc, pixel_format_attribs, 0, 1, &new_pixel_format, &num_formats); - if (!num_formats) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to create a pixel format for WGL"); - else pixel_format = new_pixel_format; - } - - PIXELFORMATDESCRIPTOR suggested; - if (!DescribePixelFormat(win->src.hdc, pixel_format, sizeof(suggested), &suggested) || - !SetPixelFormat(win->src.hdc, pixel_format, &pfd)) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to set the WGL pixel format"); - - if (!(pfd.dwFlags & PFD_GENERIC_ACCELERATED)) { - win->_flags |= RGFW_windowOpenglSoftware; - } - - if (wglCreateContextAttribsARB != NULL) { - /* create opengl/WGL context for the specified version */ - u32 index = 0; - i32 attribs[40]; - - if (RGFW_GL_HINTS[RGFW_glProfile]== RGFW_glCore) { - SET_ATTRIB(WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB); - } - else { - SET_ATTRIB(WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB); - } - - if (RGFW_GL_HINTS[RGFW_glMinor] || RGFW_GL_HINTS[RGFW_glMajor]) { - SET_ATTRIB(WGL_CONTEXT_MAJOR_VERSION_ARB, RGFW_GL_HINTS[RGFW_glMajor]); - SET_ATTRIB(WGL_CONTEXT_MINOR_VERSION_ARB, RGFW_GL_HINTS[RGFW_glMinor]); - } - - SET_ATTRIB(0, 0); - - win->src.ctx = (HGLRC)wglCreateContextAttribsARB(win->src.hdc, NULL, attribs); - } else { /* fall back to a default context (probably opengl 2 or something) */ - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to create an accelerated OpenGL Context"); - win->src.ctx = wglCreateContext(win->src.hdc); - } - - ReleaseDC(win->src.window, win->src.hdc); - win->src.hdc = GetDC(win->src.window); - wglMakeCurrent(win->src.hdc, win->src.ctx); - - if (_RGFW.root != win) - wglShareLists(_RGFW.root->src.ctx, win->src.ctx); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context initalized"); -#else - RGFW_UNUSED(win); -#endif -} - -void RGFW_window_freeOpenGL(RGFW_window* win) { -#ifdef RGFW_OPENGL - if (win->src.ctx == NULL) return; - wglDeleteContext((HGLRC) win->src.ctx); /*!< delete opengl context */ - win->src.ctx = NULL; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context freed"); -#else - RGFW_UNUSED(win); -#endif -} -#endif - - -i32 RGFW_init(void) { -#if defined(RGFW_C89) || defined(__cplusplus) - if (_RGFW_init) return 0; - _RGFW_init = RGFW_TRUE; - _RGFW.root = NULL; _RGFW.current = NULL; _RGFW.windowCount = -1; _RGFW.eventLen = 0; _RGFW.eventIndex = 0; -#endif - - #ifndef RGFW_NO_XINPUT - if (RGFW_XInput_dll == NULL) - RGFW_loadXInput(); - #endif +i32 RGFW_initPlatform(void) { #ifndef RGFW_NO_DPI #if (_WIN32_WINNT >= 0x0600) SetProcessDPIAware(); @@ -6902,36 +9666,33 @@ i32 RGFW_init(void) { #endif u8 RGFW_blk[] = { 0, 0, 0, 0 }; - _RGFW.hiddenMouse = RGFW_loadMouse(RGFW_blk, RGFW_AREA(1, 1), 4); - - _RGFW.windowCount = 0; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context initialized"); + _RGFW->hiddenMouse = RGFW_loadMouse(RGFW_blk, 1, 1, RGFW_formatRGBA8); return 1; } -RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { +RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, RGFW_window* win) { if (name[0] == 0) name = (char*) " "; - - RGFW_window_basic_init(win, rect, flags); - win->src.hIconSmall = win->src.hIconBig = NULL; - win->src.maxSize = RGFW_AREA(0, 0); - win->src.minSize = RGFW_AREA(0, 0); - win->src.aspectRatio = RGFW_AREA(0, 0); + win->src.maxSizeW = 0; + win->src.maxSizeH = 0; + win->src.minSizeW = 0; + win->src.minSizeH = 0; + win->src.aspectRatioW = 0; + win->src.aspectRatioH = 0; HINSTANCE inh = GetModuleHandleA(NULL); #ifndef __cplusplus - WNDCLASSW Class = { 0 }; /*!< Setup the Window class. */ + WNDCLASSW Class = {0}; /*!< Setup the Window class. */ #else - WNDCLASSW Class = { }; + WNDCLASSW Class = {}; #endif - if (RGFW_className == NULL) - RGFW_className = (char*)name; + if (_RGFW->className == NULL) + _RGFW->className = (char*)name; wchar_t wide_class[256]; - MultiByteToWideChar(CP_UTF8, 0, RGFW_className, -1, wide_class, 255); + MultiByteToWideChar(CP_UTF8, 0, _RGFW->className, -1, wide_class, 255); Class.lpszClassName = wide_class; Class.hInstance = inh; @@ -6950,7 +9711,7 @@ RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowF RECT windowRect, clientRect; if (!(flags & RGFW_windowNoBorder)) { - window_style |= WS_CAPTION | WS_SYSMENU | WS_BORDER | WS_MINIMIZEBOX | WS_THICKFRAME; + window_style |= WS_CAPTION | WS_SYSMENU | WS_BORDER | WS_MINIMIZEBOX; if (!(flags & RGFW_windowNoResize)) window_style |= WS_SIZEBOX | WS_MAXIMIZEBOX; @@ -6959,43 +9720,37 @@ RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowF wchar_t wide_name[256]; MultiByteToWideChar(CP_UTF8, 0, name, -1, wide_name, 255); - HWND dummyWin = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->r.x, win->r.y, win->r.w, win->r.h, 0, 0, inh, 0); + HWND dummyWin = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->x, win->y, win->w, win->h, 0, 0, inh, 0); GetWindowRect(dummyWin, &windowRect); GetClientRect(dummyWin, &clientRect); +#ifdef RGFW_OPENGL RGFW_win32_loadOpenGLFuncs(dummyWin); +#endif + DestroyWindow(dummyWin); - win->src.hOffset = (u32)(windowRect.bottom - windowRect.top) - (u32)(clientRect.bottom - clientRect.top); - win->src.window = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->r.x, win->r.y, win->r.w, win->r.h + (i32)win->src.hOffset, 0, 0, inh, 0); + win->src.offsetW = (i32)(windowRect.right - windowRect.left) - (i32)(clientRect.right - clientRect.left); + win->src.offsetH = (i32)(windowRect.bottom - windowRect.top) - (i32)(clientRect.bottom - clientRect.top); + win->src.window = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->x, win->y, win->w + (i32)win->src.offsetW, win->h + (i32)win->src.offsetH, 0, 0, inh, 0); SetPropW(win->src.window, L"RGFW", win); - RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h)); /* so WM_GETMINMAXINFO gets called again */ + RGFW_window_resize(win, win->w, win->h); /* so WM_GETMINMAXINFO gets called again */ if (flags & RGFW_windowAllowDND) { - win->_flags |= RGFW_windowAllowDND; + win->internal.flags |= RGFW_windowAllowDND; RGFW_window_setDND(win, 1); } win->src.hdc = GetDC(win->src.window); - if ((flags & RGFW_windowNoInitAPI) == 0) { - RGFW_window_initOpenGL(win); - RGFW_window_initBuffer(win); - } - - RGFW_window_setFlags(win, flags); RGFW_win32_makeWindowTransparent(win); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); - RGFW_window_show(win); - - return win; + return win; } void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { - RGFW_setBit(&win->_flags, RGFW_windowNoBorder, !border); + RGFW_setBit(&win->internal.flags, RGFW_windowNoBorder, !border); LONG style = GetWindowLong(win->src.window, GWL_STYLE); - if (border == 0) { SetWindowLong(win->src.window, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW); SetWindowPos( @@ -7004,8 +9759,8 @@ void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { ); } else { - style |= WS_OVERLAPPEDWINDOW; - if (win->_flags & RGFW_windowNoResize) style &= ~WS_MAXIMIZEBOX; + if (win->internal.flags & RGFW_windowNoResize) style &= ~WS_MAXIMIZEBOX; + 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 @@ -7014,37 +9769,34 @@ void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { } void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow) { - RGFW_setBit(&win->_flags, RGFW_windowAllowDND, allow); + RGFW_setBit(&win->internal.flags, RGFW_windowAllowDND, allow); DragAcceptFiles(win->src.window, allow); } -RGFW_area RGFW_getScreenSize(void) { - HDC dc = GetDC(NULL); - RGFW_area area = RGFW_AREA(GetDeviceCaps(dc, HORZRES), GetDeviceCaps(dc, VERTRES)); - ReleaseDC(NULL, dc); - return area; -} - -RGFW_point RGFW_getGlobalMousePoint(void) { +RGFW_bool RGFW_getGlobalMouse(i32* x, i32* y) { POINT p; GetCursorPos(&p); - - return RGFW_POINT(p.x, p.y); + if (x) *x = p.x; + if (y) *y = p.y; + return RGFW_TRUE; } -void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { +void RGFW_window_setAspectRatio(RGFW_window* win, i32 w, i32 h) { RGFW_ASSERT(win != NULL); - win->src.aspectRatio = a; + win->src.aspectRatioW = w; + win->src.aspectRatioH = h; } -void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { +void RGFW_window_setMinSize(RGFW_window* win, i32 w, i32 h) { RGFW_ASSERT(win != NULL); - win->src.minSize = a; + win->src.minSizeW = w; + win->src.minSizeH = h; } -void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { +void RGFW_window_setMaxSize(RGFW_window* win, i32 w, i32 h) { RGFW_ASSERT(win != NULL); - win->src.maxSize = a; + win->src.maxSizeW = w; + win->src.maxSizeH = h; } void RGFW_window_focus(RGFW_window* win) { @@ -7056,7 +9808,7 @@ void RGFW_window_focus(RGFW_window* win) { void RGFW_window_raise(RGFW_window* win) { RGFW_ASSERT(win); BringWindowToTop(win->src.window); - SetWindowPos(win->src.window, HWND_TOP, win->r.x, win->r.y, win->r.w, win->r.h, SWP_NOSIZE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + SetWindowPos(win->src.window, HWND_TOP, win->x, win->y, win->w, win->h, SWP_NOSIZE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); } void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { @@ -7064,24 +9816,32 @@ void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { if (fullscreen == RGFW_FALSE) { RGFW_window_setBorder(win, 1); - SetWindowPos(win->src.window, HWND_NOTOPMOST, win->_oldRect.x, win->_oldRect.y, win->_oldRect.w, win->_oldRect.h + (i32)win->src.hOffset, + SetWindowPos(win->src.window, HWND_NOTOPMOST, win->internal.oldX, win->internal.oldY, win->internal.oldW + (i32)win->src.offsetW, win->internal.oldH + (i32)win->src.offsetH, SWP_NOOWNERZORDER | SWP_FRAMECHANGED); - win->_flags &= ~(u32)RGFW_windowFullscreen; - win->r = win->_oldRect; + win->internal.flags &= ~(u32)RGFW_windowFullscreen; + win->x = win->internal.oldX; + win->y = win->internal.oldY; + win->w = win->internal.oldW; + win->h = win->internal.oldH; return; } - win->_oldRect = win->r; - win->_flags |= RGFW_windowFullscreen; + win->internal.oldX = win->x; + win->internal.oldY = win->y; + win->internal.oldW = win->w; + win->internal.oldH = win->h; + win->internal.flags |= RGFW_windowFullscreen; RGFW_monitor mon = RGFW_window_getMonitor(win); RGFW_window_setBorder(win, 0); - SetWindowPos(win->src.window, HWND_TOPMOST, 0, 0, (i32)mon.mode.area.w, (i32)mon.mode.area.h, SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW); + SetWindowPos(win->src.window, HWND_TOPMOST, (i32)mon.x, (i32)mon.x, (i32)mon.mode.w, (i32)mon.mode.h, SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW); RGFW_monitor_scaleToWindow(mon, win); - win->r = RGFW_RECT(0, 0, mon.mode.area.w, mon.mode.area.h); + win->x = mon.x; win->y = mon.x; + win->w = mon.mode.w; + win->h = mon.mode.h; } void RGFW_window_maximize(RGFW_window* win) { @@ -7112,149 +9872,16 @@ RGFW_bool RGFW_window_isFloating(RGFW_window* win) { return (GetWindowLongPtr(win->src.window, GWL_EXSTYLE) & WS_EX_TOPMOST) != 0; } -u8 RGFW_xinput2RGFW[] = { - RGFW_gamepadA, /* or PS X button */ - RGFW_gamepadB, /* or PS circle button */ - RGFW_gamepadX, /* or PS square button */ - RGFW_gamepadY, /* or PS triangle button */ - RGFW_gamepadR1, /* right bumper */ - RGFW_gamepadL1, /* left bump */ - RGFW_gamepadL2, /* left trigger */ - RGFW_gamepadR2, /* right trigger */ - 0, 0, 0, 0, 0, 0, 0, 0, - RGFW_gamepadUp, /* dpad up */ - RGFW_gamepadDown, /* dpad down */ - RGFW_gamepadLeft, /* dpad left */ - RGFW_gamepadRight, /* dpad right */ - RGFW_gamepadStart, /* start button */ - RGFW_gamepadSelect,/* select button */ - RGFW_gamepadL3, - RGFW_gamepadR3, -}; -i32 RGFW_checkXInput(RGFW_window* win, RGFW_event* e); -i32 RGFW_checkXInput(RGFW_window* win, RGFW_event* e) { - #ifndef RGFW_NO_XINPUT - - RGFW_UNUSED(win); - u16 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_RTHUMB_PRESS) - continue; - - /* gamepad + 1 = RGFW_gamepadButtonReleased */ - e->type = RGFW_gamepadButtonPressed + !(keystroke.Flags & XINPUT_KEYSTROKE_KEYDOWN); - e->button = RGFW_xinput2RGFW[keystroke.VirtualKey - 0x5800]; - RGFW_gamepadPressed[i][e->button].prev = RGFW_gamepadPressed[i][e->button].current; - RGFW_gamepadPressed[i][e->button].current = RGFW_BOOL(keystroke.Flags & XINPUT_KEYSTROKE_KEYDOWN); - - RGFW_gamepadButtonCallback(win, i, e->button, e->type == RGFW_gamepadButtonPressed); - return 1; - } - - XINPUT_STATE state; - if (XInputGetState == NULL || - XInputGetState((DWORD) i, &state) == ERROR_DEVICE_NOT_CONNECTED - ) { - if (RGFW_gamepads[i] == 0) - continue; - - RGFW_gamepads[i] = 0; - RGFW_gamepadCount--; - - win->event.type = RGFW_gamepadDisconnected; - win->event.gamepad = (u16)i; - RGFW_gamepadCallback(win, i, 0); - return 1; - } - - if (RGFW_gamepads[i] == 0) { - RGFW_gamepads[i] = 1; - RGFW_gamepadCount++; - - char str[] = "Microsoft X-Box (XInput device)"; - RGFW_MEMCPY(RGFW_gamepads_name[i], str, sizeof(str)); - RGFW_gamepads_name[i][sizeof(RGFW_gamepads_name[i]) - 1] = '\0'; - win->event.type = RGFW_gamepadConnected; - win->event.gamepad = i; - RGFW_gamepads_type[i] = RGFW_gamepadMicrosoft; - - RGFW_gamepadCallback(win, i, 1); - 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 && - state.Gamepad.sThumbLX > -INPUT_DEADZONE) && - (state.Gamepad.sThumbLY < INPUT_DEADZONE && - state.Gamepad.sThumbLY > -INPUT_DEADZONE)) - { - state.Gamepad.sThumbLX = 0; - state.Gamepad.sThumbLY = 0; - } - - if ((state.Gamepad.sThumbRX < INPUT_DEADZONE && - state.Gamepad.sThumbRX > -INPUT_DEADZONE) && - (state.Gamepad.sThumbRY < INPUT_DEADZONE && - state.Gamepad.sThumbRY > -INPUT_DEADZONE)) - { - state.Gamepad.sThumbRX = 0; - state.Gamepad.sThumbRY = 0; - } - - e->axisesCount = 2; - RGFW_point axis1 = RGFW_POINT(((float)state.Gamepad.sThumbLX / 32768.0f) * 100, ((float)state.Gamepad.sThumbLY / -32768.0f) * 100); - RGFW_point axis2 = RGFW_POINT(((float)state.Gamepad.sThumbRX / 32768.0f) * 100, ((float)state.Gamepad.sThumbRY / -32768.0f) * 100); - - if (axis1.x != e->axis[0].x || axis1.y != e->axis[0].y){ - win->event.whichAxis = 0; - - e->type = RGFW_gamepadAxisMove; - e->axis[0] = axis1; - RGFW_gamepadAxes[i][0] = e->axis[0]; - - RGFW_gamepadAxisCallback(win, e->gamepad, e->axis, e->axisesCount, e->whichAxis); - return 1; - } - - if (axis2.x != e->axis[1].x || axis2.y != e->axis[1].y) { - win->event.whichAxis = 1; - e->type = RGFW_gamepadAxisMove; - e->axis[1] = axis2; - RGFW_gamepadAxes[i][1] = e->axis[1]; - - RGFW_gamepadAxisCallback(win, e->gamepad, e->axis, e->axisesCount, e->whichAxis); - return 1; - } - } - - #endif - - return 0; -} - void RGFW_stopCheckEvents(void) { - PostMessageW(_RGFW.root->src.window, WM_NULL, 0, 0); + PostMessageW(_RGFW->root->src.window, WM_NULL, 0, 0); } -void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { - RGFW_UNUSED(win); +void RGFW_waitForEvent(i32 waitMS) { MsgWaitForMultipleObjects(0, NULL, FALSE, (DWORD)waitMS, QS_ALLINPUT); } u8 RGFW_rgfwToKeyChar(u32 rgfw_keycode) { - UINT vsc = RGFW_rgfwToApiKey(rgfw_keycode); // Should return a Windows VK_* code + UINT vsc = RGFW_rgfwToApiKey(rgfw_keycode); /* Should return a Windows VK_* code */ BYTE keyboardState[256] = {0}; if (!GetKeyboardState(keyboardState)) @@ -7272,273 +9899,17 @@ u8 RGFW_rgfwToKeyChar(u32 rgfw_keycode) { return (u8)charBuffer[0]; } -RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { - if (win == NULL || ((win->_flags & RGFW_windowFreeOnClose) && (win->_flags & RGFW_EVENT_QUIT))) return NULL; - RGFW_event* ev = RGFW_window_checkEventCore(win); - if (ev) { - return ev; - } - - static HDROP drop; - if (win->event.type == RGFW_DNDInit) { - 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); - - u32 i; - for (i = 0; i < win->event.droppedFilesCount; i++) { - UINT length = DragQueryFileW(drop, i, NULL, 0); - if (length == 0) - continue; - - WCHAR buffer[RGFW_MAX_PATH * 2]; - if (length > (RGFW_MAX_PATH * 2) - 1) - length = RGFW_MAX_PATH * 2; - - DragQueryFileW(drop, i, buffer, length + 1); - - char* str = RGFW_createUTF8FromWideStringWin32(buffer); - if (str != NULL) - RGFW_MEMCPY(win->event.droppedFiles[i], str, length + 1); - - win->event.droppedFiles[i][RGFW_MAX_PATH - 1] = '\0'; - } - - DragFinish(drop); - RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); - - win->event.type = RGFW_DND; - return &win->event; - } - - if (RGFW_checkXInput(win, &win->event)) - return &win->event; - - static BYTE keyboardState[256]; - GetKeyboardState(keyboardState); - +void RGFW_pollEvents(void) { + RGFW_resetPrevState(); MSG msg; - if (PeekMessageA(&msg, NULL, 0u, 0u, PM_REMOVE)) { - if (msg.hwnd != win->src.window && msg.hwnd != NULL) { - TranslateMessage(&msg); - DispatchMessageA(&msg); - return RGFW_window_checkEvent(win); - } - } else { - return NULL; - } - - switch (msg.message) { - case WM_MOUSELEAVE: - win->event.type = RGFW_mouseLeave; - win->_flags |= RGFW_MOUSE_LEFT; - RGFW_mouseNotifyCallback(win, win->event.point, 0); - break; - case WM_SYSKEYUP: case WM_KEYUP: { - i32 scancode = (HIWORD(msg.lParam) & (KF_EXTENDED | 0xff)); - if (scancode == 0) - scancode = (i32)MapVirtualKeyW((UINT)msg.wParam, MAPVK_VK_TO_VSC); - - switch (scancode) { - case 0x54: scancode = 0x137; break; /* Alt+PrtS */ - case 0x146: scancode = 0x45; break; /* Ctrl+Pause */ - case 0x136: scancode = 0x36; break; /* CJK IME sets the extended bit for right Shift */ - default: break; - } - - win->event.key = (u8)RGFW_apiKeyToRGFW((u32) scancode); - - if (msg.wParam == VK_CONTROL) { - if (HIWORD(msg.lParam) & KF_EXTENDED) - win->event.key = RGFW_controlR; - else win->event.key = RGFW_controlL; - } - - wchar_t charBuffer; - ToUnicodeEx((UINT)msg.wParam, (UINT)scancode, keyboardState, (wchar_t*)&charBuffer, 1, 0, NULL); - - win->event.keyChar = (u8)charBuffer; - - RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; - win->event.type = RGFW_keyReleased; - RGFW_keyboard[win->event.key].current = 0; - - RGFW_updateKeyMods(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001), (GetKeyState(VK_SCROLL) & 0x0001)); - - RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, 0); - break; - } - case WM_SYSKEYDOWN: case WM_KEYDOWN: { - i32 scancode = (HIWORD(msg.lParam) & (KF_EXTENDED | 0xff)); - if (scancode == 0) - scancode = (i32)MapVirtualKeyW((u32)msg.wParam, MAPVK_VK_TO_VSC); - - switch (scancode) { - case 0x54: scancode = 0x137; break; /* Alt+PrtS */ - case 0x146: scancode = 0x45; break; /* Ctrl+Pause */ - case 0x136: scancode = 0x36; break; /* CJK IME sets the extended bit for right Shift */ - default: break; - } - - win->event.key = (u8)RGFW_apiKeyToRGFW((u32) scancode); - if (msg.wParam == VK_CONTROL) { - if (HIWORD(msg.lParam) & KF_EXTENDED) - win->event.key = RGFW_controlR; - else win->event.key = RGFW_controlL; - } - - wchar_t charBuffer; - ToUnicodeEx((UINT)msg.wParam, (UINT)scancode, keyboardState, &charBuffer, 1, 0, NULL); - win->event.keyChar = (u8)charBuffer; - - RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; - - win->event.type = RGFW_keyPressed; - win->event.repeat = RGFW_isPressed(win, win->event.key); - RGFW_keyboard[win->event.key].current = 1; - RGFW_updateKeyMods(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001), (GetKeyState(VK_SCROLL) & 0x0001)); - - RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, 1); - break; - } - case WM_MOUSEMOVE: { - if ((win->_flags & RGFW_HOLD_MOUSE)) - break; - - win->event.type = RGFW_mousePosChanged; - - i32 x = GET_X_LPARAM(msg.lParam); - i32 y = GET_Y_LPARAM(msg.lParam); - - RGFW_mousePosCallback(win, win->event.point, win->event.vector); - - if (win->_flags & RGFW_MOUSE_LEFT) { - win->_flags &= ~(u32)RGFW_MOUSE_LEFT; - win->event.type = RGFW_mouseEnter; - RGFW_mouseNotifyCallback(win, win->event.point, 1); - } - - win->event.point.x = x; - win->event.point.y = y; - win->_lastMousePoint = RGFW_POINT(x, y); - - break; - } - case WM_INPUT: { - if (!(win->_flags & RGFW_HOLD_MOUSE)) - break; - - unsigned size = sizeof(RAWINPUT); - static RAWINPUT raw; - - 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; - - if (raw.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { - POINT pos = {0, 0}; - int width, height; - - if (raw.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) { - pos.x += GetSystemMetrics(SM_XVIRTUALSCREEN); - pos.y += GetSystemMetrics(SM_YVIRTUALSCREEN); - width = GetSystemMetrics(SM_CXVIRTUALSCREEN); - height = GetSystemMetrics(SM_CYVIRTUALSCREEN); - } - else { - width = GetSystemMetrics(SM_CXSCREEN); - height = GetSystemMetrics(SM_CYSCREEN); - } - - pos.x += (int) (((float)raw.data.mouse.lLastX / 65535.f) * (float)width); - pos.y += (int) (((float)raw.data.mouse.lLastY / 65535.f) * (float)height); - ScreenToClient(win->src.window, &pos); - - win->event.vector.x = pos.x - win->_lastMousePoint.x; - win->event.vector.y = pos.y - win->_lastMousePoint.y; - } else { - win->event.vector.x = raw.data.mouse.lLastX; - win->event.vector.y = raw.data.mouse.lLastY; - } - - win->event.type = RGFW_mousePosChanged; - win->_lastMousePoint.x += win->event.vector.x; - win->_lastMousePoint.y += win->event.vector.y; - win->event.point = win->_lastMousePoint; - RGFW_mousePosCallback(win, win->event.point, win->event.vector); - break; - } - case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: case WM_XBUTTONDOWN: - if (msg.message == WM_XBUTTONDOWN) - win->event.button = RGFW_mouseMisc1 + (GET_XBUTTON_WPARAM(msg.wParam) == XBUTTON2); - else win->event.button = (msg.message == WM_LBUTTONDOWN) ? RGFW_mouseLeft : - (msg.message == WM_RBUTTONDOWN) ? RGFW_mouseRight : RGFW_mouseMiddle; - - win->event.type = RGFW_mouseButtonPressed; - 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_LBUTTONUP: case WM_RBUTTONUP: case WM_MBUTTONUP: case WM_XBUTTONUP: - if (msg.message == WM_XBUTTONUP) - win->event.button = RGFW_mouseMisc1 + (GET_XBUTTON_WPARAM(msg.wParam) == XBUTTON2); - else win->event.button = (msg.message == WM_LBUTTONUP) ? RGFW_mouseLeft : - (msg.message == WM_RBUTTONUP) ? RGFW_mouseRight : RGFW_mouseMiddle; - win->event.type = RGFW_mouseButtonReleased; - 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_MOUSEWHEEL: - if (msg.wParam > 0) - win->event.button = RGFW_mouseScrollUp; - else - win->event.button = RGFW_mouseScrollDown; - - 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; - - win->event.type = RGFW_mouseButtonPressed; - RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); - break; - case WM_DROPFILES: { - win->event.type = RGFW_DNDInit; - - drop = (HDROP) msg.wParam; - POINT pt; - - /* Move the mouse to the position of the drop */ - DragQueryPoint(drop, &pt); - - win->event.point.x = pt.x; - win->event.point.y = pt.y; - - RGFW_dndInitCallback(win, win->event.point); - } - break; - default: - TranslateMessage(&msg); - DispatchMessageA(&msg); - return RGFW_window_checkEvent(win); + while (PeekMessageA(&msg, NULL, 0u, 0u, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageA(&msg); } - - TranslateMessage(&msg); - DispatchMessageA(&msg); - - return &win->event; } RGFW_bool RGFW_window_isHidden(RGFW_window* win) { RGFW_ASSERT(win != NULL); - return IsWindowVisible(win->src.window) == 0 && !RGFW_window_isMinimized(win); } @@ -7546,9 +9917,9 @@ RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { RGFW_ASSERT(win != NULL); #ifndef __cplusplus - WINDOWPLACEMENT placement = { 0 }; + WINDOWPLACEMENT placement = {0}; #else - WINDOWPLACEMENT placement = { }; + WINDOWPLACEMENT placement = {}; #endif GetWindowPlacement(win->src.window, &placement); return placement.showCmd == SW_SHOWMINIMIZED; @@ -7558,9 +9929,9 @@ RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { RGFW_ASSERT(win != NULL); #ifndef __cplusplus - WINDOWPLACEMENT placement = { 0 }; + WINDOWPLACEMENT placement = {0}; #else - WINDOWPLACEMENT placement = { }; + WINDOWPLACEMENT placement = {}; #endif GetWindowPlacement(win->src.window, &placement); return placement.showCmd == SW_SHOWMAXIMIZED || IsZoomed(win->src.window); @@ -7568,51 +9939,49 @@ RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { typedef struct { int iIndex; HMONITOR hMonitor; RGFW_monitor* monitors; } RGFW_mInfo; #ifndef RGFW_NO_MONITOR -RGFW_monitor win32CreateMonitor(HMONITOR src); -RGFW_monitor win32CreateMonitor(HMONITOR src) { +RGFW_monitor RGFW_win32_createMonitor(HMONITOR src); +RGFW_monitor RGFW_win32_createMonitor(HMONITOR src) { RGFW_monitor monitor; - MONITORINFOEX monitorInfo; + RGFW_MEMSET(&monitor, 0, sizeof(monitor)); - monitorInfo.cbSize = sizeof(MONITORINFOEX); - GetMonitorInfoA(src, (LPMONITORINFO)&monitorInfo); + MONITORINFOEXW monitorInfo; + monitorInfo.cbSize = sizeof(MONITORINFOEXW); + GetMonitorInfoW(src, (LPMONITORINFO)&monitorInfo); /* get the monitor's index */ - DISPLAY_DEVICEA dd; + DISPLAY_DEVICEW dd; dd.cb = sizeof(dd); DWORD deviceNum; - for (deviceNum = 0; EnumDisplayDevicesA(NULL, deviceNum, &dd, 0); deviceNum++) { + for (deviceNum = 0; EnumDisplayDevicesW(NULL, deviceNum, &dd, 0); deviceNum++) { if (!(dd.StateFlags & DISPLAY_DEVICE_ACTIVE)) continue; - DEVMODEA dm; + DEVMODEW dm; ZeroMemory(&dm, sizeof(dm)); dm.dmSize = sizeof(dm); - if (EnumDisplaySettingsA(dd.DeviceName, ENUM_CURRENT_SETTINGS, &dm)) { + if (EnumDisplaySettingsW(dd.DeviceName, ENUM_CURRENT_SETTINGS, &dm)) { monitor.mode.refreshRate = dm.dmDisplayFrequency; RGFW_splitBPP(dm.dmBitsPerPel, &monitor.mode); } - DISPLAY_DEVICEA mdd; + DISPLAY_DEVICEW mdd; mdd.cb = sizeof(mdd); - if (EnumDisplayDevicesA(dd.DeviceName, (DWORD)deviceNum, &mdd, 0)) { - RGFW_STRNCPY(monitor.name, mdd.DeviceString, sizeof(monitor.name) - 1); + if (EnumDisplayDevicesW(dd.DeviceName, (DWORD)deviceNum, &mdd, 0)) { + RGFW_createUTF8FromWideStringWin32(mdd.DeviceString, monitor.name, sizeof(monitor.name)); monitor.name[sizeof(monitor.name) - 1] = '\0'; break; } } - - - monitor.x = monitorInfo.rcWork.left; monitor.y = monitorInfo.rcWork.top; - monitor.mode.area.w = (u32)(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left); - monitor.mode.area.h = (u32)(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top); + monitor.mode.w = (i32)(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left); + monitor.mode.h = (i32)(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top); - HDC hdc = CreateDC(monitorInfo.szDevice, NULL, NULL, NULL); + HDC hdc = CreateDCW(monitorInfo.szDevice, NULL, NULL, NULL); /* get pixels per inch */ float dpiX = (float)GetDeviceCaps(hdc, LOGPIXELSX); float dpiY = (float)GetDeviceCaps(hdc, LOGPIXELSX); @@ -7638,7 +10007,7 @@ RGFW_monitor win32CreateMonitor(HMONITOR src) { } #endif - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, "monitor found"); return monitor; } #endif /* RGFW_NO_MONITOR */ @@ -7655,7 +10024,7 @@ BOOL CALLBACK GetMonitorHandle(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMon if (info->iIndex >= 6) return FALSE; - info->monitors[info->iIndex] = win32CreateMonitor(hMonitor); + info->monitors[info->iIndex] = RGFW_win32_createMonitor(hMonitor); info->iIndex++; return TRUE; @@ -7663,9 +10032,9 @@ BOOL CALLBACK GetMonitorHandle(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMon RGFW_monitor RGFW_getPrimaryMonitor(void) { #ifdef __cplusplus - return win32CreateMonitor(MonitorFromPoint({ 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); + return RGFW_win32_createMonitor(MonitorFromPoint({0, 0}, MONITOR_DEFAULTTOPRIMARY)); #else - return win32CreateMonitor(MonitorFromPoint((POINT) { 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); + return RGFW_win32_createMonitor(MonitorFromPoint((POINT){0, 0}, MONITOR_DEFAULTTOPRIMARY)); #endif } @@ -7683,7 +10052,7 @@ RGFW_monitor* RGFW_getMonitors(size_t* len) { RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { HMONITOR src = MonitorFromWindow(win->src.window, MONITOR_DEFAULTTOPRIMARY); - return win32CreateMonitor(src); + return RGFW_win32_createMonitor(src); } RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { @@ -7705,7 +10074,7 @@ RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW if (strcmp(dd.DeviceName, (const char*)monitorInfo.szDevice) != 0) continue; - + DEVMODEA dm; ZeroMemory(&dm, sizeof(dm)); dm.dmSize = sizeof(dm); @@ -7713,8 +10082,8 @@ RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW if (EnumDisplaySettingsA(dd.DeviceName, ENUM_CURRENT_SETTINGS, &dm)) { if (request & RGFW_monitorScale) { dm.dmFields |= DM_PELSWIDTH | DM_PELSHEIGHT; - dm.dmPelsWidth = mode.area.w; - dm.dmPelsHeight = mode.area.h; + dm.dmPelsWidth = (u32)mode.w; + dm.dmPelsHeight = (u32)mode.h; } if (request & RGFW_monitorRefresh) { @@ -7727,8 +10096,8 @@ RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW dm.dmBitsPerPel = (DWORD)(mode.red + mode.green + mode.blue); } - if (ChangeDisplaySettingsExA((LPCSTR)dd.DeviceName, (DEVMODE *)&dm, NULL, CDS_TEST, NULL) == DISP_CHANGE_SUCCESSFUL) { - if (ChangeDisplaySettingsExA((LPCSTR)dd.DeviceName, (DEVMODE *)&dm, NULL, CDS_UPDATEREGISTRY, NULL) == DISP_CHANGE_SUCCESSFUL) + if (ChangeDisplaySettingsExA(dd.DeviceName, &dm, NULL, CDS_TEST, NULL) == DISP_CHANGE_SUCCESSFUL) { + if (ChangeDisplaySettingsExA(dd.DeviceName, &dm, NULL, CDS_UPDATEREGISTRY, NULL) == DISP_CHANGE_SUCCESSFUL) return RGFW_TRUE; return RGFW_FALSE; } else return RGFW_FALSE; @@ -7739,17 +10108,15 @@ RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW } #endif -HICON RGFW_loadHandleImage(u8* src, i32 c, RGFW_area a, BOOL icon); -HICON RGFW_loadHandleImage(u8* src, i32 c, RGFW_area a, BOOL icon) { - size_t channels = (size_t)c; - +HICON RGFW_loadHandleImage(u8* data, i32 w, i32 h, RGFW_format format, BOOL icon); +HICON RGFW_loadHandleImage(u8* data, i32 w, i32 h, RGFW_format format, BOOL icon) { BITMAPV5HEADER bi; ZeroMemory(&bi, sizeof(bi)); bi.bV5Size = sizeof(bi); - bi.bV5Width = (i32)a.w; - bi.bV5Height = -((LONG) a.h); + bi.bV5Width = (i32)w; + bi.bV5Height = -((LONG) h); bi.bV5Planes = 1; - bi.bV5BitCount = (WORD)(channels * 8); + bi.bV5BitCount = (WORD)32; bi.bV5Compression = BI_RGB; HDC dc = GetDC(NULL); u8* target = NULL; @@ -7758,26 +10125,16 @@ HICON RGFW_loadHandleImage(u8* src, i32 c, RGFW_area a, BOOL icon) { (BITMAPINFO*) &bi, DIB_RGB_COLORS, (void**) &target, NULL, (DWORD) 0); - size_t x, y; - for (y = 0; y < a.h; y++) { - for (x = 0; x < a.w; x++) { - size_t index = (y * 4 * (size_t)a.w) + x * channels; - target[index] = src[index + 2]; - target[index + 1] = src[index + 1]; - target[index + 2] = src[index]; - target[index + 3] = src[index + 3]; - } - } - + RGFW_copyImageData(target, w, h, RGFW_formatBGRA8, data, format); ReleaseDC(NULL, dc); - HBITMAP mask = CreateBitmap((i32)a.w, (i32)a.h, 1, 1, NULL); + HBITMAP mask = CreateBitmap((i32)w, (i32)h, 1, 1, NULL); ICONINFO ii; ZeroMemory(&ii, sizeof(ii)); ii.fIcon = icon; - ii.xHotspot = a.w / 2; - ii.yHotspot = a.h / 2; + ii.xHotspot = (u32)w / 2; + ii.yHotspot = (u32)h / 2; ii.hbmMask = mask; ii.hbmColor = color; @@ -7788,9 +10145,8 @@ HICON RGFW_loadHandleImage(u8* src, i32 c, RGFW_area a, BOOL icon) { return handle; } - -void* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { - HCURSOR cursor = (HCURSOR) RGFW_loadHandleImage(icon, channels, a, FALSE); +RGFW_mouse* RGFW_loadMouse(u8* data, i32 w, i32 h, RGFW_format format) { + HCURSOR cursor = (HCURSOR) RGFW_loadHandleImage(data, w, h, format, FALSE); return cursor; } @@ -7828,16 +10184,12 @@ void RGFW_window_hide(RGFW_window* win) { } void RGFW_window_show(RGFW_window* win) { - if (win->_flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); + if (win->internal.flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); ShowWindow(win->src.window, SW_RESTORE); } #define RGFW_FREE_LIBRARY(x) if (x != NULL) FreeLibrary(x); x = NULL; -void RGFW_deinit(void) { - #ifndef RGFW_NO_XINPUT - RGFW_FREE_LIBRARY(RGFW_XInput_dll); - #endif - +void RGFW_deinitPlatform(void) { #ifndef RGFW_NO_DPI RGFW_FREE_LIBRARY(RGFW_Shcore_dll); #endif @@ -7850,55 +10202,35 @@ void RGFW_deinit(void) { #endif RGFW_FREE_LIBRARY(RGFW_wgl_dll); - _RGFW.root = NULL; - RGFW_freeMouse(_RGFW.hiddenMouse); - _RGFW.windowCount = -1; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context deinitialized"); + RGFW_freeMouse(_RGFW->hiddenMouse); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, "global context deinitialized"); } -void RGFW_window_close(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - #ifdef RGFW_BUFFER - DeleteDC(win->src.hdcMem); - DeleteObject(win->src.bitmap); - #endif - - if ((win->_flags & RGFW_windowNoInitAPI) == 0) RGFW_window_freeOpenGL(win); +void RGFW_window_closePlatform(RGFW_window* win) { RemovePropW(win->src.window, L"RGFW"); ReleaseDC(win->src.window, win->src.hdc); /*!< delete device context */ DestroyWindow(win->src.window); /*!< delete window */ if (win->src.hIconSmall) DestroyIcon(win->src.hIconSmall); if (win->src.hIconBig) DestroyIcon(win->src.hIconBig); - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a window was freed"); - _RGFW.windowCount--; - if (_RGFW.windowCount == 0) RGFW_deinit(); - - RGFW_clipboard_switch(NULL); - RGFW_FREE(win->event.droppedFiles); - if ((win->_flags & RGFW_WINDOW_ALLOC)) { - RGFW_FREE(win); - win = NULL; - } } -void RGFW_window_move(RGFW_window* win, RGFW_point v) { +void RGFW_window_move(RGFW_window* win, i32 x, i32 y) { RGFW_ASSERT(win != NULL); - win->r.x = v.x; - win->r.y = v.y; - SetWindowPos(win->src.window, HWND_TOP, win->r.x, win->r.y, 0, 0, SWP_NOSIZE); + win->x = x; + win->y = y; + SetWindowPos(win->src.window, HWND_TOP, win->x, win->y, 0, 0, SWP_NOSIZE); } -void RGFW_window_resize(RGFW_window* win, RGFW_area a) { +void RGFW_window_resize(RGFW_window* win, i32 w, i32 h) { RGFW_ASSERT(win != NULL); - win->r.w = (i32)a.w; - win->r.h = (i32)a.h; - SetWindowPos(win->src.window, HWND_TOP, 0, 0, win->r.w, win->r.h + (i32)win->src.hOffset, SWP_NOMOVE); + win->w = w; + win->h = h; + SetWindowPos(win->src.window, HWND_TOP, 0, 0, win->w + (i32)win->src.offsetW, win->h + (i32)win->src.offsetH, SWP_NOMOVE); } @@ -7936,15 +10268,13 @@ void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { } #endif -RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* src, RGFW_area a, i32 channels, u8 type) { +RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_icon type) { RGFW_ASSERT(win != NULL); #ifndef RGFW_WIN95 - RGFW_UNUSED(channels); - if (win->src.hIconSmall && (type & RGFW_iconWindow)) DestroyIcon(win->src.hIconSmall); if (win->src.hIconBig && (type & RGFW_iconTaskbar)) DestroyIcon(win->src.hIconBig); - if (src == NULL) { + if (data == NULL) { HICON defaultIcon = LoadIcon(NULL, IDI_APPLICATION); if (type & RGFW_iconWindow) SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)defaultIcon); @@ -7954,18 +10284,17 @@ RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* src, RGFW_area a, i32 chan } if (type & RGFW_iconWindow) { - win->src.hIconSmall = RGFW_loadHandleImage(src, channels, a, TRUE); + win->src.hIconSmall = RGFW_loadHandleImage(data, w, h, format, TRUE); SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)win->src.hIconSmall); } if (type & RGFW_iconTaskbar) { - win->src.hIconBig = RGFW_loadHandleImage(src, channels, a, TRUE); + win->src.hIconBig = RGFW_loadHandleImage(data, w, h, format, TRUE); SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)win->src.hIconBig); } return RGFW_TRUE; #else - RGFW_UNUSED(src); - RGFW_UNUSED(a); - RGFW_UNUSED(channels); + RGFW_UNUSED(img); + RGFW_UNUSED(type); return RGFW_FALSE; #endif } @@ -7997,7 +10326,7 @@ RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { if (textLen > 1) wcstombs(str, wstr, (size_t)(textLen)); - str[textLen] = '\0'; + str[textLen - 1] = '\0'; } } @@ -8025,7 +10354,7 @@ void RGFW_writeClipboard(const char* text, u32 textLen) { MultiByteToWideChar(CP_UTF8, 0, text, -1, buffer, (i32)textLen); GlobalUnlock(object); - if (!OpenClipboard(_RGFW.root->src.window)) { + if (!OpenClipboard(_RGFW->root->src.window)) { GlobalFree(object); return; } @@ -8035,94 +10364,307 @@ void RGFW_writeClipboard(const char* text, u32 textLen) { CloseClipboard(); } -void RGFW_window_moveMouse(RGFW_window* win, RGFW_point p) { +void RGFW_window_moveMouse(RGFW_window* win, i32 x, i32 y) { RGFW_ASSERT(win != NULL); - win->_lastMousePoint = RGFW_POINT(p.x - win->r.x, p.y - win->r.y); - SetCursorPos(p.x, p.y); + win->internal.lastMouseX = x - win->x; + win->internal.lastMouseX = y - win->y; + SetCursorPos(x, y); } #ifdef RGFW_OPENGL -void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { +RGFW_bool RGFW_extensionSupportedPlatform_OpenGL(const char * extension, size_t len) { + const char* extensions = NULL; + + RGFW_proc proc = RGFW_getProcAddress_OpenGL("wglGetExtensionsStringARB"); + RGFW_proc proc2 = RGFW_getProcAddress_OpenGL("wglGetExtensionsStringEXT"); + + if (proc) + extensions = ((const char* (*)(HDC))proc)(wglGetCurrentDC()); + else if (proc2) + extensions = ((const char*(*)(void))proc2)(); + return extensions != NULL && RGFW_extensionSupportedStr(extensions, extension, len); +} + +RGFW_proc RGFW_getProcAddress_OpenGL(const char* procname) { + RGFW_proc proc = (RGFW_proc)wglGetProcAddress(procname); + if (proc) + return proc; + + return (RGFW_proc) GetProcAddress(RGFW_wgl_dll, procname); +} + +void RGFW_win32_loadOpenGLFuncs(HWND dummyWin) { + if (wglSwapIntervalEXT != NULL && wglChoosePixelFormatARB != NULL && wglChoosePixelFormatARB != NULL) + return; + + HDC dummy_dc = GetDC(dummyWin); + u32 pfd_flags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + + PIXELFORMATDESCRIPTOR pfd = {sizeof(pfd), 1, pfd_flags, PFD_TYPE_RGBA, 32, 8, PFD_MAIN_PLANE, 32, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 32, 8, 0, PFD_MAIN_PLANE, 0, 0, 0, 0}; + + int dummy_pixel_format = ChoosePixelFormat(dummy_dc, &pfd); + SetPixelFormat(dummy_dc, dummy_pixel_format, &pfd); + + HGLRC dummy_context = wglCreateContext(dummy_dc); + + HGLRC cur = wglGetCurrentContext(); + wglMakeCurrent(dummy_dc, dummy_context); + + wglCreateContextAttribsARB = ((PFNWGLCREATECONTEXTATTRIBSARBPROC(WINAPI *)(const char*)) wglGetProcAddress)("wglCreateContextAttribsARB"); + wglChoosePixelFormatARB = ((PFNWGLCHOOSEPIXELFORMATARBPROC(WINAPI *)(const char*)) wglGetProcAddress)("wglChoosePixelFormatARB"); + + wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)(RGFW_proc)wglGetProcAddress("wglSwapIntervalEXT"); + if (wglSwapIntervalEXT == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to load swap interval function"); + } + + wglMakeCurrent(dummy_dc, cur); + wglDeleteContext(dummy_context); + ReleaseDC(dummyWin, dummy_dc); +} + +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_FULL_ACCELERATION_ARB 0x2027 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_TYPE_RGBA_ARB 0x202b +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_ALPHA_BITS_ARB 0x201b +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_STEREO_ARB 0x2012 +#define WGL_AUX_BUFFERS_ARB 0x2024 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_ACCUM_RED_BITS_ARB 0x201e +#define WGL_ACCUM_GREEN_BITS_ARB 0x201f +#define WGL_ACCUM_BLUE_BITS_ARB 0x2020 +#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021 +#define WGL_COLORSPACE_SRGB_EXT 0x3089 +#define WGL_CONTEXT_OPENGL_NO_ERROR_ARB 0x31b3 +#define WGL_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097 +#define WGL_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB 0x0000 +#define WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define WGL_ACCESS_READ_WRITE_NV 0x00000001 +#define WGL_COVERAGE_SAMPLES_NV 0x2042 +#define WGL_CONTEXT_ES_PROFILE_BIT_EXT 0x00000004 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20A9 +#define WGL_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097 +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 +#define WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004 + +RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { + const char flushControl[] = "WGL_ARB_context_flush_control"; + const char noError[] = "WGL_ARB_create_context_no_error"; + const char robustness[] = "WGL_ARB_create_context_robustness"; + + win->src.ctx.native = ctx; + win->src.gfxType = RGFW_gfxNativeOpenGL; + + PIXELFORMATDESCRIPTOR pfd; + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.iLayerType = PFD_MAIN_PLANE; + pfd.cColorBits = 32; + pfd.cAlphaBits = 8; + pfd.cDepthBits = 24; + pfd.cStencilBits = (BYTE)hints->stencil; + pfd.cAuxBuffers = (BYTE)hints->auxBuffers; + if (hints->stereo) pfd.dwFlags |= PFD_STEREO; + + /* try to create the pixel format we want for OpenGL and then try to create an OpenGL context for the specified version */ + if (hints->renderer == RGFW_glSoftware) + pfd.dwFlags |= PFD_GENERIC_FORMAT | PFD_GENERIC_ACCELERATED; + + /* get pixel format, default to a basic pixel format */ + int pixel_format = ChoosePixelFormat(win->src.hdc, &pfd); + if (wglChoosePixelFormatARB != NULL) { + i32 pixel_format_attribs[50]; + RGFW_attribStack stack; + RGFW_attribStack_init(&stack, pixel_format_attribs, 50); + + RGFW_attribStack_pushAttribs(&stack, WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB); + RGFW_attribStack_pushAttribs(&stack, WGL_DRAW_TO_WINDOW_ARB, 1); + RGFW_attribStack_pushAttribs(&stack, WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB); + RGFW_attribStack_pushAttribs(&stack, WGL_SUPPORT_OPENGL_ARB, 1); + RGFW_attribStack_pushAttribs(&stack, WGL_COLOR_BITS_ARB, 32); + RGFW_attribStack_pushAttribs(&stack, WGL_DOUBLE_BUFFER_ARB, 1); + RGFW_attribStack_pushAttribs(&stack, WGL_ALPHA_BITS_ARB, hints->alpha); + RGFW_attribStack_pushAttribs(&stack, WGL_DEPTH_BITS_ARB, hints->depth); + RGFW_attribStack_pushAttribs(&stack, WGL_STENCIL_BITS_ARB, hints->stencil); + RGFW_attribStack_pushAttribs(&stack, WGL_STEREO_ARB, hints->stereo); + RGFW_attribStack_pushAttribs(&stack, WGL_AUX_BUFFERS_ARB, hints->auxBuffers); + RGFW_attribStack_pushAttribs(&stack, WGL_RED_BITS_ARB, hints->red); + RGFW_attribStack_pushAttribs(&stack, WGL_GREEN_BITS_ARB, hints->blue); + RGFW_attribStack_pushAttribs(&stack, WGL_BLUE_BITS_ARB, hints->green); + RGFW_attribStack_pushAttribs(&stack, WGL_ACCUM_RED_BITS_ARB, hints->accumRed); + RGFW_attribStack_pushAttribs(&stack, WGL_ACCUM_GREEN_BITS_ARB, hints->accumGreen); + RGFW_attribStack_pushAttribs(&stack, WGL_ACCUM_BLUE_BITS_ARB, hints->accumBlue); + RGFW_attribStack_pushAttribs(&stack, WGL_ACCUM_ALPHA_BITS_ARB, hints->accumAlpha); + + if(hints->sRGB) { + if (hints->profile != RGFW_glES) + RGFW_attribStack_pushAttribs(&stack, WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, 1); + else + RGFW_attribStack_pushAttribs(&stack, WGL_COLORSPACE_SRGB_EXT, hints->sRGB); + } + + RGFW_attribStack_pushAttribs(&stack, WGL_COVERAGE_SAMPLES_NV, hints->samples); + + RGFW_attribStack_pushAttribs(&stack, 0, 0); + + int new_pixel_format; + UINT num_formats; + wglChoosePixelFormatARB(win->src.hdc, pixel_format_attribs, 0, 1, &new_pixel_format, &num_formats); + if (!num_formats) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to create a pixel format for WGL"); + else pixel_format = new_pixel_format; + } + + PIXELFORMATDESCRIPTOR suggested; + if (!DescribePixelFormat(win->src.hdc, pixel_format, sizeof(suggested), &suggested) || + !SetPixelFormat(win->src.hdc, pixel_format, &pfd)) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to set the WGL pixel format"); + + if (wglCreateContextAttribsARB != NULL) { + /* create OpenGL/WGL context for the specified version */ + i32 attribs[40]; + RGFW_attribStack stack; + RGFW_attribStack_init(&stack, attribs, 50); + + + i32 mask = 0; + switch (hints->profile) { + case RGFW_glES: mask |= WGL_CONTEXT_ES_PROFILE_BIT_EXT; break; + case RGFW_glCompatibility: mask |= WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; break; + case RGFW_glCore: mask |= WGL_CONTEXT_CORE_PROFILE_BIT_ARB; break; + default: mask |= WGL_CONTEXT_CORE_PROFILE_BIT_ARB; break; + } + + RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_PROFILE_MASK_ARB, mask); + + if (hints->minor || hints->major) { + RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_MAJOR_VERSION_ARB, hints->major); + RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_MINOR_VERSION_ARB, hints->minor); + } + + if (RGFW_extensionSupportedPlatform_OpenGL(noError, sizeof(noError))) + RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_OPENGL_NO_ERROR_ARB, hints->noError); + + if (RGFW_extensionSupportedPlatform_OpenGL(flushControl, sizeof(flushControl))) { + if (hints->releaseBehavior == RGFW_glReleaseFlush) { + RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_RELEASE_BEHAVIOR_ARB, WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB); /* WGL_CONTEXT_RELEASE_BEHAVIOR_ARB */ + } else if (hints->releaseBehavior == RGFW_glReleaseNone) { + RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_RELEASE_BEHAVIOR_ARB, WGL_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB); + } + } + + i32 flags = 0; + if (hints->debug) flags |= WGL_CONTEXT_DEBUG_BIT_ARB; + if (hints->robustness && RGFW_extensionSupportedPlatform_OpenGL(robustness, sizeof(robustness))) flags |= WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB; + if (flags) { + RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_FLAGS_ARB, flags); + } + + + RGFW_attribStack_pushAttribs(&stack, 0, 0); + + win->src.ctx.native->ctx = (HGLRC)wglCreateContextAttribsARB(win->src.hdc, NULL, attribs); + } + + if (wglCreateContextAttribsARB == NULL || win->src.ctx.native->ctx == NULL) { /* fall back to a default context (probably OpenGL 2 or something) */ + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to create an accelerated OpenGL Context."); + win->src.ctx.native->ctx = wglCreateContext(win->src.hdc); + } + + ReleaseDC(win->src.window, win->src.hdc); + win->src.hdc = GetDC(win->src.window); + + if (hints->share) { + wglShareLists((HGLRC)RGFW_getCurrentContext_OpenGL(), hints->share->ctx); + } + + wglMakeCurrent(win->src.hdc, win->src.ctx.native->ctx); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context initalized."); + return RGFW_TRUE; +} + +void RGFW_window_deleteContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx) { + wglDeleteContext((HGLRC) ctx->ctx); /*!< delete OpenGL context */ + win->src.ctx.native->ctx = NULL; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context freed."); +} + +void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win) { if (win == NULL) wglMakeCurrent(NULL, NULL); else - wglMakeCurrent(win->src.hdc, (HGLRC) win->src.ctx); + wglMakeCurrent(win->src.hdc, (HGLRC) win->src.ctx.native->ctx); +} +void* RGFW_getCurrentContext_OpenGL(void) { + return wglGetCurrentContext(); +} +void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { + RGFW_ASSERT(win->src.ctx.native); + SwapBuffers(win->src.hdc); } -void* RGFW_getCurrent_OpenGL(void) { return wglGetCurrentContext(); } -void RGFW_window_swapBuffers_OpenGL(RGFW_window* win){ SwapBuffers(win->src.hdc); } -#endif -#ifndef RGFW_EGL -void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { +void RGFW_window_swapInterval_OpenGL(RGFW_window* win, i32 swapInterval) { RGFW_ASSERT(win != NULL); -#if defined(RGFW_OPENGL) if (wglSwapIntervalEXT == NULL || wglSwapIntervalEXT(swapInterval) == FALSE) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to set swap interval"); -#else - RGFW_UNUSED(swapInterval); -#endif + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to set swap interval"); } #endif -void RGFW_window_swapBuffers_software(RGFW_window* win) { -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - if (win->buffer != win->src.bitmapBits) - memcpy(win->src.bitmapBits, win->buffer, win->bufferSize.w * win->bufferSize.h * 4); - - RGFW_RGB_to_BGR(win, win->src.bitmapBits); - BitBlt(win->src.hdc, 0, 0, win->r.w, win->r.h, win->src.hdcMem, 0, 0, SRCCOPY); -#else - RGFW_UNUSED(win); -#endif -} - -char* RGFW_createUTF8FromWideStringWin32(const WCHAR* source) { - if (source == NULL) { - return NULL; +RGFW_bool RGFW_createUTF8FromWideStringWin32(const WCHAR* source, char* output, size_t max) { + i32 size = 0; + if (source == NULL) { + return RGFW_FALSE; } - i32 size = WideCharToMultiByte(CP_UTF8, 0, source, -1, NULL, 0, NULL, NULL); + size = WideCharToMultiByte(CP_UTF8, 0, source, -1, NULL, 0, NULL, NULL); if (!size) { - return NULL; + return RGFW_FALSE; } - static char target[RGFW_MAX_PATH * 2]; - if (size > RGFW_MAX_PATH * 2) - size = RGFW_MAX_PATH * 2; + if (size > (i32)max) + size = (i32)max; - target[size] = 0; - - if (!WideCharToMultiByte(CP_UTF8, 0, source, -1, target, size, NULL, NULL)) { - return NULL; + if (!WideCharToMultiByte(CP_UTF8, 0, source, -1, output, size, NULL, NULL)) { + return RGFW_FALSE; } - return target; + output[size] = 0; + return RGFW_TRUE; } -u64 RGFW_getTimerFreq(void) { - static u64 frequency = 0; - if (frequency == 0) QueryPerformanceFrequency((LARGE_INTEGER*)&frequency); +#ifdef RGFW_WEBGPU +WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance instance) { + WGPUSurfaceDescriptor surfaceDesc = {0}; + WGPUSurfaceSourceWindowsHWND fromHwnd = {0}; + fromHwnd.chain.sType = WGPUSType_SurfaceSourceWindowsHWND; + fromHwnd.hwnd = window->src.window; /* Get HWND from RGFW window source */ + if (!fromHwnd.hwnd) { + fprintf(stderr, "RGFW Error: HWND is NULL for Windows window.\n"); + return NULL; + } + fromHwnd.hinstance = GetModuleHandle(NULL); /* Get current process HINSTANCE */ - return frequency; + surfaceDesc.nextInChain = (WGPUChainedStruct*)&fromHwnd.chain; + return wgpuInstanceCreateSurface(instance, &surfaceDesc); } - -u64 RGFW_getTimerValue(void) { - u64 value; - QueryPerformanceCounter((LARGE_INTEGER*)&value); - return value; -} - -void RGFW_sleep(u64 ms) { - Sleep((u32)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 /* RGFW_WINDOWS */ /* @@ -8151,6 +10693,7 @@ void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { SetThreadPriority #include #include +#ifndef __OBJC__ typedef CGRect NSRect; typedef CGPoint NSPoint; typedef CGSize NSSize; @@ -8160,207 +10703,6 @@ 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(x, y) ((BOOL (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) -#define objc_msgSend_void(x, y) ((void (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) -#define objc_msgSend_void_id(x, y, z) ((void (*)(id, SEL, id))objc_msgSend) ((id)x, (SEL)y, (id)z) -#define objc_msgSend_uint(x, y) ((NSUInteger (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) -#define objc_msgSend_void_bool(x, y, z) ((void (*)(id, SEL, BOOL))objc_msgSend) ((id)(x), (SEL)y, (BOOL)z) -#define objc_msgSend_bool_void(x, y) ((BOOL (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) -#define objc_msgSend_void_SEL(x, y, z) ((void (*)(id, SEL, SEL))objc_msgSend) ((id)(x), (SEL)y, (SEL)z) -#define objc_msgSend_id(x, y) ((id (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) -#define objc_msgSend_id_id(x, y, z) ((id (*)(id, SEL, id))objc_msgSend) ((id)(x), (SEL)y, (id)z) -#define objc_msgSend_id_bool(x, y, z) ((BOOL (*)(id, SEL, id))objc_msgSend) ((id)(x), (SEL)y, (id)z) -#define objc_msgSend_int(x, y, z) ((id (*)(id, SEL, int))objc_msgSend) ((id)(x), (SEL)y, (int)z) -#define objc_msgSend_arr(x, y, z) ((id (*)(id, SEL, int))objc_msgSend) ((id)(x), (SEL)y, (int)z) -#define objc_msgSend_ptr(x, y, z) ((id (*)(id, SEL, void*))objc_msgSend) ((id)(x), (SEL)y, (void*)z) -#define objc_msgSend_class(x, y) ((id (*)(Class, SEL))objc_msgSend) ((Class)(x), (SEL)y) -#define objc_msgSend_class_char(x, y, z) ((id (*)(Class, SEL, char*))objc_msgSend) ((Class)(x), (SEL)y, (char*)z) - -id NSApp = NULL; - -#define NSRelease(obj) objc_msgSend_void((id)obj, sel_registerName("release")) -id NSString_stringWithUTF8String(const char* str); -id 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(id str); -const char* NSString_to_char(id str) { - return ((const char* (*)(id, SEL)) objc_msgSend) ((id)(id)str, sel_registerName("UTF8String")); -} - -void si_impl_func_to_SEL_with_name(const char* class_name, const char* register_name, void* function); -void si_impl_func_to_SEL_with_name(const char* class_name, const char* register_name, void* function) { - Class selected_class; - - if (RGFW_STRNCMP(class_name, "NSView", 6) == 0) { - selected_class = objc_getClass("ViewClass"); - } else if (RGFW_STRNCMP(class_name, "NSWindow", 8) == 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) -#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":", (void*)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":", (void*)function) - -unsigned char* NSBitmapImageRep_bitmapData(id imageRep); -unsigned char* NSBitmapImageRep_bitmapData(id imageRep) { - return ((unsigned char* (*)(id, SEL))objc_msgSend) ((id)imageRep, sel_registerName("bitmapData")); -} - -typedef RGFW_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 = (1 << 8), - NSBitmapFormatThirtyTwoBitLittleEndian = (1 << 9), - NSBitmapFormatSixteenBitBigEndian = (1 << 10), - NSBitmapFormatThirtyTwoBitBigEndian = (1 << 11) -}; - -id 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); -id 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) { - SEL func = sel_registerName("initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel:"); - - return (id) ((id(*)(id, SEL, unsigned char**, NSInteger, NSInteger, NSInteger, NSInteger, bool, bool, id, NSBitmapFormat, NSInteger, NSInteger))objc_msgSend) - (NSAlloc((id)objc_getClass("NSBitmapImageRep")), func, planes, width, height, bps, spp, alpha, isPlanar, NSString_stringWithUTF8String(colorSpaceName), bitmapFormat, rowBytes, pixelBits); -} - -id NSColor_colorWithSRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha); -id NSColor_colorWithSRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) { - void* nsclass = objc_getClass("NSColor"); - SEL func = sel_registerName("colorWithSRGBRed:green:blue:alpha:"); - return ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend) - ((id)nsclass, func, red, green, blue, alpha); -} - -typedef RGFW_ENUM(NSInteger, NSOpenGLContextParameter) { - NSOpenGLContextParameterSwapInterval = 222, /* 1 param. 0 -> Don't sync, 1 -> Sync to vertical retrace */ - NSOpenGLContextParametectxaceOrder = 235, /* 1 param. 1 -> Above Window (default), -1 -> Below Window */ - NSOpenGLContextParametectxaceOpacity = 236, /* 1 param. 1-> Surface is opaque (default), 0 -> non-opaque */ - NSOpenGLContextParametectxaceBackingSize = 304, /* 2 params. Width/height of surface backing size */ - NSOpenGLContextParameterReclaimResources = 308, /* 0 params. */ - NSOpenGLContextParameterCurrentRendererID = 309, /* 1 param. Retrieves the current renderer ID */ - NSOpenGLContextParameterGPUVertexProcessing = 310, /* 1 param. Currently processing vertices with GPU (get) */ - NSOpenGLContextParameterGPUFragmentProcessing = 311, /* 1 param. Currently processing fragments with GPU (get) */ - NSOpenGLContextParameterHasDrawable = 314, /* 1 param. Boolean returned if drawable is attached */ - NSOpenGLContextParameterMPSwapsInFlight = 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 */ - NSOpenGLContextParametectxaceSurfaceVolatile API_DEPRECATED("", macos(10.0, 10.14)) = 306, /* 1 param. Surface volatile state */ -}; - -typedef RGFW_ENUM(NSInteger, NSWindowButton) { - NSWindowCloseButton = 0, - NSWindowMiniaturizeButton = 1, - NSWindowZoomButton = 2, - NSWindowToolbarButton = 3, - NSWindowDocumentIconButton = 4, - NSWindowDocumentVersionsButton = 6, - NSWindowFullScreenButton = 7, -}; -void NSOpenGLContext_setValues(id context, const int* vals, NSOpenGLContextParameter param); -void NSOpenGLContext_setValues(id context, const int* vals, NSOpenGLContextParameter param) { - ((void (*)(id, SEL, const int*, NSOpenGLContextParameter))objc_msgSend) - (context, sel_registerName("setValues:forParameter:"), vals, param); -} -void* NSOpenGLPixelFormat_initWithAttributes(const uint32_t* attribs); -void* NSOpenGLPixelFormat_initWithAttributes(const uint32_t* attribs) { - return (void*) ((id(*)(id, SEL, const uint32_t*))objc_msgSend) - (NSAlloc((id)objc_getClass("NSOpenGLPixelFormat")), sel_registerName("initWithAttributes:"), attribs); -} - -id NSPasteboard_generalPasteboard(void); -id NSPasteboard_generalPasteboard(void) { - return (id) objc_msgSend_id((id)objc_getClass("NSPasteboard"), sel_registerName("generalPasteboard")); -} - -id* cstrToNSStringArray(char** strs, size_t len); -id* cstrToNSStringArray(char** strs, size_t len) { - static id nstrs[6]; - size_t i; - for (i = 0; i < len; i++) - nstrs[i] = NSString_stringWithUTF8String(strs[i]); - - return nstrs; -} - -const char* NSPasteboard_stringForType(id pasteboard, NSPasteboardType dataType, size_t* len); -const char* NSPasteboard_stringForType(id pasteboard, NSPasteboardType dataType, size_t* len) { - SEL func = sel_registerName("stringForType:"); - id nsstr = NSString_stringWithUTF8String(dataType); - id nsString = ((id(*)(id, SEL, id))objc_msgSend)(pasteboard, func, nsstr); - const char* str = NSString_to_char(nsString); - if (len != NULL) - *len = (size_t)((NSUInteger(*)(id, SEL, int))objc_msgSend)(nsString, sel_registerName("maximumLengthOfBytesUsingEncoding:"), 4); - return str; -} - -id c_array_to_NSArray(void* array, size_t len); -id 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(id view, NSPasteboardType* newTypes, size_t len); -void NSregisterForDraggedTypes(id view, NSPasteboardType* newTypes, size_t len) { - id* ntypes = cstrToNSStringArray((char**)newTypes, len); - - id array = c_array_to_NSArray(ntypes, len); - objc_msgSend_void_id(view, sel_registerName("registerForDraggedTypes:"), array); - NSRelease(array); -} - -NSInteger NSPasteBoard_declareTypes(id pasteboard, NSPasteboardType* newTypes, size_t len, void* owner); -NSInteger NSPasteBoard_declareTypes(id pasteboard, NSPasteboardType* newTypes, size_t len, void* owner) { - id* ntypes = cstrToNSStringArray((char**)newTypes, len); - - SEL func = sel_registerName("declareTypes:owner:"); - - id array = c_array_to_NSArray(ntypes, len); - - NSInteger output = ((NSInteger(*)(id, SEL, id, void*))objc_msgSend) - (pasteboard, func, array, owner); - NSRelease(array); - - return output; -} - -#define NSRetain(obj) objc_msgSend_void((id)obj, sel_registerName("retain")) - typedef enum NSApplicationActivationPolicy { NSApplicationActivationPolicyRegular, NSApplicationActivationPolicyAccessory, @@ -8389,8 +10731,7 @@ typedef RGFW_ENUM(u32, NSWindowStyleMask) { NSWindowStyleMaskHUDWindow = 1 << 13 }; -NSPasteboardType const NSPasteboardTypeString = "public.utf8-plain-text"; /* Replaces NSStringPasteboardType */ - +#define NSPasteboardTypeString "public.utf8-plain-text" typedef RGFW_ENUM(i32, NSDragOperation) { NSDragOperationNone = 0, @@ -8403,87 +10744,331 @@ typedef RGFW_ENUM(i32, NSDragOperation) { NSDragOperationEvery = (int)ULONG_MAX }; -void* NSArray_objectAtIndex(id array, NSUInteger index) { - SEL func = sel_registerName("objectAtIndex:"); - return ((id(*)(id, SEL, NSUInteger))objc_msgSend)(array, func, index); +typedef RGFW_ENUM(NSInteger, NSOpenGLContextParameter) { + NSOpenGLContextParameterSwapInterval = 222, /* 1 param. 0 -> Don't sync, 1 -> Sync to vertical retrace */ + NSOpenGLContextParametectxaceOrder = 235, /* 1 param. 1 -> Above Window (default), -1 -> Below Window */ + NSOpenGLContextParametectxaceOpacity = 236, /* 1 param. 1-> Surface is opaque (default), 0 -> non-opaque */ + NSOpenGLContextParametectxaceBackingSize = 304, /* 2 params. Width/height of surface backing size */ + NSOpenGLContextParameterReclaimResources = 308, /* 0 params. */ + NSOpenGLContextParameterCurrentRendererID = 309, /* 1 param. Retrieves the current renderer ID */ + NSOpenGLContextParameterGPUVertexProcessing = 310, /* 1 param. Currently processing vertices with GPU (get) */ + NSOpenGLContextParameterGPUFragmentProcessing = 311, /* 1 param. Currently processing fragments with GPU (get) */ + NSOpenGLContextParameterHasDrawable = 314, /* 1 param. Boolean returned if drawable is attached */ + NSOpenGLContextParameterMPSwapsInFlight = 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 */ + NSOpenGLContextParametectxaceSurfaceVolatile API_DEPRECATED("", macos(10.0, 10.14)) = 306, /* 1 param. Surface volatile state */ +}; + +typedef RGFW_ENUM(NSInteger, NSWindowButton) { + NSWindowCloseButton = 0, + NSWindowMiniaturizeButton = 1, + NSWindowZoomButton = 2, + NSWindowToolbarButton = 3, + NSWindowDocumentIconButton = 4, + NSWindowDocumentVersionsButton = 6, + NSWindowFullScreenButton = 7, +}; + +#define NSPasteboardTypeURL "public.url" +#define NSPasteboardTypeFileURL "public.file-url" +#define NSTrackingMouseEnteredAndExited 0x01 +#define NSTrackingMouseMoved 0x02 +#define NSTrackingCursorUpdate 0x04 +#define NSTrackingActiveWhenFirstResponder 0x10 +#define NSTrackingActiveInKeyWindow 0x20 +#define NSTrackingActiveInActiveApp 0x40 +#define NSTrackingActiveAlways 0x80 +#define NSTrackingAssumeInside 0x100 +#define NSTrackingInVisibleRect 0x200 +#define NSTrackingEnabledDuringMouseDrag 0x400 +enum { + NSOpenGLPFAAllRenderers = 1, /* choose from all available renderers */ + NSOpenGLPFATripleBuffer = 3, /* choose a triple buffered pixel format */ + NSOpenGLPFADoubleBuffer = 5, /* choose a double buffered pixel format */ + NSOpenGLPFAAuxBuffers = 7, /* number of aux buffers */ + NSOpenGLPFAColorSize = 8, /* number of color buffer bits */ + NSOpenGLPFAAlphaSize = 11, /* number of alpha component bits */ + NSOpenGLPFADepthSize = 12, /* number of depth buffer bits */ + NSOpenGLPFAStencilSize = 13, /* number of stencil buffer bits */ + NSOpenGLPFAAccumSize = 14, /* number of accum buffer bits */ + NSOpenGLPFAMinimumPolicy = 51, /* never choose smaller buffers than requested */ + NSOpenGLPFAMaximumPolicy = 52, /* choose largest buffers of type requested */ + NSOpenGLPFASampleBuffers = 55, /* number of multi sample buffers */ + NSOpenGLPFASamples = 56, /* number of samples per multi sample buffer */ + NSOpenGLPFAAuxDepthStencil = 57, /* each aux buffer has its own depth stencil */ + NSOpenGLPFAColorFloat = 58, /* color buffers store floating point pixels */ + NSOpenGLPFAMultisample = 59, /* choose multisampling */ + NSOpenGLPFASupersample = 60, /* choose supersampling */ + NSOpenGLPFASampleAlpha = 61, /* request alpha filtering */ + NSOpenGLPFARendererID = 70, /* request renderer by ID */ + NSOpenGLPFANoRecovery = 72, /* disable all failure recovery systems */ + NSOpenGLPFAAccelerated = 73, /* choose a hardware accelerated renderer */ + NSOpenGLPFAClosestPolicy = 74, /* choose the closest color buffer to request */ + NSOpenGLPFABackingStore = 76, /* back buffer contents are valid after swap */ + NSOpenGLPFAScreenMask = 84, /* bit mask of supported physical screens */ + NSOpenGLPFAAllowOfflineRenderers = 96, /* allow use of offline renderers */ + NSOpenGLPFAAcceleratedCompute = 97, /* choose a hardware accelerated compute device */ + NSOpenGLPFAOpenGLProfile = 99, /* specify an OpenGL Profile to use */ + NSOpenGLProfileVersionLegacy = 0x1000, /* The requested profile is a legacy (pre-OpenGL 3.0) profile. */ + NSOpenGLProfileVersion3_2Core = 0x3200, /* The 3.2 Profile of OpenGL */ + NSOpenGLProfileVersion4_1Core = 0x3200, /* The 4.1 profile of OpenGL */ + NSOpenGLPFAVirtualScreenCount = 128, /* number of virtual screens in this format */ + NSOpenGLPFAStereo = 6, + NSOpenGLPFAOffScreen = 53, + NSOpenGLPFAFullScreen = 54, + NSOpenGLPFASingleRenderer = 71, + NSOpenGLPFARobust = 75, + NSOpenGLPFAMPSafe = 78, + NSOpenGLPFAWindow = 80, + NSOpenGLPFAMultiScreen = 81, + NSOpenGLPFACompliant = 83, + NSOpenGLPFAPixelBuffer = 90, + NSOpenGLPFARemotePixelBuffer = 91, +}; + +typedef RGFW_ENUM(u32, NSEventType) { /* various types of events */ + NSEventTypeApplicationDefined = 15, +}; +typedef unsigned long long NSEventMask; + +typedef enum NSEventModifierFlags { + NSEventModifierFlagCapsLock = 1 << 16, + NSEventModifierFlagShift = 1 << 17, + NSEventModifierFlagControl = 1 << 18, + NSEventModifierFlagOption = 1 << 19, + NSEventModifierFlagCommand = 1 << 20, + NSEventModifierFlagNumericPad = 1 << 21 +} NSEventModifierFlags; + +typedef RGFW_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 = (1 << 8), + NSBitmapFormatThirtyTwoBitLittleEndian = (1 << 9), + NSBitmapFormatSixteenBitBigEndian = (1 << 10), + NSBitmapFormatThirtyTwoBitBigEndian = (1 << 11) +}; + +#else +#import +#include +#endif /* notdef __OBJC__ */ + +#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(x, y) ((BOOL (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_void(x, y) ((void (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_void_id(x, y, z) ((void (*)(id, SEL, id))objc_msgSend) ((id)x, (SEL)y, (id)z) +#define objc_msgSend_uint(x, y) ((NSUInteger (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_void_bool(x, y, z) ((void (*)(id, SEL, BOOL))objc_msgSend) ((id)(x), (SEL)y, (BOOL)z) +#define objc_msgSend_bool_void(x, y) ((BOOL (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_void_SEL(x, y, z) ((void (*)(id, SEL, SEL))objc_msgSend) ((id)(x), (SEL)y, (SEL)z) +#define objc_msgSend_id(x, y) ((id (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_id_id(x, y, z) ((id (*)(id, SEL, id))objc_msgSend) ((id)(x), (SEL)y, (id)z) +#define objc_msgSend_id_bool(x, y, z) ((BOOL (*)(id, SEL, id))objc_msgSend) ((id)(x), (SEL)y, (id)z) +#define objc_msgSend_int(x, y, z) ((id (*)(id, SEL, int))objc_msgSend) ((id)(x), (SEL)y, (int)z) +#define objc_msgSend_arr(x, y, z) ((id (*)(id, SEL, int))objc_msgSend) ((id)(x), (SEL)y, (int)z) +#define objc_msgSend_ptr(x, y, z) ((id (*)(id, SEL, void*))objc_msgSend) ((id)(x), (SEL)y, (void*)z) +#define objc_msgSend_class(x, y) ((id (*)(Class, SEL))objc_msgSend) ((Class)(x), (SEL)y) +#define objc_msgSend_class_char(x, y, z) ((id (*)(Class, SEL, char*))objc_msgSend) ((Class)(x), (SEL)y, (char*)z) + +#define NSRelease(obj) objc_msgSend_void((id)obj, sel_registerName("release")) +RGFWDEF id NSString_stringWithUTF8String(const char* str); +id NSString_stringWithUTF8String(const char* str) { + return ((id(*)(id, SEL, const char*))objc_msgSend) ((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), str); } -id NSWindow_contentView(id window) { - SEL func = sel_registerName("contentView"); - return objc_msgSend_id(window, func); +const char* NSString_to_char(id str); +const char* NSString_to_char(id str) { + return ((const char* (*)(id, SEL)) objc_msgSend) ((id)(id)str, sel_registerName("UTF8String")); } +unsigned char* NSBitmapImageRep_bitmapData(id imageRep); +unsigned char* NSBitmapImageRep_bitmapData(id imageRep) { + return ((unsigned char* (*)(id, SEL))objc_msgSend) ((id)imageRep, sel_registerName("bitmapData")); +} + +id 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); +id 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) { + SEL func = sel_registerName("initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel:"); + + return (id) ((id(*)(id, SEL, unsigned char**, NSInteger, NSInteger, NSInteger, NSInteger, bool, bool, id, NSBitmapFormat, NSInteger, NSInteger))objc_msgSend) + (NSAlloc((id)objc_getClass("NSBitmapImageRep")), func, planes, width, height, bps, spp, alpha, isPlanar, NSString_stringWithUTF8String(colorSpaceName), bitmapFormat, rowBytes, pixelBits); +} + +id NSColor_colorWithSRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha); +id NSColor_colorWithSRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) { + Class nsclass = objc_getClass("NSColor"); + SEL func = sel_registerName("colorWithSRGBRed:green:blue:alpha:"); + return ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend) + ((id)nsclass, func, red, green, blue, alpha); +} + +id NSPasteboard_generalPasteboard(void); +id NSPasteboard_generalPasteboard(void) { + return (id) objc_msgSend_id((id)objc_getClass("NSPasteboard"), sel_registerName("generalPasteboard")); +} + +id* cstrToNSStringArray(char** strs, size_t len); +id* cstrToNSStringArray(char** strs, size_t len) { + static id nstrs[6]; + size_t i; + for (i = 0; i < len; i++) + nstrs[i] = NSString_stringWithUTF8String(strs[i]); + + return nstrs; +} + +const char* NSPasteboard_stringForType(id pasteboard, NSPasteboardType dataType, size_t* len); +const char* NSPasteboard_stringForType(id pasteboard, NSPasteboardType dataType, size_t* len) { + SEL func = sel_registerName("stringForType:"); + id nsstr = NSString_stringWithUTF8String((const char*)dataType); + id nsString = ((id(*)(id, SEL, id))objc_msgSend)(pasteboard, func, nsstr); + const char* str = NSString_to_char(nsString); + if (len != NULL) + *len = (size_t)((NSUInteger(*)(id, SEL, int))objc_msgSend)(nsString, sel_registerName("maximumLengthOfBytesUsingEncoding:"), 4); + return str; +} + +id c_array_to_NSArray(void* array, size_t len); +id c_array_to_NSArray(void* array, size_t len) { + return ((id (*)(id, SEL, void*, NSUInteger))objc_msgSend) (NSAlloc(objc_getClass("NSArray")), sel_registerName("initWithObjects:count:"), array, len); +} + + +void NSregisterForDraggedTypes(id view, NSPasteboardType* newTypes, size_t len); +void NSregisterForDraggedTypes(id view, NSPasteboardType* newTypes, size_t len) { + id* ntypes = cstrToNSStringArray((char**)newTypes, len); + + id array = c_array_to_NSArray(ntypes, len); + objc_msgSend_void_id(view, sel_registerName("registerForDraggedTypes:"), array); + NSRelease(array); +} + +NSInteger NSPasteBoard_declareTypes(id pasteboard, NSPasteboardType* newTypes, size_t len, void* owner); +NSInteger NSPasteBoard_declareTypes(id pasteboard, NSPasteboardType* newTypes, size_t len, void* owner) { + id* ntypes = cstrToNSStringArray((char**)newTypes, len); + + SEL func = sel_registerName("declareTypes:owner:"); + + id array = c_array_to_NSArray(ntypes, len); + + NSInteger output = ((NSInteger(*)(id, SEL, id, void*))objc_msgSend) + (pasteboard, func, array, owner); + NSRelease(array); + + return output; +} + +#define NSRetain(obj) objc_msgSend_void((id)obj, sel_registerName("retain")) + /* End of cocoa wrapper */ -#ifdef RGFW_OPENGL -/* MacOS opengl API spares us yet again (there are no extensions) */ -RGFW_bool RGFW_extensionSupportedPlatform(const char * extension, size_t len) { RGFW_UNUSED(extension); RGFW_UNUSED(len); return RGFW_FALSE; } -CFBundleRef RGFWnsglFramework = NULL; +static id RGFW__osxCustomInitWithRGFWWindow(id self, SEL _cmd, RGFW_window* win) { + RGFW_UNUSED(_cmd); + struct objc_super s = { self, class_getSuperclass(object_getClass(self)) }; + self = ((id (*)(struct objc_super*, SEL))objc_msgSendSuper)(&s, sel_registerName("init")); -RGFW_proc RGFW_getProcAddress(const char* procname) { - if (RGFWnsglFramework == NULL) - RGFWnsglFramework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); + if (self != nil) { + object_setInstanceVariable(self, "RGFW_window", win); + object_setInstanceVariable(self, "trackingArea", nil); - CFStringRef symbolName = CFStringCreateWithCString(kCFAllocatorDefault, procname, kCFStringEncodingASCII); + object_setInstanceVariable( + self, "markedText", + ((id (*)(id, SEL))objc_msgSend)( + ((id (*)(Class, SEL))objc_msgSend)(objc_getClass("NSMutableAttributedString"), sel_registerName("alloc")), + sel_registerName("init") + ) + ); - RGFW_proc symbol = (RGFW_proc)CFBundleGetFunctionPointerForName(RGFWnsglFramework, symbolName); + ((void (*)(id, SEL))objc_msgSend)(self, sel_registerName("updateTrackingAreas")); - CFRelease(symbolName); + ((void (*)(id, SEL, id))objc_msgSend)( + self, sel_registerName("registerForDraggedTypes:"), + ((id (*)(Class, SEL, id))objc_msgSend)( + objc_getClass("NSArray"), + sel_registerName("arrayWithObject:"), + ((id (*)(Class, SEL, const char*))objc_msgSend)( + objc_getClass("NSString"), + sel_registerName("stringWithUTF8String:"), + "public.url" + ) + ) + ); + } - return symbol; -} -#endif - -id NSWindow_delegate(RGFW_window* win) { - return (id) objc_msgSend_id((id)win->src.window, sel_registerName("delegate")); + return self; } -u32 RGFW_OnClose(id self) { +static u32 RGFW_OnClose(id self) { RGFW_window* win = NULL; object_getInstanceVariable(self, (const char*)"RGFW_window", (void**)&win); if (win == NULL) return true; - RGFW_eventQueuePushEx(e.type = RGFW_quit; e._win = win); + RGFW_window_setShouldClose(win, RGFW_TRUE); + RGFW_eventQueuePushEx(e.type = RGFW_quit; e.common.win = win); RGFW_windowQuitCallback(win); return false; } /* NOTE(EimaMei): Fixes the constant clicking when the app is running under a terminal. */ -bool acceptsFirstResponder(void) { return true; } -bool performKeyEquivalent(id event) { RGFW_UNUSED(event); return true; } +static bool RGFW__osxAcceptsFirstResponder(void) { return true; } +static bool RGFW__osxPerformKeyEquivalent(id event) { RGFW_UNUSED(event); return true; } -NSDragOperation draggingEntered(id self, SEL sel, id sender) { +static NSDragOperation RGFW__osxDraggingEntered(id self, SEL sel, id sender) { RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); return NSDragOperationCopy; } -NSDragOperation draggingUpdated(id self, SEL sel, id sender) { +static NSDragOperation RGFW__osxDraggingUpdated(id self, SEL sel, id sender) { RGFW_UNUSED(sel); RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL || (!(win->_flags & RGFW_windowAllowDND))) + if (win == NULL || (!(win->internal.flags & RGFW_windowAllowDND))) return 0; + if (!(win->internal.enabledEvents & RGFW_dataDragFlag)) return NSDragOperationCopy; NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(sender, sel_registerName("draggingLocation")); - RGFW_eventQueuePushEx(e.type = RGFW_DNDInit; - e.point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); - e._win = win); + RGFW_eventQueuePushEx(e.type = RGFW_dataDrag; + e.mouse.x = (i32)p.x; e.mouse.y = (i32)(win->h - p.y); + e.common.win = win); - RGFW_dndInitCallback(win, win->event.point); + _RGFW->windowState.win = win; + _RGFW->windowState.dataDragging = RGFW_TRUE; + _RGFW->windowState.dropX = (i32)p.x; + _RGFW->windowState.dropY = (i32)(win->h - p.y); + + RGFW_dataDragCallback(win, (i32) p.x, (i32) (win->h - p.y)); return NSDragOperationCopy; } -bool prepareForDragOperation(id self) { +static bool RGFW__osxPrepareForDragOperation(id self) { RGFW_window* win = NULL; object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL) + if (win == NULL || (!(win->internal.enabledEvents & RGFW_dataDropFlag))) return true; - if (!(win->_flags & RGFW_windowAllowDND)) { + if (!(win->internal.flags & RGFW_windowAllowDND)) { return false; } @@ -8493,14 +11078,13 @@ bool prepareForDragOperation(id self) { void RGFW__osxDraggingEnded(id self, SEL sel, id sender); void RGFW__osxDraggingEnded(id self, SEL sel, id sender) { RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); return; } -/* NOTE(EimaMei): Usually, you never need 'id self, SEL cmd' for C -> Obj-C methods. This isn't the case. */ -bool performDragOperation(id self, SEL sel, id sender) { +static bool RGFW__osxPerformDragOperation(id self, SEL sel, id sender) { RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); RGFW_window* win = NULL; object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL) + if (win == NULL || (!(win->internal.enabledEvents & RGFW_dataDropFlag))) return false; /* id pasteBoard = objc_msgSend_id(sender, sel_registerName("draggingPasteboard")); */ @@ -8515,7 +11099,7 @@ bool performDragOperation(id self, SEL sel, id sender) { /* Check if the pasteboard contains file URLs */ if (objc_msgSend_id_bool(types, sel_registerName("containsObject:"), fileURLsType) == 0) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, RGFW_DEBUG_CTX(win, 0), "No files found on the pasteboard."); + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, "No files found on the pasteboard."); return 0; } @@ -8525,49 +11109,54 @@ bool performDragOperation(id self, SEL sel, id sender) { if (count == 0) return 0; - int i; - for (i = 0; i < count; i++) { + RGFW_event event; + event.drop.files = (char**)(void*)_RGFW->files; + + u32 i; + for (i = 0; i < (u32)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")); - RGFW_STRNCPY(win->event.droppedFiles[i], filePath, RGFW_MAX_PATH - 1); - win->event.droppedFiles[i][RGFW_MAX_PATH - 1] = '\0'; + RGFW_STRNCPY(event.drop.files[i], filePath, RGFW_MAX_PATH - 1); + event.drop.files[i][RGFW_MAX_PATH - 1] = '\0'; } - NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(sender, sel_registerName("draggingLocation")); - - win->event.droppedFilesCount = (size_t)count; - RGFW_eventQueuePushEx(e.type = RGFW_DND; - e.point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); - e.droppedFilesCount = (size_t)count; - e._win = win); - - RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); + + event.drop.count = (size_t)count; + RGFW_eventQueuePushEx(e.type = RGFW_dataDrop; + e.drop.count = (size_t)count; + e.drop.files = event.drop.files; + e.common.win = win); + + _RGFW->windowState.win = win; + _RGFW->windowState.dataDrop = RGFW_TRUE; + _RGFW->windowState.filesCount = event.drop.count; + RGFW_dataDropCallback(win, event.drop.files, event.drop.count); return false; } #ifndef RGFW_NO_IOKIT #include -#include +u32 RGFW_osx_getFallbackRefreshRate(CGDirectDisplayID displayID); u32 RGFW_osx_getFallbackRefreshRate(CGDirectDisplayID displayID) { u32 refreshRate = 0; io_iterator_t it; io_service_t service; CFNumberRef indexRef, clockRef, countRef; - uint32_t clock, count; + u32 clock, count; -#ifdef kIOMainPortDefault +#ifdef kIOMainPortDefault if (IOServiceGetMatchingServices(kIOMainPortDefault, IOServiceMatching("IOFramebuffer"), &it) != 0) -#elif defined(kIOMasterPortDefault) +#elif defined(kIOMasterPortDefault) if (IOServiceGetMatchingServices(kIOMainPortDefault, IOServiceMatching("IOFramebuffer"), &it) != 0) #endif return RGFW_FALSE; while ((service = IOIteratorNext(it)) != 0) { - uint32_t index; + u32 index; indexRef = (CFNumberRef)IORegistryEntryCreateCFProperty(service, CFSTR("IOFramebufferOpenGLIndex"), kCFAllocatorDefault, kNilOptions); if (indexRef == 0) continue; - + if (CFNumberGetValue(indexRef, kCFNumberIntType, &index) && CGOpenGLDisplayMaskToDisplayID(1 << index) == displayID) { CFRelease(indexRef); break; @@ -8582,7 +11171,8 @@ u32 RGFW_osx_getFallbackRefreshRate(CGDirectDisplayID displayID) { if (CFNumberGetValue(clockRef, kCFNumberIntType, &clock) && clock) { countRef = (CFNumberRef)IORegistryEntryCreateCFProperty(service, CFSTR("IOFBCurrentPixelCount"), kCFAllocatorDefault, kNilOptions); if (countRef && CFNumberGetValue(countRef, kCFNumberIntType, &count) && count) { - refreshRate = (u32)RGFW_ROUND(clock / (double) count); + float rate = (float)((double)clock / (double) count); + refreshRate = (u32)RGFW_ROUND(rate); CFRelease(countRef); } } @@ -8593,201 +11183,6 @@ u32 RGFW_osx_getFallbackRefreshRate(CGDirectDisplayID displayID) { IOObjectRelease(it); return refreshRate; } - -IOHIDDeviceRef RGFW_osxControllers[4] = {NULL}; - -size_t findControllerIndex(IOHIDDeviceRef device) { - size_t i; - for (i = 0; i < 4; i++) - if (RGFW_osxControllers[i] == device) - return i; - return (size_t)-1; -} - -void RGFW__osxInputValueChangedCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value) { - RGFW_UNUSED(context); RGFW_UNUSED(result); RGFW_UNUSED(sender); - IOHIDElementRef element = IOHIDValueGetElement(value); - - IOHIDDeviceRef device = IOHIDElementGetDevice(element); - size_t index = findControllerIndex(device); - if (index == (size_t)-1) return; - - uint32_t usagePage = IOHIDElementGetUsagePage(element); - uint32_t usage = IOHIDElementGetUsage(element); - - CFIndex intValue = IOHIDValueGetIntegerValue(value); - - u8 RGFW_osx2RGFWSrc[2][RGFW_gamepadFinal] = {{ - 0, RGFW_gamepadSelect, RGFW_gamepadL3, RGFW_gamepadR3, RGFW_gamepadStart, - RGFW_gamepadUp, RGFW_gamepadRight, RGFW_gamepadDown, RGFW_gamepadLeft, - RGFW_gamepadL2, RGFW_gamepadR2, RGFW_gamepadL1, RGFW_gamepadR1, - RGFW_gamepadY, RGFW_gamepadB, RGFW_gamepadA, RGFW_gamepadX, RGFW_gamepadHome}, - {0, RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadR3, RGFW_gamepadX, - RGFW_gamepadY, RGFW_gamepadRight, RGFW_gamepadL1, RGFW_gamepadR1, - RGFW_gamepadL2, RGFW_gamepadR2, RGFW_gamepadDown, RGFW_gamepadStart, - RGFW_gamepadUp, RGFW_gamepadL3, RGFW_gamepadSelect, RGFW_gamepadStart, RGFW_gamepadHome} - }; - - u8* RGFW_osx2RGFW = RGFW_osx2RGFWSrc[0]; - if (RGFW_gamepads_type[index] == RGFW_gamepadMicrosoft) - RGFW_osx2RGFW = RGFW_osx2RGFWSrc[1]; - - switch (usagePage) { - case kHIDPage_Button: { - u8 button = 0; - if (usage < sizeof(RGFW_osx2RGFW)) - button = RGFW_osx2RGFW[usage]; - - RGFW_gamepadButtonCallback(_RGFW.root, (u16)index, button, (u8)intValue); - RGFW_gamepadPressed[index][button].prev = RGFW_gamepadPressed[index][button].current; - RGFW_gamepadPressed[index][button].current = RGFW_BOOL(intValue); - RGFW_eventQueuePushEx(e.type = intValue ? RGFW_gamepadButtonPressed: RGFW_gamepadButtonReleased; - e.button = button; - e.gamepad = (u16)index; - e._win = _RGFW.root); - break; - } - case kHIDPage_GenericDesktop: { - CFIndex logicalMin = IOHIDElementGetLogicalMin(element); - CFIndex logicalMax = IOHIDElementGetLogicalMax(element); - - if (logicalMax <= logicalMin) return; - if (intValue < logicalMin) intValue = logicalMin; - if (intValue > logicalMax) intValue = logicalMax; - - i8 axisValue = (i8)(-100.0 + ((intValue - logicalMin) * 200.0) / (logicalMax - logicalMin)); - - u8 whichAxis = 0; - switch (usage) { - case kHIDUsage_GD_X: RGFW_gamepadAxes[index][0].x = axisValue; whichAxis = 0; break; - case kHIDUsage_GD_Y: RGFW_gamepadAxes[index][0].y = axisValue; whichAxis = 0; break; - case kHIDUsage_GD_Z: RGFW_gamepadAxes[index][1].x = axisValue; whichAxis = 1; break; - case kHIDUsage_GD_Rz: RGFW_gamepadAxes[index][1].y = axisValue; whichAxis = 1; break; - default: return; - } - - RGFW_event e; - e.type = RGFW_gamepadAxisMove; - e.gamepad = (u16)index; - e.whichAxis = whichAxis; - e._win = _RGFW.root; - for (size_t i = 0; i < 4; i++) - e.axis[i] = RGFW_gamepadAxes[index][i]; - - RGFW_eventQueuePush(e); - - RGFW_gamepadAxisCallback(_RGFW.root, (u16)index, RGFW_gamepadAxes[index], 2, whichAxis); - } - } -} - -void RGFW__osxDeviceAddedCallback(void* context, IOReturn result, void *sender, IOHIDDeviceRef device) { - RGFW_UNUSED(context); RGFW_UNUSED(result); RGFW_UNUSED(sender); - CFTypeRef usageRef = (CFTypeRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDPrimaryUsageKey)); - int usage = 0; - if (usageRef) - CFNumberGetValue((CFNumberRef)usageRef, kCFNumberIntType, (void*)&usage); - - if (usage != kHIDUsage_GD_Joystick && usage != kHIDUsage_GD_GamePad && usage != kHIDUsage_GD_MultiAxisController) { - return; - } - - size_t i; - for (i = 0; i < 4; i++) { - if (RGFW_osxControllers[i] != NULL) - continue; - - RGFW_osxControllers[i] = device; - - IOHIDDeviceRegisterInputValueCallback(device, RGFW__osxInputValueChangedCallback, NULL); - - CFStringRef deviceName = (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); - if (deviceName) - CFStringGetCString(deviceName, RGFW_gamepads_name[i], sizeof(RGFW_gamepads_name[i]), kCFStringEncodingUTF8); - - RGFW_gamepads_type[i] = RGFW_gamepadUnknown; - if (RGFW_STRSTR(RGFW_gamepads_name[i], "Microsoft") || RGFW_STRSTR(RGFW_gamepads_name[i], "X-Box") || RGFW_STRSTR(RGFW_gamepads_name[i], "Xbox")) - RGFW_gamepads_type[i] = RGFW_gamepadMicrosoft; - else if (RGFW_STRSTR(RGFW_gamepads_name[i], "PlayStation") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS3") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS4") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS5")) - RGFW_gamepads_type[i] = RGFW_gamepadSony; - else if (RGFW_STRSTR(RGFW_gamepads_name[i], "Nintendo")) - RGFW_gamepads_type[i] = RGFW_gamepadNintendo; - else if (RGFW_STRSTR(RGFW_gamepads_name[i], "Logitech")) - RGFW_gamepads_type[i] = RGFW_gamepadLogitech; - - RGFW_gamepads[i] = (u16)i; - RGFW_gamepadCount++; - - RGFW_eventQueuePushEx(e.type = RGFW_gamepadConnected; - e.gamepad = (u16)i; - e._win = _RGFW.root); - - RGFW_gamepadCallback(_RGFW.root, (u16)i, 1); - break; - } -} - -void RGFW__osxDeviceRemovedCallback(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) { - RGFW_UNUSED(context); RGFW_UNUSED(result); RGFW_UNUSED(sender); RGFW_UNUSED(device); - CFNumberRef usageRef = (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDPrimaryUsageKey)); - int usage = 0; - if (usageRef) - CFNumberGetValue(usageRef, kCFNumberIntType, &usage); - - if (usage != kHIDUsage_GD_Joystick && usage != kHIDUsage_GD_GamePad && usage != kHIDUsage_GD_MultiAxisController) { - return; - } - - size_t index = findControllerIndex(device); - if (index != (size_t)-1) - RGFW_osxControllers[index] = NULL; - - RGFW_eventQueuePushEx(e.type = RGFW_gamepadDisconnected; - e.gamepad = (u16)index; - e._win = _RGFW.root); - RGFW_gamepadCallback(_RGFW.root, (u16)index, 0); - - RGFW_gamepadCount--; -} - -RGFWDEF void RGFW_osxInitIOKit(void); -void RGFW_osxInitIOKit(void) { - IOHIDManagerRef hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); - if (!hidManager) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errIOKit, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to create IOHIDManager."); - return; - } - - CFMutableDictionaryRef matchingDictionary = CFDictionaryCreateMutable( - kCFAllocatorDefault, - 0, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks - ); - if (!matchingDictionary) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errIOKit, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to create matching dictionary for IOKit."); - CFRelease(hidManager); - return; - } - - CFDictionarySetValue( - matchingDictionary, - CFSTR(kIOHIDDeviceUsagePageKey), - CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, (int[]){kHIDPage_GenericDesktop}) - ); - - IOHIDManagerSetDeviceMatching(hidManager, matchingDictionary); - - IOHIDManagerRegisterDeviceMatchingCallback(hidManager, RGFW__osxDeviceAddedCallback, NULL); - IOHIDManagerRegisterDeviceRemovalCallback(hidManager, RGFW__osxDeviceRemovedCallback, NULL); - - IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); - - IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone); - - /* Execute the run loop once in order to register any initially-attached joysticks */ - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); -} #endif void RGFW_moveToMacOSResourceDir(void) { @@ -8816,83 +11211,94 @@ void RGFW_moveToMacOSResourceDir(void) { } -void RGFW__osxWindowDeminiaturize(id self, SEL sel) { +static void RGFW__osxWindowDeminiaturize(id self, SEL sel) { RGFW_UNUSED(sel); RGFW_window* win = NULL; object_getInstanceVariable(self, "RGFW_window", (void**)&win); if (win == NULL) return; - win->_flags |= RGFW_windowMinimize; - RGFW_eventQueuePushEx(e.type = RGFW_windowRestored; e._win = win); - RGFW_windowRestoredCallback(win, win->r); + win->internal.flags |= RGFW_windowMinimize; + if (!(win->internal.enabledEvents & RGFW_windowMinimizedFlag)) return; + RGFW_eventQueuePushEx(e.type = RGFW_windowRestored; e.common.win = win); + RGFW_windowRestoredCallback(win, win->x, win->y, win->w, win->h); } -void RGFW__osxWindowMiniaturize(id self, SEL sel) { +static void RGFW__osxWindowMiniaturize(id self, SEL sel) { RGFW_UNUSED(sel); RGFW_window* win = NULL; object_getInstanceVariable(self, "RGFW_window", (void**)&win); if (win == NULL) return; - win->_flags &= ~(u32)RGFW_windowMinimize; - RGFW_eventQueuePushEx(e.type = RGFW_windowMinimized; e._win = win); - RGFW_windowMinimizedCallback(win, win->r); + win->internal.flags &= ~(u32)RGFW_windowMinimize; + if (!(win->internal.enabledEvents & RGFW_windowMinimizedFlag)) return; + RGFW_eventQueuePushEx(e.type = RGFW_windowMinimized; e.common.win = win); + RGFW_windowMinimizedCallback(win); } -void RGFW__osxWindowBecameKey(id self, SEL sel) { +static void RGFW__osxWindowBecameKey(id self, SEL sel) { RGFW_UNUSED(sel); RGFW_window* win = NULL; object_getInstanceVariable(self, "RGFW_window", (void**)&win); if (win == NULL) return; - win->_flags |= RGFW_windowFocus; - RGFW_eventQueuePushEx(e.type = RGFW_focusIn; e._win = win); + win->internal.inFocus = RGFW_TRUE; + if ((win->internal.holdMouse)) RGFW_window_holdMouse(win); + if (!(win->internal.enabledEvents & RGFW_focusInFlag)) return; + + RGFW_eventQueuePushEx(e.type = RGFW_focusIn; e.common.win = win); RGFW_focusCallback(win, RGFW_TRUE); - - if ((win->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(win, RGFW_AREA(win->r.w, win->r.h)); } -void RGFW__osxWindowResignKey(id self, SEL sel) { +static void RGFW__osxWindowResignKey(id self, SEL sel) { RGFW_UNUSED(sel); RGFW_window* win = NULL; object_getInstanceVariable(self, "RGFW_window", (void**)&win); if (win == NULL) return; RGFW_window_focusLost(win); - RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e._win = win); + if (!(win->internal.enabledEvents & RGFW_focusOutFlag)) return; + + RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e.common.win = win); RGFW_focusCallback(win, RGFW_FALSE); } -NSSize RGFW__osxWindowResize(id self, SEL sel, NSSize frameSize) { - RGFW_UNUSED(sel); - +static void RGFW__osxDidWindowResize(id self, SEL _cmd, id notification) { + RGFW_UNUSED(_cmd); RGFW_UNUSED(notification); RGFW_window* win = NULL; object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL) return frameSize; + if (win == NULL) return; - win->r.w = (i32)frameSize.width; - win->r.h = (i32)frameSize.height; + NSRect frame; + if (win->src.view) frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.view, sel_registerName("frame")); + else return; + + if (frame.size.width == 0 || frame.size.height == 0) return; + win->w = (i32)frame.size.width; + win->h = (i32)frame.size.height; RGFW_monitor mon = RGFW_window_getMonitor(win); - if ((i32)mon.mode.area.w == win->r.w && (i32)mon.mode.area.h - 102 <= win->r.h) { - win->_flags |= RGFW_windowMaximize; - RGFW_eventQueuePushEx(e.type = RGFW_windowMaximized; e._win = win); - RGFW_windowMaximizedCallback(win, win->r); - } else if (win->_flags & RGFW_windowMaximize) { - win->_flags &= ~(u32)RGFW_windowMaximize; - RGFW_eventQueuePushEx(e.type = RGFW_windowRestored; e._win = win); - RGFW_windowRestoredCallback(win, win->r); + if ((i32)mon.mode.w == win->w && (i32)mon.mode.h - 102 <= win->h) { + win->internal.flags |= RGFW_windowMaximize; + if (!(win->internal.enabledEvents & RGFW_windowMaximizedFlag)) return; + RGFW_eventQueuePushEx(e.type = RGFW_windowMaximized; e.common.win = win); + RGFW_windowMaximizedCallback(win, 0, 0, win->w, win->h); + } else if (win->internal.flags & RGFW_windowMaximize) { + win->internal.flags &= ~(u32)RGFW_windowMaximize; + if (!(win->internal.enabledEvents & RGFW_windowRestoredFlag)) return; + RGFW_eventQueuePushEx(e.type = RGFW_windowRestored; e.common.win = win); + RGFW_windowRestoredCallback(win, win->x, win->y, win->w, win->h); } + if (!(win->internal.enabledEvents & RGFW_windowResizedFlag)) return; - RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e._win = win); - RGFW_windowResizedCallback(win, win->r); - return frameSize; + RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e.common.win = win); + RGFW_windowResizedCallback(win, win->w, win->h); } -void RGFW__osxWindowMove(id self, SEL sel) { +static void RGFW__osxWindowMove(id self, SEL sel) { RGFW_UNUSED(sel); RGFW_window* win = NULL; @@ -8900,289 +11306,601 @@ void RGFW__osxWindowMove(id self, SEL sel) { if (win == NULL) return; NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.window, sel_registerName("frame")); - win->r.x = (i32) frame.origin.x; - win->r.y = (i32) frame.origin.y; + win->x = (i32) frame.origin.x; + win->y = (i32) frame.origin.y; - RGFW_eventQueuePushEx(e.type = RGFW_windowMoved; e._win = win); - RGFW_windowMovedCallback(win, win->r); + if (!(win->internal.enabledEvents & RGFW_windowMovedFlag)) return; + RGFW_eventQueuePushEx(e.type = RGFW_windowMoved; e.common.win = win); + RGFW_windowMovedCallback(win, win->x, win->y); } -void RGFW__osxViewDidChangeBackingProperties(id self, SEL _cmd) { +static void RGFW__osxViewDidChangeBackingProperties(id self, SEL _cmd) { RGFW_UNUSED(_cmd); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL) return; + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL || !(win->internal.enabledEvents & RGFW_scaleUpdatedFlag)) return; RGFW_monitor mon = RGFW_window_getMonitor(win); RGFW_scaleUpdatedCallback(win, mon.scaleX, mon.scaleY); - RGFW_eventQueuePushEx(e.type = RGFW_scaleUpdated; e.scaleX = mon.scaleX; e.scaleY = mon.scaleY ; e._win = win); + RGFW_eventQueuePushEx(e.type = RGFW_scaleUpdated; e.scale.x = mon.scaleX; e.scale.y = mon.scaleY ; e.common.win = win); } -void RGFW__osxDrawRect(id self, SEL _cmd, CGRect rect) { +static BOOL RGFW__osxWantsUpdateLayer(id self, SEL _cmd) { RGFW_UNUSED(self); RGFW_UNUSED(_cmd); return YES; } + +static void RGFW__osxUpdateLayer(id self, SEL _cmd) { + RGFW_UNUSED(self); RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL || !(win->internal.enabledEvents & RGFW_windowRefreshFlag)) return; + RGFW_windowRefreshCallback(win); +} + +static void RGFW__osxDrawRect(id self, SEL _cmd, CGRect rect) { RGFW_UNUSED(rect); RGFW_UNUSED(_cmd); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL) return; + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL || !(win->internal.enabledEvents & RGFW_scaleUpdatedFlag)) return; - RGFW_eventQueuePushEx(e.type = RGFW_windowRefresh; e._win = win); - RGFW_windowRefreshCallback(win); + RGFW_eventQueuePushEx(e.type = RGFW_windowRefresh; e.common.win = win); + RGFW_windowRefreshCallback(win); } -void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area) { - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - win->buffer = buffer; - win->bufferSize = area; - win->_flags |= RGFW_BUFFER_ALLOC; - #ifdef RGFW_OSMESA - win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); - OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); - OSMesaPixelStore(OSMESA_Y_UP, 0); - #endif - #else - RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); /*!< if buffer rendering is not being used */ - #endif +static void RGFW__osxMouseEntered(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL || !(win->internal.enabledEvents & RGFW_mouseEnterFlag)) return; + + win->internal.mouseInside = RGFW_TRUE; + _RGFW->windowState.win = win; + _RGFW->windowState.mouseEnter = RGFW_TRUE; + + RGFW_event e; + e.type = RGFW_mouseEnter; + NSPoint p = ((NSPoint(*)(id, SEL))objc_msgSend)(event, sel_registerName("locationInWindow")); + e.mouse.x = (i32)p.x; + e.mouse.y = (i32)(win->h - p.y); + e.common.win = win; + + RGFW_eventQueuePush(&e); + RGFW_mouseNotifyCallback(win, e.mouse.x, e.mouse.y, 1); } -void RGFW_window_cocoaSetLayer(RGFW_window* win, void* layer) { +static void RGFW__osxMouseExited(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); RGFW_UNUSED(event); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL || !(win->internal.enabledEvents & RGFW_mouseLeaveFlag)) return; + + + win->internal.mouseInside = RGFW_FALSE; + _RGFW->windowState.winLeave = win; + _RGFW->windowState.mouseLeave = RGFW_TRUE; + + RGFW_event e; + e.type = RGFW_mouseLeave; + e.mouse.x = 0; + e.mouse.y = 0; + e.common.win = win; + + RGFW_eventQueuePush(&e); + RGFW_mouseNotifyCallback(win, e.mouse.x, e.mouse.y, 0); +} + +static void RGFW__osxKeyDown(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL || !(win->internal.enabledEvents & RGFW_keyPressedFlag)) return; + + RGFW_event e; + u32 key = (u16)((u32(*)(id, SEL))objc_msgSend)(event, sel_registerName("keyCode")); + u32 mappedKey = (u32)*(((char*)(const char*)NSString_to_char(((id(*)(id, SEL))objc_msgSend)(event, sel_registerName("charactersIgnoringModifiers"))))); + if ((u8)mappedKey == 239) mappedKey = 0; + + e.key.sym = (u8)mappedKey; + e.key.value = (u8)RGFW_apiKeyToRGFW(key); + _RGFW->keyboard[e.key.value].prev = _RGFW->keyboard[e.key.value].current; + e.type = RGFW_keyPressed; + e.key.repeat = RGFW_window_isKeyPressed(win, e.key.value); + _RGFW->keyboard[e.key.value].current = 1; + e.common.win = win; + + RGFW_eventQueuePush(&e); + RGFW_keyCallback(win, e.key.value, e.key.sym, win->internal.mod, e.key.repeat, 1); +} + +static void RGFW__osxKeyUp(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL || !(win->internal.enabledEvents & RGFW_keyReleasedFlag)) return; + + RGFW_event e; + u32 key = (u16)((u32(*)(id, SEL))objc_msgSend)(event, sel_registerName("keyCode")); + u32 mappedKey = (u32)*(((char*)(const char*)NSString_to_char(((id(*)(id, SEL))objc_msgSend)(event, sel_registerName("charactersIgnoringModifiers"))))); + if ((u8)mappedKey == 239) mappedKey = 0; + + e.key.sym = (u8)mappedKey; + e.key.value = (u8)RGFW_apiKeyToRGFW(key); + _RGFW->keyboard[e.key.value].prev = _RGFW->keyboard[e.key.value].current; + e.type = RGFW_keyReleased; + e.key.repeat = RGFW_window_isKeyDown(win, (u8)e.key.value); + _RGFW->keyboard[e.key.value].current = 0; + e.common.win = win; + + RGFW_eventQueuePush(&e); + RGFW_keyCallback(win, e.key.value, e.key.sym, win->internal.mod, e.key.repeat, 0); +} + +static void RGFW__osxFlagsChanged(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; + + RGFW_event e; + u32 flags = (u32)((u32(*)(id, SEL))objc_msgSend)(event, sel_registerName("modifierFlags")); + RGFW_updateKeyModsEx(win, + ((u32)(flags & NSEventModifierFlagCapsLock) % 255), + ((flags & NSEventModifierFlagNumericPad) % 255), + ((flags & NSEventModifierFlagControl) % 255), + ((flags & NSEventModifierFlagOption) % 255), + ((flags & NSEventModifierFlagShift) % 255), + ((flags & NSEventModifierFlagCommand) % 255), 0); + u8 i; + for (i = 0; i < 9; i++) + _RGFW->keyboard[i + RGFW_capsLock].prev = _RGFW->keyboard[i + RGFW_capsLock].current; + + for (i = 0; i < 5; i++) { + u32 shift = (1 << (i + 16)); + u32 key = i + RGFW_capsLock; + if ((flags & shift) && !RGFW_window_isKeyDown(win, (u8)key)) { + _RGFW->keyboard[key].current = 1; + if (key != RGFW_capsLock) + _RGFW->keyboard[key + 4].current = 1; + e.type = RGFW_keyPressed; + e.key.value = (u8)key; + break; + } + if (!(flags & shift) && RGFW_window_isKeyDown(win, (u8)key)) { + _RGFW->keyboard[key].current = 0; + if (key != RGFW_capsLock) + _RGFW->keyboard[key + 4].current = 0; + e.type = RGFW_keyReleased; + e.key.value = (u8)key; + break; + } + } + e.key.repeat = RGFW_window_isKeyDown(win, (u8)e.key.value); + e.common.win = win; + + if (!(win->internal.enabledEvents & (RGFW_BIT(e.type)))) return; + RGFW_eventQueuePush(&e); + RGFW_keyCallback(win, e.key.value, e.key.sym, win->internal.mod, e.key.repeat, e.type == RGFW_keyPressed); +} + +static void RGFW__osxMouseMoved(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL || !(win->internal.enabledEvents & RGFW_windowMovedFlag)) return; + + RGFW_event e; + e.type = RGFW_mousePosChanged; + NSPoint p = ((NSPoint(*)(id, SEL))objc_msgSend)(event, sel_registerName("locationInWindow")); + e.mouse.x = (i32)p.x; + e.mouse.y = (i32)(win->h - p.y); + p.x = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("deltaX")); + p.y = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("deltaY")); + e.mouse.vecX = (float)p.x; + e.mouse.vecY = (float)p.y; + _RGFW->vectorX = e.mouse.vecX; + _RGFW->vectorY = e.mouse.vecY; + win->internal.lastMouseX = e.mouse.x; + win->internal.lastMouseY = e.mouse.y; + e.common.win = win; + + RGFW_eventQueuePush(&e); + RGFW_mousePosCallback(win, e.mouse.x, e.mouse.y, e.mouse.vecX, e.mouse.vecY); +} + +static void RGFW__osxMouseDown(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL || !(win->internal.enabledEvents & RGFW_mouseButtonPressedFlag)) return; + + RGFW_event e; + u32 buttonNumber = (u32)((u32(*)(id, SEL))objc_msgSend)(event, sel_registerName("buttonNumber")); + switch (buttonNumber) { + case 0: e.button.value = RGFW_mouseLeft; break; + case 1: e.button.value = RGFW_mouseRight; break; + case 2: e.button.value = RGFW_mouseMiddle; break; + default: e.button.value = (u8)buttonNumber; + } + e.type = RGFW_mouseButtonPressed; + _RGFW->mouseButtons[e.button.value].prev = _RGFW->mouseButtons[e.button.value].current; + _RGFW->mouseButtons[e.button.value].current = 1; + e.common.win = win; + + RGFW_eventQueuePush(&e); + RGFW_mouseButtonCallback(win, e.button.value, 1); +} + +static void RGFW__osxMouseUp(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL|| !(win->internal.enabledEvents & RGFW_mouseButtonReleasedFlag)) return; + + RGFW_event e; + u32 buttonNumber = (u32)((u32(*)(id, SEL))objc_msgSend)(event, sel_registerName("buttonNumber")); + switch (buttonNumber) { + case 0: e.button.value = RGFW_mouseLeft; break; + case 1: e.button.value = RGFW_mouseRight; break; + case 2: e.button.value = RGFW_mouseMiddle; break; + default: e.button.value = (u8)buttonNumber; + } + e.type = RGFW_mouseButtonReleased; + _RGFW->mouseButtons[e.button.value].prev = _RGFW->mouseButtons[e.button.value].current; + _RGFW->mouseButtons[e.button.value].current = 0; + e.common.win = win; + + RGFW_eventQueuePush(&e); + RGFW_mouseButtonCallback(win, e.button.value, 0); +} + +static void RGFW__osxScrollWheel(id self, SEL _cmd, id event) { + RGFW_UNUSED(_cmd); + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL|| !(win->internal.enabledEvents & RGFW_mouseScroll)) return; + + RGFW_event e; + float deltaX = (float)((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("deltaX")); + float deltaY = (float)((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("deltaY")); + + e.type = RGFW_mouseScroll; + e.scroll.x = deltaX; + e.scroll.y = deltaY; + e.common.win = win; + _RGFW->scrollX = e.scroll.x; + _RGFW->scrollY = e.scroll.y; + + RGFW_eventQueuePush(&e); + RGFW_mouseScrollCallback(win, deltaX, deltaY); +} + +RGFW_bool RGFW_createSurfacePtr(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { + surface->data = data; + surface->w = w; + surface->h = h; + surface->format = format; + surface->native.format = RGFW_formatRGBA8; + return RGFW_TRUE; +} + +void RGFW_surface_freePtr(RGFW_surface* surface) { RGFW_UNUSED(surface); } + +void RGFW_window_blitSurface(RGFW_window* win, RGFW_surface* surface) { + RGFW_copyImageData(surface->data, surface->w, RGFW_MIN(win->h, surface->h), surface->native.format, surface->data, surface->format); + + size_t depth = (surface->format >= RGFW_formatRGBA8) ? 4 : 3; + id image = ((id (*)(Class, SEL))objc_msgSend)(objc_getClass("NSImage"), sel_getUid("alloc")); + NSSize size = (NSSize){(double)surface->w, (double)surface->h}; + image = ((id (*)(id, SEL, NSSize))objc_msgSend)((id)image, sel_getUid("initWithSize:"), size); + + int minX = RGFW_MIN(win->w, surface->w); + int minY = RGFW_MIN(win->h, surface->h); + + id rep = NSBitmapImageRep_initWithBitmapData(&surface->data, minX, minY, 8, (i32)depth, (depth == 4), false, "NSDeviceRGBColorSpace", 1 << 1, (u32)surface->w * (u32)depth, 8 * (u32)depth); + RGFW_copyImageData(NSBitmapImageRep_bitmapData(rep), minX, minY , RGFW_formatRGBA8, surface->data, surface->format); + ((void (*)(id, SEL, id))objc_msgSend)((id)image, sel_getUid("addRepresentation:"), rep); + + id contentView = ((id (*)(id, SEL))objc_msgSend)((id)win->src.window, sel_getUid("contentView")); + ((void (*)(id, SEL, BOOL))objc_msgSend)(contentView, sel_getUid("setWantsLayer:"), YES); + id layer = ((id (*)(id, SEL))objc_msgSend)(contentView, sel_getUid("layer")); + + ((void (*)(id, SEL, id))objc_msgSend)(layer, sel_getUid("setContents:"), (id)image); + ((void (*)(id, SEL, BOOL))objc_msgSend)(contentView, sel_getUid("setNeedsDisplay:"), YES); + + NSRelease(rep); + NSRelease(image); +} + +void* RGFW_window_getView_OSX(RGFW_window* win) { return win->src.view; } + +void RGFW_window_setLayer_OSX(RGFW_window* win, void* layer) { objc_msgSend_void_id((id)win->src.view, sel_registerName("setLayer"), (id)layer); } -void* RGFW_cocoaGetLayer(void) { +void* RGFW_getLayer_OSX(void) { return objc_msgSend_class((id)objc_getClass("CAMetalLayer"), (SEL)sel_registerName("layer")); } +void* RGFW_window_getWindow_OSX(RGFW_window* win) { return win->src.window; } -NSPasteboardType const NSPasteboardTypeURL = "public.url"; -NSPasteboardType const NSPasteboardTypeFileURL = "public.file-url"; - -id RGFW__osx_generateViewClass(const char* subclass, RGFW_window* win) { - Class customViewClass; - customViewClass = objc_allocateClassPair(objc_getClass(subclass), "RGFWCustomView", 0); - - class_addIvar( customViewClass, "RGFW_window", sizeof(RGFW_window*), (u8)rint(log2(sizeof(RGFW_window*))), "L"); - class_addMethod(customViewClass, sel_registerName("drawRect:"), (IMP)RGFW__osxDrawRect, "v@:{CGRect=ffff}"); - class_addMethod(customViewClass, sel_registerName("viewDidChangeBackingProperties"), (IMP)RGFW__osxViewDidChangeBackingProperties, ""); - - id customView = objc_msgSend_id(NSAlloc(customViewClass), sel_registerName("init")); - object_setInstanceVariable(customView, "RGFW_window", win); - - return customView; +void RGFW_initKeycodesPlatform(void) { + _RGFW->keycodes[0x1D] = RGFW_0; + _RGFW->keycodes[0x12] = RGFW_1; + _RGFW->keycodes[0x13] = RGFW_2; + _RGFW->keycodes[0x14] = RGFW_3; + _RGFW->keycodes[0x15] = RGFW_4; + _RGFW->keycodes[0x17] = RGFW_5; + _RGFW->keycodes[0x16] = RGFW_6; + _RGFW->keycodes[0x1A] = RGFW_7; + _RGFW->keycodes[0x1C] = RGFW_8; + _RGFW->keycodes[0x19] = RGFW_9; + _RGFW->keycodes[0x00] = RGFW_a; + _RGFW->keycodes[0x0B] = RGFW_b; + _RGFW->keycodes[0x08] = RGFW_c; + _RGFW->keycodes[0x02] = RGFW_d; + _RGFW->keycodes[0x0E] = RGFW_e; + _RGFW->keycodes[0x03] = RGFW_f; + _RGFW->keycodes[0x05] = RGFW_g; + _RGFW->keycodes[0x04] = RGFW_h; + _RGFW->keycodes[0x22] = RGFW_i; + _RGFW->keycodes[0x26] = RGFW_j; + _RGFW->keycodes[0x28] = RGFW_k; + _RGFW->keycodes[0x25] = RGFW_l; + _RGFW->keycodes[0x2E] = RGFW_m; + _RGFW->keycodes[0x2D] = RGFW_n; + _RGFW->keycodes[0x1F] = RGFW_o; + _RGFW->keycodes[0x23] = RGFW_p; + _RGFW->keycodes[0x0C] = RGFW_q; + _RGFW->keycodes[0x0F] = RGFW_r; + _RGFW->keycodes[0x01] = RGFW_s; + _RGFW->keycodes[0x11] = RGFW_t; + _RGFW->keycodes[0x20] = RGFW_u; + _RGFW->keycodes[0x09] = RGFW_v; + _RGFW->keycodes[0x0D] = RGFW_w; + _RGFW->keycodes[0x07] = RGFW_x; + _RGFW->keycodes[0x10] = RGFW_y; + _RGFW->keycodes[0x06] = RGFW_z; + _RGFW->keycodes[0x27] = RGFW_apostrophe; + _RGFW->keycodes[0x2A] = RGFW_backSlash; + _RGFW->keycodes[0x2B] = RGFW_comma; + _RGFW->keycodes[0x18] = RGFW_equals; + _RGFW->keycodes[0x32] = RGFW_backtick; + _RGFW->keycodes[0x21] = RGFW_bracket; + _RGFW->keycodes[0x1B] = RGFW_minus; + _RGFW->keycodes[0x2F] = RGFW_period; + _RGFW->keycodes[0x1E] = RGFW_closeBracket; + _RGFW->keycodes[0x29] = RGFW_semicolon; + _RGFW->keycodes[0x2C] = RGFW_slash; + _RGFW->keycodes[0x0A] = RGFW_world1; + _RGFW->keycodes[0x33] = RGFW_backSpace; + _RGFW->keycodes[0x39] = RGFW_capsLock; + _RGFW->keycodes[0x75] = RGFW_delete; + _RGFW->keycodes[0x7D] = RGFW_down; + _RGFW->keycodes[0x77] = RGFW_end; + _RGFW->keycodes[0x24] = RGFW_enter; + _RGFW->keycodes[0x35] = RGFW_escape; + _RGFW->keycodes[0x7A] = RGFW_F1; + _RGFW->keycodes[0x78] = RGFW_F2; + _RGFW->keycodes[0x63] = RGFW_F3; + _RGFW->keycodes[0x76] = RGFW_F4; + _RGFW->keycodes[0x60] = RGFW_F5; + _RGFW->keycodes[0x61] = RGFW_F6; + _RGFW->keycodes[0x62] = RGFW_F7; + _RGFW->keycodes[0x64] = RGFW_F8; + _RGFW->keycodes[0x65] = RGFW_F9; + _RGFW->keycodes[0x6D] = RGFW_F10; + _RGFW->keycodes[0x67] = RGFW_F11; + _RGFW->keycodes[0x6F] = RGFW_F12; + _RGFW->keycodes[0x69] = RGFW_printScreen; + _RGFW->keycodes[0x6B] = RGFW_F14; + _RGFW->keycodes[0x71] = RGFW_F15; + _RGFW->keycodes[0x6A] = RGFW_F16; + _RGFW->keycodes[0x40] = RGFW_F17; + _RGFW->keycodes[0x4F] = RGFW_F18; + _RGFW->keycodes[0x50] = RGFW_F19; + _RGFW->keycodes[0x5A] = RGFW_F20; + _RGFW->keycodes[0x73] = RGFW_home; + _RGFW->keycodes[0x72] = RGFW_insert; + _RGFW->keycodes[0x7B] = RGFW_left; + _RGFW->keycodes[0x3A] = RGFW_altL; + _RGFW->keycodes[0x3B] = RGFW_controlL; + _RGFW->keycodes[0x38] = RGFW_shiftL; + _RGFW->keycodes[0x37] = RGFW_superL; + _RGFW->keycodes[0x6E] = RGFW_menu; + _RGFW->keycodes[0x47] = RGFW_numLock; + _RGFW->keycodes[0x79] = RGFW_pageDown; + _RGFW->keycodes[0x74] = RGFW_pageUp; + _RGFW->keycodes[0x7C] = RGFW_right; + _RGFW->keycodes[0x3D] = RGFW_altR; + _RGFW->keycodes[0x3E] = RGFW_controlR; + _RGFW->keycodes[0x3C] = RGFW_shiftR; + _RGFW->keycodes[0x36] = RGFW_superR; + _RGFW->keycodes[0x31] = RGFW_space; + _RGFW->keycodes[0x30] = RGFW_tab; + _RGFW->keycodes[0x7E] = RGFW_up; + _RGFW->keycodes[0x52] = RGFW_kp0; + _RGFW->keycodes[0x53] = RGFW_kp1; + _RGFW->keycodes[0x54] = RGFW_kp2; + _RGFW->keycodes[0x55] = RGFW_kp3; + _RGFW->keycodes[0x56] = RGFW_kp4; + _RGFW->keycodes[0x57] = RGFW_kp5; + _RGFW->keycodes[0x58] = RGFW_kp6; + _RGFW->keycodes[0x59] = RGFW_kp7; + _RGFW->keycodes[0x5B] = RGFW_kp8; + _RGFW->keycodes[0x5C] = RGFW_kp9; + _RGFW->keycodes[0x45] = RGFW_kpSlash; + _RGFW->keycodes[0x41] = RGFW_kpPeriod; + _RGFW->keycodes[0x4B] = RGFW_kpSlash; + _RGFW->keycodes[0x4C] = RGFW_kpReturn; + _RGFW->keycodes[0x51] = RGFW_kpEqual; + _RGFW->keycodes[0x43] = RGFW_kpMultiply; + _RGFW->keycodes[0x4E] = RGFW_kpMinus; } -#ifndef RGFW_EGL -void RGFW_window_initOpenGL(RGFW_window* win) { -#ifdef RGFW_OPENGL - void* attrs = RGFW_initFormatAttribs(); - void* format = NSOpenGLPixelFormat_initWithAttributes((uint32_t*)attrs); - - if (format == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to load pixel format for OpenGL"); - win->_flags |= RGFW_windowOpenglSoftware; - void* subAttrs = RGFW_initFormatAttribs(); - format = NSOpenGLPixelFormat_initWithAttributes((uint32_t*)subAttrs); - - if (format == NULL) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "and loading software rendering OpenGL failed"); - else - RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, RGFW_DEBUG_CTX(win, 0), "Switching to software rendering"); - } - - /* the pixel format can be passed directly to opengl context creation to create a context - this is because the format also includes information about the opengl version (which may be a bad thing) */ - - win->src.view = (id) ((id(*)(id, SEL, NSRect, uint32_t*))objc_msgSend) (RGFW__osx_generateViewClass("NSOpenGLView", win), - sel_registerName("initWithFrame:pixelFormat:"), (NSRect){{0, 0}, {win->r.w, win->r.h}}, (uint32_t*)format); - - objc_msgSend_void(win->src.view, sel_registerName("prepareOpenGL")); - win->src.ctx = objc_msgSend_id(win->src.view, sel_registerName("openGLContext")); - - if (win->_flags & RGFW_windowTransparent) { - i32 opacity = 0; - #define NSOpenGLCPSurfaceOpacity 236 - NSOpenGLContext_setValues((id)win->src.ctx, &opacity, NSOpenGLCPSurfaceOpacity); - } - - objc_msgSend_void(win->src.ctx, sel_registerName("makeCurrentContext")); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context initalized"); -#else - RGFW_UNUSED(win); -#endif -} - -void RGFW_window_freeOpenGL(RGFW_window* win) { -#ifdef RGFW_OPENGL - if (win->src.ctx == NULL) return; - objc_msgSend_void(win->src.ctx, sel_registerName("release")); - win->src.ctx = NULL; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context freed"); -#else - RGFW_UNUSED(win); -#endif -} -#endif - - -i32 RGFW_init(void) { -#if defined(RGFW_C89) || defined(__cplusplus) - if (_RGFW_init) return 0; - _RGFW_init = RGFW_TRUE; - _RGFW.root = NULL; _RGFW.current = NULL; _RGFW.windowCount = -1; _RGFW.eventLen = 0; _RGFW.eventIndex = 0; -#endif - - /* NOTE(EimaMei): Why does Apple hate good code? Like wtf, who thought of methods being a great idea??? - Imagine a universe, where MacOS had a proper system API (we would probably have like 20% better performance). - */ - si_func_to_SEL_with_name("NSObject", "windowShouldClose", (void*)RGFW_OnClose); +i32 RGFW_initPlatform(void) { + class_addMethod(objc_getClass("NSObject"), sel_registerName("windowShouldClose:"), (IMP)(void*)RGFW_OnClose, 0); /* NOTE(EimaMei): Fixes the 'Boop' sfx from constantly playing each time you click a key. Only a problem when running in the terminal. */ - si_func_to_SEL("NSWindow", acceptsFirstResponder); - si_func_to_SEL("NSWindow", performKeyEquivalent); + class_addMethod(objc_getClass("NSWindowClass"), sel_registerName("acceptsFirstResponder:"), (IMP)(void*)RGFW__osxAcceptsFirstResponder, 0); + class_addMethod(objc_getClass("NSWindowClass"), sel_registerName("performKeyEquivalent:"), (IMP)(void*)RGFW__osxPerformKeyEquivalent, 0); - if (NSApp == NULL) { - NSApp = objc_msgSend_id((id)objc_getClass("NSApplication"), sel_registerName("sharedApplication")); + _RGFW->NSApp = objc_msgSend_id((id)objc_getClass("NSApplication"), sel_registerName("sharedApplication")); - ((void (*)(id, SEL, NSUInteger))objc_msgSend) - (NSApp, sel_registerName("setActivationPolicy:"), NSApplicationActivationPolicyRegular); + ((void (*)(id, SEL, NSUInteger))objc_msgSend) + ((id)_RGFW->NSApp, sel_registerName("setActivationPolicy:"), NSApplicationActivationPolicyRegular); - #ifndef RGFW_NO_IOKIT - RGFW_osxInitIOKit(); - #endif + _RGFW->customViewClasses[0] = objc_allocateClassPair(objc_getClass("NSView"), "RGFWCustomView", 0); + _RGFW->customViewClasses[1] = objc_allocateClassPair(objc_getClass("NSOpenGLView"), "RGFWOpenGLCustomView", 0); + for (size_t i = 0; i < 2; i++) { + class_addIvar((Class)_RGFW->customViewClasses[i], "RGFW_window", sizeof(RGFW_window*), sizeof(RGFW_window*), "L"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("drawRect:"), (IMP)RGFW__osxDrawRect, "v@:{CGRect=ffff}"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("viewDidChangeBackingProperties"), (IMP)RGFW__osxViewDidChangeBackingProperties, "v@:"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseDown:"), (IMP)RGFW__osxMouseDown, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("rightMouseDown:"), (IMP)RGFW__osxMouseDown, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("otherMouseDown:"), (IMP)RGFW__osxMouseDown, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseUp:"), (IMP)RGFW__osxMouseUp, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("rightMouseUp:"), (IMP)RGFW__osxMouseUp, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("otherMouseUp:"), (IMP)RGFW__osxMouseUp, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("scrollWheel:"), (IMP)RGFW__osxScrollWheel, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseDragged:"), (IMP)RGFW__osxMouseMoved, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("rightMouseDragged:"), (IMP)RGFW__osxMouseMoved, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("otherMouseDragged:"), (IMP)RGFW__osxMouseMoved, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("keyDown:"), (IMP)RGFW__osxKeyDown, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("keyUp:"), (IMP)RGFW__osxKeyUp, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseMoved:"), (IMP)RGFW__osxMouseMoved, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseEntered:"), (IMP)RGFW__osxMouseEntered, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseExited:"), (IMP)RGFW__osxMouseExited, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("flagsChanged:"), (IMP)RGFW__osxFlagsChanged, "v@:@"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_getUid("acceptsFirstResponder"), (IMP)RGFW__osxAcceptsFirstResponder, "B@:"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("initWithRGFWWindow:"), (IMP)RGFW__osxCustomInitWithRGFWWindow, "@@:{CGRect={CGPoint=dd}{CGSize=dd}}"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("wantsUpdateLayer"), (IMP)RGFW__osxWantsUpdateLayer, "B@:"); + class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("updateLayer"), (IMP)RGFW__osxUpdateLayer, "v@:"); + objc_registerClassPair((Class)_RGFW->customViewClasses[i]); } - - _RGFW.windowCount = 0; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context initialized"); - return 0; + _RGFW->customWindowDelegateClass = objc_allocateClassPair(objc_getClass("NSObject"), "RGFWWindowDelegate", 0); + class_addIvar((Class)_RGFW->customWindowDelegateClass, "RGFW_window", sizeof(RGFW_window*), sizeof(RGFW_window*), "L"); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidResize:"), (IMP)RGFW__osxDidWindowResize, "v@:@"); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidMove:"), (IMP) RGFW__osxWindowMove, ""); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidMiniaturize:"), (IMP) RGFW__osxWindowMiniaturize, ""); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidDeminiaturize:"), (IMP) RGFW__osxWindowDeminiaturize, ""); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidBecomeKey:"), (IMP) RGFW__osxWindowBecameKey, ""); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidResignKey:"), (IMP) RGFW__osxWindowResignKey, ""); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("draggingEntered:"), (IMP)RGFW__osxDraggingEntered, "l@:@"); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("draggingUpdated:"), (IMP)RGFW__osxDraggingUpdated, "l@:@"); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("draggingExited:"), (IMP)RGFW__osxDraggingEnded, "v@:@"); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("draggingEnded:"), (IMP)RGFW__osxDraggingEnded, "v@:@"); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("prepareForDragOperation:"), (IMP)RGFW__osxPrepareForDragOperation, "B@:@"); + class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("performDragOperation:"), (IMP)RGFW__osxPerformDragOperation, "B@:@"); + objc_registerClassPair((Class)_RGFW->customWindowDelegateClass); + return 0; } -RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { - static u8 RGFW_loaded = 0; - RGFW_window_basic_init(win, rect, flags); +void RGFW_osx_initView(RGFW_window* win) { + NSRect contentRect; + contentRect.origin.x = 0; + contentRect.origin.y = 0; + contentRect.size.width = (double)win->w; + contentRect.size.height = (double)win->h; + ((void(*)(id, SEL, CGRect))objc_msgSend)((id)win->src.view, sel_registerName("setFrame:"), contentRect); - /* RR Create an autorelease pool */ + + if (RGFW_COCOA_FRAME_NAME) + objc_msgSend_ptr(win->src.view, sel_registerName("setFrameAutosaveName:"), RGFW_COCOA_FRAME_NAME); + + object_setInstanceVariable((id)win->src.view, "RGFW_window", win); + objc_msgSend_void_id((id)win->src.window, sel_registerName("setContentView:"), win->src.view); + objc_msgSend_void_bool(win->src.view, sel_registerName("setWantsLayer:"), true); + objc_msgSend_int((id)win->src.view, sel_registerName("setLayerContentsPlacement:"), 4); + + id trackingArea = objc_msgSend_id(objc_getClass("NSTrackingArea"), sel_registerName("alloc")); + trackingArea = ((id (*)(id, SEL, NSRect, NSUInteger, id, id))objc_msgSend)( + trackingArea, + sel_registerName("initWithRect:options:owner:userInfo:"), + contentRect, + NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect, + (id)win->src.view, + nil + ); + + ((void (*)(id, SEL, id))objc_msgSend)((id)win->src.view, sel_registerName("addTrackingArea:"), trackingArea); + ((void (*)(id, SEL))objc_msgSend)(trackingArea, sel_registerName("release")); +} + +RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, RGFW_window* win) { + /* RR Create an autorelease pool */ id pool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); pool = objc_msgSend_id(pool, sel_registerName("init")); RGFW_window_setMouseDefault(win); NSRect windowRect; - windowRect.origin.x = win->r.x; - windowRect.origin.y = win->r.y; - windowRect.size.width = win->r.w; - windowRect.size.height = win->r.h; - - NSBackingStoreType macArgs = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSBackingStoreBuffered | NSWindowStyleMaskTitled; + windowRect.origin.x = (double)win->x; + windowRect.origin.y = (double)win->y; + windowRect.size.width = (double)win->w; + windowRect.size.height = (double)win->h; + NSBackingStoreType macArgs = (NSBackingStoreType)(NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSBackingStoreBuffered | NSWindowStyleMaskTitled); if (!(flags & RGFW_windowNoResize)) - macArgs |= NSWindowStyleMaskResizable; + macArgs = (NSBackingStoreType)(macArgs | (NSBackingStoreType)NSWindowStyleMaskResizable); if (!(flags & RGFW_windowNoBorder)) - macArgs |= NSWindowStyleMaskTitled; + macArgs = (NSBackingStoreType)(macArgs | (NSBackingStoreType)NSWindowStyleMaskTitled); { void* nsclass = objc_getClass("NSWindow"); SEL func = sel_registerName("initWithContentRect:styleMask:backing:defer:"); win->src.window = ((id(*)(id, SEL, NSRect, NSWindowStyleMask, NSBackingStoreType, bool))objc_msgSend) - (NSAlloc(nsclass), func, windowRect, macArgs, macArgs, false); + (NSAlloc(nsclass), func, windowRect, (NSWindowStyleMask)macArgs, macArgs, false); } id str = NSString_stringWithUTF8String(name); objc_msgSend_void_id((id)win->src.window, sel_registerName("setTitle:"), str); - if ((flags & RGFW_windowNoInitAPI) == 0) { - RGFW_window_initOpenGL(win); - RGFW_window_initBuffer(win); - } - - #ifdef RGFW_OPENGL - else - #endif - { - NSRect contentRect = (NSRect){{0, 0}, {win->r.w, win->r.h}}; - win->src.view = ((id(*)(id, SEL, NSRect))objc_msgSend) (NSAlloc(objc_getClass("NSView")), sel_registerName("initWithFrame:"), contentRect); - } - - void* contentView = NSWindow_contentView((id)win->src.window); - objc_msgSend_void_bool(contentView, sel_registerName("setWantsLayer:"), true); - objc_msgSend_int((id)win->src.view, sel_registerName("setLayerContentsPlacement:"), 4); - objc_msgSend_void_id((id)win->src.window, sel_registerName("setContentView:"), win->src.view); - - if (flags & RGFW_windowTransparent) { - objc_msgSend_void_bool(win->src.window, sel_registerName("setOpaque:"), false); - - objc_msgSend_void_id((id)win->src.window, sel_registerName("setBackgroundColor:"), - NSColor_colorWithSRGB(0, 0, 0, 0)); - } - - Class delegateClass = objc_allocateClassPair(objc_getClass("NSObject"), "WindowDelegate", 0); - - class_addIvar( - delegateClass, "RGFW_window", - sizeof(RGFW_window*), (u8)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("windowWillMove:"), (IMP) RGFW__osxWindowMove, ""); - class_addMethod(delegateClass, sel_registerName("windowDidMove:"), (IMP) RGFW__osxWindowMove, ""); - class_addMethod(delegateClass, sel_registerName("windowDidMiniaturize:"), (IMP) RGFW__osxWindowMiniaturize, ""); - class_addMethod(delegateClass, sel_registerName("windowDidDeminiaturize:"), (IMP) RGFW__osxWindowDeminiaturize, ""); - class_addMethod(delegateClass, sel_registerName("windowDidBecomeKey:"), (IMP) RGFW__osxWindowBecameKey, ""); - class_addMethod(delegateClass, sel_registerName("windowDidResignKey:"), (IMP) RGFW__osxWindowResignKey, ""); - class_addMethod(delegateClass, sel_registerName("draggingEntered:"), (IMP)draggingEntered, "l@:@"); - class_addMethod(delegateClass, sel_registerName("draggingUpdated:"), (IMP)draggingUpdated, "l@:@"); - class_addMethod(delegateClass, sel_registerName("draggingExited:"), (IMP)RGFW__osxDraggingEnded, "v@:@"); - class_addMethod(delegateClass, sel_registerName("draggingEnded:"), (IMP)RGFW__osxDraggingEnded, "v@:@"); - class_addMethod(delegateClass, sel_registerName("prepareForDragOperation:"), (IMP)prepareForDragOperation, "B@:@"); - class_addMethod(delegateClass, sel_registerName("performDragOperation:"), (IMP)performDragOperation, "B@:@"); - - id delegate = objc_msgSend_id(NSAlloc(delegateClass), sel_registerName("init")); - - if (RGFW_COCOA_FRAME_NAME) - objc_msgSend_ptr(win->src.view, sel_registerName("setFrameAutosaveName:"), RGFW_COCOA_FRAME_NAME); - + id delegate = objc_msgSend_id(NSAlloc((Class)_RGFW->customWindowDelegateClass), sel_registerName("init")); object_setInstanceVariable(delegate, "RGFW_window", win); objc_msgSend_void_id((id)win->src.window, sel_registerName("setDelegate:"), delegate); if (flags & RGFW_windowAllowDND) { - win->_flags |= RGFW_windowAllowDND; + win->internal.flags |= RGFW_windowAllowDND; NSPasteboardType types[] = {NSPasteboardTypeURL, NSPasteboardTypeFileURL, NSPasteboardTypeString}; NSregisterForDraggedTypes((id)win->src.window, types, 3); } - RGFW_window_setFlags(win, flags); + objc_msgSend_void_bool((id)win->src.window, sel_registerName("setAcceptsMouseMovedEvents:"), true); - /* Show the window */ - objc_msgSend_void_bool(NSApp, sel_registerName("activateIgnoringOtherApps:"), true); - ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("makeKeyAndOrderFront:"), NULL); - RGFW_window_show(win); + if (flags & RGFW_windowTransparent) { + objc_msgSend_void_bool(win->src.window, sel_registerName("setOpaque:"), false); - if (!RGFW_loaded) { + objc_msgSend_void_id((id)win->src.window, sel_registerName("setBackgroundColor:"), + NSColor_colorWithSRGB(0, 0, 0, 0)); + } + + /* Show the window */ + objc_msgSend_void_bool((id)_RGFW->NSApp, sel_registerName("activateIgnoringOtherApps:"), true); + + if (_RGFW->root == NULL) { objc_msgSend_void(win->src.window, sel_registerName("makeMainWindow")); - - RGFW_loaded = 1; } objc_msgSend_void(win->src.window, sel_registerName("makeKeyWindow")); - objc_msgSend_void(NSApp, sel_registerName("finishLaunching")); + objc_msgSend_void((id)_RGFW->NSApp, sel_registerName("finishLaunching")); NSRetain(win->src.window); - NSRetain(NSApp); + NSRetain(_RGFW->NSApp); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); + win->src.view = ((id(*)(id, SEL, RGFW_window*))objc_msgSend) (NSAlloc((Class)_RGFW->customViewClasses[0]), sel_registerName("initWithRGFWWindow:"), win); return win; } void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.window, sel_registerName("frame")); NSRect content = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.view, sel_registerName("frame")); - float offset = 0; + double offset = 0; - RGFW_setBit(&win->_flags, RGFW_windowNoBorder, !border); - NSBackingStoreType storeType = NSWindowStyleMaskBorderless | NSWindowStyleMaskFullSizeContentView; + RGFW_setBit(&win->internal.flags, RGFW_windowNoBorder, !border); + NSBackingStoreType storeType = (NSBackingStoreType)(NSWindowStyleMaskBorderless | NSWindowStyleMaskFullSizeContentView); if (border) - storeType = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable; - if (!(win->_flags & RGFW_windowNoResize)) { - storeType |= NSWindowStyleMaskResizable; + storeType = (NSBackingStoreType)(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable); + if (!(win->internal.flags & RGFW_windowNoResize)) { + storeType = (NSBackingStoreType)(storeType | (NSBackingStoreType)NSWindowStyleMaskResizable); } ((void (*)(id, SEL, NSBackingStoreType))objc_msgSend)((id)win->src.window, sel_registerName("setStyleMask:"), storeType); @@ -9192,84 +11910,25 @@ void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { id titleBarView = objc_msgSend_id(miniaturizeButton, sel_registerName("superview")); objc_msgSend_void_bool(titleBarView, sel_registerName("setHidden:"), true); - offset = (float)(frame.size.height - content.size.height); + offset = (double)(frame.size.height - content.size.height); } - RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h + offset)); - win->r.h -= (i32)offset; + RGFW_window_resize(win, win->w, win->h + (i32)offset); + win->h -= (i32)offset; } -RGFW_area RGFW_getScreenSize(void) { - static CGDirectDisplayID display = 0; - - if (display == 0) - display = CGMainDisplayID(); - - return RGFW_AREA(CGDisplayPixelsWide(display), CGDisplayPixelsHigh(display)); -} - -RGFW_point RGFW_getGlobalMousePoint(void) { - RGFW_ASSERT(_RGFW.root != NULL); +RGFW_bool RGFW_getGlobalMouse(i32* x, i32* y) { + RGFW_ASSERT(_RGFW->root != NULL); CGEventRef e = CGEventCreate(NULL); CGPoint point = CGEventGetLocation(e); CFRelease(e); - return RGFW_POINT((u32) point.x, (u32) point.y); /*!< the point is loaded during event checks */ + if (x) *x = (i32)point.x; + if (y) *y = (i32)point.y; + return RGFW_TRUE; } -typedef RGFW_ENUM(u32, NSEventType) { /* various types of events */ - NSEventTypeLeftMouseDown = 1, - NSEventTypeLeftMouseUp = 2, - NSEventTypeRightMouseDown = 3, - NSEventTypeRightMouseUp = 4, - NSEventTypeMouseMoved = 5, - NSEventTypeLeftMouseDragged = 6, - NSEventTypeRightMouseDragged = 7, - NSEventTypeMouseEntered = 8, - NSEventTypeMouseExited = 9, - NSEventTypeKeyDown = 10, - NSEventTypeKeyUp = 11, - NSEventTypeFlagsChanged = 12, - NSEventTypeAppKitDefined = 13, - NSEventTypeSystemDefined = 14, - NSEventTypeApplicationDefined = 15, - NSEventTypePeriodic = 16, - NSEventTypeCursorUpdate = 17, - NSEventTypeScrollWheel = 22, - NSEventTypeTabletPoint = 23, - NSEventTypeTabletProximity = 24, - NSEventTypeOtherMouseDown = 25, - NSEventTypeOtherMouseUp = 26, - NSEventTypeOtherMouseDragged = 27, - /* The following event types are available on some hardware on 10.5.2 and later */ - NSEventTypeGesture = 29, - NSEventTypeMagnify = 30, - NSEventTypeSwipe = 31, - NSEventTypeRotate = 18, - NSEventTypeBeginGesture = 19, - NSEventTypeEndGesture = 20, - - NSEventTypeSmartMagnify = 32, - NSEventTypeQuickLook = 33, - - NSEventTypePressure = 34, - NSEventTypeDirectTouch = 37, - - NSEventTypeChangeMode = 38, -}; - -typedef unsigned long long NSEventMask; - -typedef enum NSEventModifierFlags { - NSEventModifierFlagCapsLock = 1 << 16, - NSEventModifierFlagShift = 1 << 17, - NSEventModifierFlagControl = 1 << 18, - NSEventModifierFlagOption = 1 << 19, - NSEventModifierFlagCommand = 1 << 20, - 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")); @@ -9279,14 +11938,12 @@ void RGFW_stopCheckEvents(void) { NSEventTypeApplicationDefined, (NSPoint){0, 0}, (NSEventModifierFlags)0, NULL, (NSInteger)0, NULL, 0, 0, 0); ((void (*)(id, SEL, id, bool))objc_msgSend) - (NSApp, sel_registerName("postEvent:atStart:"), e, 1); + ((id)_RGFW->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); - +void RGFW_waitForEvent(i32 waitMS) { id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); @@ -9295,12 +11952,12 @@ void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { SEL eventFunc = sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"); id e = (id) ((id(*)(id, SEL, NSEventMask, void*, id, bool))objc_msgSend) - (NSApp, eventFunc, + ((id)_RGFW->NSApp, eventFunc, ULONG_MAX, date, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); if (e) { ((void (*)(id, SEL, id, bool))objc_msgSend) - (NSApp, sel_registerName("postEvent:atStart:"), e, 1); + ((id)_RGFW->NSApp, sel_registerName("postEvent:atStart:"), e, 1); } objc_msgSend_bool_void(eventPool, sel_registerName("drain")); @@ -9310,251 +11967,66 @@ u8 RGFW_rgfwToKeyChar(u32 rgfw_keycode) { return (u8)rgfw_keycode; /* TODO */ } -RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { - if (win == NULL || ((win->_flags & RGFW_windowFreeOnClose) && (win->_flags & RGFW_EVENT_QUIT))) return NULL; +void RGFW_pollEvents(void) { + /* + * TODO look to see if all these events can be replaced with callbacks + * callbacks seem to give better info on mac's api + */ - objc_msgSend_void((id)win->src.mouse, sel_registerName("set")); - RGFW_event* ev = RGFW_window_checkEventCore(win); - if (ev) { - ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); - return ev; - } + RGFW_resetPrevState(); id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); - SEL eventFunc = sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"); - void* date = NULL; + while (1) { + void* date = NULL; + id e = (id) ((id(*)(id, SEL, NSEventMask, void*, id, bool))objc_msgSend) + ((id)_RGFW->NSApp, eventFunc, ULONG_MAX, date, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); - id e = (id) ((id(*)(id, SEL, NSEventMask, void*, id, bool))objc_msgSend) - (NSApp, eventFunc, ULONG_MAX, date, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); + if (e == NULL) { + objc_msgSend_void_id((id)_RGFW->NSApp, sel_registerName("sendEvent:"), e); + ((void(*)(id, SEL))objc_msgSend)((id)_RGFW->NSApp, sel_registerName("updateWindows")); + break; + } - if (e == NULL) { - objc_msgSend_bool_void(eventPool, sel_registerName("drain")); - objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), e); - ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); - return NULL; + RGFW_event event; + RGFW_MEMSET(&event, 0, sizeof(event)); + objc_msgSend_void_id((id)_RGFW->NSApp, sel_registerName("sendEvent:"), e); + ((void(*)(id, SEL))objc_msgSend)((id)_RGFW->NSApp, sel_registerName("updateWindows")); } - 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_void_id(NSApp, sel_registerName("sendEvent:"), e); - objc_msgSend_bool_void(eventPool, sel_registerName("drain")); - ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); - return NULL; - } - - 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 = 0; - - u32 type = (u32)objc_msgSend_uint(e, sel_registerName("type")); - switch (type) { - case NSEventTypeMouseEntered: { - win->event.type = RGFW_mouseEnter; - NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(e, sel_registerName("locationInWindow")); - - win->event.point = RGFW_POINT((i32) p.x, (i32) (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")); - - u32 mappedKey = (u32)*(((char*)(const char*) NSString_to_char(objc_msgSend_id(e, sel_registerName("charactersIgnoringModifiers"))))); - if (((u8)mappedKey) == 239) - mappedKey = 0; - - win->event.keyChar = (u8)mappedKey; - - win->event.key = (u8)RGFW_apiKeyToRGFW(key); - RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; - - win->event.type = RGFW_keyPressed; - win->event.repeat = RGFW_isPressed(win, win->event.key); - RGFW_keyboard[win->event.key].current = 1; - - RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, 1); - break; - } - - case NSEventTypeKeyUp: { - u32 key = (u16) objc_msgSend_uint(e, sel_registerName("keyCode")); - u32 mappedKey = (u32)*(((char*)(const char*) NSString_to_char(objc_msgSend_id(e, sel_registerName("charactersIgnoringModifiers"))))); - if (((u8)mappedKey) == 239) - mappedKey = 0; - - win->event.keyChar = (u8)mappedKey; - - win->event.key = (u8)RGFW_apiKeyToRGFW(key); - - RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; - - win->event.type = RGFW_keyReleased; - - RGFW_keyboard[win->event.key].current = 0; - RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, 0); - break; - } - - case NSEventTypeFlagsChanged: { - u32 flags = (u32)objc_msgSend_uint(e, sel_registerName("modifierFlags")); - RGFW_updateKeyModsPro(win, ((u32)(flags & NSEventModifierFlagCapsLock) % 255), ((flags & NSEventModifierFlagNumericPad) % 255), - ((flags & NSEventModifierFlagControl) % 255), ((flags & NSEventModifierFlagOption) % 255), - ((flags & NSEventModifierFlagShift) % 255), ((flags & NSEventModifierFlagCommand) % 255), 0); - 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 & shift) && !RGFW_wasPressed(win, (u8)key)) { - RGFW_keyboard[key].current = 1; - - if (key != RGFW_capsLock) - RGFW_keyboard[key+ 4].current = 1; - - win->event.type = RGFW_keyPressed; - win->event.key = (u8)key; - break; - } - - if (!(flags & shift) && RGFW_wasPressed(win, (u8)key)) { - RGFW_keyboard[key].current = 0; - - if (key != RGFW_capsLock) - RGFW_keyboard[key + 4].current = 0; - - win->event.type = RGFW_keyReleased; - win->event.key = (u8)key; - break; - } - } - - RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, win->event.type == RGFW_keyPressed); - - break; - } - case NSEventTypeLeftMouseDragged: - case NSEventTypeOtherMouseDragged: - case NSEventTypeRightMouseDragged: - case NSEventTypeMouseMoved: { - win->event.type = RGFW_mousePosChanged; - NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(e, sel_registerName("locationInWindow")); - win->event.point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); - - p.x = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaX")); - p.y = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaY")); - win->event.vector = RGFW_POINT((i32)p.x, (i32)p.y); - - win->_lastMousePoint = win->event.point; - RGFW_mousePosCallback(win, win->event.point, win->event.vector); - break; - } - case NSEventTypeLeftMouseDown: case NSEventTypeRightMouseDown: case NSEventTypeOtherMouseDown: { - u32 buttonNumber = (u32)objc_msgSend_uint(e, sel_registerName("buttonNumber")); - switch (buttonNumber) { - case 0: win->event.button = RGFW_mouseLeft; break; - case 1: win->event.button = RGFW_mouseRight; break; - case 2: win->event.button = RGFW_mouseMiddle; break; - default: win->event.button = (u8)buttonNumber; - } - - win->event.type = RGFW_mouseButtonPressed; - 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: case NSEventTypeRightMouseUp: case NSEventTypeOtherMouseUp: { - u32 buttonNumber = (u32)objc_msgSend_uint(e, sel_registerName("buttonNumber")); - switch (buttonNumber) { - case 0: win->event.button = RGFW_mouseLeft; break; - case 1: win->event.button = RGFW_mouseRight; break; - case 2: win->event.button = RGFW_mouseMiddle; break; - default: win->event.button = (u8)buttonNumber; - } - 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 NSEventTypeScrollWheel: { - double deltaY = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaY")); - - if (deltaY > 0) { - win->event.button = RGFW_mouseScrollUp; - } - else if (deltaY < 0) { - win->event.button = RGFW_mouseScrollDown; - } - - RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; - RGFW_mouseButtons[win->event.button].current = 1; - - win->event.scroll = deltaY; - - win->event.type = RGFW_mouseButtonPressed; - RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); - break; - } - - default: - objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), e); - ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); - return RGFW_window_checkEvent(win); - } - - 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_point v) { +void RGFW_window_move(RGFW_window* win, i32 x, i32 y) { RGFW_ASSERT(win != NULL); - win->r.x = v.x; - win->r.y = v.y; - ((void(*)(id, SEL, NSRect, bool, bool))objc_msgSend) - ((id)win->src.window, sel_registerName("setFrame:display:animate:"), (NSRect){{win->r.x, win->r.y}, {win->r.w, win->r.h}}, true, true); + win->x = x; + win->y = y; + ((void(*)(id,SEL,NSPoint))objc_msgSend)((id)win->src.window, sel_registerName("setFrameOrigin:"), (NSPoint){(double)x, (double)y}); } -void RGFW_window_resize(RGFW_window* win, RGFW_area a) { +void RGFW_window_resize(RGFW_window* win, i32 w, i32 h) { RGFW_ASSERT(win != NULL); NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.window, sel_registerName("frame")); NSRect content = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.view, sel_registerName("frame")); float offset = (float)(frame.size.height - content.size.height); - win->r.w = (i32)a.w; - win->r.h = (i32)a.h; + win->w = w; + win->h = h; + + ((void(*)(id, SEL, CGRect))objc_msgSend)((id)win->src.view, sel_registerName("setFrame:"), (NSRect){{0, 0}, {(double)win->w, (double)win->h}}); ((void(*)(id, SEL, NSRect, bool, bool))objc_msgSend) - ((id)win->src.window, sel_registerName("setFrame:display:animate:"), (NSRect){{win->r.x, win->r.y}, {win->r.w, win->r.h + offset}}, true, true); + ((id)win->src.window, sel_registerName("setFrame:display:animate:"), (NSRect){{(double)win->x, (double)win->y}, {(double)win->w, (double)win->h + (double)offset}}, true, true); } void RGFW_window_focus(RGFW_window* win) { RGFW_ASSERT(win); - objc_msgSend_void_bool(NSApp, sel_registerName("activateIgnoringOtherApps:"), true); + objc_msgSend_void_bool((id)_RGFW->NSApp, sel_registerName("activateIgnoringOtherApps:"), true); ((void (*)(id, SEL))objc_msgSend)((id)win->src.window, sel_registerName("makeKeyWindow")); } @@ -9566,25 +12038,38 @@ void RGFW_window_raise(RGFW_window* win) { void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { RGFW_ASSERT(win != NULL); - if (fullscreen && (win->_flags & RGFW_windowFullscreen)) return; - if (!fullscreen && !(win->_flags & RGFW_windowFullscreen)) return; + if (fullscreen && (win->internal.flags & RGFW_windowFullscreen)) return; + if (!fullscreen && !(win->internal.flags & RGFW_windowFullscreen)) return; if (fullscreen) { - win->_oldRect = win->r; + if (!(win->internal.flags & RGFW_windowFullscreen)) { + return; + } + + win->internal.oldX = win->x; + win->internal.oldY = win->y; + win->internal.oldW = win->w; + win->internal.oldH = win->h; RGFW_monitor mon = RGFW_window_getMonitor(win); - win->r = RGFW_RECT(0, 0, mon.x, mon.y); - win->_flags |= RGFW_windowFullscreen; - RGFW_window_resize(win, RGFW_AREA(mon.mode.area.w, mon.mode.area.h)); - RGFW_window_move(win, RGFW_POINT(0, 0)); + win->x = mon.x; + win->y = mon.y; + win->w = mon.mode.w; + win->h = mon.mode.h; + win->internal.flags |= RGFW_windowFullscreen; + RGFW_window_resize(win, mon.mode.w, mon.mode.h); + RGFW_window_move(win, mon.x, mon.y); } objc_msgSend_void_SEL(win->src.window, sel_registerName("toggleFullScreen:"), NULL); if (!fullscreen) { - win->r = win->_oldRect; - win->_flags &= ~(u32)RGFW_windowFullscreen; + win->x = win->internal.oldX; + win->y = win->internal.oldY; + win->w = win->internal.oldW; + win->h = win->internal.oldH; + win->internal.flags &= ~(u32)RGFW_windowFullscreen; - RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h)); - RGFW_window_move(win, RGFW_POINT(win->r.x, win->r.y)); + RGFW_window_resize(win, win->w, win->h); + RGFW_window_move(win, win->x, win->y); } } @@ -9592,7 +12077,7 @@ void RGFW_window_maximize(RGFW_window* win) { RGFW_ASSERT(win != NULL); if (RGFW_window_isMaximized(win)) return; - win->_flags |= RGFW_windowMaximize; + win->internal.flags |= RGFW_windowMaximize; objc_msgSend_void_SEL(win->src.window, sel_registerName("zoom:"), NULL); } @@ -9645,81 +12130,75 @@ void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { } #endif -void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { - if (a.w == 0 && a.h == 0) a = RGFW_AREA(1, 1); +void RGFW_window_setAspectRatio(RGFW_window* win, i32 w, i32 h) { + if (w == 0 && h == 0) { w = 1; h = 1; }; ((void (*)(id, SEL, NSSize))objc_msgSend) - ((id)win->src.window, sel_registerName("setContentAspectRatio:"), (NSSize){a.w, a.h}); + ((id)win->src.window, sel_registerName("setContentAspectRatio:"), (NSSize){(CGFloat)w, (CGFloat)h}); } -void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { - ((void (*)(id, SEL, NSSize))objc_msgSend) - ((id)win->src.window, sel_registerName("setMinSize:"), (NSSize){a.w, a.h}); +void RGFW_window_setMinSize(RGFW_window* win, i32 w, i32 h) { + ((void (*)(id, SEL, NSSize))objc_msgSend) ((id)win->src.window, sel_registerName("setMinSize:"), (NSSize){(CGFloat)w, (CGFloat)h}); } -void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { - if (a.w == 0 && a.h == 0) { - a = RGFW_getScreenSize(); +void RGFW_window_setMaxSize(RGFW_window* win, i32 w, i32 h) { + if (w == 0 && h == 0) { + RGFW_monitor mon = RGFW_window_getMonitor(win); + w = mon.mode.w; + h = mon.mode.h; } ((void (*)(id, SEL, NSSize))objc_msgSend) - ((id)win->src.window, sel_registerName("setMaxSize:"), (NSSize){a.w, a.h}); + ((id)win->src.window, sel_registerName("setMaxSize:"), (NSSize){(CGFloat)w, (CGFloat)h}); } -RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, RGFW_area area, i32 channels, u8 type) { +RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_icon type) { RGFW_ASSERT(win != NULL); RGFW_UNUSED(type); if (data == NULL) { - objc_msgSend_void_id(NSApp, sel_registerName("setApplicationIconImage:"), NULL); + objc_msgSend_void_id((id)_RGFW->NSApp, sel_registerName("setApplicationIconImage:"), NULL); return RGFW_TRUE; } - /* code by EimaMei: Make a bitmap representation, then copy the loaded image into it. */ - id representation = NSBitmapImageRep_initWithBitmapData(NULL, area.w, area.h, 8, channels, (channels == 4), false, "NSCalibratedRGBColorSpace", 1 << 1, area.w * (u32)channels, 8 * (u32)channels); - RGFW_MEMCPY(NSBitmapImageRep_bitmapData(representation), data, area.w * area.h * (u32)channels); + id representation = NSBitmapImageRep_initWithBitmapData(NULL, w, h, 8, (NSInteger)4, true, false, "NSCalibratedRGBColorSpace", 1 << 1, w * 4, 32); + RGFW_copyImageData(NSBitmapImageRep_bitmapData(representation), w, h, RGFW_formatRGBA8, data, format); - /* Add ze representation. */ - id dock_image = ((id(*)(id, SEL, NSSize))objc_msgSend) (NSAlloc((id)objc_getClass("NSImage")), sel_registerName("initWithSize:"), ((NSSize){area.w, area.h})); + id dock_image = ((id(*)(id, SEL, NSSize))objc_msgSend) (NSAlloc((id)objc_getClass("NSImage")), sel_registerName("initWithSize:"), ((NSSize){(CGFloat)w, (CGFloat)h})); objc_msgSend_void_id(dock_image, sel_registerName("addRepresentation:"), representation); - /* Finally, set the dock image to it. */ - objc_msgSend_void_id(NSApp, sel_registerName("setApplicationIconImage:"), dock_image); - /* Free the garbage. */ + objc_msgSend_void_id((id)_RGFW->NSApp, sel_registerName("setApplicationIconImage:"), dock_image); + NSRelease(dock_image); NSRelease(representation); return RGFW_TRUE; } +id NSCursor_arrowStr(const char* str); id NSCursor_arrowStr(const char* str) { void* nclass = objc_getClass("NSCursor"); SEL func = sel_registerName(str); return (id) objc_msgSend_id(nclass, func); } -RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { - if (icon == NULL) { +RGFW_mouse* RGFW_loadMouse(u8* data, i32 w, i32 h, RGFW_format format) { + if (data == NULL) { objc_msgSend_void(NSCursor_arrowStr("arrowCursor"), sel_registerName("set")); return NULL; } - /* NOTE(EimaMei): Code by yours truly. */ - /* Make a bitmap representation, then copy the loaded image into it. */ - id representation = (id)NSBitmapImageRep_initWithBitmapData(NULL, a.w, a.h, 8, channels, (channels == 4), false, "NSCalibratedRGBColorSpace", 1 << 1, a.w * (u32)channels, 8 * (u32)channels); - RGFW_MEMCPY(NSBitmapImageRep_bitmapData(representation), icon, a.w * a.h * (u32)channels); + id representation = (id)NSBitmapImageRep_initWithBitmapData(NULL, w, h, 8, (NSInteger)4, true, false, "NSCalibratedRGBColorSpace", 1 << 1, w * 4, 32); + RGFW_copyImageData(NSBitmapImageRep_bitmapData(representation), w, h, RGFW_formatRGBA8, data, format); - /* Add ze representation. */ - id cursor_image = ((id(*)(id, SEL, NSSize))objc_msgSend) (NSAlloc((id)objc_getClass("NSImage")), sel_registerName("initWithSize:"), ((NSSize){a.w, a.h})); + id cursor_image = ((id(*)(id, SEL, NSSize))objc_msgSend) (NSAlloc((id)objc_getClass("NSImage")), sel_registerName("initWithSize:"), ((NSSize){(CGFloat)w, (CGFloat)h})); objc_msgSend_void_id(cursor_image, sel_registerName("addRepresentation:"), representation); - /* Finally, set the cursor image. */ id cursor = (id) ((id(*)(id, SEL, id, NSPoint))objc_msgSend) (NSAlloc(objc_getClass("NSCursor")), sel_registerName("initWithImage:hotSpot:"), cursor_image, (NSPoint){0.0, 0.0}); - /* Free the garbage. */ NSRelease(cursor_image); NSRelease(representation); @@ -9772,18 +12251,19 @@ void RGFW_releaseCursor(RGFW_window* win) { CGAssociateMouseAndMouseCursorPosition(1); } -void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { +void RGFW_captureCursor(RGFW_window* win) { RGFW_UNUSED(win); - CGWarpMouseCursorPosition((CGPoint){r.x + (r.w / 2), r.y + (r.h / 2)}); + CGWarpMouseCursorPosition((CGPoint){(CGFloat)(win->x + (win->w / 2)), (CGFloat)(win->y + (win->h / 2))}); CGAssociateMouseAndMouseCursorPosition(0); } -void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v) { +void RGFW_window_moveMouse(RGFW_window* win, i32 x, i32 y) { RGFW_UNUSED(win); - win->_lastMousePoint = RGFW_POINT(v.x - win->r.x, v.y - win->r.y); - CGWarpMouseCursorPosition((CGPoint){v.x, v.y}); + win->internal.lastMouseX = x - win->x; + win->internal.lastMouseY = y - win->y; + CGWarpMouseCursorPosition((CGPoint){(CGFloat)x, (CGFloat)y}); } @@ -9792,7 +12272,7 @@ void RGFW_window_hide(RGFW_window* win) { } void RGFW_window_show(RGFW_window* win) { - if (win->_flags & RGFW_windowFocusOnShow) + if (win->internal.flags & RGFW_windowFocusOnShow) ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("makeKeyAndOrderFront:"), NULL); ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("orderFront:"), NULL); @@ -9818,6 +12298,7 @@ RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { return b; } +id RGFW_getNSScreenForDisplayID(CGDirectDisplayID display); id RGFW_getNSScreenForDisplayID(CGDirectDisplayID display) { Class NSScreenClass = objc_getClass("NSScreen"); @@ -9839,8 +12320,7 @@ id RGFW_getNSScreenForDisplayID(CGDirectDisplayID display) { return NULL; } -u32 RGFW_osx_getFallbackRefreshRate(CGDirectDisplayID displayID); - +u32 RGFW_osx_getRefreshRate(CGDirectDisplayID display, CGDisplayModeRef mode); u32 RGFW_osx_getRefreshRate(CGDirectDisplayID display, CGDisplayModeRef mode) { if (mode) { u32 refreshRate = (u32)CGDisplayModeGetRefreshRate(mode); @@ -9856,6 +12336,7 @@ u32 RGFW_osx_getRefreshRate(CGDirectDisplayID display, CGDisplayModeRef mode) { return 60; } +RGFW_monitor RGFW_NSCreateMonitor(CGDirectDisplayID display, id screen); RGFW_monitor RGFW_NSCreateMonitor(CGDirectDisplayID display, id screen) { RGFW_monitor monitor; @@ -9865,7 +12346,8 @@ RGFW_monitor RGFW_NSCreateMonitor(CGDirectDisplayID display, id screen) { CGRect bounds = CGDisplayBounds(display); monitor.x = (i32)bounds.origin.x; monitor.y = (i32)bounds.origin.y; - monitor.mode.area = RGFW_AREA((int) bounds.size.width, (int) bounds.size.height); + monitor.mode.w = (i32) bounds.size.width; + monitor.mode.h = (i32) bounds.size.height; monitor.mode.red = 8; monitor.mode.green = 8; monitor.mode.blue = 8; @@ -9877,8 +12359,8 @@ RGFW_monitor RGFW_NSCreateMonitor(CGDirectDisplayID display, id screen) { monitor.physW = (float)screenSizeMM.width / 25.4f; monitor.physH = (float)screenSizeMM.height / 25.4f; - float ppi_width = (monitor.mode.area.w/monitor.physW); - float ppi_height = (monitor.mode.area.h/monitor.physH); + float ppi_width = (monitor.mode.w/monitor.physW); + float ppi_height = (monitor.mode.h/monitor.physH); monitor.pixelRatio = (float)((CGFloat (*)(id, SEL))abi_objc_msgSend_fpret) (screen, sel_registerName("backingScaleFactor")); float dpi = 96.0f * monitor.pixelRatio; @@ -9886,7 +12368,7 @@ RGFW_monitor RGFW_NSCreateMonitor(CGDirectDisplayID display, id screen) { monitor.scaleX = ((i32)(((float) (ppi_width) / dpi) * 10.0f)) / 10.0f; monitor.scaleY = ((i32)(((float) (ppi_height) / dpi) * 10.0f)) / 10.0f; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, "monitor found"); return monitor; } @@ -9911,10 +12393,10 @@ RGFW_monitor* RGFW_getMonitors(size_t* len) { } RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { - CGPoint point = { mon.x, mon.y }; + CGPoint point = { (CGFloat)mon.x, (CGFloat)mon.y }; CGDirectDisplayID display; - uint32_t displayCount = 0; + u32 displayCount = 0; CGError err = CGGetDisplaysWithPoint(point, 1, &display, &displayCount); if (err != kCGErrorSuccess || displayCount != 1) return RGFW_FALSE; @@ -9929,7 +12411,8 @@ RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW CGDisplayModeRef cmode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i); RGFW_monitorMode foundMode; - foundMode.area = RGFW_AREA(CGDisplayModeGetWidth(cmode), CGDisplayModeGetHeight(cmode)); + foundMode.w = (i32)CGDisplayModeGetWidth(cmode); + foundMode.h = (i32)CGDisplayModeGetHeight(cmode); foundMode.refreshRate = RGFW_osx_getRefreshRate(display, cmode); foundMode.red = 8; foundMode.green = 8; foundMode.blue = 8; @@ -9988,104 +12471,203 @@ void RGFW_writeClipboard(const char* text, u32 textLen) { SEL func = sel_registerName("setString:forType:"); ((bool (*)(id, SEL, id, id))objc_msgSend) - (NSPasteboard_generalPasteboard(), func, NSString_stringWithUTF8String(text), NSString_stringWithUTF8String(NSPasteboardTypeString)); + (NSPasteboard_generalPasteboard(), func, NSString_stringWithUTF8String(text), NSString_stringWithUTF8String((const char*)NSPasteboardTypeString)); } - #ifdef RGFW_OPENGL - void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { - if (win != NULL) - objc_msgSend_void(win->src.ctx, sel_registerName("makeCurrentContext")); - else - objc_msgSend_id(objc_getClass("NSOpenGLContext"), sel_registerName("clearCurrentContext")); - } - void* RGFW_getCurrent_OpenGL(void) { - return objc_msgSend_id(objc_getClass("NSOpenGLContext"), sel_registerName("currentContext")); - } +#ifdef RGFW_OPENGL +void NSOpenGLContext_setValues(id context, const int* vals, NSOpenGLContextParameter param); +void NSOpenGLContext_setValues(id context, const int* vals, NSOpenGLContextParameter param) { + ((void (*)(id, SEL, const int*, NSOpenGLContextParameter))objc_msgSend) + (context, sel_registerName("setValues:forParameter:"), vals, param); +} - void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { - objc_msgSend_void(win->src.ctx, sel_registerName("flushBuffer")); - } - #endif - #if !defined(RGFW_EGL) +/* MacOS OpenGL API spares us yet again (there are no extensions) */ +RGFW_bool RGFW_extensionSupportedPlatform_OpenGL(const char * extension, size_t len) { RGFW_UNUSED(extension); RGFW_UNUSED(len); return RGFW_FALSE; } - void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { - RGFW_ASSERT(win != NULL); - #if defined(RGFW_OPENGL) +RGFW_proc RGFW_getProcAddress_OpenGL(const char* procname) { + static CFBundleRef RGFWnsglFramework = NULL; + if (RGFWnsglFramework == NULL) + RGFWnsglFramework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); - NSOpenGLContext_setValues((id)win->src.ctx, &swapInterval, 222); - #else - RGFW_UNUSED(swapInterval); + CFStringRef symbolName = CFStringCreateWithCString(kCFAllocatorDefault, procname, kCFStringEncodingASCII); + + RGFW_proc symbol = (RGFW_proc)CFBundleGetFunctionPointerForName(RGFWnsglFramework, symbolName); + + CFRelease(symbolName); + + return symbol; +} + +RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { + win->src.ctx.native = ctx; + win->src.gfxType = RGFW_gfxNativeOpenGL; + + i32 attribs[40]; + size_t render_type_index = 0; + { + RGFW_attribStack stack; + RGFW_attribStack_init(&stack, attribs, 40); + + i32 colorBits = (i32)(hints->red + hints->green + hints->blue + hints->alpha) / 4; + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAColorSize, colorBits); + + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAAlphaSize, hints->alpha); + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFADepthSize, hints->depth); + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAStencilSize, hints->stencil); + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAAuxBuffers, hints->auxBuffers); + RGFW_attribStack_pushAttrib(&stack, NSOpenGLPFAClosestPolicy); + if (hints->samples) { + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFASampleBuffers, 1); + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFASamples, hints->samples); + } else RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFASampleBuffers, 0); + + if (hints->doubleBuffer) + RGFW_attribStack_pushAttrib(&stack, NSOpenGLPFADoubleBuffer); + + #ifdef RGFW_COCOA_GRAPHICS_SWITCHING + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAAllowOfflineRenderers, kCGLPFASupportsAutomaticGraphicsSwitching) #endif + #if MAC_OS_X_VERSION_MAX_ALLOWED < 101200 + if (hints->stereo]) RGFW_attribStack_pushAttrib(&stack, NSOpenGLPFAStereo); + #endif + + /* macOS has the surface attribs and the OpenGL attribs connected for some reason maybe this is to give macOS more control to limit openGL/the OpenGL version? */ + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAOpenGLProfile, + (hints->major >= 4) ? NSOpenGLProfileVersion4_1Core : (hints->major >= 3) ? + NSOpenGLProfileVersion3_2Core : NSOpenGLProfileVersionLegacy); + + if (hints->major <= 2) { + i32 accumSize = (i32)(hints->accumRed + hints->accumGreen + hints->accumBlue + hints->accumAlpha) / 4; + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAAccumSize, accumSize); + } + + if (hints->renderer == RGFW_glSoftware) { + RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFARendererID, kCGLRendererGenericFloatID); + } else { + RGFW_attribStack_pushAttrib(&stack, NSOpenGLPFAAccelerated); + } + render_type_index = stack.count - 1; + + RGFW_attribStack_pushAttribs(&stack, 0, 0); } - #endif + void* format = (void*) ((id(*)(id, SEL, const u32*))objc_msgSend) (NSAlloc((id)objc_getClass("NSOpenGLPixelFormat")), sel_registerName("initWithAttributes:"), (u32*)attribs); + if (format == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to load pixel format for OpenGL"); -void RGFW_window_swapBuffers_software(RGFW_window* win) { -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - RGFW_RGB_to_BGR(win, win->buffer); - i32 channels = 4; - id image = ((id (*)(Class, SEL))objc_msgSend)(objc_getClass("NSImage"), sel_getUid("alloc")); - NSSize size = (NSSize){win->bufferSize.w, win->bufferSize.h}; - image = ((id (*)(id, SEL, NSSize))objc_msgSend)((id)image, sel_getUid("initWithSize:"), size); + assert(render_type_index + 3 < (sizeof(attribs) / sizeof(attribs[0]))); + attribs[render_type_index] = NSOpenGLPFARendererID; + attribs[render_type_index + 1] = kCGLRendererGenericFloatID; + attribs[render_type_index + 3] = 0; - id rep = NSBitmapImageRep_initWithBitmapData(&win->buffer, win->r.w, win->r.h , 8, channels, (channels == 4), false, - "NSDeviceRGBColorSpace", 1 << 1, (u32)win->bufferSize.w * (u32)channels, 8 * (u32)channels); - ((void (*)(id, SEL, id))objc_msgSend)((id)image, sel_getUid("addRepresentation:"), rep); + format = (void*) ((id(*)(id, SEL, const u32*))objc_msgSend) (NSAlloc((id)objc_getClass("NSOpenGLPixelFormat")), sel_registerName("initWithAttributes:"), (u32*)attribs); + if (format == NULL) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "and loading software rendering OpenGL failed"); + else + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, "Switching to software rendering"); + } - id contentView = ((id (*)(id, SEL))objc_msgSend)((id)win->src.window, sel_getUid("contentView")); - ((void (*)(id, SEL, BOOL))objc_msgSend)(contentView, sel_getUid("setWantsLayer:"), YES); - id layer = ((id (*)(id, SEL))objc_msgSend)(contentView, sel_getUid("layer")); + /* the pixel format can be passed directly to OpenGL context creation to create a context + this is because the format also includes information about the OpenGL version (which may be a bad thing) */ - ((void (*)(id, SEL, id))objc_msgSend)(layer, sel_getUid("setContents:"), (id)image); - ((void (*)(id, SEL, BOOL))objc_msgSend)(contentView, sel_getUid("setNeedsDisplay:"), YES); + if (win->src.view) + NSRelease(win->src.view); + win->src.view = (id) ((id(*)(id, SEL, NSRect, u32*))objc_msgSend) (NSAlloc(_RGFW->customViewClasses[1]), + sel_registerName("initWithFrame:pixelFormat:"), (NSRect){{0, 0}, {(double)win->w, (double)win->h}}, (u32*)format); - NSRelease(rep); - NSRelease(image); -#else - RGFW_UNUSED(win); + id share = NULL; + if (hints->share) { + share = (id)hints->share->ctx; + } + + win->src.ctx.native->ctx = ((id (*)(id, SEL, id, id))objc_msgSend)(NSAlloc(objc_getClass("NSOpenGLContext")), + sel_registerName("initWithFormat:shareContext:"), + (id)format, share); + + objc_msgSend_void_id(win->src.view, sel_registerName("setOpenGLContext:"), win->src.ctx.native->ctx); + if (win->internal.flags & RGFW_windowTransparent) { + i32 opacity = 0; + #define NSOpenGLCPSurfaceOpacity 236 + NSOpenGLContext_setValues((id)win->src.ctx.native->ctx, &opacity, (NSOpenGLContextParameter)NSOpenGLCPSurfaceOpacity); + + } + + objc_msgSend_void(win->src.ctx.native->ctx, sel_registerName("makeCurrentContext")); + + objc_msgSend_void_id((id)win->src.window, sel_registerName("setContentView:"), win->src.view); + objc_msgSend_void_bool(win->src.view, sel_registerName("setWantsLayer:"), true); + objc_msgSend_int((id)win->src.view, sel_registerName("setLayerContentsPlacement:"), 4); + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context initalized."); + return RGFW_TRUE; +} + +void RGFW_window_deleteContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx) { + objc_msgSend_void(ctx->ctx, sel_registerName("release")); + win->src.ctx.native->ctx = NULL; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context freed."); +} + +void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win) { + if (win) RGFW_ASSERT(win->src.ctx.native); + if (win != NULL) + objc_msgSend_void(win->src.ctx.native->ctx, sel_registerName("makeCurrentContext")); + else + objc_msgSend_id(objc_getClass("NSOpenGLContext"), sel_registerName("clearCurrentContext")); +} +void* RGFW_getCurrentContext_OpenGL(void) { + return objc_msgSend_id(objc_getClass("NSOpenGLContext"), sel_registerName("currentContext")); +} + +void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { + RGFW_ASSERT(win && win->src.ctx.native); + objc_msgSend_void(win->src.ctx.native->ctx, sel_registerName("flushBuffer")); +} +void RGFW_window_swapInterval_OpenGL(RGFW_window* win, i32 swapInterval) { + RGFW_ASSERT(win != NULL && win->src.ctx.native != NULL); + NSOpenGLContext_setValues((id)win->src.ctx.native->ctx, &swapInterval, (NSOpenGLContextParameter)222); +} #endif -} -void RGFW_deinit(void) { - _RGFW.windowCount = -1; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context deinitialized"); -} +void RGFW_deinitPlatform(void) { } -void RGFW_window_close(RGFW_window* win) { - RGFW_ASSERT(win != NULL); +void RGFW_window_closePlatform(RGFW_window* win) { NSRelease(win->src.view); - if ((win->_flags & RGFW_windowNoInitAPI) == 0) RGFW_window_freeOpenGL(win); +} - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - if ((win->_flags & RGFW_BUFFER_ALLOC)) - RGFW_FREE(win->buffer); - #endif - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context deinitialized"); - _RGFW.windowCount--; - if (_RGFW.windowCount == 0) RGFW_deinit(); - - RGFW_clipboard_switch(NULL); - RGFW_FREE(win->event.droppedFiles); - if ((win->_flags & RGFW_WINDOW_ALLOC)) { - RGFW_FREE(win); - win = NULL; +#ifdef RGFW_WEBGPU +WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance instance) { + WGPUSurfaceDescriptor surfaceDesc = {0}; + id* nsView = (id*)window->src.view; + if (!nsView) { + fprintf(stderr, "RGFW Error: NSView is NULL for macOS window.\n"); + return NULL; } -} -u64 RGFW_getTimerFreq(void) { - static u64 freq = 0; - if (freq == 0) { - mach_timebase_info_data_t info; - mach_timebase_info(&info); - freq = (u64)((info.denom * 1e9) / info.numer); + ((void (*)(id, SEL, BOOL))objc_msgSend)(nsView, sel_registerName("setWantsLayer:"), YES); + id layer = ((id (*)(id, SEL))objc_msgSend)(nsView, sel_registerName("layer")); + + void* metalLayer = RGFW_getLayer_OSX(); + if (metalLayer == NULL) { + return NULL; } + ((void (*)(id, SEL, id))objc_msgSend)((id)nsView, sel_registerName("setLayer:"), metalLayer); + layer = metalLayer; /* Use the newly created layer */ - return freq; + /* At this point, 'layer' should be a valid CAMetalLayer* */ + WGPUSurfaceSourceMetalLayer fromMetal = {0}; + fromMetal.chain.sType = WGPUSType_SurfaceSourceMetalLayer; +#ifdef __OBJC__ + fromMetal.layer = (__bridge CAMetalLayer*)layer; /* Use __bridge for ARC compatibility if mixing C/Obj-C */ +#else + fromMetal.layer = layer; +#endif + + surfaceDesc.nextInChain = (WGPUChainedStruct*)&fromMetal.chain; + return wgpuInstanceCreateSurface(instance, &surfaceDesc); } - -u64 RGFW_getTimerValue(void) { return (u64)mach_absolute_time(); } +#endif #endif /* RGFW_MACOS */ @@ -10101,33 +12683,40 @@ u64 RGFW_getTimerValue(void) { return (u64)mach_absolute_time(); } EM_BOOL Emscripten_on_resize(int eventType, const EmscriptenUiEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e._win = _RGFW.root); - RGFW_windowResizedCallback(_RGFW.root, RGFW_RECT(0, 0, E->windowInnerWidth, E->windowInnerHeight)); + if (!(_RGFW->root->internal.enabledEvents & RGFW_windowResizedFlag)) return EM_TRUE; + + RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e.common.win = _RGFW->root); + RGFW_windowResizedCallback(_RGFW->root, E->windowInnerWidth, E->windowInnerHeight); return EM_TRUE; } EM_BOOL Emscripten_on_fullscreenchange(int eventType, const EmscriptenFullscreenChangeEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + if (!(_RGFW->root->internal.enabledEvents & RGFW_windowResizedFlag)) return EM_TRUE; + static u8 fullscreen = RGFW_FALSE; - static RGFW_rect ogRect; + static i32 originalW, originalH; if (fullscreen == RGFW_FALSE) { - ogRect = _RGFW.root->r; + originalW = _RGFW->root->w; + originalH = _RGFW->root->h; } fullscreen = !fullscreen; - RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e._win = _RGFW.root); - _RGFW.root->r = RGFW_RECT(0, 0, E->screenWidth, E->screenHeight); + RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e.common.win = _RGFW->root); + _RGFW->root->w = E->screenWidth; + _RGFW->root->h = E->screenHeight; EM_ASM("Module.canvas.focus();"); if (fullscreen == RGFW_FALSE) { - _RGFW.root->r = RGFW_RECT(0, 0, ogRect.w, ogRect.h); - /* emscripten_request_fullscreen("#canvas", 0); */ + _RGFW->root->w = originalW; + _RGFW->root->h = originalH; } else { #if __EMSCRIPTEN_major__ >= 1 && __EMSCRIPTEN_minor__ >= 29 && __EMSCRIPTEN_tiny__ >= 0 EmscriptenFullscreenStrategy FSStrat = {0}; - FSStrat.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; /* EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT : EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; */ + FSStrat.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; FSStrat.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF; FSStrat.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; emscripten_request_fullscreen_strategy("#canvas", 1, &FSStrat); @@ -10136,97 +12725,111 @@ EM_BOOL Emscripten_on_fullscreenchange(int eventType, const EmscriptenFullscreen #endif } - emscripten_set_canvas_element_size("#canvas", _RGFW.root->r.w, _RGFW.root->r.h); - - RGFW_windowResizedCallback(_RGFW.root, _RGFW.root->r); + emscripten_set_canvas_element_size("#canvas", _RGFW->root->w, _RGFW->root->h); + RGFW_windowResizedCallback(_RGFW->root, _RGFW->root->w, _RGFW->root->h); return EM_TRUE; } - - EM_BOOL Emscripten_on_focusin(int eventType, const EmscriptenFocusEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(E); - RGFW_eventQueuePushEx(e.type = RGFW_focusIn; e._win = _RGFW.root); - _RGFW.root->_flags |= RGFW_windowFocus; - RGFW_focusCallback(_RGFW.root, 1); + if (!(_RGFW->root->internal.enabledEvents & RGFW_focusInFlag)) return EM_TRUE; - if ((_RGFW.root->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(_RGFW.root, RGFW_AREA(_RGFW.root->r.w, _RGFW.root->r.h)); + RGFW_eventQueuePushEx(e.type = RGFW_focusIn; e.common.win = _RGFW->root); + _RGFW->root->internal.inFocus = RGFW_TRUE; + RGFW_focusCallback(_RGFW->root, 1); + + if ((_RGFW->root->internal.holdMouse)) RGFW_window_holdMouse(_RGFW->root); return EM_TRUE; } EM_BOOL Emscripten_on_focusout(int eventType, const EmscriptenFocusEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(E); - RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e._win = _RGFW.root); - RGFW_window_focusLost(_RGFW.root); - RGFW_focusCallback(_RGFW.root, 0); + if (!(_RGFW->root->internal.enabledEvents & RGFW_focusOutFlag)) return EM_TRUE; + + RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e.common.win = _RGFW->root); + RGFW_window_focusLost(_RGFW->root); + RGFW_focusCallback(_RGFW->root, 0); return EM_TRUE; } EM_BOOL Emscripten_on_mousemove(int eventType, const EmscriptenMouseEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - RGFW_eventQueuePushEx(e.type = RGFW_mousePosChanged; - e.point = RGFW_POINT(E->targetX, E->targetY); - e.vector = RGFW_POINT(E->movementX, E->movementY); - e._win = _RGFW.root); - _RGFW.root->_lastMousePoint = RGFW_POINT(E->targetX, E->targetY); - RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(E->targetX, E->targetY), RGFW_POINT(E->movementX, E->movementY)); + if (!(_RGFW->root->internal.enabledEvents & RGFW_mousePosChangedFlag)) return EM_TRUE; + + RGFW_eventQueuePushEx(e.type = RGFW_mousePosChanged; + e.mouse.x = E->targetX; e.mouse.y = E->targetY; + e.mouse.vecX = E->movementX; e.mouse.vecY = E->movementY; + e.common.win = _RGFW->root); + + _RGFW->vectorX = E->movementX; + _RGFW->vectorY = E->movementY; + _RGFW->root->internal.lastMouseX = E->targetX; + _RGFW->root->internal.lastMouseY = E->targetY; + RGFW_mousePosCallback(_RGFW->root, E->targetX, E->targetY, E->movementX, E->movementY); return EM_TRUE; } EM_BOOL Emscripten_on_mousedown(int eventType, const EmscriptenMouseEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + if (!(_RGFW->root->internal.enabledEvents & RGFW_mouseButtonPressedFlag)) return EM_TRUE; + int button = E->button; if (button > 2) button += 2; RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonPressed; - e.point = RGFW_POINT(E->targetX, E->targetY); - e.vector = RGFW_POINT(E->movementX, E->movementY); - e.button = (u8)button; - e.scroll = 0; - e._win = _RGFW.root); - RGFW_mouseButtons[button].prev = RGFW_mouseButtons[button].current; - RGFW_mouseButtons[button].current = 1; + e.mouse.x = E->targetX; e.mouse.y = E->targetY; + e.mouse.vecX = E->movementX; e.mouse.vecY = E->movementY; + e.button.value = (u8)button; + e.common.win = _RGFW->root); + _RGFW->vectorX = E->movementX; + _RGFW->vectorY = E->movementY; + _RGFW->mouseButtons[button].prev = _RGFW->mouseButtons[button].current; + _RGFW->mouseButtons[button].current = 1; - RGFW_mouseButtonCallback(_RGFW.root, button, 0, 1); + RGFW_mouseButtonCallback(_RGFW->root, button, 1); return EM_TRUE; } EM_BOOL Emscripten_on_mouseup(int eventType, const EmscriptenMouseEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + if (!(_RGFW->root->internal.enabledEvents & RGFW_mouseButtonReleasedFlag)) return EM_TRUE; + int button = E->button; if (button > 2) button += 2; RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonReleased; - e.point = RGFW_POINT(E->targetX, E->targetY); - e.vector = RGFW_POINT(E->movementX, E->movementY); - e.button = (u8)button; - e.scroll = 0; - e._win = _RGFW.root); - RGFW_mouseButtons[button].prev = RGFW_mouseButtons[button].current; - RGFW_mouseButtons[button].current = 0; + e.mouse.x = E->targetX; e.mouse.y = E->targetY; + e.mouse.vecX = E->movementX; e.mouse.vecY = E->movementY; + e.button.value = (u8)button; + e.common.win = _RGFW->root); + _RGFW->vectorX = E->movementX; + _RGFW->vectorY = E->movementY; + _RGFW->mouseButtons[button].prev = _RGFW->mouseButtons[button].current; + _RGFW->mouseButtons[button].current = 0; - RGFW_mouseButtonCallback(_RGFW.root, button, 0, 0); + RGFW_mouseButtonCallback(_RGFW->root, button, 0); return EM_TRUE; } EM_BOOL Emscripten_on_wheel(int eventType, const EmscriptenWheelEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - int button = RGFW_mouseScrollUp + (E->deltaY < 0); + if (!(_RGFW->root->internal.enabledEvents & RGFW_mouseScrollFlag)) return EM_TRUE; + + _RGFW->scrollX = E->deltaX; + _RGFW->scrollY = E->deltaY; RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonPressed; - e.button = (u8)button; - e.scroll = (double)(E->deltaY < 0 ? 1 : -1); - e._win = _RGFW.root); - RGFW_mouseButtons[button].prev = RGFW_mouseButtons[button].current; - RGFW_mouseButtons[button].current = 1; - RGFW_mouseButtonCallback(_RGFW.root, button, E->deltaY < 0 ? 1 : -1, 1); + e.scroll.x = E->deltaX; + e.scroll.y = E->deltaY; + ); + RGFW_mouseScrollCallback(_RGFW->root, E->deltaX, E->deltaY); return EM_TRUE; } @@ -10234,35 +12837,44 @@ EM_BOOL Emscripten_on_wheel(int eventType, const EmscriptenWheelEvent* E, void* EM_BOOL Emscripten_on_touchstart(int eventType, const EmscriptenTouchEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + if (!(_RGFW->root->internal.enabledEvents & RGFW_mouseButtonPressedFlag)) return EM_TRUE; + size_t i; for (i = 0; i < (size_t)E->numTouches; i++) { RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonPressed; - e.point = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); - e.button = RGFW_mouseLeft; - e._win = _RGFW.root); + e.mouse.x = E->touches[i].targetX; e.mouse.y = E->touches[i].targetY; + e.button.value = RGFW_mouseLeft; + e.common.win = _RGFW->root); - RGFW_mouseButtons[RGFW_mouseLeft].prev = RGFW_mouseButtons[RGFW_mouseLeft].current; - RGFW_mouseButtons[RGFW_mouseLeft].current = 1; + _RGFW->mouseButtons[RGFW_mouseLeft].prev = _RGFW->mouseButtons[RGFW_mouseLeft].current; + _RGFW->mouseButtons[RGFW_mouseLeft].current = 1; - _RGFW.root->_lastMousePoint = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); - RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY), _RGFW.root->event.vector); - RGFW_mouseButtonCallback(_RGFW.root, RGFW_mouseLeft, 0, 1); + _RGFW->root->internal.lastMouseX = E->touches[i].targetX; + _RGFW->root->internal.lastMouseX = E->touches[i].targetY; + RGFW_mousePosCallback(_RGFW->root, E->touches[i].targetX, E->touches[i].targetY, 0, 0); + RGFW_mouseButtonCallback(_RGFW->root, RGFW_mouseLeft, 1); } return EM_TRUE; } + EM_BOOL Emscripten_on_touchmove(int eventType, const EmscriptenTouchEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + if (!(_RGFW->root->internal.enabledEvents & RGFW_mousePosChangedFlag)) return EM_TRUE; + size_t i; for (i = 0; i < (size_t)E->numTouches; i++) { RGFW_eventQueuePushEx(e.type = RGFW_mousePosChanged; - e.point = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); - e.button = RGFW_mouseLeft; - e._win = _RGFW.root); + e.mouse.x = E->touches[i].targetX; + e.mouse.y = E->touches[i].targetY; + e.mouse.x = E->touches[i].targetX; e.mouse.y = E->touches[i].targetY; + e.button.value = RGFW_mouseLeft; + e.common.win = _RGFW->root); - _RGFW.root->_lastMousePoint = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); - RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY), _RGFW.root->event.vector); + _RGFW->root->internal.lastMouseX = E->touches[i].targetX; + _RGFW->root->internal.lastMouseX = E->touches[i].targetY; + RGFW_mousePosCallback(_RGFW->root, E->touches[i].targetX, E->touches[i].targetY, 0, 0); } return EM_TRUE; } @@ -10270,60 +12882,563 @@ EM_BOOL Emscripten_on_touchmove(int eventType, const EmscriptenTouchEvent* E, vo EM_BOOL Emscripten_on_touchend(int eventType, const EmscriptenTouchEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + if (!(_RGFW->root->internal.enabledEvents & RGFW_mouseButtonReleasedFlag)) return EM_TRUE; + size_t i; for (i = 0; i < (size_t)E->numTouches; i++) { RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonReleased; - e.point = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); - e.button = RGFW_mouseLeft; - e._win = _RGFW.root); + e.mouse.x = E->touches[i].targetX; e.mouse.y = E->touches[i].targetY; + e.button.value = RGFW_mouseLeft; + e.common.win = _RGFW->root); - RGFW_mouseButtons[RGFW_mouseLeft].prev = RGFW_mouseButtons[RGFW_mouseLeft].current; - RGFW_mouseButtons[RGFW_mouseLeft].current = 0; + _RGFW->mouseButtons[RGFW_mouseLeft].prev = _RGFW->mouseButtons[RGFW_mouseLeft].current; + _RGFW->mouseButtons[RGFW_mouseLeft].current = 0; - _RGFW.root->_lastMousePoint = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); - RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY), _RGFW.root->event.vector); - RGFW_mouseButtonCallback(_RGFW.root, RGFW_mouseLeft, 0, 0); + _RGFW->root->internal.lastMouseX = E->touches[i].targetX; + _RGFW->root->internal.lastMouseY = E->touches[i].targetY; + RGFW_mousePosCallback(_RGFW->root, E->touches[i].targetX, E->touches[i].targetY, 0, 0); + RGFW_mouseButtonCallback(_RGFW->root, RGFW_mouseLeft, 0); } return EM_TRUE; } EM_BOOL Emscripten_on_touchcancel(int eventType, const EmscriptenTouchEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); return EM_TRUE; } -EM_BOOL Emscripten_on_gamepad(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) { - RGFW_UNUSED(eventType); RGFW_UNUSED(userData); +u32 RGFW_WASMPhysicalToRGFW(u32 hash); - if (gamepadEvent->index >= 4) - return 0; +void EMSCRIPTEN_KEEPALIVE RGFW_handleKeyEvent(char* key, char* code, RGFW_bool press) { + const char* iCode = code; - size_t i = gamepadEvent->index; - if (gamepadEvent->connected) { - RGFW_STRNCPY(RGFW_gamepads_name[gamepadEvent->index], gamepadEvent->id, sizeof(RGFW_gamepads_name[gamepadEvent->index]) - 1); - RGFW_gamepads_name[gamepadEvent->index][sizeof(RGFW_gamepads_name[gamepadEvent->index]) - 1] = '\0'; - RGFW_gamepads_type[i] = RGFW_gamepadUnknown; - if (RGFW_STRSTR(RGFW_gamepads_name[i], "Microsoft") || RGFW_STRSTR(RGFW_gamepads_name[i], "X-Box")) - RGFW_gamepads_type[i] = RGFW_gamepadMicrosoft; - else if (RGFW_STRSTR(RGFW_gamepads_name[i], "PlayStation") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS3") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS4") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS5")) - RGFW_gamepads_type[i] = RGFW_gamepadSony; - else if (RGFW_STRSTR(RGFW_gamepads_name[i], "Nintendo")) - RGFW_gamepads_type[i] = RGFW_gamepadNintendo; - else if (RGFW_STRSTR(RGFW_gamepads_name[i], "Logitech")) - RGFW_gamepads_type[i] = RGFW_gamepadLogitech; - RGFW_gamepadCount++; - } else { - RGFW_gamepadCount--; + u32 hash = 0; + while(*iCode) hash = ((hash ^ 0x7E057D79U) << 3) ^ (unsigned int)*iCode++; + + u32 physicalKey = RGFW_WASMPhysicalToRGFW(hash); + + u8 mappedKey = (u8)(*((u32*)key)); + + if (*((u16*)key) != mappedKey) { + mappedKey = 0; + if (*((u32*)key) == *((u32*)"Tab")) mappedKey = RGFW_tab; } - RGFW_eventQueuePushEx(e.type = (RGFW_eventType)(gamepadEvent->connected ? RGFW_gamepadConnected : RGFW_gamepadConnected); - e.gamepad = (u16)gamepadEvent->index; - e._win = _RGFW.root); + if (!(press ? (_RGFW->root->internal.enabledEvents & RGFW_keyPressedFlag) : (_RGFW->root->internal.enabledEvents & RGFW_keyReleasedFlag))) return; - RGFW_gamepadCallback(_RGFW.root, gamepadEvent->index, gamepadEvent->connected); - RGFW_gamepads[gamepadEvent->index] = gamepadEvent->connected; + RGFW_eventQueuePushEx(e.type = (RGFW_eventType)(press ? RGFW_keyPressed : RGFW_keyReleased); + e.key.value = (u8)physicalKey; + e.key.sym = (u8)mappedKey; + e.key.mod = _RGFW->root->internal.mod; + e.key.repeat = RGFW_window_isKeyDown(_RGFW->root, (u8)physicalKey); + e.common.win = _RGFW->root); - return 1; /* The event was consumed by the callback handler */ + _RGFW->keyboard[physicalKey].prev = _RGFW->keyboard[physicalKey].current; + _RGFW->keyboard[physicalKey].current = press; + + RGFW_keyCallback(_RGFW->root, physicalKey, mappedKey, _RGFW->root->internal.mod, RGFW_window_isKeyDown(_RGFW->root, (u8)physicalKey), press); } -u32 RGFW_wASMPhysicalToRGFW(u32 hash) { +void EMSCRIPTEN_KEEPALIVE RGFW_handleKeyMods(RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll) { + RGFW_updateKeyModsEx(_RGFW->root, capital, numlock, control, alt, shift, super, scroll); +} + +void EMSCRIPTEN_KEEPALIVE Emscripten_onDrop(size_t count) { + if (!(_RGFW->root->internal.flags & RGFW_windowAllowDND)) + return; + + if (!(_RGFW->root->internal.enabledEvents & RGFW_dataDropFlag)) return; + + RGFW_eventQueuePushEx(e.type = RGFW_dataDrop; + e.drop.count = count; + e.common.win = _RGFW->root); + + _RGFW->windowState.win = _RGFW->root; + _RGFW->windowState.dataDrop = RGFW_TRUE; + _RGFW->windowState.filesCount = count; + RGFW_dataDropCallback(_RGFW->root, _RGFW->files, count); +} + +void RGFW_stopCheckEvents(void) { + _RGFW->stopCheckEvents_bool = RGFW_TRUE; +} + +RGFW_bool RGFW_createSurfacePtr(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { + surface->data = data; + surface->w = w; + surface->h = h; + surface->format = format; + return RGFW_TRUE; +} + +void RGFW_window_blitSurface(RGFW_window* win, RGFW_surface* surface) { + /* TODO: Needs fixing. */ + RGFW_copyImageData(surface->data, surface->w, RGFW_MIN(win->h, surface->h), RGFW_formatRGBA8, surface->data, surface->format); + EM_ASM_({ + var data = Module.HEAPU8.slice($0, $0 + $1 * $2 * 4); + let context = document.getElementById("canvas").getContext("2d"); + let image = context.getImageData(0, 0, $1, $2); + image.data.set(data); + context.putImageData(image, 0, $4 - $2); + }, surface->data, surface->w, surface->h, RGFW_MIN(win->h, surface->w), RGFW_MIN(win->h, surface->h)); +} + +void RGFW_surface_freePtr(RGFW_surface* surface) { } + +void EMSCRIPTEN_KEEPALIVE RGFW_makeSetValue(size_t index, char* file) { + /* This seems like a terrible idea, don't replicate this unless you hate yourself or the OS */ + /* TODO: find a better way to do this + */ + RGFW_STRNCPY((char*)_RGFW->files[index], file, RGFW_MAX_PATH - 1); + _RGFW->files[index][RGFW_MAX_PATH - 1] = '\0'; +} + +#include +#include +#include +#include + +void EMSCRIPTEN_KEEPALIVE RGFW_mkdir(char* name) { mkdir(name, 0755); } + +void EMSCRIPTEN_KEEPALIVE RGFW_writeFile(const char *path, const char *data, size_t len) { + FILE* file = fopen(path, "w+"); + if (file == NULL) + return; + + fwrite(data, sizeof(char), len, file); + fclose(file); +} + +void RGFW_initKeycodesPlatform(void) { + _RGFW->keycodes[DOM_VK_BACK_QUOTE] = RGFW_backtick; + _RGFW->keycodes[DOM_VK_0] = RGFW_0; + _RGFW->keycodes[DOM_VK_1] = RGFW_1; + _RGFW->keycodes[DOM_VK_2] = RGFW_2; + _RGFW->keycodes[DOM_VK_3] = RGFW_3; + _RGFW->keycodes[DOM_VK_4] = RGFW_4; + _RGFW->keycodes[DOM_VK_5] = RGFW_5; + _RGFW->keycodes[DOM_VK_6] = RGFW_6; + _RGFW->keycodes[DOM_VK_7] = RGFW_7; + _RGFW->keycodes[DOM_VK_8] = RGFW_8; + _RGFW->keycodes[DOM_VK_9] = RGFW_9; + _RGFW->keycodes[DOM_VK_SPACE] = RGFW_space; + _RGFW->keycodes[DOM_VK_A] = RGFW_a; + _RGFW->keycodes[DOM_VK_B] = RGFW_b; + _RGFW->keycodes[DOM_VK_C] = RGFW_c; + _RGFW->keycodes[DOM_VK_D] = RGFW_d; + _RGFW->keycodes[DOM_VK_E] = RGFW_e; + _RGFW->keycodes[DOM_VK_F] = RGFW_f; + _RGFW->keycodes[DOM_VK_G] = RGFW_g; + _RGFW->keycodes[DOM_VK_H] = RGFW_h; + _RGFW->keycodes[DOM_VK_I] = RGFW_i; + _RGFW->keycodes[DOM_VK_J] = RGFW_j; + _RGFW->keycodes[DOM_VK_K] = RGFW_k; + _RGFW->keycodes[DOM_VK_L] = RGFW_l; + _RGFW->keycodes[DOM_VK_M] = RGFW_m; + _RGFW->keycodes[DOM_VK_N] = RGFW_n; + _RGFW->keycodes[DOM_VK_O] = RGFW_o; + _RGFW->keycodes[DOM_VK_P] = RGFW_p; + _RGFW->keycodes[DOM_VK_Q] = RGFW_q; + _RGFW->keycodes[DOM_VK_R] = RGFW_r; + _RGFW->keycodes[DOM_VK_S] = RGFW_s; + _RGFW->keycodes[DOM_VK_T] = RGFW_t; + _RGFW->keycodes[DOM_VK_U] = RGFW_u; + _RGFW->keycodes[DOM_VK_V] = RGFW_v; + _RGFW->keycodes[DOM_VK_W] = RGFW_w; + _RGFW->keycodes[DOM_VK_X] = RGFW_x; + _RGFW->keycodes[DOM_VK_Y] = RGFW_y; + _RGFW->keycodes[DOM_VK_Z] = RGFW_z; + _RGFW->keycodes[DOM_VK_PERIOD] = RGFW_period; + _RGFW->keycodes[DOM_VK_COMMA] = RGFW_comma; + _RGFW->keycodes[DOM_VK_SLASH] = RGFW_slash; + _RGFW->keycodes[DOM_VK_OPEN_BRACKET] = RGFW_bracket; + _RGFW->keycodes[DOM_VK_CLOSE_BRACKET] = RGFW_closeBracket; + _RGFW->keycodes[DOM_VK_SEMICOLON] = RGFW_semicolon; + _RGFW->keycodes[DOM_VK_QUOTE] = RGFW_apostrophe; + _RGFW->keycodes[DOM_VK_BACK_SLASH] = RGFW_backSlash; + _RGFW->keycodes[DOM_VK_RETURN] = RGFW_return; + _RGFW->keycodes[DOM_VK_DELETE] = RGFW_delete; + _RGFW->keycodes[DOM_VK_NUM_LOCK] = RGFW_numLock; + _RGFW->keycodes[DOM_VK_DIVIDE] = RGFW_kpSlash; + _RGFW->keycodes[DOM_VK_MULTIPLY] = RGFW_kpMultiply; + _RGFW->keycodes[DOM_VK_SUBTRACT] = RGFW_kpMinus; + _RGFW->keycodes[DOM_VK_NUMPAD1] = RGFW_kp1; + _RGFW->keycodes[DOM_VK_NUMPAD2] = RGFW_kp2; + _RGFW->keycodes[DOM_VK_NUMPAD3] = RGFW_kp3; + _RGFW->keycodes[DOM_VK_NUMPAD4] = RGFW_kp4; + _RGFW->keycodes[DOM_VK_NUMPAD5] = RGFW_kp5; + _RGFW->keycodes[DOM_VK_NUMPAD6] = RGFW_kp6; + _RGFW->keycodes[DOM_VK_NUMPAD9] = RGFW_kp9; + _RGFW->keycodes[DOM_VK_NUMPAD0] = RGFW_kp0; + _RGFW->keycodes[DOM_VK_DECIMAL] = RGFW_kpPeriod; + _RGFW->keycodes[DOM_VK_RETURN] = RGFW_kpReturn; + _RGFW->keycodes[DOM_VK_HYPHEN_MINUS] = RGFW_minus; + _RGFW->keycodes[DOM_VK_EQUALS] = RGFW_equals; + _RGFW->keycodes[DOM_VK_BACK_SPACE] = RGFW_backSpace; + _RGFW->keycodes[DOM_VK_TAB] = RGFW_tab; + _RGFW->keycodes[DOM_VK_CAPS_LOCK] = RGFW_capsLock; + _RGFW->keycodes[DOM_VK_SHIFT] = RGFW_shiftL; + _RGFW->keycodes[DOM_VK_CONTROL] = RGFW_controlL; + _RGFW->keycodes[DOM_VK_ALT] = RGFW_altL; + _RGFW->keycodes[DOM_VK_META] = RGFW_superL; + _RGFW->keycodes[DOM_VK_F1] = RGFW_F1; + _RGFW->keycodes[DOM_VK_F2] = RGFW_F2; + _RGFW->keycodes[DOM_VK_F3] = RGFW_F3; + _RGFW->keycodes[DOM_VK_F4] = RGFW_F4; + _RGFW->keycodes[DOM_VK_F5] = RGFW_F5; + _RGFW->keycodes[DOM_VK_F6] = RGFW_F6; + _RGFW->keycodes[DOM_VK_F7] = RGFW_F7; + _RGFW->keycodes[DOM_VK_F8] = RGFW_F8; + _RGFW->keycodes[DOM_VK_F9] = RGFW_F9; + _RGFW->keycodes[DOM_VK_F10] = RGFW_F10; + _RGFW->keycodes[DOM_VK_F11] = RGFW_F11; + _RGFW->keycodes[DOM_VK_F12] = RGFW_F12; + _RGFW->keycodes[DOM_VK_UP] = RGFW_up; + _RGFW->keycodes[DOM_VK_DOWN] = RGFW_down; + _RGFW->keycodes[DOM_VK_LEFT] = RGFW_left; + _RGFW->keycodes[DOM_VK_RIGHT] = RGFW_right; + _RGFW->keycodes[DOM_VK_INSERT] = RGFW_insert; + _RGFW->keycodes[DOM_VK_END] = RGFW_end; + _RGFW->keycodes[DOM_VK_PAGE_UP] = RGFW_pageUp; + _RGFW->keycodes[DOM_VK_PAGE_DOWN] = RGFW_pageDown; + _RGFW->keycodes[DOM_VK_ESCAPE] = RGFW_escape; + _RGFW->keycodes[DOM_VK_HOME] = RGFW_home; + _RGFW->keycodes[DOM_VK_SCROLL_LOCK] = RGFW_scrollLock; + _RGFW->keycodes[DOM_VK_PRINTSCREEN] = RGFW_printScreen; + _RGFW->keycodes[DOM_VK_PAUSE] = RGFW_pause; + _RGFW->keycodes[DOM_VK_F13] = RGFW_F13; + _RGFW->keycodes[DOM_VK_F14] = RGFW_F14; + _RGFW->keycodes[DOM_VK_F15] = RGFW_F15; + _RGFW->keycodes[DOM_VK_F16] = RGFW_F16; + _RGFW->keycodes[DOM_VK_F17] = RGFW_F17; + _RGFW->keycodes[DOM_VK_F18] = RGFW_F18; + _RGFW->keycodes[DOM_VK_F19] = RGFW_F19; + _RGFW->keycodes[DOM_VK_F20] = RGFW_F20; + _RGFW->keycodes[DOM_VK_F21] = RGFW_F21; + _RGFW->keycodes[DOM_VK_F22] = RGFW_F22; + _RGFW->keycodes[DOM_VK_F23] = RGFW_F23; + _RGFW->keycodes[DOM_VK_F24] = RGFW_F24; +} + +i32 RGFW_initPlatform(void) { return 0; } + +RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, RGFW_window* win) { + emscripten_set_canvas_element_size("#canvas", win->w, win->h); + emscripten_set_window_title(name); + + /* load callbacks */ + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_resize); + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, EM_FALSE, Emscripten_on_fullscreenchange); + emscripten_set_mousemove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousemove); + emscripten_set_touchstart_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchstart); + emscripten_set_touchend_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchend); + emscripten_set_touchmove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchmove); + emscripten_set_touchcancel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchcancel); + emscripten_set_mousedown_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousedown); + emscripten_set_mouseup_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mouseup); + emscripten_set_wheel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_wheel); + emscripten_set_focusin_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusin); + emscripten_set_focusout_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusout); + + if (flags & RGFW_windowAllowDND) { + win->internal.flags |= RGFW_windowAllowDND; + } + + EM_ASM({ + window.addEventListener("keydown", + (event) => { + var key = stringToNewUTF8(event.key); var code = stringToNewUTF8(event.code); + Module._RGFW_handleKeyMods(event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("Control"), event.getModifierState("Alt"), event.getModifierState("Shift"), event.getModifierState("Meta"), event.getModifierState("ScrollLock")); + Module._RGFW_handleKeyEvent(key, code, 1); + _free(key); _free(code); + }, + true); + window.addEventListener("keyup", + (event) => { + var key = stringToNewUTF8(event.key); var code = stringToNewUTF8(event.code); + Module._RGFW_handleKeyMods(event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("Control"), event.getModifierState("Alt"), event.getModifierState("Shift"), event.getModifierState("Meta"), event.getModifierState("ScrollLock")); + Module._RGFW_handleKeyEvent(key, code, 0); + _free(key); _free(code); + }, + true); + }); + + EM_ASM({ + var canvas = document.getElementById('canvas'); + canvas.addEventListener('drop', function(e) { + e.preventDefault(); + if (e.dataTransfer.file < 0) + return; + + var filenamesArray = []; + var count = e.dataTransfer.files.length; + + /* Read and save the files to emscripten's files */ + var drop_dir = '.rgfw_dropped_files'; + Module._RGFW_mkdir(drop_dir); + + for (var i = 0; i < count; i++) { + var file = e.dataTransfer.files[i]; + + var path = '/' + drop_dir + '/' + file.name.replace("//", '_'); + var reader = new FileReader(); + + reader.onloadend = (e) => { + if (reader.readyState != 2) { + out('failed to read dropped file: '+file.name+': '+reader.error); + } + else { + var data = e.target.result; + + Module._RGFW_writeFile(path, new Uint8Array(data), file.size); + } + }; + + reader.readAsArrayBuffer(file); + /* This works weird on modern OpenGL */ + var filename = stringToNewUTF8(path); + + filenamesArray.push(filename); + + Module._RGFW_makeSetValue(i, filename); + } + + Module._Emscripten_onDrop(count); + + for (var i = 0; i < count; ++i) { + _free(filenamesArray[i]); + } + }, true); + + canvas.addEventListener('dragover', function(e) { e.preventDefault(); return false; }, true); + }); + + return win; +} + +u8 RGFW_rgfwToKeyChar(u32 rgfw_keycode) { + return (u8)rgfw_keycode; /* TODO */ +} + +void RGFW_pollEvents(void) { + emscripten_sleep(0); + RGFW_resetPrevState(); +} + +void RGFW_window_resize(RGFW_window* win, i32 w, i32 h) { + RGFW_UNUSED(win); + emscripten_set_canvas_element_size("#canvas", w, h); +} + +/* NOTE: I don't know if this is possible */ +void RGFW_window_moveMouse(RGFW_window* win, i32 x, i32 y) { RGFW_UNUSED(win); RGFW_UNUSED(x); RGFW_UNUSED(y); } +/* this one might be possible but it looks iffy */ +RGFW_mouse* RGFW_loadMouse(u8* data, i32 w, i32 h, RGFW_format format) { RGFW_UNUSED(data); RGFW_UNUSED(w); RGFW_UNUSED(h); RGFW_UNUSED(format); return NULL; } + +void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { RGFW_UNUSED(win); RGFW_UNUSED(mouse); } +void RGFW_freeMouse(RGFW_mouse* mouse) { RGFW_UNUSED(mouse); } + +RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { + static const char cursors[16][16] = { + "default", "default", "text", "crosshair", + "pointer", "ew-resize", "ns-resize", "nwse-resize", "nesw-resize", + "move", "not-allowed" + }; + + RGFW_UNUSED(win); + EM_ASM( { document.getElementById("canvas").style.cursor = UTF8ToString($0); }, cursors[mouse]); + return RGFW_TRUE; +} + +RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { + return RGFW_window_setMouseStandard(win, RGFW_mouseNormal); +} + +void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show) { + RGFW_window_showMouseFlags(win, show); + if (show) + RGFW_window_setMouseDefault(win); + else + EM_ASM(document.getElementById('canvas').style.cursor = 'none';); +} + +RGFW_bool RGFW_getGlobalMouse(i32* x, i32* y) { + if(x) *x = EM_ASM_INT({ + return window.mouseX || 0; + }); + if (y) *y = EM_ASM_INT({ + return window.mouseY || 0; + }); + return RGFW_TRUE; +} + +void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool 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); +} + + +RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { + RGFW_UNUSED(str); RGFW_UNUSED(strCapacity); + /* + placeholder code for later + I'm not sure if this is possible do the the async stuff + */ + return 0; +} + +#ifdef RGFW_OPENGL +RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { + win->src.ctx.native = ctx; + win->src.gfxType = RGFW_gfxNativeOpenGL; + + EmscriptenWebGLContextAttributes attrs; + attrs.alpha = hints->alpha; + attrs.depth = hints->depth; + attrs.stencil = hints->stencil; + attrs.antialias = hints->samples; + attrs.premultipliedAlpha = EM_TRUE; + attrs.preserveDrawingBuffer = EM_FALSE; + + if (hints->doubleBuffer == 0) + attrs.renderViaOffscreenBackBuffer = 0; + else + attrs.renderViaOffscreenBackBuffer = hints->auxBuffers; + + attrs.failIfMajorPerformanceCaveat = EM_FALSE; + attrs.majorVersion = (hints->major == 0) ? 1 : hints->major; + attrs.minorVersion = hints->minor; + + attrs.enableExtensionsByDefault = EM_TRUE; + attrs.explicitSwapControl = EM_TRUE; + + emscripten_webgl_init_context_attributes(&attrs); + win->src.ctx.native->ctx = emscripten_webgl_create_context("#canvas", &attrs); + emscripten_webgl_make_context_current(win->src.ctx.native->ctx); + + #ifdef LEGACY_GL_EMULATION + EM_ASM("Module.useWebGL = true; GLImmediate.init();"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context initalized."); + #endif + return RGFW_TRUE; +} + +void RGFW_window_deleteContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx) { + emscripten_webgl_destroy_context(ctx->ctx); + win->src.ctx.native->ctx = 0; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context freed."); +} + +void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win) { + if (win) RGFW_ASSERT(win->src.ctx.native); + if (win == NULL) + emscripten_webgl_make_context_current(0); + else + emscripten_webgl_make_context_current(win->src.ctx.native->ctx); +} + +void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { + RGFW_ASSERT(win && win->src.ctx.native); + emscripten_webgl_commit_frame(); +} +void* RGFW_getCurrentContext_OpenGL(void) { return (void*)emscripten_webgl_get_current_context(); } + +RGFW_bool RGFW_extensionSupportedPlatform_OpenGL(const char* extension, size_t len) { + return EM_ASM_INT({ + var ext = UTF8ToString($0, $1); + var canvas = document.querySelector('canvas'); + var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); + if (!gl) return 0; + + var supported = gl.getSupportedExtensions(); + return supported && supported.includes(ext) ? 1 : 0; + }, extension, len); + return RGFW_FALSE; +} + +RGFW_proc RGFW_getProcAddress_OpenGL(const char* procname) { + return (RGFW_proc)emscripten_webgl_get_proc_address(procname); + return NULL; +} + +#endif + +void RGFW_window_swapInterval_OpenGL(RGFW_window* win, i32 swapInterval) { RGFW_UNUSED(win); RGFW_UNUSED(swapInterval); } + +void RGFW_deinitPlatform(void) { } + +void RGFW_window_closePlatform(RGFW_window* win) { } + +int RGFW_innerWidth(void) { return EM_ASM_INT({ return window.innerWidth; }); } +int RGFW_innerHeight(void) { return EM_ASM_INT({ return window.innerHeight; }); } + +void RGFW_releaseCursor(RGFW_window* win) { + RGFW_UNUSED(win); + emscripten_exit_pointerlock(); +} + +void RGFW_captureCursor(RGFW_window* win) { + RGFW_UNUSED(win); + emscripten_request_pointerlock("#canvas", 1); +} + + +void RGFW_window_setName(RGFW_window* win, const char* name) { + RGFW_UNUSED(win); + emscripten_set_window_title(name); +} + +void RGFW_window_maximize(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + RGFW_monitor mon = RGFW_window_getMonitor(win); + RGFW_window_move(win, 0, 0); + RGFW_window_resize(win, mon.mode.w, mon.mode.h); +} + +void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { + RGFW_ASSERT(win != NULL); + if (fullscreen) { + win->internal.flags |= RGFW_windowFullscreen; + EM_ASM( Module.requestFullscreen(false, true); ); + return; + } + win->internal.flags &= ~(u32)RGFW_windowFullscreen; + EM_ASM( Module.exitFullscreen(false, true); ); +} + +void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { + RGFW_UNUSED(win); + EM_ASM({ + var element = document.getElementById("canvas"); + if (element) + element.style.opacity = $1; + }, "elementId", opacity); +} + +#ifdef RGFW_WEBGPU +WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance instance) { + WGPUSurfaceDescriptor surfaceDesc = {0}; + WGPUEmscriptenSurfaceSourceCanvasHTMLSelector canvasDesc = {0}; + canvasDesc.chain.sType = WGPUSType_EmscriptenSurfaceSourceCanvasHTMLSelector; + canvasDesc.selector = (WGPUStringView){.data = "#canvas", .length = 7}; + + surfaceDesc.nextInChain = &canvasDesc.chain; + return wgpuInstanceCreateSurface(instance, &surfaceDesc); +} +#endif + +u32 RGFW_WASMPhysicalToRGFW(u32 hash) { switch(hash) { /* 0x0000 */ case 0x67243A2DU /* Escape */: return RGFW_escape; /* 0x0001 */ case 0x67251058U /* Digit0 */: return RGFW_0; /* 0x0002 */ @@ -10378,7 +13493,7 @@ u32 RGFW_wASMPhysicalToRGFW(u32 hash) { case 0x672FFAD4U /* Period */: return RGFW_period; /* 0x0034 */ case 0x92E0A438U /* Slash */: return RGFW_slash; /* 0x0035 */ case 0xC5A6BF7CU /* ShiftRight */: return RGFW_shiftR; - case 0x5D64DA91U /* NumpadMultiply */: return RGFW_multiply; + case 0x5D64DA91U /* NumpadMultiply */: return RGFW_kpMultiply; case 0xC914958CU /* AltLeft */: return RGFW_altL; /* 0x0038 */ case 0x92E09CB5U /* Space */: return RGFW_space; /* 0x0039 */ case 0xB8FAE73BU /* CapsLock */: return RGFW_capsLock; /* 0x003A */ @@ -10392,21 +13507,32 @@ u32 RGFW_wASMPhysicalToRGFW(u32 hash) { case 0x7174B780U /* F8 */: return RGFW_F8; /* 0x0042 */ case 0x7174B781U /* F9 */: return RGFW_F9; /* 0x0043 */ case 0x7B8E57B0U /* F10 */: return RGFW_F10; /* 0x0044 */ - case 0xC925FCDFU /* Numpad7 */: return RGFW_multiply; /* 0x0047 */ - case 0xC925FCD0U /* Numpad8 */: return RGFW_KP_8; /* 0x0048 */ - case 0xC925FCD1U /* Numpad9 */: return RGFW_KP_9; /* 0x0049 */ + case 0xC925FCDFU /* Numpad7 */: return RGFW_kpMultiply; /* 0x0047 */ + case 0xC925FCD0U /* Numpad8 */: return RGFW_kp8; /* 0x0048 */ + case 0xC925FCD1U /* Numpad9 */: return RGFW_kp9; /* 0x0049 */ case 0x5EA3E8A4U /* NumpadSubtract */: return RGFW_minus; /* 0x004A */ - case 0xC925FCDCU /* Numpad4 */: return RGFW_KP_4; /* 0x004B */ - case 0xC925FCDDU /* Numpad5 */: return RGFW_KP_5; /* 0x004C */ - case 0xC925FCDEU /* Numpad6 */: return RGFW_KP_6; /* 0x004D */ - case 0xC925FCD9U /* Numpad1 */: return RGFW_KP_1; /* 0x004F */ - case 0xC925FCDAU /* Numpad2 */: return RGFW_KP_2; /* 0x0050 */ - case 0xC925FCDBU /* Numpad3 */: return RGFW_KP_3; /* 0x0051 */ - case 0xC925FCD8U /* Numpad0 */: return RGFW_KP_0; /* 0x0052 */ + case 0xC925FCDCU /* Numpad4 */: return RGFW_kp4; /* 0x004B */ + case 0xC925FCDDU /* Numpad5 */: return RGFW_kp5; /* 0x004C */ + case 0xC925FCDEU /* Numpad6 */: return RGFW_kp6; /* 0x004D */ + case 0xC925FCD9U /* Numpad1 */: return RGFW_kp1; /* 0x004F */ + case 0xC925FCDAU /* Numpad2 */: return RGFW_kp2; /* 0x0050 */ + case 0xC925FCDBU /* Numpad3 */: return RGFW_kp3; /* 0x0051 */ + case 0xC925FCD8U /* Numpad0 */: return RGFW_kp0; /* 0x0052 */ case 0x95852DACU /* NumpadDecimal */: return RGFW_period; /* 0x0053 */ case 0x7B8E57B1U /* F11 */: return RGFW_F11; /* 0x0057 */ case 0x7B8E57B2U /* F12 */: return RGFW_F12; /* 0x0058 */ - case 0x7393FBACU /* NumpadEqual */: return RGFW_KP_Return; + case 0x7B8E57B3U /* F13 */: return DOM_PK_F13; /* 0x0064 */ + case 0x7B8E57B4U /* F14 */: return DOM_PK_F14; /* 0x0065 */ + case 0x7B8E57B5U /* F15 */: return DOM_PK_F15; /* 0x0066 */ + case 0x7B8E57B6U /* F16 */: return DOM_PK_F16; /* 0x0067 */ + case 0x7B8E57B7U /* F17 */: return DOM_PK_F17; /* 0x0068 */ + case 0x7B8E57B8U /* F18 */: return DOM_PK_F18; /* 0x0069 */ + case 0x7B8E57B9U /* F19 */: return DOM_PK_F19; /* 0x006A */ + case 0x7B8E57A8U /* F20 */: return DOM_PK_F20; /* 0x006B */ + case 0x7B8E57A9U /* F21 */: return DOM_PK_F21; /* 0x006C */ + case 0x7B8E57AAU /* F22 */: return DOM_PK_F22; /* 0x006D */ + case 0x7B8E57ABU /* F23 */: return DOM_PK_F23; /* 0x006E */ + case 0x7393FBACU /* NumpadEqual */: return RGFW_kpReturn; case 0xB88EBF7CU /* AltRight */: return RGFW_altR; /* 0xE038 */ case 0xC925873BU /* NumLock */: return RGFW_numLock; /* 0xE045 */ case 0x2C595F45U /* Home */: return RGFW_home; /* 0xE047 */ @@ -10421,602 +13547,28 @@ u32 RGFW_wASMPhysicalToRGFW(u32 hash) { case 0x6725C50DU /* Delete */: return RGFW_delete; /* 0xE053 */ case 0x6723658CU /* OSLeft */: return RGFW_superL; /* 0xE05B */ case 0x39643F7CU /* MetaRight */: return RGFW_superR; /* 0xE05C */ + case 0x380B9C8CU /* NumpadAdd */: return DOM_PK_NUMPAD_ADD; /* 0x004E */ + default: return DOM_PK_UNKNOWN; } return 0; } -void EMSCRIPTEN_KEEPALIVE RGFW_handleKeyEvent(char* key, char* code, RGFW_bool press) { - const char* iCode = code; - - u32 hash = 0; - while(*iCode) hash = ((hash ^ 0x7E057D79U) << 3) ^ (unsigned int)*iCode++; - - u32 physicalKey = RGFW_wASMPhysicalToRGFW(hash); - - u8 mappedKey = (u8)(*((u32*)key)); - - if (*((u16*)key) != mappedKey) { - mappedKey = 0; - if (*((u32*)key) == *((u32*)"Tab")) mappedKey = RGFW_tab; - } - - RGFW_eventQueuePushEx(e.type = (RGFW_eventType)(press ? RGFW_keyPressed : RGFW_keyReleased); - e.key = (u8)physicalKey; - e.keyChar = (u8)mappedKey; - e.keyMod = _RGFW.root->event.keyMod; - e._win = _RGFW.root); - - RGFW_keyboard[physicalKey].prev = RGFW_keyboard[physicalKey].current; - RGFW_keyboard[physicalKey].current = press; - - RGFW_keyCallback(_RGFW.root, physicalKey, mappedKey, _RGFW.root->event.keyMod, press); -} - -void EMSCRIPTEN_KEEPALIVE RGFW_handleKeyMods(RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll) { - RGFW_updateKeyModsPro(_RGFW.root, capital, numlock, control, alt, shift, super, scroll); -} - -void EMSCRIPTEN_KEEPALIVE Emscripten_onDrop(size_t count) { - if (!(_RGFW.root->_flags & RGFW_windowAllowDND)) - return; - - _RGFW.root->event.droppedFilesCount = count; - RGFW_eventQueuePushEx(e.type = RGFW_DND; - e.droppedFilesCount = count; - e._win = _RGFW.root); - RGFW_dndCallback(_RGFW.root, _RGFW.root->event.droppedFiles, count); -} - -RGFW_bool 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); - if (waitMS == 0) return; - - u32 start = (u32)(((u64)RGFW_getTimeNS()) / 1e+6); - - while ((_RGFW.eventLen == 0) && RGFW_stopCheckEvents_bool == RGFW_FALSE && (RGFW_getTimeNS() / 1e+6) - start < waitMS) - emscripten_sleep(0); - - RGFW_stopCheckEvents_bool = RGFW_FALSE; -} - -void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area){ - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - win->buffer = buffer; - win->bufferSize = area; - #ifdef RGFW_OSMESA - win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); - OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); - OSMesaPixelStore(OSMESA_Y_UP, 0); - #endif - #else - RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); /*!< if buffer rendering is not being used */ - #endif -} - -void EMSCRIPTEN_KEEPALIVE RGFW_makeSetValue(size_t index, char* file) { - /* This seems like a terrible idea, don't replicate this unless you hate yourself or the OS */ - /* TODO: find a better way to do this - */ - RGFW_STRNCPY((char*)_RGFW.root->event.droppedFiles[index], file, RGFW_MAX_PATH - 1); - _RGFW.root->event.droppedFiles[index][RGFW_MAX_PATH - 1] = '\0'; -} - -#include -#include -#include -#include - -void EMSCRIPTEN_KEEPALIVE RGFW_mkdir(char* name) { mkdir(name, 0755); } - -void EMSCRIPTEN_KEEPALIVE RGFW_writeFile(const char *path, const char *data, size_t len) { - FILE* file = fopen(path, "w+"); - if (file == NULL) - return; - - fwrite(data, sizeof(char), len, file); - fclose(file); -} - -void RGFW_window_initOpenGL(RGFW_window* win) { -#if defined(RGFW_OPENGL) && !defined(RGFW_WEBGPU) && !defined(RGFW_OSMESA) && !defined(RGFW_BUFFER) - EmscriptenWebGLContextAttributes attrs; - attrs.alpha = RGFW_GL_HINTS[RGFW_glDepth]; - attrs.depth = RGFW_GL_HINTS[RGFW_glAlpha]; - attrs.stencil = RGFW_GL_HINTS[RGFW_glStencil]; - attrs.antialias = RGFW_GL_HINTS[RGFW_glSamples]; - attrs.premultipliedAlpha = EM_TRUE; - attrs.preserveDrawingBuffer = EM_FALSE; - - if (RGFW_GL_HINTS[RGFW_glDoubleBuffer] == 0) - attrs.renderViaOffscreenBackBuffer = 0; - else - attrs.renderViaOffscreenBackBuffer = RGFW_GL_HINTS[RGFW_glAuxBuffers]; - - attrs.failIfMajorPerformanceCaveat = EM_FALSE; - attrs.majorVersion = (RGFW_GL_HINTS[RGFW_glMajor] == 0) ? 1 : RGFW_GL_HINTS[RGFW_glMajor]; - attrs.minorVersion = RGFW_GL_HINTS[RGFW_glMinor]; - - 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();"); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context initalized"); - #endif - glViewport(0, 0, win->r.w, win->r.h); -#endif -} - -void RGFW_window_freeOpenGL(RGFW_window* win) { -#if defined(RGFW_OPENGL) && !defined(RGFW_WEBGPU) && !defined(RGFW_OSMESA) && !defined(RGFW_OSMESA) - if (win->src.ctx == 0) return; - emscripten_webgl_destroy_context(win->src.ctx); - win->src.ctx = 0; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context freed"); -#elif defined(RGFW_OPENGL) && defined(RGFW_OSMESA) - if(win->src.ctx == 0) return; - OSMesaDestroyContext(win->src.ctx); - win->src.ctx = 0; -#else - RGFW_UNUSED(win); -#endif -} - -i32 RGFW_init(void) { -#if defined(RGFW_C89) || defined(__cplusplus) - if (_RGFW_init) return 0; - _RGFW_init = RGFW_TRUE; - _RGFW.root = NULL; _RGFW.current = NULL; _RGFW.windowCount = -2; _RGFW.eventLen = 0; _RGFW.eventIndex = 0; -#endif - - _RGFW.windowCount = 0; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context initialized"); - return 0; -} - -RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { - RGFW_window_basic_init(win, rect, flags); - RGFW_window_initOpenGL(win); - - #if defined(RGFW_WEBGPU) - win->src.ctx = wgpuCreateInstance(NULL); - win->src.device = emscripten_webgpu_get_device(); - win->src.queue = wgpuDeviceGetQueue(win->src.device); - #endif - - emscripten_set_canvas_element_size("#canvas", rect.w, rect.h); - emscripten_set_window_title(name); - - /* load callbacks */ - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_resize); - emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, EM_FALSE, Emscripten_on_fullscreenchange); - emscripten_set_mousemove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousemove); - emscripten_set_touchstart_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchstart); - emscripten_set_touchend_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchend); - emscripten_set_touchmove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchmove); - emscripten_set_touchcancel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchcancel); - emscripten_set_mousedown_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousedown); - emscripten_set_mouseup_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mouseup); - emscripten_set_wheel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_wheel); - emscripten_set_focusin_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusin); - emscripten_set_focusout_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusout); - emscripten_set_gamepadconnected_callback(NULL, 1, Emscripten_on_gamepad); - emscripten_set_gamepaddisconnected_callback(NULL, 1, Emscripten_on_gamepad); - - if (flags & RGFW_windowAllowDND) { - win->_flags |= RGFW_windowAllowDND; - } - - EM_ASM({ - window.addEventListener("keydown", - (event) => { - var key = stringToNewUTF8(event.key); var code = stringToNewUTF8(event.code); - Module._RGFW_handleKeyMods(event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("Control"), event.getModifierState("Alt"), event.getModifierState("Shift"), event.getModifierState("Meta"), event.getModifierState("ScrollLock")); - Module._RGFW_handleKeyEvent(key, code, 1); - _free(key); _free(code); - }, - true); - window.addEventListener("keyup", - (event) => { - var key = stringToNewUTF8(event.key); var code = stringToNewUTF8(event.code); - Module._RGFW_handleKeyMods(event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("Control"), event.getModifierState("Alt"), event.getModifierState("Shift"), event.getModifierState("Meta"), event.getModifierState("ScrollLock")); - Module._RGFW_handleKeyEvent(key, code, 0); - _free(key); _free(code); - }, - true); - }); - - EM_ASM({ - var canvas = document.getElementById('canvas'); - canvas.addEventListener('drop', function(e) { - e.preventDefault(); - if (e.dataTransfer.file < 0) - return; - - var filenamesArray = []; - var count = e.dataTransfer.files.length; - - /* Read and save the files to emscripten's files */ - var drop_dir = '.rgfw_dropped_files'; - Module._RGFW_mkdir(drop_dir); - - for (var i = 0; i < count; i++) { - var file = e.dataTransfer.files[i]; - - var path = '/' + drop_dir + '/' + file.name.replace("//", '_'); - var reader = new FileReader(); - - reader.onloadend = (e) => { - if (reader.readyState != 2) { - out('failed to read dropped file: '+file.name+': '+reader.error); - } - else { - var data = e.target.result; - - _RGFW_writeFile(path, new Uint8Array(data), file.size); - } - }; - - reader.readAsArrayBuffer(file); - /* This works weird on modern opengl */ - var filename = stringToNewUTF8(path); - - filenamesArray.push(filename); - - Module._RGFW_makeSetValue(i, filename); - } - - Module._Emscripten_onDrop(count); - - for (var i = 0; i < count; ++i) { - _free(filenamesArray[i]); - } - }, true); - - canvas.addEventListener('dragover', function(e) { e.preventDefault(); return false; }, true); - }); - - RGFW_window_setFlags(win, flags); - - if ((flags & RGFW_windowNoInitAPI) == 0) { - RGFW_window_initBuffer(win); - } - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); - return win; -} - -u8 RGFW_rgfwToKeyChar(u32 rgfw_keycode) { - return (u8)rgfw_keycode; /* TODO */ -} - -RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { - if (win == NULL || ((win->_flags & RGFW_windowFreeOnClose) && (win->_flags & RGFW_EVENT_QUIT))) return NULL; - RGFW_event* ev = RGFW_window_checkEventCore(win); - if (ev) return ev; - - emscripten_sample_gamepad_data(); - /* check gamepads */ - int i; - for (i = 0; (i < emscripten_get_num_gamepads()) && (i < 4); i++) { - if (RGFW_gamepads[i] == 0) - continue; - EmscriptenGamepadEvent gamepadState; - - if (emscripten_get_gamepad_status(i, &gamepadState) != EMSCRIPTEN_RESULT_SUCCESS) - break; - - /* Register buttons data for every connected gamepad */ - int j; - for (j = 0; (j < gamepadState.numButtons) && (j < 16); j++) { - u32 map[] = { - RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadX, RGFW_gamepadY, - RGFW_gamepadL1, RGFW_gamepadR1, RGFW_gamepadL2, RGFW_gamepadR2, - RGFW_gamepadSelect, RGFW_gamepadStart, - RGFW_gamepadL3, RGFW_gamepadR3, - RGFW_gamepadUp, RGFW_gamepadDown, RGFW_gamepadLeft, RGFW_gamepadRight, RGFW_gamepadHome - }; - - - u32 button = map[j]; - if (button == 404) - continue; - - if (RGFW_gamepadPressed[i][button].current != gamepadState.digitalButton[j]) { - if (gamepadState.digitalButton[j]) - win->event.type = RGFW_gamepadButtonPressed; - else - win->event.type = RGFW_gamepadButtonReleased; - - win->event.gamepad = i; - win->event.button = map[j]; - - RGFW_gamepadPressed[i][button].prev = RGFW_gamepadPressed[i][button].current; - RGFW_gamepadPressed[i][button].current = gamepadState.digitalButton[j]; - - RGFW_gamepadButtonCallback(win, win->event.gamepad, win->event.button, gamepadState.digitalButton[j]); - return &win->event; - } - } - - for (j = 0; (j < gamepadState.numAxes) && (j < 4); j += 2) { - win->event.axisesCount = gamepadState.numAxes / 2; - if (RGFW_gamepadAxes[i][(size_t)(j / 2)].x != (i8)(gamepadState.axis[j] * 100.0f) || - RGFW_gamepadAxes[i][(size_t)(j / 2)].y != (i8)(gamepadState.axis[j + 1] * 100.0f) - ) { - - RGFW_gamepadAxes[i][(size_t)(j / 2)].x = (i8)(gamepadState.axis[j] * 100.0f); - RGFW_gamepadAxes[i][(size_t)(j / 2)].y = (i8)(gamepadState.axis[j + 1] * 100.0f); - win->event.axis[(size_t)(j / 2)] = RGFW_gamepadAxes[i][(size_t)(j / 2)]; - - win->event.type = RGFW_gamepadAxisMove; - win->event.gamepad = i; - win->event.whichAxis = j / 2; - - RGFW_gamepadAxisCallback(win, win->event.gamepad, win->event.axis, win->event.axisesCount, win->event.whichAxis); - return &win->event; - } - } - } - - return NULL; -} - -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 */ -RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { RGFW_UNUSED(channels); RGFW_UNUSED(a); RGFW_UNUSED(icon); return NULL; } - -void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { RGFW_UNUSED(win); RGFW_UNUSED(mouse); } -void RGFW_freeMouse(RGFW_mouse* mouse) { RGFW_UNUSED(mouse); } - -RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { - static const char cursors[16][16] = { - "default", "default", "text", "crosshair", - "pointer", "ew-resize", "ns-resize", "nwse-resize", "nesw-resize", - "move", "not-allowed" - }; - - RGFW_UNUSED(win); - EM_ASM( { document.getElementById("canvas").style.cursor = UTF8ToString($0); }, cursors[mouse]); - return RGFW_TRUE; -} - -RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { - return RGFW_window_setMouseStandard(win, RGFW_mouseNormal); -} - -void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show) { - RGFW_window_showMouseFlags(win, 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, RGFW_bool 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); -} - - -RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { - RGFW_UNUSED(str); RGFW_UNUSED(strCapacity); - /* - placeholder code for later - I'm not sure if this is possible do the the async stuff - */ - return 0; -} - -void RGFW_window_swapBuffers_software(RGFW_window* win) { -#if defined(RGFW_OSMESA) - EM_ASM_({ - var data = Module.HEAPU8.slice($0, $0 + $1 * $2 * 4); - let context = document.getElementById("canvas").getContext("2d"); - let image = context.getImageData(0, 0, $1, $2); - image.data.set(data); - context.putImageData(image, 0, $4 - $2); - }, win->buffer, win->bufferSize.w, win->bufferSize.h, win->r.w, win->r.h); -#elif defined(RGFW_BUFFER) - EM_ASM_({ - var data = Module.HEAPU8.slice($0, $0 + $1 * $2 * 4); - let context = document.getElementById("canvas").getContext("2d"); - let image = context.getImageData(0, 0, $1, $2); - image.data.set(data); - context.putImageData(image, 0, 0); - }, win->buffer, win->bufferSize.w, win->bufferSize.h, win->r.w, win->r.h); - emscripten_sleep(0); -#else - RGFW_UNUSED(win); -#endif -} - -void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { -#if !defined(RGFW_WEBGPU) && !(defined(RGFW_OSMESA) || defined(RGFW_BUFFER)) - if (win == NULL) - emscripten_webgl_make_context_current(0); - else - emscripten_webgl_make_context_current(win->src.ctx); -#endif -} - - -void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { -#ifndef RGFW_WEBGPU - emscripten_webgl_commit_frame(); - -#endif - emscripten_sleep(0); -} - -#ifndef RGFW_WEBGPU -void* RGFW_getCurrent_OpenGL(void) { return (void*)emscripten_webgl_get_current_context(); } -#endif - -#ifndef RGFW_EGL -void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { RGFW_UNUSED(win); RGFW_UNUSED(swapInterval); } -#endif - -void RGFW_deinit(void) { _RGFW.windowCount = -1; RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context deinitialized"); } - -void RGFW_window_close(RGFW_window* win) { - if ((win->_flags & RGFW_windowNoInitAPI) == 0) RGFW_window_freeOpenGL(win); - - #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - if ((win->_flags & RGFW_BUFFER_ALLOC)) - RGFW_FREE(win->buffer); - #endif - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a window was freed"); - _RGFW.windowCount--; - if (_RGFW.windowCount == 0) RGFW_deinit(); - - RGFW_clipboard_switch(NULL); - RGFW_FREE(win->event.droppedFiles); - if ((win->_flags & RGFW_WINDOW_ALLOC)) { - RGFW_FREE(win); - win = NULL; - } -} - -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()); -} - -RGFW_bool RGFW_extensionSupportedPlatform(const char* extension, size_t len) { -#ifdef RGFW_OPENGL - return EM_ASM_INT({ - var ext = UTF8ToString($0, $1); - var canvas = document.querySelector('canvas'); - var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); - if (!gl) return 0; - - var supported = gl.getSupportedExtensions(); - return supported && supported.includes(ext) ? 1 : 0; - }, extension, len); -#else - return RGFW_FALSE; -#endif -} - -RGFW_proc RGFW_getProcAddress(const char* procname) { -#ifdef RGFW_OPENGL - return (RGFW_proc)emscripten_webgl_get_proc_address(procname); -#else - return NULL -#endif -} - -void RGFW_sleep(u64 milisecond) { - emscripten_sleep(milisecond); -} - -u64 RGFW_getTimerFreq(void) { return (u64)1000; } -u64 RGFW_getTimerValue(void) { return emscripten_get_now() * 1e+6; } - -void RGFW_releaseCursor(RGFW_window* win) { - RGFW_UNUSED(win); - emscripten_exit_pointerlock(); -} - -void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { - RGFW_UNUSED(win); RGFW_UNUSED(r); - - emscripten_request_pointerlock("#canvas", 1); -} - - -void RGFW_window_setName(RGFW_window* win, const char* name) { - RGFW_UNUSED(win); - emscripten_set_window_title(name); -} - -void RGFW_window_maximize(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - - RGFW_area screen = RGFW_getScreenSize(); - RGFW_window_move(win, RGFW_POINT(0, 0)); - RGFW_window_resize(win, screen); -} - -void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { - RGFW_ASSERT(win != NULL); - if (fullscreen) { - win->_flags |= RGFW_windowFullscreen; - EM_ASM( Module.requestFullscreen(false, true); ); - return; - } - win->_flags &= ~(u32)RGFW_windowFullscreen; - EM_ASM( Module.exitFullscreen(false, true); ); -} - -void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { - RGFW_UNUSED(win); - EM_ASM({ - var element = document.getElementById("canvas"); - if (element) - element.style.opacity = $1; - }, "elementId", opacity); -} - /* unsupported functions */ void RGFW_window_focus(RGFW_window* win) { RGFW_UNUSED(win); } void RGFW_window_raise(RGFW_window* win) { RGFW_UNUSED(win); } RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { RGFW_UNUSED(mon); RGFW_UNUSED(mode); RGFW_UNUSED(request); return RGFW_FALSE; } RGFW_monitor* RGFW_getMonitors(size_t* len) { RGFW_UNUSED(len); 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_setAspectRatio(RGFW_window* win, RGFW_area a) { RGFW_UNUSED(win); RGFW_UNUSED(a); } -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_move(RGFW_window* win, i32 x, i32 y) { RGFW_UNUSED(win); RGFW_UNUSED(x); RGFW_UNUSED(y); } +void RGFW_window_setAspectRatio(RGFW_window* win, i32 w, i32 h) { RGFW_UNUSED(win); RGFW_UNUSED(w); RGFW_UNUSED(h); } +void RGFW_window_setMinSize(RGFW_window* win, i32 w, i32 h) { RGFW_UNUSED(win); RGFW_UNUSED(w); RGFW_UNUSED(h); } +void RGFW_window_setMaxSize(RGFW_window* win, i32 w, i32 h) { RGFW_UNUSED(win); RGFW_UNUSED(w); RGFW_UNUSED(h); } void RGFW_window_minimize(RGFW_window* win) { RGFW_UNUSED(win); } void RGFW_window_restore(RGFW_window* win) { RGFW_UNUSED(win); } void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { RGFW_UNUSED(win); RGFW_UNUSED(floating); } void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { RGFW_UNUSED(win); RGFW_UNUSED(border); } -RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* icon, RGFW_area a, i32 channels, u8 type) { RGFW_UNUSED(win); RGFW_UNUSED(icon); RGFW_UNUSED(a); RGFW_UNUSED(channels); RGFW_UNUSED(type); return RGFW_FALSE; } +RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_icon type) { RGFW_UNUSED(win); RGFW_UNUSED(data); RGFW_UNUSED(w); RGFW_UNUSED(h); RGFW_UNUSED(format); RGFW_UNUSED(type); return RGFW_FALSE; } void RGFW_window_hide(RGFW_window* win) { RGFW_UNUSED(win); } void RGFW_window_show(RGFW_window* win) {RGFW_UNUSED(win); } RGFW_bool RGFW_window_isHidden(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } @@ -11024,43 +13576,338 @@ RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { RGFW_UNUSED(win); return R RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } RGFW_bool RGFW_window_isFloating(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { RGFW_UNUSED(win); return (RGFW_monitor){}; } +void RGFW_waitForEvent(i32 waitMS) { RGFW_UNUSED(waitMS); } #endif /* end of web asm defines */ -/* unix (macOS, linux, web asm) only stuff */ -#if defined(RGFW_X11) || defined(RGFW_MACOS) || defined(RGFW_WASM) || defined(RGFW_WAYLAND) -#ifndef RGFW_NO_THREADS -#include +/* + * RGFW function pointer backend, made to allow you to compile for Wayland but fallback to X11 +*/ +#ifdef RGFW_DYNAMIC +typedef RGFW_window* (*RGFW_createWindowPlatform_ptr)(const char* name, RGFW_windowFlags flags, RGFW_window* win); +typedef RGFW_bool (*RGFW_getMouse_ptr)(i32* x, i32* y); +typedef u8 (*RGFW_rgfwToKeyChar_ptr)(u32 key); +typedef void (*RGFW_pollEvents_ptr)(void); +typedef void (*RGFW_window_move_ptr)(RGFW_window* win, i32 x, i32 y); +typedef void (*RGFW_window_resize_ptr)(RGFW_window* win, i32 w, i32 h); +typedef void (*RGFW_window_setAspectRatio_ptr)(RGFW_window* win, i32 w, i32 h); +typedef void (*RGFW_window_setMinSize_ptr)(RGFW_window* win, i32 w, i32 h); +typedef void (*RGFW_window_setMaxSize_ptr)(RGFW_window* win, i32 w, i32 h); +typedef void (*RGFW_window_maximize_ptr)(RGFW_window* win); +typedef void (*RGFW_window_focus_ptr)(RGFW_window* win); +typedef void (*RGFW_window_raise_ptr)(RGFW_window* win); +typedef void (*RGFW_window_setFullscreen_ptr)(RGFW_window* win, RGFW_bool fullscreen); +typedef void (*RGFW_window_setFloating_ptr)(RGFW_window* win, RGFW_bool floating); +typedef void (*RGFW_window_setOpacity_ptr)(RGFW_window* win, u8 opacity); +typedef void (*RGFW_window_minimize_ptr)(RGFW_window* win); +typedef void (*RGFW_window_restore_ptr)(RGFW_window* win); +typedef RGFW_bool (*RGFW_window_isFloating_ptr)(RGFW_window* win); +typedef void (*RGFW_window_setName_ptr)(RGFW_window* win, const char* name); +typedef void (*RGFW_window_setMousePassthrough_ptr)(RGFW_window* win, RGFW_bool passthrough); +typedef RGFW_bool (*RGFW_window_setIconEx_ptr)(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, u8 type); +typedef RGFW_mouse* (*RGFW_loadMouse_ptr)(u8* data, i32 w, i32 h, RGFW_format format); +typedef void (*RGFW_window_setMouse_ptr)(RGFW_window* win, RGFW_mouse* mouse); +typedef void (*RGFW_window_moveMouse_ptr)(RGFW_window* win, i32 x, i32 y); +typedef RGFW_bool (*RGFW_window_setMouseDefault_ptr)(RGFW_window* win); +typedef RGFW_bool (*RGFW_window_setMouseStandard_ptr)(RGFW_window* win, u8 mouse); +typedef void (*RGFW_window_hide_ptr)(RGFW_window* win); +typedef void (*RGFW_window_show_ptr)(RGFW_window* win); +typedef RGFW_ssize_t (*RGFW_readClipboardPtr_ptr)(char* str, size_t strCapacity); +typedef void (*RGFW_writeClipboard_ptr)(const char* text, u32 textLen); +typedef RGFW_bool (*RGFW_window_isHidden_ptr)(RGFW_window* win); +typedef RGFW_bool (*RGFW_window_isMinimized_ptr)(RGFW_window* win); +typedef RGFW_bool (*RGFW_window_isMaximized_ptr)(RGFW_window* win); +typedef RGFW_monitor* (*RGFW_getMonitors_ptr)(size_t* len); +typedef RGFW_monitor (*RGFW_getPrimaryMonitor_ptr)(void); +typedef RGFW_bool (*RGFW_monitor_requestMode_ptr)(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request); +typedef RGFW_monitor (*RGFW_window_getMonitor_ptr)(RGFW_window* win); +typedef void (*RGFW_window_closePlatform_ptr)(RGFW_window* win); +typedef RGFW_bool (*RGFW_createSurfacePtr_ptr)(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface); +typedef void (*RGFW_window_blitSurface_ptr)(RGFW_window* win, RGFW_surface* surface); +typedef void (*RGFW_surface_freePtr_ptr)(RGFW_surface* surface); +typedef void (*RGFW_freeMouse_ptr)(RGFW_mouse* mouse); +typedef void (*RGFW_window_setBorder_ptr)(RGFW_window* win, RGFW_bool border); +typedef void (*RGFW_releaseCursor_ptr)(RGFW_window* win); +typedef void (*RGFW_captureCursor_ptr)(RGFW_window* win); +#ifdef RGFW_OPENGL +typedef void (*RGFW_window_makeCurrentContext_OpenGL_ptr)(RGFW_window* win); +typedef void* (*RGFW_getCurrentContext_OpenGL_ptr)(void); +typedef void (*RGFW_window_swapBuffers_OpenGL_ptr)(RGFW_window* win); +typedef void (*RGFW_window_swapInterval_OpenGL_ptr)(RGFW_window* win, i32 swapInterval); +typedef RGFW_bool (*RGFW_extensionSupportedPlatform_OpenGL_ptr)(const char* extension, size_t len); +typedef RGFW_proc (*RGFW_getProcAddress_OpenGL_ptr)(const char* procname); +typedef RGFW_bool (*RGFW_window_createContextPtr_OpenGL_ptr)(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints); +typedef void (*RGFW_window_deleteContextPtr_OpenGL_ptr)(RGFW_window* win, RGFW_glContext* ctx); +#endif +#ifdef RGFW_WEBGPU +typedef WGPUSurface (*RGFW_window_createSurface_WebGPU_ptr)(RGFW_window* window, WGPUInstance instance); +#endif -RGFW_thread RGFW_createThread(RGFW_threadFunc_ptr ptr, void* args) { - RGFW_thread t; - pthread_create((pthread_t*) &t, NULL, *ptr, args); - return t; +/* Structure to hold all function pointers */ +typedef struct RGFW_FunctionPointers { + RGFW_createSurfacePtr_ptr createSurfacePtr; + RGFW_window_blitSurface_ptr window_blitSurface; + RGFW_surface_freePtr_ptr surface_freePtr; + RGFW_freeMouse_ptr freeMouse; + RGFW_window_setBorder_ptr window_setBorder; + RGFW_releaseCursor_ptr releaseCursor; + RGFW_captureCursor_ptr captureCursor; + RGFW_createWindowPlatform_ptr createWindowPlatform; + RGFW_getMouse_ptr getGlobalMouse; + RGFW_rgfwToKeyChar_ptr rgfwToKeyChar; + RGFW_pollEvents_ptr pollEvents; + RGFW_window_move_ptr window_move; + RGFW_window_resize_ptr window_resize; + RGFW_window_setAspectRatio_ptr window_setAspectRatio; + RGFW_window_setMinSize_ptr window_setMinSize; + RGFW_window_setMaxSize_ptr window_setMaxSize; + RGFW_window_maximize_ptr window_maximize; + RGFW_window_focus_ptr window_focus; + RGFW_window_raise_ptr window_raise; + RGFW_window_setFullscreen_ptr window_setFullscreen; + RGFW_window_setFloating_ptr window_setFloating; + RGFW_window_setOpacity_ptr window_setOpacity; + RGFW_window_minimize_ptr window_minimize; + RGFW_window_restore_ptr window_restore; + RGFW_window_isFloating_ptr window_isFloating; + RGFW_window_setName_ptr window_setName; + RGFW_window_setMousePassthrough_ptr window_setMousePassthrough; + RGFW_window_setIconEx_ptr window_setIconEx; + RGFW_loadMouse_ptr loadMouse; + RGFW_window_setMouse_ptr window_setMouse; + RGFW_window_moveMouse_ptr window_moveMouse; + RGFW_window_setMouseDefault_ptr window_setMouseDefault; + RGFW_window_setMouseStandard_ptr window_setMouseStandard; + RGFW_window_hide_ptr window_hide; + RGFW_window_show_ptr window_show; + RGFW_readClipboardPtr_ptr readClipboardPtr; + RGFW_writeClipboard_ptr writeClipboard; + RGFW_window_isHidden_ptr window_isHidden; + RGFW_window_isMinimized_ptr window_isMinimized; + RGFW_window_isMaximized_ptr window_isMaximized; + RGFW_getMonitors_ptr getMonitors; + RGFW_getPrimaryMonitor_ptr getPrimaryMonitor; + RGFW_monitor_requestMode_ptr monitor_requestMode; + RGFW_window_getMonitor_ptr window_getMonitor; + RGFW_window_closePlatform_ptr window_closePlatform; +#ifdef RGFW_OPENGL + RGFW_extensionSupportedPlatform_OpenGL_ptr extensionSupportedPlatform_OpenGL; + RGFW_getProcAddress_OpenGL_ptr getProcAddress_OpenGL; + RGFW_window_createContextPtr_OpenGL_ptr window_createContextPtr_OpenGL; + RGFW_window_deleteContextPtr_OpenGL_ptr window_deleteContextPtr_OpenGL; + RGFW_window_makeCurrentContext_OpenGL_ptr window_makeCurrentContext_OpenGL; + RGFW_getCurrentContext_OpenGL_ptr getCurrentContext_OpenGL; + RGFW_window_swapBuffers_OpenGL_ptr window_swapBuffers_OpenGL; + RGFW_window_swapInterval_OpenGL_ptr window_swapInterval_OpenGL; +#endif +#ifdef RGFW_WEBGPU + RGFW_window_createSurface_WebGPU_ptr window_createSurface_WebGPU; +#endif +} RGFW_functionPointers; + +RGFW_functionPointers RGFW_api; + +RGFW_bool RGFW_createSurfacePtr(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { return RGFW_api.createSurfacePtr(data, w, h, format, surface); } +void RGFW_surface_freePtr(RGFW_surface* surface) { RGFW_api.surface_freePtr(surface); } +void RGFW_freeMouse(RGFW_mouse* mouse) { RGFW_api.freeMouse(mouse); } +void RGFW_window_blitSurface(RGFW_window* win, RGFW_surface* surface) { RGFW_api.window_blitSurface(win, surface); } +void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { RGFW_api.window_setBorder(win, border); } +void RGFW_releaseCursor(RGFW_window* win) { RGFW_api.releaseCursor(win); } +void RGFW_captureCursor(RGFW_window* win) { RGFW_api.captureCursor(win); } +RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, RGFW_window* win) { RGFW_init(); return RGFW_api.createWindowPlatform(name, flags, win); } +RGFW_bool RGFW_getGlobalMouse(i32* x, i32* y) { return RGFW_api.getGlobalMouse(x, y); } +u8 RGFW_rgfwToKeyChar(u32 key) { return RGFW_api.rgfwToKeyChar(key); } +void RGFW_pollEvents(void) { RGFW_api.pollEvents(); } +void RGFW_window_move(RGFW_window* win, i32 x, i32 y) { RGFW_api.window_move(win, x, y); } +void RGFW_window_resize(RGFW_window* win, i32 w, i32 h) { RGFW_api.window_resize(win, w, h); } +void RGFW_window_setAspectRatio(RGFW_window* win, i32 w, i32 h) { RGFW_api.window_setAspectRatio(win, w, h); } +void RGFW_window_setMinSize(RGFW_window* win, i32 w, i32 h) { RGFW_api.window_setMinSize(win, w, h); } +void RGFW_window_setMaxSize(RGFW_window* win, i32 w, i32 h) { RGFW_api.window_setMaxSize(win, w, h); } +void RGFW_window_maximize(RGFW_window* win) { RGFW_api.window_maximize(win); } +void RGFW_window_focus(RGFW_window* win) { RGFW_api.window_focus(win); } +void RGFW_window_raise(RGFW_window* win) { RGFW_api.window_raise(win); } +void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { RGFW_api.window_setFullscreen(win, fullscreen); } +void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { RGFW_api.window_setFloating(win, floating); } +void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { RGFW_api.window_setOpacity(win, opacity); } +void RGFW_window_minimize(RGFW_window* win) { RGFW_api.window_minimize(win); } +void RGFW_window_restore(RGFW_window* win) { RGFW_api.window_restore(win); } +RGFW_bool RGFW_window_isFloating(RGFW_window* win) { return RGFW_api.window_isFloating(win); } +void RGFW_window_setName(RGFW_window* win, const char* name) { RGFW_api.window_setName(win, name); } + +#ifndef RGFW_NO_PASSTHROUGH +void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { RGFW_api.window_setMousePassthrough(win, passthrough); } +#endif + +RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, u8 type) { return RGFW_api.window_setIconEx(win, data, w, h, format, type); } +RGFW_mouse* RGFW_loadMouse(u8* data, i32 w, i32 h, RGFW_format format) { return RGFW_api.loadMouse(data, w, h, format); } +void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { RGFW_api.window_setMouse(win, mouse); } +void RGFW_window_moveMouse(RGFW_window* win, i32 x, i32 y) { RGFW_api.window_moveMouse(win, x, y); } +RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { return RGFW_api.window_setMouseDefault(win); } +RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { return RGFW_api.window_setMouseStandard(win, mouse); } +void RGFW_window_hide(RGFW_window* win) { RGFW_api.window_hide(win); } +void RGFW_window_show(RGFW_window* win) { RGFW_api.window_show(win); } +RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { return RGFW_api.readClipboardPtr(str, strCapacity); } +void RGFW_writeClipboard(const char* text, u32 textLen) { RGFW_api.writeClipboard(text, textLen); } +RGFW_bool RGFW_window_isHidden(RGFW_window* win) { return RGFW_api.window_isHidden(win); } +RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { return RGFW_api.window_isMinimized(win); } +RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { return RGFW_api.window_isMaximized(win); } +RGFW_monitor* RGFW_getMonitors(size_t* len) { return RGFW_api.getMonitors(len); } +RGFW_monitor RGFW_getPrimaryMonitor(void) { return RGFW_api.getPrimaryMonitor(); } +RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { return RGFW_api.monitor_requestMode(mon, mode, request); } +RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { return RGFW_api.window_getMonitor(win); } +void RGFW_window_closePlatform(RGFW_window* win) { RGFW_api.window_closePlatform(win); } + +#ifdef RGFW_OPENGL +RGFW_bool RGFW_extensionSupportedPlatform_OpenGL(const char* extension, size_t len) { return RGFW_api.extensionSupportedPlatform_OpenGL(extension, len); } +RGFW_proc RGFW_getProcAddress_OpenGL(const char* procname) { return RGFW_api.getProcAddress_OpenGL(procname); } +RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { return RGFW_api.window_createContextPtr_OpenGL(win, ctx, hints); } +void RGFW_window_deleteContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx) { RGFW_api.window_deleteContextPtr_OpenGL(win, ctx); } +void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win) { RGFW_api.window_makeCurrentContext_OpenGL(win); } +void* RGFW_getCurrentContext_OpenGL(void) { return RGFW_api.getCurrentContext_OpenGL(); } +void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { RGFW_api.window_swapBuffers_OpenGL(win); } +void RGFW_window_swapInterval_OpenGL(RGFW_window* win, i32 swapInterval) { RGFW_api.window_swapInterval_OpenGL(win, swapInterval); } +#endif + +#ifdef RGFW_WEBGPU +WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance instance) { return RGFW_api.window_createSurface_WebGPU(window, instance); } +#endif +#endif /* RGFW_DYNAMIC */ + +/* + * start of X11 AND wayland defines + * this allows a single executable to support x11 AND wayland + * falling back to x11 if wayland fails to initalize +*/ +#if defined(RGFW_WAYLAND) && defined(RGFW_X11) +void RGFW_load_X11(void) { + RGFW_api.createSurfacePtr = RGFW_createSurfacePtr_X11; + RGFW_api.window_blitSurface = RGFW_window_blitSurface_X11; + RGFW_api.surface_freePtr = RGFW_surface_freePtr_X11; + RGFW_api.freeMouse = RGFW_freeMouse_X11; + RGFW_api.window_setBorder = RGFW_window_setBorder_X11; + RGFW_api.releaseCursor = RGFW_releaseCursor_X11; + RGFW_api.captureCursor = RGFW_captureCursor_X11; + RGFW_api.createWindowPlatform = RGFW_createWindowPlatform_X11; + RGFW_api.getGlobalMouse = RGFW_getGlobalMouse_X11; + RGFW_api.rgfwToKeyChar = RGFW_rgfwToKeyChar_X11; + RGFW_api.pollEvents = RGFW_pollEvents_X11; + RGFW_api.window_move = RGFW_window_move_X11; + RGFW_api.window_resize = RGFW_window_resize_X11; + RGFW_api.window_setAspectRatio = RGFW_window_setAspectRatio_X11; + RGFW_api.window_setMinSize = RGFW_window_setMinSize_X11; + RGFW_api.window_setMaxSize = RGFW_window_setMaxSize_X11; + RGFW_api.window_maximize = RGFW_window_maximize_X11; + RGFW_api.window_focus = RGFW_window_focus_X11; + RGFW_api.window_raise = RGFW_window_raise_X11; + RGFW_api.window_setFullscreen = RGFW_window_setFullscreen_X11; + RGFW_api.window_setFloating = RGFW_window_setFloating_X11; + RGFW_api.window_setOpacity = RGFW_window_setOpacity_X11; + RGFW_api.window_minimize = RGFW_window_minimize_X11; + RGFW_api.window_restore = RGFW_window_restore_X11; + RGFW_api.window_isFloating = RGFW_window_isFloating_X11; + RGFW_api.window_setName = RGFW_window_setName_X11; +#ifndef RGFW_NO_PASSTHROUGH + RGFW_api.window_setMousePassthrough = RGFW_window_setMousePassthrough_X11; +#endif + RGFW_api.window_setIconEx = RGFW_window_setIconEx_X11; + RGFW_api.loadMouse = RGFW_loadMouse_X11; + RGFW_api.window_setMouse = RGFW_window_setMouse_X11; + RGFW_api.window_moveMouse = RGFW_window_moveMouse_X11; + RGFW_api.window_setMouseDefault = RGFW_window_setMouseDefault_X11; + RGFW_api.window_setMouseStandard = RGFW_window_setMouseStandard_X11; + RGFW_api.window_hide = RGFW_window_hide_X11; + RGFW_api.window_show = RGFW_window_show_X11; + RGFW_api.readClipboardPtr = RGFW_readClipboardPtr_X11; + RGFW_api.writeClipboard = RGFW_writeClipboard_X11; + RGFW_api.window_isHidden = RGFW_window_isHidden_X11; + RGFW_api.window_isMinimized = RGFW_window_isMinimized_X11; + RGFW_api.window_isMaximized = RGFW_window_isMaximized_X11; + RGFW_api.getMonitors = RGFW_getMonitors_X11; + RGFW_api.getPrimaryMonitor = RGFW_getPrimaryMonitor_X11; + RGFW_api.monitor_requestMode = RGFW_monitor_requestMode_X11; + RGFW_api.window_getMonitor = RGFW_window_getMonitor_X11; + RGFW_api.window_closePlatform = RGFW_window_closePlatform_X11; +#ifdef RGFW_OPENGL + RGFW_api.extensionSupportedPlatform_OpenGL = RGFW_extensionSupportedPlatform_OpenGL_X11; + RGFW_api.getProcAddress_OpenGL = RGFW_getProcAddress_OpenGL_X11; + RGFW_api.window_createContextPtr_OpenGL = RGFW_window_createContextPtr_OpenGL_X11; + RGFW_api.window_deleteContextPtr_OpenGL = RGFW_window_deleteContextPtr_OpenGL_X11; + RGFW_api.window_makeCurrentContext_OpenGL = RGFW_window_makeCurrentContext_OpenGL_X11; + RGFW_api.getCurrentContext_OpenGL = RGFW_getCurrentContext_OpenGL_X11; + RGFW_api.window_swapBuffers_OpenGL = RGFW_window_swapBuffers_OpenGL_X11; + RGFW_api.window_swapInterval_OpenGL = RGFW_window_swapInterval_OpenGL_X11; +#endif +#ifdef RGFW_WEBGPU + RGFW_api.window_createSurface_WebGPU = RGFW_window_createSurface_WebGPU_X11; +#endif } -void RGFW_cancelThread(RGFW_thread thread) { pthread_cancel((pthread_t) thread); } -void RGFW_joinThread(RGFW_thread thread) { pthread_join((pthread_t) thread, NULL); } -#if defined(__linux__) -void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { pthread_setschedprio((pthread_t)thread, priority); } -#else -void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { RGFW_UNUSED(thread); RGFW_UNUSED(priority); } +void RGFW_load_Wayland(void) { + RGFW_api.createSurfacePtr = RGFW_createSurfacePtr_Wayland; + RGFW_api.window_blitSurface = RGFW_window_blitSurface_Wayland; + RGFW_api.surface_freePtr = RGFW_surface_freePtr_Wayland; + RGFW_api.freeMouse = RGFW_freeMouse_Wayland; + RGFW_api.window_setBorder = RGFW_window_setBorder_Wayland; + RGFW_api.releaseCursor = RGFW_releaseCursor_Wayland; + RGFW_api.captureCursor = RGFW_captureCursor_Wayland; + RGFW_api.createWindowPlatform = RGFW_createWindowPlatform_Wayland; + RGFW_api.getGlobalMouse = RGFW_getGlobalMouse_Wayland; + RGFW_api.rgfwToKeyChar = RGFW_rgfwToKeyChar_Wayland; + RGFW_api.pollEvents = RGFW_pollEvents_Wayland; + RGFW_api.window_move = RGFW_window_move_Wayland; + RGFW_api.window_resize = RGFW_window_resize_Wayland; + RGFW_api.window_setAspectRatio = RGFW_window_setAspectRatio_Wayland; + RGFW_api.window_setMinSize = RGFW_window_setMinSize_Wayland; + RGFW_api.window_setMaxSize = RGFW_window_setMaxSize_Wayland; + RGFW_api.window_maximize = RGFW_window_maximize_Wayland; + RGFW_api.window_focus = RGFW_window_focus_Wayland; + RGFW_api.window_raise = RGFW_window_raise_Wayland; + RGFW_api.window_setFullscreen = RGFW_window_setFullscreen_Wayland; + RGFW_api.window_setFloating = RGFW_window_setFloating_Wayland; + RGFW_api.window_setOpacity = RGFW_window_setOpacity_Wayland; + RGFW_api.window_minimize = RGFW_window_minimize_Wayland; + RGFW_api.window_restore = RGFW_window_restore_Wayland; + RGFW_api.window_isFloating = RGFW_window_isFloating_Wayland; + RGFW_api.window_setName = RGFW_window_setName_Wayland; +#ifndef RGFW_NO_PASSTHROUGH + RGFW_api.window_setMousePassthrough = RGFW_window_setMousePassthrough_Wayland; #endif + RGFW_api.window_setIconEx = RGFW_window_setIconEx_Wayland; + RGFW_api.loadMouse = RGFW_loadMouse_Wayland; + RGFW_api.window_setMouse = RGFW_window_setMouse_Wayland; + RGFW_api.window_moveMouse = RGFW_window_moveMouse_Wayland; + RGFW_api.window_setMouseDefault = RGFW_window_setMouseDefault_Wayland; + RGFW_api.window_setMouseStandard = RGFW_window_setMouseStandard_Wayland; + RGFW_api.window_hide = RGFW_window_hide_Wayland; + RGFW_api.window_show = RGFW_window_show_Wayland; + RGFW_api.readClipboardPtr = RGFW_readClipboardPtr_Wayland; + RGFW_api.writeClipboard = RGFW_writeClipboard_Wayland; + RGFW_api.window_isHidden = RGFW_window_isHidden_Wayland; + RGFW_api.window_isMinimized = RGFW_window_isMinimized_Wayland; + RGFW_api.window_isMaximized = RGFW_window_isMaximized_Wayland; + RGFW_api.getMonitors = RGFW_getMonitors_Wayland; + RGFW_api.getPrimaryMonitor = RGFW_getPrimaryMonitor_Wayland; + RGFW_api.monitor_requestMode = RGFW_monitor_requestMode_Wayland; + RGFW_api.window_getMonitor = RGFW_window_getMonitor_Wayland; + RGFW_api.window_closePlatform = RGFW_window_closePlatform_Wayland; +#ifdef RGFW_OPENGL + RGFW_api.extensionSupportedPlatform_OpenGL = RGFW_extensionSupportedPlatform_OpenGL_Wayland; + RGFW_api.getProcAddress_OpenGL = RGFW_getProcAddress_OpenGL_Wayland; + RGFW_api.window_createContextPtr_OpenGL = RGFW_window_createContextPtr_OpenGL_Wayland; + RGFW_api.window_deleteContextPtr_OpenGL = RGFW_window_deleteContextPtr_OpenGL_Wayland; + RGFW_api.window_makeCurrentContext_OpenGL = RGFW_window_makeCurrentContext_OpenGL_Wayland; + RGFW_api.getCurrentContext_OpenGL = RGFW_getCurrentContext_OpenGL_Wayland; + RGFW_api.window_swapBuffers_OpenGL = RGFW_window_swapBuffers_OpenGL_Wayland; + RGFW_api.window_swapInterval_OpenGL = RGFW_window_swapInterval_OpenGL_Wayland; +#endif +#ifdef RGFW_WEBGPU + RGFW_api.window_createSurface_WebGPU = RGFW_window_createSurface_WebGPU_Wayland; #endif - -#ifndef RGFW_WASM -void RGFW_sleep(u64 ms) { - struct timespec time; - time.tv_sec = 0; - time.tv_nsec = (long int)((double)ms * 1e+6); - - #ifndef RGFW_NO_UNIX_CLOCK - nanosleep(&time, NULL); - #endif } -#endif +#endif /* wayland AND x11 */ +/* end of X11 AND wayland defines */ -#endif /* end of unix / mac stuff */ #endif /* RGFW_IMPLEMENTATION */ #if defined(__cplusplus) && !defined(__EMSCRIPTEN__) @@ -11070,3 +13917,4 @@ void RGFW_sleep(u64 ms) { #if _MSC_VER #pragma warning( pop ) #endif + diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index a1b13856b..2671538d8 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -48,11 +48,6 @@ * **********************************************************************************************/ -#ifndef RAYLIB_H /* this should never actually happen, it's only here for IDEs */ -#include "raylib.h" -#include "../rcore.c" -#endif - #if defined(PLATFORM_WEB_RGFW) #define RGFW_NO_GL_HEADER #endif From 1c7240a01d75e80e49a4019aace0d7132dd06207 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 16 Dec 2025 18:18:42 +0100 Subject: [PATCH 234/430] Revert "REVIEWED: Alignment with other platforms" This reverts commit cf0d6fc664f1d0775c6b21ed79feaa1e38b2ddb4. --- src/external/RGFW.h | 17500 +++++++++++---------------- src/platforms/rcore_desktop_rgfw.c | 5 + 2 files changed, 7331 insertions(+), 10174 deletions(-) diff --git a/src/external/RGFW.h b/src/external/RGFW.h index b913d4f2d..7205bf9d8 100644 --- a/src/external/RGFW.h +++ b/src/external/RGFW.h @@ -1,8 +1,8 @@ /* * -* RGFW 1.8.1 +* RGFW 1.7.5-dev -* Copyright (C) 2022-25 Riley Mabb (@ColleagueRiley) +* Copyright (C) 2022-25 ColleagueRiley * * libpng license * @@ -33,12 +33,19 @@ /* #define RGFW_IMPLEMENTATION - (required) makes it so the source code is included #define RGFW_DEBUG - (optional) makes it so RGFW prints debug messages and errors when they're found - #define RGFW_EGL - (optional) compile with OpenGL functions, allowing you to use to use EGL instead of the native OpenGL functions + #define RGFW_OSMESA - (optional) use OSmesa as backend (instead of system's opengl api + regular opengl) + #define RGFW_BUFFER - (optional) draw directly to (RGFW) window pixel buffer that is drawn to screen (the buffer is in the RGBA format) + #define RGFW_EGL - (optional) use EGL for loading an OpenGL context (instead of the system's opengl api) + #define RGFW_OPENGL_ES1 - (optional) use EGL to load and use Opengl ES (version 1) for backend rendering (instead of the system's opengl api) + 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_DIRECTX - (optional) include integration directX functions (windows only) #define RGFW_VULKAN - (optional) include helpful vulkan integration functions and macros - #define RGFW_WEBGPU - (optional) use WebGPU for rendering - #define RGFW_NATIVE - (optional) define native RGFW types that use native API structures + #define RGFW_WEBGPU - (optional) use webGPU for rendering (Web ONLY) + #define RGFW_NO_API - (optional) don't use any rendering API (no opengl, no vulkan, no directX) + #define RGFW_LINK_EGL (optional) (windows only) if EGL is being used, if EGL functions should be defined dymanically (using GetProcAddress) #define RGFW_X11 (optional) (unix only) if X11 should be used. This option is turned on by default by unix systems except for MacOS #define RGFW_WAYLAND (optional) (unix only) use Wayland. (This can be used with X11) #define RGFW_NO_X11 (optional) (unix only) don't fallback to X11 when using Wayland @@ -55,9 +62,8 @@ #define RGFW_COCOA_GRAPHICS_SWITCHING - (optional) (cocoa) use automatic graphics switching (allow the system to choose to use GPU or iGPU) #define RGFW_COCOA_FRAME_NAME (optional) (cocoa) set frame name #define RGFW_NO_DPI - do not calculate DPI (no XRM nor libShcore included) + #define RGFW_BUFFER_BGR - use the BGR format for bufffers instead of RGB, saves processing time #define RGFW_ADVANCED_SMOOTH_RESIZE - use advanced methods for smooth resizing (may result in a spike in memory usage or worse performance) (eg. WM_TIMER and XSyncValue) - #define RGFW_NO_INFO - do not define the RGFW_info struct (without RGFW_IMPLEMENTATION) - #define RGFW_NO_GLXWINDOW - do not use GLXWindow #define RGFW_ALLOC x - choose the default allocation function (defaults to standard malloc) #define RGFW_FREE x - choose the default deallocation function (defaults to standard free) @@ -83,17 +89,20 @@ macos : gcc main.c -framework Cocoa -framework CoreVideo -framework OpenGL -fram 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", 100, 100, 500, 500, (u64)0); - RGFW_event event; + RGFW_window* win = RGFW_createWindow("name", RGFW_RECT(100, 100, 500, 500), (u64)0); - RGFW_window_setExitKey(win, RGFW_escape); - RGFW_window_setIcon(win, icon, 3, 3, RGFW_formatRGBA8); + RGFW_window_setIcon(win, icon, RGFW_AREA(3, 3), 4); while (RGFW_window_shouldClose(win) == RGFW_FALSE) { - while (RGFW_window_checkEvent(win, &event)) { - if (event.type == RGFW_quit) - break; - } + while (RGFW_window_checkEvent(win)) { + if (win->event.type == RGFW_quit || RGFW_isPressed(win, RGFW_escape)) + break; + } + + RGFW_window_swapBuffers(win); + + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); } RGFW_window_close(win); @@ -134,40 +143,25 @@ int main() { /* Credits : - EimaMei/Sacode : Code review, helped with X11, MacOS and Windows support, Silicon, siliapp.h -> referencing + 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 + stb - This project is heavily inspired by the stb single header files - SDL, GLFW and other online resources : reference implementations + GLFW: + certain parts of winapi and X11 are very poorly documented, + GLFW's source code was referenced and used throughout the project. contributors : (feel free to put yourself here if you contribute) - krisvers (@krisvers) -> code review - EimaMei (@SaCode) -> code review - Nycticebus (@Code-Nycticebus) -> bug fixes - Rob Rohan (@robrohan) -> X11 bugs and missing features, MacOS/Cocoa fixing memory issues/bugs + krisvers -> code review + EimaMei (SaCode) -> code review + Code-Nycticebus -> bug fixes + Rob Rohan -> X11 bugs and missing features, MacOS/Cocoa fixing memory issues/bugs AICDG (@THISISAGOODNAME) -> vulkan support (example) @Easymode -> support, testing/debugging, bug fixes and reviews Joshua Rowe (omnisci3nce) - bug fix, review (macOS) @lesleyrs -> bug fix, review (OpenGL) - Nick Porcino (@meshula) - testing, organization, review (MacOS, examples) - @therealmarrakesh -> documentation - @DarekParodia -> code review (X11) (C++) - @NishiOwO -> fix BSD support, fix OSMesa example - @BaynariKattu -> code review and documentation - Miguel Pinto (@konopimi) -> code review, fix vulkan example - @m-doescode -> code review (wayland) - Robert Gonzalez (@uni-dos) -> code review (wayland) - @TheLastVoyager -> code review - @yehoravramenko -> code review (winapi) - @halocupcake -> code review (OpenGL) - @GideonSerf -> documentation - Alexandre Almeida (@M374LX) -> code review (keycodes) - Vũ Xuân Trường (@wanwanvxt) -> code review (winapi) - Lucas (@lightspeedlucas) -> code review (msvc++) - Jeffery Myers (@JeffM2501) -> code review (msvc) - Zeni (@zenitsuyo) -> documentation - TheYahton (@TheYahton) -> documentation - nonexistant_object (@DiarrheaMcgee + Nick Porcino (meshula) - testing, organization, review (MacOS, examples) + @DarekParodia -> code review (X11) (C++) */ #if _MSC_VER @@ -185,74 +179,6 @@ int main() { #endif #endif -#if defined(RGFW_EGL) && !defined(RGFW_OPENGL) - #define RGFW_OPENGL -#endif - -/* these OS macros look better & are standardized */ -/* plus it helps with cross-compiling */ - -#ifdef __EMSCRIPTEN__ - #define RGFW_WASM -#endif - -#if defined(RGFW_X11) && defined(__APPLE__) && !defined(RGFW_CUSTOM_BACKEND) - #define RGFW_MACOS_X11 - #define RGFW_UNIX -#endif - -#if defined(_WIN32) && !defined(RGFW_X11) && !defined(RGFW_UNIX) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) /* (if you're using X11 on windows some how) */ - #define RGFW_WINDOWS -#endif -#if defined(RGFW_WAYLAND) - #define RGFW_DEBUG /* wayland will be in debug mode by default for now */ - #define RGFW_UNIX - #ifdef RGFW_OPENGL - #define RGFW_EGL - #endif - #ifdef RGFW_X11 - #define RGFW_DYNAMIC - #endif -#endif -#if (!defined(RGFW_WAYLAND) && !defined(RGFW_X11)) && (defined(__unix__) || defined(RGFW_MACOS_X11) || defined(RGFW_X11)) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) - #define RGFW_MACOS_X11 - #define RGFW_X11 - #define RGFW_UNIX -#elif defined(__APPLE__) && !defined(RGFW_MACOS_X11) && !defined(RGFW_X11) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) - #define RGFW_MACOS -#endif - -#ifndef RGFW_ASSERT - #include - #define RGFW_ASSERT assert -#endif - -#if !defined(__STDC_VERSION__) - #define RGFW_C89 -#endif - -#if !defined(RGFW_SNPRINTF) && (defined(RGFW_X11) || defined(RGFW_WAYLAND)) - - /* required for X11 errors */ - #include - - #ifdef RGFW_C89 - #include - static int RGFW_c89_snprintf(char *dst, size_t size, const char *format, ...) { - va_list args; - size_t count = 0; - va_start(args, format); - count = (size_t)vsprintf(dst, format, args); - RGFW_ASSERT(count + 1 < size && "Buffer overflow"); - va_end(args); - return (int)count; - } - #define RGFW_SNPRINTF RGFW_c89_snprintf - #else - #define RGFW_SNPRINTF snprintf - #endif /*RGFW_C89*/ -#endif - #ifndef RGFW_USERPTR #define RGFW_USERPTR NULL #endif @@ -265,16 +191,17 @@ int main() { #define RGFW_ROUND(x) (i32)((x) >= 0 ? (x) + 0.5f : (x) - 0.5f) #endif -#ifndef RGFW_MIN - #define RGFW_MIN(x, y) ((x < y) ? x : y) -#endif - #ifndef RGFW_ALLOC #include #define RGFW_ALLOC malloc #define RGFW_FREE free #endif +#ifndef RGFW_ASSERT + #include + #define RGFW_ASSERT assert +#endif + #if !defined(RGFW_MEMCPY) || !defined(RGFW_STRNCMP) || !defined(RGFW_STRNCPY) || !defined(RGFW_MEMSET) #include #endif @@ -306,31 +233,6 @@ int main() { #define RGFW_ATOF(num) atof(num) #endif -#if !defined(RGFW_PRINTF) && ( defined(RGFW_DEBUG) || defined(RGFW_WAYLAND) ) - /* required when using RGFW_DEBUG */ - #include - #define RGFW_PRINTF printf -#endif - -#ifndef RGFW_MAX_PATH - #define RGFW_MAX_PATH 260 /* max length of a path (for drag andn drop) */ -#endif -#ifndef RGFW_MAX_DROPS - #define RGFW_MAX_DROPS 260 /* max items you can drop at once */ -#endif - -#ifndef RGFW_MAX_EVENTS - #define RGFW_MAX_EVENTS 32 -#endif - -#ifndef RGFW_MAX_MONITORS - #define RGFW_MAX_MONITORS 6 -#endif - -#ifndef RGFW_COCOA_FRAME_NAME - #define RGFW_COCOA_FRAME_NAME NULL -#endif - #ifdef RGFW_WIN95 /* for windows 95 testing (not that it really works) */ #define RGFW_NO_MONITOR #define RGFW_NO_PASSTHROUGH @@ -365,11 +267,16 @@ int main() { #endif #endif +#ifndef RGFW_ENUM + #define RGFW_ENUM(type, name) type name; enum +#endif + + #if defined(__cplusplus) && !defined(__EMSCRIPTEN__) extern "C" { #endif -/* makes sure the header file part is only defined once by default */ + /* makes sure the header file part is only defined once by default */ #ifndef RGFW_HEADER #define RGFW_HEADER @@ -400,31 +307,1023 @@ int main() { #define RGFW_INT_DEFINED #endif -typedef ptrdiff_t RGFW_ssize_t; - #ifndef RGFW_BOOL_DEFINED #define RGFW_BOOL_DEFINED typedef u8 RGFW_bool; #endif -#define RGFW_BOOL(x) (RGFW_bool)((x) != 0) /* force a value to be 0 or 1 */ +#define RGFW_BOOL(x) (RGFW_bool)((x) ? RGFW_TRUE : RGFW_FALSE) /* force an value to be 0 or 1 */ #define RGFW_TRUE (RGFW_bool)1 #define RGFW_FALSE (RGFW_bool)0 -#define RGFW_ENUM(type, name) type name; enum -#define RGFW_BIT(x) (1 << (x)) +/* these OS macros look better & are standardized */ +/* plus it helps with cross-compiling */ +#ifdef __EMSCRIPTEN__ + #define RGFW_WASM + + #if !defined(RGFW_NO_API) && !defined(RGFW_WEBGPU) + #define RGFW_OPENGL + #endif + + #ifdef RGFW_EGL + #undef RGFW_EGL + #endif + + #include + #include + + #ifdef RGFW_WEBGPU + #include + #endif +#endif + +#if defined(RGFW_X11) && defined(__APPLE__) && !defined(RGFW_CUSTOM_BACKEND) + #define RGFW_MACOS_X11 + #define RGFW_UNIX + #undef __APPLE__ +#endif + +#if defined(_WIN32) && !defined(RGFW_X11) && !defined(RGFW_UNIX) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) /* (if you're using X11 on windows some how) */ + #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 + + #ifndef RGFW_NO_XINPUT + #ifdef __MINGW32__ /* try to find the right header */ + #include + #else + #include + #endif + #endif +#endif +#if defined(RGFW_WAYLAND) + #define RGFW_DEBUG /* wayland will be in debug mode by default for now */ + #if !defined(RGFW_NO_API) && (!defined(RGFW_BUFFER) || defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) + #define RGFW_EGL + #define RGFW_OPENGL + #include + #endif + + #define RGFW_UNIX + #include +#endif +#if !defined(RGFW_NO_X11) && (defined(__unix__) || defined(RGFW_MACOS_X11) || defined(RGFW_X11)) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) + #define RGFW_MACOS_X11 + #define RGFW_X11 + #define RGFW_UNIX + #include + #include +#elif defined(__APPLE__) && !defined(RGFW_MACOS_X11) && !defined(RGFW_X11) && !defined(RGFW_WASM) && !defined(RGFW_CUSTOM_BACKEND) + #define RGFW_MACOS + #if !defined(RGFW_BUFFER_BGR) + #define RGFW_BUFFER_BGR + #else + #undef RGFW_BUFFER_BGR + #endif +#endif + +#if (defined(RGFW_OPENGL_ES1) || defined(RGFW_OPENGL_ES2) || defined(RGFW_OPENGL_ES3)) && !defined(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 + +#ifdef RGFW_EGL + #include +#elif defined(RGFW_OSMESA) + #ifdef RGFW_WINDOWS + #define OEMRESOURCE + #include + #ifndef GLAPIENTRY + #define GLAPIENTRY APIENTRY + #endif + #ifndef GLAPI + #define GLAPI WINGDIAPI + #endif + #endif + + #ifndef __APPLE__ + #include + #else + #include + #endif +#endif + +#if (defined(RGFW_OPENGL) || defined(RGFW_WEGL)) && defined(_MSC_VER) + #pragma comment(lib, "opengl32") +#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 + +#define RGFW_COCOA_FRAME_NAME NULL + +/*! (unix) Toggle use of wayland. This will be on by default if you use `RGFW_WAYLAND` (if you don't use RGFW_WAYLAND, you don't expose WAYLAND functions) + this is mostly used to allow you to force the use of XWayland +*/ +RGFWDEF void RGFW_useWayland(RGFW_bool wayland); +RGFWDEF RGFW_bool RGFW_usingWayland(void); +/* + regular RGFW stuff +*/ + +#define RGFW_key u8 + +typedef RGFW_ENUM(u8, RGFW_eventType) { + /*! event codes */ + RGFW_eventNone = 0, /*!< no event has been sent */ + RGFW_keyPressed, /* a key has been pressed */ + RGFW_keyReleased, /*!< a key has been released */ + /*! key event note + the code of the key pressed is stored in + RGFW_event.key + !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! + + while a string version is stored in + RGFW_event.KeyString + + RGFW_event.keyMod holds the current keyMod + this means if CapsLock, NumLock are active or not + */ + RGFW_mouseButtonPressed, /*!< a mouse button has been pressed (left,middle,right) */ + RGFW_mouseButtonReleased, /*!< a mouse button has been released (left,middle,right) */ + RGFW_mousePosChanged, /*!< the position of the mouse has been changed */ + /*! mouse event note + the x and y of the mouse can be found in the vector, RGFW_event.point + + RGFW_event.button holds which mouse button was pressed + */ + RGFW_gamepadConnected, /*!< a gamepad was connected */ + RGFW_gamepadDisconnected, /*!< a gamepad was disconnected */ + RGFW_gamepadButtonPressed, /*!< a gamepad button was pressed */ + RGFW_gamepadButtonReleased, /*!< a gamepad button was released */ + RGFW_gamepadAxisMove, /*!< an axis of a gamepad was moved */ + /*! gamepad event note + RGFW_event.gamepad holds which gamepad was altered, if any + RGFW_event.button holds which gamepad button was pressed + + RGFW_event.axis holds the data of all the axises + RGFW_event.axisesCount says how many axises there are + */ + RGFW_windowMoved, /*!< the window was moved (by the user) */ + RGFW_windowResized, /*!< the window was resized (by the user), [on WASM this means the browser was resized] */ + RGFW_focusIn, /*!< window is in focus now */ + RGFW_focusOut, /*!< window is out of focus now */ + RGFW_mouseEnter, /* mouse entered the window */ + RGFW_mouseLeave, /* mouse left the window */ + RGFW_windowRefresh, /* The window content needs to be refreshed */ + + /* attribs change event note + The event data is sent straight to the window structure + with win->r.x, win->r.y, win->r.w and win->r.h + */ + RGFW_quit, /*!< the user clicked the quit button */ + RGFW_DND, /*!< a file has been dropped into the window */ + RGFW_DNDInit, /*!< the start of a dnd event, when the place where the file drop is known */ + /* dnd data note + The x and y coords of the drop are stored in the vector RGFW_event.point + + RGFW_event.droppedFilesCount holds how many files were dropped + + This is also the size of the array which stores all the dropped file string, + RGFW_event.droppedFiles + */ + RGFW_windowMaximized, /*!< the window was maximized */ + RGFW_windowMinimized, /*!< the window was minimized */ + RGFW_windowRestored, /*!< the window was restored */ + RGFW_scaleUpdated /*!< content scale factor changed */ +}; + +/*! mouse button codes (RGFW_event.button) */ +typedef RGFW_ENUM(u8, RGFW_mouseButton) { + RGFW_mouseLeft = 0, /*!< left mouse button is pressed */ + RGFW_mouseMiddle, /*!< mouse-wheel-button is pressed */ + RGFW_mouseRight, /*!< right mouse button is pressed */ + RGFW_mouseScrollUp, /*!< mouse wheel is scrolling up */ + RGFW_mouseScrollDown, /*!< mouse wheel is scrolling down */ + RGFW_mouseMisc1, RGFW_mouseMisc2, RGFW_mouseMisc3, RGFW_mouseMisc4, RGFW_mouseMisc5, + RGFW_mouseFinal +}; + +#ifndef RGFW_MAX_PATH +#define RGFW_MAX_PATH 260 /* max length of a path (for dnd) */ +#endif +#ifndef RGFW_MAX_DROPS +#define RGFW_MAX_DROPS 260 /* max items you can drop at once */ +#endif + +#define RGFW_BIT(x) (1 << x) + +/* for RGFW_event.lockstate */ +typedef RGFW_ENUM(u8, RGFW_keymod) { + RGFW_modCapsLock = RGFW_BIT(0), + RGFW_modNumLock = RGFW_BIT(1), + RGFW_modControl = RGFW_BIT(2), + RGFW_modAlt = RGFW_BIT(3), + RGFW_modShift = RGFW_BIT(4), + RGFW_modSuper = RGFW_BIT(5), + RGFW_modScrollLock = RGFW_BIT(6) +}; + +/*! gamepad button codes (based on xbox/playstation), you may need to change these values per controller */ +typedef RGFW_ENUM(u8, RGFW_gamepadCodes) { + RGFW_gamepadNone = 0, /*!< or PS X button */ + RGFW_gamepadA, /*!< or PS X button */ + RGFW_gamepadB, /*!< or PS circle button */ + RGFW_gamepadY, /*!< or PS triangle button */ + RGFW_gamepadX, /*!< or PS square button */ + RGFW_gamepadStart, /*!< start button */ + RGFW_gamepadSelect, /*!< select button */ + RGFW_gamepadHome, /*!< home button */ + RGFW_gamepadUp, /*!< dpad up */ + RGFW_gamepadDown, /*!< dpad down */ + RGFW_gamepadLeft, /*!< dpad left */ + RGFW_gamepadRight, /*!< dpad right */ + RGFW_gamepadL1, /*!< left bump */ + RGFW_gamepadL2, /*!< left trigger */ + RGFW_gamepadR1, /*!< right bumper */ + RGFW_gamepadR2, /*!< right trigger */ + RGFW_gamepadL3, /* left thumb stick */ + RGFW_gamepadR3, /*!< right thumb stick */ + RGFW_gamepadFinal +}; + +/*! basic vector type, if there's not already a point/vector type of choice */ +#ifndef RGFW_point + typedef struct RGFW_point { i32 x, y; } RGFW_point; +#endif + +/*! basic rect type, if there's not already a rect type of choice */ +#ifndef RGFW_rect + typedef struct RGFW_rect { i32 x, y, w, h; } RGFW_rect; +#endif + +/*! basic area type, if there's not already a area type of choice */ +#ifndef RGFW_area + typedef struct RGFW_area { u32 w, h; } RGFW_area; +#endif + +#if defined(__cplusplus) && !defined(__APPLE__) +#define RGFW_POINT(x, y) {(i32)x, (i32)y} +#define RGFW_RECT(x, y, w, h) {(i32)x, (i32)y, (i32)w, (i32)h} +#define RGFW_AREA(w, h) {(u32)w, (u32)h} +#else +#define RGFW_POINT(x, y) (RGFW_point){(i32)(x), (i32)(y)} +#define RGFW_RECT(x, y, w, h) (RGFW_rect){(i32)(x), (i32)(y), (i32)(w), (i32)(h)} +#define RGFW_AREA(w, h) (RGFW_area){(u32)(w), (u32)(h)} +#endif + +#ifndef RGFW_NO_MONITOR + /* monitor mode data | can be changed by the user (with functions)*/ + typedef struct RGFW_monitorMode { + RGFW_area area; /*!< monitor workarea size */ + u32 refreshRate; /*!< monitor refresh rate */ + u8 red, blue, green; + } RGFW_monitorMode; + + /*! structure for monitor data */ + typedef struct RGFW_monitor { + i32 x, y; /*!< x - y of the monitor workarea */ + char name[128]; /*!< monitor name */ + float scaleX, scaleY; /*!< monitor content scale */ + float pixelRatio; /*!< pixel ratio for monitor (1.0 for regular, 2.0 for hiDPI) */ + float physW, physH; /*!< monitor physical size in inches */ + + RGFW_monitorMode mode; + } RGFW_monitor; + + /*! get an array of all the monitors (max 6) */ + RGFWDEF RGFW_monitor* RGFW_getMonitors(size_t* len); + /*! get the primary monitor */ + RGFWDEF RGFW_monitor RGFW_getPrimaryMonitor(void); + + typedef RGFW_ENUM(u8, RGFW_modeRequest) { + RGFW_monitorScale = RGFW_BIT(0), /*!< scale the monitor size */ + RGFW_monitorRefresh = RGFW_BIT(1), /*!< change the refresh rate */ + RGFW_monitorRGB = RGFW_BIT(2), /*!< change the monitor RGB bits size */ + RGFW_monitorAll = RGFW_monitorScale | RGFW_monitorRefresh | RGFW_monitorRGB + }; + + /*! request a specific mode */ + RGFWDEF RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request); + /*! check if 2 monitor modes are the same */ + RGFWDEF RGFW_bool RGFW_monitorModeCompare(RGFW_monitorMode mon, RGFW_monitorMode mon2, RGFW_modeRequest request); +#endif + +/* RGFW mouse loading */ +typedef void RGFW_mouse; + +/*!< loads mouse icon from bitmap (similar to RGFW_window_setIcon). Icon NOT resized by default */ +RGFWDEF RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels); +/*!< frees RGFW_mouse data */ +RGFWDEF void RGFW_freeMouse(RGFW_mouse* mouse); + +/* NOTE: some parts of the data can represent different things based on the event (read comments in RGFW_event struct) */ +/*! Event structure for checking/getting events */ +typedef struct RGFW_event { + RGFW_eventType type; /*!< which event has been sent?*/ + RGFW_point point; /*!< mouse x, y of event (or drop point) */ + RGFW_point vector; /*!< raw mouse movement */ + float scaleX, scaleY; /*!< DPI scaling */ + + RGFW_key key; /*!< the physical key of the event, refers to where key is physically !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! */ + u8 keyChar; /*!< mapped key char of the event */ + + RGFW_bool repeat; /*!< key press event repeated (the key is being held) */ + RGFW_keymod keyMod; + + u8 button; /* !< which mouse (or gamepad) button was pressed */ + double scroll; /*!< the raw mouse scroll value */ + + u16 gamepad; /*! which gamepad this event applies to (if applicable to any) */ + u8 axisesCount; /*!< number of axises */ + + u8 whichAxis; /* which axis was effected */ + RGFW_point axis[4]; /*!< x, y of axises (-100 to 100) */ + + /*! drag and drop data */ + /* 260 max paths with a max length of 260 */ + char** droppedFiles; /*!< dropped files */ + size_t droppedFilesCount; /*!< house many files were dropped */ + + void* _win; /*!< the window this event applies too (for event queue events) */ +} RGFW_event; + +/*! source data for the window (used by the APIs) */ +#ifdef RGFW_WINDOWS +typedef struct RGFW_window_src { + HWND window; /*!< source window */ + HDC hdc; /*!< source HDC */ + u32 hOffset; /*!< height offset for window */ + HICON hIconSmall, hIconBig; /*!< source window icons */ + #if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) + HGLRC 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) + HDC hdcMem; + HBITMAP bitmap; + u8* bitmapBits; + #endif + RGFW_area maxSize, minSize, aspectRatio; /*!< for setting max/min resize (RGFW_WINDOWS) */ +} RGFW_window_src; +#elif defined(RGFW_UNIX) +typedef struct RGFW_window_src { +#if 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 */ + GLXFBConfig bestFbc; + #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; + #endif + GC gc; + XVisualInfo visual; + #ifdef RGFW_ADVANCED_SMOOTH_RESIZE + i64 counter_value; + XID counter; + #endif + RGFW_rect r; +#endif /* RGFW_X11 */ +#if defined(RGFW_WAYLAND) + struct wl_display* wl_display; + struct wl_surface* surface; + struct wl_buffer* wl_buffer; + struct wl_keyboard* keyboard; + + struct wl_compositor* compositor; + struct xdg_surface* xdg_surface; + struct xdg_toplevel* xdg_toplevel; + struct zxdg_toplevel_decoration_v1* decoration; + struct xdg_wm_base* xdg_wm_base; + struct wl_shm* shm; + struct wl_seat *seat; + u8* buffer; + #if defined(RGFW_EGL) + struct wl_egl_window* eglWindow; + #endif + #if defined(RGFW_EGL) && !defined(RGFW_X11) + EGLSurface EGL_surface; + EGLDisplay EGL_display; + EGLContext EGL_context; + #elif defined(RGFW_OSMESA) && !defined(RGFW_X11) + OSMesaContext ctx; + #endif +#endif /* RGFW_WAYLAND */ +} RGFW_window_src; +#endif /* RGFW_UNIX */ +#if defined(RGFW_MACOS) +typedef struct RGFW_window_src { + void* window; +#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 */ + void* mouse; +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) +#endif +} RGFW_window_src; +#elif defined(RGFW_WASM) +typedef struct RGFW_window_src { + #if defined(RGFW_WEBGPU) + WGPUInstance ctx; + WGPUDevice device; + WGPUQueue queue; + #elif defined(RGFW_OSMESA) + OSMesaContext ctx; + #else + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx; + #endif +} RGFW_window_src; +#endif + +/*! Optional arguments for making a windows */ +typedef RGFW_ENUM(u32, RGFW_windowFlags) { + RGFW_windowNoInitAPI = RGFW_BIT(0), /* do NOT init an API (including the software rendering buffer) (mostly for bindings. you can also use `#define RGFW_NO_API`) */ + RGFW_windowNoBorder = RGFW_BIT(1), /*!< the window doesn't have a border */ + RGFW_windowNoResize = RGFW_BIT(2), /*!< the window cannot be resized by the user */ + RGFW_windowAllowDND = RGFW_BIT(3), /*!< the window supports drag and drop */ + RGFW_windowHideMouse = RGFW_BIT(4), /*! the window should hide the mouse (can be toggled later on using `RGFW_window_mouseShow`) */ + RGFW_windowFullscreen = RGFW_BIT(5), /*!< the window is fullscreen by default */ + RGFW_windowTransparent = RGFW_BIT(6), /*!< the window is transparent (only properly works on X11 and MacOS, although it's meant for for windows) */ + RGFW_windowCenter = RGFW_BIT(7), /*! center the window on the screen */ + RGFW_windowOpenglSoftware = RGFW_BIT(8), /*! use OpenGL software rendering */ + RGFW_windowCocoaCHDirToRes = RGFW_BIT(9), /*! (cocoa only), change directory to resource folder */ + RGFW_windowScaleToMonitor = RGFW_BIT(10), /*! scale the window to the screen */ + RGFW_windowHide = RGFW_BIT(11), /*! the window is hidden */ + RGFW_windowMaximize = RGFW_BIT(12), + RGFW_windowCenterCursor = RGFW_BIT(13), + RGFW_windowFloating = RGFW_BIT(14), /*!< create a floating window */ + RGFW_windowFreeOnClose = RGFW_BIT(15), /*!< free (RGFW_window_close) the RGFW_window struct when the window is closed (by the end user) */ + RGFW_windowFocusOnShow = RGFW_BIT(16), /*!< focus the window when it's shown */ + RGFW_windowMinimize = RGFW_BIT(17), /*!< focus the window when it's shown */ + RGFW_windowFocus = RGFW_BIT(18), /*!< if the window is in focus */ + RGFW_windowedFullscreen = RGFW_windowNoBorder | RGFW_windowMaximize +}; + +typedef struct RGFW_window { + RGFW_window_src src; /*!< src window data */ + +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + u8* buffer; /*!< buffer for non-GPU systems (OSMesa, basic software rendering) */ + /* when rendering using RGFW_BUFFER, the buffer is in the RGBA format */ + RGFW_area bufferSize; +#endif + void* userPtr; /* ptr for usr data */ + + RGFW_event event; /*!< current event */ + + RGFW_rect r; /*!< the x, y, w and h of the struct */ + + /*! which key RGFW_window_shouldClose checks. Settting this to RGFW_keyNULL disables the feature. */ + RGFW_key exitKey; + RGFW_point _lastMousePoint; /*!< last cusor point (for raw mouse data) */ + + u32 _flags; /*!< windows flags (for RGFW to check) */ + RGFW_rect _oldRect; /*!< rect before fullscreen */ +} RGFW_window; /*!< window structure for managing the window */ + +#if defined(RGFW_X11) || defined(RGFW_MACOS) + typedef u64 RGFW_thread; /*!< thread type unix */ +#else + typedef void* RGFW_thread; /*!< thread type for windows */ +#endif + +/*! scale monitor to window size */ +RGFWDEF RGFW_bool RGFW_monitor_scaleToWindow(RGFW_monitor mon, RGFW_window* win); + +/** * @defgroup Window_management +* @{ */ + + +/*! + * the class name for X11 and WinAPI. apps with the same class will be grouped by the WM + * by default the class name will == the root window's name +*/ +RGFWDEF void RGFW_setClassName(const char* name); +RGFWDEF void RGFW_setXInstName(const char* name); /*!< X11 instance name (window name will by used by default) */ + +/*! (cocoa only) change directory to resource folder */ +RGFWDEF void RGFW_moveToMacOSResourceDir(void); + +/* NOTE: (windows) if the executable has an icon resource named RGFW_ICON, it will be set as the initial icon for the window */ + +RGFWDEF RGFW_window* RGFW_createWindow( + const char* name, /* name of the window */ + RGFW_rect rect, /* rect of window */ + RGFW_windowFlags flags /* extra arguments ((u32)0 means no flags used)*/ +); /*!< function to create a window and struct */ + +RGFWDEF RGFW_window* RGFW_createWindowPtr( + const char* name, /* name of the window */ + RGFW_rect rect, /* rect of window */ + RGFW_windowFlags flags, /* extra arguments (NULL / (u32)0 means no flags used) */ + RGFW_window* win /* ptr to the window struct you want to use */ +); /*!< function to create a window (without allocating a window struct) */ + +RGFWDEF void RGFW_window_initBuffer(RGFW_window* win); +RGFWDEF void RGFW_window_initBufferSize(RGFW_window* win, RGFW_area area); +RGFWDEF void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area); + +/*! set the window flags (will undo flags if they don't match the old ones) */ +RGFWDEF void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags); + +/*! 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 + + 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` +*/ + +RGFWDEF RGFW_event* RGFW_window_checkEvent(RGFW_window* win); /*!< check current event (returns a pointer to win->event or NULL if there is no event)*/ + +/*! + for RGFW_window_eventWait and RGFW_window_checkEvents + waitMS -> Allows the function to keep checking for events even after `RGFW_window_checkEvent == NULL` + if waitMS == 0, the loop will not wait for events + if waitMS > 0, the loop will wait that many miliseconds after there are no more events until it returns + if waitMS == -1 or waitMS == the max size of an unsigned 32-bit int, the loop will not return until it gets another event +*/ +typedef RGFW_ENUM(i32, RGFW_eventWait) { + RGFW_eventNoWait = 0, + RGFW_eventWaitNext = -1 +}; + +/*! sleep until RGFW gets an event or the timer ends (defined by OS) */ +RGFWDEF void RGFW_window_eventWait(RGFW_window* win, i32 waitMS); + +/*! + check all the events until there are none left. + This should only be used if you're using callbacks only +*/ +RGFWDEF void RGFW_window_checkEvents(RGFW_window* win, i32 waitMS); + +/*! + tell RGFW_window_eventWait to stop waiting (to be ran from another thread) +*/ +RGFWDEF void RGFW_stopCheckEvents(void); + +/*! window managment functions */ +RGFWDEF void RGFW_window_close(RGFW_window* win); /*!< close the window and free leftover data */ + +/*! move a window to a given point */ +RGFWDEF void RGFW_window_move(RGFW_window* win, + RGFW_point v /*!< new pos */ +); + +#ifndef RGFW_NO_MONITOR + /*! move window to a specific monitor */ + RGFWDEF void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor m /* monitor */); +#endif + +/*! resize window to a current size/area */ +RGFWDEF void RGFW_window_resize(RGFW_window* win, /*!< source window */ + RGFW_area a /*!< new size */ +); + +/*! set window aspect ratio */ +RGFWDEF void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a); +/*! set the minimum dimensions of a window */ +RGFWDEF void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a); +/*! set the maximum dimensions of a window */ +RGFWDEF void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a); + +RGFWDEF void RGFW_window_focus(RGFW_window* win); /*!< sets the focus to this window */ +RGFWDEF RGFW_bool RGFW_window_isInFocus(RGFW_window* win); /*!< checks the focus to this window */ +RGFWDEF void RGFW_window_raise(RGFW_window* win); /*!< raise the window (to the top) */ +RGFWDEF void RGFW_window_maximize(RGFW_window* win); /*!< maximize the window */ +RGFWDEF void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen); /*!< turn fullscreen on / off for a window */ +RGFWDEF void RGFW_window_center(RGFW_window* win); /*!< center the window */ +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_setFloating(RGFW_window* win, RGFW_bool floating); /*!< make the window a floating window */ +RGFWDEF void RGFW_window_setOpacity(RGFW_window* win, u8 opacity); /*!< sets the opacity of a window */ + +RGFWDEF RGFW_bool RGFW_window_opengl_isSoftware(RGFW_window* win); + +/*! if the window should have a border or not (borderless) based on bool value of `border` */ +RGFWDEF void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border); +RGFWDEF RGFW_bool RGFW_window_borderless(RGFW_window* win); + +/*! turn on / off dnd (RGFW_windowAllowDND stil must be passed to the window)*/ +RGFWDEF void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow); +/*! check if DND is allowed */ +RGFWDEF RGFW_bool RGFW_window_allowsDND(RGFW_window* win); + + +#ifndef RGFW_NO_PASSTHROUGH + /*! turn on / off mouse passthrough */ + RGFWDEF void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough); +#endif + +/*! rename window to a given string */ +RGFWDEF void RGFW_window_setName(RGFW_window* win, + const char* name +); + +RGFWDEF RGFW_bool 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 MAY be resized by default, set both the taskbar and window icon */ + +typedef RGFW_ENUM(u8, RGFW_icon) { + RGFW_iconTaskbar = RGFW_BIT(0), + RGFW_iconWindow = RGFW_BIT(1), + RGFW_iconBoth = RGFW_iconTaskbar | RGFW_iconWindow +}; +RGFWDEF RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* icon, RGFW_area a, i32 channels, u8 type); + +/*!< sets mouse to RGFW_mouse icon (loaded from a bitmap struct) */ +RGFWDEF void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse); + +/*!< 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 RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse); + +RGFWDEF RGFW_bool 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 becomes raw mouse movement data + + this is useful for a 3D camera +*/ +RGFWDEF void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area); +/*! if the mouse is held by RGFW */ +RGFWDEF RGFW_bool RGFW_window_mouseHeld(RGFW_window* win); +/*! stop holding the mouse and let it move freely */ +RGFWDEF void RGFW_window_mouseUnhold(RGFW_window* win); + +/*! hide the window */ +RGFWDEF void RGFW_window_hide(RGFW_window* win); +/*! show the window */ +RGFWDEF void RGFW_window_show(RGFW_window* win); + +/* + makes it so `RGFW_window_shouldClose` returns true or overrides a window close + by modifying window flags +*/ +RGFWDEF void RGFW_window_setShouldClose(RGFW_window* win, RGFW_bool shouldClose); + +/*! where the mouse is on the screen */ +RGFWDEF RGFW_point RGFW_getGlobalMousePoint(void); + +/*! 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, RGFW_bool show); +/*! if the mouse is hidden */ +RGFWDEF RGFW_bool RGFW_window_mouseHidden(RGFW_window* win); +/*! move the mouse to a given point */ +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 RGFW_bool RGFW_window_shouldClose(RGFW_window* win); +/*! if the window is fullscreen */ +RGFWDEF RGFW_bool RGFW_window_isFullscreen(RGFW_window* win); +/*! if the window is hidden */ +RGFWDEF RGFW_bool RGFW_window_isHidden(RGFW_window* win); +/*! if the window is minimized */ +RGFWDEF RGFW_bool RGFW_window_isMinimized(RGFW_window* win); +/*! if the window is maximized */ +RGFWDEF RGFW_bool RGFW_window_isMaximized(RGFW_window* win); +/*! if the window is floating */ +RGFWDEF RGFW_bool RGFW_window_isFloating(RGFW_window* win); +/** @} */ + +/** * @defgroup Monitor +* @{ */ + +#ifndef RGFW_NO_MONITOR +/* + scale the window to the monitor. + This is run by default if the user uses the arg `RGFW_scaleToMonitor` 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 + +/** @} */ + +/** * @defgroup Input +* @{ */ + +/*! 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 RGFW_bool RGFW_isPressed(RGFW_window* win, RGFW_key key); /*!< if key is pressed (key code)*/ + +RGFWDEF RGFW_bool RGFW_wasPressed(RGFW_window* win, RGFW_key key); /*!< if key was pressed (checks previous state only) (key code) */ + +RGFWDEF RGFW_bool RGFW_isHeld(RGFW_window* win, RGFW_key key); /*!< if key is held (key code) */ +RGFWDEF RGFW_bool RGFW_isReleased(RGFW_window* win, RGFW_key key); /*!< if key is released (key code) */ + +/* if a key is pressed and then released, pretty much the same as RGFW_isReleased */ +RGFWDEF RGFW_bool RGFW_isClicked(RGFW_window* win, RGFW_key key /*!< key code */); + +/*! if a mouse button is pressed */ +RGFWDEF RGFW_bool RGFW_isMousePressed(RGFW_window* win, RGFW_mouseButton button /*!< mouse button code */ ); +/*! if a mouse button is held */ +RGFWDEF RGFW_bool RGFW_isMouseHeld(RGFW_window* win, RGFW_mouseButton button /*!< mouse button code */ ); +/*! if a mouse button was released */ +RGFWDEF RGFW_bool RGFW_isMouseReleased(RGFW_window* win, RGFW_mouseButton button /*!< mouse button code */ ); +/*! if a mouse button was pressed (checks previous state only) */ +RGFWDEF RGFW_bool RGFW_wasMousePressed(RGFW_window* win, RGFW_mouseButton button /*!< mouse button code */ ); +/** @} */ + +/** * @defgroup Clipboard +* @{ */ +typedef ptrdiff_t RGFW_ssize_t; + +RGFWDEF const char* RGFW_readClipboard(size_t* size); /*!< read clipboard data */ +/*! read clipboard data or send a NULL str to just get the length of the clipboard data */ +RGFWDEF RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity); +RGFWDEF void RGFW_writeClipboard(const char* text, u32 textLen); /*!< write text to the clipboard */ +/** @} */ + + + +/** * @defgroup error handling +* @{ */ +typedef RGFW_ENUM(u8, RGFW_debugType) { + RGFW_typeError = 0, RGFW_typeWarning, RGFW_typeInfo +}; + +typedef RGFW_ENUM(u8, RGFW_errorCode) { + RGFW_noError = 0, /*!< no error */ + RGFW_errOpenglContext, RGFW_errEGLContext, /*!< error with the OpenGL context */ + RGFW_errWayland, + RGFW_errDirectXContext, + RGFW_errIOKit, + RGFW_errClipboard, + RGFW_errFailedFuncLoad, + RGFW_errBuffer, + RGFW_infoMonitor, RGFW_infoWindow, RGFW_infoBuffer, RGFW_infoGlobal, RGFW_infoOpenGL, + RGFW_warningWayland, RGFW_warningOpenGL +}; + +typedef struct RGFW_debugContext { RGFW_window* win; RGFW_monitor* monitor; u32 srcError; } RGFW_debugContext; + +#if defined(__cplusplus) && !defined(__APPLE__) +#define RGFW_DEBUG_CTX(win, err) {win, NULL, err} +#define RGFW_DEBUG_CTX_MON(monitor) {_RGFW.root, &monitor, 0} +#else +#define RGFW_DEBUG_CTX(win, err) (RGFW_debugContext){win, NULL, err} +#define RGFW_DEBUG_CTX_MON(monitor) (RGFW_debugContext){_RGFW.root, &monitor, 0} +#endif + +typedef void (* RGFW_debugfunc)(RGFW_debugType type, RGFW_errorCode err, RGFW_debugContext ctx, const char* msg); +RGFWDEF RGFW_debugfunc RGFW_setDebugCallback(RGFW_debugfunc func); +RGFWDEF void RGFW_sendDebugInfo(RGFW_debugType type, RGFW_errorCode err, RGFW_debugContext ctx, const char* msg); +/** @} */ + +/** + + + event callbacks. + These are completely optional, so you can use the normal + RGFW_checkEvent() method if you prefer that + +* @defgroup Callbacks +* @{ +*/ + +/*! RGFW_windowMoved, the window and its new rect value */ +typedef void (* RGFW_windowMovedfunc)(RGFW_window* win, RGFW_rect r); +/*! RGFW_windowResized, the window and its new rect value */ +typedef void (* RGFW_windowResizedfunc)(RGFW_window* win, RGFW_rect r); +/*! RGFW_windowRestored, the window and its new rect value */ +typedef void (* RGFW_windowRestoredfunc)(RGFW_window* win, RGFW_rect r); +/*! RGFW_windowMaximized, the window and its new rect value */ +typedef void (* RGFW_windowMaximizedfunc)(RGFW_window* win, RGFW_rect r); +/*! RGFW_windowMinimized, the window and its new rect value */ +typedef void (* RGFW_windowMinimizedfunc)(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 in focus */ +typedef void (* RGFW_focusfunc)(RGFW_window* win, RGFW_bool 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, RGFW_bool 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_point vector); +/*! RGFW_DNDInit, 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 mapped key, the physical key, the string version, the state of the mod keys, if it was a press (else it's a release) */ +typedef void (* RGFW_keyfunc)(RGFW_window* win, u8 key, u8 keyChar, RGFW_keymod keyMod, RGFW_bool 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, RGFW_mouseButton button, double scroll, RGFW_bool pressed); +/*! RGFW_gamepadButtonPressed, 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_gamepadButtonfunc)(RGFW_window* win, u16 gamepad, u8 button, RGFW_bool pressed); +/*! RGFW_gamepadAxisMove, the window that got the event, the gamepad in question, the axis values and the axis count */ +typedef void (* RGFW_gamepadAxisfunc)(RGFW_window* win, u16 gamepad, RGFW_point axis[2], u8 axisesCount, u8 whichAxis); +/*! RGFW_gamepadConnected / RGFW_gamepadDisconnected, the window that got the event, the gamepad in question, if the controller was connected (else it was disconnected) */ +typedef void (* RGFW_gamepadfunc)(RGFW_window* win, u16 gamepad, RGFW_bool connected); +/*! RGFW_dnd, the window that had the drop, the drop data and the number of files dropped */ +typedef void (* RGFW_dndfunc)(RGFW_window* win, char** droppedFiles, size_t droppedFilesCount); +/*! RGFW_scaleUpdated, the window the event was sent to, content scaleX, content scaleY */ +typedef void (* RGFW_scaleUpdatedfunc)(RGFW_window* win, float scaleX, float scaleY); + +/*! set callback for a window move event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_windowMovedfunc RGFW_setWindowMovedCallback(RGFW_windowMovedfunc func); +/*! set callback for a window resize event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_windowResizedfunc RGFW_setWindowResizedCallback(RGFW_windowResizedfunc func); +/*! set callback for a window quit event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_windowQuitfunc RGFW_setWindowQuitCallback(RGFW_windowQuitfunc func); +/*! set callback for a mouse move event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_mousePosfunc RGFW_setMousePosCallback(RGFW_mousePosfunc func); +/*! set callback for a window refresh event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_windowRefreshfunc RGFW_setWindowRefreshCallback(RGFW_windowRefreshfunc func); +/*! set callback for a window focus change event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_focusfunc RGFW_setFocusCallback(RGFW_focusfunc func); +/*! set callback for a mouse notify event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_mouseNotifyfunc RGFW_setMouseNotifyCallback(RGFW_mouseNotifyfunc func); +/*! set callback for a drop event event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_dndfunc RGFW_setDndCallback(RGFW_dndfunc func); +/*! set callback for a start of a drop event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_dndInitfunc RGFW_setDndInitCallback(RGFW_dndInitfunc func); +/*! set callback for a key (press / release) event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_keyfunc RGFW_setKeyCallback(RGFW_keyfunc func); +/*! set callback for a mouse button (press / release) event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_mouseButtonfunc RGFW_setMouseButtonCallback(RGFW_mouseButtonfunc func); +/*! set callback for a controller button (press / release) event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_gamepadButtonfunc RGFW_setGamepadButtonCallback(RGFW_gamepadButtonfunc func); +/*! set callback for a gamepad axis move event. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_gamepadAxisfunc RGFW_setGamepadAxisCallback(RGFW_gamepadAxisfunc func); +/*! set callback for when a controller is connected or disconnected. Returns the previous callback function (if it was set) */ +RGFWDEF RGFW_gamepadfunc RGFW_setGamepadCallback(RGFW_gamepadfunc func); +/*! set call back for when window is maximized. Returns the previous callback function (if it was set) */ +RGFWDEF RGFW_windowResizedfunc RGFW_setWindowMaximizedCallback(RGFW_windowResizedfunc func); +/*! set call back for when window is minimized. Returns the previous callback function (if it was set) */ +RGFWDEF RGFW_windowResizedfunc RGFW_setWindowMinimizedCallback(RGFW_windowResizedfunc func); +/*! set call back for when window is restored. Returns the previous callback function (if it was set) */ +RGFWDEF RGFW_windowResizedfunc RGFW_setWindowRestoredCallback(RGFW_windowResizedfunc func); +/*! set callback for when the DPI changes. Returns previous callback function (if it was set) */ +RGFWDEF RGFW_scaleUpdatedfunc RGFW_setScaleUpdatedCallback(RGFW_scaleUpdatedfunc func); +/** @} */ + +/** * @defgroup Threads +* @{ */ + +#ifndef RGFW_NO_THREADS +/*! threading functions */ + +/*! NOTE! (for X11/linux) : if you define a window in a thread, it must be run after the original thread's window is created or else there will be a memory error */ +/* + I'd suggest you use sili's threading functions instead + if you're going to use sili + which is a good idea generally +*/ + +#if defined(__unix__) || defined(__APPLE__) || defined(RGFW_WASM) || defined(RGFW_CUSTOM_BACKEND) + typedef void* (* RGFW_threadFunc_ptr)(void*); +#else + typedef DWORD (__stdcall *RGFW_threadFunc_ptr) (LPVOID lpThreadParameter); +#endif + +RGFWDEF RGFW_thread RGFW_createThread(RGFW_threadFunc_ptr ptr, void* args); /*!< create a thread */ +RGFWDEF void RGFW_cancelThread(RGFW_thread thread); /*!< cancels a thread */ +RGFWDEF void RGFW_joinThread(RGFW_thread thread); /*!< join thread to current thread */ +RGFWDEF void RGFW_setThreadPriority(RGFW_thread thread, u8 priority); /*!< sets the priority priority */ +#endif + +/** @} */ + +/** * @defgroup gamepad +* @{ */ + +typedef RGFW_ENUM(u8, RGFW_gamepadType) { + RGFW_gamepadMicrosoft = 0, RGFW_gamepadSony, RGFW_gamepadNintendo, RGFW_gamepadLogitech, RGFW_gamepadUnknown +}; + +/*! gamepad count starts at 0*/ +RGFWDEF u32 RGFW_isPressedGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button); +RGFWDEF u32 RGFW_isReleasedGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button); +RGFWDEF u32 RGFW_isHeldGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button); +RGFWDEF u32 RGFW_wasPressedGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button); +RGFWDEF RGFW_point RGFW_getGamepadAxis(RGFW_window* win, u16 controller, u16 whichAxis); +RGFWDEF const char* RGFW_getGamepadName(RGFW_window* win, u16 controller); +RGFWDEF size_t RGFW_getGamepadCount(RGFW_window* win); +RGFWDEF RGFW_gamepadType RGFW_getGamepadType(RGFW_window* win, u16 controller); + +/** @} */ + +/** * @defgroup graphics_API +* @{ */ + +/*!< make the window the current opengl drawing context + + NOTE: + if you want to switch the graphics context's thread, + you have to run RGFW_window_makeCurrent(NULL); on the old thread + then RGFW_window_makeCurrent(valid_window) on the new thread +*/ +RGFWDEF void RGFW_window_makeCurrent(RGFW_window* win); + +/*! get current RGFW window graphics context */ +RGFWDEF RGFW_window* RGFW_getCurrent(void); + +/* 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); +/*!< render the software rendering buffer (this is called by RGFW_window_swapInterval) */ +RGFWDEF void RGFW_window_swapBuffers_software(RGFW_window* win); + +typedef void (*RGFW_proc)(void); /* function pointer equivalent of void* */ + +/*! native API functions */ +#if defined(RGFW_OPENGL) || defined(RGFW_EGL) +/*!< create an opengl context for the RGFW window, run by createWindow by default (unless the RGFW_windowNoInitAPI is included) */ +RGFWDEF void RGFW_window_initOpenGL(RGFW_window* win); +/*!< called by `RGFW_window_close` by default (unless the RGFW_windowNoInitAPI is set) */ +RGFWDEF void RGFW_window_freeOpenGL(RGFW_window* win); + +/*! OpenGL init hints */ +typedef RGFW_ENUM(u8, RGFW_glHints) { + RGFW_glStencil = 0, /*!< set stencil buffer bit size (8 by default) */ + RGFW_glSamples, /*!< set number of sampiling buffers (4 by default) */ + RGFW_glStereo, /*!< use GL_STEREO (GL_FALSE by default) */ + RGFW_glAuxBuffers, /*!< number of aux buffers (0 by default) */ + RGFW_glDoubleBuffer, /*!< request double buffering */ + RGFW_glRed, RGFW_glGreen, RGFW_glBlue, RGFW_glAlpha, /*!< set RGBA bit sizes */ + RGFW_glDepth, + RGFW_glAccumRed, RGFW_glAccumGreen, RGFW_glAccumBlue,RGFW_glAccumAlpha, /*!< set accumulated RGBA bit sizes */ + RGFW_glSRGB, /*!< request sRGA */ + RGFW_glRobustness, /*!< request a robust context */ + RGFW_glDebug, /*!< request opengl debugging */ + RGFW_glNoError, /*!< request no opengl errors */ + RGFW_glReleaseBehavior, + RGFW_glProfile, + RGFW_glMajor, RGFW_glMinor, + RGFW_glFinalHint = 32, /*!< the final hint (not for setting) */ + RGFW_releaseFlush = 0, RGFW_glReleaseNone, /* RGFW_glReleaseBehavior options */ + RGFW_glCore = 0, RGFW_glCompatibility /*!< RGFW_glProfile options */ +}; +RGFWDEF void RGFW_setGLHint(RGFW_glHints hint, i32 value); +RGFWDEF RGFW_bool RGFW_extensionSupported(const char* extension, size_t len); /*!< check if whether the specified API extension is supported by the current OpenGL or OpenGL ES context */ +RGFWDEF RGFW_proc 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 */ +RGFWDEF void RGFW_window_swapBuffers_OpenGL(RGFW_window* win); /*!< swap opengl buffer (only) called by RGFW_window_swapInterval */ +void* RGFW_getCurrent_OpenGL(void); /*!< get the current context (OpenGL backend (GLX) (WGL) (EGL) (cocoa) (webgl))*/ + +RGFWDEF RGFW_bool RGFW_extensionSupportedPlatform(const char* extension, size_t len); /*!< check if whether the specified platform-specific API extension is supported by the current OpenGL or OpenGL ES context */ +#endif #ifdef RGFW_VULKAN - #if defined(RGFW_WAYLAND) && defined(RGFW_X11) + #define VK_USE_PLATFORM_WAYLAND_KHR + #define VK_USE_PLATFORM_XLIB_KHR + #define RGFW_VK_SURFACE ((RGFW_usingWayland()) ? ("VK_KHR_wayland_surface") : ("VK_KHR_xlib_surface")) + #elif defined(RGFW_WAYLAND) #define VK_USE_PLATFORM_WAYLAND_KHR #define VK_USE_PLATFORM_XLIB_KHR - #define RGFW_VK_SURFACE ((RGFW_usingWayland()) ? ("VK_KHR_wayland_surface") : ("VK_KHR_xlib_surface")) - #elif defined(RGFW_WAYLAND) - #define VK_USE_PLATFORM_WAYLAND_KHR - #define VK_USE_PLATFORM_XLIB_KHR - #define RGFW_VK_SURFACE "VK_KHR_wayland_surface" - #elif defined(RGFW_X11) + #define RGFW_VK_SURFACE "VK_KHR_wayland_surface" + #elif defined(RGFW_X11) #define VK_USE_PLATFORM_XLIB_KHR #define RGFW_VK_SURFACE "VK_KHR_xlib_surface" #elif defined(RGFW_WINDOWS) @@ -438,39 +1337,63 @@ typedef ptrdiff_t RGFW_ssize_t; #define RGFW_VK_SURFACE NULL #endif +/* if you don't want to use the above macros */ +RGFWDEF const char** RGFW_getVKRequiredInstanceExtensions(size_t* count); /*!< gets (static) extension array (and size (which will be 2)) */ + +#include + +RGFWDEF VkResult RGFW_window_createVKSurface(RGFW_window* win, VkInstance instance, VkSurfaceKHR* surface); +RGFWDEF RGFW_bool RGFW_getVKPresentationSupport(VkInstance instance, VkPhysicalDevice physicalDevice, u32 queueFamilyIndex); +#endif +#ifdef RGFW_DIRECTX +#ifndef RGFW_WINDOWS + #undef RGFW_DIRECTX +#else + #define OEMRESOURCE + #include + + #ifndef __cplusplus + #define __uuidof(T) IID_##T + #endif +RGFWDEF int RGFW_window_createDXSwapChain(RGFW_window* win, IDXGIFactory* pFactory, IUnknown* pDevice, IDXGISwapChain** swapchain); +#endif #endif +/** @} */ -/*! @brief The stucture that contains information about the current RGFW instance */ -typedef struct RGFW_info RGFW_info; +/** * @defgroup Supporting +* @{ */ -/*! @brief The window stucture for interfacing with the window */ -typedef struct RGFW_window RGFW_window; +/*! optional init/deinit function */ +RGFWDEF i32 RGFW_init(void); /*!< is called by default when the first window is created by default */ +RGFWDEF void RGFW_deinit(void); /*!< is called by default when the last open window is closed */ -/*! @brief The source window stucture for interfacing with the underlying windowing API (e.g. winapi, wayland, cocoa, etc) */ -typedef struct RGFW_window_src RGFW_window_src; +RGFWDEF double RGFW_getTime(void); /*!< get time in seconds since RGFW_setTime, which ran when the first window is open */ +RGFWDEF u64 RGFW_getTimeNS(void); /*!< get time in nanoseconds RGFW_setTime, which ran when the first window is open */ +RGFWDEF void RGFW_sleep(u64 milisecond); /*!< sleep for a set time */ +RGFWDEF void RGFW_setTime(double time); /*!< set timer in seconds */ +RGFWDEF u64 RGFW_getTimerValue(void); /*!< get API timer value */ +RGFWDEF u64 RGFW_getTimerFreq(void); /*!< get API time freq */ -/*! @brief The color format for pixel data */ -typedef RGFW_ENUM(u8, RGFW_format) { - RGFW_formatRGB8 = 0, /*!< 8-bit RGB (3 channels) */ - RGFW_formatBGR8, /*!< 8-bit BGR (3 channels) */ - RGFW_formatRGBA8, /*!< 8-bit RGBA (4 channels) */ - RGFW_formatARGB8, /*!< 8-bit RGBA (4 channels) */ - RGFW_formatBGRA8, /*!< 8-bit BGRA (4 channels) */ - RGFW_formatABGR8, /*!< 8-bit BGRA (4 channels) */ - RGFW_formatCount -}; +/*< updates fps / sets fps to cap (must by ran manually by the user at the end of a frame), returns current fps */ +RGFWDEF u32 RGFW_checkFPS(double startTime, u32 frameCount, u32 fpsCap); -/*! @brief a stucture for interfacing with the underlying native image (e.g. XImage, HBITMAP, etc) */ -typedef struct RGFW_nativeImage RGFW_nativeImage; +/*!< change which window is the root window */ +RGFWDEF void RGFW_setRootWindow(RGFW_window* win); +RGFWDEF RGFW_window* RGFW_getRootWindow(void); -/*! @brief a stucture for interfacing with pixel data as a renderable surface */ -typedef struct RGFW_surface RGFW_surface; +/*! standard event queue, used for injecting events and returning source API callback events like any other queue check */ +/* these are all used internally by RGFW */ +void RGFW_eventQueuePush(RGFW_event event); +RGFW_event* RGFW_eventQueuePop(RGFW_window* win); -/*! a raw pointer to the underlying mouse handle for setting and creating custom mouse icons */ -typedef void RGFW_mouse; +/* for C++ / C89 */ +#define RGFW_eventQueuePushEx(eventInit) { RGFW_event e; eventInit; RGFW_eventQueuePush(e); } -/*! @brief RGFW's abstract keycodes */ +/*! + key codes and mouse icon enums +*/ +#undef RGFW_key typedef RGFW_ENUM(u8, RGFW_key) { RGFW_keyNULL = 0, RGFW_escape = '\033', @@ -485,11 +1408,13 @@ typedef RGFW_ENUM(u8, RGFW_key) { RGFW_7 = '7', RGFW_8 = '8', RGFW_9 = '9', + RGFW_minus = '-', RGFW_equals = '=', RGFW_backSpace = '\b', RGFW_tab = '\t', RGFW_space = ' ', + RGFW_a = 'a', RGFW_b = 'b', RGFW_c = 'c', @@ -516,17 +1441,20 @@ typedef RGFW_ENUM(u8, RGFW_key) { RGFW_x = 'x', RGFW_y = 'y', RGFW_z = 'z', + RGFW_period = '.', RGFW_comma = ',', RGFW_slash = '/', RGFW_bracket = '[', - RGFW_closeBracket = ']', + RGFW_closeBracket = ']', RGFW_semicolon = ';', RGFW_apostrophe = '\'', RGFW_backSlash = '\\', RGFW_return = '\n', RGFW_enter = RGFW_return, + RGFW_delete = '\177', /* 127 */ + RGFW_F1, RGFW_F2, RGFW_F3, @@ -539,19 +1467,7 @@ typedef RGFW_ENUM(u8, RGFW_key) { RGFW_F10, RGFW_F11, RGFW_F12, - RGFW_F13, - RGFW_F14, - RGFW_F15, - RGFW_F16, - RGFW_F17, - RGFW_F18, - RGFW_F19, - RGFW_F20, - RGFW_F21, - RGFW_F22, - RGFW_F23, - RGFW_F24, - RGFW_F25, + RGFW_capsLock, RGFW_shiftL, RGFW_controlL, @@ -566,262 +1482,41 @@ typedef RGFW_ENUM(u8, RGFW_key) { RGFW_left, RGFW_right, RGFW_insert, - RGFW_menu, RGFW_end, RGFW_home, RGFW_pageUp, RGFW_pageDown, + RGFW_numLock, - RGFW_kpSlash, - RGFW_kpMultiply, - RGFW_kpPlus, - RGFW_kpMinus, - RGFW_kpEqual, - RGFW_kp1, - RGFW_kp2, - RGFW_kp3, - RGFW_kp4, - RGFW_kp5, - RGFW_kp6, - RGFW_kp7, - RGFW_kp8, - RGFW_kp9, - RGFW_kp0, - RGFW_kpPeriod, - RGFW_kpReturn, + 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_scrollLock, RGFW_printScreen, RGFW_pause, - RGFW_world1, - RGFW_world2, RGFW_keyLast = 256 /* padding for alignment ~(175 by default) */ -}; - -/*! @brief abstract mouse button codes */ -typedef RGFW_ENUM(u8, RGFW_mouseButton) { - RGFW_mouseLeft = 0, /*!< left mouse button is pressed */ - RGFW_mouseMiddle, /*!< mouse-wheel-button is pressed */ - RGFW_mouseRight, /*!< right mouse button is pressed */ - RGFW_mouseMisc1, RGFW_mouseMisc2, RGFW_mouseMisc3, RGFW_mouseMisc4, RGFW_mouseMisc5, - RGFW_mouseFinal -}; - -/*! abstract key modifier codes */ -typedef RGFW_ENUM(u8, RGFW_keymod) { - RGFW_modCapsLock = RGFW_BIT(0), - RGFW_modNumLock = RGFW_BIT(1), - RGFW_modControl = RGFW_BIT(2), - RGFW_modAlt = RGFW_BIT(3), - RGFW_modShift = RGFW_BIT(4), - RGFW_modSuper = RGFW_BIT(5), - RGFW_modScrollLock = RGFW_BIT(6) -}; - -/*! @brief codes for the event types that can be sent */ -typedef RGFW_ENUM(u8, RGFW_eventType) { - RGFW_eventNone = 0, /*!< no event has been sent */ - RGFW_keyPressed, /* a key has been pressed */ - RGFW_keyReleased, /*!< a key has been released */ - /*! key event note - the code of the key pressed is stored in - RGFW_event.key.value - !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! - - while a string version is stored in - RGFW_event.key.valueString - - RGFW_event.key.mod holds the current mod - this means if CapsLock, NumLock are active or not - */ - RGFW_mouseButtonPressed, /*!< a mouse button has been pressed (left,middle,right) */ - RGFW_mouseButtonReleased, /*!< a mouse button has been released (left,middle,right) */ - RGFW_mouseScroll, /*!< a mouse scroll event */ - RGFW_mousePosChanged, /*!< the position of the mouse has been changed */ - /*! mouse event note - the x and y of the mouse can be found in the vector, RGFW_x, y - - RGFW_event.button.value holds which mouse button was pressed - */ - RGFW_windowMoved, /*!< the window was moved (by the user) */ - RGFW_windowResized, /*!< the window was resized (by the user), [on WASM this means the browser was resized] */ - RGFW_focusIn, /*!< window is in focus now */ - RGFW_focusOut, /*!< window is out of focus now */ - RGFW_mouseEnter, /* mouse entered the window */ - RGFW_mouseLeave, /* mouse left the window */ - RGFW_windowRefresh, /* The window content needs to be refreshed */ - - /* attribs change event note - The event data is sent straight to the window structure - with win->x, win->y, win->w and win->h - */ - RGFW_quit, /*!< the user clicked the quit button */ - RGFW_dataDrop, /*!< a file has been dropped into the window */ - RGFW_dataDrag, /*!< the start of a drag and drop event, when the file is being dragged */ - /* drop data note - The x and y coords of the drop are stored in the vector RGFW_x, y - - RGFW_event.drop.count holds how many files were dropped - - This is also the size of the array which stores all the dropped file string, - RGFW_event.drop.files - */ - RGFW_windowMaximized, /*!< the window was maximized */ - RGFW_windowMinimized, /*!< the window was minimized */ - RGFW_windowRestored, /*!< the window was restored */ - RGFW_scaleUpdated /*!< content scale factor changed */ -}; - -/*! @brief flags for toggling wether or not an event should be processed */ -typedef RGFW_ENUM(u32, RGFW_eventFlag) { - RGFW_keyPressedFlag = RGFW_BIT(RGFW_keyPressed), - RGFW_keyReleasedFlag = RGFW_BIT(RGFW_keyReleased), - RGFW_mouseScrollFlag = RGFW_BIT(RGFW_mouseScroll), - RGFW_mouseButtonPressedFlag = RGFW_BIT(RGFW_mouseButtonPressed), - RGFW_mouseButtonReleasedFlag = RGFW_BIT(RGFW_mouseButtonReleased), - RGFW_mousePosChangedFlag = RGFW_BIT(RGFW_mousePosChanged), - RGFW_mouseEnterFlag = RGFW_BIT(RGFW_mouseEnter), - RGFW_mouseLeaveFlag = RGFW_BIT(RGFW_mouseLeave), - RGFW_windowMovedFlag = RGFW_BIT(RGFW_windowMoved), - RGFW_windowResizedFlag = RGFW_BIT(RGFW_windowResized), - RGFW_focusInFlag = RGFW_BIT(RGFW_focusIn), - RGFW_focusOutFlag = RGFW_BIT(RGFW_focusOut), - RGFW_windowRefreshFlag = RGFW_BIT(RGFW_windowRefresh), - RGFW_windowMaximizedFlag = RGFW_BIT(RGFW_windowMaximized), - RGFW_windowMinimizedFlag = RGFW_BIT(RGFW_windowMinimized), - RGFW_windowRestoredFlag = RGFW_BIT(RGFW_windowRestored), - RGFW_scaleUpdatedFlag = RGFW_BIT(RGFW_scaleUpdated), - RGFW_quitFlag = RGFW_BIT(RGFW_quit), - RGFW_dataDropFlag = RGFW_BIT(RGFW_dataDrop), - RGFW_dataDragFlag = RGFW_BIT(RGFW_dataDrag), - - RGFW_keyEventsFlag = RGFW_keyPressedFlag | RGFW_keyReleasedFlag, - RGFW_mouseEventsFlag = RGFW_mouseButtonPressedFlag | RGFW_mouseButtonReleasedFlag | RGFW_mousePosChangedFlag | RGFW_mouseEnterFlag | RGFW_mouseLeaveFlag | RGFW_mouseScrollFlag , - RGFW_windowEventsFlag = RGFW_windowMovedFlag | RGFW_windowResizedFlag | RGFW_windowRefreshFlag | RGFW_windowMaximizedFlag | RGFW_windowMinimizedFlag | RGFW_windowRestoredFlag | RGFW_scaleUpdatedFlag, - RGFW_focusEventsFlag = RGFW_focusInFlag | RGFW_focusOutFlag, - RGFW_dataDropEventsFlag = RGFW_dataDropFlag | RGFW_dataDragFlag, - RGFW_allEventFlags = RGFW_keyEventsFlag | RGFW_mouseEventsFlag | RGFW_windowEventsFlag | RGFW_focusEventsFlag | RGFW_dataDropEventsFlag | RGFW_quitFlag -}; - -/*! Event structure(s) and union for checking/getting events */ - -/*! @brief common event data across all events */ -typedef struct RGFW_commonEvent { - RGFW_eventType type; /*!< which event has been sent?*/ - RGFW_window* win; /*!< the window this event applies too (for event queue events) */ -} RGFW_commonEvent; - -/*! @brief event data for any mouse button event (press/release) */ -typedef struct RGFW_mouseButtonEvent { - RGFW_eventType type; /*!< which event has been sent?*/ - RGFW_window* win; /*!< the window this event applies too (for event queue events) */ - u8 value; /* !< which mouse button was pressed */ -} RGFW_mouseButtonEvent; - -/*! @brief event data for any mouse scroll event */ -typedef struct RGFW_mouseScrollEvent { - RGFW_eventType type; /*!< which event has been sent?*/ - RGFW_window* win; /*!< the window this event applies too (for event queue events) */ - float x, y; /*!< the raw mouse scroll value */ -} RGFW_mouseScrollEvent; - -/*! @brief event data for any mouse position event (RGFW_mousePosChanged) */ -typedef struct RGFW_mousePosEvent { - RGFW_eventType type; /*!< which event has been sent?*/ - RGFW_window* win; /*!< the window this event applies too (for event queue events) */ - i32 x, y; /*!< mouse x, y of event (or drop point) */ - float vecX, vecY; /*!< raw mouse movement */ -} RGFW_mousePosEvent; - -/*! @brief event data for any key event (press/release) */ -typedef struct RGFW_keyEvent { - RGFW_eventType type; /*!< which event has been sent?*/ - RGFW_window* win; /*!< the window this event applies too (for event queue events) */ - RGFW_key value; /*!< the physical key of the event, refers to where key is physically !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! */ - u8 sym; /*!< mapped key char of the event */ - RGFW_bool repeat; /*!< key press event repeated (the key is being held) */ - RGFW_keymod mod; -} RGFW_keyEvent; - -/*! @brief event data for any data drop event */ -typedef struct RGFW_dataDropEvent { - RGFW_eventType type; /*!< which event has been sent?*/ - RGFW_window* win; /*!< the window this event applies too (for event queue events) */ - /* 260 max paths with a max length of 260 */ - char** files; /*!< dropped files */ - size_t count; /*!< how many files were dropped */ -} RGFW_dataDropEvent; - -/*! @brief event data for any data drag event */ -typedef struct RGFW_dataDragEvent { - RGFW_eventType type; /*!< which event has been sent?*/ - RGFW_window* win; /*!< the window this event applies too (for event queue events) */ - i32 x, y; /*!< mouse x, y of event (or drop point) */ -} RGFW_dataDragEvent; - -/*! @brief event data for when the window scale (DPI) is updated */ -typedef struct RGFW_scaleUpdatedEvent { - RGFW_eventType type; /*!< which event has been sent?*/ - RGFW_window* win; /*!< the window this event applies too (for event queue events) */ - float x, y; /*!< DPI scaling */ -} RGFW_scaleUpdatedEvent; - -/*! @brief union for all of the event stucture types */ -typedef union RGFW_event { - RGFW_eventType type; /*!< which event has been sent?*/ - RGFW_commonEvent common; /*!< common event data (e.g.) type and win */ - RGFW_mouseButtonEvent button; /*!< data for a button press/release */ - RGFW_mouseScrollEvent scroll; /*!< data for a mouse scroll */ - RGFW_mousePosEvent mouse; /*!< data for mouse motion events */ - RGFW_keyEvent key; /*!< data for key press/release/hold events */ - RGFW_dataDropEvent drop; /*!< dropping a file events */ - RGFW_dataDragEvent drag; /* data for dragging a file events */ - RGFW_scaleUpdatedEvent scale; /* data for monitor scaling events */ -} RGFW_event; - -/*! - @!brief codes for for RGFW_the code is stupid and C++ waitForEvent - waitMS -> Allows the function to keep checking for events even after there are no more events - if waitMS == 0, the loop will not wait for events - if waitMS > 0, the loop will wait that many miliseconds after there are no more events until it returns - if waitMS == -1 or waitMS == the max size of an unsigned 32-bit int, the loop will not return until it gets another event -*/ -typedef RGFW_ENUM(i32, RGFW_eventWait) { - RGFW_eventNoWait = 0, - RGFW_eventWaitNext = -1 -}; + }; +/*! converts api keycode to the RGFW unmapped/physical key */ +RGFWDEF u32 RGFW_apiKeyToRGFW(u32 keycode); +/*! converts RGFW keycode to the unmapped/physical api key */ +RGFWDEF u32 RGFW_rgfwToApiKey(u32 keycode); +/*! converts RGFW keycode to the mapped keychar */ +RGFWDEF u8 RGFW_rgfwToKeyChar(u32 keycode); -/*! @brief optional bitwise arguments for making a windows, these can be OR'd together */ -typedef RGFW_ENUM(u32, RGFW_windowFlags) { - RGFW_windowNoBorder = RGFW_BIT(0), /*!< the window doesn't have a border */ - RGFW_windowNoResize = RGFW_BIT(1), /*!< the window cannot be resized by the user */ - RGFW_windowAllowDND = RGFW_BIT(2), /*!< the window supports drag and drop */ - RGFW_windowHideMouse = RGFW_BIT(3), /*! the window should hide the mouse (can be toggled later on using `RGFW_window_showMouse`) */ - RGFW_windowFullscreen = RGFW_BIT(4), /*!< the window is fullscreen by default */ - RGFW_windowTransparent = RGFW_BIT(5), /*!< the window is transparent (only properly works on X11 and MacOS, although it's meant for for windows) */ - RGFW_windowCenter = RGFW_BIT(6), /*! center the window on the screen */ - RGFW_windowScaleToMonitor = RGFW_BIT(8), /*! scale the window to the screen */ - RGFW_windowHide = RGFW_BIT(9), /*! the window is hidden */ - RGFW_windowMaximize = RGFW_BIT(10), /*!< maximize the window on creation */ - RGFW_windowCenterCursor = RGFW_BIT(11), /*!< center the cursor to the window on creation */ - RGFW_windowFloating = RGFW_BIT(12), /*!< create a floating window */ - RGFW_windowFocusOnShow = RGFW_BIT(13), /*!< focus the window when it's shown */ - RGFW_windowMinimize = RGFW_BIT(14), /*!< focus the window when it's shown */ - RGFW_windowFocus = RGFW_BIT(15), /*!< if the window is in focus */ - RGFW_windowOpenGL = RGFW_BIT(17), /*!< create an OpenGL context (you can also do this manually with RGFW_window_createContext_OpenGL) */ - RGFW_windowEGL = RGFW_BIT(18), /*!< create an EGL context (you can also do this manually with RGFW_window_createContext_EGL) */ - RGFW_windowedFullscreen = RGFW_windowNoBorder | RGFW_windowMaximize -}; - -/*! @brief the types of icon to set */ -typedef RGFW_ENUM(u8, RGFW_icon) { - RGFW_iconTaskbar = RGFW_BIT(0), - RGFW_iconWindow = RGFW_BIT(1), - RGFW_iconBoth = RGFW_iconTaskbar | RGFW_iconWindow -}; - -/*! @brief standard mouse icons */ typedef RGFW_ENUM(u8, RGFW_mouseIcons) { RGFW_mouseNormal = 0, RGFW_mouseArrow, @@ -834,2188 +1529,46 @@ typedef RGFW_ENUM(u8, RGFW_mouseIcons) { RGFW_mouseResizeNESW, RGFW_mouseResizeAll, RGFW_mouseNotAllowed, - RGFW_mouseIconCount, RGFW_mouseIconFinal = 16 /* padding for alignment */ }; - -/*! @brief the type of debug message */ -typedef RGFW_ENUM(u8, RGFW_debugType) { - RGFW_typeError = 0, RGFW_typeWarning, RGFW_typeInfo -}; - -/*! @brief error codes for known failure types */ -typedef RGFW_ENUM(u8, RGFW_errorCode) { - RGFW_noError = 0, /*!< no error */ - RGFW_errOutOfMemory, - RGFW_errOpenGLContext, RGFW_errEGLContext, /*!< error with the OpenGL context */ - RGFW_errWayland, RGFW_errX11, - RGFW_errDirectXContext, - RGFW_errIOKit, - RGFW_errClipboard, - RGFW_errFailedFuncLoad, - RGFW_errBuffer, - RGFW_errEventQueue, - RGFW_infoMonitor, RGFW_infoWindow, RGFW_infoBuffer, RGFW_infoGlobal, RGFW_infoOpenGL, - RGFW_warningWayland, RGFW_warningOpenGL -}; - -/*! @brief callback function type for debug messags */ -typedef void (* RGFW_debugfunc)(RGFW_debugType type, RGFW_errorCode err, const char* msg); - -/*! @brief RGFW_windowMoved, the window and its new rect value */ -typedef void (* RGFW_windowMovedfunc)(RGFW_window* win, i32 x, i32 y); -/*! @brief RGFW_windowResized, the window and its new rect value */ -typedef void (* RGFW_windowResizedfunc)(RGFW_window* win, i32 w, i32 h); -/*! @brief RGFW_windowRestored, the window and its new rect value */ -typedef void (* RGFW_windowRestoredfunc)(RGFW_window* win, i32 x, i32 y, i32 w, i32 h); -/*! @brief RGFW_windowMaximized, the window and its new rect value */ -typedef void (* RGFW_windowMaximizedfunc)(RGFW_window* win, i32 x, i32 y, i32 w, i32 h); -/*! @brief RGFW_windowMinimized, the window and its new rect value */ -typedef void (* RGFW_windowMinimizedfunc)(RGFW_window* win); -/*! @brief RGFW_quit, the window that was closed */ -typedef void (* RGFW_windowQuitfunc)(RGFW_window* win); -/*! @brief RGFW_focusIn / RGFW_focusOut, the window who's focus has changed and if its in focus */ -typedef void (* RGFW_focusfunc)(RGFW_window* win, RGFW_bool inFocus); -/*! @brief 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, i32 x, i32 y, RGFW_bool status); -/*! @brief RGFW_mousePosChanged, the window that the move happened on, and the new point of the mouse */ -typedef void (* RGFW_mousePosfunc)(RGFW_window* win, i32 x, i32 y, float vecX, float vecY); -/*! @brief RGFW_dataDrag, the window, the point of the drop on the windows */ -typedef void (* RGFW_dataDragfunc)(RGFW_window* win, i32 x, i32 y); -/*! @brief RGFW_windowRefresh, the window that needs to be refreshed */ -typedef void (* RGFW_windowRefreshfunc)(RGFW_window* win); -/*! @brief RGFW_keyPressed / RGFW_keyReleased, the window that got the event, the mapped key, the physical key, the string version, the state of the mod keys, if it was a press (else it's a release) */ -typedef void (* RGFW_keyfunc)(RGFW_window* win, u8 key, u8 sym, RGFW_keymod mod, RGFW_bool repeat, RGFW_bool pressed); -/*! @brief 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, RGFW_mouseButton button, RGFW_bool pressed); -/*! @brief RGFW_mouseScroll, the window that got the event, the x scroll value, the y scroll value */ -typedef void (* RGFW_mouseScrollfunc)(RGFW_window* win, float x, float y); -/*! @brief RGFW_dataDrop the window that had the drop, the drop data and the number of files dropped */ -typedef void (* RGFW_dataDropfunc)(RGFW_window* win, char** files, size_t count); -/*! @brief RGFW_scaleUpdated, the window the event was sent to, content scaleX, content scaleY */ -typedef void (* RGFW_scaleUpdatedfunc)(RGFW_window* win, float scaleX, float scaleY); - -/*! @brief function pointer equivalent of void* */ -typedef void (*RGFW_proc)(void); - -#ifndef RGFW_NO_MONITOR - -/*! @brief monitor mode data | can be changed by the user (with functions)*/ -typedef struct RGFW_monitorMode { - i32 w, h; /*!< monitor workarea size */ - u32 refreshRate; /*!< monitor refresh rate */ - u8 red, blue, green; -} RGFW_monitorMode; - -/*! @brief structure for monitor data */ -typedef struct RGFW_monitor { - i32 x, y; /*!< x - y of the monitor workarea */ - char name[128]; /*!< monitor name */ - float scaleX, scaleY; /*!< monitor content scale */ - float pixelRatio; /*!< pixel ratio for monitor (1.0 for regular, 2.0 for hiDPI) */ - float physW, physH; /*!< monitor physical size in inches */ - RGFW_monitorMode mode; -} RGFW_monitor; - -/*! @brief what type of request you are making for the monitor */ -typedef RGFW_ENUM(u8, RGFW_modeRequest) { - RGFW_monitorScale = RGFW_BIT(0), /*!< scale the monitor size */ - RGFW_monitorRefresh = RGFW_BIT(1), /*!< change the refresh rate */ - RGFW_monitorRGB = RGFW_BIT(2), /*!< change the monitor RGB bits size */ - RGFW_monitorAll = RGFW_monitorScale | RGFW_monitorRefresh | RGFW_monitorRGB -}; - -#endif - -#if defined(RGFW_OPENGL) - -/*! @brief abstract structure for interfacing with the underlying OpenGL API */ -typedef struct RGFW_glContext RGFW_glContext; - -/*! @brief abstract structure for interfacing with the underlying EGL API */ -typedef struct RGFW_eglContext RGFW_eglContext; - -/*! values for the releaseBehavior hint */ -typedef RGFW_ENUM(i32, RGFW_glReleaseBehavior) { - RGFW_glReleaseFlush = 0, /*!< flush the pipeline will be flushed when the context is release */ - RGFW_glReleaseNone /*!< do nothing on release */ -}; - -/*! values for the profile hint */ -typedef RGFW_ENUM(i32, RGFW_glProfile) { - RGFW_glCore = 0, /*!< the core OpenGL version, e.g. just support for that version */ - RGFW_glCompatibility, /*!< allow compatibility for older versions of RGFW as well as the requested version */ - RGFW_glES /*!< use OpenGL ES */ -}; - -/*! values for the renderer hint */ -typedef RGFW_ENUM(i32, RGFW_glRenderer) { - RGFW_glAccelerated = 0, /*!< hardware accelerated (GPU) */ - RGFW_glSoftware /*!< software rendered (CPU) */ -}; - -/*! OpenGL initalization hints */ -typedef struct RGFW_glHints { - i32 stencil; /*!< set stencil buffer bit size (0 by default) */ - i32 samples; /*!< set number of sample buffers (0 by default) */ - i32 stereo; /*!< hint the context to use stereoscopic frame buffers for 3D (false by default) */ - i32 auxBuffers; /*!< number of aux buffers (0 by default) */ - i32 doubleBuffer; /*!< request double buffering (true by default) */ - i32 red, green, blue, alpha; /*!< set color bit sizes (all 8 by default) */ - i32 depth; /*!< set depth buffer bit size (24 by default) */ - i32 accumRed, accumGreen, accumBlue, accumAlpha; /*!< set accumulated RGBA bit sizes (all 0 by default) */ - RGFW_bool sRGB; /*!< request sRGA format (false by default) */ - RGFW_bool robustness; /*!< request a "robust" (as in memory-safe) context (false by default). For more information check the overview section: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_robustness.txt */ - RGFW_bool debug; /*!< request OpenGL debugging (false by default). */ - RGFW_bool noError; /*!< request no OpenGL errors (false by default). This causes OpenGL errors to be undefined behavior. For more information check the overview section: https://registry.khronos.org/OpenGL/extensions/KHR/KHR_no_error.txt */ - RGFW_glReleaseBehavior releaseBehavior; /*!< hint how the OpenGL driver should behave when changing contexts (RGFW_glReleaseNone by default). For more information check the overview section: https://registry.khronos.org/OpenGL/extensions/KHR/KHR_context_flush_control.txt */ - RGFW_glProfile profile; /*!< set OpenGL API profile (RGFW_glCore by default) */ - i32 major, minor; /*!< set the OpenGL API profile version (by default RGFW_glMajor is 1, RGFW_glMinor is 0) */ - RGFW_glContext* share; /*!< Share this OpenGL context with newly created OpenGL contexts; defaults to NULL. */ - RGFW_eglContext* shareEGL; /*!< Share this EGL context with newly created OpenGL contexts; defaults to NULL. */ - RGFW_glRenderer renderer; /*!< renderer to use e.g. accelerated or software defaults to accelerated */ -} RGFW_glHints; - -#endif - -/**! - * @brief Allocates memory using the allocator defined by RGFW_ALLOC at compile time. - * @param size The size (in bytes) of the memory block to allocate. - * @return A pointer to the allocated memory block. -*/ -RGFWDEF void* RGFW_alloc(size_t size); - -/**! - * @brief Frees memory using the deallocator defined by RGFW_FREE at compile time. - * @param ptr A pointer to the memory block to free. -*/ -RGFWDEF void RGFW_free(void* ptr); - -/**! - * @brief Returns the size (in bytes) of the RGFW_window structure. - * @return The size of the RGFW_window structure. -*/ -RGFWDEF size_t RGFW_sizeofWindow(void); - -/**! - * @brief Returns the size (in bytes) of the RGFW_window_src structure. - * @return The size of the RGFW_window_src structure. -*/ -RGFWDEF size_t RGFW_sizeofWindowSrc(void); - -/**! - * @brief (Unix) Toggles the use of Wayland. - * This is enabled by default when compiled with `RGFW_WAYLAND`. - * If not using `RGFW_WAYLAND`, Wayland functions are not exposed. - * This function can be used to force the use of XWayland. - * @param wayland A boolean value indicating whether to use Wayland (true) or not (false). -*/ -RGFWDEF void RGFW_useWayland(RGFW_bool wayland); - -/**! - * @brief Checks if Wayland is currently being used. - * @return RGFW_TRUE if using Wayland, RGFW_FALSE otherwise. -*/ -RGFWDEF RGFW_bool RGFW_usingWayland(void); - -/**! - * @brief Retrieves the current Cocoa layer (macOS only). - * @return A pointer to the Cocoa layer, or NULL if the platform is not in use. -*/ -RGFWDEF void* RGFW_getLayer_OSX(void); - -/**! - * @brief Retrieves the current X11 display connection. - * @return A pointer to the X11 display, or NULL if the platform is not in use. -*/ -RGFWDEF void* RGFW_getDisplay_X11(void); - -/**! - * @brief Retrieves the current Wayland display connection. - * @return A pointer to the Wayland display (`struct wl_display*`), or NULL if the platform is not in use. -*/ -RGFWDEF struct wl_display* RGFW_getDisplay_Wayland(void); - -/**! - * @brief Sets the class name for X11 and WinAPI windows. - * Windows with the same class name will be grouped by the window manager. - * By default, the class name matches the root window’s name. - * @param name The class name to assign. -*/ -RGFWDEF void RGFW_setClassName(const char* name); - -/**! - * @brief Sets the X11 instance name. - * By default, the window name will be used as the instance name. - * @param name The X11 instance name to set. -*/ -RGFWDEF void RGFW_setXInstName(const char* name); - -/**! - * @brief (macOS only) Changes the current working directory to the application’s resource folder. -*/ -RGFWDEF void RGFW_moveToMacOSResourceDir(void); - -/*! copy image to another image, respecting each image's format */ -RGFWDEF void RGFW_copyImageData(u8* dest_data, i32 w, i32 h, RGFW_format dest_format, u8* src_data, RGFW_format src_format); - -/**! - * @brief Returns the size (in bytes) of the RGFW_nativeImage structure. - * @return The size of the RGFW_nativeImage structure. -*/ -RGFWDEF size_t RGFW_sizeofNativeImage(void); - -/**! - * @brief Returns the size (in bytes) of the RGFW_surface structure. - * @return The size of the RGFW_surface structure. -*/ -RGFWDEF size_t RGFW_sizeofSurface(void); - -/**! - * @brief Creates a new surface from raw pixel data. - * @param data A pointer to the pixel data buffer. - * @param w The width of the surface in pixels. - * @param h The height of the surface in pixels. - * @param format The pixel format of the data. - * @return A pointer to the newly created RGFW_surface. - * - * NOTE: when you create a surface using RGFW_createSurface / ptr, on X11 it uses the root window's visual - * this means it may fail to render on any other window if the visual does not match - * RGFW_window_createSurface and RGFW_window_createSurfacePtr exist only for X11 to address this issues - * Of course, you can also manually set the root window with RGFW_setRootWindow -*/ -RGFWDEF RGFW_surface* RGFW_createSurface(u8* data, i32 w, i32 h, RGFW_format format); - -/**! - * @brief Creates a surface using a pre-allocated RGFW_surface structure. - * @param data A pointer to the pixel data buffer. - * @param w The width of the surface in pixels. - * @param h The height of the surface in pixels. - * @param format The pixel format of the data. - * @param surface A pointer to a pre-allocated RGFW_surface structure. - * @return RGFW_TRUE if successful, RGFW_FALSE otherwise. -*/ -RGFWDEF RGFW_bool RGFW_createSurfacePtr(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface); - -/**! - * @brief Retrieves the native image associated with a surface. - * @param surface A pointer to the RGFW_surface. - * @return A pointer to the native RGFW_nativeImage associated with the surface. -*/ -RGFWDEF RGFW_nativeImage* RGFW_surface_getNativeImage(RGFW_surface* surface); - -/**! - * @brief Frees the surface pointer and any buffers used for software rendering. - * @param surface A pointer to the RGFW_surface to free. -*/ -RGFWDEF void RGFW_surface_free(RGFW_surface* surface); - -/**! - * @brief Frees only the internal buffers used for software rendering, leaving the surface struct intact. - * @param surface A pointer to the RGFW_surface whose buffers should be freed. -*/ -RGFWDEF void RGFW_surface_freePtr(RGFW_surface* surface); - - -/**! - * @brief Loads a mouse icon from bitmap data (similar to RGFW_window_setIcon). - * @param data A pointer to the bitmap pixel data. - * @param w The width of the mouse icon in pixels. - * @param h The height of the mouse icon in pixels. - * @param format The pixel format of the data. - * @return A pointer to the newly loaded RGFW_mouse structure. - * - * @note The icon is not resized by default. -*/ -RGFWDEF RGFW_mouse* RGFW_loadMouse(u8* data, i32 w, i32 h, RGFW_format format); - -/**! - * @brief Frees the data associated with an RGFW_mouse structure. - * @param mouse A pointer to the RGFW_mouse to free. -*/ -RGFWDEF void RGFW_freeMouse(RGFW_mouse* mouse); - -#ifndef RGFW_NO_MONITOR - -/**! - * @brief Retrieves an array of all available monitors. - * @param len [OUTPUT] A pointer to store the number of monitors found (maximum of 6). - * @return A pointer to an array of RGFW_monitor structures. -*/ -RGFWDEF RGFW_monitor* RGFW_getMonitors(size_t* len); - -/**! - * @brief Retrieves the primary monitor. - * @return The RGFW_monitor structure representing the primary monitor. -*/ -RGFWDEF RGFW_monitor RGFW_getPrimaryMonitor(void); - -/**! - * @brief Requests a specific display mode for a monitor. - * @param mon The monitor to apply the mode change to. - * @param mode The desired RGFW_monitorMode. - * @param request The RGFW_modeRequest describing how to handle the mode change. - * @return RGFW_TRUE if the mode was successfully applied, otherwise RGFW_FALSE. -*/ -RGFWDEF RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request); - -/**! - * @brief Compares two monitor modes to check if they are equivalent. - * @param mon The first monitor mode. - * @param mon2 The second monitor mode. - * @param request The RGFW_modeRequest that defines the comparison parameters. - * @return RGFW_TRUE if both modes are equivalent, otherwise RGFW_FALSE. -*/ -RGFWDEF RGFW_bool RGFW_monitorModeCompare(RGFW_monitorMode mon, RGFW_monitorMode mon2, RGFW_modeRequest request); - -/**! - * @brief Scales a monitor’s mode to match a window’s size. - * @param mon The monitor to be scaled. - * @param win The window whose size should be used as a reference. - * @return RGFW_TRUE if the scaling was successful, otherwise RGFW_FALSE. -*/ -RGFWDEF RGFW_bool RGFW_monitor_scaleToWindow(RGFW_monitor mon, struct RGFW_window* win); - -#endif - -/**! -* @brief sleep until RGFW gets an event or the timer ends (defined by OS) -* @param waitMS how long to wait for the next event (in miliseconds) -*/ -RGFWDEF void RGFW_waitForEvent(i32 waitMS); - -/**! -* @brief Set if events should be queued or not (enabled by default if the event queue is checked) -* @param queue boolean value if RGFW should queue events or not -*/ -RGFWDEF void RGFW_setQueueEvents(RGFW_bool queue); - -/**! -* @brief check all the events until there are none left and updates window structure attributes -*/ -RGFWDEF void RGFW_pollEvents(void); - -/**! -* @brief check all the events until there are none left and updates window structure attributes -* queues events if the queue is checked and/or requested -*/ -RGFWDEF void RGFW_stopCheckEvents(void); - -/** * @defgroup Input -* @{ */ - -/**! - * @brief returns true if the key is pressed during the current frame - * @param key the key code of the key you want to check - * @return The boolean value if the key is pressed or not -*/ -RGFWDEF RGFW_bool RGFW_isKeyPressed(RGFW_key key); - -/**! - * @brief returns true if the key was released during the current frame - * @param key the key code of the key you want to check - * @return The boolean value if the key is released or not -*/ -RGFWDEF RGFW_bool RGFW_isKeyReleased(RGFW_key key); - -/**! - * @brief returns true if the key is down - * @param key the key code of the key you want to check - * @return The boolean value if the key is down or not -*/ -RGFWDEF RGFW_bool RGFW_isKeyDown(RGFW_key key); - -/**! - * @brief returns true if the mouse button is pressed during the current frame - * @param button the mouse button code of the button you want to check - * @return The boolean value if the button is pressed or not -*/ -RGFWDEF RGFW_bool RGFW_isMousePressed(RGFW_mouseButton button); - -/**! - * @brief returns true if the mouse button is released during the current frame - * @param button the mouse button code of the button you want to check - * @return The boolean value if the button is released or not -*/ -RGFWDEF RGFW_bool RGFW_isMouseReleased(RGFW_mouseButton button); - -/**! - * @brief returns true if the mouse button is down - * @param button the mouse button code of the button you want to check - * @return The boolean value if the button is down or not -*/ -RGFWDEF RGFW_bool RGFW_isMouseDown(RGFW_mouseButton button); - -/**! - * @brief outputs the current x, y position of the mouse - * @param X [OUTPUT] a pointer for the output X value - * @param Y [OUTPUT] a pointer for the output Y value -*/ -RGFWDEF void RGFW_getMouseScroll(float* x, float* y); - -/**! - * @brief outputs the current x, y movement vector of the mouse - * @param X [OUTPUT] a pointer for the output X vector value - * @param Y [OUTPUT] a pointer for the output Y vector value -*/ -RGFWDEF void RGFW_getMouseVector(float* x, float* y); /** @} */ -/**! - * @brief creates a new window - * @param name the requested title of the window - * @param x the requested x position of the window - * @param y the requested y position of the window - * @param w the requested width of the window - * @param h the requested height of the window - * @param flags extra arguments ((u32)0 means no flags used) - * @return A pointer to the newly created window structure - * - * NOTE: (windows) if the executable has an icon resource named RGFW_ICON, it will be set as the initial icon for the window -*/ -RGFWDEF RGFW_window* RGFW_createWindow(const char* name, i32 x, i32 y, i32 w, i32 h, RGFW_windowFlags flags); - -/**! - * @brief creates a new window using a pre-allocated window structure - * @param name the requested title of the window - * @param x the requested x position of the window - * @param y the requested y position of the window - * @param w the requested width of the window - * @param h the requested height of the window - * @param flags extra arguments ((u32)0 means no flags used) - * @param win a pointer the pre-allocated window structure - * @return A pointer to the newly created window structure -*/ -RGFWDEF RGFW_window* RGFW_createWindowPtr(const char* name, i32 x, i32 y, i32 w, i32 h, RGFW_windowFlags flags, RGFW_window* win); - -/**! - * @brief creates a new surface structure - * @param win the source window of the surface - * @param data a pointer to the raw data of the structure (you allocate this) - * @param w the width the data - * @param h the height of the data - * @return A pointer to the newly created surface structure - * - * NOTE: when you create a surface using RGFW_createSurface / ptr, on X11 it uses the root window's visual - * this means it may fail to render on any other window if the visual does not match - * RGFW_window_createSurface and RGFW_window_createSurfacePtr exist only for X11 to address this issues - * Of course, you can also manually set the root window with RGFW_setRootWindow - */ -RGFWDEF RGFW_surface* RGFW_window_createSurface(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format); - -/**! - * @brief creates a new surface structure using a pre-allocated surface structure - * @param win the source window of the surface - * @param data a pointer to the raw data of the structure (you allocate this) - * @param w the width the data - * @param h the height of the data - * @param a pointer to the pre-allocated surface structure - * @return a bool if the creation was successful or not -*/ -RGFWDEF RGFW_bool RGFW_window_createSurfacePtr(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface); - -/**! - * @brief blits a surface stucture to the window - * @param win a pointer the window to blit to - * @param surface a pointer to the surface -*/ -RGFWDEF void RGFW_window_blitSurface(RGFW_window* win, RGFW_surface* surface); - -/**! - * @brief gets the position of the window | with RGFW_window.x and window.y - * @param x [OUTPUT] the x position of the window - * @param y [OUTPUT] the y position of the window - * @return a bool if the function was successful -*/ -RGFWDEF RGFW_bool RGFW_window_getPosition(RGFW_window* win, i32* x, i32* y); /*!< */ - -/**! - * @brief gets the size of the window | with RGFW_window.w and window.h - * @param win a pointer to the window - * @param w [OUTPUT] the width of the window - * @param h [OUTPUT] the height of the window - * @return a bool if the function was successful -*/ -RGFWDEF RGFW_bool RGFW_window_getSize(RGFW_window* win, i32* w, i32* h); - -/**! - * @brief gets the flags of the window | returns RGFW_window._flags - * @param win a pointer to the window - * @return the window flags -*/ -RGFWDEF u32 RGFW_window_getFlags(RGFW_window* win); - -/**! - * @brief returns the exit key assigned to the window - * @param win a pointer to the target window - * @return The key code assigned as the exit key -*/ -RGFWDEF RGFW_key RGFW_window_getExitKey(RGFW_window* win); - -/**! - * @brief sets the exit key for the window - * @param win a pointer to the target window - * @param key the key code to assign as the exit key -*/ -RGFWDEF void RGFW_window_setExitKey(RGFW_window* win, RGFW_key key); - -/**! - * @brief sets the types of events you want the window to receive - * @param win a pointer to the target window - * @param events the event flags to enable (use RGFW_allEventFlags for all) -*/ -RGFWDEF void RGFW_window_setEnabledEvents(RGFW_window* win, RGFW_eventFlag events); - -/**! - * @brief gets the currently enabled events for the window - * @param win a pointer to the target window - * @return The enabled event flags for the window -*/ -RGFWDEF RGFW_eventFlag RGFW_window_getEnabledEvents(RGFW_window* win); - -/**! - * @brief enables all events and disables selected ones - * @param win a pointer to the target window - * @param events the event flags to disable -*/ -RGFWDEF void RGFW_window_setDisabledEvents(RGFW_window* win, RGFW_eventFlag events); - -/**! - * @brief directly enables or disables a specific event or group of events - * @param win a pointer to the target window - * @param event the event flag or group of flags to modify - * @param state RGFW_TRUE to enable, RGFW_FALSE to disable -*/ -RGFWDEF void RGFW_window_setEventState(RGFW_window* win, RGFW_eventFlag event, RGFW_bool state); - -/**! - * @brief gets the user pointer associated with the window - * @param win a pointer to the target window - * @return The user-defined pointer stored in the window -*/ -RGFWDEF void* RGFW_window_getUserPtr(RGFW_window* win); - -/**! - * @brief sets a user pointer for the window - * @param win a pointer to the target window - * @param ptr a pointer to associate with the window -*/ -RGFWDEF void RGFW_window_setUserPtr(RGFW_window* win, void* ptr); - -/**! - * @brief retrieves the platform-specific window source pointer - * @param win a pointer to the target window - * @return A pointer to the internal RGFW_window_src structure -*/ -RGFWDEF RGFW_window_src* RGFW_window_getSrc(RGFW_window* win); - -/**! - * @brief sets the macOS layer object associated with the window - * @param win a pointer to the target window - * @param layer a pointer to the macOS layer object - * @note Only available on macOS platforms -*/ -RGFWDEF void RGFW_window_setLayer_OSX(RGFW_window* win, void* layer); - -/**! - * @brief retrieves the macOS view object associated with the window - * @param win a pointer to the target window - * @return A pointer to the macOS view object, or NULL if not on macOS -*/ -RGFWDEF void* RGFW_window_getView_OSX(RGFW_window* win); - -/**! - * @brief retrieves the macOS window object - * @param win a pointer to the target window - * @return A pointer to the macOS window object, or NULL if not on macOS -*/ -RGFWDEF void* RGFW_window_getWindow_OSX(RGFW_window* win); - -/**! - * @brief retrieves the HWND handle for the window - * @param win a pointer to the target window - * @return A pointer to the Windows HWND handle, or NULL if not on Windows -*/ -RGFWDEF void* RGFW_window_getHWND(RGFW_window* win); - -/**! - * @brief retrieves the HDC handle for the window - * @param win a pointer to the target window - * @return A pointer to the Windows HDC handle, or NULL if not on Windows -*/ -RGFWDEF void* RGFW_window_getHDC(RGFW_window* win); - -/**! - * @brief retrieves the X11 Window handle for the window - * @param win a pointer to the target window - * @return The X11 Window handle, or 0 if not on X11 -*/ -RGFWDEF u64 RGFW_window_getWindow_X11(RGFW_window* win); - -/**! - * @brief retrieves the Wayland surface handle for the window - * @param win a pointer to the target window - * @return A pointer to the Wayland wl_surface, or NULL if not on Wayland -*/ -RGFWDEF struct wl_surface* RGFW_window_getWindow_Wayland(RGFW_window* win); - -/** * @defgroup Window_management -* @{ */ - -/*! set the window flags (will undo flags if they don't match the old ones) */ -RGFWDEF void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags); - -/**! - * @brief polls and pops the next event from the window's event queue - * @param win a pointer to the target window - * @param event [OUTPUT] a pointer to store the retrieved event - * @return RGFW_TRUE if an event was found, RGFW_FALSE otherwise - * - * NOTE: Using this function without a loop may cause event lag. - * For multi-threaded systems, use RGFW_pollEvents combined with RGFW_window_checkQueuedEvent. - * - * Example: - * RGFW_event event; - * while (RGFW_window_checkEvent(win, &event)) { - * // handle event - * } -*/ -RGFWDEF RGFW_bool RGFW_window_checkEvent(RGFW_window* win, RGFW_event* event); - -/**! - * @brief pops the first queued event for the window - * @param win a pointer to the target window - * @param event [OUTPUT] a pointer to store the retrieved event - * @return RGFW_TRUE if an event was found, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_checkQueuedEvent(RGFW_window* win, RGFW_event* event); - -/**! - * @brief checks if a key was pressed while the window is in focus - * @param win a pointer to the target window - * @param key the key code to check - * @return RGFW_TRUE if the key was pressed, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_isKeyPressed(RGFW_window* win, RGFW_key key); - -/**! - * @brief checks if a key is currently being held down - * @param win a pointer to the target window - * @param key the key code to check - * @return RGFW_TRUE if the key is held down, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_isKeyDown(RGFW_window* win, RGFW_key key); - -/**! - * @brief checks if a key was released - * @param win a pointer to the target window - * @param key the key code to check - * @return RGFW_TRUE if the key was released, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_isKeyReleased(RGFW_window* win, RGFW_key key); - -/**! - * @brief checks if a mouse button was pressed - * @param win a pointer to the target window - * @param button the mouse button code to check - * @return RGFW_TRUE if the mouse button was pressed, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_isMousePressed(RGFW_window* win, RGFW_mouseButton button); - -/**! - * @brief checks if a mouse button is currently held down - * @param win a pointer to the target window - * @param button the mouse button code to check - * @return RGFW_TRUE if the mouse button is down, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_isMouseDown(RGFW_window* win, RGFW_mouseButton button); - -/**! - * @brief checks if a mouse button was released - * @param win a pointer to the target window - * @param button the mouse button code to check - * @return RGFW_TRUE if the mouse button was released, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_isMouseReleased(RGFW_window* win, RGFW_mouseButton button); - -/**! - * @brief checks if the mouse left the window (true only for the first frame) - * @param win a pointer to the target window - * @return RGFW_TRUE if the mouse left, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_didMouseLeave(RGFW_window* win); - -/**! - * @brief checks if the mouse entered the window (true only for the first frame) - * @param win a pointer to the target window - * @return RGFW_TRUE if the mouse entered, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_didMouseEnter(RGFW_window* win); - -/**! - * @brief checks if the mouse is currently inside the window bounds - * @param win a pointer to the target window - * @return RGFW_TRUE if the mouse is inside, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_isMouseInside(RGFW_window* win); - -/**! - * @brief checks if there is data being dragged into or within the window - * @param win a pointer to the target window - * @return RGFW_TRUE if data is being dragged, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_isDataDragging(RGFW_window* win); - -/**! - * @brief gets the position of a data drag - * @param win a pointer to the target window - * @param x [OUTPUT] pointer to store the x position - * @param y [OUTPUT] pointer to store the y position - * @return RGFW_TRUE if there is an active drag, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_getDataDrag(RGFW_window* win, i32* x, i32* y); - -/**! - * @brief checks if a data drop occurred in the window (first frame only) - * @param win a pointer to the target window - * @return RGFW_TRUE if data was dropped, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_didDataDrop(RGFW_window* win); - -/**! - * @brief retrieves files from a data drop (drag and drop) - * @param win a pointer to the target window - * @param files [OUTPUT] a pointer to the array of file paths - * @param count [OUTPUT] the number of dropped files - * @return RGFW_TRUE if a data drop occurred, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_getDataDrop(RGFW_window* win, const char*** files, size_t* count); - -/**! - * @brief closes the window and frees its associated structure - * @param win a pointer to the target window -*/ -RGFWDEF void RGFW_window_close(RGFW_window* win); - -/**! - * @brief closes the window without freeing its structure - * @param win a pointer to the target window -*/ -RGFWDEF void RGFW_window_closePtr(RGFW_window* win); - -/**! - * @brief moves the window to a new position on the screen - * @param win a pointer to the target window - * @param x the new x position - * @param y the new y position -*/ -RGFWDEF void RGFW_window_move(RGFW_window* win, i32 x, i32 y); - -#ifndef RGFW_NO_MONITOR -/**! - * @brief moves the window to a specific monitor - * @param win a pointer to the target window - * @param m the target monitor -*/ -RGFWDEF void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor m); -#endif - -/**! - * @brief resizes the window to the given dimensions - * @param win a pointer to the target window - * @param w the new width - * @param h the new height -*/ -RGFWDEF void RGFW_window_resize(RGFW_window* win, i32 w, i32 h); - -/**! - * @brief sets the aspect ratio of the window - * @param win a pointer to the target window - * @param w the width ratio - * @param h the height ratio -*/ -RGFWDEF void RGFW_window_setAspectRatio(RGFW_window* win, i32 w, i32 h); - -/**! - * @brief sets the minimum size of the window - * @param win a pointer to the target window - * @param w the minimum width - * @param h the minimum height -*/ -RGFWDEF void RGFW_window_setMinSize(RGFW_window* win, i32 w, i32 h); - -/**! - * @brief sets the maximum size of the window - * @param win a pointer to the target window - * @param w the maximum width - * @param h the maximum height -*/ -RGFWDEF void RGFW_window_setMaxSize(RGFW_window* win, i32 w, i32 h); - -/**! - * @brief sets focus to the window - * @param win a pointer to the target window -*/ -RGFWDEF void RGFW_window_focus(RGFW_window* win); - -/**! - * @brief checks if the window is currently in focus - * @param win a pointer to the target window - * @return RGFW_TRUE if the window is in focus, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_isInFocus(RGFW_window* win); - -/**! - * @brief raises the window to the top of the stack - * @param win a pointer to the target window -*/ -RGFWDEF void RGFW_window_raise(RGFW_window* win); - -/**! - * @brief maximizes the window - * @param win a pointer to the target window -*/ -RGFWDEF void RGFW_window_maximize(RGFW_window* win); - -/**! - * @brief toggles fullscreen mode for the window - * @param win a pointer to the target window - * @param fullscreen RGFW_TRUE to enable fullscreen, RGFW_FALSE to disable -*/ -RGFWDEF void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen); - -/**! - * @brief centers the window on the screen - * @param win a pointer to the target window -*/ -RGFWDEF void RGFW_window_center(RGFW_window* win); - -/**! - * @brief minimizes the window - * @param win a pointer to the target window -*/ -RGFWDEF void RGFW_window_minimize(RGFW_window* win); - -/**! - * @brief restores the window from minimized state - * @param win a pointer to the target window -*/ -RGFWDEF void RGFW_window_restore(RGFW_window* win); - -/**! - * @brief makes the window a floating window - * @param win a pointer to the target window - * @param floating RGFW_TRUE to float, RGFW_FALSE to disable -*/ -RGFWDEF void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating); - -/**! - * @brief sets the opacity level of the window - * @param win a pointer to the target window - * @param opacity the opacity level (0–255) -*/ -RGFWDEF void RGFW_window_setOpacity(RGFW_window* win, u8 opacity); - -/**! - * @brief toggles window borders - * @param win a pointer to the target window - * @param border RGFW_TRUE for bordered, RGFW_FALSE for borderless -*/ -RGFWDEF void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border); - -/**! - * @brief checks if the window is borderless - * @param win a pointer to the target window - * @return RGFW_TRUE if borderless, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_borderless(RGFW_window* win); - -/**! - * @brief toggles drag-and-drop (DND) support for the window - * @param win a pointer to the target window - * @param allow RGFW_TRUE to allow DND, RGFW_FALSE to disable - * @note RGFW_windowAllowDND must still be passed when creating the window -*/ -RGFWDEF void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow); - -/**! - * @brief checks if drag-and-drop (DND) is allowed - * @param win a pointer to the target window - * @return RGFW_TRUE if DND is enabled, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_allowsDND(RGFW_window* win); - -#ifndef RGFW_NO_PASSTHROUGH -/**! - * @brief toggles mouse passthrough for the window - * @param win a pointer to the target window - * @param passthrough RGFW_TRUE to enable passthrough, RGFW_FALSE to disable -*/ -RGFWDEF void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough); -#endif - -/**! - * @brief renames the window - * @param win a pointer to the target window - * @param name the new title string for the window -*/ -RGFWDEF void RGFW_window_setName(RGFW_window* win, const char* name); - -/**! - * @brief sets the icon for the window and taskbar - * @param win a pointer to the target window - * @param data the image data - * @param w the width of the icon - * @param h the height of the icon - * @param format the image format - * @return RGFW_TRUE if successful, RGFW_FALSE otherwise - * - * NOTE: The image may be resized by default. -*/ -RGFWDEF RGFW_bool RGFW_window_setIcon(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format); - -/**! - * @brief sets the icon for the window and/or taskbar - * @param win a pointer to the target window - * @param data the image data - * @param w the width of the icon - * @param h the height of the icon - * @param format the image format - * @param type the target icon type (taskbar, window, or both) - * @return RGFW_TRUE if successful, RGFW_FALSE otherwise -*/ -RGFWDEF RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_icon type); - -/**! - * @brief sets the mouse icon for the window using a loaded bitmap - * @param win a pointer to the target window - * @param mouse a pointer to the RGFW_mouse struct containing the icon -*/ -RGFWDEF void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse); - -/**! - * @brief Sets the mouse to a standard system cursor. - * @param win The target window. - * @param mouse The standard cursor type (see RGFW_MOUSE enum). - * @return True if the standard cursor was successfully applied. -*/ -RGFWDEF RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, RGFW_mouseIcons mouse); - -/**! - * @brief Sets the mouse to the default cursor icon. - * @param win The target window. - * @return True if the default cursor was successfully set. -*/ -RGFWDEF RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win); - -/**! - * @brief Locks the cursor to the center of the window. - * @param win The target window. - * - * While the cursor is held, X and Y report raw mouse movement data. - * Useful for 3D camera or first-person movement systems. -*/ -RGFWDEF void RGFW_window_holdMouse(RGFW_window* win); - -/**! - * @brief Returns true if the mouse is currently held by RGFW. - * @param win The target window. - * @return True if the mouse is being held. -*/ -RGFWDEF RGFW_bool RGFW_window_isHoldingMouse(RGFW_window* win); - -/**! - * @brief Releases the mouse so it can move freely again. - * @param win The target window. -*/ -RGFWDEF void RGFW_window_unholdMouse(RGFW_window* win); - -/**! - * @brief Hides the window from view. - * @param win The target window. -*/ -RGFWDEF void RGFW_window_hide(RGFW_window* win); - -/**! - * @brief Shows the window if it was hidden. - * @param win The target window. -*/ -RGFWDEF void RGFW_window_show(RGFW_window* win); - -/**! - * @brief Sets whether the window should close. - * @param win The target window. - * @param shouldClose True to signal the window should close, false to keep it open. - * - * This can override or trigger the `RGFW_window_shouldClose` state by modifying window flags. -*/ -RGFWDEF void RGFW_window_setShouldClose(RGFW_window* win, RGFW_bool shouldClose); - -/**! - * @brief Retrieves the current global mouse position. - * @param x [OUTPUT] Pointer to store the X position of the mouse on the screen. - * @param y [OUTPUT] Pointer to store the Y position of the mouse on the screen. - * @return True if the position was successfully retrieved. -*/ -RGFWDEF RGFW_bool RGFW_getGlobalMouse(i32* x, i32* y); - -/**! - * @brief Retrieves the mouse position relative to the window. - * @param win The target window. - * @param x [OUTPUT] Pointer to store the X position within the window. - * @param y [OUTPUT] Pointer to store the Y position within the window. - * @return True if the position was successfully retrieved. -*/ -RGFWDEF RGFW_bool RGFW_window_getMouse(RGFW_window* win, i32* x, i32* y); - -/**! - * @brief Shows or hides the mouse cursor for the window. - * @param win The target window. - * @param show True to show the mouse, false to hide it. -*/ -RGFWDEF void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show); - -/**! - * @brief Checks if the mouse is currently hidden in the window. - * @param win The target window. - * @return True if the mouse is hidden. -*/ -RGFWDEF RGFW_bool RGFW_window_isMouseHidden(RGFW_window* win); - -/**! - * @brief Moves the mouse to the specified position within the window. - * @param win The target window. - * @param x The new X position. - * @param y The new Y position. -*/ -RGFWDEF void RGFW_window_moveMouse(RGFW_window* win, i32 x, i32 y); - -/**! - * @brief Checks if the window should close. - * @param win The target window. - * @return True if the window should close (for example, if ESC was pressed or a close event occurred). -*/ -RGFWDEF RGFW_bool RGFW_window_shouldClose(RGFW_window* win); - -/**! - * @brief Checks if the window is currently fullscreen. - * @param win The target window. - * @return True if the window is fullscreen. -*/ -RGFWDEF RGFW_bool RGFW_window_isFullscreen(RGFW_window* win); - -/**! - * @brief Checks if the window is currently hidden. - * @param win The target window. - * @return True if the window is hidden. -*/ -RGFWDEF RGFW_bool RGFW_window_isHidden(RGFW_window* win); - -/**! - * @brief Checks if the window is minimized. - * @param win The target window. - * @return True if the window is minimized. -*/ -RGFWDEF RGFW_bool RGFW_window_isMinimized(RGFW_window* win); - -/**! - * @brief Checks if the window is maximized. - * @param win The target window. - * @return True if the window is maximized. -*/ -RGFWDEF RGFW_bool RGFW_window_isMaximized(RGFW_window* win); - -/**! - * @brief Checks if the window is floating. - * @param win The target window. - * @return True if the window is floating. -*/ -RGFWDEF RGFW_bool RGFW_window_isFloating(RGFW_window* win); -/** @} */ - -/** * @defgroup Monitor -* @{ */ - -#ifndef RGFW_NO_MONITOR -/**! - * @brief Scales the window to match its monitor’s resolution. - * @param win The target window. - * - * This function is automatically called when the flag `RGFW_scaleToMonitor` - * is used during window creation. -*/ -RGFWDEF void RGFW_window_scaleToMonitor(RGFW_window* win); - -/**! - * @brief Retrieves the monitor structure associated with the window. - * @param win The target window. - * @return The monitor structure of the window. -*/ -RGFWDEF RGFW_monitor RGFW_window_getMonitor(RGFW_window* win); -#endif - -/** @} */ - -/** * @defgroup Clipboard -* @{ */ - -/**! - * @brief Reads clipboard data. - * @param size [OUTPUT] A pointer that will be filled with the size of the clipboard data. - * @return A pointer to the clipboard data as a string. -*/ -RGFWDEF const char* RGFW_readClipboard(size_t* size); - -/**! - * @brief Reads clipboard data into a provided buffer, or returns the required length if str is NULL. - * @param str [OUTPUT] A pointer to the buffer that will receive the clipboard data (or NULL to get required size). - * @param strCapacity The capacity of the provided buffer. - * @return The number of bytes read or required length of clipboard data. -*/ -RGFWDEF RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity); - -/**! - * @brief Writes text to the clipboard. - * @param text The text to be written to the clipboard. - * @param textLen The length of the text being written. -*/ -RGFWDEF void RGFW_writeClipboard(const char* text, u32 textLen); -/** @} */ - - - -/** * @defgroup error handling -* @{ */ -/**! - * @brief Sets the callback function to handle debug messages from RGFW. - * @param func The function pointer to be used as the debug callback. - * @return The previously set debug callback function. -*/ -RGFWDEF RGFW_debugfunc RGFW_setDebugCallback(RGFW_debugfunc func); - -/**! - * @brief Sends a debug message manually through the currently set debug callback. - * @param type The type of debug message being sent. - * @param err The associated error code. - * @param msg The debug message text. -*/ -RGFWDEF void RGFW_sendDebugInfo(RGFW_debugType type, RGFW_errorCode err, const char* msg); -/** @} */ - -/** - - - event callbacks. - These are completely optional, so you can use the normal - RGFW_checkEvent() method if you prefer that - -* @defgroup Callbacks -* @{ -*/ - -/**! - * @brief Sets the callback function for window move events. - * @param func The function to be called when the window is moved. - * @return The previously set callback function, if any. -*/ -RGFWDEF RGFW_windowMovedfunc RGFW_setWindowMovedCallback(RGFW_windowMovedfunc func); - -/**! - * @brief Sets the callback function for window resize events. - * @param func The function to be called when the window is resized. - * @return The previously set callback function, if any. -*/ -RGFWDEF RGFW_windowResizedfunc RGFW_setWindowResizedCallback(RGFW_windowResizedfunc func); - -/**! - * @brief Sets the callback function for window quit events. - * @param func The function to be called when the window receives a quit signal. - * @return The previously set callback function, if any. -*/ -RGFWDEF RGFW_windowQuitfunc RGFW_setWindowQuitCallback(RGFW_windowQuitfunc func); - -/**! - * @brief Sets the callback function for mouse move events. - * @param func The function to be called when the mouse moves within the window. - * @return The previously set callback function, if any. -*/ -RGFWDEF RGFW_mousePosfunc RGFW_setMousePosCallback(RGFW_mousePosfunc func); - -/**! - * @brief Sets the callback function for window refresh events. - * @param func The function to be called when the window needs to be refreshed. - * @return The previously set callback function, if any. -*/ -RGFWDEF RGFW_windowRefreshfunc RGFW_setWindowRefreshCallback(RGFW_windowRefreshfunc func); - -/**! - * @brief Sets the callback function for focus change events. - * @param func The function to be called when the window gains or loses focus. - * @return The previously set callback function, if any. -*/ -RGFWDEF RGFW_focusfunc RGFW_setFocusCallback(RGFW_focusfunc func); - -/**! - * @brief Sets the callback function for mouse notification events. - * @param func The function to be called when a mouse notification event occurs. - * @return The previously set callback function, if any. -*/ -RGFWDEF RGFW_mouseNotifyfunc RGFW_setMouseNotifyCallback(RGFW_mouseNotifyfunc func); - -/**! - * @brief Sets the callback function for data drop events. - * @param func The function to be called when data is dropped into the window. - * @return The previously set callback function, if any. -*/ -RGFWDEF RGFW_dataDropfunc RGFW_setDataDropCallback(RGFW_dataDropfunc func); - -/**! - * @brief Sets the callback function for the start of a data drag event. - * @param func The function to be called when data dragging begins. - * @return The previously set callback function, if any. -*/ -RGFWDEF RGFW_dataDragfunc RGFW_setDataDragCallback(RGFW_dataDragfunc func); - -/**! - * @brief Sets the callback function for key press and release events. - * @param func The function to be called when a key is pressed or released. - * @return The previously set callback function, if any. -*/ -RGFWDEF RGFW_keyfunc RGFW_setKeyCallback(RGFW_keyfunc func); - -/**! - * @brief Sets the callback function for mouse button press and release events. - * @param func The function to be called when a mouse button is pressed or released. - * @return The previously set callback function, if any. -*/ -RGFWDEF RGFW_mouseButtonfunc RGFW_setMouseButtonCallback(RGFW_mouseButtonfunc func); - -/**! - * @brief Sets the callback function for mouse scroll events. - * @param func The function to be called when the mouse wheel is scrolled. - * @return The previously set callback function, if any. -*/ -RGFWDEF RGFW_mouseScrollfunc RGFW_setMouseScrollCallback(RGFW_mouseScrollfunc func); - -/**! - * @brief Sets the callback function for window maximize events. - * @param func The function to be called when the window is maximized. - * @return The previously set callback function, if any. -*/ -RGFWDEF RGFW_windowMaximizedfunc RGFW_setWindowMaximizedCallback(RGFW_windowMaximizedfunc func); - -/**! - * @brief Sets the callback function for window minimize events. - * @param func The function to be called when the window is minimized. - * @return The previously set callback function, if any. -*/ -RGFWDEF RGFW_windowMinimizedfunc RGFW_setWindowMinimizedCallback(RGFW_windowMinimizedfunc func); - -/**! - * @brief Sets the callback function for window restore events. - * @param func The function to be called when the window is restored from a minimized or maximized state. - * @return The previously set callback function, if any. -*/ -RGFWDEF RGFW_windowRestoredfunc RGFW_setWindowRestoredCallback(RGFW_windowRestoredfunc func); - -/**! - * @brief Sets the callback function for DPI (scale) update events. - * @param func The function to be called when the window’s DPI or scale changes. - * @return The previously set callback function, if any. -*/ -RGFWDEF RGFW_scaleUpdatedfunc RGFW_setScaleUpdatedCallback(RGFW_scaleUpdatedfunc func); -/** @} */ - -/** * @defgroup graphics_API -* @{ */ - -/*! native rendering API functions */ -#if defined(RGFW_OPENGL) -/* these are native opengl specific functions and will NOT work with EGL */ - -/*!< make the window the current OpenGL drawing context - - NOTE: - if you want to switch the graphics context's thread, - you have to run RGFW_window_makeCurrentContext_OpenGL(NULL); on the old thread - then RGFW_window_makeCurrentContext_OpenGL(valid_window) on the new thread -*/ - -/**! - * @brief Sets the global OpenGL hints to the specified pointer. - * @param hints A pointer to the RGFW_glHints structure containing the desired OpenGL settings. -*/ -RGFWDEF void RGFW_setGlobalHints_OpenGL(RGFW_glHints* hints); - -/**! - * @brief Resets the global OpenGL hints to their default values. -*/ -RGFWDEF void RGFW_resetGlobalHints_OpenGL(void); - -/**! - * @brief Gets the current global OpenGL hints pointer. - * @return A pointer to the currently active RGFW_glHints structure. -*/ -RGFWDEF RGFW_glHints* RGFW_getGlobalHints_OpenGL(void); - -/**! - * @brief Creates and allocates an OpenGL context for the specified window. - * @param win A pointer to the target RGFW_window. - * @param hints A pointer to an RGFW_glHints structure defining context creation parameters. - * @return A pointer to the newly created RGFW_glContext. -*/ -RGFWDEF RGFW_glContext* RGFW_window_createContext_OpenGL(RGFW_window* win, RGFW_glHints* hints); - -/**! - * @brief Creates an OpenGL context for the specified window using a preallocated context structure. - * @param win A pointer to the target RGFW_window. - * @param ctx A pointer to an already allocated RGFW_glContext structure. - * @param hints A pointer to an RGFW_glHints structure defining context creation parameters. - * @return RGFW_TRUE on success, RGFW_FALSE on failure. -*/ -RGFWDEF RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints); - -/**! - * @brief Retrieves the OpenGL context associated with a window. - * @param win A pointer to the RGFW_window. - * @return A pointer to the associated RGFW_glContext, or NULL if none exists or if the context is EGL-based. -*/ -RGFWDEF RGFW_glContext* RGFW_window_getContext_OpenGL(RGFW_window* win); - -/**! - * @brief Deletes and frees the OpenGL context. - * @param win A pointer to the RGFW_window. - * @param ctx A pointer to the RGFW_glContext to delete. - * - * @note This is automatically called by RGFW_window_close if the window’s context is not NULL. -*/ -RGFWDEF void RGFW_window_deleteContext_OpenGL(RGFW_window* win, RGFW_glContext* ctx); - -/**! - * @brief Deletes the OpenGL context without freeing its memory. - * @param win A pointer to the RGFW_window. - * @param ctx A pointer to the RGFW_glContext to delete. - * - * @note This is automatically called by RGFW_window_close if the window’s context is not NULL. -*/ -RGFWDEF void RGFW_window_deleteContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx); - -/**! - * @brief Retrieves the native source context from an RGFW_glContext. - * @param ctx A pointer to the RGFW_glContext. - * @return A pointer to the native OpenGL context handle. -*/ -RGFWDEF void* RGFW_glContext_getSourceContext(RGFW_glContext* ctx); - -/**! - * @brief Makes the specified window the current OpenGL rendering target. - * @param win A pointer to the RGFW_window to make current. - * - * @note This is typically called internally by RGFW_window_makeCurrent. -*/ -RGFWDEF void RGFW_window_makeCurrentWindow_OpenGL(RGFW_window* win); - -/**! - * @brief Makes the OpenGL context of the specified window current. - * @param win A pointer to the RGFW_window whose context should be made current. - * - * @note To move a context between threads, call RGFW_window_makeCurrentContext_OpenGL(NULL) - * on the old thread before making it current on the new one. -*/ -RGFWDEF void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win); - -/**! - * @brief Swaps the OpenGL buffers for the specified window. - * @param win A pointer to the RGFW_window whose buffers should be swapped. - * - * @note Typically called by RGFW_window_swapInterval. -*/ -RGFWDEF void RGFW_window_swapBuffers_OpenGL(RGFW_window* win); - -/**! - * @brief Retrieves the current OpenGL context. - * @return A pointer to the currently active OpenGL context (GLX, WGL, Cocoa, or WebGL backend). -*/ -RGFWDEF void* RGFW_getCurrentContext_OpenGL(void); - -/**! - * @brief Retrieves the current OpenGL window. - * @return A pointer to the RGFW_window currently bound as the OpenGL context target. -*/ -RGFWDEF RGFW_window* RGFW_getCurrentWindow_OpenGL(void); - -/**! - * @brief Sets the OpenGL swap interval (vsync). - * @param win A pointer to the RGFW_window. - * @param swapInterval The desired swap interval value (0 to disable vsync, 1 to enable). -*/ -RGFWDEF void RGFW_window_swapInterval_OpenGL(RGFW_window* win, i32 swapInterval); - -/**! - * @brief Retrieves the address of a native OpenGL procedure. - * @param procname The name of the OpenGL function to look up. - * @return A pointer to the function, or NULL if not found. -*/ -RGFWDEF RGFW_proc RGFW_getProcAddress_OpenGL(const char* procname); - -/**! - * @brief Checks whether a specific OpenGL or OpenGL ES API extension is supported. - * @param extension The name of the extension to check. - * @param len The length of the extension string. - * @return RGFW_TRUE if supported, RGFW_FALSE otherwise. -*/ -RGFWDEF RGFW_bool RGFW_extensionSupported_OpenGL(const char* extension, size_t len); - -/**! - * @brief Checks whether a specific platform-dependent OpenGL extension is supported. - * @param extension The name of the extension to check. - * @param len The length of the extension string. - * @return RGFW_TRUE if supported, RGFW_FALSE otherwise. -*/ -RGFWDEF RGFW_bool RGFW_extensionSupportedPlatform_OpenGL(const char* extension, size_t len); - -/* these are EGL specific functions, they may fallback to OpenGL */ -#ifdef RGFW_EGL -/**! - * @brief Creates and allocates an OpenGL/EGL context for the specified window. - * @param win A pointer to the target RGFW_window. - * @param hints A pointer to an RGFW_glHints structure defining context creation parameters. - * @return A pointer to the newly created RGFW_eglContext. -*/ -RGFWDEF RGFW_eglContext* RGFW_window_createContext_EGL(RGFW_window* win, RGFW_glHints* hints); - -/**! - * @brief Creates an OpenGL/EGL context for the specified window using a preallocated context structure. - * @param win A pointer to the target RGFW_window. - * @param ctx A pointer to an already allocated RGFW_eglContext structure. - * @param hints A pointer to an RGFW_glHints structure defining context creation parameters. - * @return RGFW_TRUE on success, RGFW_FALSE on failure. -*/ -RGFWDEF RGFW_bool RGFW_window_createContextPtr_EGL(RGFW_window* win, RGFW_eglContext* ctx, RGFW_glHints* hints); - -/**! - * @brief Frees and deletes an OpenGL/EGL context. - * @param win A pointer to the RGFW_window. - * @param ctx A pointer to the RGFW_eglContext to delete. - * - * @note Automatically called by RGFW_window_close if RGFW owns the context. -*/ -RGFWDEF void RGFW_window_deleteContext_EGL(RGFW_window* win, RGFW_eglContext* ctx); - -/**! - * @brief Deletes an OpenGL/EGL context without freeing its memory. - * @param win A pointer to the RGFW_window. - * @param ctx A pointer to the RGFW_eglContext to delete. - * - * @note Automatically called by RGFW_window_close if RGFW owns the context. -*/ -RGFWDEF void RGFW_window_deleteContextPtr_EGL(RGFW_window* win, RGFW_eglContext* ctx); - -/**! - * @brief Retrieves the OpenGL/EGL context associated with a window. - * @param win A pointer to the RGFW_window. - * @return A pointer to the associated RGFW_eglContext, or NULL if none exists or if the context is a native OpenGL context. -*/ -RGFWDEF RGFW_eglContext* RGFW_window_getContext_EGL(RGFW_window* win); - -/**! - * @brief Retrieves the EGL display handle. - * @return A pointer to the native EGLDisplay. -*/ -RGFWDEF void* RGFW_getDisplay_EGL(void); - -/**! - * @brief Retrieves the native source context from an RGFW_eglContext. - * @param ctx A pointer to the RGFW_eglContext. - * @return A pointer to the native EGLContext handle. -*/ -RGFWDEF void* RGFW_eglContext_getSourceContext(RGFW_eglContext* ctx); - -/**! - * @brief Retrieves the EGL surface handle from an RGFW_eglContext. - * @param ctx A pointer to the RGFW_eglContext. - * @return A pointer to the EGLSurface associated with the context. -*/ -RGFWDEF void* RGFW_eglContext_getSurface(RGFW_eglContext* ctx); - -/**! - * @brief Retrieves the Wayland EGL window handle from an RGFW_eglContext. - * @param ctx A pointer to the RGFW_eglContext. - * @return A pointer to the wl_egl_window associated with the EGL context. -*/ -RGFWDEF struct wl_egl_window* RGFW_eglContext_wlEGLWindow(RGFW_eglContext* ctx); - -/**! - * @brief Swaps the EGL buffers for the specified window. - * @param win A pointer to the RGFW_window whose buffers should be swapped. - * - * @note Typically called by RGFW_window_swapInterval. -*/ -RGFWDEF void RGFW_window_swapBuffers_EGL(RGFW_window* win); - -/**! - * @brief Makes the specified window the current EGL rendering target. - * @param win A pointer to the RGFW_window to make current. - * - * @note This is typically called internally by RGFW_window_makeCurrent. -*/ -RGFWDEF void RGFW_window_makeCurrentWindow_EGL(RGFW_window* win); - -/**! - * @brief Makes the EGL context of the specified window current. - * @param win A pointer to the RGFW_window whose context should be made current. - * - * @note To move a context between threads, call RGFW_window_makeCurrentContext_EGL(NULL) - * on the old thread before making it current on the new one. -*/ -RGFWDEF void RGFW_window_makeCurrentContext_EGL(RGFW_window* win); - -/**! - * @brief Retrieves the current EGL context. - * @return A pointer to the currently active EGLContext. -*/ -RGFWDEF void* RGFW_getCurrentContext_EGL(void); - -/**! - * @brief Retrieves the current EGL window. - * @return A pointer to the RGFW_window currently bound as the EGL context target. -*/ -RGFWDEF RGFW_window* RGFW_getCurrentWindow_EGL(void); - -/**! - * @brief Sets the EGL swap interval (vsync). - * @param win A pointer to the RGFW_window. - * @param swapInterval The desired swap interval value (0 to disable vsync, 1 to enable). -*/ -RGFWDEF void RGFW_window_swapInterval_EGL(RGFW_window* win, i32 swapInterval); - -/**! - * @brief Retrieves the address of a native OpenGL or OpenGL ES procedure in an EGL context. - * @param procname The name of the OpenGL function to look up. - * @return A pointer to the function, or NULL if not found. -*/ -RGFWDEF RGFW_proc RGFW_getProcAddress_EGL(const char* procname); - -/**! - * @brief Checks whether a specific OpenGL or OpenGL ES API extension is supported in the current EGL context. - * @param extension The name of the extension to check. - * @param len The length of the extension string. - * @return RGFW_TRUE if supported, RGFW_FALSE otherwise. -*/ -RGFWDEF RGFW_bool RGFW_extensionSupported_EGL(const char* extension, size_t len); - -/**! - * @brief Checks whether a specific platform-dependent EGL extension is supported in the current context. - * @param extension The name of the extension to check. - * @param len The length of the extension string. - * @return RGFW_TRUE if supported, RGFW_FALSE otherwise. -*/ -RGFWDEF RGFW_bool RGFW_extensionSupportedPlatform_EGL(const char* extension, size_t len); -#endif -#endif - -#ifdef RGFW_VULKAN -#include - -/* if you don't want to use the above macros */ - -/**! - * @brief Retrieves the Vulkan instance extensions required by RGFW. - * @param count [OUTPUT] A pointer that will receive the number of required extensions (typically 2). - * @return A pointer to a static array of required Vulkan instance extension names. -*/ -RGFWDEF const char** RGFW_getRequiredInstanceExtensions_Vulkan(size_t* count); - -/**! - * @brief Creates a Vulkan surface for the specified window. - * @param win A pointer to the RGFW_window for which to create the Vulkan surface. - * @param instance The Vulkan instance used to create the surface. - * @param surface [OUTPUT] A pointer to a VkSurfaceKHR handle that will receive the created surface. - * @return A VkResult indicating success or failure. -*/ -RGFWDEF VkResult RGFW_window_createSurface_Vulkan(RGFW_window* win, VkInstance instance, VkSurfaceKHR* surface); - -/**! - * @brief Checks whether the specified Vulkan physical device and queue family support presentation for RGFW. - * @param instance The Vulkan instance. - * @param physicalDevice The Vulkan physical device to check. - * @param queueFamilyIndex The index of the queue family to query for presentation support. - * @return RGFW_TRUE if presentation is supported, RGFW_FALSE otherwise. -*/ -RGFWDEF RGFW_bool RGFW_getPresentationSupport_Vulkan(VkInstance instance, VkPhysicalDevice physicalDevice, u32 queueFamilyIndex); -#endif - -#ifdef RGFW_DIRECTX -#ifndef RGFW_WINDOWS - #undef RGFW_DIRECTX -#else - #define OEMRESOURCE - #include - - #ifndef __cplusplus - #define __uuidof(T) IID_##T - #endif -/**! - * @brief Creates a DirectX swap chain for the specified RGFW window. - * @param win A pointer to the RGFW_window for which to create the swap chain. - * @param pFactory A pointer to the IDXGIFactory used to create the swap chain. - * @param pDevice A pointer to the DirectX device (e.g., ID3D11Device or ID3D12Device). - * @param swapchain [OUTPUT] A pointer to an IDXGISwapChain pointer that will receive the created swap chain. - * @return An integer result code (0 on success, or a DirectX error code on failure). -*/ -RGFWDEF int RGFW_window_createSwapChain_DirectX(RGFW_window* win, IDXGIFactory* pFactory, IUnknown* pDevice, IDXGISwapChain** swapchain); -#endif -#endif - -#ifdef RGFW_WEBGPU - #include - /**! - * @brief Creates a WebGPU surface for the specified RGFW window. - * @param window A pointer to the RGFW_window for which to create the surface. - * @param instance The WebGPU instance used to create the surface. - * @return The created WGPUSurface handle. - */ - RGFWDEF WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance instance); -#endif - -/** @} */ - -/** * @defgroup Supporting -* @{ */ - -/**! - * @brief Sets the root (main) RGFW window. - * @param win A pointer to the RGFW_window to set as the root window. -*/ -RGFWDEF void RGFW_setRootWindow(RGFW_window* win); - -/**! - * @brief Retrieves the current root RGFW window. - * @return A pointer to the current root RGFW_window. -*/ -RGFWDEF RGFW_window* RGFW_getRootWindow(void); - -/**! - * @brief Pushes an event into the standard RGFW event queue. - * @param event A pointer to the RGFW_event to be added to the queue. -*/ -RGFWDEF void RGFW_eventQueuePush(const RGFW_event* event); - -/**! - * @brief Clears all events from the RGFW event queue without processing them. -*/ -RGFWDEF void RGFW_eventQueueFlush(void); - -/**! - * @brief Pops the next event from the RGFW event queue for the specified window. - * @param win A pointer to the RGFW_window to retrieve an event for. - * @return A pointer to the popped RGFW_event, or NULL if the queue is empty. -*/ -RGFWDEF RGFW_event* RGFW_eventQueuePop(RGFW_window* win); - -/**! - * @brief Converts an API keycode to the RGFW unmapped (physical) key. - * @param keycode The platform-specific keycode. - * @return The corresponding RGFW keycode. -*/ -RGFWDEF u32 RGFW_apiKeyToRGFW(u32 keycode); - -/**! - * @brief Converts an RGFW keycode to the unmapped (physical) API key. - * @param keycode The RGFW keycode. - * @return The corresponding platform-specific keycode. -*/ -RGFWDEF u32 RGFW_rgfwToApiKey(u32 keycode); - -/**! - * @brief Converts an RGFW keycode to the mapped character representation. - * @param keycode The RGFW keycode. - * @return The corresponding key character. -*/ -RGFWDEF u8 RGFW_rgfwToKeyChar(u32 keycode); - -/**! - * @brief Retrieves the size of the RGFW_info structure. - * @return The size (in bytes) of RGFW_info. -*/ -RGFWDEF size_t RGFW_sizeofInfo(void); - -/**! - * @brief Initializes the RGFW library. - * @return 0 on success, or a negative error code on failure. - * @note This is automatically called when the first window is created. -*/ -RGFWDEF i32 RGFW_init(void); - -/**! - * @brief Deinitializes the RGFW library. - * @note This is automatically called when the last open window is closed. -*/ -RGFWDEF void RGFW_deinit(void); - -/**! - * @brief Initializes RGFW using a user-provided RGFW_info structure. - * @param info A pointer to an RGFW_info structure to be used for initialization. - * @return 0 on success, or a negative error code on failure. -*/ -RGFWDEF i32 RGFW_init_ptr(RGFW_info* info); - -/**! - * @brief Deinitializes a specific RGFW instance stored in the provided RGFW_info pointer. - * @param info A pointer to the RGFW_info structure representing the instance to deinitialize. -*/ -RGFWDEF void RGFW_deinit_ptr(RGFW_info* info); - -/**! - * @brief Sets the global RGFW_info structure pointer. - * @param info A pointer to the RGFW_info structure to set. -*/ -RGFWDEF void RGFW_setInfo(RGFW_info* info); - -/**! - * @brief Retrieves the global RGFW_info structure pointer. - * @return A pointer to the current RGFW_info structure. -*/ -RGFWDEF RGFW_info* RGFW_getInfo(void); - -/** @} */ #endif /* RGFW_HEADER */ - -#if !defined(RGFW_NATIVE_HEADER) && (defined(RGFW_NATIVE) || defined(RGFW_IMPLEMENTATION)) -#define RGFW_NATIVE_HEADER - #if (defined(RGFW_OPENGL) || defined(RGFW_WEGL)) && defined(_MSC_VER) - #pragma comment(lib, "opengl32") - #endif - - #ifdef RGFW_OPENGL - struct RGFW_eglContext { - void* ctx; - void* surface; - struct wl_egl_window* eglWindow; - }; - - typedef union RGFW_gfxContext { - RGFW_glContext* native; - RGFW_eglContext* egl; - } RGFW_gfxContext; - - typedef RGFW_ENUM(u32, RGFW_gfxContextType) { - RGFW_gfxNativeOpenGL = RGFW_BIT(0), - RGFW_gfxEGL = RGFW_BIT(1), - RGFW_gfxOwnedByRGFW = RGFW_BIT(2) - }; - #endif - - /*! source data for the window (used by the APIs) */ - #ifdef RGFW_WINDOWS - - #define WIN32_LEAN_AND_MEAN - #define OEMRESOURCE - #include - - struct RGFW_nativeImage { - HBITMAP bitmap; - u8* bitmapBits; - RGFW_format format; - HDC hdcMem; - }; - - #ifdef RGFW_OPENGL - struct RGFW_glContext { HGLRC ctx; }; - #endif - - struct RGFW_window_src { - HWND window; /*!< source window */ - HDC hdc; /*!< source HDC */ - i32 offsetW, offsetH; /*!< width and height offset for window */ - HICON hIconSmall, hIconBig; /*!< source window icons */ - i32 maxSizeW, maxSizeH, minSizeW, minSizeH, aspectRatioW, aspectRatioH; /*!< for setting max/min resize (RGFW_WINDOWS) */ - #ifdef RGFW_OPENGL - RGFW_gfxContext ctx; - RGFW_gfxContextType gfxType; - #endif - }; - -#elif defined(RGFW_UNIX) - #ifdef RGFW_X11 - #include - #include - #endif - - #ifdef RGFW_WAYLAND - #ifdef RGFW_LIBDECOR - #include - #endif - - #include - #include - #endif - - struct RGFW_nativeImage { - #ifdef RGFW_X11 - XImage* bitmap; - #endif - #ifdef RGFW_WAYLAND - struct wl_buffer* wl_buffer; - #endif - u8* buffer; - RGFW_format format; - }; - - #ifdef RGFW_OPENGL - struct RGFW_glContext { - #ifdef RGFW_X11 - struct __GLXcontextRec* ctx; /*!< source graphics context */ - Window window; - #endif - #ifdef RGFW_WAYLAND - RGFW_eglContext egl; - #endif - }; - #endif - - struct RGFW_window_src { - i32 x, y, w, h; - #ifdef RGFW_OPENGL - RGFW_gfxContext ctx; - RGFW_gfxContextType gfxType; - #endif -#ifdef RGFW_X11 - Window window; /*!< source window */ - Window parent; /*!< parent window */ - GC gc; - #ifdef RGFW_ADVANCED_SMOOTH_RESIZE - i64 counter_value; - XID counter; - #endif -#endif /* RGFW_X11 */ - -#if defined(RGFW_WAYLAND) - struct wl_surface* surface; - struct xdg_surface* xdg_surface; - struct xdg_toplevel* xdg_toplevel; - struct zxdg_toplevel_decoration_v1* decoration; - struct zwp_locked_pointer_v1 *locked_pointer; - struct xdg_toplevel_icon_v1 *icon; - u32 decoration_mode; - /* State flags to configure the window */ - RGFW_bool pending_activated; - RGFW_bool activated; - RGFW_bool resizing; - RGFW_bool pending_maximized; - RGFW_bool maximized; - RGFW_bool minimized; - - RGFW_bool using_custom_cursor; - struct wl_surface* custom_cursor_surface; - - RGFW_monitor active_monitor; - - struct wl_data_source *data_source; // offer data to other clients - - #ifdef RGFW_LIBDECOR - struct libdecor* decorContext; - #endif -#endif /* RGFW_WAYLAND */ - }; - +#if defined(RGFW_X11) || defined(RGFW_WAYLAND) + #define RGFW_OS_BASED_VALUE(l, w, m, h) l +#elif defined(RGFW_WINDOWS) + #define RGFW_OS_BASED_VALUE(l, w, m, h) w #elif defined(RGFW_MACOS) - - struct RGFW_nativeImage { - RGFW_format format; - }; - - #ifdef RGFW_OPENGL - struct RGFW_glContext { void* ctx; }; - #endif - - struct RGFW_window_src { - void* window; - void* view; /* apple viewpoint thingy */ - void* mouse; - #ifdef RGFW_OPENGL - RGFW_gfxContext ctx; - RGFW_gfxContextType gfxType; - #endif - }; - + #define RGFW_OS_BASED_VALUE(l, w, m, h) m #elif defined(RGFW_WASM) - - #include - #include - - struct RGFW_nativeImage { - RGFW_format format; - }; - - #ifdef RGFW_OPENGL - struct RGFW_glContext { - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx; - }; - #endif - - struct RGFW_window_src { - #ifdef RGFW_OPENGL - RGFW_gfxContext ctx; - RGFW_gfxContextType gfxType; - #endif - }; - + #define RGFW_OS_BASED_VALUE(l, w, m, h) h #endif -struct RGFW_surface { - u8* data; - i32 w, h; - RGFW_format format; - RGFW_nativeImage native; -}; - -/*! internal window data that is not specific to the OS */ -typedef struct RGFW_windowInternal { - /*! which key RGFW_window_shouldClose checks. Settting this to RGFW_keyNULL disables the feature. */ - RGFW_key exitKey; - i32 lastMouseX, lastMouseY; /*!< last cusor point (for raw mouse data) */ - - RGFW_bool shouldClose; - RGFW_bool holdMouse; - RGFW_bool inFocus; - RGFW_bool mouseInside; - RGFW_keymod mod; - RGFW_eventFlag enabledEvents; - u32 flags; /*!< windows flags (for RGFW to check and modify) */ - i32 oldX, oldY, oldW, oldH; -} RGFW_windowInternal; - -struct RGFW_window { - RGFW_window_src src; /*!< src window data */ - RGFW_windowInternal internal; /*!< internal window data that is not specific to the OS */ - void* userPtr; /* ptr for usr data */ - i32 x, y, w, h; /*!< position and size of the window */ -}; /*!< window structure for the window */ - -typedef struct RGFW_windowState { - RGFW_bool mouseEnter; - RGFW_bool dataDragging; - RGFW_bool dataDrop; - size_t filesCount; - i32 dropX, dropY; - RGFW_window* win; /*!< it's not possible for one of these events to happen in the frame that the other event happened */ - - RGFW_bool mouseLeave; - RGFW_window* winLeave; /*!< if a mouse leaves one widow and enters the next */ -} RGFW_windowState; - -typedef struct { - RGFW_bool current; - RGFW_bool prev; -} RGFW_keyState; - -#ifndef RGFW_NO_MONITOR - typedef struct RGFW_monitorNode { - RGFW_monitor mon; - struct RGFW_monitorNode* next; -#ifdef RGFW_WAYLAND - u32 id; /* Add id so wl_outputs can be removed */ - struct wl_output *output; - struct zxdg_output_v1 *xdg_output; -#endif - } RGFW_monitorNode; - - typedef struct RGFW_monitorList { - RGFW_monitorNode* head; - RGFW_monitorNode* cur; - } RGFW_monitorList; - - typedef struct RGFW_monitors { - RGFW_monitorList list; - RGFW_monitorList freeList; - size_t count; - RGFW_monitorNode data[RGFW_MAX_MONITORS]; - } RGFW_monitors; - - RGFWDEF RGFW_monitorNode* RGFW_monitors_add(RGFW_monitor mon); - RGFWDEF void RGFW_monitors_remove(RGFW_monitorNode* node, RGFW_monitorNode* prev); -#endif - -struct RGFW_info { - RGFW_window* root; - i32 windowCount; - - RGFW_mouse* hiddenMouse; - - RGFW_event events[RGFW_MAX_EVENTS]; /* A circular buffer (FIFO), using eventBottom/Len */ - - i32 eventBottom; - i32 eventLen; - RGFW_bool queueEvents; - RGFW_bool polledEvents; - - u32 apiKeycodes[RGFW_keyLast]; - #if defined(RGFW_X11) || defined(RGFW_WAYLAND) - u8 keycodes[256]; - #elif defined(RGFW_WINDOWS) - u8 keycodes[512]; - #elif defined(RGFW_MACOS) - u8 keycodes[128]; - #elif defined(RGFW_WASM) - u8 keycodes[256]; - #endif - - const char* className; - RGFW_bool useWaylandBool; - RGFW_bool stopCheckEvents_bool ; - u64 timerOffset; - - char* clipboard_data; - char* clipboard; /* for writing to the clipboard selection */ - size_t clipboard_len; - char filesSrc[RGFW_MAX_PATH * RGFW_MAX_DROPS]; - char** files; - #ifdef RGFW_X11 - Display* display; - XContext context; - Window helperWindow; - const char* instName; - XErrorEvent* x11Error; - #endif - #ifdef RGFW_WAYLAND - struct wl_display* wl_display; - struct xkb_context *xkb_context; - struct xkb_keymap *keymap; - struct xkb_state *xkb_state; - struct zxdg_decoration_manager_v1 *decoration_manager; - struct zwp_relative_pointer_manager_v1 *relative_pointer_manager; - struct zwp_relative_pointer_v1 *relative_pointer; - struct zwp_pointer_constraints_v1 *constraint_manager; - struct xdg_toplevel_icon_manager_v1 *icon_manager; - - struct zxdg_output_manager_v1 *xdg_output_manager; - - struct wl_data_device_manager *data_device_manager; - struct wl_data_device *data_device; // supports clipboard and DND - - struct wl_keyboard* wl_keyboard; - struct wl_pointer* wl_pointer; - struct wl_compositor* compositor; - struct xdg_wm_base* xdg_wm_base; - struct wl_shm* shm; - struct wl_seat *seat; - struct wl_registry *registry; - u32 mouse_enter_serial; - struct wl_cursor_theme* wl_cursor_theme; - struct wl_surface* cursor_surface; - - RGFW_window* kbOwner; - - #endif - - RGFW_monitors monitors; - - #ifdef RGFW_UNIX - int eventWait_forceStop[3]; - #endif - - #ifdef RGFW_MACOS - void* NSApp; - void* customViewClasses[2]; /* NSView and NSOpenGLView */ - void* customWindowDelegateClass; - #endif - - #ifdef RGFW_OPENGL - RGFW_window* current; - #endif - #ifdef RGFW_EGL - void* EGL_display; - #endif - - RGFW_window* mouseOwner; - RGFW_windowState windowState; /*! for checking window state events */ - - RGFW_keyState mouseButtons[RGFW_mouseFinal]; - RGFW_keyState keyboard[RGFW_keyLast]; - float scrollX, scrollY; - float vectorX, vectorY; -}; -#endif /* RGFW_NATIVE_HEADER */ #ifdef RGFW_IMPLEMENTATION +RGFW_bool RGFW_useWaylandBool = 1; +void RGFW_useWayland(RGFW_bool wayland) { RGFW_useWaylandBool = wayland; } +RGFW_bool RGFW_usingWayland(void) { return RGFW_useWaylandBool; } -/* global private API */ - -/* for C++ / C89 */ -#define RGFW_eventQueuePushEx(eventInit) { RGFW_event e; eventInit; RGFW_eventQueuePush(&e); } - -RGFWDEF RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, RGFW_window* win); -RGFWDEF void RGFW_window_closePlatform(RGFW_window* win); - -RGFWDEF void RGFW_window_focusLost(RGFW_window* win); -RGFWDEF void RGFW_window_setFlagsInternal(RGFW_window* win, RGFW_windowFlags flags, RGFW_windowFlags cmpFlags); - -RGFWDEF void RGFW_initKeycodes(void); -RGFWDEF void RGFW_initKeycodesPlatform(void); -RGFWDEF void RGFW_resetPrevState(void); -RGFWDEF void RGFW_resetKey(void); -RGFWDEF void RGFW_unloadEGL(void); -RGFWDEF void RGFW_updateKeyModsEx(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll); -RGFWDEF void RGFW_updateKeyMods(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool scroll); -RGFWDEF void RGFW_window_showMouseFlags(RGFW_window* win, RGFW_bool show); -RGFWDEF void RGFW_updateKeyMod(RGFW_window* win, RGFW_keymod mod, RGFW_bool value); - -RGFWDEF void RGFW_setBit(u32* var, u32 mask, RGFW_bool set); -RGFWDEF void RGFW_splitBPP(u32 bpp, RGFW_monitorMode* mode); - -RGFWDEF void RGFW_captureCursor(RGFW_window* win); -RGFWDEF void RGFW_releaseCursor(RGFW_window* win); - -RGFWDEF void RGFW_copyImageData64(u8* dest_data, i32 w, i32 h, RGFW_format dest_format, - u8* src_data, RGFW_format src_format, RGFW_bool is64bit); - -RGFWDEF RGFW_bool RGFW_loadEGL(void); - -#ifdef RGFW_OPENGL -typedef struct RGFW_attribStack { - i32* attribs; - size_t count; - size_t max; -} RGFW_attribStack; -RGFWDEF void RGFW_attribStack_init(RGFW_attribStack* stack, i32* attribs, size_t max); -RGFWDEF void RGFW_attribStack_pushAttrib(RGFW_attribStack* stack, i32 attrib); -RGFWDEF void RGFW_attribStack_pushAttribs(RGFW_attribStack* stack, i32 attrib1, i32 attrib2); - -RGFWDEF RGFW_bool RGFW_extensionSupportedStr(const char* extensions, const char* ext, size_t len); +#if !defined(RGFW_NO_X11) && defined(RGFW_WAYLAND) +#define RGFW_GOTO_WAYLAND(fallback) if (RGFW_useWaylandBool && fallback == 0) goto wayland +#define RGFW_WAYLAND_LABEL wayland:; +#else +#define RGFW_GOTO_WAYLAND(fallback) +#define RGFW_WAYLAND_LABEL #endif -typedef struct RGFW_colorLayout { i32 r, g, b, a; } RGFW_colorLayout; - -#ifdef RGFW_X11 -RGFWDEF void RGFW_XCreateWindow (XVisualInfo visual, const char* name, RGFW_windowFlags flags, RGFW_window* win); -#endif -#ifdef RGFW_MACOS -RGFWDEF void RGFW_osx_initView(RGFW_window* win); -#endif -/* end of global private API defs */ - -RGFW_info* _RGFW = NULL; -void RGFW_setInfo(RGFW_info* info) { _RGFW = info; } -RGFW_info* RGFW_getInfo(void) { return _RGFW; } - - -void* RGFW_alloc(size_t size) { return RGFW_ALLOC(size); } -void RGFW_free(void* ptr) { RGFW_FREE(ptr); } - -void RGFW_useWayland(RGFW_bool wayland) { RGFW_init(); _RGFW->useWaylandBool = RGFW_BOOL(wayland); } -RGFW_bool RGFW_usingWayland(void) { return _RGFW->useWaylandBool; } - +char* RGFW_clipboard_data; void RGFW_clipboard_switch(char* newstr); void RGFW_clipboard_switch(char* newstr) { - if (_RGFW->clipboard_data != NULL) - RGFW_FREE(_RGFW->clipboard_data); - _RGFW->clipboard_data = newstr; + if (RGFW_clipboard_data != NULL) + RGFW_FREE(RGFW_clipboard_data); + RGFW_clipboard_data = newstr; } #define RGFW_CHECK_CLIPBOARD() \ - if (size <= 0 && _RGFW->clipboard_data != NULL) \ - return (const char*)_RGFW->clipboard_data; \ + if (size <= 0 && RGFW_clipboard_data != NULL) \ + return (const char*)RGFW_clipboard_data; \ else if (size <= 0) \ return "\0"; @@ -3036,6 +1589,52 @@ const char* RGFW_readClipboard(size_t* len) { return (const char*)str; } +RGFW_debugfunc RGFW_debugCallback = NULL; +RGFW_debugfunc RGFW_setDebugCallback(RGFW_debugfunc func) { + RGFW_debugfunc RGFW_debugCallbackPrev = RGFW_debugCallback; + RGFW_debugCallback = func; + return RGFW_debugCallbackPrev; +} + +#ifdef RGFW_DEBUG +#include +#endif + +void RGFW_sendDebugInfo(RGFW_debugType type, RGFW_errorCode err, RGFW_debugContext ctx, const char* msg) { + if (RGFW_debugCallback) RGFW_debugCallback(type, err, ctx, msg); + #ifdef RGFW_DEBUG + switch (type) { + case RGFW_typeInfo: printf("RGFW INFO (%i %i): %s", type, err, msg); break; + case RGFW_typeError: printf("RGFW DEBUG (%i %i): %s", type, err, msg); break; + case RGFW_typeWarning: printf("RGFW WARNING (%i %i): %s", type, err, msg); break; + default: break; + } + + switch (err) { + #ifdef RGFW_BUFFER + case RGFW_errBuffer: case RGFW_infoBuffer: printf(" buffer size: %i %i\n", ctx.win->bufferSize.w, ctx.win->bufferSize.h); break; + #endif + case RGFW_infoMonitor: printf(": scale (%s):\n rect: {%i, %i, %i, %i}\n physical size:%f %f\n scale: %f %f\n pixelRatio: %f\n refreshRate: %i\n depth: %i\n", ctx.monitor->name, ctx.monitor->x, ctx.monitor->y, ctx.monitor->mode.area.w, ctx.monitor->mode.area.h, ctx.monitor->physW, ctx.monitor->physH, ctx.monitor->scaleX, ctx.monitor->scaleY, ctx.monitor->pixelRatio, ctx.monitor->mode.refreshRate, ctx.monitor->mode.red + ctx.monitor->mode.green + ctx.monitor->mode.blue); break; + case RGFW_infoWindow: printf(" with rect of {%i, %i, %i, %i} \n", ctx.win->r.x, ctx.win->r.y,ctx. win->r.w, ctx.win->r.h); break; + case RGFW_errDirectXContext: printf(" srcError %i\n", ctx.srcError); break; + default: printf("\n"); + } + #endif +} + +u64 RGFW_timerOffset = 0; +void RGFW_setTime(double time) { + RGFW_timerOffset = RGFW_getTimerValue() - (u64)(time * (double)RGFW_getTimerFreq()); +} + +double RGFW_getTime(void) { + return (double) ((double)(RGFW_getTimerValue() - RGFW_timerOffset) / (double)RGFW_getTimerFreq()); +} + +u64 RGFW_getTimeNS(void) { + return (u64)(((double)((RGFW_getTimerValue() - RGFW_timerOffset)) * 1e9) / (double)RGFW_getTimerFreq()); +} + /* RGFW_IMPLEMENTATION starts with generic RGFW defines @@ -3044,44 +1643,205 @@ This is the start of keycode data -void RGFW_initKeycodes(void) { - RGFW_MEMSET(_RGFW->keycodes, 0, sizeof(_RGFW->keycodes)); - RGFW_initKeycodesPlatform(); - u32 i, y; - for (i = 0; i < RGFW_keyLast; i++) { - for (y = 0; y < sizeof(_RGFW->keycodes); y++) { - if (_RGFW->keycodes[y] == i) { - _RGFW->apiKeycodes[i] = y; - break; +/* + the c++ compiler doesn't support setting up an array like, + we'll have to do it during runtime using a function & this messy setup +*/ + +#ifndef RGFW_CUSTOM_BACKEND + +#if !defined(__cplusplus) && !defined(RGFW_C89) +#define RGFW_NEXT , +#define RGFW_MAP +#else +#define RGFW_NEXT ; +#define RGFW_MAP RGFW_keycodes +#endif + +u32 RGFW_apiKeycodes[RGFW_keyLast] = { 0 }; + +u8 RGFW_keycodes [RGFW_OS_BASED_VALUE(256, 512, 128, 256)] = { +#if defined(__cplusplus) || defined(RGFW_C89) + 0 +}; +void RGFW_init_keys(void); +void RGFW_init_keys(void) { +#endif + RGFW_MAP [RGFW_OS_BASED_VALUE(49, 0x029, 50, DOM_VK_BACK_QUOTE)] = RGFW_backtick RGFW_NEXT + + RGFW_MAP [RGFW_OS_BASED_VALUE(19, 0x00B, 29, DOM_VK_0)] = RGFW_0 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(10, 0x002, 18, DOM_VK_1)] = RGFW_1 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(11, 0x003, 19, DOM_VK_2)] = RGFW_2 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(12, 0x004, 20, DOM_VK_3)] = RGFW_3 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(13, 0x005, 21, DOM_VK_4)] = RGFW_4 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(14, 0x006, 23, DOM_VK_5)] = RGFW_5 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(15, 0x007, 22, DOM_VK_6)] = RGFW_6 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(16, 0x008, 26, DOM_VK_7)] = RGFW_7 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(17, 0x009, 28, DOM_VK_8)] = RGFW_8 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(18, 0x00A, 25, DOM_VK_9)] = RGFW_9, + RGFW_MAP [RGFW_OS_BASED_VALUE(65, 0x039, 49, DOM_VK_SPACE)] = RGFW_space, + RGFW_MAP [RGFW_OS_BASED_VALUE(38, 0x01E, 0, DOM_VK_A)] = RGFW_a RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(56, 0x030, 11, DOM_VK_B)] = RGFW_b RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(54, 0x02E, 8, DOM_VK_C)] = RGFW_c RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(40, 0x020, 2, DOM_VK_D)] = RGFW_d RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(26, 0x012, 14, DOM_VK_E)] = RGFW_e RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(41, 0x021, 3, DOM_VK_F)] = RGFW_f RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(42, 0x022, 5, DOM_VK_G)] = RGFW_g RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(43, 0x023, 4, DOM_VK_H)] = RGFW_h RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(31, 0x017, 34, DOM_VK_I)] = RGFW_i RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(44, 0x024, 38, DOM_VK_J)] = RGFW_j RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(45, 0x025, 40, DOM_VK_K)] = RGFW_k RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(46, 0x026, 37, DOM_VK_L)] = RGFW_l RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(58, 0x032, 46, DOM_VK_M)] = RGFW_m RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(57, 0x031, 45, DOM_VK_N)] = RGFW_n RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(32, 0x018, 31, DOM_VK_O)] = RGFW_o RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(33, 0x019, 35, DOM_VK_P)] = RGFW_p RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(24, 0x010, 12, DOM_VK_Q)] = RGFW_q RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(27, 0x013, 15, DOM_VK_R)] = RGFW_r RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(39, 0x01F, 1, DOM_VK_S)] = RGFW_s RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(28, 0x014, 17, DOM_VK_T)] = RGFW_t RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(30, 0x016, 32, DOM_VK_U)] = RGFW_u RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(55, 0x02F, 9, DOM_VK_V)] = RGFW_v RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(25, 0x011, 13, DOM_VK_W)] = RGFW_w RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(53, 0x02D, 7, DOM_VK_X)] = RGFW_x RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(29, 0x015, 16, DOM_VK_Y)] = RGFW_y RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(52, 0x02C, 6, DOM_VK_Z)] = RGFW_z, + RGFW_MAP [RGFW_OS_BASED_VALUE(60, 0x034, 47, DOM_VK_PERIOD)] = RGFW_period RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(59, 0x033, 43, DOM_VK_COMMA)] = RGFW_comma RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(61, 0x035, 44, DOM_VK_SLASH)] = RGFW_slash RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(34, 0x01A, 33, DOM_VK_OPEN_BRACKET)] = RGFW_bracket RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(35, 0x01B, 30, DOM_VK_CLOSE_BRACKET)] = RGFW_closeBracket RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(47, 0x027, 41, DOM_VK_SEMICOLON)] = RGFW_semicolon RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(48, 0x028, 39, DOM_VK_QUOTE)] = RGFW_apostrophe RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(51, 0x02B, 42, DOM_VK_BACK_SLASH)] = RGFW_backSlash, + RGFW_MAP [RGFW_OS_BASED_VALUE(36, 0x01C, 36, DOM_VK_RETURN)] = RGFW_return RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(119, 0x153, 118, DOM_VK_DELETE)] = RGFW_delete RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(77, 0x145, 72, DOM_VK_NUM_LOCK)] = RGFW_numLock RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(106, 0x135, 82, DOM_VK_DIVIDE)] = RGFW_KP_Slash RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(63, 0x037, 76, DOM_VK_MULTIPLY)] = RGFW_multiply RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(82, 0x04A, 67, DOM_VK_SUBTRACT)] = RGFW_KP_Minus RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(87, 0x04F, 84, DOM_VK_NUMPAD1)] = RGFW_KP_1 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(88, 0x050, 85, DOM_VK_NUMPAD2)] = RGFW_KP_2 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(89, 0x051, 86, DOM_VK_NUMPAD3)] = RGFW_KP_3 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(83, 0x04B, 87, DOM_VK_NUMPAD4)] = RGFW_KP_4 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(84, 0x04C, 88, DOM_VK_NUMPAD5)] = RGFW_KP_5 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(85, 0x04D, 89, DOM_VK_NUMPAD6)] = RGFW_KP_6 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(79, 0x047, 90, DOM_VK_NUMPAD7)] = RGFW_KP_7 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(80, 0x048, 92, DOM_VK_NUMPAD8)] = RGFW_KP_8 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(81, 0x049, 93, DOM_VK_NUMPAD9)] = RGFW_KP_9 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(90, 0x052, 83, DOM_VK_NUMPAD0)] = RGFW_KP_0 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(91, 0x053, 65, DOM_VK_DECIMAL)] = RGFW_KP_Period RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(104, 0x11C, 77, 0)] = RGFW_KP_Return, + RGFW_MAP [RGFW_OS_BASED_VALUE(20, 0x00C, 27, DOM_VK_HYPHEN_MINUS)] = RGFW_minus RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(21, 0x00D, 24, DOM_VK_EQUALS)] = RGFW_equals RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(22, 0x00E, 51, DOM_VK_BACK_SPACE)] = RGFW_backSpace RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(23, 0x00F, 48, DOM_VK_TAB)] = RGFW_tab RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(66, 0x03A, 57, DOM_VK_CAPS_LOCK)] = RGFW_capsLock RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(50, 0x02A, 56, DOM_VK_SHIFT)] = RGFW_shiftL RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(37, 0x01D, 59, DOM_VK_CONTROL)] = RGFW_controlL RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(64, 0x038, 58, DOM_VK_ALT)] = RGFW_altL RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(133, 0x15B, 55, DOM_VK_WIN)] = RGFW_superL, + #if !defined(RGFW_MACOS) && !defined(RGFW_WASM) + RGFW_MAP [RGFW_OS_BASED_VALUE(105, 0x11D, 59, 0)] = RGFW_controlR RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(134, 0x15C, 55, 0)] = RGFW_superR, + RGFW_MAP [RGFW_OS_BASED_VALUE(62, 0x036, 56, 0)] = RGFW_shiftR RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(108, 0x138, 58, 0)] = RGFW_altR, + #endif + RGFW_MAP [RGFW_OS_BASED_VALUE(67, 0x03B, 127, DOM_VK_F1)] = RGFW_F1 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(68, 0x03C, 121, DOM_VK_F2)] = RGFW_F2 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(69, 0x03D, 100, DOM_VK_F3)] = RGFW_F3 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(70, 0x03E, 119, DOM_VK_F4)] = RGFW_F4 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(71, 0x03F, 97, DOM_VK_F5)] = RGFW_F5 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(72, 0x040, 98, DOM_VK_F6)] = RGFW_F6 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(73, 0x041, 99, DOM_VK_F7)] = RGFW_F7 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(74, 0x042, 101, DOM_VK_F8)] = RGFW_F8 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(75, 0x043, 102, DOM_VK_F9)] = RGFW_F9 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(76, 0x044, 110, DOM_VK_F10)] = RGFW_F10 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(95, 0x057, 104, DOM_VK_F11)] = RGFW_F11 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(96, 0x058, 111, DOM_VK_F12)] = RGFW_F12 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(111, 0x148, 126, DOM_VK_UP)] = RGFW_up RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(116, 0x150, 125, DOM_VK_DOWN)] = RGFW_down RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(113, 0x14B, 123, DOM_VK_LEFT)] = RGFW_left RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(114, 0x14D, 124, DOM_VK_RIGHT)] = RGFW_right RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(118, 0x152, 115, DOM_VK_INSERT)] = RGFW_insert RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(115, 0x14F, 120, DOM_VK_END)] = RGFW_end RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(112, 0x149, 117, DOM_VK_PAGE_UP)] = RGFW_pageUp RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(117, 0x151, 122, DOM_VK_PAGE_DOWN)] = RGFW_pageDown RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(9, 0x001, 53, DOM_VK_ESCAPE)] = RGFW_escape RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(110, 0x147, 116, DOM_VK_HOME)] = RGFW_home RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(78, 0x046, 107, DOM_VK_SCROLL_LOCK)] = RGFW_scrollLock RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(107, 0x137, 105, DOM_VK_PRINTSCREEN)] = RGFW_printScreen RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(128, 0x045, 113, DOM_VK_PAUSE)] = RGFW_pause RGFW_NEXT +#if defined(__cplusplus) || defined(RGFW_C89) +} +#else +}; +#endif + +#undef RGFW_NEXT +#undef RGFW_MAP + +u32 RGFW_apiKeyToRGFW(u32 keycode) { + #if defined(__cplusplus) || defined(RGFW_C89) + if (RGFW_keycodes[RGFW_OS_BASED_VALUE(49, 0x029, 50, DOM_VK_BACK_QUOTE)] != RGFW_backtick) { + RGFW_init_keys(); + } + #endif + + /* make sure the key isn't out of bounds */ + if (keycode > sizeof(RGFW_keycodes) / sizeof(u8)) + return 0; + + return RGFW_keycodes[keycode]; +} + +u32 RGFW_rgfwToApiKey(u32 keycode) { + if (RGFW_apiKeycodes[RGFW_backtick] != RGFW_OS_BASED_VALUE(49, 0x029, 50, DOM_VK_BACK_QUOTE)) { + for (u32 i = 0; i < RGFW_keyLast; i++) { + for (u32 y = 0; y < sizeof(RGFW_keycodes); y++) { + if (RGFW_keycodes[y] == i) { + RGFW_apiKeycodes[i] = y; + break; + } } } } - - RGFW_resetKey(); -} - -u32 RGFW_apiKeyToRGFW(u32 keycode) { - /* make sure the key isn't out of bounds */ - if (keycode > sizeof(_RGFW->keycodes) / sizeof(u8)) - return 0; - - return _RGFW->keycodes[keycode]; -} - -u32 RGFW_rgfwToApiKey(u32 keycode) { /* make sure the key isn't out of bounds */ - if (keycode > sizeof(_RGFW->apiKeycodes) / sizeof(u32)) + if (keycode > sizeof(RGFW_apiKeycodes) / sizeof(u32)) return 0; - return _RGFW->apiKeycodes[keycode]; + return RGFW_apiKeycodes[keycode]; } +#endif /* RGFW_CUSTOM_BACKEND */ -void RGFW_resetKey(void) { RGFW_MEMSET(_RGFW->keyboard, 0, sizeof(_RGFW->keyboard)); } +typedef struct { + RGFW_bool current : 1; + RGFW_bool prev : 1; +} RGFW_keyState; + +RGFW_keyState RGFW_keyboard[RGFW_keyLast] = { {0, 0} }; + +RGFWDEF void RGFW_resetKeyPrev(void); +void RGFW_resetKeyPrev(void) { + size_t i; /*!< reset each previous state */ + for (i = 0; i < RGFW_keyLast; i++) RGFW_keyboard[i].prev = 0; +} +RGFWDEF void RGFW_resetKey(void); +void RGFW_resetKey(void) { RGFW_MEMSET(RGFW_keyboard, 0, sizeof(RGFW_keyboard)); } /* this is the end of keycode data */ +/* gamepad data */ +RGFW_keyState RGFW_gamepadPressed[4][32]; /*!< if a key is currently pressed or not (per gamepad) */ +RGFW_point RGFW_gamepadAxes[4][4]; /*!< if a key is currently pressed or not (per gamepad) */ + +RGFW_gamepadType RGFW_gamepads_type[4]; /*!< if a key is currently pressed or not (per gamepad) */ +i32 RGFW_gamepads[4] = {0, 0, 0, 0}; /*!< limit of 4 gamepads at a time */ +char RGFW_gamepads_name[4][128]; /*!< gamepad names */ +u16 RGFW_gamepadCount = 0; /*!< the actual amount of gamepads */ + /* event callback defines start here */ @@ -3094,101 +1854,79 @@ void RGFW_resetKey(void) { RGFW_MEMSET(_RGFW->keyboard, 0, sizeof(_RGFW->keyboar RGFW_EMPTY_DEF exists to prevent the missing-prototypes warning */ +static void RGFW_windowMovedfuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } +static void RGFW_windowResizedfuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } +static void RGFW_windowRestoredfuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } +static void RGFW_windowMinimizedfuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } +static void RGFW_windowMaximizedfuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } +static void RGFW_windowQuitfuncEMPTY(RGFW_window* win) { RGFW_UNUSED(win); } +static void RGFW_focusfuncEMPTY(RGFW_window* win, RGFW_bool inFocus) {RGFW_UNUSED(win); RGFW_UNUSED(inFocus);} +static void RGFW_mouseNotifyfuncEMPTY(RGFW_window* win, RGFW_point point, RGFW_bool status) {RGFW_UNUSED(win); RGFW_UNUSED(point); RGFW_UNUSED(status);} +static void RGFW_mousePosfuncEMPTY(RGFW_window* win, RGFW_point point, RGFW_point vector) {RGFW_UNUSED(win); RGFW_UNUSED(point); RGFW_UNUSED(vector);} +static void RGFW_dndInitfuncEMPTY(RGFW_window* win, RGFW_point point) {RGFW_UNUSED(win); RGFW_UNUSED(point);} +static void RGFW_windowRefreshfuncEMPTY(RGFW_window* win) {RGFW_UNUSED(win); } +static void RGFW_keyfuncEMPTY(RGFW_window* win, RGFW_key key, u8 keyChar, RGFW_keymod keyMod, RGFW_bool pressed) {RGFW_UNUSED(win); RGFW_UNUSED(key); RGFW_UNUSED(keyChar); RGFW_UNUSED(keyMod); RGFW_UNUSED(pressed);} +static void RGFW_mouseButtonfuncEMPTY(RGFW_window* win, RGFW_mouseButton button, double scroll, RGFW_bool pressed) {RGFW_UNUSED(win); RGFW_UNUSED(button); RGFW_UNUSED(scroll); RGFW_UNUSED(pressed);} +static void RGFW_gamepadButtonfuncEMPTY(RGFW_window* win, u16 gamepad, u8 button, RGFW_bool pressed) {RGFW_UNUSED(win); RGFW_UNUSED(gamepad); RGFW_UNUSED(button); RGFW_UNUSED(pressed); } +static void RGFW_gamepadAxisfuncEMPTY(RGFW_window* win, u16 gamepad, RGFW_point axis[2], u8 axisesCount, u8 whichAxis) {RGFW_UNUSED(win); RGFW_UNUSED(gamepad); RGFW_UNUSED(axis); RGFW_UNUSED(axisesCount); RGFW_UNUSED(whichAxis); } +static void RGFW_gamepadfuncEMPTY(RGFW_window* win, u16 gamepad, RGFW_bool connected) {RGFW_UNUSED(win); RGFW_UNUSED(gamepad); RGFW_UNUSED(connected);} +static void RGFW_dndfuncEMPTY(RGFW_window* win, char** droppedFiles, size_t droppedFilesCount) {RGFW_UNUSED(win); RGFW_UNUSED(droppedFiles); RGFW_UNUSED(droppedFilesCount);} +static void RGFW_scaleUpdatedfuncEMPTY(RGFW_window* win, float scaleX, float scaleY) {RGFW_UNUSED(win); RGFW_UNUSED(scaleX); RGFW_UNUSED(scaleY); } + #define RGFW_CALLBACK_DEFINE(x, x2) \ -RGFW_##x##func RGFW_##x##CallbackSrc = NULL; \ +RGFW_##x##func RGFW_##x##Callback = RGFW_##x##funcEMPTY; \ RGFW_##x##func RGFW_set##x2##Callback(RGFW_##x##func func) { \ - RGFW_##x##func prev = RGFW_##x##CallbackSrc; \ - RGFW_##x##CallbackSrc = func; \ + RGFW_##x##func prev = RGFW_##x##Callback; \ + RGFW_##x##Callback = func; \ return prev; \ } - RGFW_CALLBACK_DEFINE(windowMaximized, WindowMaximized) -#define RGFW_windowMaximizedCallback(win, x, y, w, h) if (RGFW_windowMaximizedCallbackSrc) RGFW_windowMaximizedCallbackSrc(win, x, y, w, h); - RGFW_CALLBACK_DEFINE(windowMinimized, WindowMinimized) -#define RGFW_windowMinimizedCallback(w) if (RGFW_windowMinimizedCallbackSrc) RGFW_windowMinimizedCallbackSrc(w); - RGFW_CALLBACK_DEFINE(windowRestored, WindowRestored) -#define RGFW_windowRestoredCallback(win, x, y, w, h) if (RGFW_windowRestoredCallbackSrc) RGFW_windowRestoredCallbackSrc(win, x, y, w, h); - RGFW_CALLBACK_DEFINE(windowMoved, WindowMoved) -#define RGFW_windowMovedCallback(w, x, y) if (RGFW_windowMovedCallbackSrc) RGFW_windowMovedCallbackSrc(w, x, y); - RGFW_CALLBACK_DEFINE(windowResized, WindowResized) -#define RGFW_windowResizedCallback(win, w, h) if (RGFW_windowResizedCallbackSrc) RGFW_windowResizedCallbackSrc(win, w, h); - RGFW_CALLBACK_DEFINE(windowQuit, WindowQuit) -#define RGFW_windowQuitCallback(w) if (RGFW_windowQuitCallbackSrc) RGFW_windowQuitCallbackSrc(w); - RGFW_CALLBACK_DEFINE(mousePos, MousePos) -#define RGFW_mousePosCallback(w, x, y, vecX, vecY) if (RGFW_mousePosCallbackSrc) RGFW_mousePosCallbackSrc(w, x, y, vecX, vecY); - RGFW_CALLBACK_DEFINE(windowRefresh, WindowRefresh) -#define RGFW_windowRefreshCallback(w) if (RGFW_windowRefreshCallbackSrc) RGFW_windowRefreshCallbackSrc(w); - RGFW_CALLBACK_DEFINE(focus, Focus) -#define RGFW_focusCallback(w, inFocus) if (RGFW_focusCallbackSrc) RGFW_focusCallbackSrc(w, inFocus); - RGFW_CALLBACK_DEFINE(mouseNotify, MouseNotify) -#define RGFW_mouseNotifyCallback(w, x, y, status) if (RGFW_mouseNotifyCallbackSrc) RGFW_mouseNotifyCallbackSrc(w, x, y, status); - -RGFW_CALLBACK_DEFINE(dataDrop, DataDrop) -#define RGFW_dataDropCallback(w, files, count) if (RGFW_dataDropCallbackSrc) RGFW_dataDropCallbackSrc(w, files, count); - -RGFW_CALLBACK_DEFINE(dataDrag, DataDrag) -#define RGFW_dataDragCallback(w, x, y) if (RGFW_dataDragCallbackSrc) RGFW_dataDragCallbackSrc(w, x, y); - +RGFW_CALLBACK_DEFINE(dnd, Dnd) +RGFW_CALLBACK_DEFINE(dndInit, DndInit) RGFW_CALLBACK_DEFINE(key, Key) -#define RGFW_keyCallback(w, key, sym, mod, repeat, press) if (RGFW_keyCallbackSrc) RGFW_keyCallbackSrc(w, key, sym, mod, repeat, press); - RGFW_CALLBACK_DEFINE(mouseButton, MouseButton) -#define RGFW_mouseButtonCallback(w, button, press) if (RGFW_mouseButtonCallbackSrc) RGFW_mouseButtonCallbackSrc(w, button, press); - -RGFW_CALLBACK_DEFINE(mouseScroll, MouseScroll) -#define RGFW_mouseScrollCallback(w, x, y) if (RGFW_mouseScrollCallbackSrc) RGFW_mouseScrollCallbackSrc(w, x, y); - +RGFW_CALLBACK_DEFINE(gamepadButton, GamepadButton) +RGFW_CALLBACK_DEFINE(gamepadAxis, GamepadAxis) +RGFW_CALLBACK_DEFINE(gamepad, Gamepad) RGFW_CALLBACK_DEFINE(scaleUpdated, ScaleUpdated) -#define RGFW_scaleUpdatedCallback(w, scaleX, scaleY) if (RGFW_scaleUpdatedCallbackSrc) RGFW_scaleUpdatedCallbackSrc(w, scaleX, scaleY); - -RGFW_CALLBACK_DEFINE(debug, Debug) -#define RGFW_debugCallback(type, err, msg) if (RGFW_debugCallbackSrc) RGFW_debugCallbackSrc(type, err, msg); #undef RGFW_CALLBACK_DEFINE -#ifdef RGFW_DEBUG -#include -#endif +void RGFW_window_checkEvents(RGFW_window* win, i32 waitMS) { + RGFW_window_eventWait(win, waitMS); -void RGFW_sendDebugInfo(RGFW_debugType type, RGFW_errorCode err, const char* msg) { - RGFW_debugCallback(type, err, msg); - - #ifdef RGFW_DEBUG - switch (type) { - case RGFW_typeInfo: RGFW_PRINTF("RGFW INFO (%i %i): %s", type, err, msg); break; - case RGFW_typeError: RGFW_PRINTF("RGFW DEBUG (%i %i): %s", type, err, msg); break; - case RGFW_typeWarning: RGFW_PRINTF("RGFW WARNING (%i %i): %s", type, err, msg); break; - default: break; + while (RGFW_window_checkEvent(win) != NULL && RGFW_window_shouldClose(win) == 0) { + if (win->event.type == RGFW_quit) return; } - RGFW_PRINTF("\n"); + #ifdef RGFW_WASM /* WASM needs to run the sleep function for asyncify */ + RGFW_sleep(0); #endif } void RGFW_window_checkMode(RGFW_window* win); void RGFW_window_checkMode(RGFW_window* win) { - if (RGFW_window_isMinimized(win) && (win->internal.enabledEvents & RGFW_windowMinimizedFlag)) { - win->internal.flags |= RGFW_windowMinimize; - RGFW_eventQueuePushEx(e.type = RGFW_windowMinimized; e.common.win = win); - RGFW_windowMinimizedCallback(win); - } else if (RGFW_window_isMaximized(win) && (win->internal.enabledEvents & RGFW_windowMaximizedFlag)) { - win->internal.flags |= RGFW_windowMaximize; - RGFW_eventQueuePushEx(e.type = RGFW_windowMaximized; e.common.win = win); - RGFW_windowMaximizedCallback(win, win->x, win->y, win->w, win->h); - } else if ((((win->internal.flags & RGFW_windowMinimize) && !RGFW_window_isMaximized(win)) || - (win->internal.flags & RGFW_windowMaximize && !RGFW_window_isMaximized(win))) && (win->internal.enabledEvents & RGFW_windowRestoredFlag)) { - win->internal.flags &= ~(u32)RGFW_windowMinimize; - if (RGFW_window_isMaximized(win) == RGFW_FALSE) win->internal.flags &= ~(u32)RGFW_windowMaximize; - RGFW_eventQueuePushEx(e.type = RGFW_windowRestored; e.common.win = win); - RGFW_windowRestoredCallback(win, win->x, win->y, win->w, win->h); + if (RGFW_window_isMinimized(win)) { + win->_flags |= RGFW_windowMinimize; + RGFW_windowMinimizedCallback(win, win->r); + } else if (RGFW_window_isMaximized(win)) { + win->_flags |= RGFW_windowMaximize; + RGFW_eventQueuePushEx(e.type = RGFW_windowMaximized; e._win = win); + RGFW_windowMaximizedCallback(win, win->r); + } else if (((win->_flags & RGFW_windowMinimize) && !RGFW_window_isMaximized(win)) || + (win->_flags & RGFW_windowMaximize && !RGFW_window_isMaximized(win))) { + win->_flags &= ~(u32)RGFW_windowMinimize; + if (RGFW_window_isMaximized(win) == RGFW_FALSE) win->_flags &= ~(u32)RGFW_windowMaximize; + RGFW_eventQueuePushEx(e.type = RGFW_windowRestored; e._win = win); + RGFW_windowRestoredCallback(win, win->r); } } @@ -3196,359 +1934,173 @@ void RGFW_window_checkMode(RGFW_window* win) { no more event call back defines */ -size_t RGFW_sizeofInfo(void) { return sizeof(RGFW_info); } -size_t RGFW_sizeofNativeImage(void) { return sizeof(RGFW_nativeImage); } -size_t RGFW_sizeofSurface(void) { return sizeof(RGFW_surface); } -size_t RGFW_sizeofWindow(void) { return sizeof(RGFW_window); } -size_t RGFW_sizeofWindowSrc(void) { return sizeof(RGFW_window_src); } +#define SET_ATTRIB(a, v) { \ + RGFW_ASSERT(((size_t) index + 1) < sizeof(attribs) / sizeof(attribs[0])); \ + attribs[index++] = a; \ + attribs[index++] = v; \ +} -RGFW_window_src* RGFW_window_getSrc(RGFW_window* win) { return &win->src; } -RGFW_bool RGFW_window_getPosition(RGFW_window* win, i32* x, i32* y) { if (x) *x = win->x; if (y) *y = win->y; return RGFW_TRUE; } -RGFW_bool RGFW_window_getSize(RGFW_window* win, i32* w, i32* h) { if (w) *w = win->w; if (h) *h = win->h; return RGFW_TRUE; } -u32 RGFW_window_getFlags(RGFW_window* win) { return win->internal.flags; } -RGFW_key RGFW_window_getExitKey(RGFW_window* win) { return win->internal.exitKey; } -void RGFW_window_setExitKey(RGFW_window* win, RGFW_key key) { win->internal.exitKey = key; } -void RGFW_window_setEnabledEvents(RGFW_window* win, RGFW_eventFlag events) { win->internal.enabledEvents = events; } -RGFW_eventFlag RGFW_window_getEnabledEvents(RGFW_window* win) { return win->internal.enabledEvents; } -void RGFW_window_setDisabledEvents(RGFW_window* win, RGFW_eventFlag events) { RGFW_window_setEnabledEvents(win, (RGFW_allEventFlags) & ~(u32)events); } -void RGFW_window_setEventState(RGFW_window* win, RGFW_eventFlag event, RGFW_bool state) { RGFW_setBit(&win->internal.enabledEvents, event, state); } -void* RGFW_window_getUserPtr(RGFW_window* win) { return win->userPtr; } -void RGFW_window_setUserPtr(RGFW_window* win, void* ptr) { win->userPtr = ptr; } +#define RGFW_EVENT_PASSED RGFW_BIT(24) /* if a queued event was passed */ +#define RGFW_EVENT_QUIT RGFW_BIT(25) /* the window close button was pressed */ +#define RGFW_HOLD_MOUSE RGFW_BIT(26) /*!< hold the moues still */ +#define RGFW_MOUSE_LEFT RGFW_BIT(27) /* if mouse left the window */ +#define RGFW_WINDOW_ALLOC RGFW_BIT(28) /* if window was allocated by RGFW */ +#define RGFW_BUFFER_ALLOC RGFW_BIT(29) /* if window.buffer was allocated by RGFW */ +#define RGFW_WINDOW_INIT RGFW_BIT(30) /* if window.buffer was allocated by RGFW */ +#define RGFW_INTERNAL_FLAGS (RGFW_EVENT_QUIT | RGFW_EVENT_PASSED | RGFW_HOLD_MOUSE | RGFW_MOUSE_LEFT | RGFW_WINDOW_ALLOC | RGFW_BUFFER_ALLOC | RGFW_windowFocus) +RGFW_window* RGFW_createWindow(const char* name, RGFW_rect rect, RGFW_windowFlags flags) { + RGFW_window* win = (RGFW_window*)RGFW_ALLOC(sizeof(RGFW_window)); + RGFW_ASSERT(win != NULL); + win->_flags = RGFW_WINDOW_ALLOC; + return RGFW_createWindowPtr(name, rect, flags, win); +} #if defined(RGFW_USE_XDL) && defined(RGFW_X11) #define XDL_IMPLEMENTATION #include "XDL.h" #endif -#ifndef RGFW_FORCE_INIT -RGFW_info _rgfwGlobal; +#define RGFW_MAX_EVENTS 32 +typedef struct RGFW_globalStruct { + RGFW_window* root; + RGFW_window* current; + i32 windowCount; + i32 eventLen; + i32 eventIndex; + + #ifdef RGFW_X11 + Display* display; + Window helperWindow; + char* clipboard; /* for writing to the clipboard selection */ + size_t clipboard_len; + #endif + #ifdef RGFW_WAYLAND + struct wl_display* wl_display; + #endif + #if defined(RGFW_X11) || defined(RGFW_WINDOWS) || defined(RGFW_WAYLAND) + RGFW_mouse* hiddenMouse; + #endif + RGFW_event events[RGFW_MAX_EVENTS]; + +} RGFW_globalStruct; +#if !defined(RGFW_C89) && !defined(__cplusplus) +RGFW_globalStruct _RGFW = {.root = NULL, .current = NULL, .windowCount = -1, .eventLen = 0, .eventIndex = 0}; +#define _RGFW_init RGFW_TRUE +#else +RGFW_bool _RGFW_init = RGFW_FALSE; +RGFW_globalStruct _RGFW; #endif -i32 RGFW_init(void) { return RGFW_init_ptr(&_rgfwGlobal); } -void RGFW_deinit(void) { RGFW_deinit_ptr(&_rgfwGlobal); } - -i32 RGFW_initPlatform(void); -void RGFW_deinitPlatform(void); - -i32 RGFW_init_ptr(RGFW_info* info) { - if (info == _RGFW || info == NULL) return 1; - - RGFW_setInfo(info); - RGFW_MEMSET(_RGFW, 0, sizeof(RGFW_info)); - _RGFW->queueEvents = RGFW_FALSE; - _RGFW->polledEvents = RGFW_FALSE; -#ifdef RGFW_WAYLAND - _RGFW->useWaylandBool = RGFW_TRUE; -#endif - - _RGFW->files = (char**)(void*)_RGFW->filesSrc; - u32 i; - for (i = 0; i < RGFW_MAX_DROPS; i++) - _RGFW->files[i] = (char*)(_RGFW->filesSrc + RGFW_MAX_DROPS + (i * RGFW_MAX_PATH)); - - _RGFW->monitors.freeList.head = &_RGFW->monitors.data[0]; - _RGFW->monitors.freeList.cur = _RGFW->monitors.freeList.head; - - for (i = 1; i < RGFW_MAX_MONITORS; i++) { - RGFW_monitorNode* newNode = &_RGFW->monitors.data[i]; - _RGFW->monitors.freeList.cur->next = newNode; - _RGFW->monitors.freeList.cur = _RGFW->monitors.freeList.cur->next; - } - - RGFW_initKeycodes(); - i32 out = RGFW_initPlatform(); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, "global context initialized"); - - return out; -} - -#ifndef RGFW_EGL -void RGFW_unloadEGL(void) { } -#endif - -void RGFW_deinit_ptr(RGFW_info* info) { - if (info == NULL) return; - - RGFW_setInfo(info); - RGFW_unloadEGL(); - RGFW_deinitPlatform(); - - _RGFW->root = NULL; - _RGFW->windowCount = 0; - RGFW_setInfo(NULL); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, "global context deinitialized"); -} - -RGFW_window* RGFW_createWindow(const char* name, i32 x, i32 y, i32 w, i32 h, RGFW_windowFlags flags) { - RGFW_window* win = (RGFW_window*)RGFW_ALLOC(sizeof(RGFW_window)); - RGFW_ASSERT(win != NULL); - return RGFW_createWindowPtr(name, x, y, w, h, flags, win); -} - -void RGFW_window_close(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - RGFW_window_closePtr(win); - RGFW_FREE(win); -} - -RGFW_window* RGFW_createWindowPtr(const char* name, i32 x, i32 y, i32 w, i32 h, RGFW_windowFlags flags, RGFW_window* win) { - RGFW_ASSERT(win != NULL); - RGFW_MEMSET(win, 0, sizeof(RGFW_window)); - if (_RGFW == NULL) RGFW_init(); - _RGFW->windowCount++; - - /* rect based the requested flags */ - if (_RGFW->root == NULL) { - RGFW_setRootWindow(win); - } - - /* set and init the new window's data */ - win->x = x; - win->y = y; - win->w = w; - win->h = h; - win->internal.flags = flags; - win->internal.enabledEvents = RGFW_allEventFlags; - - RGFW_window* ret = RGFW_createWindowPlatform(name, flags, win); - -#ifndef RGFW_X11 - RGFW_window_setFlagsInternal(win, flags, 0); -#endif - -#ifdef RGFW_OPENGL - win->src.gfxType = 0; - if (flags & RGFW_windowOpenGL) - RGFW_window_createContext_OpenGL(win, RGFW_getGlobalHints_OpenGL()); -#endif - -#ifdef RGFW_EGL - if (flags & RGFW_windowEGL) - RGFW_window_createContext_EGL(win, RGFW_getGlobalHints_OpenGL()); -#endif - - /* X11 creates the window after the OpenGL context is created (because of visual garbage), - * so we have to wait to set the flags - * This is required so that way the user can create their own OpenGL context after RGFW_createWindow is used - * if a window is crated, CreateContext will delete the window and create a new one - * */ -#ifdef RGFW_X11 - RGFW_window_setFlagsInternal(win, flags, 0); -#endif - -#ifdef RGFW_MACOS - /*NOTE: another OpenGL/setFlags related hack, this because OSX the 'view' class must be setup after the NSOpenGL view is made AND after setFlags happens */ - RGFW_osx_initView(win); -#endif - -#ifdef RGFW_WAYLAND - /* recieve all events needed to configure the surface */ - /* also gets the wl_outputs */ - if (RGFW_usingWayland()) { - wl_display_roundtrip(_RGFW->wl_display); - /* NOTE: this is a hack so that way wayland spawns a window, even if nothing is drawn */ - if (!(flags & RGFW_windowOpenGL) && !(flags & RGFW_windowEGL)) { - u8* data = (u8*)RGFW_ALLOC((u32)(win->w * win->h * 3)); - RGFW_MEMSET(data, 0, (u32)(win->w * win->h * 3) * sizeof(u8)); - RGFW_surface* surface = RGFW_createSurface(data, win->w, win->h, RGFW_formatBGR8); - RGFW_window_blitSurface(win, surface); - RGFW_FREE(data); - RGFW_surface_free(surface); - } - } -#endif - - RGFW_window_setMouseDefault(win); - RGFW_window_setName(win, name); - if (!(flags & RGFW_windowHide)) { - RGFW_window_show(win); - } - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, "a new window was created"); - - - return ret; -} - -void RGFW_window_closePtr(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - - #ifdef RGFW_EGL - if ((win->src.gfxType & RGFW_gfxEGL) && win->src.ctx.egl) { - RGFW_window_deleteContext_EGL(win, win->src.ctx.egl); - win->src.ctx.egl = NULL; - } - #endif - - #ifdef RGFW_OPENGL - if ((win->src.gfxType & RGFW_gfxNativeOpenGL) && win->src.ctx.native) { - RGFW_window_deleteContext_OpenGL(win, win->src.ctx.native); - win->src.ctx.native = NULL; - } - #endif - - RGFW_window_closePlatform(win); - - RGFW_clipboard_switch(NULL); - _RGFW->windowCount--; - if (_RGFW->windowCount == 0) RGFW_deinit(); - - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, "a window was freed"); -} - -void RGFW_setQueueEvents(RGFW_bool queue) { _RGFW->queueEvents = RGFW_BOOL(queue); } - -void RGFW_eventQueueFlush(void) { _RGFW->eventLen = 0; } - -void RGFW_eventQueuePush(const RGFW_event* event) { - if (_RGFW->queueEvents == RGFW_FALSE) return; - RGFW_ASSERT(_RGFW->eventLen >= 0); - - if (_RGFW->eventLen >= RGFW_MAX_EVENTS) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEventQueue, "Event queue limit 'RGFW_MAX_EVENTS' has been reached automatically flushing queue."); - RGFW_eventQueueFlush(); - return; - } - - i32 eventTop = (_RGFW->eventBottom + _RGFW->eventLen) % RGFW_MAX_EVENTS; - _RGFW->eventLen += 1; - _RGFW->events[eventTop] = *event; +void RGFW_eventQueuePush(RGFW_event event) { + if (_RGFW.eventLen >= RGFW_MAX_EVENTS) return; + _RGFW.events[_RGFW.eventLen] = event; + _RGFW.eventLen++; } RGFW_event* RGFW_eventQueuePop(RGFW_window* win) { - RGFW_ASSERT(_RGFW->eventLen >= 0 && _RGFW->eventLen <= RGFW_MAX_EVENTS); - RGFW_event* ev; + RGFW_event* ev; + if (_RGFW.eventLen == 0) return NULL; - if (_RGFW->eventLen == 0) { - return NULL; - } - - ev = &_RGFW->events[_RGFW->eventBottom]; - _RGFW->eventLen -= 1; - _RGFW->eventBottom = (_RGFW->eventBottom + 1) % RGFW_MAX_EVENTS; - - if (ev->common.win != win && ev->common.win != NULL) { - RGFW_eventQueuePush(ev); - return NULL; + ev = (RGFW_event*)&_RGFW.events[_RGFW.eventIndex]; + + _RGFW.eventLen--; + if (_RGFW.eventLen >= 0 && _RGFW.eventIndex < _RGFW.eventLen) { + _RGFW.eventIndex++; + } else if (_RGFW.eventLen == 0) { + _RGFW.eventIndex = 0; + } + + if (ev->_win != win && ev->_win != NULL) { + RGFW_eventQueuePush(*ev); + return NULL; } + ev->droppedFilesCount = win->event.droppedFilesCount; + ev->droppedFiles = win->event.droppedFiles; return ev; } -void RGFW_resetPrevState(void) { - size_t i; /*!< reset each previous state */ - for (i = 0; i < RGFW_keyLast; i++) _RGFW->keyboard[i].prev = _RGFW->keyboard[i].current; - for (i = 0; i < RGFW_mouseFinal; i++) _RGFW->mouseButtons[i].prev = _RGFW->mouseButtons[i].current; - _RGFW->scrollX = 0.0f; - _RGFW->scrollY = 0.0f; - _RGFW->vectorX = (float)0.0f; - _RGFW->vectorY = (float)0.0f; - RGFW_MEMSET(&_RGFW->windowState, 0, sizeof(_RGFW->windowState)); -} - -RGFW_bool RGFW_isKeyPressed(RGFW_key key) { - return _RGFW != NULL && _RGFW->keyboard[key].current && !_RGFW->keyboard[key].prev; -} - -RGFW_bool RGFW_isKeyDown(RGFW_key key) { - return _RGFW != NULL && _RGFW->keyboard[key].current; -} - -RGFW_bool RGFW_isKeyReleased(RGFW_key key) { - return _RGFW != NULL && !_RGFW->keyboard[key].current && _RGFW->keyboard[key].prev; -} - - -RGFW_bool RGFW_isMousePressed(RGFW_mouseButton button) { - return _RGFW != NULL && _RGFW->mouseButtons[button].current && !_RGFW->mouseButtons[button].prev; -} -RGFW_bool RGFW_isMouseDown(RGFW_mouseButton button) { - return _RGFW != NULL && _RGFW->mouseButtons[button].current; -} -RGFW_bool RGFW_isMouseReleased(RGFW_mouseButton button) { - return _RGFW != NULL && !_RGFW->mouseButtons[button].current && _RGFW->mouseButtons[button].prev; -} - -void RGFW_getMouseScroll(float* x, float* y) { - RGFW_ASSERT(_RGFW != NULL); - if (x) *x = _RGFW->scrollX; - if (y) *y = _RGFW->scrollY; -} - -void RGFW_getMouseVector(float* x, float* y) { - RGFW_ASSERT(_RGFW != NULL); - if (x) *x = _RGFW->vectorX; - if (y) *y = _RGFW->vectorY; -} - -RGFW_bool RGFW_window_didMouseLeave(RGFW_window* win) { return _RGFW->windowState.winLeave == win && _RGFW->windowState.mouseLeave; } -RGFW_bool RGFW_window_didMouseEnter(RGFW_window* win) { return _RGFW->windowState.win == win && _RGFW->windowState.mouseEnter; } -RGFW_bool RGFW_window_isMouseInside(RGFW_window* win) { return win->internal.mouseInside; } - -RGFW_bool RGFW_window_isDataDragging(RGFW_window* win) { return RGFW_window_getDataDrag(win, (i32*)NULL, (i32*)NULL); } -RGFW_bool RGFW_window_didDataDrop(RGFW_window* win) { return RGFW_window_getDataDrop(win, (const char***)NULL, (size_t*)NULL);} - - -RGFW_bool RGFW_window_getDataDrag(RGFW_window* win, i32* x, i32* y) { - if (_RGFW->windowState.win != win || _RGFW->windowState.dataDragging == RGFW_FALSE) return RGFW_FALSE; - if (x) *x = _RGFW->windowState.dropX; - if (y) *y = _RGFW->windowState.dropY; - return RGFW_TRUE; -} -RGFW_bool RGFW_window_getDataDrop(RGFW_window* win, const char*** files, size_t* count) { - if (_RGFW->windowState.win != win || _RGFW->windowState.dataDrop == RGFW_FALSE) return RGFW_FALSE; - if (files) *files = (const char**)_RGFW->files; - if (count) *count = _RGFW->windowState.filesCount; - return RGFW_TRUE; -} - -RGFW_bool RGFW_window_checkEvent(RGFW_window* win, RGFW_event* event) { - if (_RGFW->eventLen == 0 && _RGFW->polledEvents == RGFW_FALSE) { - _RGFW->queueEvents = RGFW_TRUE; - RGFW_pollEvents(); - _RGFW->polledEvents = RGFW_TRUE; - } - - if (RGFW_window_checkQueuedEvent(win, event) == RGFW_FALSE) { - _RGFW->polledEvents = RGFW_FALSE; - return RGFW_FALSE; - } - - return RGFW_TRUE; -} - -RGFW_bool RGFW_window_checkQueuedEvent(RGFW_window* win, RGFW_event* event) { +RGFW_event* RGFW_window_checkEventCore(RGFW_window* win); +RGFW_event* RGFW_window_checkEventCore(RGFW_window* win) { RGFW_event* ev; - RGFW_ASSERT(win != NULL); - _RGFW->queueEvents = RGFW_TRUE; + RGFW_ASSERT(win != NULL); + if (win->event.type == 0 && _RGFW.eventLen == 0) + RGFW_resetKeyPrev(); + + if (win->event.type == RGFW_quit && win->_flags & RGFW_windowFreeOnClose) { + static RGFW_event event; + event = win->event; + RGFW_window_close(win); + return &event; + } + + if (win->event.type != RGFW_DNDInit) win->event.type = 0; + /* check queued events */ ev = RGFW_eventQueuePop(win); if (ev != NULL) { if (ev->type == RGFW_quit) RGFW_window_setShouldClose(win, RGFW_TRUE); - *event = *ev; - return RGFW_TRUE; + win->event = *ev; } + else return NULL; - return RGFW_FALSE; + return &win->event; } -void RGFW_setRootWindow(RGFW_window* win) { _RGFW->root = win; } -RGFW_window* RGFW_getRootWindow(void) { return _RGFW->root; } -#ifndef RGFW_EGL -RGFW_bool RGFW_loadEGL(void) { return RGFW_FALSE; } -#endif +RGFWDEF void RGFW_window_basic_init(RGFW_window* win, RGFW_rect rect, RGFW_windowFlags flags); +void RGFW_setRootWindow(RGFW_window* win) { _RGFW.root = win; } +RGFW_window* RGFW_getRootWindow(void) { return _RGFW.root; } + +/* do a basic initialization for RGFW_window, this is to standard it for each OS */ +void RGFW_window_basic_init(RGFW_window* win, RGFW_rect rect, RGFW_windowFlags flags) { + RGFW_UNUSED(flags); + if (_RGFW.windowCount == -1 || _RGFW_init == RGFW_FALSE) RGFW_init(); + _RGFW.windowCount++; + + /* rect based the requested flags */ + if (_RGFW.root == NULL) { + RGFW_setRootWindow(win); + RGFW_setTime(0); + } + + if (!(win->_flags & RGFW_WINDOW_ALLOC)) win->_flags = 0; + + /* set and init the new window's data */ + win->r = rect; + win->exitKey = RGFW_escape; + win->event.droppedFilesCount = 0; + + win->_flags = 0 | (win->_flags & RGFW_WINDOW_ALLOC); + win->_flags |= flags; + win->event.keyMod = 0; + win->_lastMousePoint.x = 0; + win->_lastMousePoint.y = 0; + + win->event.droppedFiles = (char**)RGFW_ALLOC(RGFW_MAX_PATH * RGFW_MAX_DROPS); + RGFW_ASSERT(win->event.droppedFiles != NULL); + + { + u32 i; + for (i = 0; i < RGFW_MAX_DROPS; i++) + win->event.droppedFiles[i] = (char*)(win->event.droppedFiles + RGFW_MAX_DROPS + (i * RGFW_MAX_PATH)); + } +} + +void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags flags) { + RGFW_windowFlags cmpFlags = win->_flags; + if (win->_flags & RGFW_WINDOW_INIT) cmpFlags = 0; -void RGFW_window_setFlagsInternal(RGFW_window* win, RGFW_windowFlags flags, RGFW_windowFlags cmpFlags) { #ifndef RGFW_NO_MONITOR if (flags & RGFW_windowScaleToMonitor) RGFW_window_scaleToMonitor(win); #endif if (flags & RGFW_windowCenter) RGFW_window_center(win); - if (flags & RGFW_windowCenterCursor) RGFW_window_moveMouse(win, win->x + (win->w / 2), win->y + (win->h / 2)); + if (flags & RGFW_windowCenterCursor) + RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); if (flags & RGFW_windowNoBorder) RGFW_window_setBorder(win, 0); - else if (cmpFlags & RGFW_windowNoBorder) RGFW_window_setBorder(win, 1); + else RGFW_window_setBorder(win, 1); if (flags & RGFW_windowFullscreen) RGFW_window_setFullscreen(win, RGFW_TRUE); else if (cmpFlags & RGFW_windowFullscreen) RGFW_window_setFullscreen(win, 0); if (flags & RGFW_windowMaximize) RGFW_window_maximize(win); @@ -3559,97 +2111,153 @@ void RGFW_window_setFlagsInternal(RGFW_window* win, RGFW_windowFlags flags, RGFW else if (cmpFlags & RGFW_windowHideMouse) RGFW_window_showMouse(win, 1); if (flags & RGFW_windowHide) RGFW_window_hide(win); else if (cmpFlags & RGFW_windowHide) RGFW_window_show(win); + if (flags & RGFW_windowCocoaCHDirToRes) RGFW_moveToMacOSResourceDir(); if (flags & RGFW_windowFloating) RGFW_window_setFloating(win, 1); else if (cmpFlags & RGFW_windowFloating) RGFW_window_setFloating(win, 0); if (flags & RGFW_windowFocus) RGFW_window_focus(win); if (flags & RGFW_windowNoResize) { - RGFW_window_setMaxSize(win, win->w, win->h); - RGFW_window_setMinSize(win, win->w, win->h); + RGFW_window_setMaxSize(win, RGFW_AREA(win->r.w, win->r.h)); + RGFW_window_setMinSize(win, RGFW_AREA(win->r.w, win->r.h)); } else if (cmpFlags & RGFW_windowNoResize) { - RGFW_window_setMaxSize(win, 0, 0); - RGFW_window_setMinSize(win, 0, 0); + RGFW_window_setMaxSize(win, RGFW_AREA(0, 0)); + RGFW_window_setMinSize(win, RGFW_AREA(0, 0)); } - win->internal.flags = flags; + win->_flags = flags | (win->_flags & RGFW_INTERNAL_FLAGS); } - -void RGFW_window_setFlags(RGFW_window* win, RGFW_windowFlags flags) { RGFW_window_setFlagsInternal(win, flags, win->internal.flags); } +RGFW_bool RGFW_window_opengl_isSoftware(RGFW_window* win) { + return RGFW_BOOL(win->_flags |= RGFW_windowOpenglSoftware); +} RGFW_bool RGFW_window_isInFocus(RGFW_window* win) { #ifdef RGFW_WASM return RGFW_TRUE; #else - return RGFW_BOOL(win->internal.inFocus); + return RGFW_BOOL(win->_flags & RGFW_windowFocus); #endif } -void RGFW_setClassName(const char* name) { RGFW_init(); _RGFW->className = name; } +void RGFW_window_initBuffer(RGFW_window* win) { + RGFW_area area = RGFW_getScreenSize(); + if ((win->_flags & RGFW_windowNoResize)) + area = RGFW_AREA(win->r.w, win->r.h); + + RGFW_window_initBufferSize(win, area); +} + +void RGFW_window_initBufferSize(RGFW_window* win, RGFW_area area) { +#if defined(RGFW_BUFFER) || defined(RGFW_OSMESA) + win->_flags |= RGFW_BUFFER_ALLOC; + #ifndef RGFW_WINDOWS + u8* buffer = (u8*)RGFW_ALLOC(area.w * area.h * 4); + RGFW_ASSERT(buffer != NULL); + + RGFW_window_initBufferPtr(win, buffer, area); + #else /* windows's bitmap allocs memory for us */ + RGFW_window_initBufferPtr(win, (u8*)NULL, area); + #endif +#else + RGFW_UNUSED(win); RGFW_UNUSED(area); +#endif +} + +#ifdef RGFW_MACOS +RGFWDEF void RGFW_window_cocoaSetLayer(RGFW_window* win, void* layer); +RGFWDEF void* RGFW_cocoaGetLayer(void); +#endif + +const char* RGFW_className = NULL; +void RGFW_setClassName(const char* name) { RGFW_className = name; } #ifndef RGFW_X11 void RGFW_setXInstName(const char* name) { RGFW_UNUSED(name); } #endif -RGFW_bool RGFW_window_getMouse(RGFW_window* win, i32* x, i32* y) { - RGFW_ASSERT(win != NULL); - if (x) *x = win->internal.lastMouseX; - if (y) *y = win->internal.lastMouseY; - return RGFW_TRUE; +RGFW_keyState RGFW_mouseButtons[RGFW_mouseFinal] = { {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + +RGFW_bool RGFW_isMousePressed(RGFW_window* win, RGFW_mouseButton button) { + return RGFW_mouseButtons[button].current && (win == NULL || RGFW_window_isInFocus(win)); +} +RGFW_bool RGFW_wasMousePressed(RGFW_window* win, RGFW_mouseButton button) { + return RGFW_mouseButtons[button].prev && (win != NULL || RGFW_window_isInFocus(win)); +} +RGFW_bool RGFW_isMouseHeld(RGFW_window* win, RGFW_mouseButton button) { + return (RGFW_isMousePressed(win, button) && RGFW_wasMousePressed(win, button)); +} +RGFW_bool RGFW_isMouseReleased(RGFW_window* win, RGFW_mouseButton button) { + return (!RGFW_isMousePressed(win, button) && RGFW_wasMousePressed(win, button)); } -RGFW_bool RGFW_window_isKeyPressed(RGFW_window* win, RGFW_key key) { return RGFW_isKeyPressed(key) && RGFW_window_isInFocus(win); } -RGFW_bool RGFW_window_isKeyDown(RGFW_window* win, RGFW_key key) { return RGFW_isKeyDown(key) && RGFW_window_isInFocus(win); } -RGFW_bool RGFW_window_isKeyReleased(RGFW_window* win, RGFW_key key) { return RGFW_isKeyReleased(key) && RGFW_window_isInFocus(win); } +RGFW_point RGFW_window_getMousePoint(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + return win->_lastMousePoint; +} -RGFW_bool RGFW_window_isMousePressed(RGFW_window* win, RGFW_mouseButton button) { return RGFW_isMousePressed(button) && RGFW_window_isInFocus(win); } -RGFW_bool RGFW_window_isMouseDown(RGFW_window* win, RGFW_mouseButton button) { return RGFW_isMouseDown(button) && RGFW_window_isInFocus(win); } -RGFW_bool RGFW_window_isMouseReleased(RGFW_window* win, RGFW_mouseButton button) { return RGFW_isMouseReleased(button) && RGFW_window_isInFocus(win); } +RGFW_bool RGFW_isPressed(RGFW_window* win, RGFW_key key) { + return RGFW_keyboard[key].current && (win == NULL || RGFW_window_isInFocus(win)); +} +RGFW_bool RGFW_wasPressed(RGFW_window* win, RGFW_key key) { + return RGFW_keyboard[key].prev && (win == NULL || RGFW_window_isInFocus(win)); +} +RGFW_bool RGFW_isHeld(RGFW_window* win, RGFW_key key) { + return (RGFW_isPressed(win, key) && RGFW_wasPressed(win, key)); +} -#ifndef RGFW_X11 -void* RGFW_getDisplay_X11(void) { return NULL; } -u64 RGFW_window_getWindow_X11(RGFW_window* win) { RGFW_UNUSED(win); return 0; } +RGFW_bool RGFW_isClicked(RGFW_window* win, RGFW_key key) { + return (RGFW_wasPressed(win, key) && !RGFW_isPressed(win, key)); +} + +RGFW_bool RGFW_isReleased(RGFW_window* win, RGFW_key key) { + return (!RGFW_isPressed(win, key) && RGFW_wasPressed(win, key)); +} + +void RGFW_window_makeCurrent(RGFW_window* win) { + _RGFW.current = win; +#if defined(RGFW_OPENGL) || defined(RGFW_EGL) + RGFW_window_makeCurrent_OpenGL(win); #endif +} -#ifndef RGFW_WAYLAND -struct wl_display* RGFW_getDisplay_Wayland(void) { return NULL; } -struct wl_surface* RGFW_window_getWindow_Wayland(RGFW_window* win) { RGFW_UNUSED(win); return NULL; } +RGFW_window* RGFW_getCurrent(void) { + return _RGFW.current; +} + +void RGFW_window_swapBuffers(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + RGFW_window_swapBuffers_software(win); +#if defined(RGFW_OPENGL) || defined(RGFW_EGL) + RGFW_window_swapBuffers_OpenGL(win); #endif +} -#ifndef RGFW_WINDOWS -void* RGFW_window_getHWND(RGFW_window* win) { RGFW_UNUSED(win); return NULL; } -void* RGFW_window_getHDC(RGFW_window* win) { RGFW_UNUSED(win); return NULL; } -#endif - -#ifndef RGFW_MACOS -void* RGFW_window_getView_OSX(RGFW_window* win) { RGFW_UNUSED(win); return NULL; } -void RGFW_window_setLayer_OSX(RGFW_window* win, void* layer) { RGFW_UNUSED(win); RGFW_UNUSED(layer); } -void* RGFW_getLayer_OSX(void) { return NULL; } -void* RGFW_window_getWindow_OSX(RGFW_window* win) { RGFW_UNUSED(win); return NULL; } -#endif - -void RGFW_setBit(u32* var, u32 mask, RGFW_bool set) { - if (set) *var |= mask; - else *var &= ~mask; +RGFWDEF void RGFW_setBit(u32* data, u32 bit, RGFW_bool value); +void RGFW_setBit(u32* data, u32 bit, RGFW_bool value) { + if (value) + *data |= bit; + else if (!value && (*(data) & bit)) + *data ^= bit; } void RGFW_window_center(RGFW_window* win) { RGFW_ASSERT(win != NULL); - RGFW_monitor mon = RGFW_window_getMonitor(win); - RGFW_window_move(win, (i32)(mon.mode.w - win->w) / 2, (mon.mode.h - win->h) / 2); + RGFW_area screenR = RGFW_getScreenSize(); + RGFW_window_move(win, RGFW_POINT((i32)(screenR.w - (u32)win->r.w) / 2, (screenR.h - (u32)win->r.h) / 2)); } RGFW_bool RGFW_monitor_scaleToWindow(RGFW_monitor mon, RGFW_window* win) { RGFW_monitorMode mode; RGFW_ASSERT(win != NULL); - mode.w = win->w; - mode.h = win->h; + mode.area.w = (u32)win->r.w; + mode.area.h = (u32)win->r.h; return RGFW_monitor_requestMode(mon, mode, RGFW_monitorScale); } +void RGFW_splitBPP(u32 bpp, RGFW_monitorMode* mode); void RGFW_splitBPP(u32 bpp, RGFW_monitorMode* mode) { if (bpp == 32) bpp = 24; mode->red = mode->green = mode->blue = (u8)(bpp / 3); @@ -3660,21 +2268,21 @@ void RGFW_splitBPP(u32 bpp, RGFW_monitorMode* mode) { } RGFW_bool RGFW_monitorModeCompare(RGFW_monitorMode mon, RGFW_monitorMode mon2, RGFW_modeRequest request) { - return (((mon.w == mon2.w && mon.h == mon2.h) || !(request & RGFW_monitorScale)) && + return (((mon.area.w == mon2.area.w && mon.area.h == mon2.area.h) || !(request & RGFW_monitorScale)) && ((mon.refreshRate == mon2.refreshRate) || !(request & RGFW_monitorRefresh)) && ((mon.red == mon2.red && mon.green == mon2.green && mon.blue == mon2.blue) || !(request & RGFW_monitorRGB))); } RGFW_bool RGFW_window_shouldClose(RGFW_window* win) { - return (win == NULL || win->internal.shouldClose || (win->internal.exitKey && RGFW_window_isKeyPressed(win, win->internal.exitKey))); + return (win == NULL || (win->_flags & RGFW_EVENT_QUIT)|| (win->exitKey && RGFW_isPressed(win, win->exitKey))); } void RGFW_window_setShouldClose(RGFW_window* win, RGFW_bool shouldClose) { if (shouldClose) { - win->internal.shouldClose = RGFW_TRUE; + win->_flags |= RGFW_EVENT_QUIT; RGFW_windowQuitCallback(win); } else { - win->internal.shouldClose = RGFW_FALSE; + win->_flags &= ~(u32)RGFW_EVENT_QUIT; } } @@ -3684,169 +2292,123 @@ void RGFW_window_scaleToMonitor(RGFW_window* win) { if (monitor.scaleX == 0 && monitor.scaleY == 0) return; - RGFW_window_resize(win, (i32)(monitor.scaleX * (float)win->w), (i32)(monitor.scaleY * (float)win->h)); + RGFW_window_resize(win, RGFW_AREA((u32)(monitor.scaleX * (float)win->r.w), (u32)(monitor.scaleY * (float)win->r.h))); } void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor m) { - RGFW_window_move(win, m.x + win->x, m.y + win->y); + RGFW_window_move(win, RGFW_POINT(m.x + win->r.x, m.y + win->r.y)); } #endif -RGFW_surface* RGFW_createSurface(u8* data, i32 w, i32 h, RGFW_format format) { - RGFW_surface* surface = (RGFW_surface*)RGFW_ALLOC(sizeof(RGFW_surface)); - RGFW_MEMSET(surface, 0, sizeof(RGFW_surface)); - RGFW_createSurfacePtr(data, w, h, format, surface); - return surface; +RGFW_bool RGFW_window_setIcon(RGFW_window* win, u8* icon, RGFW_area a, i32 channels) { + return RGFW_window_setIconEx(win, icon, a, channels, RGFW_iconBoth); } -void RGFW_surface_free(RGFW_surface* surface) { - RGFW_surface_freePtr(surface); - RGFW_FREE(surface); +RGFWDEF void RGFW_captureCursor(RGFW_window* win, RGFW_rect); +RGFWDEF void RGFW_releaseCursor(RGFW_window* win); + + +RGFW_bool RGFW_window_mouseHeld(RGFW_window* win) { return RGFW_BOOL(win->_flags & RGFW_HOLD_MOUSE); } + +void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area) { + if (!area.w && !area.h) + area = RGFW_AREA(win->r.w / 2, win->r.h / 2); + + win->_flags |= RGFW_HOLD_MOUSE; + RGFW_captureCursor(win, win->r); + RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); } -RGFW_nativeImage* RGFW_surface_getNativeImage(RGFW_surface* surface) { - return &surface->native; -} - -RGFW_surface* RGFW_window_createSurface(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format) { - RGFW_surface* surface = (RGFW_surface*)RGFW_ALLOC(sizeof(RGFW_surface)); - RGFW_MEMSET(surface, 0, sizeof(RGFW_surface)); - RGFW_window_createSurfacePtr(win, data, w, h, format, surface); - return surface; -} -#ifndef RGFW_X11 -RGFW_bool RGFW_window_createSurfacePtr(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { - RGFW_UNUSED(win); - return RGFW_createSurfacePtr(data, w, h, format, surface); -} -#endif - -const RGFW_colorLayout RGFW_layouts[RGFW_formatCount] = { - { 0, 1, 2, 3 }, /* RGFW_formatRGB8 */ - { 2, 1, 0, 3 }, /* RGFW_formatBGR8 */ - { 0, 1, 2, 3 }, /* RGFW_formatRGBA8 */ - { 1, 2, 3, 0 }, /* RGFW_formatARGB8 */ - { 2, 1, 0, 3 }, /* RGFW_formatBGRA8 */ - { 3, 2, 1, 0 }, /* RGFW_formatABGR8 */ -}; - - -void RGFW_copyImageData(u8* dest_data, i32 w, i32 h, RGFW_format dest_format, u8* src_data, RGFW_format src_format) { - RGFW_copyImageData64(dest_data, w, h, dest_format, src_data, src_format, RGFW_FALSE); -} - -void RGFW_copyImageData64(u8* dest_data, i32 dest_w, i32 dest_h, RGFW_format dest_format, u8* src_data, RGFW_format src_format, RGFW_bool is64bit) { - RGFW_ASSERT(dest_data && src_data); - - u32 src_channels = (src_format >= RGFW_formatRGBA8) ? 4 : 3; - u32 dest_channels = (dest_format >= RGFW_formatRGBA8) ? 4 : 3; - - u32 pixel_count = (u32)(dest_w * dest_h); - - if (src_format == dest_format) { - RGFW_MEMCPY(dest_data, src_data, pixel_count * dest_channels); - return; - } - - const RGFW_colorLayout* src_layout = &RGFW_layouts[src_format]; - const RGFW_colorLayout* dest_layout = &RGFW_layouts[dest_format]; - - u32 i, i2 = 0; - for (i = 0; i < pixel_count; i++) { - const u8* src_px = &src_data[i * src_channels]; - u8* dst_px = &dest_data[i2 * dest_channels]; - u8 rgba[4] = {0}; - rgba[0] = src_px[src_layout->r]; - rgba[1] = src_px[src_layout->g]; - rgba[2] = src_px[src_layout->b]; - rgba[3] = 255; - if (src_channels == 4) - rgba[3] = src_px[src_layout->a]; - - dst_px[dest_layout->r] = rgba[0]; - dst_px[dest_layout->g] = rgba[1]; - dst_px[dest_layout->b] = rgba[2]; - if (dest_channels == 4) - dst_px[dest_layout->a] = rgba[3]; - - i2 += 1 + is64bit; - } -} - -RGFW_monitorNode* RGFW_monitors_add(RGFW_monitor mon) { - RGFW_monitorNode* node = NULL; - if (_RGFW->monitors.freeList.head == NULL) return node; - - node = _RGFW->monitors.freeList.head; - mon = node->mon; - - _RGFW->monitors.freeList.head = node->next; - if (_RGFW->monitors.freeList.head == NULL) { - _RGFW->monitors.freeList.cur = NULL; - } - - node->next = NULL; - - if (_RGFW->monitors.list.head == NULL) { - _RGFW->monitors.list.head = node; - } else { - _RGFW->monitors.list.cur->next = node; - } - - _RGFW->monitors.list.cur = node; - - node->mon = mon; - _RGFW->monitors.count += 1; - return node; -} - -void RGFW_monitors_remove(RGFW_monitorNode* node, RGFW_monitorNode* prev) { - _RGFW->monitors.count -= 1; - - /* remove node from the list */ - if (prev != node) { - prev->next = node->next; - } else { /* node is the head */ - _RGFW->monitors.list.head = NULL; - } - - node->next = NULL; - - /* move node to the free list */ - if (_RGFW->monitors.freeList.head == NULL) { - _RGFW->monitors.freeList.head = node; - } else { - _RGFW->monitors.freeList.cur->next = node; - } - - _RGFW->monitors.freeList.cur = node; -} - -RGFW_bool RGFW_window_setIcon(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format) { - return RGFW_window_setIconEx(win, data, w, h, format, RGFW_iconBoth); -} - -void RGFW_window_holdMouse(RGFW_window* win) { - win->internal.holdMouse = RGFW_TRUE; - _RGFW->mouseOwner = win; - RGFW_captureCursor(win); - RGFW_window_moveMouse(win, win->x + (win->w / 2), win->y + (win->h / 2)); -} - -RGFW_bool RGFW_window_isHoldingMouse(RGFW_window* win) { return RGFW_BOOL(win->internal.holdMouse); } - -void RGFW_window_unholdMouse(RGFW_window* win) { - win->internal.holdMouse = RGFW_FALSE; - _RGFW->mouseOwner = NULL; +void RGFW_window_mouseUnhold(RGFW_window* win) { + win->_flags &= ~(u32)RGFW_HOLD_MOUSE; RGFW_releaseCursor(win); } -void RGFW_updateKeyMod(RGFW_window* win, RGFW_keymod mod, RGFW_bool value) { - if (value) win->internal.mod |= mod; - else win->internal.mod &= ~mod; +u32 RGFW_checkFPS(double startTime, u32 frameCount, u32 fpsCap) { + double deltaTime = RGFW_getTime() - startTime; + if (deltaTime == 0) return 0; + + double fps = (frameCount / deltaTime); /* the numer of frames over the time it took for them to render */ + if (fpsCap && fps > fpsCap) { + double frameTime = (double)frameCount / (double)fpsCap; /* how long it should take to finish the frames */ + double sleepTime = frameTime - deltaTime; /* subtract how long it should have taken with how long it did take */ + + if (sleepTime > 0) RGFW_sleep((u32)(sleepTime * 1000)); + } + + return (u32) fps; } -void RGFW_updateKeyModsEx(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll) { +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) +void RGFW_RGB_to_BGR(RGFW_window* win, u8* data) { + #if !defined(RGFW_BUFFER_BGR) && !defined(RGFW_OSMESA) + u32 x, y; + for (y = 0; y < (u32)win->r.h; y++) { + for (x = 0; x < (u32)win->r.w; x++) { + u32 index = (y * 4 * win->bufferSize.w) + x * 4; + + u8 red = data[index]; + data[index] = win->buffer[index + 2]; + data[index + 2] = red; + } + } + #elif defined(RGFW_OSMESA) + u32 y; + for(y = 0; y < (u32)win->r.h; y++){ + u32 index_from = (y + (win->bufferSize.h - win->r.h)) * 4 * win->bufferSize.w; + u32 index_to = y * 4 * win->bufferSize.w; + memcpy(&data[index_to], &data[index_from], 4 * win->bufferSize.w); + } + #else + RGFW_UNUSED(win); RGFW_UNUSED(data); + #endif +} +#endif + +u32 RGFW_isPressedGamepad(RGFW_window* win, u8 c, RGFW_gamepadCodes button) { + RGFW_UNUSED(win); + return RGFW_gamepadPressed[c][button].current; +} +u32 RGFW_wasPressedGamepad(RGFW_window* win, u8 c, RGFW_gamepadCodes button) { + RGFW_UNUSED(win); + return RGFW_gamepadPressed[c][button].prev; +} +u32 RGFW_isReleasedGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button) { + RGFW_UNUSED(win); + return !RGFW_isPressedGamepad(win, controller, button) && RGFW_wasPressedGamepad(win, controller, button); +} +u32 RGFW_isHeldGamepad(RGFW_window* win, u8 controller, RGFW_gamepadCodes button) { + RGFW_UNUSED(win); + return RGFW_isPressedGamepad(win, controller, button) && RGFW_wasPressedGamepad(win, controller, button); +} + +RGFW_point RGFW_getGamepadAxis(RGFW_window* win, u16 controller, u16 whichAxis) { + RGFW_UNUSED(win); + return RGFW_gamepadAxes[controller][whichAxis]; +} +const char* RGFW_getGamepadName(RGFW_window* win, u16 controller) { + RGFW_UNUSED(win); + return (const char*)RGFW_gamepads_name[controller]; +} + +size_t RGFW_getGamepadCount(RGFW_window* win) { + RGFW_UNUSED(win); + return RGFW_gamepadCount; +} + +RGFW_gamepadType RGFW_getGamepadType(RGFW_window* win, u16 controller) { + RGFW_UNUSED(win); + return RGFW_gamepads_type[controller]; +} + +RGFWDEF void RGFW_updateKeyMod(RGFW_window* win, RGFW_keymod mod, RGFW_bool value); +void RGFW_updateKeyMod(RGFW_window* win, RGFW_keymod mod, RGFW_bool value) { + if (value) win->event.keyMod |= mod; + else win->event.keyMod &= ~mod; +} + +RGFWDEF void RGFW_updateKeyModsPro(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll); +void RGFW_updateKeyModsPro(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll) { RGFW_updateKeyMod(win, RGFW_modCapsLock, capital); RGFW_updateKeyMod(win, RGFW_modNumLock, numlock); RGFW_updateKeyMod(win, RGFW_modControl, control); @@ -3856,63 +2418,60 @@ void RGFW_updateKeyModsEx(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock RGFW_updateKeyMod(win, RGFW_modScrollLock, scroll); } +RGFWDEF void RGFW_updateKeyMods(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool scroll); void RGFW_updateKeyMods(RGFW_window* win, RGFW_bool capital, RGFW_bool numlock, RGFW_bool scroll) { - RGFW_updateKeyModsEx(win, capital, numlock, - RGFW_window_isKeyDown(win, RGFW_controlL) || RGFW_window_isKeyDown(win, RGFW_controlR), - RGFW_window_isKeyDown(win, RGFW_altL) || RGFW_window_isKeyDown(win, RGFW_altR), - RGFW_window_isKeyDown(win, RGFW_shiftL) || RGFW_window_isKeyDown(win, RGFW_shiftR), - RGFW_window_isKeyDown(win, RGFW_superL) || RGFW_window_isKeyDown(win, RGFW_superR), + RGFW_updateKeyModsPro(win, capital, numlock, + RGFW_isPressed(win, RGFW_controlL) || RGFW_isPressed(win, RGFW_controlR), + RGFW_isPressed(win, RGFW_altL) || RGFW_isPressed(win, RGFW_altR), + RGFW_isPressed(win, RGFW_shiftL) || RGFW_isPressed(win, RGFW_shiftR), + RGFW_isPressed(win, RGFW_superL) || RGFW_isPressed(win, RGFW_superR), scroll); } +RGFWDEF void RGFW_window_showMouseFlags(RGFW_window* win, RGFW_bool show); void RGFW_window_showMouseFlags(RGFW_window* win, RGFW_bool show) { - if (show && (win->internal.flags & RGFW_windowHideMouse)) - win->internal.flags ^= RGFW_windowHideMouse; - else if (!show && !(win->internal.flags & RGFW_windowHideMouse)) - win->internal.flags |= RGFW_windowHideMouse; + if (show && (win->_flags & RGFW_windowHideMouse)) + win->_flags ^= RGFW_windowHideMouse; + else if (!show && !(win->_flags & RGFW_windowHideMouse)) + win->_flags |= RGFW_windowHideMouse; } -RGFW_bool RGFW_window_isMouseHidden(RGFW_window* win) { - return (RGFW_bool)RGFW_BOOL(((RGFW_window*)win)->internal.flags & RGFW_windowHideMouse); +RGFW_bool RGFW_window_mouseHidden(RGFW_window* win) { + return (RGFW_bool)RGFW_BOOL(win->_flags & RGFW_windowHideMouse); } RGFW_bool RGFW_window_borderless(RGFW_window* win) { - return (RGFW_bool)RGFW_BOOL(win->internal.flags & RGFW_windowNoBorder); + return (RGFW_bool)RGFW_BOOL(win->_flags & RGFW_windowNoBorder); } -RGFW_bool RGFW_window_isFullscreen(RGFW_window* win){ return RGFW_BOOL(win->internal.flags & RGFW_windowFullscreen); } -RGFW_bool RGFW_window_allowsDND(RGFW_window* win) { return RGFW_BOOL(win->internal.flags & RGFW_windowAllowDND); } +RGFW_bool RGFW_window_isFullscreen(RGFW_window* win){ return RGFW_BOOL(win->_flags & RGFW_windowFullscreen); } +RGFW_bool RGFW_window_allowsDND(RGFW_window* win) { return RGFW_BOOL(win->_flags & RGFW_windowAllowDND); } void RGFW_window_focusLost(RGFW_window* win) { /* standard routines for when a window looses focus */ - win->internal.inFocus = RGFW_FALSE; - if ((win->internal.flags & RGFW_windowFullscreen)) + _RGFW.root->_flags &= ~(u32)RGFW_windowFocus; + if ((win->_flags & RGFW_windowFullscreen)) RGFW_window_minimize(win); - size_t key; - for (key = 0; key < RGFW_keyLast; key++) { - if (RGFW_isKeyDown((u8)key) == RGFW_FALSE) continue; - - _RGFW->keyboard[key].current = RGFW_FALSE; - u8 sym = RGFW_rgfwToKeyChar((u32)key); - - if ((win->internal.enabledEvents & RGFW_BIT(RGFW_keyReleased))) { - RGFW_keyCallback(win, (u8)key, sym, win->internal.mod, RGFW_FALSE, RGFW_FALSE); - RGFW_eventQueuePushEx(e.type = RGFW_keyReleased; - e.key.value = (u8)key; - e.key.sym = sym; - e.key.repeat = RGFW_FALSE; - e.key.mod = win->internal.mod; - e.common.win = win); - } + for (size_t key = 0; key < RGFW_keyLast; key++) { + if (RGFW_isPressed(NULL, (u8)key) == RGFW_FALSE) continue; + RGFW_keyboard[key].current = RGFW_FALSE; + u8 keyChar = RGFW_rgfwToKeyChar((u32)key); + RGFW_keyCallback(win, (u8)key, keyChar, win->event.keyMod, RGFW_FALSE); + RGFW_eventQueuePushEx(e.type = RGFW_keyReleased; + e.key = (u8)key; + e.keyChar = keyChar; + e.repeat = RGFW_FALSE; + e.keyMod = win->event.keyMod; + e._win = win); } - + RGFW_resetKey(); } #ifndef RGFW_WINDOWS void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow) { - RGFW_setBit(&win->internal.flags, RGFW_windowAllowDND, allow); + RGFW_setBit(&win->_flags, RGFW_windowAllowDND, allow); } #endif @@ -3927,8 +2486,8 @@ struct timespec; #if defined(RGFW_WAYLAND) || defined(RGFW_X11) || defined(RGFW_WINDOWS) void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show) { RGFW_window_showMouseFlags(win, show); - if (show == RGFW_FALSE) - RGFW_window_setMouse(win, _RGFW->hiddenMouse); + if (show == 0) + RGFW_window_setMouse(win, _RGFW.hiddenMouse); else RGFW_window_setMouseDefault(win); } @@ -3948,109 +2507,50 @@ void RGFW_moveToMacOSResourceDir(void) { } OpenGL defines start here (Normal, EGL, OSMesa) */ -#if defined(RGFW_OPENGL) -/* EGL, OpenGL */ -#define RGFW_DEFAULT_GL_HINTS { \ - /* Stencil */ 0, \ - /* Samples */ 0, \ - /* Stereo */ RGFW_FALSE, \ - /* AuxBuffers */ 0, \ - /* DoubleBuffer */ RGFW_TRUE, \ - /* Red */ 8, \ - /* Green */ 8, \ - /* Blue */ 8, \ - /* Alpha */ 8, \ - /* Depth */ 24, \ - /* AccumRed */ 0, \ - /* AccumGreen */ 0, \ - /* AccumBlue */ 0, \ - /* AccumAlpha */ 0, \ - /* SRGB */ RGFW_FALSE, \ - /* Robustness */ RGFW_FALSE, \ - /* Debug */ RGFW_FALSE, \ - /* NoError */ RGFW_FALSE, \ - /* ReleaseBehavior */ RGFW_glReleaseNone, \ - /* Profile */ RGFW_glCore, \ - /* Major */ 1, \ - /* Minor */ 0, \ - /* Share */ NULL, \ - /* Share_EGL */ NULL, \ - /* renderer */ RGFW_glAccelerated \ -} +#if defined(RGFW_OPENGL) || defined(RGFW_EGL) -RGFW_glHints RGFW_globalHints_OpenGL_SRC = RGFW_DEFAULT_GL_HINTS; -RGFW_glHints* RGFW_globalHints_OpenGL = &RGFW_globalHints_OpenGL_SRC; - -void RGFW_resetGlobalHints_OpenGL(void) { -#if !defined(__cplusplus) || defined(RGFW_MACOS) - RGFW_globalHints_OpenGL_SRC = (RGFW_glHints)RGFW_DEFAULT_GL_HINTS; -#else - RGFW_globalHints_OpenGL_SRC = RGFW_DEFAULT_GL_HINTS; -#endif -} -void RGFW_setGlobalHints_OpenGL(RGFW_glHints* hints) { RGFW_globalHints_OpenGL = hints; } -RGFW_glHints* RGFW_getGlobalHints_OpenGL(void) { RGFW_init(); return RGFW_globalHints_OpenGL; } - - -void* RGFW_glContext_getSourceContext(RGFW_glContext* ctx) { - RGFW_UNUSED(ctx); - -#ifdef RGFW_WAYLAND - if (RGFW_usingWayland()) return (void*)ctx->egl.ctx; +#ifdef RGFW_WINDOWS + #define WIN32_LEAN_AND_MEAN + #define OEMRESOURCE + #include #endif -#if defined(RGFW_X11) - return (void*)ctx->ctx; -#else - return NULL; -#endif -} - -RGFW_glContext* RGFW_window_createContext_OpenGL(RGFW_window* win, RGFW_glHints* hints) { - #ifdef RGFW_WAYLAND - if (RGFW_usingWayland()) { - return (RGFW_glContext*)RGFW_window_createContext_EGL(win, hints); - } +#if !defined(__APPLE__) && !defined(RGFW_NO_GL_HEADER) + #include +#elif defined(__APPLE__) + #ifndef GL_SILENCE_DEPRECATION + #define GL_SILENCE_DEPRECATION #endif - RGFW_glContext* ctx = (RGFW_glContext*)RGFW_ALLOC(sizeof(RGFW_glContext)); - if (RGFW_window_createContextPtr_OpenGL(win, ctx, hints) == RGFW_FALSE) { - RGFW_FREE(ctx); - win->src.ctx.native = NULL; - return NULL; - } - win->src.gfxType |= RGFW_gfxOwnedByRGFW; - return ctx; -} + #include + #include +#endif -RGFW_glContext* RGFW_window_getContext_OpenGL(RGFW_window* win) { - if (win->src.gfxType & RGFW_windowEGL) return NULL; - return win->src.ctx.native; -} +/* EGL, normal OpenGL only */ +#ifndef RGFW_EGL +i32 RGFW_GL_HINTS[RGFW_glFinalHint] = {8, +#else +i32 RGFW_GL_HINTS[RGFW_glFinalHint] = {0, +#endif + 0, 0, 0, 1, 8, 8, 8, 8, 24, 0, 0, 0, 0, 0, 0, 0, 0, RGFW_glReleaseNone, RGFW_glCore, 0, 0}; -void RGFW_window_deleteContext_OpenGL(RGFW_window* win, RGFW_glContext* ctx) { - RGFW_window_deleteContextPtr_OpenGL(win, ctx); - if (win->src.gfxType & RGFW_gfxOwnedByRGFW) RGFW_FREE(ctx); +void RGFW_setGLHint(RGFW_glHints hint, i32 value) { + if (hint < RGFW_glFinalHint && hint) RGFW_GL_HINTS[hint] = value; } RGFW_bool RGFW_extensionSupportedStr(const char* extensions, const char* ext, size_t len) { const char *start = extensions; - const char *where; + const char *where; const char* terminator; - if (extensions == NULL || ext == NULL) { + if (extensions == NULL || ext == NULL) return RGFW_FALSE; - } - while (ext[len - 1] == '\0' && len > 3) { - len--; - } - - where = RGFW_STRSTR(extensions, ext); + where = strstr(extensions, ext); while (where) { - terminator = where + len; + terminator = where + len; if ((where == start || *(where - 1) == ' ') && (*terminator == ' ' || *terminator == '\0')) { - return RGFW_TRUE; + return RGFW_TRUE; } where = RGFW_STRSTR(terminator, ext); } @@ -4058,523 +2558,457 @@ RGFW_bool RGFW_extensionSupportedStr(const char* extensions, const char* ext, si return RGFW_FALSE; } -RGFWDEF RGFW_bool RGFW_extensionSupported_base(const char* extension, size_t len); -RGFW_bool RGFW_extensionSupported_base(const char* extension, size_t len) { +RGFW_bool RGFW_extensionSupported(const char* extension, size_t len) { #ifdef GL_NUM_EXTENSIONS - if (RGFW_globalHints_OpenGL->major >= 3) { + if (RGFW_GL_HINTS[RGFW_glMajor] >= 3) { i32 i; - GLint count = 0; - RGFW_proc RGFW_glGetStringi = RGFW_getProcAddress_OpenGL("glGetStringi"); - RGFW_proc RGFW_glGetIntegerv = RGFW_getProcAddress_OpenGL("glGetIntegerv"); - if (RGFW_glGetIntegerv) + RGFW_proc RGFW_glGetStringi = RGFW_getProcAddress("glGetStringi"); + RGFW_proc RGFW_glGetIntegerv = RGFW_getProcAddress("RGFW_glGetIntegerv"); + if (RGFW_glGetIntegerv) ((void(*)(GLenum, GLint*))RGFW_glGetIntegerv)(GL_NUM_EXTENSIONS, &count); for (i = 0; RGFW_glGetStringi && i < count; i++) { const char* en = ((const char* (*)(u32, u32))RGFW_glGetStringi)(GL_EXTENSIONS, (u32)i); - if (en && RGFW_STRNCMP(en, extension, len) == 0) { - return RGFW_TRUE; - } + if (en && RGFW_STRNCMP(en, extension, len) == 0) + return RGFW_TRUE; } - } else + } else #endif { - RGFW_proc RGFW_glGetString = RGFW_getProcAddress_OpenGL("glGetString"); - #define RGFW_GL_EXTENSIONS 0x1F03 + RGFW_proc RGFW_glGetString = RGFW_getProcAddress("glGetString"); + if (RGFW_glGetString) { - const char* extensions = ((const char*(*)(u32))RGFW_glGetString)(RGFW_GL_EXTENSIONS); - - if ((extensions != NULL) && RGFW_extensionSupportedStr(extensions, extension, len)) { - return RGFW_TRUE; - } + const char* extensions = ((const char*(*)(u32))RGFW_glGetString)(GL_EXTENSIONS); + if ((extensions != NULL) && RGFW_extensionSupportedStr(extensions, extension, len)) + return RGFW_TRUE; } } - return RGFW_FALSE; + + return RGFW_extensionSupportedPlatform(extension, len); } -RGFW_bool RGFW_extensionSupported_OpenGL(const char* extension, size_t len) { - if (RGFW_extensionSupported_base(extension, len)) return RGFW_TRUE; - return RGFW_extensionSupportedPlatform_OpenGL(extension, len); +/* OPENGL normal only (no EGL / OSMesa) */ +#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) && !defined(RGFW_CUSTOM_BACKEND) && !defined(RGFW_WASM) + +#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, 0) + #define RGFW_GL_DRAW_TYPE RGFW_OS_BASED_VALUE(GLX_RENDER_TYPE, 0x2013, 0, 0) + #define RGFW_GL_FULL_FORMAT RGFW_OS_BASED_VALUE(GLX_TRUE_COLOR, 0x2027, 0, 0) + #define RGFW_GL_RED_SIZE RGFW_OS_BASED_VALUE(GLX_RED_SIZE, 0x2015, 0, 0) + #define RGFW_GL_GREEN_SIZE RGFW_OS_BASED_VALUE(GLX_GREEN_SIZE, 0x2017, 0, 0) + #define RGFW_GL_BLUE_SIZE RGFW_OS_BASED_VALUE(GLX_BLUE_SIZE, 0x2019, 0, 0) + #define RGFW_GL_USE_RGBA RGFW_OS_BASED_VALUE(GLX_RGBA_BIT, 0x202B, 0, 0) + #define RGFW_GL_ACCUM_RED_SIZE RGFW_OS_BASED_VALUE(14, 0x201E, 0, 0) + #define RGFW_GL_ACCUM_GREEN_SIZE RGFW_OS_BASED_VALUE(15, 0x201F, 0, 0) + #define RGFW_GL_ACCUM_BLUE_SIZE RGFW_OS_BASED_VALUE(16, 0x2020, 0, 0) + #define RGFW_GL_ACCUM_ALPHA_SIZE RGFW_OS_BASED_VALUE(17, 0x2021, 0, 0) + #define RGFW_GL_SRGB RGFW_OS_BASED_VALUE(0x20b2, 0x3089, 0, 0) + #define RGFW_GL_NOERROR RGFW_OS_BASED_VALUE(0x31b3, 0x31b3, 0, 0) + #define RGFW_GL_FLAGS RGFW_OS_BASED_VALUE(GLX_CONTEXT_FLAGS_ARB, 0x2094, 0, 0) + #define RGFW_GL_RELEASE_BEHAVIOR RGFW_OS_BASED_VALUE(GLX_CONTEXT_RELEASE_BEHAVIOR_ARB, 0x2097 , 0, 0) + #define RGFW_GL_CONTEXT_RELEASE RGFW_OS_BASED_VALUE(GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB, 0x2098, 0, 0) + #define RGFW_GL_CONTEXT_NONE RGFW_OS_BASED_VALUE(GLX_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB, 0x0000, 0, 0) + #define RGFW_GL_FLAGS RGFW_OS_BASED_VALUE(GLX_CONTEXT_FLAGS_ARB, 0x2094, 0, 0) + #define RGFW_GL_DEBUG_BIT RGFW_OS_BASED_VALUE(GLX_CONTEXT_FLAGS_ARB, 0x2094, 0, 0) + #define RGFW_GL_ROBUST_BIT RGFW_OS_BASED_VALUE(GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB, 0x00000004, 0, 0) +#endif + +#ifdef RGFW_WINDOWS + #define WGL_SUPPORT_OPENGL_ARB 0x2010 + #define WGL_COLOR_BITS_ARB 0x2014 + #define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 + #define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 + #define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 + #define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 + #define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 + #define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 + #define WGL_SAMPLE_BUFFERS_ARB 0x2041 + #define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9 + #define WGL_PIXEL_TYPE_ARB 0x2013 + #define WGL_TYPE_RGBA_ARB 0x202B + + #define WGL_TRANSPARENT_ARB 0x200A +#endif + +/* The window'ing api needs to know how to render the data we (or opengl) give it + MacOS and Windows do this using a structure called a "pixel format" + X11 calls it a "Visual" + This function returns the attributes for the format we want */ +i32* RGFW_initFormatAttribs(void); +i32* RGFW_initFormatAttribs(void) { + static i32 attribs[] = { + #if defined(RGFW_X11) || defined(RGFW_WINDOWS) + RGFW_GL_RENDER_TYPE, + RGFW_GL_FULL_FORMAT, + RGFW_GL_DRAW, 1, + RGFW_GL_DRAW_TYPE , RGFW_GL_USE_RGBA, + #endif + + #ifdef RGFW_X11 + GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT, + #endif + + #ifdef RGFW_MACOS + 72, + 8, 24, + #endif + + #ifdef RGFW_WINDOWS + WGL_SUPPORT_OPENGL_ARB, 1, + WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + WGL_COLOR_BITS_ARB, 32, + #endif + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + size_t index = (sizeof(attribs) / sizeof(attribs[0])) - 27; + + #define RGFW_GL_ADD_ATTRIB(attrib, attVal) \ + if (attVal) { \ + attribs[index] = attrib;\ + attribs[index + 1] = attVal;\ + index += 2;\ + } + + #if defined(RGFW_MACOS) && defined(RGFW_COCOA_GRAPHICS_SWITCHING) + RGFW_GL_ADD_ATTRIB(96, kCGLPFASupportsAutomaticGraphicsSwitching); + #endif + + RGFW_GL_ADD_ATTRIB(RGFW_GL_DOUBLEBUFFER, 1); + + RGFW_GL_ADD_ATTRIB(RGFW_GL_ALPHA_SIZE, RGFW_GL_HINTS[RGFW_glAlpha]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_DEPTH_SIZE, RGFW_GL_HINTS[RGFW_glDepth]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_STENCIL_SIZE, RGFW_GL_HINTS[RGFW_glStencil]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_STEREO, RGFW_GL_HINTS[RGFW_glStereo]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_AUX_BUFFERS, RGFW_GL_HINTS[RGFW_glAuxBuffers]); + + #if defined(RGFW_X11) || defined(RGFW_WINDOWS) + RGFW_GL_ADD_ATTRIB(RGFW_GL_RED_SIZE, RGFW_GL_HINTS[RGFW_glRed]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_GREEN_SIZE, RGFW_GL_HINTS[RGFW_glBlue]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_BLUE_SIZE, RGFW_GL_HINTS[RGFW_glGreen]); + #endif + + #if defined(RGFW_X11) || defined(RGFW_WINDOWS) + RGFW_GL_ADD_ATTRIB(RGFW_GL_ACCUM_RED_SIZE, RGFW_GL_HINTS[RGFW_glAccumRed]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_ACCUM_GREEN_SIZE, RGFW_GL_HINTS[RGFW_glAccumBlue]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_ACCUM_BLUE_SIZE, RGFW_GL_HINTS[RGFW_glAccumGreen]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_ACCUM_ALPHA_SIZE, RGFW_GL_HINTS[RGFW_glAccumAlpha]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_SRGB, RGFW_GL_HINTS[RGFW_glSRGB]); + RGFW_GL_ADD_ATTRIB(RGFW_GL_NOERROR, RGFW_GL_HINTS[RGFW_glNoError]); + + if (RGFW_GL_HINTS[RGFW_glReleaseBehavior] == RGFW_releaseFlush) { + RGFW_GL_ADD_ATTRIB(RGFW_GL_RELEASE_BEHAVIOR, RGFW_GL_CONTEXT_RELEASE); + } else if (RGFW_GL_HINTS[RGFW_glReleaseBehavior] == RGFW_glReleaseNone) { + RGFW_GL_ADD_ATTRIB(RGFW_GL_RELEASE_BEHAVIOR, RGFW_GL_CONTEXT_NONE); + } + + i32 flags = 0; + if (RGFW_GL_HINTS[RGFW_glDebug]) flags |= RGFW_GL_DEBUG_BIT; + if (RGFW_GL_HINTS[RGFW_glRobustness]) flags |= RGFW_GL_ROBUST_BIT; + RGFW_GL_ADD_ATTRIB(RGFW_GL_FLAGS, flags); + #else + i32 accumSize = (i32)(RGFW_GL_HINTS[RGFW_glAccumRed] + RGFW_GL_HINTS[RGFW_glAccumGreen] + RGFW_GL_HINTS[RGFW_glAccumBlue] + RGFW_GL_HINTS[RGFW_glAccumAlpha]) / 4; + RGFW_GL_ADD_ATTRIB(14, accumSize); + #endif + + #ifndef RGFW_X11 + RGFW_GL_ADD_ATTRIB(RGFW_GL_SAMPLES, RGFW_GL_HINTS[RGFW_glSamples]); + #endif + + #ifdef RGFW_MACOS + if (_RGFW.root->_flags & RGFW_windowOpenglSoftware) { + RGFW_GL_ADD_ATTRIB(70, kCGLRendererGenericFloatID); + } else { + attribs[index] = RGFW_GL_RENDER_TYPE; + index += 1; + } + #endif + + #ifdef RGFW_MACOS + /* macOS has the surface attribs and the opengl attribs connected for some reason + maybe this is to give macOS more control to limit openGL/the opengl version? */ + + attribs[index] = 99; + attribs[index + 1] = 0x1000; + + + if (RGFW_GL_HINTS[RGFW_glMajor] >= 4 || RGFW_GL_HINTS[RGFW_glMajor] >= 3) { + attribs[index + 1] = (i32) ((RGFW_GL_HINTS[RGFW_glMajor] >= 4) ? 0x4100 : 0x3200); + } + #endif + + RGFW_GL_ADD_ATTRIB(0, 0); + + return attribs; } -void RGFW_window_makeCurrentWindow_OpenGL(RGFW_window* win) { - if (win) { - _RGFW->current = win; - } +/* EGL only (no OSMesa nor normal OPENGL) */ +#elif defined(RGFW_EGL) - RGFW_window_makeCurrentContext_OpenGL(win); -} - -RGFW_window* RGFW_getCurrentWindow_OpenGL(void) { return _RGFW->current; } -void RGFW_attribStack_init(RGFW_attribStack* stack, i32* attribs, size_t max) { stack->attribs = attribs; stack->count = 0; stack->max = max; } -void RGFW_attribStack_pushAttrib(RGFW_attribStack* stack, i32 attrib) { - RGFW_ASSERT(stack->count < stack->max); - stack->attribs[stack->count] = attrib; - stack->count += 1; -} -void RGFW_attribStack_pushAttribs(RGFW_attribStack* stack, i32 attrib1, i32 attrib2) { - RGFW_attribStack_pushAttrib(stack, attrib1); - RGFW_attribStack_pushAttrib(stack, attrib2); -} - -/* EGL */ -#ifdef RGFW_EGL #include -PFNEGLINITIALIZEPROC RGFW_eglInitialize; -PFNEGLGETCONFIGSPROC RGFW_eglGetConfigs; -PFNEGLCHOOSECONFIGPROC RGFW_eglChooseConfig; -PFNEGLCREATEWINDOWSURFACEPROC RGFW_eglCreateWindowSurface; -PFNEGLCREATECONTEXTPROC RGFW_eglCreateContext; -PFNEGLMAKECURRENTPROC RGFW_eglMakeCurrent; -PFNEGLGETDISPLAYPROC RGFW_eglGetDisplay; -PFNEGLSWAPBUFFERSPROC RGFW_eglSwapBuffers; -PFNEGLSWAPINTERVALPROC RGFW_eglSwapInterval; -PFNEGLBINDAPIPROC RGFW_eglBindAPI; -PFNEGLDESTROYCONTEXTPROC RGFW_eglDestroyContext; -PFNEGLTERMINATEPROC RGFW_eglTerminate; -PFNEGLDESTROYSURFACEPROC RGFW_eglDestroySurface; -PFNEGLGETCURRENTCONTEXTPROC RGFW_eglGetCurrentContext; -PFNEGLGETPROCADDRESSPROC RGFW_eglGetProcAddress = NULL; -PFNEGLQUERYSTRINGPROC RGFW_eglQueryString; -PFNEGLGETCONFIGATTRIBPROC RGFW_eglGetConfigAttrib; +#if defined(RGFW_LINK_EGL) + typedef EGLBoolean(EGLAPIENTRY* PFN_eglInitialize)(EGLDisplay, EGLint*, EGLint*); + + PFNEGLINITIALIZEPROC eglInitializeSource; + PFNEGLGETCONFIGSPROC eglGetConfigsSource; + PFNEGLCHOOSECONFIgamepadROC eglChooseConfigSource; + PFNEGLCREATEWINDOWSURFACEPROC eglCreateWindowSurfaceSource; + PFNEGLCREATECONTEXTPROC eglCreateContextSource; + PFNEGLMAKECURRENTPROC eglMakeCurrentSource; + PFNEGLGETDISPLAYPROC eglGetDisplaySource; + PFNEGLSWAPBUFFERSPROC eglSwapBuffersSource; + PFNEGLSWAPINTERVALPROC eglSwapIntervalSource; + PFNEGLBINDAPIPROC eglBindAPISource; + PFNEGLDESTROYCONTEXTPROC eglDestroyContextSource; + PFNEGLTERMINATEPROC eglTerminateSource; + PFNEGLDESTROYSURFACEPROC eglDestroySurfaceSource; + + #define eglInitialize eglInitializeSource + #define eglGetConfigs eglGetConfigsSource + #define eglChooseConfig eglChooseConfigSource + #define eglCreateWindowSurface eglCreateWindowSurfaceSource + #define eglCreateContext eglCreateContextSource + #define eglMakeCurrent eglMakeCurrentSource + #define eglGetDisplay eglGetDisplaySource + #define eglSwapBuffers eglSwapBuffersSource + #define eglSwapInterval eglSwapIntervalSource + #define eglBindAPI eglBindAPISource + #define eglDestroyContext eglDestroyContextSource + #define eglTerminate eglTerminateSource + #define eglDestroySurface eglDestroySurfaceSource; +#endif + #define EGL_SURFACE_MAJOR_VERSION_KHR 0x3098 #define EGL_SURFACE_MINOR_VERSION_KHR 0x30fb -#ifdef RGFW_WINDOWS - #include -#elif defined(RGFW_MACOS) || defined(RGFW_UNIX) - #include +#ifndef RGFW_GL_ADD_ATTRIB +#define RGFW_GL_ADD_ATTRIB(attrib, attVal) \ + if (attVal) { \ + attribs[index] = attrib;\ + attribs[index + 1] = attVal;\ + index += 2;\ + } #endif + +void RGFW_window_initOpenGL(RGFW_window* win) { +#if defined(RGFW_LINK_EGL) + eglInitializeSource = (PFNEGLINITIALIZEPROC) eglGetProcAddress("eglInitialize"); + eglGetConfigsSource = (PFNEGLGETCONFIGSPROC) eglGetProcAddress("eglGetConfigs"); + eglChooseConfigSource = (PFNEGLCHOOSECONFIgamepadROC) eglGetProcAddress("eglChooseConfig"); + eglCreateWindowSurfaceSource = (PFNEGLCREATEWINDOWSURFACEPROC) eglGetProcAddress("eglCreateWindowSurface"); + eglCreateContextSource = (PFNEGLCREATECONTEXTPROC) eglGetProcAddress("eglCreateContext"); + eglMakeCurrentSource = (PFNEGLMAKECURRENTPROC) eglGetProcAddress("eglMakeCurrent"); + eglGetDisplaySource = (PFNEGLGETDISPLAYPROC) eglGetProcAddress("eglGetDisplay"); + eglSwapBuffersSource = (PFNEGLSWAPBUFFERSPROC) eglGetProcAddress("eglSwapBuffers"); + eglSwapIntervalSource = (PFNEGLSWAPINTERVALPROC) eglGetProcAddress("eglSwapInterval"); + eglBindAPISource = (PFNEGLBINDAPIPROC) eglGetProcAddress("eglBindAPI"); + eglDestroyContextSource = (PFNEGLDESTROYCONTEXTPROC) eglGetProcAddress("eglDestroyContext"); + eglTerminateSource = (PFNEGLTERMINATEPROC) eglGetProcAddress("eglTerminate"); + eglDestroySurfaceSource = (PFNEGLDESTROYSURFACEPROC) eglGetProcAddress("eglDestroySurface"); + + RGFW_ASSERT(eglInitializeSource != NULL && + eglGetConfigsSource != NULL && + eglChooseConfigSource != NULL && + eglCreateWindowSurfaceSource != NULL && + eglCreateContextSource != NULL && + eglMakeCurrentSource != NULL && + eglGetDisplaySource != NULL && + eglSwapBuffersSource != NULL && + eglSwapIntervalsSource != NULL && + eglBindAPISource != NULL && + eglDestroyContextSource != NULL && + eglTerminateSource != NULL && + eglDestroySurfaceSource != NULL); +#endif /* RGFW_LINK_EGL */ + #ifdef RGFW_WAYLAND -#include + if (RGFW_useWaylandBool) + win->src.eglWindow = wl_egl_window_create(win->src.surface, win->r.w, win->r.h); #endif -void* RGFW_eglLibHandle = NULL; - -void* RGFW_getDisplay_EGL(void) { return _RGFW->EGL_display; } -void* RGFW_eglContext_getSourceContext(RGFW_eglContext* ctx) { return ctx->ctx; } -void* RGFW_eglContext_getSurface(RGFW_eglContext* ctx) { return ctx->surface; } -struct wl_egl_window* RGFW_eglContext_wlEGLWindow(RGFW_eglContext* ctx) { return ctx->eglWindow; } - -RGFW_bool RGFW_loadEGL(void) { - RGFW_init(); - if (RGFW_eglGetProcAddress != NULL) { - return RGFW_TRUE; - } - -#ifndef RGFW_WASM #ifdef RGFW_WINDOWS - const char* libNames[] = { "libEGL.dll", "EGL.dll" }; - #elif defined(RGFW_MACOS) || defined(RGFW_UNIX) - /* Linux and macOS */ - const char* libNames[] = { - "libEGL.so.1", /* most common */ - "libEGL.so", /* fallback */ - "/System/Library/Frameworks/OpenGL.framework/OpenGL" /* fallback for older macOS EGL-like systems */ - }; + win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.hdc); + #elif defined(RGFW_MACOS) + win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType)0); + #elif defined(RGFW_WAYLAND) + if (RGFW_useWaylandBool) + win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.wl_display); + else + #endif + #ifdef RGFW_X11 + win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.display); + #else + {} + #endif + #if !defined(RGFW_WAYLAND) && !defined(RGFW_WINDOWS) && !defined(RGFW_X11) + win->src.EGL_display = eglGetDisplay((EGLNativeDisplayType) win->src.display); #endif - for (size_t i = 0; i < sizeof(libNames) / sizeof(libNames[0]); ++i) { - #ifdef RGFW_WINDOWS - RGFW_eglLibHandle = (void*)LoadLibraryA(libNames[i]); - if (RGFW_eglLibHandle) { - RGFW_eglGetProcAddress = (PFNEGLGETPROCADDRESSPROC)(RGFW_proc)GetProcAddress((HMODULE)RGFW_eglLibHandle, "eglGetProcAddress"); - break; - } - #elif defined(RGFW_MACOS) || defined(RGFW_UNIX) - RGFW_eglLibHandle = dlopen(libNames[i], RTLD_LAZY | RTLD_GLOBAL); - if (RGFW_eglLibHandle) { - void* lib = dlsym(RGFW_eglLibHandle, "eglGetProcAddress"); - if (lib != NULL) RGFW_MEMCPY(&RGFW_eglGetProcAddress, &lib, sizeof(PFNEGLGETPROCADDRESSPROC)); - break; - } - #endif - } + EGLint major, minor; - if (!RGFW_eglLibHandle || !RGFW_eglGetProcAddress) { - return RGFW_FALSE; - } - - RGFW_eglInitialize = (PFNEGLINITIALIZEPROC) RGFW_eglGetProcAddress("eglInitialize"); - RGFW_eglGetConfigs = (PFNEGLGETCONFIGSPROC) RGFW_eglGetProcAddress("eglGetConfigs"); - RGFW_eglChooseConfig = (PFNEGLCHOOSECONFIGPROC) RGFW_eglGetProcAddress("eglChooseConfig"); - RGFW_eglCreateWindowSurface = (PFNEGLCREATEWINDOWSURFACEPROC) RGFW_eglGetProcAddress("eglCreateWindowSurface"); - RGFW_eglCreateContext = (PFNEGLCREATECONTEXTPROC) RGFW_eglGetProcAddress("eglCreateContext"); - RGFW_eglMakeCurrent = (PFNEGLMAKECURRENTPROC) RGFW_eglGetProcAddress("eglMakeCurrent"); - RGFW_eglGetDisplay = (PFNEGLGETDISPLAYPROC) RGFW_eglGetProcAddress("eglGetDisplay"); - RGFW_eglSwapBuffers = (PFNEGLSWAPBUFFERSPROC) RGFW_eglGetProcAddress("eglSwapBuffers"); - RGFW_eglSwapInterval = (PFNEGLSWAPINTERVALPROC) RGFW_eglGetProcAddress("eglSwapInterval"); - RGFW_eglBindAPI = (PFNEGLBINDAPIPROC) RGFW_eglGetProcAddress("eglBindAPI"); - RGFW_eglDestroyContext = (PFNEGLDESTROYCONTEXTPROC) RGFW_eglGetProcAddress("eglDestroyContext"); - RGFW_eglTerminate = (PFNEGLTERMINATEPROC) RGFW_eglGetProcAddress("eglTerminate"); - RGFW_eglDestroySurface = (PFNEGLDESTROYSURFACEPROC) RGFW_eglGetProcAddress("eglDestroySurface"); - RGFW_eglQueryString = (PFNEGLQUERYSTRINGPROC) RGFW_eglGetProcAddress("eglQueryString"); - RGFW_eglGetCurrentContext = (PFNEGLGETCURRENTCONTEXTPROC) RGFW_eglGetProcAddress("eglGetCurrentContext"); - RGFW_eglGetConfigAttrib = (PFNEGLGETCONFIGATTRIBPROC) RGFW_eglGetProcAddress("eglGetConfigAttrib"); - -#else - RGFW_eglGetProcAddress = eglGetProcAddress; - RGFW_eglInitialize = (PFNEGLINITIALIZEPROC) eglInitialize; - RGFW_eglGetConfigs = (PFNEGLGETCONFIGSPROC) eglGetConfigs; - RGFW_eglChooseConfig = (PFNEGLCHOOSECONFIGPROC) eglChooseConfig; - RGFW_eglCreateWindowSurface = (PFNEGLCREATEWINDOWSURFACEPROC) eglCreateWindowSurface; - RGFW_eglCreateContext = (PFNEGLCREATECONTEXTPROC) eglCreateContext; - RGFW_eglMakeCurrent = (PFNEGLMAKECURRENTPROC) eglMakeCurrent; - RGFW_eglGetDisplay = (PFNEGLGETDISPLAYPROC) eglGetDisplay; - RGFW_eglSwapBuffers = (PFNEGLSWAPBUFFERSPROC) eglSwapBuffers; - RGFW_eglSwapInterval = (PFNEGLSWAPINTERVALPROC) eglSwapInterval; - RGFW_eglBindAPI = (PFNEGLBINDAPIPROC) eglBindAPI; - RGFW_eglDestroyContext = (PFNEGLDESTROYCONTEXTPROC) eglDestroyContext; - RGFW_eglTerminate = (PFNEGLTERMINATEPROC) eglTerminate; - RGFW_eglDestroySurface = (PFNEGLDESTROYSURFACEPROC) eglDestroySurface; - RGFW_eglQueryString = (PFNEGLQUERYSTRINGPROC) eglQueryString; - RGFW_eglGetCurrentContext = (PFNEGLGETCURRENTCONTEXTPROC) eglGetCurrentContext; - RGFW_eglGetConfigAttrib = (PFNEGLGETCONFIGATTRIBPROC)eglGetConfigAttrib; -#endif - - RGFW_bool out = RGFW_BOOL(RGFW_eglInitialize!= NULL && - RGFW_eglGetConfigs!= NULL && - RGFW_eglChooseConfig!= NULL && - RGFW_eglCreateWindowSurface!= NULL && - RGFW_eglCreateContext!= NULL && - RGFW_eglMakeCurrent!= NULL && - RGFW_eglGetDisplay!= NULL && - RGFW_eglSwapBuffers!= NULL && - RGFW_eglSwapInterval != NULL && - RGFW_eglBindAPI!= NULL && - RGFW_eglDestroyContext!= NULL && - RGFW_eglTerminate!= NULL && - RGFW_eglDestroySurface!= NULL && - RGFW_eglQueryString != NULL && - RGFW_eglGetCurrentContext != NULL && - RGFW_eglGetConfigAttrib != NULL); - - if (out) { - #ifdef RGFW_WINDOWS - HDC dc = GetDC(NULL); - _RGFW->EGL_display = RGFW_eglGetDisplay((EGLNativeDisplayType) dc); - ReleaseDC(NULL, dc); - #elif defined(RGFW_WAYLAND) - if (_RGFW->useWaylandBool) - _RGFW->EGL_display = RGFW_eglGetDisplay((EGLNativeDisplayType) _RGFW->wl_display); - else - #endif - #ifdef RGFW_X11 - _RGFW->EGL_display = RGFW_eglGetDisplay((EGLNativeDisplayType) _RGFW->display); - #else - {} - #endif - #if !defined(RGFW_WAYLAND) && !defined(RGFW_WINDOWS) && !defined(RGFW_X11) - _RGFW->EGL_display = RGFW_eglGetDisplay(EGL_DEFAULT_DISPLAY); - #endif - } - - RGFW_eglInitialize(_RGFW->EGL_display, NULL, NULL); - return out; -} - - -void RGFW_unloadEGL(void) { - if (!RGFW_eglLibHandle) return; - RGFW_eglTerminate(_RGFW->EGL_display); - #ifdef RGFW_WINDOWS - FreeLibrary((HMODULE)RGFW_eglLibHandle); - #elif defined(RGFW_MACOS) || defined(RGFW_UNIX) - dlclose(RGFW_eglLibHandle); - #endif - - RGFW_eglLibHandle = NULL; - RGFW_eglGetProcAddress = NULL; -} - -RGFW_bool RGFW_window_createContextPtr_EGL(RGFW_window* win, RGFW_eglContext* ctx, RGFW_glHints* hints) { - if (RGFW_loadEGL() == RGFW_FALSE) return RGFW_FALSE; - win->src.ctx.egl = ctx; - win->src.gfxType = RGFW_gfxEGL; - -#ifdef RGFW_WAYLAND - if (_RGFW->useWaylandBool) - win->src.ctx.egl->eglWindow = wl_egl_window_create(win->src.surface, win->w, win->h); -#endif + eglInitialize(win->src.EGL_display, &major, &minor); #ifndef EGL_OPENGL_ES1_BIT #define EGL_OPENGL_ES1_BIT 0x1 #endif - EGLint egl_config[24]; + EGLint egl_config[24] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, + #ifdef RGFW_OPENGL_ES1 + EGL_OPENGL_ES1_BIT, + #elif defined(RGFW_OPENGL_ES3) + EGL_OPENGL_ES3_BIT, + #elif defined(RGFW_OPENGL_ES2) + EGL_OPENGL_ES2_BIT, + #else + EGL_OPENGL_BIT, + #endif + EGL_NONE, EGL_NONE + }; { - RGFW_attribStack stack; - RGFW_attribStack_init(&stack, egl_config, 24); + size_t index = 7; + EGLint* attribs = egl_config; - RGFW_attribStack_pushAttribs(&stack, EGL_SURFACE_TYPE, EGL_WINDOW_BIT); - RGFW_attribStack_pushAttrib(&stack, EGL_RENDERABLE_TYPE); + RGFW_GL_ADD_ATTRIB(EGL_RED_SIZE, RGFW_GL_HINTS[RGFW_glRed]); + RGFW_GL_ADD_ATTRIB(EGL_GREEN_SIZE, RGFW_GL_HINTS[RGFW_glBlue]); + RGFW_GL_ADD_ATTRIB(EGL_BLUE_SIZE, RGFW_GL_HINTS[RGFW_glGreen]); + RGFW_GL_ADD_ATTRIB(EGL_ALPHA_SIZE, RGFW_GL_HINTS[RGFW_glAlpha]); + RGFW_GL_ADD_ATTRIB(EGL_DEPTH_SIZE, RGFW_GL_HINTS[RGFW_glDepth]); - if (hints->profile == RGFW_glES) { - switch (hints->major) { - case 1: RGFW_attribStack_pushAttrib(&stack, EGL_OPENGL_ES1_BIT); break; - case 2: RGFW_attribStack_pushAttrib(&stack, EGL_OPENGL_ES2_BIT); break; - case 3: RGFW_attribStack_pushAttrib(&stack, EGL_OPENGL_ES3_BIT); break; - default: break; - } - } else { - RGFW_attribStack_pushAttrib(&stack, EGL_OPENGL_BIT); - } + if (RGFW_GL_HINTS[RGFW_glSRGB]) + RGFW_GL_ADD_ATTRIB(0x3089, RGFW_GL_HINTS[RGFW_glSRGB]); - RGFW_attribStack_pushAttribs(&stack, EGL_RED_SIZE, hints->red); - RGFW_attribStack_pushAttribs(&stack, EGL_GREEN_SIZE, hints->green); - RGFW_attribStack_pushAttribs(&stack, EGL_BLUE_SIZE, hints->blue); - RGFW_attribStack_pushAttribs(&stack, EGL_ALPHA_SIZE, hints->alpha); - RGFW_attribStack_pushAttribs(&stack, EGL_DEPTH_SIZE, hints->depth); - - RGFW_attribStack_pushAttribs(&stack, EGL_STENCIL_SIZE, hints->stencil); - if (hints->samples) { - RGFW_attribStack_pushAttribs(&stack, EGL_SAMPLE_BUFFERS, 1); - RGFW_attribStack_pushAttribs(&stack, EGL_SAMPLES, hints->samples); - } - - RGFW_attribStack_pushAttribs(&stack, EGL_NONE, EGL_NONE); + RGFW_GL_ADD_ATTRIB(EGL_NONE, EGL_NONE); } - EGLint numConfigs, best_config = -1, best_samples = 0; + EGLConfig config; + EGLint numConfigs; + eglChooseConfig(win->src.EGL_display, egl_config, &config, 1, &numConfigs); - RGFW_eglChooseConfig(_RGFW->EGL_display, egl_config, NULL, 0, &numConfigs); - EGLConfig* configs = (EGLConfig*)RGFW_ALLOC(sizeof(EGLConfig) * (u32)numConfigs); - - RGFW_eglChooseConfig(_RGFW->EGL_display, egl_config, configs, numConfigs, &numConfigs); - -#ifdef RGFW_X11 - RGFW_bool transparent = (win->internal.flags & RGFW_windowTransparent); - EGLint best_depth = 0; -#endif - - for (EGLint i = 0; i < numConfigs; i++) { - EGLint visual_id = 0; - EGLint samples = 0; - - RGFW_eglGetConfigAttrib(_RGFW->EGL_display, configs[i], EGL_NATIVE_VISUAL_ID, &visual_id); - RGFW_eglGetConfigAttrib(_RGFW->EGL_display, configs[i], EGL_SAMPLES, &samples); - - if (best_config == -1) best_config = i; - -#ifdef RGFW_X11 - if (_RGFW->useWaylandBool == RGFW_FALSE) { - XVisualInfo vinfo_template; - vinfo_template.visualid = (VisualID)visual_id; - - int num_visuals = 0; - XVisualInfo* vi = XGetVisualInfo(_RGFW->display, VisualIDMask, &vinfo_template, &num_visuals); - if (!vi) continue; - if ((!transparent || vi->depth == 32) && best_depth == 0) { - best_config = i; - best_depth = vi->depth; - } - - if ((!(transparent) || vi->depth == 32) && (samples <= hints->samples && samples > best_samples)) { - best_depth = vi->depth; - best_config = i; - best_samples = samples; - XFree(vi); - continue; - } - } -#endif - - if (samples <= hints->samples && samples > best_samples) { - best_config = i; - best_samples = samples; - } - } - - EGLConfig config = configs[best_config]; - RGFW_FREE(configs); -#ifdef RGFW_X11 - if (_RGFW->useWaylandBool == RGFW_FALSE) { - /* This is required so that way the user can create their own OpenGL context after RGFW_createWindow is used */ - XVisualInfo* result; - XVisualInfo desired; - EGLint visualID = 0, count = 0; - - RGFW_eglGetConfigAttrib(_RGFW->EGL_display, config, EGL_NATIVE_VISUAL_ID, &visualID); - if (visualID) { - desired.visualid = (VisualID)visualID; - result = XGetVisualInfo(_RGFW->display, VisualIDMask, &desired, &count); - } else RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEGLContext, "Failed to fetch a valid EGL VisualID"); - - if (result == NULL || count == 0) { - if (win->src.window == 0) { - /* try to create a EGL context anyway (this will work if you're not using a NVidia driver) */ - win->internal.flags &= ~(u32)RGFW_windowEGL; - RGFW_createWindowPlatform("", win->internal.flags, win); - } - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEGLContext, "Failed to find a valid visual for the EGL config"); - } else { - if (win->src.window) RGFW_window_closePlatform(win); - RGFW_XCreateWindow(*result, "", win->internal.flags, win); - XFree(result); - } - } -#endif - - EGLint surf_attribs[9]; - - { - RGFW_attribStack stack; - RGFW_attribStack_init(&stack, surf_attribs, 9); - - const char present_opaque_str[] = "EGL_EXT_present_opaque"; - RGFW_bool opaque_extension_Found = RGFW_extensionSupportedPlatform_EGL(present_opaque_str, sizeof(present_opaque_str)); - - #ifndef EGL_PRESENT_OPAQUE_EXT - #define EGL_PRESENT_OPAQUE_EXT 0x31df - #endif - - #ifndef EGL_GL_COLORSPACE_KHR - #define EGL_GL_COLORSPACE_KHR 0x309D - #ifndef EGL_GL_COLORSPACE_SRGB_KHR - #define EGL_GL_COLORSPACE_SRGB_KHR 0x3089 - #endif - #endif - - const char gl_colorspace_str[] = "EGL_KHR_gl_colorspace"; - RGFW_bool gl_colorspace_Found = RGFW_extensionSupportedPlatform_EGL(gl_colorspace_str, sizeof(gl_colorspace_str)); - - if (hints->sRGB && gl_colorspace_Found) { - RGFW_attribStack_pushAttribs(&stack, EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR); - } - - if (!(win->internal.flags & RGFW_windowTransparent) && opaque_extension_Found) - RGFW_attribStack_pushAttribs(&stack, EGL_PRESENT_OPAQUE_EXT, EGL_TRUE); - - if (hints->doubleBuffer == 0) { - RGFW_attribStack_pushAttribs(&stack, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER); - } - - RGFW_attribStack_pushAttribs(&stack, EGL_NONE, EGL_NONE); - } #if defined(RGFW_MACOS) - void* layer = RGFW_getLayer_OSX(); + void* layer = RGFW_cocoaGetLayer(); - RGFW_window_setLayer_OSX(win, layer); + RGFW_window_cocoaSetLayer(win, layer); - win->src.ctx.egl->surface = RGFW_eglCreateWindowSurface(_RGFW->EGL_display, config, (EGLNativeWindowType) layer, surf_attribs); + win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) layer, NULL); #elif defined(RGFW_WINDOWS) - win->src.ctx.egl->surface = RGFW_eglCreateWindowSurface(_RGFW->EGL_display, config, (EGLNativeWindowType) win->src.window, surf_attribs); + win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.window, NULL); #elif defined(RGFW_WAYLAND) - if (_RGFW->useWaylandBool) - win->src.ctx.egl->surface = RGFW_eglCreateWindowSurface(_RGFW->EGL_display, config, (EGLNativeWindowType) win->src.ctx.egl->eglWindow, surf_attribs); + if (RGFW_useWaylandBool) + win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.eglWindow, NULL); else #endif #ifdef RGFW_X11 - win->src.ctx.egl->surface = RGFW_eglCreateWindowSurface(_RGFW->EGL_display, config, (EGLNativeWindowType) win->src.window, surf_attribs); + win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.window, NULL); #else {} #endif - #ifdef RGFW_WASM - win->src.ctx.egl->surface = eglCreateWindowSurface(_RGFW->EGL_display, config, 0, 0); + #if !defined(RGFW_X11) && !defined(RGFW_WAYLAND) && !defined(RGFW_MACOS) + win->src.EGL_surface = eglCreateWindowSurface(win->src.EGL_display, config, (EGLNativeWindowType) win->src.window, NULL); #endif - if (win->src.ctx.egl->surface == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEGLContext, "Failed to create an EGL surface."); - return RGFW_FALSE; + EGLint attribs[12]; + size_t index = 0; + +#ifdef RGFW_OPENGL_ES1 + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_CLIENT_VERSION, 1); +#elif defined(RGFW_OPENGL_ES2) + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_CLIENT_VERSION, 2); +#elif defined(RGFW_OPENGL_ES3) + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_CLIENT_VERSION, 3); +#endif + + RGFW_GL_ADD_ATTRIB(EGL_STENCIL_SIZE, RGFW_GL_HINTS[RGFW_glStencil]); + RGFW_GL_ADD_ATTRIB(EGL_SAMPLES, RGFW_GL_HINTS[RGFW_glSamples]); + + if (RGFW_GL_HINTS[RGFW_glDoubleBuffer] == 0) + RGFW_GL_ADD_ATTRIB(EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER); + + if (RGFW_GL_HINTS[RGFW_glMajor]) { + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_MAJOR_VERSION, RGFW_GL_HINTS[RGFW_glMajor]); + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_MINOR_VERSION, RGFW_GL_HINTS[RGFW_glMinor]); + + if (RGFW_GL_HINTS[RGFW_glProfile] == RGFW_glCore) { + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT); + } + else { + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT); + } } - EGLint attribs[20]; - { - RGFW_attribStack stack; - RGFW_attribStack_init(&stack, attribs, 20); - - if (hints->major || hints->minor) { - RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_MAJOR_VERSION, hints->major); - RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_MINOR_VERSION, hints->minor); - } - - if (hints->profile == RGFW_glCore) { - RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT); - } else if (hints->profile == RGFW_glCompatibility) { - RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT); - } - - RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_OPENGL_ROBUST_ACCESS, hints->robustness); - RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_OPENGL_DEBUG, hints->debug); - - #ifndef EGL_CONTEXT_RELEASE_BEHAVIOR_KHR - #define EGL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x2097 - #endif - - #ifndef EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR - #define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098 - #endif - - if (hints->releaseBehavior == RGFW_glReleaseFlush) { - RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_RELEASE_BEHAVIOR_KHR, EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR); - } else { - RGFW_attribStack_pushAttribs(&stack, EGL_CONTEXT_RELEASE_BEHAVIOR_KHR, 0x0000); - } - - RGFW_attribStack_pushAttribs(&stack, EGL_NONE, EGL_NONE); + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_ROBUST_ACCESS, RGFW_GL_HINTS[RGFW_glRobustness]); + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_DEBUG, RGFW_GL_HINTS[RGFW_glDebug]); + if (RGFW_GL_HINTS[RGFW_glReleaseBehavior] == RGFW_releaseFlush) { + RGFW_GL_ADD_ATTRIB(0x2097, 0x2098); + } else { + RGFW_GL_ADD_ATTRIB(0x2096, 0x0000); } - if (hints->profile == RGFW_glES) - RGFW_eglBindAPI(EGL_OPENGL_ES_API); - else - RGFW_eglBindAPI(EGL_OPENGL_API); + RGFW_GL_ADD_ATTRIB(EGL_NONE, EGL_NONE); - win->src.ctx.egl->ctx = RGFW_eglCreateContext(_RGFW->EGL_display, config, hints->shareEGL, attribs); - - if (win->src.ctx.egl->ctx == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEGLContext, "Failed to create an EGL context."); - return RGFW_FALSE; - } - - RGFW_eglMakeCurrent(_RGFW->EGL_display, win->src.ctx.egl->surface, win->src.ctx.egl->surface, win->src.ctx.egl->ctx); - RGFW_eglSwapBuffers(_RGFW->EGL_display, win->src.ctx.egl->surface); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "EGL context initalized."); - return RGFW_TRUE; -} - -RGFW_eglContext* RGFW_window_getContext_EGL(RGFW_window* win) { - if (win->src.gfxType == RGFW_windowOpenGL) return NULL; - return win->src.ctx.egl; -} - -void RGFW_window_deleteContextPtr_EGL(RGFW_window* win, RGFW_eglContext* ctx) { - if (_RGFW->EGL_display == NULL) return; - - RGFW_eglDestroySurface(_RGFW->EGL_display, ctx->surface); - RGFW_eglDestroyContext(_RGFW->EGL_display, ctx->ctx); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "EGL context freed"); - #ifdef RGFW_WAYLAND - if (_RGFW->useWaylandBool == RGFW_FALSE) return; - wl_egl_window_destroy(win->src.ctx.egl->eglWindow); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "EGL window context freed"); + #if defined(RGFW_OPENGL_ES1) || defined(RGFW_OPENGL_ES2) || defined(RGFW_OPENGL_ES3) + eglBindAPI(EGL_OPENGL_ES_API); + #else + eglBindAPI(EGL_OPENGL_API); #endif - win->src.ctx.egl = NULL; + + win->src.EGL_context = eglCreateContext(win->src.EGL_display, config, EGL_NO_CONTEXT, attribs); + + if (win->src.EGL_context == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errEGLContext, RGFW_DEBUG_CTX(win, 0), "failed to create an EGL opengl context"); + return; + } + + eglMakeCurrent(win->src.EGL_display, win->src.EGL_surface, win->src.EGL_surface, win->src.EGL_context); + eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "EGL opengl context initalized"); } -void RGFW_window_makeCurrentContext_EGL(RGFW_window* win) { if (win) RGFW_ASSERT(win->src.ctx.egl); - if (win == NULL) - RGFW_eglMakeCurrent(_RGFW->EGL_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +void RGFW_window_freeOpenGL(RGFW_window* win) { + if (win->src.EGL_display == NULL) return; + + eglDestroySurface(win->src.EGL_display, win->src.EGL_surface); + eglDestroyContext(win->src.EGL_display, win->src.EGL_context); + eglTerminate(win->src.EGL_display); + win->src.EGL_display = NULL; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "EGL opengl context freed"); +} + +void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { + if (win == NULL) + eglMakeCurrent(_RGFW.root->src.EGL_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); else { - RGFW_eglMakeCurrent(_RGFW->EGL_display, win->src.ctx.egl->surface, win->src.ctx.egl->surface, win->src.ctx.egl->ctx); + eglMakeCurrent(win->src.EGL_display, win->src.EGL_surface, win->src.EGL_surface, win->src.EGL_context); } } -void RGFW_window_swapBuffers_EGL(RGFW_window* win) { - if (RGFW_eglSwapBuffers) - RGFW_eglSwapBuffers(_RGFW->EGL_display, win->src.ctx.egl->surface); - else RGFW_window_swapBuffers_OpenGL(win); -} +void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); } -void* RGFW_getCurrentContext_EGL(void) { - return RGFW_eglGetCurrentContext(); -} +void* RGFW_getCurrent_OpenGL(void) { return eglGetCurrentContext(); } -RGFW_proc RGFW_getProcAddress_EGL(const char* procname) { +#ifdef RGFW_APPLE +void* RGFWnsglFramework = NULL; +#elif defined(RGFW_WINDOWS) +HMODULE RGFW_wgl_dll = NULL; +#endif + +RGFW_proc RGFW_getProcAddress(const char* procname) { #if defined(RGFW_WINDOWS) RGFW_proc proc = (RGFW_proc) GetProcAddress(RGFW_wgl_dll, procname); @@ -4582,46 +3016,19 @@ RGFW_proc RGFW_getProcAddress_EGL(const char* procname) { return proc; #endif - return (RGFW_proc) RGFW_eglGetProcAddress(procname); + return (RGFW_proc) eglGetProcAddress(procname); } -RGFW_bool RGFW_extensionSupportedPlatform_EGL(const char* extension, size_t len) { - if (RGFW_loadEGL() == RGFW_FALSE) return RGFW_FALSE; - const char* extensions = RGFW_eglQueryString(_RGFW->EGL_display, EGL_EXTENSIONS); - return extensions != NULL && RGFW_extensionSupportedStr(extensions, extension, len); +RGFW_bool RGFW_extensionSupportedPlatform(const char* extension, size_t len) { + const char* extensions = eglQueryString(_RGFW.root->src.EGL_display, EGL_EXTENSIONS); + return extensions != NULL && RGFW_extensionSupportedStr(extensions, extension, len); } -void RGFW_window_swapInterval_EGL(RGFW_window* win, i32 swapInterval) { +void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { RGFW_ASSERT(win != NULL); - RGFW_eglSwapInterval(_RGFW->EGL_display, swapInterval); -} -RGFW_bool RGFW_extensionSupported_EGL(const char* extension, size_t len) { - if (RGFW_extensionSupported_base(extension, len)) return RGFW_TRUE; - return RGFW_extensionSupportedPlatform_EGL(extension, len); -} + eglSwapInterval(win->src.EGL_display, swapInterval); -void RGFW_window_makeCurrentWindow_EGL(RGFW_window* win) { - _RGFW->current = win; - RGFW_window_makeCurrentContext_EGL(win); -} - -RGFW_window* RGFW_getCurrentWindow_EGL(void) { return _RGFW->current; } - -RGFW_eglContext* RGFW_window_createContext_EGL(RGFW_window* win, RGFW_glHints* hints) { - RGFW_eglContext* ctx = (RGFW_eglContext*)RGFW_ALLOC(sizeof(RGFW_eglContext)); - if (RGFW_window_createContextPtr_EGL(win, ctx, hints) == RGFW_FALSE) { - RGFW_FREE(ctx); - win->src.ctx.egl = NULL; - return NULL; - } - win->src.gfxType |= RGFW_gfxOwnedByRGFW; - return ctx; -} - -void RGFW_window_deleteContext_EGL(RGFW_window* win, RGFW_eglContext* ctx) { - RGFW_window_deleteContextPtr_EGL(win, ctx); - if (win->src.gfxType & RGFW_gfxOwnedByRGFW) RGFW_FREE(ctx); } #endif /* RGFW_EGL */ @@ -4639,7 +3046,7 @@ void RGFW_window_deleteContext_EGL(RGFW_window* win, RGFW_eglContext* ctx) { #include #endif -const char** RGFW_getRequiredInstanceExtensions_Vulkan(size_t* count) { +const char** RGFW_getVKRequiredInstanceExtensions(size_t* count) { static const char* arr[2] = {VK_KHR_SURFACE_EXTENSION_NAME}; arr[1] = RGFW_VK_SURFACE; if (count != NULL) *count = 2; @@ -4647,20 +3054,20 @@ const char** RGFW_getRequiredInstanceExtensions_Vulkan(size_t* count) { return (const char**)arr; } -VkResult RGFW_window_createSurface_Vulkan(RGFW_window* win, VkInstance instance, VkSurfaceKHR* surface) { +VkResult RGFW_window_createVKSurface(RGFW_window* win, VkInstance instance, VkSurfaceKHR* surface) { RGFW_ASSERT(win != NULL); RGFW_ASSERT(instance); RGFW_ASSERT(surface != NULL); *surface = VK_NULL_HANDLE; #ifdef RGFW_X11 - - VkXlibSurfaceCreateInfoKHR x11 = { VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, 0, 0, (Display*) _RGFW->display, (Window) win->src.window }; + RGFW_GOTO_WAYLAND(0); + VkXlibSurfaceCreateInfoKHR x11 = { VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, 0, 0, (Display*) win->src.display, (Window) win->src.window }; return vkCreateXlibSurfaceKHR(instance, &x11, NULL, surface); #endif #if defined(RGFW_WAYLAND) - - VkWaylandSurfaceCreateInfoKHR wayland = { VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, 0, 0, (struct wl_display*) _RGFW->wl_display, (struct wl_surface*) win->src.surface }; +RGFW_WAYLAND_LABEL + VkWaylandSurfaceCreateInfoKHR wayland = { VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, 0, 0, (struct wl_display*) win->src.wl_display, (struct wl_surface*) win->src.surface }; return vkCreateWaylandSurfaceKHR(instance, &wayland, NULL, surface); #elif defined(RGFW_WINDOWS) VkWin32SurfaceCreateInfoKHR win32 = { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, 0, 0, GetModuleHandle(NULL), (HWND)win->src.window }; @@ -4668,24 +3075,28 @@ VkResult RGFW_window_createSurface_Vulkan(RGFW_window* win, VkInstance instance, return vkCreateWin32SurfaceKHR(instance, &win32, NULL, surface); #elif defined(RGFW_MACOS) && !defined(RGFW_MACOS_X11) void* contentView = ((void* (*)(id, SEL))objc_msgSend)((id)win->src.window, sel_getUid("contentView")); - VkMacOSSurfaceCreateSurfaceMVK macos = { VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK, 0, 0, 0, (void*)contentView }; + VkMacOSSurfaceCreateFlagsMVK macos = { VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK, 0, 0, win->src.display, (void*)contentView }; + return vkCreateMacOSSurfaceMVK(instance, &macos, NULL, surface); #endif } -RGFW_bool RGFW_getPresentationSupport_Vulkan(VkInstance instance, VkPhysicalDevice physicalDevice, u32 queueFamilyIndex) { +RGFW_bool RGFW_getVKPresentationSupport(VkInstance instance, VkPhysicalDevice physicalDevice, u32 queueFamilyIndex) { RGFW_ASSERT(instance); - if (_RGFW == NULL) RGFW_init(); + if (_RGFW.windowCount == -1 || _RGFW_init == RGFW_FALSE) RGFW_init(); #ifdef RGFW_X11 + RGFW_GOTO_WAYLAND(0); + Visual* visual = DefaultVisual(_RGFW.display, DefaultScreen(_RGFW.display)); + if (_RGFW.root) + visual = _RGFW.root->src.visual.visual; - Visual* visual = DefaultVisual(_RGFW->display, DefaultScreen(_RGFW->display)); - RGFW_bool out = vkGetPhysicalDeviceXlibPresentationSupportKHR(physicalDevice, queueFamilyIndex, _RGFW->display, XVisualIDFromVisual(visual)); + RGFW_bool out = vkGetPhysicalDeviceXlibPresentationSupportKHR(physicalDevice, queueFamilyIndex, _RGFW.display, XVisualIDFromVisual(visual)); return out; #endif #if defined(RGFW_WAYLAND) - - RGFW_bool wlout = vkGetPhysicalDeviceWaylandPresentationSupportKHR(physicalDevice, queueFamilyIndex, _RGFW->wl_display); +RGFW_WAYLAND_LABEL + RGFW_bool wlout = vkGetPhysicalDeviceWaylandPresentationSupportKHR(physicalDevice, queueFamilyIndex, _RGFW.wl_display); return wlout; #elif defined(RGFW_WINDOWS) #elif defined(RGFW_MACOS) && !defined(RGFW_MACOS_X11) @@ -4698,138 +3109,1341 @@ RGFW_bool RGFW_getPresentationSupport_Vulkan(VkInstance instance, VkPhysicalDevi This is where OS specific stuff starts */ -/* start of unix (wayland or X11 (unix) ) defines */ + +#if (defined(RGFW_WAYLAND) || defined(RGFW_X11)) && !defined(RGFW_NO_LINUX) + int RGFW_eventWait_forceStop[] = {0, 0, 0}; /* for wait events */ + + #if defined(__linux__) + #include + #include + #include + #include + + u32 RGFW_linux_updateGamepad(RGFW_window* win); + u32 RGFW_linux_updateGamepad(RGFW_window* win) { + /* check for new gamepads */ + static const char* str[] = {"/dev/input/js0", "/dev/input/js1", "/dev/input/js2", "/dev/input/js3", "/dev/input/js4", "/dev/input/js5"}; + static u8 RGFW_rawGamepads[6]; + { + u16 i; + for (i = 0; i < 6; i++) { + u16 index = RGFW_gamepadCount; + if (RGFW_rawGamepads[i]) { + struct input_id device_info; + if (ioctl(RGFW_rawGamepads[i], EVIOCGID, &device_info) == -2) { + if (errno == ENODEV) { + RGFW_rawGamepads[i] = 0; + } + } + continue; + } + + i32 js = open(str[i], O_RDONLY); + + if (js <= 0) + break; + + if (RGFW_gamepadCount >= 4) { + close(js); + break; + } + + RGFW_rawGamepads[i] = 1; + + int axes, buttons; + if (ioctl(js, JSIOCGAXES, &axes) < 0 || ioctl(js, JSIOCGBUTTONS, &buttons) < 0) { + close(js); + continue; + } + + if (buttons <= 5 || buttons >= 30) { + close(js); + continue; + } + + RGFW_gamepadCount++; + + RGFW_gamepads[index] = js; + + ioctl(js, JSIOCGNAME(sizeof(RGFW_gamepads_name[index])), RGFW_gamepads_name[index]); + RGFW_gamepads_name[index][sizeof(RGFW_gamepads_name[index]) - 1] = 0; + + u8 j; + for (j = 0; j < 16; j++) { + RGFW_gamepadPressed[index][j].prev = 0; + RGFW_gamepadPressed[index][j].current = 0; + } + + win->event.type = RGFW_gamepadConnected; + + RGFW_gamepads_type[index] = RGFW_gamepadUnknown; + if (RGFW_STRSTR(RGFW_gamepads_name[index], "Microsoft") || RGFW_STRSTR(RGFW_gamepads_name[index], "X-Box")) + RGFW_gamepads_type[index] = RGFW_gamepadMicrosoft; + else if (RGFW_STRSTR(RGFW_gamepads_name[index], "PlayStation") || RGFW_STRSTR(RGFW_gamepads_name[index], "PS3") || RGFW_STRSTR(RGFW_gamepads_name[index], "PS4") || RGFW_STRSTR(RGFW_gamepads_name[index], "PS5")) + RGFW_gamepads_type[index] = RGFW_gamepadSony; + else if (RGFW_STRSTR(RGFW_gamepads_name[index], "Nintendo")) + RGFW_gamepads_type[index] = RGFW_gamepadNintendo; + else if (RGFW_STRSTR(RGFW_gamepads_name[index], "Logitech")) + RGFW_gamepads_type[index] = RGFW_gamepadLogitech; + + win->event.gamepad = index; + RGFW_gamepadCallback(win, index, 1); + return 1; + } + } + /* check gamepad events */ + u8 i; + + for (i = 0; i < RGFW_gamepadCount; i++) { + struct js_event e; + if (RGFW_gamepads[i] == 0) + continue; + + i32 flags = fcntl(RGFW_gamepads[i], F_GETFL, 0); + fcntl(RGFW_gamepads[i], F_SETFL, flags | O_NONBLOCK); + + ssize_t bytes; + while ((bytes = read(RGFW_gamepads[i], &e, sizeof(e))) > 0) { + switch (e.type) { + case JS_EVENT_BUTTON: { + size_t typeIndex = 0; + if (RGFW_gamepads_type[i] == RGFW_gamepadMicrosoft) typeIndex = 1; + else if (RGFW_gamepads_type[i] == RGFW_gamepadLogitech) typeIndex = 2; + + win->event.type = e.value ? RGFW_gamepadButtonPressed : RGFW_gamepadButtonReleased; + u8 RGFW_linux2RGFW[3][RGFW_gamepadR3 + 8] = {{ /* ps */ + RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadY, RGFW_gamepadX, RGFW_gamepadL1, RGFW_gamepadR1, RGFW_gamepadL2, RGFW_gamepadR2, + RGFW_gamepadSelect, RGFW_gamepadStart, RGFW_gamepadHome, RGFW_gamepadL3, RGFW_gamepadR3, RGFW_gamepadUp, RGFW_gamepadDown, RGFW_gamepadLeft, RGFW_gamepadRight, + },{ /* xbox */ + RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadX, RGFW_gamepadY, RGFW_gamepadL1, RGFW_gamepadR1, RGFW_gamepadSelect, RGFW_gamepadStart, + RGFW_gamepadHome, RGFW_gamepadL3, RGFW_gamepadR3, 255, 255, RGFW_gamepadUp, RGFW_gamepadDown, RGFW_gamepadLeft, RGFW_gamepadRight + },{ /* Logitech */ + RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadX, RGFW_gamepadY, RGFW_gamepadL1, RGFW_gamepadR1, RGFW_gamepadL2, RGFW_gamepadR2, + RGFW_gamepadSelect, RGFW_gamepadStart, RGFW_gamepadHome, RGFW_gamepadL3, RGFW_gamepadR3, RGFW_gamepadUp, RGFW_gamepadDown, RGFW_gamepadLeft, RGFW_gamepadRight + } + }; + + win->event.button = RGFW_linux2RGFW[typeIndex][e.number]; + win->event.gamepad = i; + if (win->event.button == 255) break; + + RGFW_gamepadPressed[i][win->event.button].prev = RGFW_gamepadPressed[i][win->event.button].current; + RGFW_gamepadPressed[i][win->event.button].current = RGFW_BOOL(e.value); + RGFW_gamepadButtonCallback(win, i, win->event.button, RGFW_BOOL(e.value)); + + return 1; + } + case JS_EVENT_AXIS: { + size_t axis = e.number / 2; + if (axis == 2) axis = 1; + + ioctl(RGFW_gamepads[i], JSIOCGAXES, &win->event.axisesCount); + win->event.axisesCount = 2; + + if (axis < 3) { + if (e.number == 0 || e.number == 3) + RGFW_gamepadAxes[i][axis].x = (i32)((e.value / 32767.0f) * 100); + else if (e.number == 1 || e.number == 4) { + RGFW_gamepadAxes[i][axis].y = (i32)((e.value / 32767.0f) * 100); + } + } + + win->event.axis[axis] = RGFW_gamepadAxes[i][axis]; + win->event.type = RGFW_gamepadAxisMove; + win->event.gamepad = i; + win->event.whichAxis = (u8)axis; + RGFW_gamepadAxisCallback(win, i, win->event.axis, win->event.axisesCount, win->event.whichAxis); + return 1; + } + default: break; + } + } + if (bytes == -1 && errno == ENODEV) { + RGFW_gamepadCount--; + close(RGFW_gamepads[i]); + RGFW_gamepads[i] = 0; + + win->event.type = RGFW_gamepadDisconnected; + win->event.gamepad = i; + RGFW_gamepadCallback(win, i, 0); + return 1; + } + } + return 0; + } + + #endif +#endif + + + +/* + + Start of Wayland defines + + +*/ + +#ifdef RGFW_WAYLAND +/* +Wayland TODO: (out of date) +- fix RGFW_keyPressed lock state + + RGFW_windowMoved, the window was moved (by the user) + RGFW_windowResized the window was resized (by the user), [on WASM this means the browser was resized] + RGFW_windowRefresh The window content needs to be refreshed + + RGFW_DND a file has been dropped into the window + RGFW_DNDInit + +- window args: + #define RGFW_windowNoResize the window cannot be resized by the user + #define RGFW_windowAllowDND the window supports drag and drop + #define RGFW_scaleToMonitor scale the window to the screen + +- other missing functions functions ("TODO wayland") (~30 functions) +- fix buffer rendering weird behavior +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +RGFW_window* RGFW_key_win = NULL; + +/* wayland global garbage (wayland bad, X11 is fine (ish) (not really)) */ +#include "xdg-shell.h" +#include "xdg-decoration-unstable-v1.h" + +struct xkb_context *xkb_context; +struct xkb_keymap *keymap = NULL; +struct xkb_state *xkb_state = NULL; +enum zxdg_toplevel_decoration_v1_mode client_preferred_mode, RGFW_current_mode; +struct zxdg_decoration_manager_v1 *decoration_manager = NULL; + +struct wl_cursor_theme* RGFW_wl_cursor_theme = NULL; +struct wl_surface* RGFW_cursor_surface = NULL; +struct wl_cursor_image* RGFW_cursor_image = NULL; + +void xdg_wm_base_ping_handler(void *data, + struct xdg_wm_base *wm_base, uint32_t serial) +{ + RGFW_UNUSED(data); + xdg_wm_base_pong(wm_base, serial); +} + +const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = xdg_wm_base_ping_handler, +}; + +RGFW_bool RGFW_wl_configured = 0; + +void xdg_surface_configure_handler(void *data, + struct xdg_surface *xdg_surface, uint32_t serial) +{ + RGFW_UNUSED(data); + xdg_surface_ack_configure(xdg_surface, serial); + RGFW_wl_configured = 1; +} + +const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_configure_handler, +}; + +void xdg_toplevel_configure_handler(void *data, + struct xdg_toplevel *toplevel, int32_t width, int32_t height, + struct wl_array *states) +{ + RGFW_UNUSED(data); RGFW_UNUSED(toplevel); RGFW_UNUSED(states); + RGFW_UNUSED(width); RGFW_UNUSED(height); +} + +void xdg_toplevel_close_handler(void *data, + struct xdg_toplevel *toplevel) +{ + RGFW_UNUSED(data); + RGFW_window* win = (RGFW_window*)xdg_toplevel_get_user_data(toplevel); + if (win == NULL) + win = RGFW_key_win; + + RGFW_eventQueuePushEx(e.type = RGFW_quit; e._win = win); + RGFW_windowQuitCallback(win); +} + +void shm_format_handler(void *data, + struct wl_shm *shm, uint32_t format) +{ + RGFW_UNUSED(data); RGFW_UNUSED(shm); RGFW_UNUSED(format); +} + +const struct wl_shm_listener shm_listener = { + .format = shm_format_handler, +}; + +const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_configure_handler, + .close = xdg_toplevel_close_handler, +}; + +RGFW_window* RGFW_mouse_win = NULL; + +void pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(serial); RGFW_UNUSED(surface_x); RGFW_UNUSED(surface_y); + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + RGFW_mouse_win = win; + + RGFW_eventQueuePushEx(e.type = RGFW_mouseEnter; + e.point = RGFW_POINT(wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)); + e._win = win); + + RGFW_mouseNotifyCallback(win, win->event.point, RGFW_TRUE); +} +void pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(serial); RGFW_UNUSED(surface); + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + if (RGFW_mouse_win == win) + RGFW_mouse_win = NULL; + + RGFW_eventQueuePushEx(e.type = RGFW_mouseLeave; + e.point = win->event.point; + e._win = win); + + RGFW_mouseNotifyCallback(win, win->event.point, RGFW_FALSE); +} +void pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(x); RGFW_UNUSED(y); + + RGFW_ASSERT(RGFW_mouse_win != NULL); + RGFW_eventQueuePushEx(e.type = RGFW_mousePosChanged; + e.point = RGFW_POINT(wl_fixed_to_double(x), wl_fixed_to_double(y)); + e._win = RGFW_mouse_win); + + RGFW_mousePosCallback(RGFW_mouse_win, RGFW_POINT(wl_fixed_to_double(x), wl_fixed_to_double(y)), RGFW_mouse_win->event.vector); +} +void pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(serial); + RGFW_ASSERT(RGFW_mouse_win != NULL); + + u32 b = (button - 0x110); + + /* flip right and middle button codes */ + if (b == 1) b = 2; + else if (b == 2) b = 1; + + RGFW_mouseButtons[b].prev = RGFW_mouseButtons[b].current; + RGFW_mouseButtons[b].current = RGFW_BOOL(state); + + RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonReleased - RGFW_BOOL(state); + e.point = RGFW_mouse_win->event.point; + e.button = (u8)b; + e._win = RGFW_mouse_win); + RGFW_mouseButtonCallback(RGFW_mouse_win, (u8)b, 0, RGFW_BOOL(state)); +} +void pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(axis); + RGFW_ASSERT(RGFW_mouse_win != NULL); + + double scroll = - wl_fixed_to_double(value); + + RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonPressed; + e.point = RGFW_mouse_win->event.point; + e.button = RGFW_mouseScrollUp + (scroll < 0); + e.scroll = scroll; + e._win = RGFW_mouse_win); + + RGFW_mouseButtonCallback(RGFW_mouse_win, RGFW_mouseScrollUp + (scroll < 0), scroll, 1); +} + +void RGFW_doNothing(void) { } + +void keyboard_keymap (void *data, struct wl_keyboard *keyboard, uint32_t format, int32_t fd, uint32_t size) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(format); + + char *keymap_string = mmap (NULL, size, PROT_READ, MAP_SHARED, fd, 0); + xkb_keymap_unref (keymap); + keymap = xkb_keymap_new_from_string (xkb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + + munmap (keymap_string, size); + close (fd); + xkb_state_unref (xkb_state); + xkb_state = xkb_state_new (keymap); +} +void keyboard_enter (void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(keys); + + RGFW_key_win = (RGFW_window*)wl_surface_get_user_data(surface); + + RGFW_key_win->_flags |= RGFW_windowFocus; + RGFW_eventQueuePushEx(e.type = RGFW_focusIn; e._win = RGFW_key_win); + RGFW_focusCallback(RGFW_key_win, RGFW_TRUE); + + if ((RGFW_key_win->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(RGFW_key_win, RGFW_AREA(RGFW_key_win->r.w, RGFW_key_win->r.h)); +} +void keyboard_leave (void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); + + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + if (RGFW_key_win == win) + RGFW_key_win = NULL; + + RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e._win = win); + RGFW_focusCallback(win, RGFW_FALSE); + RGFW_window_focusLost(win); +} +void keyboard_key (void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); + + if (RGFW_key_win == NULL) return; + + xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, key + 8); + + u32 RGFWkey = RGFW_apiKeyToRGFW(key + 8); + RGFW_keyboard[RGFWkey].prev = RGFW_keyboard[RGFWkey].current; + RGFW_keyboard[RGFWkey].current = RGFW_BOOL(state); + + RGFW_eventQueuePushEx(e.type = (u8)(RGFW_keyPressed + state); + e.key = (u8)RGFWkey; + e.keyChar = (u8)keysym; + e.repeat = RGFW_isHeld(RGFW_key_win, (u8)RGFWkey); + e._win = RGFW_key_win); + + RGFW_updateKeyMods(RGFW_key_win, RGFW_BOOL(xkb_keymap_mod_get_index(keymap, "Lock")), RGFW_BOOL(xkb_keymap_mod_get_index(keymap, "Mod2")), RGFW_BOOL(xkb_keymap_mod_get_index(keymap, "ScrollLock"))); + RGFW_keyCallback(RGFW_key_win, (u8)RGFWkey, (u8)keysym, RGFW_key_win->event.keyMod, RGFW_BOOL(state)); +} +void keyboard_modifiers (void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); + xkb_state_update_mask (xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); +} +struct wl_keyboard_listener keyboard_listener = {&keyboard_keymap, &keyboard_enter, &keyboard_leave, &keyboard_key, &keyboard_modifiers, (void (*)(void *, struct wl_keyboard *, +int, int))&RGFW_doNothing}; + +void seat_capabilities (void *data, struct wl_seat *seat, uint32_t capabilities) { + RGFW_UNUSED(data); + static struct wl_pointer_listener pointer_listener = {&pointer_enter, &pointer_leave, &pointer_motion, &pointer_button, &pointer_axis, (void (*)(void *, struct wl_pointer *))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t, uint32_t))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t, int32_t))&RGFW_doNothing, (void (*)(void *, struct wl_pointer *, uint32_t, int32_t))&RGFW_doNothing, (void (*)(void*, struct wl_pointer*, uint32_t, uint32_t))&RGFW_doNothing}; + + if (capabilities & WL_SEAT_CAPABILITY_POINTER) { + struct wl_pointer *pointer = wl_seat_get_pointer (seat); + wl_pointer_add_listener (pointer, &pointer_listener, NULL); + } + if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { + struct wl_keyboard *keyboard = wl_seat_get_keyboard (seat); + wl_keyboard_add_listener (keyboard, &keyboard_listener, NULL); + } +} +struct wl_seat_listener seat_listener = {&seat_capabilities, (void (*)(void *, struct wl_seat *, const char *))&RGFW_doNothing}; + +void wl_global_registry_handler(void *data, + struct wl_registry *registry, uint32_t id, const char *interface, + uint32_t version) +{ + RGFW_window* win = (RGFW_window*)data; + RGFW_UNUSED(version); + if (RGFW_STRNCMP(interface, "wl_compositor", 16) == 0) { + win->src.compositor = wl_registry_bind(registry, + id, &wl_compositor_interface, 4); + } else if (RGFW_STRNCMP(interface, "xdg_wm_base", 12) == 0) { + win->src.xdg_wm_base = wl_registry_bind(registry, + id, &xdg_wm_base_interface, 1); + } else if (RGFW_STRNCMP(interface, zxdg_decoration_manager_v1_interface.name, 255) == 0) { + decoration_manager = wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1); + } else if (RGFW_STRNCMP(interface, "wl_shm", 7) == 0) { + win->src.shm = wl_registry_bind(registry, + id, &wl_shm_interface, 1); + wl_shm_add_listener(win->src.shm, &shm_listener, NULL); + } else if (RGFW_STRNCMP(interface,"wl_seat", 8) == 0) { + win->src.seat = wl_registry_bind(registry, id, &wl_seat_interface, 1); + wl_seat_add_listener(win->src.seat, &seat_listener, NULL); + } +} + +void wl_global_registry_remove(void *data, struct wl_registry *registry, uint32_t name) { RGFW_UNUSED(data); RGFW_UNUSED(registry); RGFW_UNUSED(name); } +const struct wl_registry_listener registry_listener = { + .global = wl_global_registry_handler, + .global_remove = wl_global_registry_remove, +}; + +void decoration_handle_configure(void *data, + struct zxdg_toplevel_decoration_v1 *decoration, + enum zxdg_toplevel_decoration_v1_mode mode) { + RGFW_UNUSED(data); RGFW_UNUSED(decoration); + RGFW_current_mode = mode; +} + +const struct zxdg_toplevel_decoration_v1_listener decoration_listener = { + .configure = decoration_handle_configure, +}; + +void randname(char *buf) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + long r = ts.tv_nsec; + + int i; + for (i = 0; i < 6; ++i) { + buf[i] = (char)('A'+(r&15)+(r&16)*2); + r >>= 5; + } +} + +size_t wl_stringlen(char* name) { + size_t i = 0; + while (name[i]) { i++; } + return i; +} + +int anonymous_shm_open(void) { + char name[] = "/RGFW-wayland-XXXXXX"; + int retries = 100; + + do { + randname(name + wl_stringlen(name) - 6); + + --retries; + /* shm_open guarantees that O_CLOEXEC is set */ + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + + return -1; +} + +int create_shm_file(off_t size) { + int fd = anonymous_shm_open(); + if (fd < 0) { + return fd; + } + + if (ftruncate(fd, size) < 0) { + close(fd); + return -1; + } + + return fd; +} + +void wl_surface_frame_done(void *data, struct wl_callback *cb, uint32_t time) { + RGFW_UNUSED(data); RGFW_UNUSED(cb); RGFW_UNUSED(time); + + #ifdef RGFW_BUFFER + RGFW_window* win = (RGFW_window*)data; + wl_surface_attach(win->src.surface, win->src.wl_buffer, 0, 0); + wl_surface_damage_buffer(win->src.surface, 0, 0, win->r.w, win->r.h); + wl_surface_commit(win->src.surface); + #endif +} + +const struct wl_callback_listener wl_surface_frame_listener = { + .done = wl_surface_frame_done, +}; +#endif /* RGFW_WAYLAND */ +/* + End of Wayland defines +*/ + +/* + + +Start of Linux / Unix defines + + +*/ #ifdef RGFW_UNIX -#include -#include +#if !defined(RGFW_NO_X11_CURSOR) && defined(RGFW_X11) +#include +#endif + +#include + +#ifndef RGFW_NO_DPI +#include +#include +#endif + +#include +#include +#include #include -void RGFW_stopCheckEvents(void) { +#include /* for converting keycode to string */ +#include /* for hiding */ +#include +#include +#include - _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; - } +#include /* for data limits (mainly used in drag and drop functions) */ +#include + +/* atoms needed for drag and drop */ +Atom XdndAware, XtextPlain, XtextUriList; +Atom RGFW_XUTF8_STRING = 0; + +Atom wm_delete_window = 0, RGFW_XCLIPBOARD = 0; + +#if defined(RGFW_X11) && !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) + typedef XcursorImage* (*PFN_XcursorImageCreate)(int, int); + typedef void (*PFN_XcursorImageDestroy)(XcursorImage*); + typedef Cursor(*PFN_XcursorImageLoadCursor)(Display*, const XcursorImage*); +#endif +#if defined(RGFW_OPENGL) && defined(RGFW_X11) + typedef GLXContext(*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); +#endif + +#if !defined(RGFW_NO_X11_XI_PRELOAD) && defined(RGFW_X11) + typedef int (* PFN_XISelectEvents)(Display*,Window,XIEventMask*,int); + PFN_XISelectEvents XISelectEventsSRC = NULL; + #define XISelectEvents XISelectEventsSRC + + void* X11Xihandle = NULL; +#endif + +#if !defined(RGFW_NO_X11_EXT_PRELOAD) && defined(RGFW_X11) + typedef void (* PFN_XSyncIntToValue)(XSyncValue*, int); + PFN_XSyncIntToValue XSyncIntToValueSRC = NULL; + #define XSyncIntToValue XSyncIntToValueSRC + + typedef Status (* PFN_XSyncSetCounter)(Display*, XSyncCounter, XSyncValue); + PFN_XSyncSetCounter XSyncSetCounterSRC = NULL; + #define XSyncSetCounter XSyncSetCounterSRC + + typedef XSyncCounter (* PFN_XSyncCreateCounter)(Display*, XSyncValue); + PFN_XSyncCreateCounter XSyncCreateCounterSRC = NULL; + #define XSyncCreateCounter XSyncCreateCounterSRC + + typedef void (* PFN_XShapeCombineMask)(Display*,Window,int,int,int,Pixmap,int); + PFN_XShapeCombineMask XShapeCombineMaskSRC; + #define XShapeCombineMask XShapeCombineMaskSRC + + typedef void (* PFN_XShapeCombineRegion)(Display*,Window,int,int,int,Region,int); + PFN_XShapeCombineRegion XShapeCombineRegionSRC; + #define XShapeCombineRegion XShapeCombineRegionSRC + void* X11XEXThandle = NULL; +#endif + +#if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) && defined(RGFW_X11) + PFN_XcursorImageLoadCursor XcursorImageLoadCursorSRC = NULL; + PFN_XcursorImageCreate XcursorImageCreateSRC = NULL; + PFN_XcursorImageDestroy XcursorImageDestroySRC = NULL; + + #define XcursorImageLoadCursor XcursorImageLoadCursorSRC + #define XcursorImageCreate XcursorImageCreateSRC + #define XcursorImageDestroy XcursorImageDestroySRC + + void* X11Cursorhandle = NULL; +#endif + +#ifdef RGFW_X11 +const char* RGFW_instName = NULL; +void RGFW_setXInstName(const char* name) { RGFW_instName = name; } +#endif + +#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) +RGFW_bool RGFW_extensionSupportedPlatform(const char * extension, size_t len) { + const char* extensions = glXQueryExtensionsString(_RGFW.display, XDefaultScreen(_RGFW.display)); + return (extensions != NULL) && RGFW_extensionSupportedStr(extensions, extension, len); } +RGFW_proc RGFW_getProcAddress(const char* procname) { return (RGFW_proc) glXGetProcAddress((GLubyte*) procname); } +#endif -RGFWDEF u64 RGFW_linux_getTimeNS(i32 clock); -u64 RGFW_linux_getTimeNS(i32 clock) { - struct timespec ts; - const u64 scale_factor = 1000000000; - clock_gettime(clock, &ts); - return (u64)ts.tv_sec * scale_factor + (u64)ts.tv_nsec; -} +void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area) { + RGFW_GOTO_WAYLAND(0); -void RGFW_waitForEvent(i32 waitMS) { - if (waitMS == 0) return; +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + win->buffer = (u8*)buffer; + win->bufferSize = area; - 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[2]; - fds[0].fd = 0; - fds[0].events = POLLIN; - fds[0].revents = 0; - fds[1].fd = _RGFW->eventWait_forceStop[0]; - fds[1].events = POLLIN; - fds[1].revents = 0; - - - if (RGFW_usingWayland()) { - #ifdef RGFW_WAYLAND - fds[0].fd = wl_display_get_fd(_RGFW->wl_display); - - /* empty the queue */ - while (wl_display_prepare_read(_RGFW->wl_display) != 0) { - /* error occured when dispatching the queue */ - if (wl_display_dispatch_pending(_RGFW->wl_display) == -1) { - return; - } - } - - /* send any pending requests to the compositor */ - while (wl_display_flush(_RGFW->wl_display) == -1) { - - /* queue is full dispatch them */ - if (errno == EAGAIN) { - if (wl_display_dispatch_pending(_RGFW->wl_display) == -1) { - return; - } - } else { - return; - } - } + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoBuffer, RGFW_DEBUG_CTX(win, 0), "createing a 4 channel buffer"); + #ifdef RGFW_X11 + #ifdef RGFW_OSMESA + win->src.ctx = OSMesaCreateContext(OSMESA_BGRA, NULL); + OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); + OSMesaPixelStore(OSMESA_Y_UP, 0); #endif - } else { - #ifdef RGFW_X11 - fds[0].fd = ConnectionNumber(_RGFW->display); + + win->src.bitmap = XCreateImage( + win->src.display, win->src.visual.visual, (u32)win->src.visual.depth, + ZPixmap, 0, NULL, area.w, area.h, 32, 0 + ); + #endif + #ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL {} + u32 size = (u32)(win->r.w * win->r.h * 4); + int fd = create_shm_file(size); + if (fd < 0) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, RGFW_DEBUG_CTX(win, (u32)fd),"Failed to create a buffer."); + exit(1); + } + + win->src.buffer = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (win->src.buffer == MAP_FAILED) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, RGFW_DEBUG_CTX(win, 0), "mmap failed!"); + close(fd); + exit(1); + } + + win->_flags |= RGFW_BUFFER_ALLOC; + + struct wl_shm_pool* pool = wl_shm_create_pool(win->src.shm, fd, (i32)size); + win->src.wl_buffer = wl_shm_pool_create_buffer(pool, 0, win->r.w, win->r.h, win->r.w * 4, + WL_SHM_FORMAT_ARGB8888); + wl_shm_pool_destroy(pool); + + close(fd); + + wl_surface_attach(win->src.surface, win->src.wl_buffer, 0, 0); + wl_surface_commit(win->src.surface); + + u8 color[] = {0x00, 0x00, 0x00, 0xFF}; + + size_t i; + for (i = 0; i < area.w * area.h * 4; i += 4) { + RGFW_MEMCPY(&win->buffer[i], color, 4); + } + + RGFW_MEMCPY(win->src.buffer, win->buffer, (size_t)(win->r.w * win->r.h * 4)); + + #if defined(RGFW_OSMESA) + win->src.ctx = OSMesaCreateContext(OSMESA_BGRA, NULL); + OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); + OSMesaPixelStore(OSMESA_Y_UP, 0); #endif - } - - i32 clock = 0; - #if defined(_POSIX_MONOTONIC_CLOCK) - struct timespec ts; - RGFW_MEMSET(&ts, 0, sizeof(struct timespec)); - - if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) - clock = CLOCK_MONOTONIC; - #else - clock = CLOCK_REALTIME; + #endif +#else + #ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL{} #endif - u64 start = RGFW_linux_getTimeNS(clock); - if (RGFW_usingWayland()) { - #ifdef RGFW_WAYLAND - while (wl_display_dispatch_pending(_RGFW->wl_display) == 0) { - if (poll(fds, 1, waitMS) <= 0) { - wl_display_cancel_read(_RGFW->wl_display); - break; - } else { - if (wl_display_read_events(_RGFW->wl_display) == -1) - return; - } + RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); +#endif +} - if (waitMS != RGFW_eventWaitNext) { - waitMS -= (i32)(RGFW_linux_getTimeNS(clock) - start) / (i32)1e+6; - } - } +#define RGFW_LOAD_ATOM(name) \ + static Atom name = 0; \ + if (name == 0) name = XInternAtom(_RGFW.display, #name, False); - /* queue contains events from read, dispatch them */ - if (wl_display_dispatch_pending(_RGFW->wl_display) == -1) { +void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { + RGFW_setBit(&win->_flags, RGFW_windowNoBorder, !border); + + RGFW_GOTO_WAYLAND(0); + #ifdef RGFW_X11 + RGFW_LOAD_ATOM(_MOTIF_WM_HINTS); + + struct __x11WindowHints { + unsigned long flags, functions, decorations, status; + long input_mode; + } hints; + hints.flags = 2; + hints.decorations = border; + + XChangeProperty(win->src.display, win->src.window, _MOTIF_WM_HINTS, _MOTIF_WM_HINTS, 32, + PropModeReplace, (u8*)&hints, 5 + ); + + if (RGFW_window_isHidden(win) == 0) { + RGFW_window_hide(win); + RGFW_window_show(win); + } + + #endif + #ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + RGFW_UNUSED(win); RGFW_UNUSED(border); + #endif +} + +void RGFW_releaseCursor(RGFW_window* win) { +RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + XUngrabPointer(win->src.display, CurrentTime); + + /* disable raw input */ + unsigned char mask[] = { 0 }; + XIEventMask em; + em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; + + XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + RGFW_UNUSED(win); +#endif +} + +void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { +RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + /* enable raw input */ + unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; + XISetMask(mask, XI_RawMotion); + + XIEventMask em; + em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; + + XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); + + XGrabPointer(win->src.display, win->src.window, True, PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); + RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (i32)(r.w / 2), win->r.y + (i32)(r.h / 2))); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + RGFW_UNUSED(win); RGFW_UNUSED(r); +#endif +} + +#define RGFW_LOAD_LIBRARY(x, lib) if (x == NULL) x = dlopen(lib, RTLD_LAZY | RTLD_LOCAL) +#define RGFW_PROC_DEF(proc, name) if (name##SRC == NULL && proc != NULL) { \ + void* ptr = dlsym(proc, #name); \ + if (ptr != NULL) memcpy(&name##SRC, &ptr, sizeof(PFN_##name)); \ +} + +#ifdef RGFW_X11 +void RGFW_window_getVisual(RGFW_window* win) { +#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) + i32* visual_attribs = RGFW_initFormatAttribs(); + i32 fbcount; + GLXFBConfig* fbc = glXChooseFBConfig(win->src.display, DefaultScreen(win->src.display), visual_attribs, &fbcount); + + i32 best_fbc = -1; + i32 best_depth = 0; + i32 best_samples = 0; + + if (fbcount == 0) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to find any valid GLX visual configs"); return; } - #endif - } else { - #ifdef RGFW_X11 - while (XPending(_RGFW->display) == 0) { - if (poll(fds, 1, waitMS) <= 0) - break; - if (waitMS != RGFW_eventWaitNext) { - waitMS -= (i32)(RGFW_linux_getTimeNS(clock) - start) / (i32)1e+6; + i32 i; + for (i = 0; i < fbcount; i++) { + XVisualInfo* vi = glXGetVisualFromFBConfig(win->src.display, fbc[i]); + if (vi == NULL) + continue; + + i32 samp_buf, samples; + glXGetFBConfigAttrib(win->src.display, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf); + glXGetFBConfigAttrib(win->src.display, fbc[i], GLX_SAMPLES, &samples); + + if (best_fbc == -1) best_fbc = i; + if ((!(win->_flags & RGFW_windowTransparent) || vi->depth == 32) && best_depth == 0) { + best_fbc = i; + best_depth = vi->depth; } + if ((!(win->_flags & RGFW_windowTransparent) || vi->depth == 32) && samples <= RGFW_GL_HINTS[RGFW_glSamples] && samples > best_samples) { + best_fbc = i; + best_depth = vi->depth; + best_samples = samples; + } + XFree(vi); } + + if (best_fbc == -1) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to get a valid GLX visual"); + return; + } + + win->src.bestFbc = fbc[best_fbc]; + XVisualInfo* vi = glXGetVisualFromFBConfig(win->src.display, win->src.bestFbc); + if (vi->depth != 32 && (win->_flags & RGFW_windowTransparent)) + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, RGFW_DEBUG_CTX(win, 0), "Failed to to find a matching visual with a 32-bit depth"); + + if (best_samples < RGFW_GL_HINTS[RGFW_glSamples]) + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, RGFW_DEBUG_CTX(win, 0), "Failed to load matching sampiling"); + + int configCaveat; + if (glXGetFBConfigAttrib(win->src.display, win->src.bestFbc, GLX_CONFIG_CAVEAT, &configCaveat) == Success && + configCaveat == GLX_SLOW_CONFIG) { + win->_flags |= RGFW_windowOpenglSoftware; + } + + XFree(fbc); + win->src.visual = *vi; + XFree(vi); +#else + win->src.visual.visual = DefaultVisual(win->src.display, DefaultScreen(win->src.display)); + win->src.visual.depth = DefaultDepth(win->src.display, DefaultScreen(win->src.display)); + if (win->_flags & RGFW_windowTransparent) { + XMatchVisualInfo(win->src.display, DefaultScreen(win->src.display), 32, TrueColor, &win->src.visual); /*!< for RGBA backgrounds */ + if (win->src.visual.depth != 32) + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, RGFW_DEBUG_CTX(win, 0), "Failed to load a 32-bit depth"); + } +#endif +} +#endif +#ifndef RGFW_EGL +void RGFW_window_initOpenGL(RGFW_window* win) { +#ifdef RGFW_OPENGL + i32 context_attribs[7] = { 0, 0, 0, 0, 0, 0, 0 }; + context_attribs[0] = GLX_CONTEXT_PROFILE_MASK_ARB; + if (RGFW_GL_HINTS[RGFW_glProfile] == RGFW_glCore) + context_attribs[1] = GLX_CONTEXT_CORE_PROFILE_BIT_ARB; + else + context_attribs[1] = GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; + + if (RGFW_GL_HINTS[RGFW_glMinor] || RGFW_GL_HINTS[RGFW_glMajor]) { + context_attribs[2] = GLX_CONTEXT_MAJOR_VERSION_ARB; + context_attribs[3] = RGFW_GL_HINTS[RGFW_glMajor]; + context_attribs[4] = GLX_CONTEXT_MINOR_VERSION_ARB; + context_attribs[5] = RGFW_GL_HINTS[RGFW_glMinor]; + } + + glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0; + glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc) + glXGetProcAddressARB((GLubyte*) "glXCreateContextAttribsARB"); + + GLXContext ctx = NULL; + if (_RGFW.root != NULL && _RGFW.root != win) { + ctx = _RGFW.root->src.ctx; + RGFW_window_makeCurrent_OpenGL(_RGFW.root); + } + + if (glXCreateContextAttribsARB == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "failed to load proc address 'glXCreateContextAttribsARB', loading a generic opengl context"); + win->src.ctx = glXCreateContext(win->src.display, &win->src.visual, ctx, True); + } + else { + win->src.ctx = glXCreateContextAttribsARB(win->src.display, win->src.bestFbc, ctx, True, context_attribs); + XSync(win->src.display, False); + if (win->src.ctx == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "failed to create an opengl context with AttribsARB, loading a generic opengl context"); + win->src.ctx = glXCreateContext(win->src.display, &win->src.visual, ctx, True); + } + } + + glXMakeCurrent(win->src.display, (Drawable) win->src.window, (GLXContext) win->src.ctx); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context initalized"); +#else + RGFW_UNUSED(win); +#endif +} + +void RGFW_window_freeOpenGL(RGFW_window* win) { +#ifdef RGFW_OPENGL + if (win->src.ctx == NULL) return; + glXDestroyContext(win->src.display, win->src.ctx); + win->src.ctx = NULL; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context freed"); +#else +RGFW_UNUSED(win); +#endif +} +#endif + + +i32 RGFW_init(void) { + RGFW_GOTO_WAYLAND(1); +#if defined(RGFW_C89) || defined(__cplusplus) + if (_RGFW_init) return 0; + _RGFW_init = RGFW_TRUE; + _RGFW.root = NULL; _RGFW.current = NULL; _RGFW.windowCount = -1; _RGFW.eventLen = 0; _RGFW.eventIndex = 0; +#endif + +#ifdef RGFW_X11 + if (_RGFW.windowCount != -1) return 0; + #ifdef RGFW_USE_XDL + XDL_init(); + #endif + + #if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) + #if defined(__CYGWIN__) + RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor-1.so"); + #elif defined(__OpenBSD__) || defined(__NetBSD__) + RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor.so"); + #else + RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor.so.1"); + #endif + RGFW_PROC_DEF(X11Cursorhandle, XcursorImageCreate); + RGFW_PROC_DEF(X11Cursorhandle, XcursorImageDestroy); + RGFW_PROC_DEF(X11Cursorhandle, XcursorImageLoadCursor); + #endif + + #if !defined(RGFW_NO_X11_XI_PRELOAD) + #if defined(__CYGWIN__) + RGFW_LOAD_LIBRARY(X11Xihandle, "libXi-6.so"); + #elif defined(__OpenBSD__) || defined(__NetBSD__) + RGFW_LOAD_LIBRARY(X11Xihandle, "libXi.so"); + #else + RGFW_LOAD_LIBRARY(X11Xihandle, "libXi.so.6"); + #endif + RGFW_PROC_DEF(X11Xihandle, XISelectEvents); + #endif + + #if !defined(RGFW_NO_X11_EXT_PRELOAD) + #if defined(__CYGWIN__) + RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext-6.so"); + #elif defined(__OpenBSD__) || defined(__NetBSD__) + RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext.so"); + #else + RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext.so.6"); + #endif + RGFW_PROC_DEF(X11XEXThandle, XSyncCreateCounter); + RGFW_PROC_DEF(X11XEXThandle, XSyncIntToValue); + RGFW_PROC_DEF(X11XEXThandle, XSyncSetCounter); + RGFW_PROC_DEF(X11XEXThandle, XShapeCombineRegion); + RGFW_PROC_DEF(X11XEXThandle, XShapeCombineMask); + #endif + + XInitThreads(); /*!< init X11 threading */ + _RGFW.display = XOpenDisplay(0); + XSetWindowAttributes wa; + RGFW_MEMSET(&wa, 0, sizeof(wa)); + wa.event_mask = PropertyChangeMask; + _RGFW.helperWindow = XCreateWindow(_RGFW.display, XDefaultRootWindow(_RGFW.display), 0, 0, 1, 1, 0, 0, + InputOnly, DefaultVisual(_RGFW.display, DefaultScreen(_RGFW.display)), CWEventMask, &wa); + + _RGFW.windowCount = 0; + u8 RGFW_blk[] = { 0, 0, 0, 0 }; + _RGFW.hiddenMouse = RGFW_loadMouse(RGFW_blk, RGFW_AREA(1, 1), 4); + _RGFW.clipboard = NULL; + + XkbComponentNamesRec rec; + XkbDescPtr desc = XkbGetMap(_RGFW.display, 0, XkbUseCoreKbd); + XkbDescPtr evdesc; + u8 old[sizeof(RGFW_keycodes) / sizeof(RGFW_keycodes[0])]; + + XkbGetNames(_RGFW.display, XkbKeyNamesMask, desc); + + RGFW_MEMSET(&rec, 0, sizeof(rec)); + rec.keycodes = (char*)"evdev"; + evdesc = XkbGetKeyboardByName(_RGFW.display, XkbUseCoreKbd, &rec, XkbGBN_KeyNamesMask, XkbGBN_KeyNamesMask, False); + /* memo: RGFW_keycodes[x11 keycode] = rgfw keycode */ + if(evdesc != NULL && desc != NULL){ + for(int i = 0; i < (int)sizeof(RGFW_keycodes) / (int)sizeof(RGFW_keycodes[0]); i++){ + old[i] = RGFW_keycodes[i]; + RGFW_keycodes[i] = 0; + } + for(int i = evdesc->min_key_code; i <= evdesc->max_key_code; i++){ + for(int j = desc->min_key_code; j <= desc->max_key_code; j++){ + if(strncmp(evdesc->names->keys[i].name, desc->names->keys[j].name, XkbKeyNameLength) == 0){ + RGFW_keycodes[j] = old[i]; + break; + } + } + } + XkbFreeKeyboard(desc, 0, True); + XkbFreeKeyboard(evdesc, 0, True); + } +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL + _RGFW.wl_display = wl_display_connect(NULL); +#endif + _RGFW.windowCount = 0; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context initialized"); + return 0; +} + + +RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { + RGFW_window_basic_init(win, rect, flags); + +#ifdef RGFW_WAYLAND + win->src.compositor = NULL; +#endif + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + i64 event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | FocusChangeMask | LeaveWindowMask | EnterWindowMask | ExposureMask; /*!< X11 events accepted */ + + win->src.display = XOpenDisplay(NULL); + RGFW_window_getVisual(win); + + /* make X window attrubutes */ + XSetWindowAttributes swa; + RGFW_MEMSET(&swa, 0, sizeof(swa)); + + Colormap cmap; + swa.colormap = cmap = XCreateColormap(win->src.display, + DefaultRootWindow(win->src.display), + win->src.visual.visual, AllocNone); + swa.event_mask = event_mask; + + /* create the window */ + win->src.window = XCreateWindow(win->src.display, DefaultRootWindow(win->src.display), win->r.x, win->r.y, (u32)win->r.w, (u32)win->r.h, + 0, win->src.visual.depth, InputOutput, win->src.visual.visual, + CWColormap | CWBorderPixel | CWEventMask, &swa); + + XFreeColors(win->src.display, cmap, NULL, 0, 0); + + win->src.gc = XCreateGC(win->src.display, win->src.window, 0, NULL); + + /* In your .desktop app, if you set the property + StartupWMClass=RGFW that will assoicate the launcher icon + with your application - robrohan */ + if (RGFW_className == NULL) + RGFW_className = (char*)name; + + XClassHint hint; + hint.res_class = (char*)RGFW_className; + if (RGFW_instName == NULL) hint.res_name = (char*)name; + else hint.res_name = (char*)RGFW_instName; + XSetClassHint(win->src.display, win->src.window, &hint); + + #ifndef RGFW_NO_MONITOR + if (flags & RGFW_windowScaleToMonitor) + RGFW_window_scaleToMonitor(win); + #endif + XSelectInput(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(win->src.display, "WM_DELETE_WINDOW", False); + RGFW_XUTF8_STRING = XInternAtom(win->src.display, "UTF8_STRING", False); + RGFW_XCLIPBOARD = XInternAtom(win->src.display, "CLIPBOARD", False); + } + + XSetWMProtocols(win->src.display, (Drawable) win->src.window, &wm_delete_window, 1); + /* set the background */ + RGFW_window_setName(win, name); + + XMoveWindow(win->src.display, (Drawable) win->src.window, win->r.x, win->r.y); /*!< move the window to it's proper cords */ + + if (flags & RGFW_windowAllowDND) { /* init drag and drop atoms and turn on drag and drop for this window */ + win->_flags |= RGFW_windowAllowDND; + + /* actions */ + XtextUriList = XInternAtom(win->src.display, "text/uri-list", False); + XtextPlain = XInternAtom(win->src.display, "text/plain", False); + XdndAware = XInternAtom(win->src.display, "XdndAware", False); + const u8 version = 5; + + XChangeProperty(win->src.display, win->src.window, + XdndAware, 4, 32, + PropModeReplace, &version, 1); /*!< turns on drag and drop */ + } + +#ifdef RGFW_ADVANCED_SMOOTH_RESIZE + RGFW_LOAD_ATOM(_NET_WM_SYNC_REQUEST_COUNTER) + RGFW_LOAD_ATOM(_NET_WM_SYNC_REQUEST) + Atom protcols[2] = {_NET_WM_SYNC_REQUEST, wm_delete_window}; + XSetWMProtocols(win->src.display, win->src.window, protcols, 2); + + XSyncValue initial_value; + XSyncIntToValue(&initial_value, 0); + win->src.counter = XSyncCreateCounter(win->src.display, initial_value); + + XChangeProperty(win->src.display, win->src.window, _NET_WM_SYNC_REQUEST_COUNTER, XA_CARDINAL, 32, PropModeReplace, (uint8_t*)&win->src.counter, 1); +#endif + + if ((flags & RGFW_windowNoInitAPI) == 0) { + RGFW_window_initOpenGL(win); + RGFW_window_initBuffer(win); + } + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); + RGFW_window_setMouseDefault(win); + RGFW_window_setFlags(win, flags); + + win->src.r = win->r; + + RGFW_window_show(win); + return win; /*return newly created window */ +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningWayland, RGFW_DEBUG_CTX(win, 0), "RGFW Wayland support is experimental"); + + win->src.wl_display = _RGFW.wl_display; + if (win->src.wl_display == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errWayland, RGFW_DEBUG_CTX(win, 0), "Failed to load Wayland display"); + #ifdef RGFW_X11 + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningWayland, RGFW_DEBUG_CTX(win, 0), "Falling back to X11"); + RGFW_useWayland(0); + return RGFW_createWindowPtr(name, rect, flags, win); #endif + return NULL; } - /* drain any data in the stop request */ - if (_RGFW->eventWait_forceStop[2]) { - char data[64]; - RGFW_MEMSET(data, 0, sizeof(data)); - (void)!read(_RGFW->eventWait_forceStop[0], data, sizeof(data)); - _RGFW->eventWait_forceStop[2] = 0; + #ifdef RGFW_X11 + win->src.display = _RGFW.display; + win->src.window = _RGFW.helperWindow; + XMapWindow(_RGFW.display, win->src.window); + XFlush(win->src.display); + if (wm_delete_window == 0) { + wm_delete_window = XInternAtom(win->src.display, "WM_DELETE_WINDOW", False); + RGFW_XUTF8_STRING = XInternAtom(win->src.display, "UTF8_STRING", False); + RGFW_XCLIPBOARD = XInternAtom(win->src.display, "CLIPBOARD", False); + } + #endif + + struct wl_registry *registry = wl_display_get_registry(win->src.wl_display); + wl_registry_add_listener(registry, ®istry_listener, win); + + wl_display_roundtrip(win->src.wl_display); + wl_display_dispatch(win->src.wl_display); + + if (win->src.compositor == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errWayland, RGFW_DEBUG_CTX(win, 0), "Can't find compositor."); + return NULL; } + + if (RGFW_wl_cursor_theme == NULL) { + RGFW_wl_cursor_theme = wl_cursor_theme_load(NULL, 24, win->src.shm); + RGFW_cursor_surface = wl_compositor_create_surface(win->src.compositor); + + struct wl_cursor* cursor = wl_cursor_theme_get_cursor(RGFW_wl_cursor_theme, "left_ptr"); + RGFW_cursor_image = cursor->images[0]; + struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(RGFW_cursor_image); + + wl_surface_attach(RGFW_cursor_surface, cursor_buffer, 0, 0); + wl_surface_commit(RGFW_cursor_surface); + } + + xdg_wm_base_add_listener(win->src.xdg_wm_base, &xdg_wm_base_listener, NULL); + + xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + win->src.surface = wl_compositor_create_surface(win->src.compositor); + wl_surface_set_user_data(win->src.surface, win); + + win->src.xdg_surface = xdg_wm_base_get_xdg_surface(win->src.xdg_wm_base, win->src.surface); + xdg_surface_add_listener(win->src.xdg_surface, &xdg_surface_listener, NULL); + + xdg_wm_base_set_user_data(win->src.xdg_wm_base, win); + + win->src.xdg_toplevel = xdg_surface_get_toplevel(win->src.xdg_surface); + xdg_toplevel_set_user_data(win->src.xdg_toplevel, win); + xdg_toplevel_add_listener(win->src.xdg_toplevel, &xdg_toplevel_listener, NULL); + + xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->r.w, win->r.h); + + if (!(flags & RGFW_windowNoBorder)) { + win->src.decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( + decoration_manager, win->src.xdg_toplevel); + } + + wl_display_roundtrip(win->src.wl_display); + + wl_surface_commit(win->src.surface); + RGFW_window_show(win); + + /* wait for the surface to be configured */ + while (wl_display_dispatch(win->src.wl_display) != -1 && !RGFW_wl_configured) { } + + if ((flags & RGFW_windowNoInitAPI) == 0) { + RGFW_window_initOpenGL(win); + RGFW_window_initBuffer(win); + } + struct wl_callback* callback = wl_surface_frame(win->src.surface); + wl_callback_add_listener(callback, &wl_surface_frame_listener, win); + wl_surface_commit(win->src.surface); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); + + #ifndef RGFW_NO_MONITOR + if (flags & RGFW_windowScaleToMonitor) + RGFW_window_scaleToMonitor(win); + #endif + + RGFW_window_setName(win, name); + RGFW_window_setMouseDefault(win); + RGFW_window_setFlags(win, flags); + return win; /* return newly created window */ +#endif +} + +RGFW_area RGFW_getScreenSize(void) { + RGFW_GOTO_WAYLAND(1); + RGFW_init(); + + #ifdef RGFW_X11 + Screen* scrn = DefaultScreenOfDisplay(_RGFW.display); + return RGFW_AREA(scrn->width, scrn->height); + #endif + #ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL return RGFW_AREA(_RGFW.root->r.w, _RGFW.root->r.h); /* TODO */ + #endif +} + +RGFW_point RGFW_getGlobalMousePoint(void) { + RGFW_init(); + RGFW_point RGFWMouse = RGFW_POINT(0, 0); + RGFW_GOTO_WAYLAND(1); +#ifdef RGFW_X11 + i32 x, y; + u32 z; + Window window1, window2; + XQueryPointer(_RGFW.display, XDefaultRootWindow(_RGFW.display), &window1, &window2, &RGFWMouse.x, &RGFWMouse.y, &x, &y, &z); + return RGFWMouse; +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + return RGFWMouse; +#endif +} + +RGFWDEF void RGFW_XHandleClipboardSelection(XEvent* event); +void RGFW_XHandleClipboardSelection(XEvent* event) { RGFW_UNUSED(event); +#ifdef RGFW_X11 + RGFW_LOAD_ATOM(ATOM_PAIR); + RGFW_LOAD_ATOM(MULTIPLE); + RGFW_LOAD_ATOM(TARGETS); + RGFW_LOAD_ATOM(SAVE_TARGETS); + + const XSelectionRequestEvent* request = &event->xselectionrequest; + const Atom formats[] = { RGFW_XUTF8_STRING, XA_STRING }; + const int formatCount = sizeof(formats) / sizeof(formats[0]); + + if (request->target == TARGETS) { + const Atom targets[] = { TARGETS, MULTIPLE, RGFW_XUTF8_STRING, XA_STRING }; + + XChangeProperty(_RGFW.display, request->requestor, request->property, + XA_ATOM, 32, PropModeReplace, (u8*) targets, sizeof(targets) / sizeof(Atom)); + } else if (request->target == MULTIPLE) { + Atom* targets = NULL; + + Atom actualType = 0; + int actualFormat = 0; + unsigned long count = 0, bytesAfter = 0; + + XGetWindowProperty(_RGFW.display, request->requestor, request->property, 0, LONG_MAX, + False, ATOM_PAIR, &actualType, &actualFormat, &count, &bytesAfter, (u8**) &targets); + + unsigned long i; + for (i = 0; i < (u32)count; i += 2) { + if (targets[i] == RGFW_XUTF8_STRING || targets[i] == XA_STRING) + XChangeProperty(_RGFW.display, request->requestor, targets[i + 1], targets[i], + 8, PropModeReplace, (const unsigned char *)_RGFW.clipboard, (i32)_RGFW.clipboard_len); + else + targets[i + 1] = None; + } + + XChangeProperty(_RGFW.display, + request->requestor, request->property, ATOM_PAIR, 32, + PropModeReplace, (u8*) targets, (i32)count); + + XFlush(_RGFW.display); + XFree(targets); + } else if (request->target == SAVE_TARGETS) + XChangeProperty(_RGFW.display, request->requestor, request->property, 0, 32, PropModeReplace, NULL, 0); + else { + int i; + for (i = 0; i < formatCount; i++) { + if (request->target != formats[i]) + continue; + XChangeProperty(_RGFW.display, request->requestor, request->property, request->target, + 8, PropModeReplace, (u8*) _RGFW.clipboard, (i32)_RGFW.clipboard_len); + } + } + + XEvent reply = { SelectionNotify }; + reply.xselection.property = request->property; + reply.xselection.display = request->display; + reply.xselection.requestor = request->requestor; + reply.xselection.selection = request->selection; + reply.xselection.target = request->target; + reply.xselection.time = request->time; + + XSendEvent(_RGFW.display, request->requestor, False, 0, &reply); +#endif } char* RGFW_strtok(char* str, const char* delimStr); @@ -4882,678 +4496,19 @@ char* RGFW_strtok(char* str, const char* delimStr) { return token_start; } -#ifdef RGFW_X11 -RGFWDEF i32 RGFW_initPlatform_X11(void); -RGFWDEF void RGFW_deinitPlatform_X11(void); -#endif -#ifdef RGFW_WAYLAND -RGFWDEF i32 RGFW_initPlatform_Wayland(void); -RGFWDEF void RGFW_deinitPlatform_Wayland(void); -#endif - -RGFWDEF void RGFW_load_X11(void); -RGFWDEF void RGFW_load_Wayland(void); - -#if !defined(RGFW_X11) || !defined(RGFW_WAYLAND) -void RGFW_load_X11(void) { } -void RGFW_load_Wayland(void) { } -#endif - -/* - * Sadly we have to use magic linux keycodes - * We can't use X11 functions, because that breaks Wayland, but they use the same keycodes so there's no use redeffing them - * We can't use linux enums, because the headers don't exist on BSD - */ -void RGFW_initKeycodesPlatform(void) { - _RGFW->keycodes[49] = RGFW_backtick; - _RGFW->keycodes[19] = RGFW_0; - _RGFW->keycodes[10] = RGFW_1; - _RGFW->keycodes[11] = RGFW_2; - _RGFW->keycodes[12] = RGFW_3; - _RGFW->keycodes[13] = RGFW_4; - _RGFW->keycodes[14] = RGFW_5; - _RGFW->keycodes[15] = RGFW_6; - _RGFW->keycodes[16] = RGFW_7; - _RGFW->keycodes[17] = RGFW_8; - _RGFW->keycodes[18] = RGFW_9; - _RGFW->keycodes[65] = RGFW_space; - _RGFW->keycodes[38] = RGFW_a; - _RGFW->keycodes[56] = RGFW_b; - _RGFW->keycodes[54] = RGFW_c; - _RGFW->keycodes[40] = RGFW_d; - _RGFW->keycodes[26] = RGFW_e; - _RGFW->keycodes[41] = RGFW_f; - _RGFW->keycodes[42] = RGFW_g; - _RGFW->keycodes[43] = RGFW_h; - _RGFW->keycodes[31] = RGFW_i; - _RGFW->keycodes[44] = RGFW_j; - _RGFW->keycodes[45] = RGFW_k; - _RGFW->keycodes[46] = RGFW_l; - _RGFW->keycodes[58] = RGFW_m; - _RGFW->keycodes[57] = RGFW_n; - _RGFW->keycodes[32] = RGFW_o; - _RGFW->keycodes[33] = RGFW_p; - _RGFW->keycodes[24] = RGFW_q; - _RGFW->keycodes[27] = RGFW_r; - _RGFW->keycodes[39] = RGFW_s; - _RGFW->keycodes[28] = RGFW_t; - _RGFW->keycodes[30] = RGFW_u; - _RGFW->keycodes[55] = RGFW_v; - _RGFW->keycodes[25] = RGFW_w; - _RGFW->keycodes[53] = RGFW_x; - _RGFW->keycodes[29] = RGFW_y; - _RGFW->keycodes[52] = RGFW_z; - _RGFW->keycodes[60] = RGFW_period; - _RGFW->keycodes[59] = RGFW_comma; - _RGFW->keycodes[61] = RGFW_slash; - _RGFW->keycodes[34] = RGFW_bracket; - _RGFW->keycodes[35] = RGFW_closeBracket; - _RGFW->keycodes[47] = RGFW_semicolon; - _RGFW->keycodes[48] = RGFW_apostrophe; - _RGFW->keycodes[51] = RGFW_backSlash; - _RGFW->keycodes[36] = RGFW_return; - _RGFW->keycodes[119] = RGFW_delete; - _RGFW->keycodes[77] = RGFW_numLock; - _RGFW->keycodes[106] = RGFW_kpSlash; - _RGFW->keycodes[63] = RGFW_kpMultiply; - _RGFW->keycodes[86] = RGFW_kpPlus; - _RGFW->keycodes[82] = RGFW_kpMinus; - _RGFW->keycodes[87] = RGFW_kp1; - _RGFW->keycodes[88] = RGFW_kp2; - _RGFW->keycodes[89] = RGFW_kp3; - _RGFW->keycodes[83] = RGFW_kp4; - _RGFW->keycodes[84] = RGFW_kp5; - _RGFW->keycodes[85] = RGFW_kp6; - _RGFW->keycodes[81] = RGFW_kp9; - _RGFW->keycodes[90] = RGFW_kp0; - _RGFW->keycodes[91] = RGFW_kpPeriod; - _RGFW->keycodes[104] = RGFW_kpReturn; - _RGFW->keycodes[20] = RGFW_minus; - _RGFW->keycodes[21] = RGFW_equals; - _RGFW->keycodes[22] = RGFW_backSpace; - _RGFW->keycodes[23] = RGFW_tab; - _RGFW->keycodes[66] = RGFW_capsLock; - _RGFW->keycodes[50] = RGFW_shiftL; - _RGFW->keycodes[37] = RGFW_controlL; - _RGFW->keycodes[64] = RGFW_altL; - _RGFW->keycodes[133] = RGFW_superL; - _RGFW->keycodes[105] = RGFW_controlR; - _RGFW->keycodes[134] = RGFW_superR; - _RGFW->keycodes[62] = RGFW_shiftR; - _RGFW->keycodes[108] = RGFW_altR; - _RGFW->keycodes[67] = RGFW_F1; - _RGFW->keycodes[68] = RGFW_F2; - _RGFW->keycodes[69] = RGFW_F3; - _RGFW->keycodes[70] = RGFW_F4; - _RGFW->keycodes[71] = RGFW_F5; - _RGFW->keycodes[72] = RGFW_F6; - _RGFW->keycodes[73] = RGFW_F7; - _RGFW->keycodes[74] = RGFW_F8; - _RGFW->keycodes[75] = RGFW_F9; - _RGFW->keycodes[76] = RGFW_F10; - _RGFW->keycodes[95] = RGFW_F11; - _RGFW->keycodes[96] = RGFW_F12; - _RGFW->keycodes[111] = RGFW_up; - _RGFW->keycodes[116] = RGFW_down; - _RGFW->keycodes[113] = RGFW_left; - _RGFW->keycodes[114] = RGFW_right; - _RGFW->keycodes[118] = RGFW_insert; - _RGFW->keycodes[115] = RGFW_end; - _RGFW->keycodes[112] = RGFW_pageUp; - _RGFW->keycodes[117] = RGFW_pageDown; - _RGFW->keycodes[9] = RGFW_escape; - _RGFW->keycodes[110] = RGFW_home; - _RGFW->keycodes[78] = RGFW_scrollLock; - _RGFW->keycodes[107] = RGFW_printScreen; - _RGFW->keycodes[128] = RGFW_pause; - _RGFW->keycodes[191] = RGFW_F13; - _RGFW->keycodes[192] = RGFW_F14; - _RGFW->keycodes[193] = RGFW_F15; - _RGFW->keycodes[194] = RGFW_F16; - _RGFW->keycodes[195] = RGFW_F17; - _RGFW->keycodes[196] = RGFW_F18; - _RGFW->keycodes[197] = RGFW_F19; - _RGFW->keycodes[198] = RGFW_F20; - _RGFW->keycodes[199] = RGFW_F21; - _RGFW->keycodes[200] = RGFW_F22; - _RGFW->keycodes[201] = RGFW_F23; - _RGFW->keycodes[202] = RGFW_F24; - _RGFW->keycodes[203] = RGFW_F25; - _RGFW->keycodes[142] = RGFW_kpEqual; - _RGFW->keycodes[161] = RGFW_world1; /* non-US key #1 */ - _RGFW->keycodes[162] = RGFW_world2; /* non-US key #2 */ -} - -i32 RGFW_initPlatform(void) { -#ifdef RGFW_WAYLAND - RGFW_load_Wayland(); - i32 ret = RGFW_initPlatform_Wayland(); - if (ret == 0) { - return 0; - } else { - #ifdef RGFW_X11 - RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningWayland, "Falling back to X11"); - RGFW_useWayland(0); - #else - return ret; - #endif - } -#endif -#ifdef RGFW_X11 - RGFW_load_X11(); - return RGFW_initPlatform_X11(); -#else - return 0; -#endif -} - - -void RGFW_deinitPlatform(void) { - if (_RGFW->eventWait_forceStop[0] || _RGFW->eventWait_forceStop[1]){ - close(_RGFW->eventWait_forceStop[0]); - close(_RGFW->eventWait_forceStop[1]); - } -#ifdef RGFW_WAYLAND - if (_RGFW->useWaylandBool) { - RGFW_deinitPlatform_Wayland(); - return; - } -#endif -#ifdef RGFW_X11 - RGFW_deinitPlatform_X11(); -#endif -} - -#endif /* end of wayland or X11 defines */ - - -/* - - -Start of Linux / Unix defines - - -*/ - -#ifdef RGFW_X11 -#ifdef RGFW_WAYLAND -#define RGFW_FUNC(func) func##_X11 -#else -#define RGFW_FUNC(func) func -#endif - -#include -#include - -#include /* for data limits (mainly used in drag and drop functions) */ -#include - -void RGFW_setXInstName(const char* name) { _RGFW->instName = name; } -#if !defined(RGFW_NO_X11_CURSOR) && defined(RGFW_X11) - #include -#endif - -#ifndef RGFW_NO_DPI - #include - #include -#endif - -#include -#include -#include - -#include /* for converting keycode to string */ -#include /* for hiding */ -#include -#include -#include - -#ifdef RGFW_OPENGL - #ifndef __gl_h_ - #define __gl_h_ - #define RGFW_gl_ndef - #define GLubyte unsigned char - #define GLenum unsigned int - #define GLint int - #define GLuint unsigned int - #define GLsizei int - #define GLfloat float - #define GLvoid void - #define GLbitfield unsigned int - #define GLintptr ptrdiff_t - #define GLsizeiptr ptrdiff_t - #define GLboolean unsigned char - #endif - - #include /* GLX defs, xlib.h, gl.h */ - #ifndef GLX_MESA_swap_control - #define GLX_MESA_swap_control - #endif - - #ifdef RGFW_gl_ndef - #undef __gl_h_ - #undef GLubyte - #undef GLenum - #undef GLint - #undef GLuint - #undef GLsizei - #undef GLfloat - #undef GLvoid - #undef GLbitfield - #undef GLintptr - #undef GLsizeiptr - #undef GLboolean - #endif - typedef GLXContext(*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); -#endif - -/* atoms needed for drag and drop */ -#if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) - typedef XcursorImage* (*PFN_XcursorImageCreate)(int, int); - typedef void (*PFN_XcursorImageDestroy)(XcursorImage*); - typedef Cursor(*PFN_XcursorImageLoadCursor)(Display*, const XcursorImage*); -#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_EXT_PRELOAD) - typedef void (* PFN_XSyncIntToValue)(XSyncValue*, int); - PFN_XSyncIntToValue XSyncIntToValueSRC = NULL; - #define XSyncIntToValue XSyncIntToValueSRC - - typedef Status (* PFN_XSyncSetCounter)(Display*, XSyncCounter, XSyncValue); - PFN_XSyncSetCounter XSyncSetCounterSRC = NULL; - #define XSyncSetCounter XSyncSetCounterSRC - - typedef XSyncCounter (* PFN_XSyncCreateCounter)(Display*, XSyncValue); - PFN_XSyncCreateCounter XSyncCreateCounterSRC = NULL; - #define XSyncCreateCounter XSyncCreateCounterSRC - - typedef void (* PFN_XShapeCombineMask)(Display*,Window,int,int,int,Pixmap,int); - PFN_XShapeCombineMask XShapeCombineMaskSRC; - #define XShapeCombineMask XShapeCombineMaskSRC - - typedef void (* PFN_XShapeCombineRegion)(Display*,Window,int,int,int,Region,int); - PFN_XShapeCombineRegion XShapeCombineRegionSRC; - #define XShapeCombineRegion XShapeCombineRegionSRC - void* X11XEXThandle = NULL; -#endif - -#if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) - PFN_XcursorImageLoadCursor XcursorImageLoadCursorSRC = NULL; - PFN_XcursorImageCreate XcursorImageCreateSRC = NULL; - PFN_XcursorImageDestroy XcursorImageDestroySRC = NULL; - - #define XcursorImageLoadCursor XcursorImageLoadCursorSRC - #define XcursorImageCreate XcursorImageCreateSRC - #define XcursorImageDestroy XcursorImageDestroySRC - - void* X11Cursorhandle = NULL; -#endif - -void* RGFW_getDisplay_X11(void) { return _RGFW->display; } -u64 RGFW_window_getWindow_X11(RGFW_window* win) { return (u64)win->src.window; } - -RGFWDEF RGFW_format RGFW_XImage_getFormat(XImage* image); -RGFW_format RGFW_XImage_getFormat(XImage* image) { - switch (image->bits_per_pixel) { - case 24: - if (image->red_mask == 0xFF0000 && image->green_mask == 0x00FF00 && image->blue_mask == 0x0000FF) - return RGFW_formatRGB8; - if (image->red_mask == 0x0000FF && image->green_mask == 0x00FF00 && image->blue_mask == 0xFF0000) - return RGFW_formatBGR8; - break; - case 32: - if (image->red_mask == 0x00FF0000 && image->green_mask == 0x0000FF00 && image->blue_mask == 0x000000FF) - return RGFW_formatBGRA8; - if (image->red_mask == 0x000000FF && image->green_mask == 0x0000FF00 && image->blue_mask == 0x00FF0000) - return RGFW_formatRGBA8; - if (image->red_mask == 0x0000FF00 && image->green_mask == 0x00FF0000 && image->blue_mask == 0xFF000000) - return RGFW_formatABGR8; - if (image->red_mask == 0x00FF0000 && image->green_mask == 0x0000FF00 && image->blue_mask == 0x000000FF) - return RGFW_formatARGB8; /* ambiguous without alpha */ - break; - } - return RGFW_formatARGB8; -} - -RGFW_bool RGFW_window_createSurfacePtr(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { - RGFW_ASSERT(surface != NULL); - surface->data = data; - surface->w = w; - surface->h = h; - surface->format = format; - - XWindowAttributes attrs; - if (XGetWindowAttributes(_RGFW->display, win->src.window, &attrs) == 0) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, "Failed to get window attributes."); - return RGFW_FALSE; - } - - surface->native.bitmap = XCreateImage(_RGFW->display, attrs.visual, (u32)attrs.depth, - ZPixmap, 0, NULL, (u32)surface->w, (u32)surface->h, 32, 0); - - surface->native.buffer = (u8*)RGFW_ALLOC((size_t)(w * h * 4)); - surface->native.format = RGFW_XImage_getFormat(surface->native.bitmap); - - if (surface->native.bitmap == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, "Failed to create XImage."); - return RGFW_FALSE; - } - - surface->native.format = RGFW_formatBGRA8; - return RGFW_TRUE; -} - -RGFW_bool RGFW_FUNC(RGFW_createSurfacePtr) (u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { - return RGFW_window_createSurfacePtr(_RGFW->root, data, w, h, format, surface); -} - -void RGFW_FUNC(RGFW_window_blitSurface) (RGFW_window* win, RGFW_surface* surface) { - RGFW_ASSERT(surface != NULL); - surface->native.bitmap->data = (char*)surface->native.buffer; - RGFW_copyImageData((u8*)surface->native.buffer, surface->w, RGFW_MIN(win->h, surface->h), surface->native.format, surface->data, surface->format); - - XPutImage(_RGFW->display, win->src.window, win->src.gc, surface->native.bitmap, 0, 0, 0, 0, (u32)RGFW_MIN(win->w, surface->w), (u32)RGFW_MIN(win->h, surface->h)); - surface->native.bitmap->data = NULL; - return; -} - -void RGFW_FUNC(RGFW_surface_freePtr) (RGFW_surface* surface) { - RGFW_ASSERT(surface != NULL); - RGFW_FREE(surface->native.buffer); - XDestroyImage(surface->native.bitmap); - return; -} - -#define RGFW_LOAD_ATOM(name) \ - static Atom name = 0; \ - if (name == 0) name = XInternAtom(_RGFW->display, #name, False); - -void RGFW_FUNC(RGFW_window_setBorder) (RGFW_window* win, RGFW_bool border) { - RGFW_setBit(&win->internal.flags, RGFW_windowNoBorder, !border); - RGFW_LOAD_ATOM(_MOTIF_WM_HINTS); - - struct __x11WindowHints { - unsigned long flags, functions, decorations, status; - long input_mode; - } hints; - hints.flags = 2; - hints.decorations = border; - - XChangeProperty(_RGFW->display, win->src.window, _MOTIF_WM_HINTS, _MOTIF_WM_HINTS, 32, PropModeReplace, (u8*)&hints, 5); - - if (RGFW_window_isHidden(win) == 0) { - RGFW_window_hide(win); - RGFW_window_show(win); - } -} - -void RGFW_FUNC(RGFW_releaseCursor) (RGFW_window* win) { - RGFW_UNUSED(win); - XUngrabPointer(_RGFW->display, CurrentTime); - - /* disable raw input */ - unsigned char mask[] = { 0 }; - XIEventMask em; - em.deviceid = XIAllMasterDevices; - em.mask_len = sizeof(mask); - em.mask = mask; - - XISelectEvents(_RGFW->display, XDefaultRootWindow(_RGFW->display), &em, 1); -} - -void RGFW_FUNC(RGFW_captureCursor) (RGFW_window* win) { - /* enable raw input */ - unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; - XISetMask(mask, XI_RawMotion); - - XIEventMask em; - em.deviceid = XIAllMasterDevices; - em.mask_len = sizeof(mask); - em.mask = mask; - - XISelectEvents(_RGFW->display, XDefaultRootWindow(_RGFW->display), &em, 1); - - unsigned int event_mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask; - XGrabPointer(_RGFW->display, win->src.window, False, event_mask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); - RGFW_window_moveMouse(win, win->x + (i32)(win->w / 2), win->y + (i32)(win->h / 2)); -} - -#define RGFW_LOAD_LIBRARY(x, lib) if (x == NULL) x = dlopen(lib, RTLD_LAZY | RTLD_LOCAL) -#define RGFW_PROC_DEF(proc, name) if (name##SRC == NULL && proc != NULL) { \ - void* ptr = dlsym(proc, #name); \ - if (ptr != NULL) RGFW_MEMCPY(&name##SRC, &ptr, sizeof(PFN_##name)); \ -} - -RGFWDEF void RGFW_window_getVisual(XVisualInfo* visual, RGFW_bool transparent); -void RGFW_window_getVisual(XVisualInfo* visual, RGFW_bool transparent) { - visual->visual = DefaultVisual(_RGFW->display, DefaultScreen(_RGFW->display)); - visual->depth = DefaultDepth(_RGFW->display, DefaultScreen(_RGFW->display)); - if (transparent) { - XMatchVisualInfo(_RGFW->display, DefaultScreen(_RGFW->display), 32, TrueColor, visual); /*!< for RGBA backgrounds */ - if (visual->depth != 32) - RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, "Failed to load a 32-bit depth."); - } -} - -RGFWDEF int RGFW_XErrorHandler(Display* display, XErrorEvent* ev); -int RGFW_XErrorHandler(Display* display, XErrorEvent* ev) { - char errorText[512]; - XGetErrorText(display, ev->error_code, errorText, sizeof(errorText)); - - char buf[1024]; - RGFW_SNPRINTF(buf, sizeof(buf), "[X Error] %s\n Error code: %d\n Request code: %d\n Minor code: %d\n Serial: %lu\n", - errorText, - ev->error_code, ev->request_code, ev->minor_code, ev->serial); - - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errX11, buf); - _RGFW->x11Error = ev; - return 0; -} - -void RGFW_XCreateWindow (XVisualInfo visual, const char* name, RGFW_windowFlags flags, RGFW_window* win) { - i64 event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | FocusChangeMask | - LeaveWindowMask | EnterWindowMask | ExposureMask | VisibilityChangeMask | PropertyChangeMask; - - /* make X window attrubutes */ - XSetWindowAttributes swa; - RGFW_MEMSET(&swa, 0, sizeof(swa)); - - win->src.parent = DefaultRootWindow(_RGFW->display); - - Colormap cmap; - swa.colormap = cmap = XCreateColormap(_RGFW->display, - win->src.parent, - visual.visual, AllocNone); - swa.event_mask = event_mask; - swa.background_pixmap = None; - - /* create the window */ - win->src.window = XCreateWindow(_RGFW->display, win->src.parent, win->x, win->y, (u32)win->w, (u32)win->h, - 0, visual.depth, InputOutput, visual.visual, - CWBorderPixel | CWColormap | CWEventMask, &swa); - - XFreeColors(_RGFW->display, cmap, NULL, 0, 0); - - XSaveContext(_RGFW->display, win->src.window, _RGFW->context, (XPointer)win); - - win->src.gc = XCreateGC(_RGFW->display, win->src.window, 0, NULL); - - /* In your .desktop app, if you set the property - StartupWMClass=RGFW that will assoicate the launcher icon - with your application - robrohan */ - if (_RGFW->className == NULL) - _RGFW->className = (char*)name; - - XClassHint hint; - hint.res_class = (char*)_RGFW->className; - if (_RGFW->instName == NULL) hint.res_name = (char*)name; - else hint.res_name = (char*)_RGFW->instName; - XSetClassHint(_RGFW->display, win->src.window, &hint); - - #ifndef RGFW_NO_MONITOR - if (flags & RGFW_windowScaleToMonitor) - RGFW_window_scaleToMonitor(win); - #endif - XSelectInput(_RGFW->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 */ - RGFW_LOAD_ATOM(WM_DELETE_WINDOW); - XSetWMProtocols(_RGFW->display, (Drawable) win->src.window, &WM_DELETE_WINDOW, 1); - /* set the background */ - RGFW_window_setName(win, name); - - XMoveWindow(_RGFW->display, (Drawable) win->src.window, win->x, win->y); /*!< move the window to it's proper cords */ - - if (flags & RGFW_windowAllowDND) { /* init drag and drop atoms and turn on drag and drop for this window */ - win->internal.flags |= RGFW_windowAllowDND; - - /* actions */ - Atom XdndAware = XInternAtom(_RGFW->display, "XdndAware", False); - const u8 version = 5; - - XChangeProperty(_RGFW->display, win->src.window, - XdndAware, 4, 32, - PropModeReplace, &version, 1); /*!< turns on drag and drop */ - } - -#ifdef RGFW_ADVANCED_SMOOTH_RESIZE - RGFW_LOAD_ATOM(_NET_WM_SYNC_REQUEST_COUNTER) - RGFW_LOAD_ATOM(_NET_WM_SYNC_REQUEST) - - Atom protcols[2] = {_NET_WM_SYNC_REQUEST, WM_DELETE_WINDOW}; - XSetWMProtocols(_RGFW->display, win->src.window, protcols, 2); - - XSyncValue initial_value; - XSyncIntToValue(&initial_value, 0); - win->src.counter = XSyncCreateCounter(_RGFW->display, initial_value); - - XChangeProperty(_RGFW->display, win->src.window, _NET_WM_SYNC_REQUEST_COUNTER, XA_CARDINAL, 32, PropModeReplace, (uint8_t*)&win->src.counter, 1); -#endif - - win->src.x = win->x; - win->src.y = win->y; - win->src.w = win->w; - win->src.h = win->h; - - XSetWindowBackground(_RGFW->display, win->src.window, None); - XClearWindow(_RGFW->display, win->src.window); - - /* stupid hack to make resizing the window less bad */ - XSetWindowBackgroundPixmap(_RGFW->display, win->src.window, None); -} - -RGFW_window* RGFW_FUNC(RGFW_createWindowPlatform) (const char* name, RGFW_windowFlags flags, RGFW_window* win) { - if ((flags & RGFW_windowOpenGL) || (flags & RGFW_windowEGL)) { - win->src.window = 0; - return win; - } - - XVisualInfo visual; - RGFW_window_getVisual(&visual, RGFW_BOOL(win->internal.flags & RGFW_windowTransparent)); - RGFW_XCreateWindow(visual, name, flags, win); - return win; /*return newly created window */ -} - -RGFW_bool RGFW_FUNC(RGFW_getGlobalMouse) (i32* fX, i32* fY) { - RGFW_init(); - i32 x, y; - u32 z; - Window window1, window2; - XQueryPointer(_RGFW->display, XDefaultRootWindow(_RGFW->display), &window1, &window2, fX, fY, &x, &y, &z); - return RGFW_TRUE; -} - -RGFWDEF void RGFW_XHandleClipboardSelection(XEvent* event); -void RGFW_XHandleClipboardSelection(XEvent* event) { RGFW_UNUSED(event); - RGFW_LOAD_ATOM(ATOM_PAIR); - RGFW_LOAD_ATOM(MULTIPLE); - RGFW_LOAD_ATOM(TARGETS); - RGFW_LOAD_ATOM(SAVE_TARGETS); - RGFW_LOAD_ATOM(UTF8_STRING); - - const XSelectionRequestEvent* request = &event->xselectionrequest; - Atom formats[2] = {0}; - formats[0] = UTF8_STRING; - formats[1] = XA_STRING; - const int formatCount = sizeof(formats) / sizeof(formats[0]); - - if (request->target == TARGETS) { - Atom targets[4] = {0}; - targets[0] = TARGETS; - targets[1] = MULTIPLE; - targets[2] = UTF8_STRING; - targets[3] = XA_STRING; - - XChangeProperty(_RGFW->display, request->requestor, request->property, - XA_ATOM, 32, PropModeReplace, (u8*) targets, sizeof(targets) / sizeof(Atom)); - } else if (request->target == MULTIPLE) { - Atom* targets = NULL; - - Atom actualType = 0; - int actualFormat = 0; - unsigned long count = 0, bytesAfter = 0; - - XGetWindowProperty(_RGFW->display, request->requestor, request->property, 0, LONG_MAX, - False, ATOM_PAIR, &actualType, &actualFormat, &count, &bytesAfter, (u8**) &targets); - - unsigned long i; - for (i = 0; i < (u32)count; i += 2) { - if (targets[i] == UTF8_STRING || targets[i] == XA_STRING) - XChangeProperty(_RGFW->display, request->requestor, targets[i + 1], targets[i], - 8, PropModeReplace, (const unsigned char *)_RGFW->clipboard, (i32)_RGFW->clipboard_len); - else - targets[i + 1] = None; - } - - XChangeProperty(_RGFW->display, - request->requestor, request->property, ATOM_PAIR, 32, - PropModeReplace, (u8*) targets, (i32)count); - - XFlush(_RGFW->display); - XFree(targets); - } else if (request->target == SAVE_TARGETS) - XChangeProperty(_RGFW->display, request->requestor, request->property, 0, 32, PropModeReplace, NULL, 0); - else { - int i; - for (i = 0; i < formatCount; i++) { - if (request->target != formats[i]) - continue; - XChangeProperty(_RGFW->display, request->requestor, request->property, request->target, - 8, PropModeReplace, (u8*) _RGFW->clipboard, (i32)_RGFW->clipboard_len); - } - } - - XEvent reply = { SelectionNotify }; - reply.xselection.property = request->property; - reply.xselection.display = request->display; - reply.xselection.requestor = request->requestor; - reply.xselection.selection = request->selection; - reply.xselection.target = request->target; - reply.xselection.time = request->time; - - XSendEvent(_RGFW->display, request->requestor, False, 0, &reply); - XFlush(_RGFW->display); -} - i32 RGFW_XHandleClipboardSelectionHelper(void); -u8 RGFW_FUNC(RGFW_rgfwToKeyChar) (u32 key) { - u32 keycode = RGFW_rgfwToApiKey(key); - Window root = DefaultRootWindow(_RGFW->display); +u8 RGFW_rgfwToKeyChar(u32 key) { + u32 keycode = RGFW_rgfwToApiKey(key); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + Window root = DefaultRootWindow(_RGFW.display); Window ret_root, ret_child; int root_x, root_y, win_x, win_y; unsigned int mask; - XQueryPointer(_RGFW->display, root, &ret_root, &ret_child, &root_x, &root_y, &win_x, &win_y, &mask); - KeySym sym = (KeySym)XkbKeycodeToKeysym(_RGFW->display, (KeyCode)keycode, 0, (KeyCode)mask & ShiftMask ? 1 : 0); + XQueryPointer(_RGFW.display, root, &ret_root, &ret_child, &root_x, &root_y, &win_x, &win_y, &mask); + KeySym sym = (KeySym)XkbKeycodeToKeysym(_RGFW.display, (KeyCode)keycode, 0, (KeyCode)mask & ShiftMask ? 1 : 0); if ((mask & LockMask) && sym >= XK_a && sym <= XK_z) sym = (mask & ShiftMask) ? sym + 32 : sym - 32; @@ -5561,10 +4516,25 @@ u8 RGFW_FUNC(RGFW_rgfwToKeyChar) (u32 key) { sym = 0; return (u8)sym; +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL RGFW_UNUSED(keycode); + return (u8)key; +#endif } -RGFWDEF void RGFW_XHandleEvent(void); -void RGFW_XHandleEvent(void) { +RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { + RGFW_XHandleClipboardSelectionHelper(); + + if (win == NULL || ((win->_flags & RGFW_windowFreeOnClose) && (win->_flags & RGFW_EVENT_QUIT))) return NULL; + RGFW_event* ev = RGFW_window_checkEventCore(win); + if (ev) return ev; + + #if defined(__linux__) && !defined(RGFW_NO_LINUX) + if (RGFW_linux_updateGamepad(win)) return &win->event; + #endif + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 RGFW_LOAD_ATOM(XdndTypeList); RGFW_LOAD_ATOM(XdndSelection); RGFW_LOAD_ATOM(XdndEnter); @@ -5576,8 +4546,20 @@ void RGFW_XHandleEvent(void) { RGFW_LOAD_ATOM(XdndActionCopy); RGFW_LOAD_ATOM(_NET_WM_SYNC_REQUEST); RGFW_LOAD_ATOM(WM_PROTOCOLS); - RGFW_LOAD_ATOM(WM_STATE); - RGFW_LOAD_ATOM(_NET_WM_STATE); + XPending(win->src.display); + + XEvent E; /*!< raw X11 event */ + + /* if there is no unread qued events, get a new one */ + if ((QLength(win->src.display) || XEventsQueued(win->src.display, QueuedAlready) + XEventsQueued(win->src.display, QueuedAfterReading)) + && win->event.type != RGFW_quit + ) + XNextEvent(win->src.display, &E); + else { + return NULL; + } + + win->event.type = 0; /* xdnd data */ static Window source = 0; @@ -5585,641 +4567,550 @@ void RGFW_XHandleEvent(void) { static i32 format = 0; XEvent reply = { ClientMessage }; - XEvent E; - RGFW_event event; - RGFW_MEMSET(&event, 0, sizeof(event)); - - XNextEvent(_RGFW->display, &E); - switch (E.type) { - case SelectionRequest: - RGFW_XHandleClipboardSelection(&E); - return; - case GenericEvent: { - RGFW_window* win = _RGFW->mouseOwner; - if (win == NULL) return; - if (!(win->internal.enabledEvents & RGFW_BIT(RGFW_mousePosChanged))) return; - - /* MotionNotify is used for mouse events if the mouse isn't held */ - if (!(win->internal.holdMouse)) { - XFreeEventData(_RGFW->display, &E.xcookie); - return; - } - - XGetEventData(_RGFW->display, &E.xcookie); - if (E.xcookie.evtype == XI_RawMotion) { - XIRawEvent *raw = (XIRawEvent *)E.xcookie.data; - if (raw->valuators.mask_len == 0) { - XFreeEventData(_RGFW->display, &E.xcookie); - return; - } - - 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]; - - event.mouse.vecX = (float)deltaX; - event.mouse.vecY = (float)deltaY; - _RGFW->vectorX = (float)event.mouse.vecX; - _RGFW->vectorY = (float)event.mouse.vecY; - event.mouse.x = win->internal.lastMouseX + (i32)event.mouse.vecX; - event.mouse.y = win->internal.lastMouseY + (i32)event.mouse.vecY; - win->internal.lastMouseX = event.mouse.x; - win->internal.lastMouseY = event.mouse.y; - RGFW_window_moveMouse(win, win->x + (win->w / 2), win->y + (win->h / 2)); - - event.type = RGFW_mousePosChanged; - RGFW_mousePosCallback(win, event.mouse.x, event.mouse.y, (float)event.mouse.vecX, (float)event.mouse.vecY); - } - - XFreeEventData(_RGFW->display, &E.xcookie); - if (event.type) - RGFW_eventQueuePush(&event); - return; - } - } - - RGFW_window* win = NULL; - if (XFindContext(_RGFW->display, E.xany.window, _RGFW->context, (XPointer*) &win) != 0) { - return; - } - - event.common.win = win; - - /* - Repeated key presses are sent as a release followed by another press at the same time. - We want to convert that into a single key press event with the repeat flag set - */ - if (E.type == KeyRelease && XEventsQueued(_RGFW->display, QueuedAfterReading)) { - XEvent NE; - XPeekEvent(_RGFW->display, &NE); - if (NE.type == KeyPress && E.xkey.time == NE.xkey.time && E.xkey.keycode == NE.xkey.keycode) { - /* Use the next KeyPress event */ - XNextEvent(_RGFW->display, &E); - event.key.repeat = RGFW_TRUE; - } - } switch (E.type) { - case KeyPress: { - if (!(win->internal.enabledEvents & RGFW_keyPressedFlag)) return; - event.type = RGFW_keyPressed; - event.key.value = (u8)RGFW_apiKeyToRGFW(E.xkey.keycode); - event.key.sym = (u8)RGFW_rgfwToKeyChar(event.key.value); + case KeyPress: + case KeyRelease: { + win->event.repeat = RGFW_FALSE; + /* check if it's a real key release */ + if (E.type == KeyRelease && XEventsQueued(win->src.display, QueuedAfterReading)) { /* get next event if there is one */ + XEvent NE; + XPeekEvent(win->src.display, &NE); - _RGFW->keyboard[event.key.value].prev = _RGFW->keyboard[event.key.value].current; - _RGFW->keyboard[event.key.value].current = RGFW_TRUE; + if (E.xkey.time == NE.xkey.time && E.xkey.keycode == NE.xkey.keycode) /* check if the current and next are both the same */ + win->event.repeat = RGFW_TRUE; + } - XkbStateRec state; - XkbGetState(_RGFW->display, XkbUseCoreKbd, &state); - RGFW_updateKeyMods(win, (state.locked_mods & LockMask), (state.locked_mods & Mod2Mask), (state.locked_mods & Mod3Mask)); + /* set event key data */ + win->event.key = (u8)RGFW_apiKeyToRGFW(E.xkey.keycode); + win->event.keyChar = (u8)RGFW_rgfwToKeyChar(win->event.key); - RGFW_keyCallback(win, event.key.value, event.key.sym, win->internal.mod, event.key.repeat, RGFW_TRUE); + RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; + + /* get keystate data */ + win->event.type = (E.type == KeyPress) ? RGFW_keyPressed : RGFW_keyReleased; + + XKeyboardState keystate; + XGetKeyboardControl(win->src.display, &keystate); + + RGFW_keyboard[win->event.key].current = (E.type == KeyPress); + + XkbStateRec state; + XkbGetState(win->src.display, XkbUseCoreKbd, &state); + RGFW_updateKeyMods(win, (state.locked_mods & LockMask), (state.locked_mods & Mod2Mask), (state.locked_mods & Mod3Mask)); + + RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, (E.type == KeyPress)); + break; + } + case ButtonPress: + case ButtonRelease: + if (E.xbutton.button > RGFW_mouseFinal) { /* skip this event */ + XFlush(win->src.display); + return RGFW_window_checkEvent(win); + } + + win->event.type = RGFW_mouseButtonPressed + (E.type == ButtonRelease); /* the events match */ + win->event.button = (u8)(E.xbutton.button - 1); + switch(win->event.button) { + case RGFW_mouseScrollUp: + win->event.scroll = 1; + break; + case RGFW_mouseScrollDown: + win->event.scroll = -1; + break; + default: break; + } + + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + + if (win->event.repeat == RGFW_FALSE) + win->event.repeat = RGFW_isPressed(win, win->event.key); + + 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; + + win->event.vector.x = win->event.point.x - win->_lastMousePoint.x; + win->event.vector.y = win->event.point.y - win->_lastMousePoint.y; + win->_lastMousePoint = win->event.point; + + win->event.type = RGFW_mousePosChanged; + RGFW_mousePosCallback(win, win->event.point, win->event.vector); + break; + + case GenericEvent: { + /* MotionNotify is used for mouse events if the mouse isn't held */ + if (!(win->_flags & RGFW_HOLD_MOUSE)) { + XFreeEventData(win->src.display, &E.xcookie); break; } - case KeyRelease: { - if (!(win->internal.enabledEvents & RGFW_keyReleasedFlag)) return; - event.type = RGFW_keyReleased; - event.key.value = (u8)RGFW_apiKeyToRGFW(E.xkey.keycode); - event.key.sym = (u8)RGFW_rgfwToKeyChar(event.key.value); - - /* get keystate data */ - _RGFW->keyboard[event.key.value].prev = _RGFW->keyboard[event.key.value].current; - _RGFW->keyboard[event.key.value].current = RGFW_FALSE; - - XkbStateRec state; - XkbGetState(_RGFW->display, XkbUseCoreKbd, &state); - RGFW_updateKeyMods(win, (state.locked_mods & LockMask), (state.locked_mods & Mod2Mask), (state.locked_mods & Mod3Mask)); - - RGFW_keyCallback(win, event.key.value, event.key.sym, win->internal.mod, event.key.repeat, RGFW_FALSE); - break; - } - case ButtonPress: - if (E.xbutton.button >= Button4 && E.xbutton.button <= 7) { - if (!(win->internal.enabledEvents & RGFW_mouseScrollFlag)) return; - event.type = RGFW_mouseScroll; - } else { - if (!(win->internal.enabledEvents & RGFW_mouseButtonPressedFlag) || E.xbutton.button > RGFW_mouseFinal) return; - event.type = RGFW_mouseButtonPressed; - } - - switch(E.xbutton.button) { - case Button1: event.button.value = RGFW_mouseLeft; break; - case Button2: event.button.value = RGFW_mouseMiddle; break; - case Button3: event.button.value = RGFW_mouseRight; break; - case Button4: event.scroll.y = 1.0; break; - case Button5: event.scroll.y = -1.0; break; - case 6: event.scroll.x = 1.0f; break; - case 7: event.scroll.x = -1.0f; break; - default: - event.button.value = (u8)E.xbutton.button - Button1 - 4; - break; - } - - if (event.type == RGFW_mouseScroll) { - _RGFW->scrollX = event.scroll.x; - _RGFW->scrollY = event.scroll.y; - RGFW_mouseScrollCallback(win, event.scroll.x, event.scroll.y); + 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; } - _RGFW->mouseButtons[event.button.value].prev = _RGFW->mouseButtons[event.button.value].current; - _RGFW->mouseButtons[event.button.value].current = RGFW_TRUE; - RGFW_mouseButtonCallback(win, event.button.value, RGFW_TRUE); - break; - case ButtonRelease: - if (E.xbutton.button >= Button4 && E.xbutton.button <= 7) break; - if (!(win->internal.enabledEvents & RGFW_mouseButtonReleasedFlag) || E.xbutton.button > RGFW_mouseFinal) return; - event.type = RGFW_mouseButtonReleased; - switch(E.xbutton.button) { - case Button1: event.button.value = RGFW_mouseLeft; break; - case Button2: event.button.value = RGFW_mouseMiddle; break; - case Button3: event.button.value = RGFW_mouseRight; break; - default: - event.button.value = (u8)E.xbutton.button - Button1 - 4; - break; - } + double deltaX = 0.0f; + double deltaY = 0.0f; - _RGFW->mouseButtons[event.button.value].prev = _RGFW->mouseButtons[event.button.value].current; - _RGFW->mouseButtons[event.button.value].current = RGFW_FALSE; - RGFW_mouseButtonCallback(win, event.button.value, RGFW_FALSE); - break; - case MotionNotify: - if (win->internal.holdMouse) return; - if (!(win->internal.enabledEvents & RGFW_mousePosChangedFlag)) return; - event.mouse.x = E.xmotion.x; - event.mouse.y = E.xmotion.y; + /* 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]; - event.mouse.vecX = (float)(event.mouse.x - win->internal.lastMouseX); - event.mouse.vecY = (float)(event.mouse.y - win->internal.lastMouseY); - _RGFW->vectorX = event.mouse.vecX; - _RGFW->vectorY = event.mouse.vecY; - win->internal.lastMouseX = event.mouse.x; - win->internal.lastMouseY = event.mouse.y; - event.type = RGFW_mousePosChanged; - RGFW_mousePosCallback(win, event.mouse.x, event.mouse.y, (float)event.mouse.vecX, (float)event.mouse.vecY); - break; + win->event.vector = RGFW_POINT((i32)deltaX, (i32)deltaY); + win->event.point.x = win->_lastMousePoint.x + win->event.vector.x; + win->event.point.y = win->_lastMousePoint.y + win->event.vector.y; + win->_lastMousePoint = win->event.point; - case Expose: { - if (!(win->internal.enabledEvents & RGFW_windowRefreshFlag)) return; - event.type = RGFW_windowRefresh; - RGFW_windowRefreshCallback(win); + RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); + + win->event.type = RGFW_mousePosChanged; + RGFW_mousePosCallback(win, win->event.point, win->event.vector); + } + + XFreeEventData(win->src.display, &E.xcookie); + break; + } + + case Expose: { + win->event.type = RGFW_windowRefresh; + RGFW_windowRefreshCallback(win); #ifdef RGFW_ADVANCED_SMOOTH_RESIZE - XSyncValue value; - XSyncIntToValue(&value, (i32)win->src.counter_value); - XSyncSetCounter(_RGFW->display, win->src.counter, value); + XSyncValue value; + XSyncIntToValue(&value, (i32)win->src.counter_value); + XSyncSetCounter(win->src.display, win->src.counter, value); #endif + break; + } + case MapNotify: case UnmapNotify: RGFW_window_checkMode(win); break; + case ClientMessage: { + /* if the client closed the window */ + if (E.xclient.data.l[0] == (long)wm_delete_window) { + win->event.type = RGFW_quit; + RGFW_window_setShouldClose(win, RGFW_TRUE); + RGFW_windowQuitCallback(win); break; } - - case PropertyNotify: - if (E.xproperty.state != PropertyNewValue) break; - - if (E.xproperty.atom == WM_STATE) { - if (RGFW_window_isMinimized(win) && !(win->internal.flags & RGFW_windowMinimized)) { - win->internal.flags |= RGFW_windowMinimize; - RGFW_eventQueuePushEx(e.type = RGFW_windowMinimized; e.common.win = win); - RGFW_windowMinimizedCallback(win); - break; - } - } else if (E.xproperty.atom == _NET_WM_STATE) { - if (!(win->internal.flags & RGFW_windowMaximize)) { - win->internal.flags |= RGFW_windowMaximize; - RGFW_eventQueuePushEx(e.type = RGFW_windowMaximized; e.common.win = win); - RGFW_windowMaximizedCallback(win, win->x, win->y, win->w, win->h); - break; - } - } - - RGFW_window_checkMode(win); - break; - case MapNotify: case UnmapNotify: RGFW_window_checkMode(win); break; - case ClientMessage: { - RGFW_LOAD_ATOM(WM_DELETE_WINDOW); - /* if the client closed the window */ - if (E.xclient.data.l[0] == (long)WM_DELETE_WINDOW) { - event.type = RGFW_quit; - RGFW_window_setShouldClose(win, RGFW_TRUE); - RGFW_windowQuitCallback(win); - break; - } #ifdef RGFW_ADVANCED_SMOOTH_RESIZE - if (E.xclient.message_type == WM_PROTOCOLS && (Atom)E.xclient.data.l[0] == _NET_WM_SYNC_REQUEST) { - RGFW_windowRefreshCallback(win); - win->src.counter_value = 0; - win->src.counter_value |= E.xclient.data.l[2]; - win->src.counter_value |= (E.xclient.data.l[3] << 32); + if (E.xclient.message_type == WM_PROTOCOLS && (Atom)E.xclient.data.l[0] == _NET_WM_SYNC_REQUEST) { + RGFW_windowRefreshCallback(win); + win->src.counter_value = 0; + win->src.counter_value |= E.xclient.data.l[2]; + win->src.counter_value |= (E.xclient.data.l[3] << 32); - XSyncValue value; - XSyncIntToValue(&value, (i32)win->src.counter_value); - XSyncSetCounter(_RGFW->display, win->src.counter, value); - break; - } + XSyncValue value; + XSyncIntToValue(&value, (i32)win->src.counter_value); + XSyncSetCounter(win->src.display, win->src.counter, value); + break; + } #endif - if ((win->internal.flags & RGFW_windowAllowDND) == 0) - return; + if ((win->_flags & RGFW_windowAllowDND) == 0) + break; - reply.xclient.window = source; - reply.xclient.format = 32; - reply.xclient.data.l[0] = (long)win->src.window; - reply.xclient.data.l[1] = 0; - reply.xclient.data.l[2] = None; - - if (E.xclient.message_type == XdndEnter) { - if (version > 5) - break; - - unsigned long count; - Atom* formats; - Atom real_formats[6]; - Bool list = E.xclient.data.l[1] & 1; - - source = (unsigned long int)E.xclient.data.l[0]; - version = E.xclient.data.l[1] >> 24; - format = None; - if (list) { - Atom actualType; - i32 actualFormat; - unsigned long bytesAfter; - - XGetWindowProperty( - _RGFW->display, source, XdndTypeList, - 0, LONG_MAX, False, 4, - &actualType, &actualFormat, &count, &bytesAfter, (u8**)&formats - ); - } else { - count = 0; - - size_t i; - for (i = 2; i < 5; i++) { - if (E.xclient.data.l[i] != None) { - real_formats[count] = (unsigned long int)E.xclient.data.l[i]; - count += 1; - } - } - - formats = real_formats; - } - - Atom XtextPlain = XInternAtom(_RGFW->display, "text/plain", False); - Atom XtextUriList = XInternAtom(_RGFW->display, "text/uri-list", False); - - size_t i; - for (i = 0; i < count; i++) { - if (formats[i] == XtextUriList || formats[i] == XtextPlain) { - format = (int)formats[i]; - break; - } - } - - if (list) { - XFree(formats); - } + reply.xclient.window = source; + reply.xclient.format = 32; + reply.xclient.data.l[0] = (long)win->src.window; + reply.xclient.data.l[1] = 0; + reply.xclient.data.l[2] = None; + if (E.xclient.message_type == XdndEnter) { + if (version > 5) break; - } - if (E.xclient.message_type == XdndPosition) { - const i32 xabs = (E.xclient.data.l[2] >> 16) & 0xffff; - const i32 yabs = (E.xclient.data.l[2]) & 0xffff; - Window dummy; - i32 xpos, ypos; + unsigned long count; + Atom* formats; + Atom real_formats[6]; + Bool list = E.xclient.data.l[1] & 1; - if (version > 5) - break; + source = (unsigned long int)E.xclient.data.l[0]; + version = E.xclient.data.l[1] >> 24; + format = None; + if (list) { + Atom actualType; + i32 actualFormat; + unsigned long bytesAfter; - XTranslateCoordinates( - _RGFW->display, XDefaultRootWindow(_RGFW->display), win->src.window, - xabs, yabs, &xpos, &ypos, &dummy + XGetWindowProperty( + win->src.display, source, XdndTypeList, + 0, LONG_MAX, False, 4, + &actualType, &actualFormat, &count, &bytesAfter, (u8**)&formats ); + } else { + count = 0; - event.drag.x = xpos; - event.drag.y = ypos; - - reply.xclient.window = source; - reply.xclient.message_type = XdndStatus; - - if (format) { - reply.xclient.data.l[1] = 1; - if (version >= 2) - reply.xclient.data.l[4] = (long)XdndActionCopy; + size_t i; + for (i = 2; i < 5; i++) { + if (E.xclient.data.l[i] != None) { + real_formats[count] = (unsigned long int)E.xclient.data.l[i]; + count += 1; + } } - XSendEvent(_RGFW->display, source, False, NoEventMask, &reply); - XFlush(_RGFW->display); - break; + formats = real_formats; } - if (E.xclient.message_type != XdndDrop) - break; + + size_t i; + for (i = 0; i < count; i++) { + if (formats[i] == XtextUriList || formats[i] == XtextPlain) { + format = (int)formats[i]; + break; + } + } + + if (list) { + XFree(formats); + } + + break; + } + + if (E.xclient.message_type == XdndPosition) { + const i32 xabs = (E.xclient.data.l[2] >> 16) & 0xffff; + const i32 yabs = (E.xclient.data.l[2]) & 0xffff; + Window dummy; + i32 xpos, ypos; if (version > 5) break; - event.type = RGFW_dataDrag; + XTranslateCoordinates( + win->src.display, XDefaultRootWindow(win->src.display), win->src.window, + xabs, yabs, &xpos, &ypos, &dummy + ); + + win->event.point.x = xpos; + win->event.point.y = ypos; + + reply.xclient.window = source; + reply.xclient.message_type = XdndStatus; if (format) { - Time time = (version >= 1) - ? (Time)E.xclient.data.l[2] - : CurrentTime; - - XConvertSelection( - _RGFW->display, XdndSelection, (Atom)format, - XdndSelection, win->src.window, time - ); - } else if (version >= 2) { - XEvent new_reply = { ClientMessage }; - - XSendEvent(_RGFW->display, source, False, NoEventMask, &new_reply); - XFlush(_RGFW->display); + reply.xclient.data.l[1] = 1; + if (version >= 2) + reply.xclient.data.l[4] = (long)XdndActionCopy; } - _RGFW->windowState.win = win; - _RGFW->windowState.dataDragging = RGFW_TRUE; - _RGFW->windowState.dropX = event.drag.x; - _RGFW->windowState.dropY = event.drag.y; + XSendEvent(win->src.display, source, False, NoEventMask, &reply); + XFlush(win->src.display); + break; + } + if (E.xclient.message_type != XdndDrop) + break; - if (win->internal.enabledEvents & RGFW_dataDragFlag) return; - RGFW_dataDragCallback(win, event.drag.x, event.drag.y); - } break; - case SelectionNotify: { - /* this is only for checking for xdnd drops */ - if (!(win->internal.enabledEvents & RGFW_dataDropFlag) || E.xselection.property != XdndSelection || !(win->internal.flags & RGFW_windowAllowDND)) - return; - char* data; - unsigned long result; + if (version > 5) + break; - Atom actualType; - i32 actualFormat; - unsigned long bytesAfter; + size_t i; + for (i = 0; i < win->event.droppedFilesCount; i++) + win->event.droppedFiles[i][0] = '\0'; - XGetWindowProperty(_RGFW->display, E.xselection.requestor, E.xselection.property, 0, LONG_MAX, False, E.xselection.target, &actualType, &actualFormat, &result, &bytesAfter, (u8**) &data); + win->event.droppedFilesCount = 0; - if (result == 0) - break; - const char* prefix = (const char*)"file://"; + win->event.type = RGFW_DNDInit; - char* line; + if (format) { + Time time = (version >= 1) + ? (Time)E.xclient.data.l[2] + : CurrentTime; - event.drop.files = _RGFW->files; - event.drop.count = 0; - event.type = RGFW_dataDrop; + XConvertSelection( + win->src.display, XdndSelection, (Atom)format, + XdndSelection, win->src.window, time + ); + } else if (version >= 2) { + XEvent new_reply = { ClientMessage }; - while ((line = (char*)RGFW_strtok(data, "\r\n"))) { - char path[RGFW_MAX_PATH]; + XSendEvent(win->src.display, source, False, NoEventMask, &new_reply); + XFlush(win->src.display); + } - data = NULL; + RGFW_dndInitCallback(win, win->event.point); + } break; + case SelectionRequest: + RGFW_XHandleClipboardSelection(&E); + XFlush(win->src.display); + return RGFW_window_checkEvent(win); + case SelectionNotify: { + /* this is only for checking for xdnd drops */ + if (E.xselection.property != XdndSelection || !(win->_flags & RGFW_windowAllowDND)) + break; + char* data; + unsigned long result; - if (line[0] == '#') - continue; + Atom actualType; + i32 actualFormat; + unsigned long bytesAfter; - char* l; - for (l = line; 1; l++) { - if ((l - line) > 7) - break; - else if (*l != prefix[(l - line)]) - break; - else if (*l == '\0' && prefix[(l - line)] == '\0') { - line += 7; - while (*line != '/') - line++; - break; - } else if (*l == '\0') - break; - } + XGetWindowProperty(win->src.display, E.xselection.requestor, E.xselection.property, 0, LONG_MAX, False, E.xselection.target, &actualType, &actualFormat, &result, &bytesAfter, (u8**) &data); - event.drop.count++; + if (result == 0) + break; - size_t index = 0; - while (*line) { - if (line[0] == '%' && line[1] && line[2]) { - char digits[3] = {0}; - digits[0] = line[1]; - digits[1] = line[2]; - digits[2] = '\0'; - path[index] = (char) RGFW_STRTOL(digits, NULL, 16); - line += 2; - } else + const char* prefix = (const char*)"file://"; + + char* line; + + win->event.droppedFilesCount = 0; + win->event.type = RGFW_DND; + + while ((line = (char*)RGFW_strtok(data, "\r\n"))) { + char path[RGFW_MAX_PATH]; + + data = NULL; + + if (line[0] == '#') + continue; + + char* l; + for (l = line; 1; l++) { + if ((l - line) > 7) + break; + else if (*l != prefix[(l - line)]) + break; + else if (*l == '\0' && prefix[(l - line)] == '\0') { + line += 7; + while (*line != '/') + line++; + break; + } else if (*l == '\0') + break; + } + + win->event.droppedFilesCount++; + + size_t index = 0; + while (*line) { + if (line[0] == '%' && line[1] && line[2]) { + const char digits[3] = { line[1], line[2], '\0' }; + path[index] = (char) RGFW_STRTOL(digits, NULL, 16); + line += 2; + } else path[index] = *line; - index++; - line++; - } - path[index] = '\0'; - RGFW_MEMCPY(event.drop.files[event.drop.count - 1], path, index + 1); + index++; + line++; } - - _RGFW->windowState.win = win; - _RGFW->windowState.dataDrop = RGFW_TRUE; - _RGFW->windowState.filesCount = event.drop.count; - - RGFW_dataDropCallback(win, event.drop.files, event.drop.count); - if (data) - XFree(data); - - if (version >= 2) { - XEvent new_reply = { ClientMessage }; - new_reply.xclient.window = source; - new_reply.xclient.message_type = XdndFinished; - new_reply.xclient.format = 32; - new_reply.xclient.data.l[1] = (long int)result; - new_reply.xclient.data.l[2] = (long int)XdndActionCopy; - XSendEvent(_RGFW->display, source, False, NoEventMask, &new_reply); - XFlush(_RGFW->display); - } - break; - } - case FocusIn: - if ((win->internal.flags & RGFW_windowFullscreen)) - XMapRaised(_RGFW->display, win->src.window); - if ((win->internal.holdMouse)) RGFW_window_holdMouse(win); - - if (!(win->internal.enabledEvents & RGFW_focusInFlag)) return; - win->internal.inFocus = RGFW_TRUE; - event.type = RGFW_focusIn; - RGFW_focusCallback(win, 1); - - break; - case FocusOut: - if (!(win->internal.enabledEvents & RGFW_focusOutFlag)) return; - event.type = RGFW_focusOut; - RGFW_focusCallback(win, 0); - RGFW_window_focusLost(win); - break; - case EnterNotify: { - win->internal.mouseInside = RGFW_TRUE; - _RGFW->windowState.win = win; - _RGFW->windowState.mouseEnter = RGFW_TRUE; - - if (!(win->internal.enabledEvents & RGFW_mouseEnterFlag)) return; - event.type = RGFW_mouseEnter; - event.mouse.x = E.xcrossing.x; - event.mouse.y = E.xcrossing.y; - RGFW_mouseNotifyCallback(win, event.mouse.x, event.mouse.y, 1); - break; + path[index] = '\0'; + RGFW_MEMCPY(win->event.droppedFiles[win->event.droppedFilesCount - 1], path, index + 1); } - case LeaveNotify: { - win->internal.mouseInside = RGFW_FALSE; - _RGFW->windowState.winLeave = win; - _RGFW->windowState.mouseLeave = RGFW_TRUE; - if (!(win->internal.enabledEvents & RGFW_mouseLeaveFlag)) return; - event.type = RGFW_mouseLeave; - RGFW_mouseNotifyCallback(win, event.mouse.x, event.mouse.y, 0); - break; + RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); + if (data) + XFree(data); + + if (version >= 2) { + XEvent new_reply = { ClientMessage }; + new_reply.xclient.window = source; + new_reply.xclient.message_type = XdndFinished; + new_reply.xclient.format = 32; + new_reply.xclient.data.l[1] = (long int)result; + new_reply.xclient.data.l[2] = (long int)XdndActionCopy; + XSendEvent(win->src.display, source, False, NoEventMask, &new_reply); + XFlush(win->src.display); } - case ReparentNotify: - win->src.parent = E.xreparent.parent; - break; - case ConfigureNotify: { - /* detect resize */ - RGFW_window_checkMode(win); - if (E.xconfigure.width != win->src.w || E.xconfigure.height != win->src.h) { - win->src.w = win->w = E.xconfigure.width; - win->src.h = win->h = E.xconfigure.height; + break; + } + case FocusIn: + if ((win->_flags & RGFW_windowFullscreen)) + XMapRaised(win->src.display, win->src.window); - if (!(win->internal.enabledEvents & RGFW_windowResizedFlag)) return; - event.type = RGFW_windowResized; - RGFW_windowResizedCallback(win, win->w, win->h); - RGFW_eventQueuePush(&event); - } + win->_flags |= RGFW_windowFocus; + win->event.type = RGFW_focusIn; + RGFW_focusCallback(win, 1); - i32 x = E.xconfigure.x; - i32 y = E.xconfigure.y; - /* - if the event came from the server and we're not a direct child of the root window then - we're using local coords which need to be translated into screen coords - */ - Window root = DefaultRootWindow(_RGFW->display); - if (E.xany.send_event == 0 && win->src.parent != root) { - Window dummy = 0; - XTranslateCoordinates(_RGFW->display, win->src.parent, root, x, y, &x, &y, &dummy); - } - - /* detect move */ - if (E.xconfigure.x != win->src.x || E.xconfigure.y != win->src.y) { - win->src.x = win->x = E.xconfigure.x; - win->src.y = win->y = E.xconfigure.y; - - if (!(win->internal.enabledEvents & RGFW_windowMovedFlag)) return; - event.type = RGFW_windowMoved; - RGFW_windowMovedCallback(win, win->x, win->y); - RGFW_eventQueuePush(&event); - } - return; - } - default: - break; + if ((win->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(win, RGFW_AREA(win->r.w, win->r.h)); + break; + case FocusOut: + win->event.type = RGFW_focusOut; + RGFW_focusCallback(win, 0); + RGFW_window_focusLost(win); + break; + case PropertyNotify: RGFW_window_checkMode(win); 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; } - if (event.type) { - RGFW_eventQueuePush(&event); + case LeaveNotify: { + win->event.type = RGFW_mouseLeave; + RGFW_mouseNotifyCallback(win, win->event.point, 0); + break; } - XFlush(_RGFW->display); + case ConfigureNotify: { + /* detect resize */ + RGFW_window_checkMode(win); + if (E.xconfigure.width != win->src.r.w || E.xconfigure.height != win->src.r.h) { + win->event.type = RGFW_windowResized; + win->src.r = win->r = RGFW_RECT(win->src.r.x, win->src.r.y, E.xconfigure.width, E.xconfigure.height); + RGFW_windowResizedCallback(win, win->r); + break; + } + + /* detect move */ + if (E.xconfigure.x != win->src.r.x || E.xconfigure.y != win->src.r.y) { + win->event.type = RGFW_windowMoved; + win->src.r = win->r = RGFW_RECT(E.xconfigure.x, E.xconfigure.y, win->src.r.w, win->src.r.h); + RGFW_windowMovedCallback(win, win->r); + break; + } + + break; + } + default: + XFlush(win->src.display); + return RGFW_window_checkEvent(win); + } + XFlush(win->src.display); + if (win->event.type) return &win->event; + else return NULL; +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + if ((win->_flags & RGFW_windowHide) == 0) + wl_display_roundtrip(win->src.wl_display); + return NULL; +#endif } -void RGFW_FUNC(RGFW_pollEvents) (void) { - RGFW_resetPrevState(); - - XPending(_RGFW->display); - /* if there is no unread queued events, get a new one */ - while ((QLength(_RGFW->display) || XEventsQueued(_RGFW->display, QueuedAlready) + XEventsQueued(_RGFW->display, QueuedAfterReading))) { - RGFW_XHandleEvent(); - } -} - -void RGFW_FUNC(RGFW_window_move) (RGFW_window* win, i32 x, i32 y) { +void RGFW_window_move(RGFW_window* win, RGFW_point v) { + RGFW_ASSERT(win != NULL); + win->r.x = v.x; + win->r.y = v.y; + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + XMoveWindow(win->src.display, win->src.window, v.x, v.y); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL RGFW_ASSERT(win != NULL); - win->x = x; - win->y = y; - XMoveWindow(_RGFW->display, win->src.window, x, y); - return; + if (win->src.compositor) { + struct wl_pointer *pointer = wl_seat_get_pointer(win->src.seat); + if (!pointer) { + return; + } + + wl_display_flush(win->src.wl_display); + } +#endif } -void RGFW_FUNC(RGFW_window_resize) (RGFW_window* win, i32 w, i32 h) { +void RGFW_window_resize(RGFW_window* win, RGFW_area a) { RGFW_ASSERT(win != NULL); - win->w = (i32)w; - win->h = (i32)h; + win->r.w = (i32)a.w; + win->r.h = (i32)a.h; + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + XResizeWindow(win->src.display, win->src.window, a.w, a.h); - XResizeWindow(_RGFW->display, win->src.window, (u32)w, (u32)h); - - if ((win->internal.flags & RGFW_windowNoResize)) { + if ((win->_flags & RGFW_windowNoResize)) { XSizeHints sh; sh.flags = (1L << 4) | (1L << 5); - sh.min_width = sh.max_width = (i32)w; - sh.min_height = sh.max_height = (i32)h; + sh.min_width = sh.max_width = (i32)a.w; + sh.min_height = sh.max_height = (i32)a.h; - XSetWMSizeHints(_RGFW->display, (Drawable) win->src.window, &sh, XA_WM_NORMAL_HINTS); + XSetWMSizeHints(win->src.display, (Drawable) win->src.window, &sh, XA_WM_NORMAL_HINTS); } - return; +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + if (win->src.compositor) { + xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->r.w, win->r.h); + #ifdef RGFW_OPENGL + wl_egl_window_resize(win->src.eglWindow, (i32)a.w, (i32)a.h, 0, 0); + #endif + } +#endif } -void RGFW_FUNC(RGFW_window_setAspectRatio) (RGFW_window* win, i32 w, i32 h) { +void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(0); - - if (w == 0 && h == 0) + if (a.w == 0 && a.h == 0) return; +#ifdef RGFW_X11 XSizeHints hints; long flags; - XGetWMNormalHints(_RGFW->display, win->src.window, &hints, &flags); + XGetWMNormalHints(win->src.display, win->src.window, &hints, &flags); hints.flags |= PAspect; - hints.min_aspect.x = hints.max_aspect.x = (i32)w; - hints.min_aspect.y = hints.max_aspect.y = (i32)h; + hints.min_aspect.x = hints.max_aspect.x = (i32)a.w; + hints.min_aspect.y = hints.max_aspect.y = (i32)a.h; - XSetWMNormalHints(_RGFW->display, win->src.window, &hints); + XSetWMNormalHints(win->src.display, win->src.window, &hints); return; +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL +#endif } -void RGFW_FUNC(RGFW_window_setMinSize) (RGFW_window* win, i32 w, i32 h) { +void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { RGFW_ASSERT(win != NULL); - + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 long flags; XSizeHints hints; RGFW_MEMSET(&hints, 0, sizeof(XSizeHints)); - XGetWMNormalHints(_RGFW->display, win->src.window, &hints, &flags); + XGetWMNormalHints(win->src.display, win->src.window, &hints, &flags); hints.flags |= PMinSize; - hints.min_width = (i32)w; - hints.min_height = (i32)h; + hints.min_width = (i32)a.w; + hints.min_height = (i32)a.h; - XSetWMNormalHints(_RGFW->display, win->src.window, &hints); + XSetWMNormalHints(win->src.display, win->src.window, &hints); return; +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL RGFW_UNUSED(a); +#endif } -void RGFW_FUNC(RGFW_window_setMaxSize) (RGFW_window* win, i32 w, i32 h) { +void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { RGFW_ASSERT(win != NULL); - + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 long flags; XSizeHints hints; RGFW_MEMSET(&hints, 0, sizeof(XSizeHints)); - XGetWMNormalHints(_RGFW->display, win->src.window, &hints, &flags); + XGetWMNormalHints(win->src.display, win->src.window, &hints, &flags); hints.flags |= PMaxSize; - hints.max_width = (i32)w; - hints.max_height = (i32)h; + hints.max_width = (i32)a.w; + hints.max_height = (i32)a.h; - XSetWMNormalHints(_RGFW->display, win->src.window, &hints); - return; + XSetWMNormalHints(win->src.display, win->src.window, &hints); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL RGFW_UNUSED(a); +#endif } +#ifdef RGFW_X11 void RGFW_toggleXMaximized(RGFW_window* win, RGFW_bool maximized); void RGFW_toggleXMaximized(RGFW_window* win, RGFW_bool maximized) { RGFW_ASSERT(win != NULL); @@ -6238,36 +5129,52 @@ void RGFW_toggleXMaximized(RGFW_window* win, RGFW_bool maximized) { xev.xclient.data.l[3] = 0; xev.xclient.data.l[4] = 0; - XSendEvent(_RGFW->display, DefaultRootWindow(_RGFW->display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XSendEvent(win->src.display, DefaultRootWindow(win->src.display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); } +#endif -void RGFW_FUNC(RGFW_window_maximize) (RGFW_window* win) { - win->internal.oldX = win->x; - win->internal.oldY = win->y; - win->internal.oldW = win->w; - win->internal.oldH = win->h; - +void RGFW_window_maximize(RGFW_window* win) { + win->_oldRect = win->r; + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 RGFW_toggleXMaximized(win, 1); return; +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + return; +#endif } -void RGFW_FUNC(RGFW_window_focus) (RGFW_window* win) { +void RGFW_window_focus(RGFW_window* win) { RGFW_ASSERT(win); - + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 XWindowAttributes attr; - XGetWindowAttributes(_RGFW->display, win->src.window, &attr); + XGetWindowAttributes(win->src.display, win->src.window, &attr); if (attr.map_state != IsViewable) return; - XSetInputFocus(_RGFW->display, win->src.window, RevertToPointerRoot, CurrentTime); - XFlush(_RGFW->display); + XSetInputFocus(win->src.display, win->src.window, RevertToPointerRoot, CurrentTime); + XFlush(win->src.display); +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL; +#endif } -void RGFW_FUNC(RGFW_window_raise) (RGFW_window* win) { +void RGFW_window_raise(RGFW_window* win) { RGFW_ASSERT(win); - XRaiseWindow(_RGFW->display, win->src.window); - XMapRaised(_RGFW->display, win->src.window); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + XRaiseWindow(win->src.display, win->src.window); + XMapRaised(win->src.display, win->src.window); +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL; +#endif } +#ifdef RGFW_X11 void RGFW_window_setXAtom(RGFW_window* win, Atom netAtom, RGFW_bool fullscreen); void RGFW_window_setXAtom(RGFW_window* win, Atom netAtom, RGFW_bool fullscreen) { RGFW_ASSERT(win != NULL); @@ -6284,66 +5191,94 @@ void RGFW_window_setXAtom(RGFW_window* win, Atom netAtom, RGFW_bool fullscreen) xev.xclient.data.l[1] = (long int)netAtom; xev.xclient.data.l[2] = 0; - XSendEvent(_RGFW->display, DefaultRootWindow(_RGFW->display), False, SubstructureNotifyMask | SubstructureRedirectMask, &xev); + XSendEvent(win->src.display, DefaultRootWindow(win->src.display), False, SubstructureNotifyMask | SubstructureRedirectMask, &xev); } +#endif -void RGFW_FUNC(RGFW_window_setFullscreen)(RGFW_window* win, RGFW_bool fullscreen) { +void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { RGFW_ASSERT(win != NULL); - + RGFW_GOTO_WAYLAND(0); if (fullscreen) { - win->internal.flags |= RGFW_windowFullscreen; - win->internal.oldX = win->x; - win->internal.oldY = win->y; - win->internal.oldW = win->w; - win->internal.oldH = win->h; + win->_flags |= RGFW_windowFullscreen; + win->_oldRect = win->r; } - else win->internal.flags &= ~(u32)RGFW_windowFullscreen; + else win->_flags &= ~(u32)RGFW_windowFullscreen; +#ifdef RGFW_X11 RGFW_LOAD_ATOM(_NET_WM_STATE_FULLSCREEN); RGFW_window_setXAtom(win, _NET_WM_STATE_FULLSCREEN, fullscreen); - XRaiseWindow(_RGFW->display, win->src.window); - XMapRaised(_RGFW->display, win->src.window); + XRaiseWindow(win->src.display, win->src.window); + XMapRaised(win->src.display, win->src.window); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL; +#endif } -void RGFW_FUNC(RGFW_window_setFloating)(RGFW_window* win, RGFW_bool floating) { +void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 RGFW_LOAD_ATOM(_NET_WM_STATE_ABOVE); RGFW_window_setXAtom(win, _NET_WM_STATE_ABOVE, floating); +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL RGFW_UNUSED(floating); +#endif } -void RGFW_FUNC(RGFW_window_setOpacity)(RGFW_window* win, u8 opacity) { +void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 const u32 value = (u32) (0xffffffffu * (double) opacity); RGFW_LOAD_ATOM(NET_WM_WINDOW_OPACITY); - XChangeProperty(_RGFW->display, win->src.window, + XChangeProperty(win->src.display, win->src.window, NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) &value, 1); +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL RGFW_UNUSED(opacity); +#endif } -void RGFW_FUNC(RGFW_window_minimize)(RGFW_window* win) { +void RGFW_window_minimize(RGFW_window* win) { RGFW_ASSERT(win != NULL); - + RGFW_GOTO_WAYLAND(0); if (RGFW_window_isMaximized(win)) return; - win->internal.oldX = win->x; - win->internal.oldY = win->y; - win->internal.oldW = win->w; - win->internal.oldH = win->h; - XIconifyWindow(_RGFW->display, win->src.window, DefaultScreen(_RGFW->display)); - XFlush(_RGFW->display); + win->_oldRect = win->r; +#ifdef RGFW_X11 + XIconifyWindow(win->src.display, win->src.window, DefaultScreen(win->src.display)); + XFlush(win->src.display); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL; +#endif } -void RGFW_FUNC(RGFW_window_restore)(RGFW_window* win) { +void RGFW_window_restore(RGFW_window* win) { RGFW_ASSERT(win != NULL); - RGFW_toggleXMaximized(win, RGFW_FALSE); - RGFW_window_move(win, win->internal.oldX, win->internal.oldY); - RGFW_window_resize(win, win->internal.oldW, win->internal.oldH); - + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + RGFW_toggleXMaximized(win, 0); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL +#endif + win->r = win->_oldRect; + RGFW_window_move(win, RGFW_POINT(win->r.x, win->r.y)); + RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h)); + RGFW_window_show(win); - XFlush(_RGFW->display); +#ifdef RGFW_X11 + XFlush(win->src.display); +#endif } -RGFW_bool RGFW_FUNC(RGFW_window_isFloating)(RGFW_window* win) { +RGFW_bool RGFW_window_isFloating(RGFW_window* win) { + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 RGFW_LOAD_ATOM(_NET_WM_STATE); RGFW_LOAD_ATOM(_NET_WM_STATE_ABOVE); @@ -6352,7 +5287,7 @@ RGFW_bool RGFW_FUNC(RGFW_window_isFloating)(RGFW_window* win) { unsigned long nitems, bytes_after; Atom* prop_return = NULL; - int status = XGetWindowProperty(_RGFW->display, win->src.window, _NET_WM_STATE, 0, (~0L), False, XA_ATOM, + int status = XGetWindowProperty(win->src.display, win->src.window, _NET_WM_STATE, 0, (~0L), False, XA_ATOM, &actual_type, &actual_format, &nitems, &bytes_after, (unsigned char **)&prop_return); @@ -6365,148 +5300,226 @@ RGFW_bool RGFW_FUNC(RGFW_window_isFloating)(RGFW_window* win) { if (prop_return) XFree(prop_return); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL RGFW_UNUSED(win); +#endif return RGFW_FALSE; } -void RGFW_FUNC(RGFW_window_setName)(RGFW_window* win, const char* name) { +void RGFW_window_setName(RGFW_window* win, const char* name) { RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(0); + #ifdef RGFW_X11 + XStoreName(win->src.display, win->src.window, name); - XStoreName(_RGFW->display, win->src.window, name); - - RGFW_LOAD_ATOM(_NET_WM_NAME); RGFW_LOAD_ATOM(UTF8_STRING); + RGFW_LOAD_ATOM(_NET_WM_NAME); char buf[256]; RGFW_MEMSET(buf, 0, sizeof(buf)); RGFW_STRNCPY(buf, name, sizeof(buf) - 1); XChangeProperty( - _RGFW->display, win->src.window, _NET_WM_NAME, UTF8_STRING, + win->src.display, win->src.window, _NET_WM_NAME, RGFW_XUTF8_STRING, 8, PropModeReplace, (u8*)buf, sizeof(buf) ); + #endif + #ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + if (win->src.compositor) + xdg_toplevel_set_title(win->src.xdg_toplevel, name); + #endif } #ifndef RGFW_NO_PASSTHROUGH -void RGFW_FUNC(RGFW_window_setMousePassthrough) (RGFW_window* win, RGFW_bool passthrough) { +void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 if (passthrough) { Region region = XCreateRegion(); - XShapeCombineRegion(_RGFW->display, win->src.window, ShapeInput, 0, 0, region, ShapeSet); + XShapeCombineRegion(win->src.display, win->src.window, ShapeInput, 0, 0, region, ShapeSet); XDestroyRegion(region); return; } - XShapeCombineMask(_RGFW->display, win->src.window, ShapeInput, 0, 0, None, ShapeSet); + XShapeCombineMask(win->src.display, win->src.window, ShapeInput, 0, 0, None, ShapeSet); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL RGFW_UNUSED(passthrough); +#endif } #endif /* RGFW_NO_PASSTHROUGH */ -RGFW_bool RGFW_FUNC(RGFW_window_setIconEx) (RGFW_window* win, u8* data_src, i32 w, i32 h, RGFW_format format, RGFW_icon type) { - Atom _NET_WM_ICON = XInternAtom(_RGFW->display, "_NET_WM_ICON", False); +RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* icon, RGFW_area a, i32 channels, u8 type) { RGFW_ASSERT(win != NULL); - if (data_src == NULL) { + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + RGFW_LOAD_ATOM(_NET_WM_ICON); + if (icon == NULL || (channels != 3 && channels != 4)) { RGFW_bool res = (RGFW_bool)XChangeProperty( - _RGFW->display, win->src.window, _NET_WM_ICON, XA_CARDINAL, 32, + win->src.display, win->src.window, _NET_WM_ICON, XA_CARDINAL, 32, PropModeReplace, (u8*)NULL, 0 ); return res; } - i32 count = (i32)(2 + (w * h)); + i32 count = (i32)(2 + (a.w * a.h)); unsigned long* data = (unsigned long*) RGFW_ALLOC((u32)count * sizeof(unsigned long)); RGFW_ASSERT(data != NULL); - RGFW_MEMSET(data, 0, (u32)count * sizeof(unsigned long)); - data[0] = (unsigned long)w; - data[1] = (unsigned long)h; + data[0] = (unsigned long)a.w; + data[1] = (unsigned long)a.h; + + unsigned long* target = &data[2]; + u32 x, y; + + for (x = 0; x < a.w; x++) { + for (y = 0; y < a.h; y++) { + size_t i = y * a.w + x; + u32 alpha = (channels == 4) ? icon[i * 4 + 3] : 0xFF; + + target[i] = (unsigned long)((icon[i * 4 + 0]) << 16) | + (unsigned long)((icon[i * 4 + 1]) << 8) | + (unsigned long)((icon[i * 4 + 2]) << 0) | + (unsigned long)(alpha << 24); + } + } - RGFW_copyImageData64((u8*)&data[2], w, h, RGFW_formatBGRA8, data_src, format, RGFW_TRUE); RGFW_bool res = RGFW_TRUE; if (type & RGFW_iconTaskbar) { res = (RGFW_bool)XChangeProperty( - _RGFW->display, win->src.window, _NET_WM_ICON, XA_CARDINAL, 32, + win->src.display, win->src.window, _NET_WM_ICON, XA_CARDINAL, 32, PropModeReplace, (u8*)data, count ); } - RGFW_copyImageData64((u8*)&data[2], w, h, RGFW_formatBGRA8, data_src, format, RGFW_FALSE); - if (type & RGFW_iconWindow) { XWMHints wm_hints; wm_hints.flags = IconPixmapHint; - i32 depth = DefaultDepth(_RGFW->display, DefaultScreen(_RGFW->display)); - XImage *image = XCreateImage(_RGFW->display, DefaultVisual(_RGFW->display, DefaultScreen(_RGFW->display)), - (u32)depth, ZPixmap, 0, (char *)&data[2], (u32)w, (u32)h, 32, 0); + i32 depth = DefaultDepth(win->src.display, DefaultScreen(win->src.display)); + XImage *image = XCreateImage(win->src.display, DefaultVisual(win->src.display, DefaultScreen(win->src.display)), + (u32)depth, ZPixmap, 0, (char *)target, a.w, a.h, 32, 0); - wm_hints.icon_pixmap = XCreatePixmap(_RGFW->display, win->src.window, (u32)w, (u32)h, (u32)depth); - XPutImage(_RGFW->display, wm_hints.icon_pixmap, DefaultGC(_RGFW->display, DefaultScreen(_RGFW->display)), image, 0, 0, 0, 0, (u32)w, (u32)h); + wm_hints.icon_pixmap = XCreatePixmap(win->src.display, win->src.window, a.w, a.h, (u32)depth); + XPutImage(win->src.display, wm_hints.icon_pixmap, DefaultGC(win->src.display, DefaultScreen(win->src.display)), image, 0, 0, 0, 0, a.w, a.h); image->data = NULL; XDestroyImage(image); - XSetWMHints(_RGFW->display, win->src.window, &wm_hints); + XSetWMHints(win->src.display, win->src.window, &wm_hints); } RGFW_FREE(data); - XFlush(_RGFW->display); + XFlush(win->src.display); return RGFW_BOOL(res); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL RGFW_UNUSED(icon); RGFW_UNUSED(a); RGFW_UNUSED(channels); RGFW_UNUSED(type); + return RGFW_FALSE; +#endif } -RGFW_mouse* RGFW_FUNC(RGFW_loadMouse) (u8* data, i32 w, i32 h, RGFW_format format) { - RGFW_ASSERT(data); +RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { + RGFW_ASSERT(icon); + RGFW_ASSERT(channels == 3 || channels == 4); + RGFW_GOTO_WAYLAND(0); + +#ifdef RGFW_X11 #ifndef RGFW_NO_X11_CURSOR RGFW_init(); - XcursorImage* native = XcursorImageCreate((i32)w, (i32)h); + XcursorImage* native = XcursorImageCreate((i32)a.w, (i32)a.h); native->xhot = 0; native->yhot = 0; - RGFW_MEMSET(native->pixels, 0, (u32)(w * h * 4)); - RGFW_copyImageData((u8*)native->pixels, w, h, RGFW_formatBGRA8, data, format); - Cursor cursor = XcursorImageLoadCursor(_RGFW->display, native); + XcursorPixel* target = native->pixels; + size_t x, y; + for (x = 0; x < a.w; x++) { + for (y = 0; y < a.h; y++) { + size_t i = y * a.w + x; + u32 alpha = (channels == 4) ? icon[i * 4 + 3] : 0xFF; + + target[i] = (u32)((icon[i * 4 + 0]) << 16) + | (u32)((icon[i * 4 + 1]) << 8) + | (u32)((icon[i * 4 + 2]) << 0) + | (u32)(alpha << 24); + } + } + + Cursor cursor = XcursorImageLoadCursor(_RGFW.display, native); XcursorImageDestroy(native); return (void*)cursor; #else - RGFW_UNUSED(data); RGFW_UNUSED(w); RGFW_UNUSED(h); RGFW_UNUSED(format); + RGFW_UNUSED(image); RGFW_UNUSED(a.w); RGFW_UNUSED(channels); return NULL; #endif +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + RGFW_UNUSED(icon); RGFW_UNUSED(a); RGFW_UNUSED(channels); + return NULL; /* TODO */ +#endif } -void RGFW_FUNC(RGFW_window_setMouse)(RGFW_window* win, RGFW_mouse* mouse) { +void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { +RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 RGFW_ASSERT(win && mouse); - XDefineCursor(_RGFW->display, win->src.window, (Cursor)mouse); + XDefineCursor(win->src.display, win->src.window, (Cursor)mouse); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + RGFW_UNUSED(win); RGFW_UNUSED(mouse); +#endif } -void RGFW_FUNC(RGFW_freeMouse)(RGFW_mouse* mouse) { +void RGFW_freeMouse(RGFW_mouse* mouse) { +RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 RGFW_ASSERT(mouse); - XFreeCursor(_RGFW->display, (Cursor)mouse); + XFreeCursor(_RGFW.display, (Cursor)mouse); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + RGFW_UNUSED(mouse); +#endif } -void RGFW_FUNC(RGFW_window_moveMouse)(RGFW_window* win, i32 x, i32 y) { +void RGFW_window_moveMouse(RGFW_window* win, RGFW_point p) { +RGFW_GOTO_WAYLAND(1); +#ifdef RGFW_X11 RGFW_ASSERT(win != NULL); XEvent event; - XQueryPointer(_RGFW->display, DefaultRootWindow(_RGFW->display), + XQueryPointer(win->src.display, DefaultRootWindow(win->src.display), &event.xbutton.root, &event.xbutton.window, &event.xbutton.x_root, &event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y, &event.xbutton.state); - win->internal.lastMouseX = x - win->x; - win->internal.lastMouseY = y - win->y; - if (event.xbutton.x == x && event.xbutton.y == y) + win->_lastMousePoint = RGFW_POINT(p.x - win->r.x, p.y - win->r.y); + if (event.xbutton.x == p.x && event.xbutton.y == p.y) return; - XWarpPointer(_RGFW->display, None, win->src.window, 0, 0, 0, 0, (int) x - win->x, (int) y - win->y); + XWarpPointer(win->src.display, None, win->src.window, 0, 0, 0, 0, (int) p.x - win->r.x, (int) p.y - win->r.y); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + RGFW_UNUSED(win); RGFW_UNUSED(p); +#endif } -RGFW_bool RGFW_FUNC(RGFW_window_setMouseDefault) (RGFW_window* win) { +RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { return RGFW_window_setMouseStandard(win, RGFW_mouseArrow); } -RGFW_bool RGFW_FUNC(RGFW_window_setMouseStandard) (RGFW_window* win, u8 mouse) { +RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { RGFW_ASSERT(win != NULL); - + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 static const u8 mouseIconSrc[16] = { 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}; if (mouse > (sizeof(mouseIconSrc) / sizeof(u8))) @@ -6514,33 +5527,63 @@ RGFW_bool RGFW_FUNC(RGFW_window_setMouseStandard) (RGFW_window* win, u8 mouse) { mouse = mouseIconSrc[mouse]; - Cursor cursor = XCreateFontCursor(_RGFW->display, mouse); - XDefineCursor(_RGFW->display, win->src.window, (Cursor) cursor); - XFreeCursor(_RGFW->display, (Cursor) cursor); + Cursor cursor = XCreateFontCursor(win->src.display, mouse); + XDefineCursor(win->src.display, win->src.window, (Cursor) cursor); + + XFreeCursor(win->src.display, (Cursor) cursor); return RGFW_TRUE; +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL { } + static const char* iconStrings[16] = { "left_ptr", "left_ptr", "text", "cross", "pointer", "e-resize", "n-resize", "nw-resize", "ne-resize", "all-resize", "not-allowed" }; + + struct wl_cursor* wlcursor = wl_cursor_theme_get_cursor(RGFW_wl_cursor_theme, iconStrings[mouse]); + RGFW_cursor_image = wlcursor->images[0]; + struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(RGFW_cursor_image); + + wl_surface_attach(RGFW_cursor_surface, cursor_buffer, 0, 0); + wl_surface_commit(RGFW_cursor_surface); + return RGFW_TRUE; + +#endif } -void RGFW_FUNC(RGFW_window_hide)(RGFW_window* win) { - XUnmapWindow(_RGFW->display, win->src.window); +void RGFW_window_hide(RGFW_window* win) { + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + XUnmapWindow(win->src.display, win->src.window); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + wl_surface_attach(win->src.surface, NULL, 0, 0); + wl_surface_commit(win->src.surface); + win->_flags |= RGFW_windowHide; +#endif } -void RGFW_FUNC(RGFW_window_show) (RGFW_window* win) { - win->internal.flags &= ~(u32)RGFW_windowHide; - if (win->internal.flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); - - XMapWindow(_RGFW->display, win->src.window); - RGFW_window_move(win, win->x, win->y); - return; +void RGFW_window_show(RGFW_window* win) { + win->_flags &= ~(u32)RGFW_windowHide; + if (win->_flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + XMapWindow(win->src.display, win->src.window); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + /* wl_surface_attach(win->src.surface, win->rc., 0, 0); */ + wl_surface_commit(win->src.surface); +#endif } -RGFW_ssize_t RGFW_FUNC(RGFW_readClipboardPtr)(char* str, size_t strCapacity) { +RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { + RGFW_GOTO_WAYLAND(1); +#ifdef RGFW_X11 RGFW_init(); - RGFW_LOAD_ATOM(XSEL_DATA); RGFW_LOAD_ATOM(UTF8_STRING); RGFW_LOAD_ATOM(CLIPBOARD); - if (XGetSelectionOwner(_RGFW->display, CLIPBOARD) == _RGFW->helperWindow) { + if (XGetSelectionOwner(_RGFW.display, RGFW_XCLIPBOARD) == _RGFW.helperWindow) { if (str != NULL) - RGFW_STRNCPY(str, _RGFW->clipboard, _RGFW->clipboard_len - 1); - _RGFW->clipboard[_RGFW->clipboard_len - 1] = '\0'; - return (RGFW_ssize_t)_RGFW->clipboard_len - 1; + RGFW_STRNCPY(str, _RGFW.clipboard, _RGFW.clipboard_len - 1); + _RGFW.clipboard[_RGFW.clipboard_len - 1] = '\0'; + return (RGFW_ssize_t)_RGFW.clipboard_len - 1; } XEvent event; @@ -6549,13 +5592,15 @@ RGFW_ssize_t RGFW_FUNC(RGFW_readClipboardPtr)(char* str, size_t strCapacity) { char* data; Atom target; - XConvertSelection(_RGFW->display, CLIPBOARD, UTF8_STRING, XSEL_DATA, _RGFW->helperWindow, CurrentTime); - XSync(_RGFW->display, 0); + RGFW_LOAD_ATOM(XSEL_DATA); + + XConvertSelection(_RGFW.display, RGFW_XCLIPBOARD, RGFW_XUTF8_STRING, XSEL_DATA, _RGFW.helperWindow, CurrentTime); + XSync(_RGFW.display, 0); while (1) { - XNextEvent(_RGFW->display, &event); + XNextEvent(_RGFW.display, &event); if (event.type != SelectionNotify) continue; - if (event.xselection.selection != CLIPBOARD || event.xselection.property == 0) + if (event.xselection.selection != RGFW_XCLIPBOARD || event.xselection.property == 0) return -1; break; } @@ -6568,7 +5613,7 @@ RGFW_ssize_t RGFW_FUNC(RGFW_readClipboardPtr)(char* str, size_t strCapacity) { if (sizeN > strCapacity && str != NULL) size = -1; - if ((target == UTF8_STRING || target == XA_STRING) && str != NULL) { + if ((target == RGFW_XUTF8_STRING || target == XA_STRING) && str != NULL) { RGFW_MEMCPY(str, data, sizeN); str[sizeN] = '\0'; XFree(data); @@ -6578,16 +5623,22 @@ RGFW_ssize_t RGFW_FUNC(RGFW_readClipboardPtr)(char* str, size_t strCapacity) { size = (RGFW_ssize_t)sizeN; return size; + #endif + #if defined(RGFW_WAYLAND) + RGFW_WAYLAND_LABEL RGFW_UNUSED(str); RGFW_UNUSED(strCapacity); + return 0; + #endif } i32 RGFW_XHandleClipboardSelectionHelper(void) { +#ifdef RGFW_X11 RGFW_LOAD_ATOM(SAVE_TARGETS); XEvent event; - XPending(_RGFW->display); + XPending(_RGFW.display); - if (QLength(_RGFW->display) || XEventsQueued(_RGFW->display, QueuedAlready) + XEventsQueued(_RGFW->display, QueuedAfterReading)) - XNextEvent(_RGFW->display, &event); + if (QLength(_RGFW.display) || XEventsQueued(_RGFW.display, QueuedAlready) + XEventsQueued(_RGFW.display, QueuedAfterReading)) + XNextEvent(_RGFW.display, &event); else return 0; @@ -6603,41 +5654,60 @@ i32 RGFW_XHandleClipboardSelectionHelper(void) { } return 0; +#else + return 1; +#endif } -void RGFW_FUNC(RGFW_writeClipboard)(const char* text, u32 textLen) { - RGFW_LOAD_ATOM(SAVE_TARGETS); RGFW_LOAD_ATOM(CLIPBOARD); +void RGFW_writeClipboard(const char* text, u32 textLen) { + RGFW_GOTO_WAYLAND(1); + #ifdef RGFW_X11 + RGFW_LOAD_ATOM(SAVE_TARGETS); RGFW_init(); /* request ownership of the clipboard section and request to convert it, this means its our job to convert it */ - XSetSelectionOwner(_RGFW->display, CLIPBOARD, _RGFW->helperWindow, CurrentTime); - if (XGetSelectionOwner(_RGFW->display, CLIPBOARD) != _RGFW->helperWindow) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, "X11 failed to become owner of clipboard selection"); + XSetSelectionOwner(_RGFW.display, RGFW_XCLIPBOARD, _RGFW.helperWindow, CurrentTime); + if (XGetSelectionOwner(_RGFW.display, RGFW_XCLIPBOARD) != _RGFW.helperWindow) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, RGFW_DEBUG_CTX(_RGFW.root, 0), "X11 failed to become owner of clipboard selection"); return; } - if (_RGFW->clipboard) - RGFW_FREE(_RGFW->clipboard); + if (_RGFW.clipboard) + RGFW_FREE(_RGFW.clipboard); - _RGFW->clipboard = (char*)RGFW_ALLOC(textLen); - RGFW_ASSERT(_RGFW->clipboard != NULL); + _RGFW.clipboard = (char*)RGFW_ALLOC(textLen); + RGFW_ASSERT(_RGFW.clipboard != NULL); - RGFW_STRNCPY(_RGFW->clipboard, text, textLen - 1); - _RGFW->clipboard[textLen - 1] = '\0'; - _RGFW->clipboard_len = textLen; - return; + RGFW_STRNCPY(_RGFW.clipboard, text, textLen - 1); + _RGFW.clipboard[textLen - 1] = '\0'; + _RGFW.clipboard_len = textLen; + #endif + #ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + RGFW_UNUSED(text); RGFW_UNUSED(textLen); + #endif } -RGFW_bool RGFW_FUNC(RGFW_window_isHidden)(RGFW_window* win) { +RGFW_bool RGFW_window_isHidden(RGFW_window* win) { RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 + XWindowAttributes windowAttributes; - XGetWindowAttributes(_RGFW->display, win->src.window, &windowAttributes); + XGetWindowAttributes(win->src.display, win->src.window, &windowAttributes); return (windowAttributes.map_state == IsUnmapped && !RGFW_window_isMinimized(win)); +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + return RGFW_FALSE; +#endif } -RGFW_bool RGFW_FUNC(RGFW_window_isMinimized)(RGFW_window* win) { +RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 RGFW_LOAD_ATOM(WM_STATE); Atom actual_type; @@ -6645,7 +5715,7 @@ RGFW_bool RGFW_FUNC(RGFW_window_isMinimized)(RGFW_window* win) { unsigned long nitems, bytes_after; unsigned char* prop_data; - i32 status = XGetWindowProperty(_RGFW->display, win->src.window, WM_STATE, 0, 2, False, + i32 status = XGetWindowProperty(win->src.display, win->src.window, WM_STATE, 0, 2, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop_data); @@ -6658,12 +5728,19 @@ RGFW_bool RGFW_FUNC(RGFW_window_isMinimized)(RGFW_window* win) { XFree(prop_data); XWindowAttributes windowAttributes; - XGetWindowAttributes(_RGFW->display, win->src.window, &windowAttributes); + XGetWindowAttributes(win->src.display, win->src.window, &windowAttributes); return windowAttributes.map_state != IsViewable; +#endif +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + return RGFW_FALSE; +#endif } -RGFW_bool RGFW_FUNC(RGFW_window_isMaximized)(RGFW_window* win) { +RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { RGFW_ASSERT(win != NULL); + RGFW_GOTO_WAYLAND(0); +#ifdef RGFW_X11 RGFW_LOAD_ATOM(_NET_WM_STATE); RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_VERT); RGFW_LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ); @@ -6673,7 +5750,7 @@ RGFW_bool RGFW_FUNC(RGFW_window_isMaximized)(RGFW_window* win) { unsigned long nitems, bytes_after; unsigned char* prop_data; - i32 status = XGetWindowProperty(_RGFW->display, win->src.window, _NET_WM_STATE, 0, 1024, False, + i32 status = XGetWindowProperty(win->src.display, win->src.window, _NET_WM_STATE, 0, 1024, False, XA_ATOM, &actual_type, &actual_format, &nitems, &bytes_after, &prop_data); @@ -6695,10 +5772,23 @@ RGFW_bool RGFW_FUNC(RGFW_window_isMaximized)(RGFW_window* win) { if (prop_data != NULL) XFree(prop_data); - +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL; +#endif return RGFW_FALSE; } +#ifndef RGFW_NO_DPI +u32 RGFW_XCalculateRefreshRate(XRRModeInfo mi); +u32 RGFW_XCalculateRefreshRate(XRRModeInfo mi) { + if (mi.hTotal == 0 || mi.vTotal == 0) return 0; + return (u32) RGFW_ROUND((double) mi.dotClock / ((double) mi.hTotal * (double) mi.vTotal)); +} +#endif + + +#ifdef RGFW_X11 static float XGetSystemContentDPI(Display* display, i32 screen) { float dpi = 96.0f; @@ -6722,53 +5812,49 @@ static float XGetSystemContentDPI(Display* display, i32 screen) { return dpi; } +#endif RGFW_monitor RGFW_XCreateMonitor(i32 screen); RGFW_monitor RGFW_XCreateMonitor(i32 screen) { RGFW_monitor monitor; RGFW_init(); - Display* display = _RGFW->display; + RGFW_GOTO_WAYLAND(1); +#ifdef RGFW_X11 + Display* display = _RGFW.display; if (screen == -1) screen = DefaultScreen(display); Screen* scrn = DefaultScreenOfDisplay(display); + RGFW_area size = RGFW_AREA(scrn->width, scrn->height); monitor.x = 0; monitor.y = 0; - monitor.mode.w = scrn->width; - monitor.mode.h = scrn->height; + monitor.mode.area = RGFW_AREA(size.w, size.h); monitor.physW = (float)DisplayWidthMM(display, screen) / 25.4f; monitor.physH = (float)DisplayHeightMM(display, screen) / 25.4f; - RGFW_splitBPP((u32)DefaultDepth(display, screen), &monitor.mode); + RGFW_splitBPP((u32)DefaultDepth(display, DefaultScreen(display)), &monitor.mode); char* name = XDisplayName((const char*)display); RGFW_STRNCPY(monitor.name, name, sizeof(monitor.name) - 1); monitor.name[sizeof(monitor.name) - 1] = '\0'; float dpi = XGetSystemContentDPI(display, screen); - monitor.pixelRatio = dpi >= 192.0f ? 2 : 1.0f; + monitor.pixelRatio = dpi >= 192.0f ? 2 : 1; monitor.scaleX = (float) (dpi) / 96.0f; monitor.scaleY = (float) (dpi) / 96.0f; #ifndef RGFW_NO_DPI - XRRCrtcInfo* ci = NULL; - XRRScreenResources* sr = NULL; + XRRScreenResources* sr = XRRGetScreenResourcesCurrent(display, RootWindow(display, screen)); + monitor.mode.refreshRate = RGFW_XCalculateRefreshRate(sr->modes[screen]); - { - XRRScreenConfiguration* conf = XRRGetScreenInfo(display, RootWindow(display, screen)); - monitor.mode.refreshRate = (u32)XRRConfigCurrentRate(conf); - - sr = XRRGetScreenResourcesCurrent(display, RootWindow(display, screen)); + XRRCrtcInfo* ci = NULL; int crtc = screen; if (sr->ncrtc > crtc) { ci = XRRGetCrtcInfo(display, sr, sr->crtcs[crtc]); } - - XRRFreeScreenConfigInfo(conf); - } #endif #ifndef RGFW_NO_DPI @@ -6776,7 +5862,7 @@ RGFW_monitor RGFW_XCreateMonitor(i32 screen) { if (info == NULL || ci == NULL) { XRRFreeScreenResources(sr); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, "monitor found"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); return monitor; } @@ -6787,21 +5873,18 @@ RGFW_monitor RGFW_XCreateMonitor(i32 screen) { RGFW_STRNCPY(monitor.name, info->name, sizeof(monitor.name) - 1); monitor.name[sizeof(monitor.name) - 1] = '\0'; - XRRFreeOutputInfo(info); - info = NULL; + if ((u8)physW && (u8)physH) { + monitor.physW = physW; + monitor.physH = physH; + } - if (physW > 0.0f && physH > 0.0f) { - monitor.physW = physW; - monitor.physH = physH; - } + monitor.x = ci->x; + monitor.y = ci->y; - monitor.x = ci->x; - monitor.y = ci->y; - - if (ci->width && ci->height) { - monitor.mode.w = (i32)ci->width; - monitor.mode.h = (i32)ci->height; - } + if (ci->width && ci->height) { + monitor.mode.area.w = (u32)ci->width; + monitor.mode.area.h = (u32)ci->height; + } #endif #ifndef RGFW_NO_DPI @@ -6809,15 +5892,24 @@ RGFW_monitor RGFW_XCreateMonitor(i32 screen) { XRRFreeScreenResources(sr); #endif - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, "monitor found"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); return monitor; +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL RGFW_UNUSED(screen); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); + return monitor; +#endif } -RGFW_monitor* RGFW_FUNC(RGFW_getMonitors)(size_t* len) { +RGFW_monitor* RGFW_getMonitors(size_t* len) { static RGFW_monitor monitors[7]; + + RGFW_GOTO_WAYLAND(1); + #ifdef RGFW_X11 RGFW_init(); - Display* display = _RGFW->display; + Display* display = _RGFW.display; i32 max = ScreenCount(display); i32 i; @@ -6827,41 +5919,52 @@ RGFW_monitor* RGFW_FUNC(RGFW_getMonitors)(size_t* len) { if (len != NULL) *len = (size_t)((max <= 6) ? (max) : (6)); return monitors; + #endif + #ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL RGFW_UNUSED(len); + return monitors; /* TODO WAYLAND */ + #endif } -RGFW_monitor RGFW_FUNC(RGFW_getPrimaryMonitor)(void) { +RGFW_monitor RGFW_getPrimaryMonitor(void) { + RGFW_GOTO_WAYLAND(1); + #ifdef RGFW_X11 return RGFW_XCreateMonitor(-1); + #endif + #ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL return (RGFW_monitor){ 0 }; /* TODO WAYLAND */ + #endif } -RGFW_bool RGFW_FUNC(RGFW_monitor_requestMode)(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { +RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { + RGFW_GOTO_WAYLAND(1); +#ifdef RGFW_X11 #ifndef RGFW_NO_DPI RGFW_init(); - XRRScreenConfiguration *conf = XRRGetScreenInfo(_RGFW->display, DefaultRootWindow(_RGFW->display)); - XRRScreenResources* screenRes = XRRGetScreenResources(_RGFW->display, DefaultRootWindow(_RGFW->display)); + XRRScreenResources* screenRes = XRRGetScreenResources(_RGFW.display, DefaultRootWindow(_RGFW.display)); if (screenRes == NULL) return RGFW_FALSE; int i; for (i = 0; i < screenRes->ncrtc; i++) { - XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(_RGFW->display, screenRes, screenRes->crtcs[i]); + XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(_RGFW.display, screenRes, screenRes->crtcs[i]); if (!crtcInfo) continue; - if (mon.x == crtcInfo->x && mon.y == crtcInfo->y && (u32)mon.mode.w == crtcInfo->width && (u32)mon.mode.h == crtcInfo->height) { + if (mon.x == crtcInfo->x && mon.y == crtcInfo->y && (u32)mon.mode.area.w == crtcInfo->width && (u32)mon.mode.area.h == crtcInfo->height) { RRMode rmode = None; int index; for (index = 0; index < screenRes->nmode; index++) { RGFW_monitorMode foundMode; - foundMode.w = (i32)screenRes->modes[index].width; - foundMode.h = (i32)screenRes->modes[index].height; - foundMode.refreshRate = (u32)XRRConfigCurrentRate(conf); - RGFW_splitBPP((u32)DefaultDepth(_RGFW->display, DefaultScreen(_RGFW->display)), &foundMode); + foundMode.area = RGFW_AREA(screenRes->modes[index].width, screenRes->modes[index].height); + foundMode.refreshRate = RGFW_XCalculateRefreshRate(screenRes->modes[index]); + RGFW_splitBPP((u32)DefaultDepth(_RGFW.display, DefaultScreen(_RGFW.display)), &foundMode); if (RGFW_monitorModeCompare(mode, foundMode, request)) { rmode = screenRes->modes[index].id; RROutput output = screenRes->outputs[i]; - XRROutputInfo* info = XRRGetOutputInfo(_RGFW->display, screenRes, output); + XRROutputInfo* info = XRRGetOutputInfo(_RGFW.display, screenRes, output); if (info) { - XRRSetCrtcConfig(_RGFW->display, screenRes, screenRes->crtcs[i], + XRRSetCrtcConfig(_RGFW.display, screenRes, screenRes->crtcs[i], CurrentTime, 0, 0, rmode, RR_Rotate_0, &output, 1); XRRFreeOutputInfo(info); XRRFreeCrtcInfo(crtcInfo); @@ -6880,394 +5983,144 @@ RGFW_bool RGFW_FUNC(RGFW_monitor_requestMode)(RGFW_monitor mon, RGFW_monitorMode } XRRFreeScreenResources(screenRes); - XRRFreeScreenConfigInfo(conf); + return RGFW_FALSE; + #endif +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL RGFW_UNUSED(mon); RGFW_UNUSED(mode); RGFW_UNUSED(request); #endif return RGFW_FALSE; } -RGFW_monitor RGFW_FUNC(RGFW_window_getMonitor) (RGFW_window* win) { +RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { RGFW_monitor mon; RGFW_MEMSET(&mon, 0, sizeof(mon)); RGFW_ASSERT(win != NULL); - + RGFW_GOTO_WAYLAND(1); +#ifdef RGFW_X11 XWindowAttributes attrs; - if (!XGetWindowAttributes(_RGFW->display, win->src.window, &attrs)) { + if (!XGetWindowAttributes(win->src.display, win->src.window, &attrs)) { return mon; } i32 i; - for (i = 0; i < ScreenCount(_RGFW->display) && i < 6; i++) { - Screen* screen = ScreenOfDisplay(_RGFW->display, i); + for (i = 0; i < ScreenCount(win->src.display) && i < 6; i++) { + Screen* screen = ScreenOfDisplay(win->src.display, i); if (attrs.x >= 0 && attrs.x < XWidthOfScreen(screen) && attrs.y >= 0 && attrs.y < XHeightOfScreen(screen)) return RGFW_XCreateMonitor(i); } +#endif +#ifdef RGFW_WAYLAND +RGFW_WAYLAND_LABEL +#endif return mon; } -#ifdef RGFW_OPENGL -RGFW_bool RGFW_FUNC(RGFW_window_createContextPtr_OpenGL) (RGFW_window* win, RGFW_glContext* context, RGFW_glHints* hints) { - /* for checking extensions later */ - const char sRGBARBstr[] = "GLX_ARB_framebuffer_sRGB"; - const char sRGBEXTstr[] = "GLX_EXT_framebuffer_sRGB"; - const char noErorrStr[] = "GLX_ARB_create_context_no_error"; - const char flushStr[] = "GLX_ARB_context_flush_control"; - const char robustStr[] = "GLX_ARB_create_context_robustness"; - - /* basic RGFW int */ - win->src.ctx.native = context; - win->src.gfxType = RGFW_gfxNativeOpenGL; - /* This is required so that way the user can create their own OpenGL context after RGFW_createWindow is used */ - if (win->src.window) RGFW_window_closePlatform(win); - - RGFW_bool transparent = (win->internal.flags & RGFW_windowTransparent); - - /* start by creating a GLX config / X11 Viusal */ - XVisualInfo visual; - GLXFBConfig bestFbc; - - i32 visual_attribs[40]; - RGFW_attribStack stack; - RGFW_attribStack_init(&stack, visual_attribs, 40); - RGFW_attribStack_pushAttribs(&stack, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR); - RGFW_attribStack_pushAttribs(&stack, GLX_X_RENDERABLE, 1); - RGFW_attribStack_pushAttribs(&stack, GLX_RENDER_TYPE, GLX_RGBA_BIT); - RGFW_attribStack_pushAttribs(&stack, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT); - RGFW_attribStack_pushAttribs(&stack, GLX_DOUBLEBUFFER, 1); - RGFW_attribStack_pushAttribs(&stack, GLX_ALPHA_SIZE, hints->alpha); - RGFW_attribStack_pushAttribs(&stack, GLX_DEPTH_SIZE, hints->depth); - RGFW_attribStack_pushAttribs(&stack, GLX_STENCIL_SIZE, hints->stencil); - RGFW_attribStack_pushAttribs(&stack, GLX_STEREO, hints->stereo); - RGFW_attribStack_pushAttribs(&stack, GLX_AUX_BUFFERS, hints->auxBuffers); - RGFW_attribStack_pushAttribs(&stack, GLX_RED_SIZE, hints->red); - RGFW_attribStack_pushAttribs(&stack, GLX_GREEN_SIZE, hints->green); - RGFW_attribStack_pushAttribs(&stack, GLX_BLUE_SIZE, hints->blue); - RGFW_attribStack_pushAttribs(&stack, GLX_ACCUM_RED_SIZE, hints->accumRed); - RGFW_attribStack_pushAttribs(&stack, GLX_ACCUM_GREEN_SIZE, hints->accumGreen); - RGFW_attribStack_pushAttribs(&stack, GLX_ACCUM_BLUE_SIZE, hints->accumBlue); - RGFW_attribStack_pushAttribs(&stack, GLX_ACCUM_ALPHA_SIZE, hints->accumAlpha); - - if (hints->sRGB) { - if (RGFW_extensionSupportedPlatform_OpenGL(sRGBARBstr, sizeof(sRGBARBstr))) - RGFW_attribStack_pushAttribs(&stack, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, hints->sRGB); - if (RGFW_extensionSupportedPlatform_OpenGL(sRGBEXTstr, sizeof(sRGBEXTstr))) - RGFW_attribStack_pushAttribs(&stack, GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, hints->sRGB); - } - - RGFW_attribStack_pushAttribs(&stack, 0, 0); - - /* find the configs */ - i32 fbcount; - GLXFBConfig* fbc = glXChooseFBConfig(_RGFW->display, DefaultScreen(_RGFW->display), visual_attribs, &fbcount); - - i32 best_fbc = -1; - i32 best_depth = 0; - i32 best_samples = 0; - - if (fbcount == 0) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to find any valid GLX visual configs."); - return 0; - } - - /* search through all found configs to find the best match */ - i32 i; - for (i = 0; i < fbcount; i++) { - XVisualInfo* vi = glXGetVisualFromFBConfig(_RGFW->display, fbc[i]); - if (vi == NULL) - continue; - - i32 samp_buf, samples; - glXGetFBConfigAttrib(_RGFW->display, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf); - glXGetFBConfigAttrib(_RGFW->display, fbc[i], GLX_SAMPLES, &samples); - - if (best_fbc == -1) best_fbc = i; - if ((!(transparent) || vi->depth == 32) && best_depth == 0) { - best_fbc = i; - best_depth = vi->depth; - } - if ((!(transparent) || vi->depth == 32) && samples <= hints->samples && samples > best_samples) { - best_fbc = i; - best_depth = vi->depth; - best_samples = samples; - } - XFree(vi); - } - - if (best_fbc == -1) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to get a valid GLX visual."); - return 0; - } - - /* we found a config */ - bestFbc = fbc[best_fbc]; - XVisualInfo* vi = glXGetVisualFromFBConfig(_RGFW->display, bestFbc); - if (vi->depth != 32 && transparent) - RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, "Failed to to find a matching visual with a 32-bit depth."); - - if (best_samples < hints->samples) - RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, "Failed to load a matching sample count."); - - XFree(fbc); - visual = *vi; - XFree(vi); - - /* use the visual to create a new window */ - RGFW_XCreateWindow(visual, "", win->internal.flags, win); - - /* create the actual OpenGL context */ - i32 context_attribs[40]; - RGFW_attribStack_init(&stack, context_attribs, 40); - - i32 mask = 0; - switch (hints->profile) { - case RGFW_glES: mask |= GLX_CONTEXT_ES_PROFILE_BIT_EXT; break; - case RGFW_glCompatibility: mask |= GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; break; - case RGFW_glCore: mask |= GLX_CONTEXT_CORE_PROFILE_BIT_ARB; break; - default: mask |= GLX_CONTEXT_CORE_PROFILE_BIT_ARB; break; - } - - RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_PROFILE_MASK_ARB, mask); - - if (hints->minor || hints->major) { - RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_MAJOR_VERSION_ARB, hints->major); - RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_MINOR_VERSION_ARB, hints->minor); - } - - - if (RGFW_extensionSupportedPlatform_OpenGL(flushStr, sizeof(flushStr))) { - if (hints->releaseBehavior == RGFW_glReleaseFlush) { - RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_RELEASE_BEHAVIOR_ARB, GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB); - } else if (hints->releaseBehavior == RGFW_glReleaseNone) { - RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_RELEASE_BEHAVIOR_ARB, GLX_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB); - } - } - - i32 flags = 0; - if (hints->debug) flags |= GLX_CONTEXT_DEBUG_BIT_ARB; - if (hints->robustness && RGFW_extensionSupportedPlatform_OpenGL(robustStr, sizeof(robustStr))) flags |= GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB; - if (flags) { - RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_FLAGS_ARB, flags); - } - - if (RGFW_extensionSupportedPlatform_OpenGL(noErorrStr, sizeof(noErorrStr))) { - RGFW_attribStack_pushAttribs(&stack, GLX_CONTEXT_OPENGL_NO_ERROR_ARB, hints->noError); - } - - RGFW_attribStack_pushAttribs(&stack, 0, 0); - - /* create the context */ - glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0; - char str[] = "glXCreateContextAttribsARB"; - glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)glXGetProcAddressARB((u8*) str); - - GLXContext ctx = NULL; - if (hints->share) { - ctx = hints->share->ctx; - } - - if (glXCreateContextAttribsARB == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to load proc address 'glXCreateContextAttribsARB', loading a generic OpenGL context."); - win->src.ctx.native->ctx = glXCreateContext(_RGFW->display, &visual, ctx, True); - } else { - _RGFW->x11Error = NULL; - win->src.ctx.native->ctx = glXCreateContextAttribsARB(_RGFW->display, bestFbc, ctx, True, context_attribs); - if (_RGFW->x11Error || win->src.ctx.native->ctx == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to create an OpenGL context with AttribsARB, loading a generic OpenGL context."); - win->src.ctx.native->ctx = glXCreateContext(_RGFW->display, &visual, ctx, True); - } - } - - #ifndef RGFW_NO_GLXWINDOW - win->src.ctx.native->window = glXCreateWindow(_RGFW->display, bestFbc, win->src.window, NULL); - #else - win->src.ctx.native->window = win->src.window; - #endif - - glXMakeCurrent(_RGFW->display, (Drawable)win->src.ctx.native->window, (GLXContext)win->src.ctx.native->ctx); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context initalized."); - - return RGFW_TRUE; -} - -void RGFW_FUNC(RGFW_window_deleteContextPtr_OpenGL) (RGFW_window* win, RGFW_glContext* ctx) { - #ifndef RGFW_NO_GLXWINDOW - if (win->src.ctx.native->window != win->src.window) { - glXDestroyWindow(_RGFW->display, win->src.ctx.native->window); - } - #endif - - glXDestroyContext(_RGFW->display, ctx->ctx); - win->src.ctx.native = NULL; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context freed."); -} - -RGFW_bool RGFW_FUNC(RGFW_extensionSupportedPlatform_OpenGL)(const char * extension, size_t len) { - RGFW_init(); - const char* extensions = glXQueryExtensionsString(_RGFW->display, XDefaultScreen(_RGFW->display)); - return (extensions != NULL) && RGFW_extensionSupportedStr(extensions, extension, len); -} - -RGFW_proc RGFW_FUNC(RGFW_getProcAddress_OpenGL)(const char* procname) { return glXGetProcAddress((u8*) procname); } - -void RGFW_FUNC(RGFW_window_makeCurrentContext_OpenGL) (RGFW_window* win) { if (win) RGFW_ASSERT(win->src.ctx.native); +#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) +void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { if (win == NULL) glXMakeCurrent(NULL, (Drawable)NULL, (GLXContext) NULL); else - glXMakeCurrent(_RGFW->display, (Drawable)win->src.ctx.native->window, (GLXContext) win->src.ctx.native->ctx); - return; + glXMakeCurrent(win->src.display, (Drawable) win->src.window, (GLXContext) win->src.ctx); } -void* RGFW_FUNC(RGFW_getCurrentContext_OpenGL) (void) { return glXGetCurrentContext(); } -void RGFW_FUNC(RGFW_window_swapBuffers_OpenGL) (RGFW_window* win) { RGFW_ASSERT(win->src.ctx.native); glXSwapBuffers(_RGFW->display, win->src.ctx.native->window); } +void* RGFW_getCurrent_OpenGL(void) { return glXGetCurrentContext(); } +void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { glXSwapBuffers(win->src.display, win->src.window); } +#endif -void RGFW_FUNC(RGFW_window_swapInterval_OpenGL) (RGFW_window* win, i32 swapInterval) { +void RGFW_window_swapBuffers_software(RGFW_window* win) { RGFW_ASSERT(win != NULL); - /* cached pfn to avoid calling glXGetProcAddress more than once */ - static PFNGLXSWAPINTERVALEXTPROC pfn = NULL; - static int (*pfn2)(int) = NULL; - - if (pfn == NULL) { - u8 str[] = "glXSwapIntervalEXT"; - pfn = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddress(str); - if (pfn == NULL) { - pfn = (PFNGLXSWAPINTERVALEXTPROC)1; - const char* array[] = {"GLX_MESA_swap_control", "GLX_SGI_swap_control"}; - - size_t i; - for (i = 0; i < sizeof(array) / sizeof(char*) && pfn2 == NULL; i++) { - pfn2 = (int(*)(int))glXGetProcAddress((u8*)array[i]); - } - - if (pfn2 != NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to load swap interval function, fallingback to the native swapinterval function"); - } else { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to load swap interval function"); - } + RGFW_GOTO_WAYLAND(0); +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + #ifdef RGFW_X11 + win->src.bitmap->data = (char*) win->buffer; + RGFW_RGB_to_BGR(win, (u8*)win->src.bitmap->data); + XPutImage(win->src.display, win->src.window, win->src.gc, win->src.bitmap, 0, 0, 0, 0, win->bufferSize.w, win->bufferSize.h); + win->src.bitmap->data = NULL; + return; + #endif + #ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + #if !defined(RGFW_BUFFER_BGR) && !defined(RGFW_OSMESA) + RGFW_RGB_to_BGR(win, win->src.buffer); + #else + size_t y; + for (y = 0; y < win->r.h; y++) { + u32 index = (y * 4 * win->r.w); + u32 index2 = (y * 4 * win->bufferSize.w); + RGFW_MEMCPY(&win->src.buffer[index], &win->buffer[index2], win->r.w * 4); } - } + #endif - if (pfn != (PFNGLXSWAPINTERVALEXTPROC)1) { - pfn(_RGFW->display, win->src.ctx.native->window, swapInterval); - } - else if (pfn2 != NULL) { - pfn2(swapInterval); - } + wl_surface_frame_done(win, NULL, 0); + wl_surface_commit(win->src.surface); + #endif +#else +#ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL +#endif + RGFW_UNUSED(win); +#endif } -#endif /* RGFW_OPENGL */ -i32 RGFW_initPlatform_X11(void) { - #ifdef RGFW_USE_XDL - XDL_init(); - #endif +#if !defined(RGFW_EGL) - #if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) - #if defined(__CYGWIN__) - RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor-1.so"); - #elif defined(__OpenBSD__) || defined(__NetBSD__) - RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor.so"); - #else - RGFW_LOAD_LIBRARY(X11Cursorhandle, "libXcursor.so.1"); - #endif - RGFW_PROC_DEF(X11Cursorhandle, XcursorImageCreate); - RGFW_PROC_DEF(X11Cursorhandle, XcursorImageDestroy); - RGFW_PROC_DEF(X11Cursorhandle, XcursorImageLoadCursor); - #endif +void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { + RGFW_ASSERT(win != NULL); - #if !defined(RGFW_NO_X11_XI_PRELOAD) - #if defined(__CYGWIN__) - RGFW_LOAD_LIBRARY(X11Xihandle, "libXi-6.so"); - #elif defined(__OpenBSD__) || defined(__NetBSD__) - RGFW_LOAD_LIBRARY(X11Xihandle, "libXi.so"); - #else - RGFW_LOAD_LIBRARY(X11Xihandle, "libXi.so.6"); - #endif - RGFW_PROC_DEF(X11Xihandle, XISelectEvents); - #endif + #if defined(RGFW_OPENGL) + // cached pfn to avoid calling glXGetProcAddress more than once + static PFNGLXSWAPINTERVALEXTPROC pfn = (PFNGLXSWAPINTERVALEXTPROC)123; + static int (*pfn2)(int) = NULL; - #if !defined(RGFW_NO_X11_EXT_PRELOAD) - #if defined(__CYGWIN__) - RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext-6.so"); - #elif defined(__OpenBSD__) || defined(__NetBSD__) - RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext.so"); - #else - RGFW_LOAD_LIBRARY(X11XEXThandle, "libXext.so.6"); - #endif - RGFW_PROC_DEF(X11XEXThandle, XSyncCreateCounter); - RGFW_PROC_DEF(X11XEXThandle, XSyncIntToValue); - RGFW_PROC_DEF(X11XEXThandle, XSyncSetCounter); - RGFW_PROC_DEF(X11XEXThandle, XShapeCombineRegion); - RGFW_PROC_DEF(X11XEXThandle, XShapeCombineMask); - #endif + if (pfn == (PFNGLXSWAPINTERVALEXTPROC)123) { + pfn = ((PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddress((GLubyte*) "glXSwapIntervalEXT")); + if (pfn == NULL) { + const char* array[] = {"GLX_MESA_swap_control", "GLX_SGI_swap_control"}; + u32 i; + for (i = 0; i < sizeof(array) / sizeof(char*) && pfn2 == NULL; i++) + pfn2 = ((int(*)(int))glXGetProcAddress((GLubyte*) array[i])); - XInitThreads(); /*!< init X11 threading */ - _RGFW->display = XOpenDisplay(0); - _RGFW->context = XUniqueContext(); - - XSetWindowAttributes wa; - RGFW_MEMSET(&wa, 0, sizeof(wa)); - wa.event_mask = PropertyChangeMask; - _RGFW->helperWindow = XCreateWindow(_RGFW->display, XDefaultRootWindow(_RGFW->display), 0, 0, 1, 1, 0, 0, - InputOnly, DefaultVisual(_RGFW->display, DefaultScreen(_RGFW->display)), CWEventMask, &wa); - - u8 RGFW_blk[] = { 0, 0, 0, 0 }; - _RGFW->hiddenMouse = RGFW_loadMouse(RGFW_blk, 1, 1, RGFW_formatRGBA8); - _RGFW->clipboard = NULL; - - XkbComponentNamesRec rec; - XkbDescPtr desc = XkbGetMap(_RGFW->display, 0, XkbUseCoreKbd); - XkbDescPtr evdesc; - XSetErrorHandler(RGFW_XErrorHandler); - u8 old[256]; - - XkbGetNames(_RGFW->display, XkbKeyNamesMask, desc); - - RGFW_MEMSET(&rec, 0, sizeof(rec)); - char evdev[] = "evdev"; - rec.keycodes = evdev; - evdesc = XkbGetKeyboardByName(_RGFW->display, XkbUseCoreKbd, &rec, XkbGBN_KeyNamesMask, XkbGBN_KeyNamesMask, False); - /* memo: RGFW_keycodes[x11 keycode] = rgfw keycode */ - if(evdesc != NULL && desc != NULL) { - int i, j; - for(i = 0; i < (int)sizeof(old); i++){ - old[i] = _RGFW->keycodes[i]; - _RGFW->keycodes[i] = 0; + if (pfn2 != NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to load swap interval function, fallingback to the native swapinterval function"); + } else { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to load swap interval function"); + } } - for(i = evdesc->min_key_code; i <= evdesc->max_key_code; i++){ - for(j = desc->min_key_code; j <= desc->max_key_code; j++){ - if(RGFW_STRNCMP(evdesc->names->keys[i].name, desc->names->keys[j].name, XkbKeyNameLength) == 0){ - _RGFW->keycodes[j] = old[i]; - break; - } - } - } - XkbFreeKeyboard(desc, 0, True); - XkbFreeKeyboard(evdesc, 0, True); } - return 0; + if (pfn != NULL) + pfn(win->src.display, win->src.window, swapInterval); + else if (pfn2 != NULL) { + pfn2(swapInterval); + } + #else + RGFW_UNUSED(swapInterval); + #endif } +#endif -void RGFW_deinitPlatform_X11(void) { +void RGFW_deinit(void) { + if (_RGFW.windowCount == -1 || _RGFW_init == RGFW_FALSE) return; #define RGFW_FREE_LIBRARY(x) if (x != NULL) dlclose(x); x = NULL; +#ifdef RGFW_X11 /* to save the clipboard on the x server after the window is closed */ - RGFW_LOAD_ATOM(CLIPBOARD_MANAGER); RGFW_LOAD_ATOM(CLIPBOARD); + RGFW_LOAD_ATOM(CLIPBOARD_MANAGER); RGFW_LOAD_ATOM(SAVE_TARGETS); - if (XGetSelectionOwner(_RGFW->display, CLIPBOARD) == _RGFW->helperWindow) { - XConvertSelection(_RGFW->display, CLIPBOARD_MANAGER, SAVE_TARGETS, None, _RGFW->helperWindow, CurrentTime); + if (XGetSelectionOwner(_RGFW.display, RGFW_XCLIPBOARD) == _RGFW.helperWindow) { + XConvertSelection(_RGFW.display, CLIPBOARD_MANAGER, SAVE_TARGETS, None, _RGFW.helperWindow, CurrentTime); while (RGFW_XHandleClipboardSelectionHelper()); } - if (_RGFW->clipboard) { - RGFW_FREE(_RGFW->clipboard); - _RGFW->clipboard = NULL; + if (_RGFW.clipboard) { + RGFW_FREE(_RGFW.clipboard); + _RGFW.clipboard = NULL; } - if (_RGFW->hiddenMouse) { - RGFW_freeMouse(_RGFW->hiddenMouse); - _RGFW->hiddenMouse = NULL; - } + RGFW_freeMouse(_RGFW.hiddenMouse); - XDestroyWindow(_RGFW->display, (Drawable) _RGFW->helperWindow); /*!< close the window */ - XCloseDisplay(_RGFW->display); /*!< kill connection to the x server */ + XDestroyWindow(_RGFW.display, (Drawable) _RGFW.helperWindow); /*!< close the window */ + XCloseDisplay(_RGFW.display); /*!< kill connection to the x server */ #if !defined(RGFW_NO_X11_CURSOR_PRELOAD) && !defined(RGFW_NO_X11_CURSOR) RGFW_FREE_LIBRARY(X11Cursorhandle); @@ -7283,1592 +6136,210 @@ void RGFW_deinitPlatform_X11(void) { #if !defined(RGFW_NO_X11_EXT_PRELOAD) RGFW_FREE_LIBRARY(X11XEXThandle); #endif +#endif +#ifdef RGFW_WAYLAND + wl_display_disconnect(_RGFW.wl_display); +#endif + #ifndef RGFW_NO_LINUX + 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_gamepadCount; i++) { + if(RGFW_gamepads[i]) + close(RGFW_gamepads[i]); + } + #endif + + _RGFW.root = NULL; + _RGFW.windowCount = -1; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context deinitialized"); } -void RGFW_FUNC(RGFW_window_closePlatform)(RGFW_window* win) { - if (win->internal.holdMouse) - XUngrabPointer(_RGFW->display, CurrentTime); +void RGFW_window_close(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + if ((win->_flags & RGFW_windowNoInitAPI) == 0) RGFW_window_freeOpenGL(win); - XFreeGC(_RGFW->display, win->src.gc); - XDeleteContext(_RGFW->display, win->src.window, _RGFW->context); - XDestroyWindow(_RGFW->display, (Drawable) win->src.window); /*!< close the window */ + RGFW_GOTO_WAYLAND(0); + #ifdef RGFW_X11 + /* ungrab pointer if it was grabbed */ + if (win->_flags & RGFW_HOLD_MOUSE) + XUngrabPointer(win->src.display, CurrentTime); + + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + if (win->buffer != NULL) { + if ((win->_flags & RGFW_BUFFER_ALLOC)) + RGFW_FREE(win->buffer); + XDestroyImage((XImage*) win->src.bitmap); + } + #endif + + XFreeGC(win->src.display, win->src.gc); + XDestroyWindow(win->src.display, (Drawable) win->src.window); /*!< close the window */ + win->src.window = 0; + XCloseDisplay(win->src.display); + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a window was freed"); + _RGFW.windowCount--; + if (_RGFW.windowCount == 0) RGFW_deinit(); + + RGFW_clipboard_switch(NULL); + RGFW_FREE(win->event.droppedFiles); + if ((win->_flags & RGFW_WINDOW_ALLOC)) { + RGFW_FREE(win); + win = NULL; + } return; + #endif + + #ifdef RGFW_WAYLAND + RGFW_WAYLAND_LABEL + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a window was freed"); + + xdg_toplevel_destroy(win->src.xdg_toplevel); + xdg_surface_destroy(win->src.xdg_surface); + wl_surface_destroy(win->src.surface); + + _RGFW.windowCount--; + if (_RGFW.windowCount == 0) RGFW_deinit(); + + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + wl_buffer_destroy(win->src.wl_buffer); + if ((win->_flags & RGFW_BUFFER_ALLOC)) + RGFW_FREE(win->buffer); + + munmap(win->src.buffer, (size_t)(win->r.w * win->r.h * 4)); + #endif + + RGFW_clipboard_switch(NULL); + RGFW_FREE(win->event.droppedFiles); + if ((win->_flags & RGFW_WINDOW_ALLOC)) { + RGFW_FREE(win); + win = NULL; + } + #endif } -#ifdef RGFW_WEBGPU -WGPUSurface RGFW_FUNC(RGFW_window_createSurface_WebGPU) (RGFW_window* window, WGPUInstance instance) { - WGPUSurfaceDescriptor surfaceDesc = {0}; - WGPUSurfaceSourceXlibWindow fromXlib = {0}; - fromXlib.chain.sType = WGPUSType_SurfaceSourceXlibWindow; - fromXlib.display = _RGFW->display; - fromXlib.window = window->src.window; - surfaceDesc.nextInChain = (WGPUChainedStruct*)&fromXlib.chain; - return wgpuInstanceCreateSurface(instance, &surfaceDesc); -} -#endif - -#endif /* End of X11 linux / wayland / unix defines */ -/* +#include +#include +#include - Start of Wayland defayland -*/ +void RGFW_stopCheckEvents(void) { -#ifdef RGFW_WAYLAND + RGFW_eventWait_forceStop[2] = 1; + while (1) { + const char byte = 0; + const ssize_t result = write(RGFW_eventWait_forceStop[1], &byte, 1); + if (result == 1 || result == -1) + break; + } +} + +void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { + if (waitMS == 0) return; + + u8 i; + if (RGFW_eventWait_forceStop[0] == 0 || RGFW_eventWait_forceStop[1] == 0) { + if (pipe(RGFW_eventWait_forceStop) != -1) { + fcntl(RGFW_eventWait_forceStop[0], F_GETFL, 0); + fcntl(RGFW_eventWait_forceStop[0], F_GETFD, 0); + fcntl(RGFW_eventWait_forceStop[1], F_GETFL, 0); + fcntl(RGFW_eventWait_forceStop[1], F_GETFD, 0); + } + } + + struct pollfd fds[] = { + #ifdef RGFW_WAYLAND + { wl_display_get_fd(win->src.wl_display), POLLIN, 0 }, + #else + { ConnectionNumber(win->src.display), POLLIN, 0 }, + #endif + #ifdef RGFW_X11 + { ConnectionNumber(_RGFW.display), POLLIN, 0 }, + #endif + { RGFW_eventWait_forceStop[0], POLLIN, 0 }, + #if defined(__linux__) + { -1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0} + #endif + }; + + u8 index = 2; #ifdef RGFW_X11 -#undef RGFW_FUNC /* remove previous define */ -#define RGFW_FUNC(func) func##_Wayland -#else -#define RGFW_FUNC(func) func + index++; #endif -/* -Wayland TODO: (out of date) -- fix RGFW_keyPressed lock state + #if defined(__linux__) || defined(__NetBSD__) + for (i = 0; i < RGFW_gamepadCount; i++) { + if (RGFW_gamepads[i] == 0) + continue; - RGFW_windowMoved, the window was moved (by the user) - RGFW_windowRefresh The window content needs to be refreshed - - RGFW_dataDrop a file has been dropped into the window - RGFW_dataDrag - -- window args: - #define RGFW_windowNoResize the window cannot be resized by the user - #define RGFW_windowAllowDND the window supports drag and drop - #define RGFW_scaleToMonitor scale the window to the screen - -- other missing functions functions ("TODO wayland") (~30 functions) -- fix buffer rendering weird behavior -*/ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct wl_display* RGFW_getDisplay_Wayland(void) { return _RGFW->wl_display; } -struct wl_surface* RGFW_window_getWindow_Wayland(RGFW_window* win) { return win->src.surface; } - - -/* wayland global garbage (wayland bad, X11 is fine (ish) (not really)) */ -#include "xdg-shell.h" -#include "xdg-toplevel-icon-v1.h" -#include "xdg-decoration-unstable-v1.h" -#include "relative-pointer-unstable-v1.h" -#include "pointer-constraints-unstable-v1.h" -#include "xdg-output-unstable-v1.h" - - -void RGFW_toggleWaylandMaximized(RGFW_window* win, RGFW_bool maximized); - -static void RGFW_wl_setOpaque(RGFW_window* win) { - struct wl_region* wl_region = wl_compositor_create_region(_RGFW->compositor); - - if (!wl_region) return; /* return if no region was created */ - - wl_region_add(wl_region, 0, 0, win->w, win->h); - wl_surface_set_opaque_region(win->src.surface, wl_region); - wl_region_destroy(wl_region); - -} - -static void RGFW_wl_xdg_wm_base_ping_handler(void* data, struct xdg_wm_base* wm_base, - u32 serial) { - RGFW_UNUSED(data); - xdg_wm_base_pong(wm_base, serial); -} -static void RGFW_wl_xdg_surface_configure_handler(void* data, struct xdg_surface* xdg_surface, - u32 serial) { - - xdg_surface_ack_configure(xdg_surface, serial); - - RGFW_window* win = (RGFW_window*)data; - - if (win == NULL) { - win = _RGFW->kbOwner; - if (win == NULL) - return; - } - - /* useful for libdecor */ - if (win->src.activated != win->src.pending_activated) { - win->src.activated = win->src.pending_activated; - } - - if (win->src.maximized != win->src.pending_maximized) { - RGFW_toggleWaylandMaximized(win, win->src.pending_maximized); - - RGFW_window_checkMode(win); - } - - - if (win->src.resizing) { - - /* Do not create a resize event if the window is maximized */ - if (!win->src.maximized && win->internal.enabledEvents & RGFW_windowResizedFlag) { - RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e.common.win = win); - RGFW_windowResizedCallback(win, win->w, win->h); + fds[index].fd = RGFW_gamepads[i]; + index++; } - RGFW_window_resize(win, win->w, win->h); - if (!(win->internal.flags & RGFW_windowTransparent)) { - RGFW_wl_setOpaque(win); - } - } - -} - -static void RGFW_wl_xdg_toplevel_configure_handler(void* data, struct xdg_toplevel* toplevel, - i32 width, i32 height, struct wl_array* states) { - - RGFW_UNUSED(toplevel); - RGFW_window* win = (RGFW_window*)data; - - - win->src.pending_activated = RGFW_FALSE; - win->src.pending_maximized = RGFW_FALSE; - win->src.resizing = RGFW_FALSE; - - - enum xdg_toplevel_state* state; - wl_array_for_each(state, states) { - switch (*state) { - case XDG_TOPLEVEL_STATE_ACTIVATED: - win->src.pending_activated = RGFW_TRUE; - break; - case XDG_TOPLEVEL_STATE_MAXIMIZED: - win->src.pending_maximized = RGFW_TRUE; - break; - default: - break; - } - - } - /* if width and height are not zero and are not the same as the window */ - /* the window is resizing so update the values */ - if ((width && height) && (win->w != width || win->h != height)) { - win->src.resizing = RGFW_TRUE; - win->src.w = win->w = width; - win->src.h = win->h = height; - } -} - -static void RGFW_wl_xdg_toplevel_close_handler(void* data, struct xdg_toplevel *toplevel) { - RGFW_UNUSED(toplevel); - RGFW_window* win = (RGFW_window*)data; - - if (!win->internal.shouldClose) { - RGFW_eventQueuePushEx(e.type = RGFW_quit; e.common.win = win); - RGFW_window_setShouldClose(win, RGFW_TRUE); - RGFW_windowQuitCallback(win); - } -} - -static void RGFW_wl_xdg_decoration_configure_handler(void* data, - struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, u32 mode) { - RGFW_window* win = (RGFW_window*)data; RGFW_UNUSED(zxdg_toplevel_decoration_v1); - - /* this is expected to run once */ - /* set the decoration mode set by earlier request */ - if (mode != win->src.decoration_mode) { - win->src.decoration_mode = mode; - } -} - -static void RGFW_wl_shm_format_handler(void* data, struct wl_shm *shm, u32 format) { - RGFW_UNUSED(data); RGFW_UNUSED(shm); RGFW_UNUSED(format); -} - -static void RGFW_wl_relative_pointer_motion(void *data, struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1, - u32 time_hi, u32 time_lo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel) { - - RGFW_UNUSED(zwp_relative_pointer_v1); RGFW_UNUSED(time_hi); RGFW_UNUSED(time_lo); - RGFW_UNUSED(dx_unaccel); RGFW_UNUSED(dy_unaccel); - - RGFW_info* RGFW = (RGFW_info*)data; - RGFW_window* win = RGFW->mouseOwner; - - RGFW_ASSERT(win); - - float vecX = (float)wl_fixed_to_double(dx); - float vecY = (float)wl_fixed_to_double(dy); - - RGFW_eventQueuePushEx(e.type = RGFW_mousePosChanged; - e.mouse.x = win->internal.lastMouseX; - e.mouse.y = win->internal.lastMouseY; - e.mouse.vecX = vecX; - e.mouse.vecY = vecY; - e.common.win = win); - - RGFW->vectorX = vecX; - RGFW->vectorY = vecY; - RGFW_mousePosCallback(win, win->internal.lastMouseX, win->internal.lastMouseY, vecX, vecY); -} - -static void RGFW_wl_pointer_locked(void *data, struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1) { - RGFW_UNUSED(zwp_locked_pointer_v1); - RGFW_info* RGFW = (RGFW_info*)data; - RGFW_window* win = RGFW->mouseOwner; - - win->internal.lastMouseX = win->w / 2; - win->internal.lastMouseY = win->h / 2; - zwp_locked_pointer_v1_set_cursor_position_hint(win->src.locked_pointer, wl_fixed_from_int((win->w / 2)), wl_fixed_from_int((win->h / 2))); - wl_pointer_set_cursor(RGFW->wl_pointer, RGFW->mouse_enter_serial, NULL, 0, 0); /* draw no cursor */ -} - -static void RGFW_wl_pointer_enter(void* data, struct wl_pointer* pointer, u32 serial, - struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { - RGFW_info* RGFW = (RGFW_info*)data; - RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); - - /* save when the pointer is locked or using default cursor */ - RGFW->mouse_enter_serial = serial; - win->internal.mouseInside = RGFW_TRUE; - RGFW->windowState.win = win; - RGFW->windowState.mouseEnter = RGFW_TRUE; - - RGFW->mouseOwner = win; - - /* set the cursor */ - if (win->src.using_custom_cursor) { - wl_pointer_set_cursor(pointer, serial, win->src.custom_cursor_surface, 0, 0); - } - else { - RGFW_window_setMouseDefault(win); - } - - if (!(win->internal.enabledEvents & RGFW_mouseEnterFlag)) return; - - i32 x = (i32)wl_fixed_to_double(surface_x); - i32 y = (i32)wl_fixed_to_double(surface_y); - - RGFW_eventQueuePushEx(e.type = RGFW_mouseEnter; - e.mouse.x = x; - e.mouse.y = y; - e.common.win = win); - - win->internal.lastMouseX = x; - win->internal.lastMouseY = y; - - RGFW_mouseNotifyCallback(win, x, y, RGFW_TRUE); -} - -static void RGFW_wl_pointer_leave(void* data, struct wl_pointer *pointer, u32 serial, struct wl_surface *surface) { - RGFW_UNUSED(pointer); RGFW_UNUSED(serial); - RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); - RGFW_info* RGFW = (RGFW_info*)data; - if (RGFW->mouseOwner == win) - RGFW->mouseOwner = NULL; - - win->internal.mouseInside = RGFW_FALSE; - RGFW->windowState.winLeave = win; - RGFW->windowState.mouseLeave = RGFW_TRUE; - - if (!(win->internal.enabledEvents & RGFW_mouseLeaveFlag)) return; - - RGFW_eventQueuePushEx(e.type = RGFW_mouseLeave; - e.mouse.x = win->internal.lastMouseX; - e.mouse.y = win->internal.lastMouseY; - e.common.win = win); - - RGFW_mouseNotifyCallback(win, win->internal.lastMouseX, win->internal.lastMouseY, RGFW_FALSE); -} - -static void RGFW_wl_pointer_motion(void* data, struct wl_pointer *pointer, u32 time, wl_fixed_t x, wl_fixed_t y) { - RGFW_UNUSED(pointer); RGFW_UNUSED(time); - - RGFW_info* RGFW = (RGFW_info*)data; - RGFW_ASSERT(RGFW->mouseOwner != NULL); - - RGFW_window* win = RGFW->mouseOwner; - - if (!(win->internal.enabledEvents & RGFW_mousePosChangedFlag)) return; - - i32 convertedX = (i32)wl_fixed_to_double(x); - i32 convertedY = (i32)wl_fixed_to_double(y); - float newVecX = (float)(convertedX - win->internal.lastMouseX); - float newVecY = (float)(convertedY - win->internal.lastMouseY); - - RGFW_eventQueuePushEx(e.type = RGFW_mousePosChanged; - e.mouse.x = convertedX; - e.mouse.y = convertedY; - e.mouse.vecX = newVecX; - e.mouse.vecY = newVecY; - e.common.win = win); - - RGFW->vectorX = newVecX; - RGFW->vectorY = newVecY; - win->internal.lastMouseX = convertedX; - win->internal.lastMouseY = convertedY; - RGFW_mousePosCallback(win, convertedX, convertedY, newVecX, newVecY); -} - -static void RGFW_wl_pointer_button(void* data, struct wl_pointer *pointer, u32 serial, u32 time, u32 button, u32 state) { - RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(serial); - RGFW_info* RGFW = (RGFW_info*)data; - - RGFW_ASSERT(RGFW->mouseOwner != NULL); - RGFW_window* win = RGFW->mouseOwner; - - if (!(win->internal.enabledEvents & (RGFW_BIT(RGFW_mouseButtonReleased - RGFW_BOOL(state))))) return; - u32 b = (button - 0x110); - - /* flip right and middle button codes */ - if (b == 1) b = 2; - else if (b == 2) b = 1; - - RGFW->mouseButtons[b].prev = RGFW->mouseButtons[b].current; - RGFW->mouseButtons[b].current = RGFW_BOOL(state); - - RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonReleased - RGFW_BOOL(state); - e.button.value = (u8)b; - e.common.win = win); - RGFW_mouseButtonCallback(win, (u8)b, RGFW_BOOL(state)); -} - -static void RGFW_wl_pointer_axis(void* data, struct wl_pointer *pointer, u32 time, u32 axis, wl_fixed_t value) { - RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(axis); - - RGFW_info* RGFW = (RGFW_info*)data; - RGFW_ASSERT(RGFW->mouseOwner != NULL); - RGFW_window* win = RGFW->mouseOwner; - - float scrollX = 0.0; - float scrollY = 0.0; - - if (!(win->internal.enabledEvents & (RGFW_BIT(RGFW_mouseScroll)))) return; - - if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) - scrollX = (float)(-wl_fixed_to_double(value) / 10.0); - else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) - scrollY = (float)(-wl_fixed_to_double(value) / 10.0); - - - RGFW->scrollX = (float)scrollX; - RGFW->scrollY = (float)scrollY; - RGFW_mouseScrollCallback(win, scrollX, scrollY); - RGFW_eventQueuePushEx(e.type = RGFW_mouseScroll; - e.scroll.x = scrollX; - e.scroll.y = scrollY; - e.common.win = win); -} - - -static void RGFW_doNothing(void) { } - -static void RGFW_wl_keyboard_keymap(void* data, struct wl_keyboard *keyboard, u32 format, i32 fd, u32 size) { - RGFW_UNUSED(keyboard); RGFW_UNUSED(format); - RGFW_info* RGFW = (RGFW_info*)data; - - char *keymap_string = mmap (NULL, size, PROT_READ, MAP_SHARED, fd, 0); - xkb_keymap_unref(RGFW->keymap); - RGFW->keymap = xkb_keymap_new_from_string(RGFW->xkb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); - - munmap(keymap_string, size); - close(fd); - xkb_state_unref(RGFW->xkb_state); - RGFW->xkb_state = xkb_state_new(RGFW->keymap); -} - -static void RGFW_wl_keyboard_enter(void* data, struct wl_keyboard *keyboard, u32 serial, struct wl_surface *surface, struct wl_array *keys) { - RGFW_UNUSED(keyboard); RGFW_UNUSED(keys); - - RGFW_info* RGFW = (RGFW_info*)data; - RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); - RGFW->kbOwner = win; - - // this is to prevent race conditions - if (RGFW->data_device != NULL && win->src.data_source != NULL) { - wl_data_device_set_selection(RGFW->data_device, win->src.data_source, serial); - } - if (!(win->internal.enabledEvents & RGFW_focusInFlag)) return; - - /* is set when RGFW_window_minimize is called; if the minimize button is */ - /* pressed this flag is not set since there is no event to listen for */ - if (win->src.minimized == RGFW_TRUE) win->src.minimized = RGFW_FALSE; - - win->internal.inFocus = RGFW_TRUE; - RGFW_eventQueuePushEx(e.type = RGFW_focusIn; e.common.win = win); - RGFW_focusCallback(win, RGFW_TRUE); - - if ((win->internal.holdMouse)) RGFW_window_holdMouse(win); -} - -static void RGFW_wl_keyboard_leave(void* data, struct wl_keyboard *keyboard, u32 serial, struct wl_surface *surface) { - RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); - - RGFW_info* RGFW = (RGFW_info*)data; - RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); - if (RGFW->kbOwner == win) - RGFW->kbOwner = NULL; - - if (!(win->internal.enabledEvents & RGFW_focusOutFlag)) return; - - RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e.common.win = win); - RGFW_focusCallback(win, RGFW_FALSE); - RGFW_window_focusLost(win); -} - -static void RGFW_wl_keyboard_key(void* data, struct wl_keyboard *keyboard, u32 serial, u32 time, u32 key, u32 state) { - RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); - - RGFW_info* RGFW = (RGFW_info*)data; - if (RGFW->kbOwner == NULL) return; - - RGFW_window *RGFW_key_win = RGFW->kbOwner; - if (!(RGFW_key_win->internal.enabledEvents & (RGFW_BIT(RGFW_keyPressed + state)))) return; - - xkb_keysym_t keysym = xkb_state_key_get_one_sym(RGFW->xkb_state, key + 8); - - u32 RGFWkey = RGFW_apiKeyToRGFW(key + 8); - RGFW->keyboard[RGFWkey].prev = RGFW->keyboard[RGFWkey].current; - RGFW->keyboard[RGFWkey].current = RGFW_BOOL(state); - - RGFW_eventQueuePushEx(e.type = (u8)(RGFW_keyPressed + state); - e.key.value = (u8)RGFWkey; - e.key.sym = (u8)keysym; - e.key.repeat = RGFW_window_isKeyDown(RGFW_key_win, (u8)RGFWkey); - e.common.win = RGFW_key_win); - - RGFW_updateKeyMods(RGFW_key_win, RGFW_BOOL(xkb_keymap_mod_get_index(RGFW->keymap, "Lock")), RGFW_BOOL(xkb_keymap_mod_get_index(RGFW->keymap, "Mod2")), RGFW_BOOL(xkb_keymap_mod_get_index(RGFW->keymap, "ScrollLock"))); - RGFW_keyCallback(RGFW_key_win, (u8)RGFWkey, (u8)keysym, RGFW_key_win->internal.mod, RGFW_window_isKeyDown(RGFW_key_win, (u8)RGFWkey), RGFW_BOOL(state)); -} - -static void RGFW_wl_keyboard_modifiers(void* data, struct wl_keyboard *keyboard, u32 serial, u32 mods_depressed, u32 mods_latched, u32 mods_locked, u32 group) { - RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); - RGFW_info* RGFW = (RGFW_info*)data; - xkb_state_update_mask(RGFW->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); -} - -static void RGFW_wl_seat_capabilities(void* data, struct wl_seat *seat, u32 capabilities) { - RGFW_info* RGFW = (RGFW_info*)data; - static struct wl_pointer_listener pointer_listener; - RGFW_MEMSET(&pointer_listener, 0, sizeof(pointer_listener)); - pointer_listener.enter = &RGFW_wl_pointer_enter; - pointer_listener.leave = &RGFW_wl_pointer_leave; - pointer_listener.motion = &RGFW_wl_pointer_motion; - pointer_listener.button = &RGFW_wl_pointer_button; - pointer_listener.axis = &RGFW_wl_pointer_axis; - - static struct wl_keyboard_listener keyboard_listener; - RGFW_MEMSET(&keyboard_listener, 0, sizeof(keyboard_listener)); - keyboard_listener.keymap = &RGFW_wl_keyboard_keymap; - keyboard_listener.enter = &RGFW_wl_keyboard_enter; - keyboard_listener.leave = &RGFW_wl_keyboard_leave; - keyboard_listener.key = &RGFW_wl_keyboard_key; - keyboard_listener.modifiers = &RGFW_wl_keyboard_modifiers; - - if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && !RGFW->wl_pointer) { - RGFW->wl_pointer = wl_seat_get_pointer(seat); - wl_pointer_add_listener(RGFW->wl_pointer, &pointer_listener, RGFW); - } - if ((capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && !RGFW->wl_keyboard) { - RGFW->wl_keyboard = wl_seat_get_keyboard(seat); - wl_keyboard_add_listener(RGFW->wl_keyboard, &keyboard_listener, RGFW); - } - - if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && RGFW->wl_pointer) { - wl_pointer_destroy(RGFW->wl_pointer); - } - if (!(capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && RGFW->wl_keyboard) { - wl_keyboard_destroy(RGFW->wl_keyboard); - } -} - -static void RGFW_wl_output_set_geometry(void *data, struct wl_output *wl_output, - int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, - int32_t subpixel, const char *make, const char *model, int32_t transform) { - - RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; - monitor->x = x; - monitor->y = y; - - monitor->physW = (float)physical_width / 25.4f; - monitor->physH = (float)physical_height / 25.4f; - - RGFW_UNUSED(wl_output); - RGFW_UNUSED(subpixel); - RGFW_UNUSED(make); - RGFW_UNUSED(model); - RGFW_UNUSED(transform); -} - -static void RGFW_wl_output_set_mode(void *data, struct wl_output *wl_output, uint32_t flags, - int32_t width, int32_t height, int32_t refresh) { - - RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; - - monitor->mode.w = width; - monitor->mode.h = height; - monitor->mode.refreshRate = (u32)RGFW_ROUND( ((float)refresh / 1000) ); - RGFW_UNUSED(width); - RGFW_UNUSED(height); - RGFW_UNUSED(wl_output); - RGFW_UNUSED(flags); -} - -static void RGFW_wl_output_set_scale(void *data, struct wl_output *wl_output, int32_t factor) { - /* this is for pixelRatio */ - RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; - - monitor->pixelRatio = (float)factor; - RGFW_UNUSED(wl_output); -} - -static void RGFW_wl_output_set_name(void *data, struct wl_output *wl_output, const char *name) { - RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; - - RGFW_STRNCPY(monitor->name, name, sizeof(monitor->name) - 1); - monitor->name[sizeof(monitor->name) - 1] = '\0'; - - RGFW_UNUSED(wl_output); - -} - -static void RGFW_xdg_output_logical_pos(void *data, struct zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y) { - RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; - monitor->x = x; - monitor->y = y; - RGFW_UNUSED(zxdg_output_v1); -} - -static void RGFW_xdg_output_logical_size(void *data, struct zxdg_output_v1 *zxdg_output_v1, int32_t width, int32_t height) { - RGFW_monitor* monitor = &((RGFW_monitorNode*)data)->mon; - - float mon_float_width = (float) monitor->mode.w; - float mon_float_height = (float) monitor->mode.h; - - monitor->scaleX = (mon_float_width / (float) width); - monitor->scaleY = (mon_float_height / (float) height); - - /* under xwayland the monitor changes w & h when compositor scales it */ - monitor->mode.w = width; - monitor->mode.h = height; - RGFW_UNUSED(zxdg_output_v1); -} - -static void RGFW_wl_create_outputs(struct wl_registry *const registry, uint32_t id) { - struct wl_output *output = wl_registry_bind(registry, id, &wl_output_interface, wl_display_get_version(_RGFW->wl_display) < 4 ? 3 : 4); - RGFW_monitorNode* node; - RGFW_monitor mon; - - if (!output) return; - - char RGFW_mon_default_name[10]; - - RGFW_SNPRINTF(RGFW_mon_default_name, sizeof(RGFW_mon_default_name), "monitor-%li", _RGFW->monitors.count); - RGFW_STRNCPY(mon.name, RGFW_mon_default_name, sizeof(mon.name) - 1); - mon.name[sizeof(mon.name) - 1] = '\0'; - - /* set in case compositor does not send one */ - /* or no xdg_output support */ - mon.scaleY = mon.scaleX = mon.pixelRatio = 1.0f; - - node = RGFW_monitors_add(mon); - if (node == NULL) return; - - node->id = id; - node->output = output; - - static const struct wl_output_listener wl_output_listener = { - .geometry = RGFW_wl_output_set_geometry, - .mode = RGFW_wl_output_set_mode, - .done = (void (*)(void *,struct wl_output *))&RGFW_doNothing, - .scale = RGFW_wl_output_set_scale, - .name = RGFW_wl_output_set_name, - .description = (void (*)(void *, struct wl_output *, const char *))&RGFW_doNothing - }; - - /* the wl_output will have a reference to the node */ - wl_output_set_user_data(output, node); - - /* pass the monitor so we can access it in the callback functions */ - wl_output_add_listener(output, &wl_output_listener, node); - - if (!_RGFW->xdg_output_manager) return; /* compositor does not support it */ - - static const struct zxdg_output_v1_listener xdg_output_listener = { - .name = (void (*)(void *,struct zxdg_output_v1 *, const char *))&RGFW_doNothing, - .done = (void (*)(void *,struct zxdg_output_v1 *))&RGFW_doNothing, - .description = (void (*)(void *,struct zxdg_output_v1 *, const char *))&RGFW_doNothing, - .logical_position = RGFW_xdg_output_logical_pos, - .logical_size = RGFW_xdg_output_logical_size - }; - - node->xdg_output = zxdg_output_manager_v1_get_xdg_output(_RGFW->xdg_output_manager, node->output); - zxdg_output_v1_add_listener(node->xdg_output, &xdg_output_listener, node); -} - -static void RGFW_wl_surface_enter(void *data, struct wl_surface *wl_surface, struct wl_output *output) { - RGFW_UNUSED(wl_surface); - - RGFW_window* win = (RGFW_window*)data; - RGFW_monitorNode* node = wl_output_get_user_data(output); - win->src.active_monitor = node->mon; - - #ifndef RGFW_NO_MONITOR - if (win->internal.flags & RGFW_windowScaleToMonitor) - RGFW_window_scaleToMonitor(win); #endif -} -static void RGFW_wl_data_source_send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, int32_t fd) { - RGFW_UNUSED(data); RGFW_UNUSED(wl_data_source); - // a client can accept our clipboard - if (RGFW_STRNCMP(mime_type, "text/plain;charset=utf-8", 25) == 0) { - // do not write \0 - write(fd, _RGFW->clipboard, _RGFW->clipboard_len - 1); + u64 start = RGFW_getTimeNS(); + + + #ifdef RGFW_WAYLAND + while (wl_display_dispatch(win->src.wl_display) <= 0 + #else + while (XPending(win->src.display) == 0 + #endif + #ifdef RGFW_X11 + && XPending(_RGFW.display) == 0 + #endif + ) { + if (poll(fds, index, waitMS) <= 0) + break; + + if (waitMS != RGFW_eventWaitNext) + waitMS -= (i32)(RGFW_getTimeNS() - start) / (i32)1e+6; } - close(fd); -} + /* drain any data in the stop request */ + if (RGFW_eventWait_forceStop[2]) { + char data[64]; + (void)!read(RGFW_eventWait_forceStop[0], data, sizeof(data)); -static void RGFW_wl_data_source_cancelled(void *data, struct wl_data_source *wl_data_source) { - - RGFW_info* RGFW = (RGFW_info*)data; - - if (RGFW->kbOwner->src.data_source == wl_data_source) { - RGFW->kbOwner->src.data_source = NULL; - } - - wl_data_source_destroy(wl_data_source); - -} - -static void RGFW_wl_data_device_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *wl_data_offer) { - - RGFW_UNUSED(data); RGFW_UNUSED(wl_data_device); - static const struct wl_data_offer_listener wl_data_offer_listener = { - .offer = (void (*)(void *data, struct wl_data_offer *wl_data_offer, const char *))RGFW_doNothing, - .source_actions = (void (*)(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action))RGFW_doNothing, - .action = (void (*)(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action))RGFW_doNothing - }; - wl_data_offer_add_listener(wl_data_offer, &wl_data_offer_listener, NULL); -} - -static void RGFW_wl_data_device_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *wl_data_offer) { - RGFW_UNUSED(data); RGFW_UNUSED(wl_data_device); - /* Clipboard is empty */ - if (wl_data_offer == NULL) { - return; - } - - int pfds[2]; - pipe(pfds); - - wl_data_offer_receive(wl_data_offer, "text/plain;charset=utf-8", pfds[1]); - close(pfds[1]); - - wl_display_roundtrip(_RGFW->wl_display); - - char buf[1024]; - - ssize_t n = read(pfds[0], buf, sizeof(buf)); - - _RGFW->clipboard = (char*)RGFW_ALLOC((size_t)n); - RGFW_ASSERT(_RGFW->clipboard != NULL); - RGFW_STRNCPY(_RGFW->clipboard, buf, (size_t)n); - - _RGFW->clipboard_len = (size_t)n + 1; - - close(pfds[0]); - - wl_data_offer_destroy(wl_data_offer); - -} - -static void RGFW_wl_global_registry_handler(void* data, struct wl_registry *registry, u32 id, const char *interface, u32 version) { - - static struct wl_seat_listener seat_listener = {&RGFW_wl_seat_capabilities, (void (*)(void *, struct wl_seat *, const char *))&RGFW_doNothing}; - static const struct wl_shm_listener shm_listener = { .format = RGFW_wl_shm_format_handler }; - - RGFW_info* RGFW = (RGFW_info*)data; - RGFW_UNUSED(version); - - if (RGFW_STRNCMP(interface, "wl_compositor", 16) == 0) { - RGFW->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 4); - } else if (RGFW_STRNCMP(interface, "xdg_wm_base", 12) == 0) { - RGFW->xdg_wm_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1); - } else if (RGFW_STRNCMP(interface, zxdg_decoration_manager_v1_interface.name, 255) == 0) { - RGFW->decoration_manager = wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1); - } else if (RGFW_STRNCMP(interface, zwp_pointer_constraints_v1_interface.name, 255) == 0) { - RGFW->constraint_manager = wl_registry_bind(registry, id, &zwp_pointer_constraints_v1_interface, 1); - } else if (RGFW_STRNCMP(interface, zwp_relative_pointer_manager_v1_interface.name, 255) == 0) { - RGFW->relative_pointer_manager = wl_registry_bind(registry, id, &zwp_relative_pointer_manager_v1_interface, 1); - } else if (RGFW_STRNCMP(interface, xdg_toplevel_icon_manager_v1_interface.name, 255) == 0) { - RGFW->icon_manager = wl_registry_bind(registry, id, &xdg_toplevel_icon_manager_v1_interface, 1); - } else if (RGFW_STRNCMP(interface, "wl_shm", 7) == 0) { - RGFW->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); - wl_shm_add_listener(RGFW->shm, &shm_listener, RGFW); - } else if (RGFW_STRNCMP(interface,"wl_seat", 8) == 0) { - RGFW->seat = wl_registry_bind(registry, id, &wl_seat_interface, 1); - wl_seat_add_listener(RGFW->seat, &seat_listener, RGFW); - } else if (RGFW_STRNCMP(interface, zxdg_output_manager_v1_interface.name, 255) == 0) { - RGFW->xdg_output_manager = wl_registry_bind(registry, id, &zxdg_output_manager_v1_interface, 1); - } else if (RGFW_STRNCMP(interface,"wl_output", 10) == 0) { - RGFW_wl_create_outputs(registry, id); - } else if (RGFW_STRNCMP(interface,"wl_data_device_manager", 23) == 0) { - RGFW->data_device_manager = wl_registry_bind(registry, id, &wl_data_device_manager_interface, 1); + RGFW_eventWait_forceStop[2] = 0; } } -static void RGFW_wl_global_registry_remove(void* data, struct wl_registry *registry, u32 id) { - RGFW_UNUSED(data); RGFW_UNUSED(registry); - RGFW_info* RGFW = (RGFW_info*)data; - RGFW_monitorNode* prev = RGFW->monitors.list.head; - RGFW_monitorNode* node = NULL; - if (prev == NULL) return; +i32 RGFW_getClock(void); +i32 RGFW_getClock(void) { + static i32 clock = -1; + if (clock != -1) return clock; - if (prev->id != id) { - /* find the first node that has a matching id */ - while(prev->next != NULL && prev->next->id != id) { - prev = prev->next; - } + #if defined(_POSIX_MONOTONIC_CLOCK) + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) + clock = CLOCK_MONOTONIC; + #else + clock = CLOCK_REALTIME; + #endif - if (prev->next == NULL) return; - node = prev->next; - } else { - node = prev; - } - - if (node->output) { - wl_output_destroy(node->output); - } - - if (node->xdg_output) { - zxdg_output_v1_destroy(node->xdg_output); - } - - RGFW_monitors_remove(node, prev); + return clock; } -static void RGFW_wl_randname(char *buf) { +u64 RGFW_getTimerFreq(void) { return 1000000000LLU; } +u64 RGFW_getTimerValue(void) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); - long r = ts.tv_nsec; - - int i; - for (i = 0; i < 6; ++i) { - buf[i] = (char)('A'+(r&15)+(r&16)*2); - r >>= 5; - } + return (u64)ts.tv_sec * RGFW_getTimerFreq() + (u64)ts.tv_nsec; } +#endif /* end of wayland or X11 defines */ -static size_t RGFW_wl_stringlen(char* name) { - size_t i = 0; - while (name[i]) { i++; } - return i; -} -static int RGFW_wl_anonymous_shm_open(void) { - char name[] = "/RGFW-wayland-XXXXXX"; - int retries = 100; - - do { - RGFW_wl_randname(name + RGFW_wl_stringlen(name) - 6); - - --retries; - /* shm_open guarantees that O_CLOEXEC is set */ - int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); - if (fd >= 0) { - shm_unlink(name); - return fd; - } - } while (retries > 0 && errno == EEXIST); - - return -1; -} - -static int RGFW_wl_create_shm_file(off_t size) { - int fd = RGFW_wl_anonymous_shm_open(); - if (fd < 0) { - return fd; - } - - if (ftruncate(fd, size) < 0) { - close(fd); - return -1; - } - - return fd; -} - -i32 RGFW_initPlatform_Wayland(void) { - _RGFW->wl_display = wl_display_connect(NULL); - if (_RGFW->wl_display == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errWayland, "Failed to load Wayland display"); - return -1; - } - - _RGFW->compositor = NULL; - static const struct wl_registry_listener registry_listener = { - .global = RGFW_wl_global_registry_handler, - .global_remove = RGFW_wl_global_registry_remove, - }; - - _RGFW->registry = wl_display_get_registry(_RGFW->wl_display); - wl_registry_add_listener(_RGFW->registry, ®istry_listener, _RGFW); - - wl_display_roundtrip(_RGFW->wl_display); /* bind to globals */ - - if (_RGFW->compositor == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errWayland, "Can't find compositor."); - return 1; - } - - if (_RGFW->wl_cursor_theme == NULL) { - _RGFW->wl_cursor_theme = wl_cursor_theme_load(NULL, 24, _RGFW->shm); - _RGFW->cursor_surface = wl_compositor_create_surface(_RGFW->compositor); - } - - u8 RGFW_blk[] = { 0, 0, 0, 0 }; - _RGFW->hiddenMouse = RGFW_loadMouse(RGFW_blk, 1, 1, RGFW_formatRGBA8); - - static const struct xdg_wm_base_listener xdg_wm_base_listener = { - .ping = RGFW_wl_xdg_wm_base_ping_handler, - }; - - xdg_wm_base_add_listener(_RGFW->xdg_wm_base, &xdg_wm_base_listener, NULL); - - _RGFW->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - - static const struct wl_data_device_listener wl_data_device_listener = { - .data_offer = RGFW_wl_data_device_data_offer, - .enter = (void (*)(void *, struct wl_data_device *, u32, struct wl_surface*, wl_fixed_t, wl_fixed_t, struct wl_data_offer *))&RGFW_doNothing, - .leave = (void (*)(void *, struct wl_data_device *))&RGFW_doNothing, - .motion = (void (*)(void *, struct wl_data_device *, u32, wl_fixed_t, wl_fixed_t))&RGFW_doNothing, - .drop = (void (*)(void *, struct wl_data_device *))&RGFW_doNothing, - .selection = RGFW_wl_data_device_selection - }; - - if (_RGFW->seat && _RGFW->data_device_manager) { - _RGFW->data_device = wl_data_device_manager_get_data_device(_RGFW->data_device_manager, _RGFW->seat); - wl_data_device_add_listener(_RGFW->data_device, &wl_data_device_listener, NULL); - } - - return 0; -} - -void RGFW_deinitPlatform_Wayland(void) { - if (_RGFW->clipboard) { - RGFW_FREE(_RGFW->clipboard); - _RGFW->clipboard = NULL; - } - - if (_RGFW->wl_pointer) { - wl_pointer_destroy(_RGFW->wl_pointer); - } - if (_RGFW->wl_keyboard) { - wl_keyboard_destroy(_RGFW->wl_keyboard); - } - - wl_registry_destroy(_RGFW->registry); - if (_RGFW->decoration_manager != NULL) - zxdg_decoration_manager_v1_destroy(_RGFW->decoration_manager); - if (_RGFW->relative_pointer_manager != NULL) { - zwp_relative_pointer_manager_v1_destroy(_RGFW->relative_pointer_manager); - } - - if (_RGFW->relative_pointer) { - zwp_relative_pointer_v1_destroy(_RGFW->relative_pointer); - } - - if (_RGFW->constraint_manager != NULL) { - zwp_pointer_constraints_v1_destroy(_RGFW->constraint_manager); - } - - if (_RGFW->xdg_output_manager != NULL) - if (_RGFW->icon_manager != NULL) { - xdg_toplevel_icon_manager_v1_destroy(_RGFW->icon_manager); - } - - if (_RGFW->xdg_output_manager) { - zxdg_output_manager_v1_destroy(_RGFW->xdg_output_manager); - } - - if (_RGFW->data_device_manager) { - wl_data_device_manager_destroy(_RGFW->data_device_manager); - } - - if (_RGFW->data_device) { - wl_data_device_destroy(_RGFW->data_device); - } - - if (_RGFW->wl_cursor_theme != NULL) { - wl_cursor_theme_destroy(_RGFW->wl_cursor_theme); - } - - RGFW_freeMouse(_RGFW->hiddenMouse); - - RGFW_monitorNode* node = _RGFW->monitors.list.head; - - while (node != NULL) { - if (node->output) { - wl_output_destroy(node->output); - } - - if (node->xdg_output) { - zxdg_output_v1_destroy(node->xdg_output); - } - - _RGFW->monitors.count -= 1; - node = node->next; - - } - - wl_surface_destroy(_RGFW->cursor_surface); - wl_shm_destroy(_RGFW->shm); - wl_seat_release(_RGFW->seat); - xdg_wm_base_destroy(_RGFW->xdg_wm_base); - wl_compositor_destroy(_RGFW->compositor); - wl_display_disconnect(_RGFW->wl_display); -} - -RGFW_bool RGFW_FUNC(RGFW_createSurfacePtr) (u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { - RGFW_ASSERT(surface != NULL); - surface->data = data; - surface->w = w; - surface->h = h; - surface->format = format; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoBuffer, "Creating a 4 channel buffer"); - - u32 size = (u32)(surface->w * surface->h * 4); - int fd = RGFW_wl_create_shm_file(size); - if (fd < 0) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, "Failed to create a buffer."); - return RGFW_FALSE; - } - - surface->native.buffer = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (surface->native.buffer == MAP_FAILED) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, "mmap failed."); - return RGFW_FALSE; - } - - struct wl_shm_pool* pool = wl_shm_create_pool(_RGFW->shm, fd, (i32)size); - surface->native.wl_buffer = wl_shm_pool_create_buffer(pool, 0, (i32)surface->w, (i32)surface->h, (i32)surface->w * 4, WL_SHM_FORMAT_ARGB8888); - wl_shm_pool_destroy(pool); - - close(fd); - - surface->native.format = RGFW_formatBGRA8; - return RGFW_TRUE; -} - -void RGFW_FUNC(RGFW_window_blitSurface) (RGFW_window* win, RGFW_surface* surface) { - RGFW_ASSERT(surface != NULL); - RGFW_copyImageData(surface->native.buffer, win->w, RGFW_MIN(win->h, surface->h), surface->native.format, surface->data, surface->format); - - wl_surface_attach(win->src.surface, surface->native.wl_buffer, 0, 0); - wl_surface_damage(win->src.surface, 0, 0, RGFW_MIN(win->w, surface->w), RGFW_MIN(win->h, surface->h)); - wl_surface_commit(win->src.surface); -} - -void RGFW_FUNC(RGFW_surface_freePtr) (RGFW_surface* surface) { - RGFW_ASSERT(surface != NULL); - wl_buffer_destroy(surface->native.wl_buffer); - munmap(surface->native.buffer, (size_t)(surface->w * surface->h * 4)); -} - -void RGFW_FUNC(RGFW_window_setBorder) (RGFW_window* win, RGFW_bool border) { - RGFW_setBit(&win->internal.flags, RGFW_windowNoBorder, !border); - - /* for now just toggle between SSD & CSD depending on the bool */ - if (_RGFW->decoration_manager != NULL) { - zxdg_toplevel_decoration_v1_set_mode(win->src.decoration, (border ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE)); - } -} - -void RGFW_FUNC(RGFW_releaseCursor) (RGFW_window* win) { - RGFW_ASSERT(win); - /* compositor has no support or window is not locked do nothing */ - if (_RGFW->constraint_manager == NULL || _RGFW->relative_pointer_manager == NULL) return; - - if (win->src.locked_pointer != NULL) { - zwp_locked_pointer_v1_destroy(win->src.locked_pointer); - win->src.locked_pointer = NULL; - } - if (_RGFW->relative_pointer != NULL) { - zwp_relative_pointer_v1_destroy(_RGFW->relative_pointer); - _RGFW->relative_pointer = NULL; - } - - _RGFW->mouseOwner = win; /* unhold mouse sets this to null; set it back */ -} - -void RGFW_FUNC(RGFW_captureCursor) (RGFW_window* win) { - RGFW_ASSERT(win); - /* compositor has no support or window already is locked do nothing */ - if (_RGFW->constraint_manager == NULL || _RGFW->relative_pointer_manager == NULL) return; - - if (_RGFW->relative_pointer == NULL) { - _RGFW->relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(_RGFW->relative_pointer_manager, _RGFW->wl_pointer); - - static const struct zwp_relative_pointer_v1_listener relative_motion_listener = { - .relative_motion = RGFW_wl_relative_pointer_motion - }; - - zwp_relative_pointer_v1_add_listener(_RGFW->relative_pointer, &relative_motion_listener, _RGFW); - } - - if (win->src.locked_pointer == NULL) { - win->src.locked_pointer = zwp_pointer_constraints_v1_lock_pointer(_RGFW->constraint_manager, win->src.surface, _RGFW->wl_pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); - - static const struct zwp_locked_pointer_v1_listener locked_listener = { - .locked = RGFW_wl_pointer_locked, - .unlocked = (void (*)(void *, struct zwp_locked_pointer_v1 *))RGFW_doNothing - }; - - zwp_locked_pointer_v1_add_listener(win->src.locked_pointer, &locked_listener, _RGFW); - } -} - -RGFW_window* RGFW_FUNC(RGFW_createWindowPlatform) (const char* name, RGFW_windowFlags flags, RGFW_window* win) { - RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningWayland, "RGFW Wayland support is experimental"); - - static const struct xdg_surface_listener xdg_surface_listener = { - .configure = RGFW_wl_xdg_surface_configure_handler, - }; - - static const struct wl_surface_listener wl_surface_listener = { - .enter = RGFW_wl_surface_enter, - .leave = (void (*)(void *, struct wl_surface *, struct wl_output *))&RGFW_doNothing, - .preferred_buffer_scale = (void (*)(void *, struct wl_surface *, i32))&RGFW_doNothing, - .preferred_buffer_transform = (void (*)(void *, struct wl_surface *, u32))&RGFW_doNothing - }; - - win->src.surface = wl_compositor_create_surface(_RGFW->compositor); - wl_surface_add_listener(win->src.surface, &wl_surface_listener, win); - - /* create a surface for a custom cursor */ - win->src.custom_cursor_surface = wl_compositor_create_surface(_RGFW->compositor); - - win->src.xdg_surface = xdg_wm_base_get_xdg_surface(_RGFW->xdg_wm_base, win->src.surface); - xdg_surface_add_listener(win->src.xdg_surface, &xdg_surface_listener, win); - - xdg_wm_base_set_user_data(_RGFW->xdg_wm_base, win); - - win->src.xdg_toplevel = xdg_surface_get_toplevel(win->src.xdg_surface); - - xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->w, win->h); - - if (!(win->internal.flags & RGFW_windowTransparent)) { /* no transparency */ - RGFW_wl_setOpaque(win); - } - - static const struct xdg_toplevel_listener xdg_toplevel_listener = { - .configure = RGFW_wl_xdg_toplevel_configure_handler, - .close = RGFW_wl_xdg_toplevel_close_handler, - }; - - xdg_toplevel_add_listener(win->src.xdg_toplevel, &xdg_toplevel_listener, win); - - /* compositor supports both SSD & CSD - So choose accordingly - */ - if (_RGFW->decoration_manager) { - u32 decoration_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; - win->src.decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( - _RGFW->decoration_manager, win->src.xdg_toplevel); - - static const struct zxdg_toplevel_decoration_v1_listener xdg_decoration_listener = { - .configure = RGFW_wl_xdg_decoration_configure_handler - }; - - zxdg_toplevel_decoration_v1_add_listener(win->src.decoration, &xdg_decoration_listener, win); - - /* we want no decorations */ - if ((flags & RGFW_windowNoBorder)) { - decoration_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; - } - - zxdg_toplevel_decoration_v1_set_mode(win->src.decoration, decoration_mode); - - /* no xdg_decoration support */ - } else if (!(flags & RGFW_windowNoBorder)) { - /* TODO, some fallback */ - #ifdef RGFW_LIBDECOR - static struct libdecor_interface interface = { - .error = NULL, - }; - - static struct libdecor_frame_interface frameInterface = {0}; /*= { - RGFW_wl_handle_configure, - RGFW_wl_handle_close, - RGFW_wl_handle_commit, - RGFW_wl_handle_dismiss_popup, - };*/ - - win->src.decorContext = libdecor_new(_RGFW->wl_display, &interface); - if (win->src.decorContext) { - struct libdecor_frame *frame = libdecor_decorate(win->src.decorContext, win->src.surface, &frameInterface, win); - if (!frame) { - libdecor_unref(win->src.decorContext); - win->src.decorContext = NULL; - } else { - libdecor_frame_set_app_id(frame, "my-libdecor-app"); - libdecor_frame_set_title(frame, "My Libdecor Window"); - } - } - #endif - } - - if (_RGFW->icon_manager != NULL) { - /* set the default wayland icon */ - xdg_toplevel_icon_manager_v1_set_icon(_RGFW->icon_manager, win->src.xdg_toplevel, NULL); - } - - wl_surface_commit(win->src.surface); - wl_display_dispatch(_RGFW->wl_display); - RGFW_UNUSED(name); - - return win; -} - -RGFW_bool RGFW_FUNC(RGFW_getGlobalMouse) (i32* x, i32* y) { - RGFW_init(); - if (x) *x = 0; - if (y) *y = 0; - return RGFW_FALSE; -} - -u8 RGFW_FUNC(RGFW_rgfwToKeyChar)(u32 key) { - return (u8)key; -} - -void RGFW_FUNC(RGFW_pollEvents) (void) { - RGFW_resetPrevState(); - - /* send buffered requests to compositor */ - while (wl_display_flush(_RGFW->wl_display) == -1) { - /* compositor not responding to new requests */ - /* so let's dispatch some events so the compositor responds */ - if (errno == EAGAIN) { - if (wl_display_dispatch_pending(_RGFW->wl_display) == -1) { - return; - } - } else { - return; - } - } - - /* read the events; if empty this reads from the */ - /* wayland file descriptor */ - if (wl_display_dispatch(_RGFW->wl_display) == -1) { - return; - } - -} - -void RGFW_FUNC(RGFW_window_move) (RGFW_window* win, i32 x, i32 y) { - RGFW_ASSERT(win != NULL); - win->x = x; - win->y = y; -} - - -void RGFW_FUNC(RGFW_window_resize) (RGFW_window* win, i32 w, i32 h) { - RGFW_ASSERT(win != NULL); - win->w = w; - win->h = h; - if (_RGFW->compositor) { - xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->w, win->h); - #ifdef RGFW_OPENGL - if (win->src.ctx.egl) - wl_egl_window_resize(win->src.ctx.egl->eglWindow, (i32)w, (i32)h, 0, 0); - #endif - } -} - -void RGFW_FUNC(RGFW_window_setAspectRatio) (RGFW_window* win, i32 w, i32 h) { - RGFW_ASSERT(win != NULL); - - if (w == 0 && h == 0) - return; - xdg_toplevel_set_max_size(win->src.xdg_toplevel, (i32)w, (i32)h); -} - -void RGFW_FUNC(RGFW_window_setMinSize) (RGFW_window* win, i32 w, i32 h) { - RGFW_ASSERT(win != NULL); - xdg_toplevel_set_min_size(win->src.xdg_toplevel, w, h); -} - -void RGFW_FUNC(RGFW_window_setMaxSize) (RGFW_window* win, i32 w, i32 h) { - RGFW_ASSERT(win != NULL); - xdg_toplevel_set_max_size(win->src.xdg_toplevel, w, h); -} - -void RGFW_toggleWaylandMaximized(RGFW_window* win, RGFW_bool maximized) { - win->src.maximized = maximized; - if (maximized) { - xdg_toplevel_set_maximized(win->src.xdg_toplevel); - } else { - xdg_toplevel_unset_maximized(win->src.xdg_toplevel); - } -} - -void RGFW_FUNC(RGFW_window_maximize) (RGFW_window* win) { - win->internal.oldX = win->x; - win->internal.oldY = win->y; - win->internal.oldW = win->w; - win->internal.oldH = win->h; - RGFW_toggleWaylandMaximized(win, 1); - return; -} - -void RGFW_FUNC(RGFW_window_focus)(RGFW_window* win) { - RGFW_ASSERT(win); -} - -void RGFW_FUNC(RGFW_window_raise)(RGFW_window* win) { - RGFW_ASSERT(win); -} - -void RGFW_FUNC(RGFW_window_setFullscreen)(RGFW_window* win, RGFW_bool fullscreen) { - RGFW_ASSERT(win != NULL); - if (fullscreen) { - - win->internal.flags |= RGFW_windowFullscreen; - win->internal.oldX = win->x; - win->internal.oldY = win->y; - win->internal.oldW = win->w; - win->internal.oldH = win->h; - xdg_toplevel_set_fullscreen(win->src.xdg_toplevel, NULL); /* let the compositor decide */ - } else { - win->internal.flags &= ~(u32)RGFW_windowFullscreen; - xdg_toplevel_unset_fullscreen(win->src.xdg_toplevel); - } - -} - -void RGFW_FUNC(RGFW_window_setFloating) (RGFW_window* win, RGFW_bool floating) { - RGFW_ASSERT(win != NULL); - RGFW_UNUSED(floating); -} - -void RGFW_FUNC(RGFW_window_setOpacity) (RGFW_window* win, u8 opacity) { - RGFW_ASSERT(win != NULL); - RGFW_UNUSED(opacity); -} - -void RGFW_FUNC(RGFW_window_minimize)(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - if (RGFW_window_isMaximized(win)) return; - win->internal.oldX = win->x; - win->internal.oldY = win->y; - win->internal.oldW = win->w; - win->internal.oldH = win->h; - win->src.minimized = RGFW_TRUE; - xdg_toplevel_set_minimized(win->src.xdg_toplevel); -} - -void RGFW_FUNC(RGFW_window_restore)(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - RGFW_toggleWaylandMaximized(win, RGFW_FALSE); - - RGFW_window_move(win, win->internal.oldX, win->internal.oldY); - RGFW_window_resize(win, win->internal.oldW, win->internal.oldH); - - RGFW_window_show(win); - RGFW_window_move(win, win->internal.oldX, win->internal.oldY); - RGFW_window_resize(win, win->internal.oldW, win->internal.oldH); - - RGFW_window_show(win); -} - -RGFW_bool RGFW_FUNC(RGFW_window_isFloating)(RGFW_window* win) { - return (!RGFW_window_isFullscreen(win) && !RGFW_window_isMaximized(win)); -} - -void RGFW_FUNC(RGFW_window_setName) (RGFW_window* win, const char* name) { - RGFW_ASSERT(win != NULL); - if (_RGFW->compositor) - xdg_toplevel_set_title(win->src.xdg_toplevel, name); -} - -#ifndef RGFW_NO_PASSTHROUGH -void RGFW_FUNC(RGFW_window_setMousePassthrough) (RGFW_window* win, RGFW_bool passthrough) { - RGFW_ASSERT(win != NULL); - RGFW_UNUSED(passthrough); -} -#endif /* RGFW_NO_PASSTHROUGH */ - -RGFW_bool RGFW_FUNC(RGFW_window_setIconEx) (RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_icon type) { - RGFW_ASSERT(win != NULL); - RGFW_UNUSED(type); - - if (_RGFW->icon_manager == NULL || w != h) return RGFW_FALSE; - - if (win->src.icon) { - xdg_toplevel_icon_v1_destroy(win->src.icon); - win->src.icon= NULL; - } - - RGFW_surface* surface = RGFW_createSurface(data, w, h, format); - - if (surface == NULL) return RGFW_FALSE; - - RGFW_copyImageData(surface->native.buffer, RGFW_MIN(w, surface->w), RGFW_MIN(h, surface->h), surface->native.format, surface->data, surface->format); - - win->src.icon = xdg_toplevel_icon_manager_v1_create_icon(_RGFW->icon_manager); - xdg_toplevel_icon_v1_add_buffer(win->src.icon, surface->native.wl_buffer, 1); - xdg_toplevel_icon_manager_v1_set_icon(_RGFW->icon_manager, win->src.xdg_toplevel, win->src.icon); - - RGFW_surface_free(surface); - return RGFW_TRUE; -} - -RGFW_mouse* RGFW_FUNC(RGFW_loadMouse)(u8* data, i32 w, i32 h, RGFW_format format) { - - RGFW_surface *mouse_surface = RGFW_createSurface(data, w, h, format); - - if (mouse_surface == NULL) return NULL; - - RGFW_copyImageData(mouse_surface->native.buffer, RGFW_MIN(w, mouse_surface->w), RGFW_MIN(h, mouse_surface->h), mouse_surface->native.format, mouse_surface->data, mouse_surface->format); - - return (void*) mouse_surface; -} - -void RGFW_FUNC(RGFW_window_setMouse)(RGFW_window* win, RGFW_mouse* mouse) { - RGFW_ASSERT(win); RGFW_ASSERT(mouse); - RGFW_surface *mouse_surface = (RGFW_surface*)mouse; - - win->src.using_custom_cursor = RGFW_TRUE; - - struct wl_buffer *mouse_buffer = mouse_surface->native.wl_buffer; - - wl_surface_attach(win->src.custom_cursor_surface, mouse_buffer, 0, 0); - wl_surface_damage(win->src.custom_cursor_surface, 0, 0, mouse_surface->w, mouse_surface->h); - wl_surface_commit(win->src.custom_cursor_surface); - -} - -void RGFW_FUNC(RGFW_freeMouse)(RGFW_mouse* mouse) { - if (mouse != NULL) { - RGFW_surface_free((RGFW_surface*)mouse); - } -} - -void RGFW_FUNC(RGFW_window_moveMouse)(RGFW_window* win, i32 x, i32 y) { - RGFW_UNUSED(win); RGFW_UNUSED(x); RGFW_UNUSED(y); -} - -RGFW_bool RGFW_FUNC(RGFW_window_setMouseDefault)(RGFW_window* win) { - return RGFW_window_setMouseStandard(win, RGFW_mouseArrow); -} - -RGFW_bool RGFW_FUNC(RGFW_window_setMouseStandard)(RGFW_window* win, u8 mouse) { - RGFW_ASSERT(win != NULL); - static const char* iconStrings[16] = { "arrow", "left_ptr", "xterm", "crosshair", "hand2", "sb_h_double_arrow", "sb_v_double_arrow", "bottom_left_corner", "bottom_right_corner", "fleur", "forbidden" }; - - win->src.using_custom_cursor = RGFW_FALSE; - - if (mouse > RGFW_mouseIconCount - 1) return RGFW_FALSE; - - struct wl_cursor* wlcursor = wl_cursor_theme_get_cursor(_RGFW->wl_cursor_theme, iconStrings[mouse]); - struct wl_cursor_image* cursor_image = wlcursor->images[0]; - struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(cursor_image); - wl_pointer_set_cursor(_RGFW->wl_pointer, _RGFW->mouse_enter_serial, _RGFW->cursor_surface, (i32)cursor_image->hotspot_x, (i32)cursor_image->hotspot_y); - wl_surface_attach(_RGFW->cursor_surface, cursor_buffer, 0, 0); - wl_surface_damage(_RGFW->cursor_surface, 0, 0, (i32)cursor_image->width, (i32)cursor_image->height); - wl_surface_commit(_RGFW->cursor_surface); - return RGFW_TRUE; -} - -void RGFW_FUNC(RGFW_window_hide) (RGFW_window* win) { - wl_surface_attach(win->src.surface, NULL, 0, 0); - wl_surface_commit(win->src.surface); - win->internal.flags |= RGFW_windowHide; -} - -void RGFW_FUNC(RGFW_window_show) (RGFW_window* win) { - win->internal.flags &= ~(u32)RGFW_windowHide; - if (win->internal.flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); - /* wl_surface_attach(win->src.surface, win->x, win->y, win->w, win->h, 0, 0); */ - wl_surface_commit(win->src.surface); -} - -RGFW_ssize_t RGFW_FUNC(RGFW_readClipboardPtr) (char* str, size_t strCapacity) { - - RGFW_UNUSED(strCapacity); - - if (str != NULL) - RGFW_STRNCPY(str, _RGFW->clipboard, _RGFW->clipboard_len - 1); - _RGFW->clipboard[_RGFW->clipboard_len - 1] = '\0'; - return (RGFW_ssize_t)_RGFW->clipboard_len - 1; -} - -void RGFW_FUNC(RGFW_writeClipboard) (const char* text, u32 textLen) { - - // compositor does not support wl_data_device_manager - // clients cannot read rgfw's clipboard - if (_RGFW->data_device_manager == NULL) return; - // clear the clipboard - if (_RGFW->clipboard) - RGFW_FREE(_RGFW->clipboard); - - // set the contents - _RGFW->clipboard = (char*)RGFW_ALLOC(textLen); - RGFW_ASSERT(_RGFW->clipboard != NULL); - RGFW_STRNCPY(_RGFW->clipboard, text, textLen - 1); - _RGFW->clipboard[textLen - 1] = '\0'; - _RGFW->clipboard_len = textLen; - - // means we already wrote to the clipboard - // so destroy it to create a new one - RGFW_window* win = _RGFW->kbOwner; - - if (win->src.data_source != NULL) { - wl_data_source_destroy(win->src.data_source); - win->src.data_source = NULL; - } - - // advertise to other clients that we offer text - win->src.data_source = wl_data_device_manager_create_data_source(_RGFW->data_device_manager); - - // basic error checking - if (win->src.data_source == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, "Could not create clipboard data source"); - return; - } - wl_data_source_offer(win->src.data_source , "text/plain;charset=utf-8"); - - // needed RGFW_doNothing because wayland will call the functions - // if not set they are random data that lead to a crash - static const struct wl_data_source_listener data_source_listener = { - .target = (void (*)(void *, struct wl_data_source *, const char *))&RGFW_doNothing, - .action = (void (*)(void *, struct wl_data_source *, u32))&RGFW_doNothing, - .dnd_drop_performed = (void (*)(void *, struct wl_data_source *))&RGFW_doNothing, - .dnd_finished = (void (*)(void *, struct wl_data_source *))&RGFW_doNothing, - .send = RGFW_wl_data_source_send, - .cancelled = RGFW_wl_data_source_cancelled - }; - - wl_data_source_add_listener(win->src.data_source, &data_source_listener, _RGFW); - -} - -RGFW_bool RGFW_FUNC(RGFW_window_isHidden) (RGFW_window* win) { - RGFW_ASSERT(win != NULL); - return RGFW_FALSE; -} - -RGFW_bool RGFW_FUNC(RGFW_window_isMinimized) (RGFW_window* win) { - RGFW_ASSERT(win != NULL); - return win->src.minimized; -} - -RGFW_bool RGFW_FUNC(RGFW_window_isMaximized) (RGFW_window* win) { - RGFW_ASSERT(win != NULL); - return win->src.maximized; -} - -RGFW_monitor* RGFW_FUNC(RGFW_getMonitors) (size_t* len) { - static RGFW_monitor monitors[RGFW_MAX_MONITORS]; - RGFW_init(); - if (len != NULL) { - *len = _RGFW->monitors.count; - } - - u8 i = 0; - RGFW_monitorNode* cur_node = _RGFW->monitors.list.head; - while (cur_node != NULL) { - monitors[i] = cur_node->mon; - ++i; - cur_node = cur_node->next; - } - return monitors; -} - -RGFW_monitor RGFW_FUNC(RGFW_getPrimaryMonitor) (void) { - return _RGFW->monitors.list.head->mon; -} - -RGFW_bool RGFW_FUNC(RGFW_monitor_requestMode) (RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { - RGFW_UNUSED(mon); RGFW_UNUSED(mode); RGFW_UNUSED(request); - return RGFW_FALSE; -} - -RGFW_monitor RGFW_FUNC(RGFW_window_getMonitor) (RGFW_window* win) { - RGFW_ASSERT(win); - return win->src.active_monitor; -} - -#ifdef RGFW_OPENGL -RGFW_bool RGFW_FUNC(RGFW_extensionSupportedPlatform_OpenGL) (const char * extension, size_t len) { return RGFW_extensionSupportedPlatform_EGL(extension, len); } -RGFW_proc RGFW_FUNC(RGFW_getProcAddress_OpenGL) (const char* procname) { return RGFW_getProcAddress_EGL(procname); } - - -RGFW_bool RGFW_FUNC(RGFW_window_createContextPtr_OpenGL)(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { - RGFW_bool out = RGFW_window_createContextPtr_EGL(win, &ctx->egl, hints); - win->src.gfxType = RGFW_gfxNativeOpenGL; - return out; -} -void RGFW_FUNC(RGFW_window_deleteContextPtr_OpenGL) (RGFW_window* win, RGFW_glContext* ctx) { RGFW_window_deleteContextPtr_EGL(win, &ctx->egl); win->src.ctx.native = NULL; } - -void RGFW_FUNC(RGFW_window_makeCurrentContext_OpenGL) (RGFW_window* win) { RGFW_window_makeCurrentContext_EGL(win); } -void* RGFW_FUNC(RGFW_getCurrentContext_OpenGL) (void) { return RGFW_getCurrentContext_EGL(); } -void RGFW_FUNC(RGFW_window_swapBuffers_OpenGL) (RGFW_window* win) { RGFW_window_swapBuffers_EGL(win); } -void RGFW_FUNC(RGFW_window_swapInterval_OpenGL) (RGFW_window* win, i32 swapInterval) { RGFW_window_swapInterval_EGL(win, swapInterval); } -#endif /* RGFW_OPENGL */ - -void RGFW_FUNC(RGFW_window_closePlatform)(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, "a window was freed"); - #ifdef RGFW_LIBDECOR - if (win->src.decorContext) - libdecor_unref(win->src.decorContext); - #endif - - if (win->src.decoration) { - zxdg_toplevel_decoration_v1_destroy(win->src.decoration); - } - - if (win->src.xdg_toplevel) { - xdg_toplevel_destroy(win->src.xdg_toplevel); - } - - wl_surface_destroy(win->src.custom_cursor_surface); - - if (win->src.locked_pointer) { - zwp_locked_pointer_v1_destroy(win->src.locked_pointer); - } - - if (win->src.icon) { - xdg_toplevel_icon_v1_destroy(win->src.icon); - } - - xdg_surface_destroy(win->src.xdg_surface); - wl_surface_destroy(win->src.surface); -} - -#ifdef RGFW_WEBGPU -WGPUSurface RGFW_FUNC(RGFW_window_createSurface_WebGPU) (RGFW_window* window, WGPUInstance instance) { - WGPUSurfaceDescriptor surfaceDesc = {0}; - WGPUSurfaceSourceWaylandSurface fromWl = {0}; - fromWl.chain.sType = WGPUSType_SurfaceSourceWaylandSurface; - fromWl.display = _RGFW->wl_display; - fromWl.surface = window->src.surface; - - surfaceDesc.nextInChain = (WGPUChainedStruct*)&fromWl.chain; - return wgpuInstanceCreateSurface(instance, &surfaceDesc); -} -#endif - - - -#endif /* RGFW_WAYLAND */ -/* - End of Wayland defines -*/ /* @@ -8882,22 +6353,7 @@ WGPUSurface RGFW_FUNC(RGFW_window_createSurface_WebGPU) (RGFW_window* window, WG #define OEMRESOURCE #include -#ifndef OCR_NORMAL -#define OCR_NORMAL 32512 -#define OCR_IBEAM 32513 -#define OCR_WAIT 32514 -#define OCR_CROSS 32515 -#define OCR_UP 32516 -#define OCR_SIZENWSE 32642 -#define OCR_SIZENESW 32643 -#define OCR_SIZEWE 32644 -#define OCR_SIZENS 32645 -#define OCR_SIZEALL 32646 -#define OCR_NO 32648 -#define OCR_HAND 32649 -#define OCR_APPSTARTING 32650 -#endif - +#include #include #include #include @@ -8909,7 +6365,19 @@ WGPUSurface RGFW_FUNC(RGFW_window_createSurface_WebGPU) (RGFW_window* window, WG #define WM_DPICHANGED 0x02E0 #endif -RGFW_bool RGFW_createUTF8FromWideStringWin32(const WCHAR* source, char* out, size_t max); +#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 + + HMODULE RGFW_XInput_dll = NULL; +#endif + +char* RGFW_createUTF8FromWideStringWin32(const WCHAR* source); #define GL_FRONT 0x0404 #define GL_BACK 0x0405 @@ -8920,11 +6388,16 @@ typedef int (*PFN_wglGetSwapIntervalEXT)(void); PFN_wglGetSwapIntervalEXT wglGetSwapIntervalEXTSrc = NULL; #define wglGetSwapIntervalEXT wglGetSwapIntervalEXTSrc + +void* RGFWgamepadApi = NULL; + /* these two wgl functions need to be preloaded */ typedef HGLRC (WINAPI *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hdc, HGLRC hglrc, const int *attribList); PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL; -HMODULE RGFW_wgl_dll = NULL; +#ifndef RGFW_EGL + HMODULE RGFW_wgl_dll = NULL; +#endif #ifndef RGFW_NO_LOAD_WGL typedef HGLRC(WINAPI* PFN_wglCreateContext)(HDC); @@ -8952,11 +6425,28 @@ HMODULE RGFW_wgl_dll = NULL; #define wglShareLists wglShareListsSRC #endif -void* RGFW_window_getHWND(RGFW_window* win) { return win->src.window; } -void* RGFW_window_getHDC(RGFW_window* win) { return win->src.hdc; } +#if defined(RGFW_OPENGL) && !defined(RGFW_EGL) +RGFW_bool RGFW_extensionSupportedPlatform(const char * extension, size_t len) { + const char* extensions = NULL; -#ifdef RGFW_OPENGL -RGFWDEF void RGFW_win32_loadOpenGLFuncs(HWND dummyWin); + RGFW_proc proc = RGFW_getProcAddress("wglGetExtensionsStringARB"); + RGFW_proc proc2 = RGFW_getProcAddress("wglGetExtensionsStringEXT"); + + if (proc) + extensions = ((const char* (*)(HDC))proc)(wglGetCurrentDC()); + else if (proc2) + extensions = ((const char*(*)(void))proc2)(); + + return extensions != NULL && RGFW_extensionSupportedStr(extensions, extension, len); +} + +RGFW_proc RGFW_getProcAddress(const char* procname) { + RGFW_proc proc = (RGFW_proc)wglGetProcAddress(procname); + if (proc) + return proc; + + return (RGFW_proc) GetProcAddress(RGFW_wgl_dll, procname); +} typedef HRESULT (APIENTRY* PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc, const int* piAttribIList, const FLOAT* pfAttribFList, UINT nMaxFormats, int* piFormats, UINT* nNumFormats); PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = NULL; @@ -8967,15 +6457,13 @@ PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; #ifndef RGFW_NO_DWM HMODULE RGFW_dwm_dll = NULL; -#ifndef _DWMAPI_H_ typedef struct { DWORD dwFlags; int fEnable; HRGN hRgnBlur; int fTransitionOnMaximized;} DWM_BLURBEHIND; -#endif typedef HRESULT (WINAPI * PFN_DwmEnableBlurBehindWindow)(HWND, const DWM_BLURBEHIND*); PFN_DwmEnableBlurBehindWindow DwmEnableBlurBehindWindowSRC = NULL; #endif void RGFW_win32_makeWindowTransparent(RGFW_window* win); void RGFW_win32_makeWindowTransparent(RGFW_window* win) { - if (!(win->internal.flags & RGFW_windowTransparent)) return; + if (!(win->_flags & RGFW_windowTransparent)) return; #ifndef RGFW_NO_DWM if (DwmEnableBlurBehindWindowSRC != NULL) { @@ -8998,55 +6486,49 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) RGFW_window* win = (RGFW_window*)GetPropW(hWnd, L"RGFW"); if (win == NULL) return DefWindowProcW(hWnd, message, wParam, lParam); - static BYTE keyboardState[256]; - GetKeyboardState(keyboardState); - - RGFW_event event; - RGFW_MEMSET(&event, 0, sizeof(event)); - event.common.win = win; - RECT windowRect; GetWindowRect(hWnd, &windowRect); switch (message) { case WM_CLOSE: case WM_QUIT: - RGFW_window_setShouldClose(win, RGFW_TRUE); + RGFW_eventQueuePushEx(e.type = RGFW_quit; e._win = win); RGFW_windowQuitCallback(win); - RGFW_eventQueuePushEx(e.type = RGFW_quit; e.common.win = win); return 0; case WM_ACTIVATE: { RGFW_bool inFocus = RGFW_BOOL(LOWORD(wParam) != WA_INACTIVE); + if (inFocus) win->_flags |= RGFW_windowFocus; + else win->_flags &= ~ (u32)RGFW_windowFocus; + RGFW_eventQueuePushEx(e.type = (RGFW_eventType)((u8)RGFW_focusOut - inFocus); e._win = win); - win->internal.inFocus = RGFW_BOOL(inFocus); - if ((win->internal.enabledEvents & (RGFW_BIT(RGFW_focusIn - inFocus)))) { - RGFW_eventQueuePushEx(e.type = (RGFW_eventType)((u8)RGFW_focusOut - inFocus); e.common.win = win); - RGFW_focusCallback(win, inFocus); - } - if (inFocus == RGFW_FALSE) RGFW_window_focusLost(win); - if ((win->internal.flags & RGFW_windowFullscreen) && inFocus == RGFW_TRUE) - RGFW_window_setFullscreen(win, 1); + RGFW_focusCallback(win, inFocus); + RGFW_window_focusLost(win); + + if ((win->_flags & RGFW_windowFullscreen) == 0) + return DefWindowProcW(hWnd, message, wParam, lParam); + + win->_flags &= ~(u32)RGFW_EVENT_PASSED; + if (inFocus == RGFW_FALSE) RGFW_window_minimize(win); + else RGFW_window_setFullscreen(win, 1); return DefWindowProcW(hWnd, message, wParam, lParam); } case WM_MOVE: - win->x = windowRect.left; - win->y = windowRect.top; - - if (!(win->internal.enabledEvents & RGFW_windowMovedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam);; - RGFW_eventQueuePushEx(e.type = RGFW_windowMoved; e.common.win = win); - RGFW_windowMovedCallback(win, win->x, win->y); + win->r.x = windowRect.left; + win->r.y = windowRect.top; + RGFW_eventQueuePushEx(e.type = RGFW_windowMoved; e._win = win); + RGFW_windowMovedCallback(win, win->r); return DefWindowProcW(hWnd, message, wParam, lParam); case WM_SIZE: { - if (win->src.aspectRatioW != 0 && win->src.aspectRatioH != 0) { - double aspectRatio = (double)win->src.aspectRatioW / win->src.aspectRatioH; + if (win->src.aspectRatio.w != 0 && win->src.aspectRatio.h != 0) { + double aspectRatio = (double)win->src.aspectRatio.w / win->src.aspectRatio.h; int width = windowRect.right - windowRect.left; int height = windowRect.bottom - windowRect.top; int newHeight = (int)(width / aspectRatio); int newWidth = (int)(height * aspectRatio); - if (win->w > (i32)((windowRect.right - windowRect.left) - win->src.offsetW) || - win->h > (i32)((windowRect.bottom - windowRect.top) - win->src.offsetH)) + if (win->r.w > windowRect.right - windowRect.left || + win->r.h > (i32)((u32)(windowRect.bottom - windowRect.top) - win->src.hOffset)) { if (newHeight > height) windowRect.right = windowRect.left + newWidth; else windowRect.bottom = windowRect.top + newHeight; @@ -9055,47 +6537,43 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) else windowRect.bottom = windowRect.top + newHeight; } - RGFW_window_resize(win, (windowRect.right - windowRect.left) - win->src.offsetW, - (windowRect.bottom - windowRect.top) - win->src.offsetH); + RGFW_window_resize(win, RGFW_AREA((windowRect.right - windowRect.left), + (u32)(windowRect.bottom - windowRect.top) - (u32)win->src.hOffset)); } - win->w = (windowRect.right - windowRect.left) - (i32)win->src.offsetW; - win->h = (windowRect.bottom - windowRect.top) - (i32)win->src.offsetH; - if (!(win->internal.enabledEvents & RGFW_windowResizedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam);; - RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e.common.win = win); - RGFW_windowResizedCallback(win, win->w, win->h); + win->r.w = windowRect.right - windowRect.left; + win->r.h = (windowRect.bottom - windowRect.top) - (i32)win->src.hOffset; + RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e._win = win); + RGFW_windowResizedCallback(win, win->r); RGFW_window_checkMode(win); return DefWindowProcW(hWnd, message, wParam, lParam); } #ifndef RGFW_NO_MONITOR case WM_DPICHANGED: { - if (win->internal.flags & RGFW_windowScaleToMonitor) RGFW_window_scaleToMonitor(win); + if (win->_flags & RGFW_windowScaleToMonitor) RGFW_window_scaleToMonitor(win); const float scaleX = HIWORD(wParam) / (float) 96; const float scaleY = LOWORD(wParam) / (float) 96; - - if (!(win->internal.enabledEvents & RGFW_scaleUpdatedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam);; RGFW_scaleUpdatedCallback(win, scaleX, scaleY); - RGFW_eventQueuePushEx(e.type = RGFW_scaleUpdated; e.scale.x = scaleX; e.scale.y = scaleY; e.common.win = win); + RGFW_eventQueuePushEx(e.type = RGFW_scaleUpdated; e.scaleX = scaleX; e.scaleY = scaleY; e._win = win); return DefWindowProcW(hWnd, message, wParam, lParam); } #endif case WM_GETMINMAXINFO: { MINMAXINFO* mmi = (MINMAXINFO*) lParam; - mmi->ptMinTrackSize.x = (LONG)(win->src.minSizeW + win->src.offsetW); - mmi->ptMinTrackSize.y = (LONG)(win->src.minSizeH + win->src.offsetH); - if (win->src.maxSizeW == 0 && win->src.maxSizeH == 0) + mmi->ptMinTrackSize.x = (LONG)win->src.minSize.w; + mmi->ptMinTrackSize.y = (LONG)(win->src.minSize.h + win->src.hOffset); + if (win->src.maxSize.w == 0 && win->src.maxSize.h == 0) return DefWindowProcW(hWnd, message, wParam, lParam); - mmi->ptMaxTrackSize.x = (LONG)(win->src.maxSizeW + win->src.offsetW); - mmi->ptMaxTrackSize.y = (LONG)(win->src.maxSizeH + win->src.offsetH); + mmi->ptMaxTrackSize.x = (LONG)win->src.maxSize.w; + mmi->ptMaxTrackSize.y = (LONG)(win->src.maxSize.h + win->src.hOffset); return DefWindowProcW(hWnd, message, wParam, lParam); } case WM_PAINT: { - if (!(win->internal.enabledEvents & RGFW_windowRefreshFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); PAINTSTRUCT ps; BeginPaint(hWnd, &ps); - RGFW_eventQueuePushEx(e.type = RGFW_windowRefresh; e.common.win = win); + RGFW_eventQueuePushEx(e.type = RGFW_windowRefresh; e._win = win); RGFW_windowRefreshCallback(win); EndPaint(hWnd, &ps); @@ -9111,9 +6589,7 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) #ifdef RGFW_ADVANCED_SMOOTH_RESIZE case WM_ENTERSIZEMOVE: SetTimer(win->src.window, 1, USER_TIMER_MINIMUM, NULL); break; case WM_EXITSIZEMOVE: KillTimer(win->src.window, 1); break; - case WM_TIMER: - if (!(win->internal.enabledEvents & RGFW_windowRefreshFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); - RGFW_windowRefreshCallback(win); break; + case WM_TIMER: RGFW_windowRefreshCallback(win); break; #endif case WM_NCLBUTTONDOWN: { /* workaround for half-second pause when starting to move window @@ -9124,272 +6600,11 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) break; ScreenToClient(win->src.window, &point); - PostMessage(win->src.window, WM_MOUSEMOVE, 0, (u32)(point.x)|((u32)(point.y) << 16)); + PostMessage(win->src.window, WM_MOUSEMOVE, 0, ((uint32_t)point.x)|(((uint32_t)point.y) << 16)); break; } - case WM_MOUSELEAVE: - win->internal.mouseInside = RGFW_FALSE; - _RGFW->windowState.winLeave = win; - _RGFW->windowState.mouseLeave = RGFW_TRUE; - if (!(win->internal.enabledEvents & RGFW_mouseLeaveFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); - event.type = RGFW_mouseLeave; - RGFW_window_getMouse(win, &event.mouse.x, &event.mouse.y); - RGFW_mouseNotifyCallback(win, event.mouse.x, event.mouse.y, 0); - break; - case WM_SYSKEYUP: case WM_KEYUP: { - if (!(win->internal.enabledEvents & RGFW_keyReleasedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); - i32 scancode = (HIWORD(lParam) & (KF_EXTENDED | 0xff)); - if (scancode == 0) - scancode = (i32)MapVirtualKeyW((UINT)wParam, MAPVK_VK_TO_VSC); - - switch (scancode) { - case 0x54: scancode = 0x137; break; /* Alt+PrtS */ - case 0x146: scancode = 0x45; break; /* Ctrl+Pause */ - case 0x136: scancode = 0x36; break; /* CJK IME sets the extended bit for right Shift */ - default: break; - } - - event.key.value = (u8)RGFW_apiKeyToRGFW((u32) scancode); - - if (wParam == VK_CONTROL) { - if (HIWORD(lParam) & KF_EXTENDED) - event.key.value = RGFW_controlR; - else event.key.value = RGFW_controlL; - } - - wchar_t charBuffer; - ToUnicodeEx((UINT)wParam, (UINT)scancode, keyboardState, (wchar_t*)&charBuffer, 1, 0, NULL); - - event.key.sym = (u8)charBuffer; - - _RGFW->keyboard[event.key.value].prev = _RGFW->keyboard[event.key.value].current; - event.type = RGFW_keyReleased; - event.key.repeat = ((lParam & 0x40000000) != 0) || RGFW_window_isKeyDown(win, event.key.value); - _RGFW->keyboard[event.key.value].current = 0; - - RGFW_updateKeyMods(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001), (GetKeyState(VK_SCROLL) & 0x0001)); - event.key.mod = win->internal.mod; - - RGFW_keyCallback(win, event.key.value, event.key.sym, event.key.mod, event.key.repeat,0); - break; - } - case WM_SYSKEYDOWN: case WM_KEYDOWN: { - if (!(win->internal.enabledEvents & RGFW_keyPressedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); - i32 scancode = (HIWORD(lParam) & (KF_EXTENDED | 0xff)); - if (scancode == 0) - scancode = (i32)MapVirtualKeyW((u32)wParam, MAPVK_VK_TO_VSC); - - switch (scancode) { - case 0x54: scancode = 0x137; break; /* Alt+PrtS */ - case 0x146: scancode = 0x45; break; /* Ctrl+Pause */ - case 0x136: scancode = 0x36; break; /* CJK IME sets the extended bit for right Shift */ - default: break; - } - - event.key.value = (u8)RGFW_apiKeyToRGFW((u32) scancode); - if (wParam == VK_CONTROL) { - if (HIWORD(lParam) & KF_EXTENDED) - event.key.value = RGFW_controlR; - else event.key.value = RGFW_controlL; - } - - wchar_t charBuffer; - ToUnicodeEx((UINT)wParam, (UINT)scancode, keyboardState, &charBuffer, 1, 0, NULL); - event.key.sym = (u8)charBuffer; - - _RGFW->keyboard[event.key.value].prev = _RGFW->keyboard[event.key.value].current; - event.type = RGFW_keyPressed; - event.key.repeat = ((lParam & 0x40000000) != 0) || RGFW_window_isKeyDown(win, event.key.value); - _RGFW->keyboard[event.key.value].current = 1; - - RGFW_updateKeyMods(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001), (GetKeyState(VK_SCROLL) & 0x0001)); - event.key.mod = win->internal.mod; - - RGFW_keyCallback(win, event.key.value, event.key.sym, event.key.mod, event.key.repeat, 1); - break; - } - case WM_MOUSEMOVE: { - if (!(win->internal.enabledEvents & RGFW_mousePosChangedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); - if ((win->internal.holdMouse)) - break; - - - event.mouse.x = GET_X_LPARAM(lParam); - event.mouse.y = GET_Y_LPARAM(lParam); - event.mouse.vecX = (float)(event.mouse.x - win->internal.lastMouseX); - event.mouse.vecY = (float)(event.mouse.y - win->internal.lastMouseY); - _RGFW->vectorX = event.mouse.vecX; - _RGFW->vectorY = event.mouse.vecY; - - RGFW_mousePosCallback(win, event.mouse.x, event.mouse.y, event.mouse.vecX, event.mouse.vecY); - - if (win->internal.mouseInside == RGFW_FALSE) { - win->internal.mouseInside = RGFW_TRUE; - _RGFW->windowState.win = win; - _RGFW->windowState.mouseEnter = RGFW_TRUE; - event.type = RGFW_mouseEnter; - RGFW_mouseNotifyCallback(win, event.mouse.x, event.mouse.y, 1); - RGFW_eventQueuePush(&event); - } - - event.type = RGFW_mousePosChanged; - win->internal.lastMouseX = event.mouse.x; - win->internal.lastMouseY = event.mouse.y; - break; - } - case WM_INPUT: { - if (!(win->internal.enabledEvents & RGFW_mousePosChangedFlag) || !(win->internal.holdMouse)) return DefWindowProcW(hWnd, message, wParam, lParam); - unsigned size = sizeof(RAWINPUT); - static RAWINPUT raw; - - GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &size, sizeof(RAWINPUTHEADER)); - - if (raw.header.dwType != RIM_TYPEMOUSE || (raw.data.mouse.lLastX == 0 && raw.data.mouse.lLastY == 0) ) - break; - - if (raw.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { - POINT pos = {0, 0}; - int width, height; - - if (raw.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) { - pos.x += GetSystemMetrics(SM_XVIRTUALSCREEN); - pos.y += GetSystemMetrics(SM_YVIRTUALSCREEN); - width = GetSystemMetrics(SM_CXVIRTUALSCREEN); - height = GetSystemMetrics(SM_CYVIRTUALSCREEN); - } - else { - width = GetSystemMetrics(SM_CXSCREEN); - height = GetSystemMetrics(SM_CYSCREEN); - } - - pos.x += (int) (((float)raw.data.mouse.lLastX / 65535.f) * (float)width); - pos.y += (int) (((float)raw.data.mouse.lLastY / 65535.f) * (float)height); - ScreenToClient(win->src.window, &pos); - - event.mouse.vecX = (float)(pos.x - win->internal.lastMouseX); - event.mouse.vecY = (float)(pos.y - win->internal.lastMouseY); - } else { - event.mouse.vecX = (float)(raw.data.mouse.lLastX); - event.mouse.vecY = (float)(raw.data.mouse.lLastY); - } - - event.type = RGFW_mousePosChanged; - win->internal.lastMouseX += (i32)event.mouse.vecX; - win->internal.lastMouseY += (i32)event.mouse.vecY; - _RGFW->vectorX = event.mouse.vecX; - _RGFW->vectorY = event.mouse.vecY; - event.mouse.x = win->internal.lastMouseX; - event.mouse.y = win->internal.lastMouseY; - RGFW_mousePosCallback(win, event.mouse.x, event.mouse.y, event.mouse.vecX, event.mouse.vecY); - break; - } - case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: case WM_XBUTTONDOWN: - if (!(win->internal.enabledEvents & RGFW_mouseButtonPressedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); - if (message == WM_XBUTTONDOWN) - event.button.value = RGFW_mouseMisc1 + (GET_XBUTTON_WPARAM(wParam) == XBUTTON2); - else event.button.value = (message == WM_LBUTTONDOWN) ? (u8)RGFW_mouseLeft : - (message == WM_RBUTTONDOWN) ? (u8)RGFW_mouseRight : (u8)RGFW_mouseMiddle; - - event.type = RGFW_mouseButtonPressed; - _RGFW->mouseButtons[event.button.value].prev = _RGFW->mouseButtons[event.button.value].current; - _RGFW->mouseButtons[event.button.value].current = 1; - RGFW_mouseButtonCallback(win, event.button.value, 1); - break; - case WM_LBUTTONUP: case WM_RBUTTONUP: case WM_MBUTTONUP: case WM_XBUTTONUP: - if (!(win->internal.enabledEvents & RGFW_mouseButtonReleasedFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); - if (message == WM_XBUTTONUP) - event.button.value = RGFW_mouseMisc1 + (GET_XBUTTON_WPARAM(wParam) == XBUTTON2); - else event.button.value = (message == WM_LBUTTONUP) ? (u8)RGFW_mouseLeft : - (message == WM_RBUTTONUP) ? (u8)RGFW_mouseRight : (u8)RGFW_mouseMiddle; - event.type = RGFW_mouseButtonReleased; - _RGFW->mouseButtons[event.button.value].prev = _RGFW->mouseButtons[event.button.value].current; - _RGFW->mouseButtons[event.button.value].current = 0; - RGFW_mouseButtonCallback(win, event.button.value, 0); - break; - case WM_MOUSEWHEEL: - if (!(win->internal.enabledEvents & RGFW_mouseScrollFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); - - event.type = RGFW_mouseScroll; - event.scroll.x = 0.0f; - event.scroll.y = (float)((i16) HIWORD(wParam) / (double) WHEEL_DELTA); - _RGFW->scrollX = event.scroll.x; - _RGFW->scrollY = event.scroll.y; - - RGFW_mouseScrollCallback(win, event.scroll.x, event.scroll.y); - break; - case 0x020E: /* WM_MOUSEHWHEEL */ - if (!(win->internal.enabledEvents & RGFW_mouseScrollFlag)) return DefWindowProcW(hWnd, message, wParam, lParam); - - event.type = RGFW_mouseScroll; - event.scroll.x = -(float)((i16) HIWORD(wParam) / (double) WHEEL_DELTA); - event.scroll.y = (float)0.0f; - _RGFW->scrollX = event.scroll.x; - _RGFW->scrollY = event.scroll.y; - - RGFW_mouseScrollCallback(win, event.scroll.x, event.scroll.y); - break; - case WM_DROPFILES: { - event.type = RGFW_dataDrag; - - HDROP drop = (HDROP) wParam; - POINT pt; - - /* Move the mouse to the position of the drop */ - DragQueryPoint(drop, &pt); - - event.drag.x = pt.x; - event.drag.y = pt.y; - - _RGFW->windowState.win = win; - _RGFW->windowState.dataDragging = RGFW_TRUE; - _RGFW->windowState.dropX = event.drag.x; - _RGFW->windowState.dropY = event.drag.y; - - if ((win->internal.enabledEvents & RGFW_dataDrag)) { - RGFW_dataDragCallback(win, event.drag.x, event.drag.y); - RGFW_eventQueuePush(&event); - } - - if (!(win->internal.enabledEvents & RGFW_dataDrop)) return DefWindowProcW(hWnd, message, wParam, lParam); - event.type = 0; - event.type = RGFW_dataDrop; - event.drop.files = _RGFW->files; - event.drop.count = 0; - event.drop.count = DragQueryFileW(drop, 0xffffffff, NULL, 0); - - u32 i; - for (i = 0; i < event.drop.count; i++) { - UINT length = DragQueryFileW(drop, i, NULL, 0); - if (length == 0) - continue; - - WCHAR buffer[RGFW_MAX_PATH * 2]; - if (length > (RGFW_MAX_PATH * 2) - 1) - length = RGFW_MAX_PATH * 2; - - DragQueryFileW(drop, i, buffer, length + 1); - - RGFW_createUTF8FromWideStringWin32(buffer, event.drop.files[i], RGFW_MAX_PATH); - - event.drop.files[i][RGFW_MAX_PATH - 1] = '\0'; - event.common.win = win; - } - - DragFinish(drop); - - _RGFW->windowState.win = win; - _RGFW->windowState.dataDrop = RGFW_TRUE; - _RGFW->windowState.filesCount = event.drop.count; - RGFW_dataDropCallback(win, event.drop.files, event.drop.count); - break; - } default: break; } - - if (event.type) { - RGFW_eventQueuePush(&event); - } - return DefWindowProcW(hWnd, message, wParam, lParam); } @@ -9416,50 +6631,58 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) RGFW_ASSERT(name##SRC != NULL); \ } -RGFW_bool RGFW_createSurfacePtr(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { - RGFW_ASSERT(surface != NULL); - surface->data = data; - surface->w = w; - surface->h = h; - surface->format = format; +#ifndef RGFW_NO_XINPUT +void RGFW_loadXInput(void); +void RGFW_loadXInput(void) { + u32 i; + static const char* names[] = {"xinput1_4.dll", "xinput9_1_0.dll", "xinput1_2.dll", "xinput1_1.dll"}; + + for (i = 0; i < sizeof(names) / sizeof(const char*) && (XInputGetStateSRC == NULL || XInputGetKeystrokeSRC != NULL); i++) { + RGFW_XInput_dll = LoadLibraryA(names[i]); + RGFW_PROC_DEF(RGFW_XInput_dll, XInputGetState); + RGFW_PROC_DEF(RGFW_XInput_dll, XInputGetKeystroke); + } + + if (XInputGetStateSRC == NULL) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errFailedFuncLoad, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to load XInputGetState"); + if (XInputGetKeystrokeSRC == NULL) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errFailedFuncLoad, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to load XInputGetKeystroke"); +} +#endif + +void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area){ +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + win->buffer = buffer; + win->bufferSize = area; BITMAPV5HEADER bi; ZeroMemory(&bi, sizeof(bi)); bi.bV5Size = sizeof(bi); - bi.bV5Width = (i32)w; - bi.bV5Height = -((LONG) h); + bi.bV5Width = (i32)area.w; + bi.bV5Height = -((LONG) area.h); bi.bV5Planes = 1; - bi.bV5BitCount = (format >= RGFW_formatRGBA8) ? 32 : 24; + bi.bV5BitCount = 32; bi.bV5Compression = BI_RGB; - surface->native.bitmap = CreateDIBSection(_RGFW->root->src.hdc, + win->src.bitmap = CreateDIBSection(win->src.hdc, (BITMAPINFO*) &bi, DIB_RGB_COLORS, - (void**) &surface->native.bitmapBits, + (void**) &win->src.bitmapBits, NULL, (DWORD) 0); - surface->native.format = (format >= RGFW_formatRGBA8) ? RGFW_formatBGRA8 : RGFW_formatBGR8; + if (win->buffer == NULL) + win->buffer = win->src.bitmapBits; - if (surface->native.bitmap == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errBuffer, "Failed to create DIB section."); - return RGFW_FALSE; - } + win->src.hdcMem = CreateCompatibleDC(win->src.hdc); + SelectObject(win->src.hdcMem, win->src.bitmap); - surface->native.hdcMem = CreateCompatibleDC(_RGFW->root->src.hdc); - SelectObject(surface->native.hdcMem, surface->native.bitmap); - - return RGFW_TRUE; -} - -void RGFW_surface_freePtr(RGFW_surface* surface) { - RGFW_ASSERT(surface != NULL); - - DeleteDC(surface->native.hdcMem); - DeleteObject(surface->native.bitmap); -} - -void RGFW_window_blitSurface(RGFW_window* win, RGFW_surface* surface) { - RGFW_copyImageData(surface->native.bitmapBits, surface->w, RGFW_MIN(win->h, surface->h), surface->native.format, surface->data, surface->format); - BitBlt(win->src.hdc, 0, 0, RGFW_MIN(win->w, surface->w), RGFW_MIN(win->h, surface->h), surface->native.hdcMem, 0, 0, SRCCOPY); + #if defined(RGFW_OSMESA) + win->src.ctx = OSMesaCreateContext(OSMESA_BGRA, NULL); + OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); + OSMesaPixelStore(OSMESA_Y_UP, 0); + #endif + #else + RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); /*!< if buffer rendering is not being used */ + #endif } void RGFW_releaseCursor(RGFW_window* win) { @@ -9469,8 +6692,8 @@ void RGFW_releaseCursor(RGFW_window* win) { RegisterRawInputDevices(&id, 1, sizeof(id)); } -void RGFW_captureCursor(RGFW_window* win) { - RGFW_UNUSED(win); +void RGFW_captureCursor(RGFW_window* win, RGFW_rect rect) { + RGFW_UNUSED(win); RGFW_UNUSED(rect); RECT clipRect; GetClientRect(win->src.window, &clipRect); @@ -9485,13 +6708,13 @@ void RGFW_captureCursor(RGFW_window* win) { #define RGFW_LOAD_LIBRARY(x, lib) if (x == NULL) { x = LoadLibraryA(lib); RGFW_ASSERT(x != NULL); } #ifdef RGFW_DIRECTX -int RGFW_window_createSwapChain_DirectX(RGFW_window* win, IDXGIFactory* pFactory, IUnknown* pDevice, IDXGISwapChain** swapchain) { +int RGFW_window_createDXSwapChain(RGFW_window* win, IDXGIFactory* pFactory, IUnknown* pDevice, IDXGISwapChain** swapchain) { RGFW_ASSERT(win && pFactory && pDevice && swapchain); static DXGI_SWAP_CHAIN_DESC swapChainDesc = { 0 }; swapChainDesc.BufferCount = 2; - swapChainDesc.BufferDesc.Width = win->w; - swapChainDesc.BufferDesc.Height = win->h; + swapChainDesc.BufferDesc.Width = win->r.w; + swapChainDesc.BufferDesc.Height = win->r.h; swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapChainDesc.OutputWindow = (HWND)win->src.window; @@ -9502,7 +6725,7 @@ int RGFW_window_createSwapChain_DirectX(RGFW_window* win, IDXGIFactory* pFactory HRESULT hr = pFactory->lpVtbl->CreateSwapChain(pFactory, (IUnknown*)pDevice, &swapChainDesc, swapchain); if (FAILED(hr)) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errDirectXContext, "Failed to create DirectX swap chain!"); + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errDirectXContext, RGFW_DEBUG_CTX(win, hr), "Failed to create DirectX swap chain!"); return -2; } @@ -9510,130 +6733,143 @@ int RGFW_window_createSwapChain_DirectX(RGFW_window* win, IDXGIFactory* pFactory } #endif -/* we're doing it with magic numbers because some keys are missing */ -void RGFW_initKeycodesPlatform(void) { - _RGFW->keycodes[0x00B] = RGFW_0; - _RGFW->keycodes[0x002] = RGFW_1; - _RGFW->keycodes[0x003] = RGFW_2; - _RGFW->keycodes[0x004] = RGFW_3; - _RGFW->keycodes[0x005] = RGFW_4; - _RGFW->keycodes[0x006] = RGFW_5; - _RGFW->keycodes[0x007] = RGFW_6; - _RGFW->keycodes[0x008] = RGFW_7; - _RGFW->keycodes[0x009] = RGFW_8; - _RGFW->keycodes[0x00A] = RGFW_9; - _RGFW->keycodes[0x01E] = RGFW_a; - _RGFW->keycodes[0x030] = RGFW_b; - _RGFW->keycodes[0x02E] = RGFW_c; - _RGFW->keycodes[0x020] = RGFW_d; - _RGFW->keycodes[0x012] = RGFW_e; - _RGFW->keycodes[0x021] = RGFW_f; - _RGFW->keycodes[0x022] = RGFW_g; - _RGFW->keycodes[0x023] = RGFW_h; - _RGFW->keycodes[0x017] = RGFW_i; - _RGFW->keycodes[0x024] = RGFW_j; - _RGFW->keycodes[0x025] = RGFW_k; - _RGFW->keycodes[0x026] = RGFW_l; - _RGFW->keycodes[0x032] = RGFW_m; - _RGFW->keycodes[0x031] = RGFW_n; - _RGFW->keycodes[0x018] = RGFW_o; - _RGFW->keycodes[0x019] = RGFW_p; - _RGFW->keycodes[0x010] = RGFW_q; - _RGFW->keycodes[0x013] = RGFW_r; - _RGFW->keycodes[0x01F] = RGFW_s; - _RGFW->keycodes[0x014] = RGFW_t; - _RGFW->keycodes[0x016] = RGFW_u; - _RGFW->keycodes[0x02F] = RGFW_v; - _RGFW->keycodes[0x011] = RGFW_w; - _RGFW->keycodes[0x02D] = RGFW_x; - _RGFW->keycodes[0x015] = RGFW_y; - _RGFW->keycodes[0x02C] = RGFW_z; - _RGFW->keycodes[0x028] = RGFW_apostrophe; - _RGFW->keycodes[0x02B] = RGFW_backSlash; - _RGFW->keycodes[0x033] = RGFW_comma; - _RGFW->keycodes[0x00D] = RGFW_equals; - _RGFW->keycodes[0x029] = RGFW_backtick; - _RGFW->keycodes[0x01A] = RGFW_bracket; - _RGFW->keycodes[0x00C] = RGFW_minus; - _RGFW->keycodes[0x034] = RGFW_period; - _RGFW->keycodes[0x01B] = RGFW_closeBracket; - _RGFW->keycodes[0x027] = RGFW_semicolon; - _RGFW->keycodes[0x035] = RGFW_slash; - _RGFW->keycodes[0x056] = RGFW_world2; - _RGFW->keycodes[0x00E] = RGFW_backSpace; - _RGFW->keycodes[0x153] = RGFW_delete; - _RGFW->keycodes[0x14F] = RGFW_end; - _RGFW->keycodes[0x01C] = RGFW_enter; - _RGFW->keycodes[0x001] = RGFW_escape; - _RGFW->keycodes[0x147] = RGFW_home; - _RGFW->keycodes[0x152] = RGFW_insert; - _RGFW->keycodes[0x15D] = RGFW_menu; - _RGFW->keycodes[0x151] = RGFW_pageDown; - _RGFW->keycodes[0x149] = RGFW_pageUp; - _RGFW->keycodes[0x045] = RGFW_pause; - _RGFW->keycodes[0x039] = RGFW_space; - _RGFW->keycodes[0x00F] = RGFW_tab; - _RGFW->keycodes[0x03A] = RGFW_capsLock; - _RGFW->keycodes[0x145] = RGFW_numLock; - _RGFW->keycodes[0x046] = RGFW_scrollLock; - _RGFW->keycodes[0x03B] = RGFW_F1; - _RGFW->keycodes[0x03C] = RGFW_F2; - _RGFW->keycodes[0x03D] = RGFW_F3; - _RGFW->keycodes[0x03E] = RGFW_F4; - _RGFW->keycodes[0x03F] = RGFW_F5; - _RGFW->keycodes[0x040] = RGFW_F6; - _RGFW->keycodes[0x041] = RGFW_F7; - _RGFW->keycodes[0x042] = RGFW_F8; - _RGFW->keycodes[0x043] = RGFW_F9; - _RGFW->keycodes[0x044] = RGFW_F10; - _RGFW->keycodes[0x057] = RGFW_F11; - _RGFW->keycodes[0x058] = RGFW_F12; - _RGFW->keycodes[0x064] = RGFW_F13; - _RGFW->keycodes[0x065] = RGFW_F14; - _RGFW->keycodes[0x066] = RGFW_F15; - _RGFW->keycodes[0x067] = RGFW_F16; - _RGFW->keycodes[0x068] = RGFW_F17; - _RGFW->keycodes[0x069] = RGFW_F18; - _RGFW->keycodes[0x06A] = RGFW_F19; - _RGFW->keycodes[0x06B] = RGFW_F20; - _RGFW->keycodes[0x06C] = RGFW_F21; - _RGFW->keycodes[0x06D] = RGFW_F22; - _RGFW->keycodes[0x06E] = RGFW_F23; - _RGFW->keycodes[0x076] = RGFW_F24; - _RGFW->keycodes[0x038] = RGFW_altL; - _RGFW->keycodes[0x01D] = RGFW_controlL; - _RGFW->keycodes[0x02A] = RGFW_shiftL; - _RGFW->keycodes[0x15B] = RGFW_superL; - _RGFW->keycodes[0x137] = RGFW_printScreen; - _RGFW->keycodes[0x138] = RGFW_altR; - _RGFW->keycodes[0x11D] = RGFW_controlR; - _RGFW->keycodes[0x036] = RGFW_shiftR; - _RGFW->keycodes[0x15C] = RGFW_superR; - _RGFW->keycodes[0x150] = RGFW_down; - _RGFW->keycodes[0x14B] = RGFW_left; - _RGFW->keycodes[0x14D] = RGFW_right; - _RGFW->keycodes[0x148] = RGFW_up; - _RGFW->keycodes[0x052] = RGFW_kp0; - _RGFW->keycodes[0x04F] = RGFW_kp1; - _RGFW->keycodes[0x050] = RGFW_kp2; - _RGFW->keycodes[0x051] = RGFW_kp3; - _RGFW->keycodes[0x04B] = RGFW_kp4; - _RGFW->keycodes[0x04C] = RGFW_kp5; - _RGFW->keycodes[0x04D] = RGFW_kp6; - _RGFW->keycodes[0x047] = RGFW_kp7; - _RGFW->keycodes[0x048] = RGFW_kp8; - _RGFW->keycodes[0x049] = RGFW_kp9; - _RGFW->keycodes[0x04E] = RGFW_kpPlus; - _RGFW->keycodes[0x053] = RGFW_kpPeriod; - _RGFW->keycodes[0x135] = RGFW_kpSlash; - _RGFW->keycodes[0x11C] = RGFW_kpReturn; - _RGFW->keycodes[0x059] = RGFW_kpEqual; - _RGFW->keycodes[0x037] = RGFW_kpMultiply; - _RGFW->keycodes[0x04A] = RGFW_kpMinus; +void RGFW_win32_loadOpenGLFuncs(HWND dummyWin); +void RGFW_win32_loadOpenGLFuncs(HWND dummyWin) { +#ifdef RGFW_OPENGL + if (wglSwapIntervalEXT != NULL && wglChoosePixelFormatARB != NULL && wglChoosePixelFormatARB != NULL) + return; + + HDC dummy_dc = GetDC(dummyWin); + u32 pfd_flags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + + PIXELFORMATDESCRIPTOR pfd = {sizeof(pfd), 1, pfd_flags, PFD_TYPE_RGBA, 32, 8, PFD_MAIN_PLANE, 32, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 32, 8, 0, PFD_MAIN_PLANE, 0, 0, 0, 0}; + + int dummy_pixel_format = ChoosePixelFormat(dummy_dc, &pfd); + SetPixelFormat(dummy_dc, dummy_pixel_format, &pfd); + + HGLRC dummy_context = wglCreateContext(dummy_dc); + wglMakeCurrent(dummy_dc, dummy_context); + + wglCreateContextAttribsARB = ((PFNWGLCREATECONTEXTATTRIBSARBPROC(WINAPI *)(const char*)) wglGetProcAddress)("wglCreateContextAttribsARB"); + wglChoosePixelFormatARB = ((PFNWGLCHOOSEPIXELFORMATARBPROC(WINAPI *)(const char*)) wglGetProcAddress)("wglChoosePixelFormatARB"); + + wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)(RGFW_proc)wglGetProcAddress("wglSwapIntervalEXT"); + if (wglSwapIntervalEXT == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to load swap interval function"); + } + + wglMakeCurrent(dummy_dc, 0); + wglDeleteContext(dummy_context); + ReleaseDC(dummyWin, dummy_dc); +#else + RGFW_UNUSED(dummyWin); +#endif } +#ifndef RGFW_EGL +void RGFW_window_initOpenGL(RGFW_window* win) { +#ifdef RGFW_OPENGL + PIXELFORMATDESCRIPTOR pfd; + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.iLayerType = PFD_MAIN_PLANE; + pfd.cColorBits = 32; + pfd.cAlphaBits = 8; + pfd.cDepthBits = 24; + pfd.cStencilBits = (BYTE)RGFW_GL_HINTS[RGFW_glStencil]; + pfd.cAuxBuffers = (BYTE)RGFW_GL_HINTS[RGFW_glAuxBuffers]; + if (RGFW_GL_HINTS[RGFW_glStereo]) pfd.dwFlags |= PFD_STEREO; + + /* try to create the pixel format we want for opengl and then try to create an opengl context for the specified version */ + if (win->_flags & RGFW_windowOpenglSoftware) + pfd.dwFlags |= PFD_GENERIC_FORMAT | PFD_GENERIC_ACCELERATED; + + /* get pixel format, default to a basic pixel format */ + int pixel_format = ChoosePixelFormat(win->src.hdc, &pfd); + if (wglChoosePixelFormatARB != NULL) { + i32* pixel_format_attribs = (i32*)RGFW_initFormatAttribs(); + + int new_pixel_format; + UINT num_formats; + wglChoosePixelFormatARB(win->src.hdc, pixel_format_attribs, 0, 1, &new_pixel_format, &num_formats); + if (!num_formats) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to create a pixel format for WGL"); + else pixel_format = new_pixel_format; + } + + PIXELFORMATDESCRIPTOR suggested; + if (!DescribePixelFormat(win->src.hdc, pixel_format, sizeof(suggested), &suggested) || + !SetPixelFormat(win->src.hdc, pixel_format, &pfd)) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to set the WGL pixel format"); + + if (!(pfd.dwFlags & PFD_GENERIC_ACCELERATED)) { + win->_flags |= RGFW_windowOpenglSoftware; + } + + if (wglCreateContextAttribsARB != NULL) { + /* create opengl/WGL context for the specified version */ + u32 index = 0; + i32 attribs[40]; + + if (RGFW_GL_HINTS[RGFW_glProfile]== RGFW_glCore) { + SET_ATTRIB(WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB); + } + else { + SET_ATTRIB(WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB); + } + + if (RGFW_GL_HINTS[RGFW_glMinor] || RGFW_GL_HINTS[RGFW_glMajor]) { + SET_ATTRIB(WGL_CONTEXT_MAJOR_VERSION_ARB, RGFW_GL_HINTS[RGFW_glMajor]); + SET_ATTRIB(WGL_CONTEXT_MINOR_VERSION_ARB, RGFW_GL_HINTS[RGFW_glMinor]); + } + + SET_ATTRIB(0, 0); + + win->src.ctx = (HGLRC)wglCreateContextAttribsARB(win->src.hdc, NULL, attribs); + } else { /* fall back to a default context (probably opengl 2 or something) */ + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to create an accelerated OpenGL Context"); + win->src.ctx = wglCreateContext(win->src.hdc); + } + + ReleaseDC(win->src.window, win->src.hdc); + win->src.hdc = GetDC(win->src.window); + wglMakeCurrent(win->src.hdc, win->src.ctx); + + if (_RGFW.root != win) + wglShareLists(_RGFW.root->src.ctx, win->src.ctx); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context initalized"); +#else + RGFW_UNUSED(win); +#endif +} + +void RGFW_window_freeOpenGL(RGFW_window* win) { +#ifdef RGFW_OPENGL + if (win->src.ctx == NULL) return; + wglDeleteContext((HGLRC) win->src.ctx); /*!< delete opengl context */ + win->src.ctx = NULL; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context freed"); +#else + RGFW_UNUSED(win); +#endif +} +#endif + + +i32 RGFW_init(void) { +#if defined(RGFW_C89) || defined(__cplusplus) + if (_RGFW_init) return 0; + _RGFW_init = RGFW_TRUE; + _RGFW.root = NULL; _RGFW.current = NULL; _RGFW.windowCount = -1; _RGFW.eventLen = 0; _RGFW.eventIndex = 0; +#endif + + #ifndef RGFW_NO_XINPUT + if (RGFW_XInput_dll == NULL) + RGFW_loadXInput(); + #endif -i32 RGFW_initPlatform(void) { #ifndef RGFW_NO_DPI #if (_WIN32_WINNT >= 0x0600) SetProcessDPIAware(); @@ -9666,33 +6902,36 @@ i32 RGFW_initPlatform(void) { #endif u8 RGFW_blk[] = { 0, 0, 0, 0 }; - _RGFW->hiddenMouse = RGFW_loadMouse(RGFW_blk, 1, 1, RGFW_formatRGBA8); + _RGFW.hiddenMouse = RGFW_loadMouse(RGFW_blk, RGFW_AREA(1, 1), 4); + + _RGFW.windowCount = 0; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context initialized"); return 1; } -RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, RGFW_window* win) { +RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { if (name[0] == 0) name = (char*) " "; + + RGFW_window_basic_init(win, rect, flags); + win->src.hIconSmall = win->src.hIconBig = NULL; - win->src.maxSizeW = 0; - win->src.maxSizeH = 0; - win->src.minSizeW = 0; - win->src.minSizeH = 0; - win->src.aspectRatioW = 0; - win->src.aspectRatioH = 0; + win->src.maxSize = RGFW_AREA(0, 0); + win->src.minSize = RGFW_AREA(0, 0); + win->src.aspectRatio = RGFW_AREA(0, 0); HINSTANCE inh = GetModuleHandleA(NULL); #ifndef __cplusplus - WNDCLASSW Class = {0}; /*!< Setup the Window class. */ + WNDCLASSW Class = { 0 }; /*!< Setup the Window class. */ #else - WNDCLASSW Class = {}; + WNDCLASSW Class = { }; #endif - if (_RGFW->className == NULL) - _RGFW->className = (char*)name; + if (RGFW_className == NULL) + RGFW_className = (char*)name; wchar_t wide_class[256]; - MultiByteToWideChar(CP_UTF8, 0, _RGFW->className, -1, wide_class, 255); + MultiByteToWideChar(CP_UTF8, 0, RGFW_className, -1, wide_class, 255); Class.lpszClassName = wide_class; Class.hInstance = inh; @@ -9711,7 +6950,7 @@ RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, RECT windowRect, clientRect; if (!(flags & RGFW_windowNoBorder)) { - window_style |= WS_CAPTION | WS_SYSMENU | WS_BORDER | WS_MINIMIZEBOX; + window_style |= WS_CAPTION | WS_SYSMENU | WS_BORDER | WS_MINIMIZEBOX | WS_THICKFRAME; if (!(flags & RGFW_windowNoResize)) window_style |= WS_SIZEBOX | WS_MAXIMIZEBOX; @@ -9720,37 +6959,43 @@ RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, wchar_t wide_name[256]; MultiByteToWideChar(CP_UTF8, 0, name, -1, wide_name, 255); - HWND dummyWin = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->x, win->y, win->w, win->h, 0, 0, inh, 0); + HWND dummyWin = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->r.x, win->r.y, win->r.w, win->r.h, 0, 0, inh, 0); GetWindowRect(dummyWin, &windowRect); GetClientRect(dummyWin, &clientRect); -#ifdef RGFW_OPENGL RGFW_win32_loadOpenGLFuncs(dummyWin); -#endif - DestroyWindow(dummyWin); - win->src.offsetW = (i32)(windowRect.right - windowRect.left) - (i32)(clientRect.right - clientRect.left); - win->src.offsetH = (i32)(windowRect.bottom - windowRect.top) - (i32)(clientRect.bottom - clientRect.top); - win->src.window = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->x, win->y, win->w + (i32)win->src.offsetW, win->h + (i32)win->src.offsetH, 0, 0, inh, 0); + win->src.hOffset = (u32)(windowRect.bottom - windowRect.top) - (u32)(clientRect.bottom - clientRect.top); + win->src.window = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->r.x, win->r.y, win->r.w, win->r.h + (i32)win->src.hOffset, 0, 0, inh, 0); SetPropW(win->src.window, L"RGFW", win); - RGFW_window_resize(win, win->w, win->h); /* so WM_GETMINMAXINFO gets called again */ + RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h)); /* so WM_GETMINMAXINFO gets called again */ if (flags & RGFW_windowAllowDND) { - win->internal.flags |= RGFW_windowAllowDND; + win->_flags |= RGFW_windowAllowDND; RGFW_window_setDND(win, 1); } win->src.hdc = GetDC(win->src.window); + if ((flags & RGFW_windowNoInitAPI) == 0) { + RGFW_window_initOpenGL(win); + RGFW_window_initBuffer(win); + } + + RGFW_window_setFlags(win, flags); RGFW_win32_makeWindowTransparent(win); - return win; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); + RGFW_window_show(win); + + return win; } void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { - RGFW_setBit(&win->internal.flags, RGFW_windowNoBorder, !border); + RGFW_setBit(&win->_flags, RGFW_windowNoBorder, !border); LONG style = GetWindowLong(win->src.window, GWL_STYLE); + if (border == 0) { SetWindowLong(win->src.window, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW); SetWindowPos( @@ -9759,8 +7004,8 @@ void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { ); } else { - if (win->internal.flags & RGFW_windowNoResize) style &= ~WS_MAXIMIZEBOX; - SetWindowLong(win->src.window, GWL_STYLE, style | WS_OVERLAPPEDWINDOW); + style |= WS_OVERLAPPEDWINDOW; + if (win->_flags & RGFW_windowNoResize) style &= ~WS_MAXIMIZEBOX; SetWindowPos( win->src.window, HWND_TOP, 0, 0, 0, 0, SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE @@ -9769,34 +7014,37 @@ void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { } void RGFW_window_setDND(RGFW_window* win, RGFW_bool allow) { - RGFW_setBit(&win->internal.flags, RGFW_windowAllowDND, allow); + RGFW_setBit(&win->_flags, RGFW_windowAllowDND, allow); DragAcceptFiles(win->src.window, allow); } -RGFW_bool RGFW_getGlobalMouse(i32* x, i32* y) { +RGFW_area RGFW_getScreenSize(void) { + HDC dc = GetDC(NULL); + RGFW_area area = RGFW_AREA(GetDeviceCaps(dc, HORZRES), GetDeviceCaps(dc, VERTRES)); + ReleaseDC(NULL, dc); + return area; +} + +RGFW_point RGFW_getGlobalMousePoint(void) { POINT p; GetCursorPos(&p); - if (x) *x = p.x; - if (y) *y = p.y; - return RGFW_TRUE; + + return RGFW_POINT(p.x, p.y); } -void RGFW_window_setAspectRatio(RGFW_window* win, i32 w, i32 h) { +void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { RGFW_ASSERT(win != NULL); - win->src.aspectRatioW = w; - win->src.aspectRatioH = h; + win->src.aspectRatio = a; } -void RGFW_window_setMinSize(RGFW_window* win, i32 w, i32 h) { +void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { RGFW_ASSERT(win != NULL); - win->src.minSizeW = w; - win->src.minSizeH = h; + win->src.minSize = a; } -void RGFW_window_setMaxSize(RGFW_window* win, i32 w, i32 h) { +void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { RGFW_ASSERT(win != NULL); - win->src.maxSizeW = w; - win->src.maxSizeH = h; + win->src.maxSize = a; } void RGFW_window_focus(RGFW_window* win) { @@ -9808,7 +7056,7 @@ void RGFW_window_focus(RGFW_window* win) { void RGFW_window_raise(RGFW_window* win) { RGFW_ASSERT(win); BringWindowToTop(win->src.window); - SetWindowPos(win->src.window, HWND_TOP, win->x, win->y, win->w, win->h, SWP_NOSIZE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + SetWindowPos(win->src.window, HWND_TOP, win->r.x, win->r.y, win->r.w, win->r.h, SWP_NOSIZE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); } void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { @@ -9816,32 +7064,24 @@ void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { if (fullscreen == RGFW_FALSE) { RGFW_window_setBorder(win, 1); - SetWindowPos(win->src.window, HWND_NOTOPMOST, win->internal.oldX, win->internal.oldY, win->internal.oldW + (i32)win->src.offsetW, win->internal.oldH + (i32)win->src.offsetH, + SetWindowPos(win->src.window, HWND_NOTOPMOST, win->_oldRect.x, win->_oldRect.y, win->_oldRect.w, win->_oldRect.h + (i32)win->src.hOffset, SWP_NOOWNERZORDER | SWP_FRAMECHANGED); - win->internal.flags &= ~(u32)RGFW_windowFullscreen; - win->x = win->internal.oldX; - win->y = win->internal.oldY; - win->w = win->internal.oldW; - win->h = win->internal.oldH; + win->_flags &= ~(u32)RGFW_windowFullscreen; + win->r = win->_oldRect; return; } - win->internal.oldX = win->x; - win->internal.oldY = win->y; - win->internal.oldW = win->w; - win->internal.oldH = win->h; - win->internal.flags |= RGFW_windowFullscreen; + win->_oldRect = win->r; + win->_flags |= RGFW_windowFullscreen; RGFW_monitor mon = RGFW_window_getMonitor(win); RGFW_window_setBorder(win, 0); - SetWindowPos(win->src.window, HWND_TOPMOST, (i32)mon.x, (i32)mon.x, (i32)mon.mode.w, (i32)mon.mode.h, SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW); + SetWindowPos(win->src.window, HWND_TOPMOST, 0, 0, (i32)mon.mode.area.w, (i32)mon.mode.area.h, SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW); RGFW_monitor_scaleToWindow(mon, win); - win->x = mon.x; win->y = mon.x; - win->w = mon.mode.w; - win->h = mon.mode.h; + win->r = RGFW_RECT(0, 0, mon.mode.area.w, mon.mode.area.h); } void RGFW_window_maximize(RGFW_window* win) { @@ -9872,16 +7112,149 @@ RGFW_bool RGFW_window_isFloating(RGFW_window* win) { return (GetWindowLongPtr(win->src.window, GWL_EXSTYLE) & WS_EX_TOPMOST) != 0; } -void RGFW_stopCheckEvents(void) { - PostMessageW(_RGFW->root->src.window, WM_NULL, 0, 0); +u8 RGFW_xinput2RGFW[] = { + RGFW_gamepadA, /* or PS X button */ + RGFW_gamepadB, /* or PS circle button */ + RGFW_gamepadX, /* or PS square button */ + RGFW_gamepadY, /* or PS triangle button */ + RGFW_gamepadR1, /* right bumper */ + RGFW_gamepadL1, /* left bump */ + RGFW_gamepadL2, /* left trigger */ + RGFW_gamepadR2, /* right trigger */ + 0, 0, 0, 0, 0, 0, 0, 0, + RGFW_gamepadUp, /* dpad up */ + RGFW_gamepadDown, /* dpad down */ + RGFW_gamepadLeft, /* dpad left */ + RGFW_gamepadRight, /* dpad right */ + RGFW_gamepadStart, /* start button */ + RGFW_gamepadSelect,/* select button */ + RGFW_gamepadL3, + RGFW_gamepadR3, +}; +i32 RGFW_checkXInput(RGFW_window* win, RGFW_event* e); +i32 RGFW_checkXInput(RGFW_window* win, RGFW_event* e) { + #ifndef RGFW_NO_XINPUT + + RGFW_UNUSED(win); + u16 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_RTHUMB_PRESS) + continue; + + /* gamepad + 1 = RGFW_gamepadButtonReleased */ + e->type = RGFW_gamepadButtonPressed + !(keystroke.Flags & XINPUT_KEYSTROKE_KEYDOWN); + e->button = RGFW_xinput2RGFW[keystroke.VirtualKey - 0x5800]; + RGFW_gamepadPressed[i][e->button].prev = RGFW_gamepadPressed[i][e->button].current; + RGFW_gamepadPressed[i][e->button].current = RGFW_BOOL(keystroke.Flags & XINPUT_KEYSTROKE_KEYDOWN); + + RGFW_gamepadButtonCallback(win, i, e->button, e->type == RGFW_gamepadButtonPressed); + return 1; + } + + XINPUT_STATE state; + if (XInputGetState == NULL || + XInputGetState((DWORD) i, &state) == ERROR_DEVICE_NOT_CONNECTED + ) { + if (RGFW_gamepads[i] == 0) + continue; + + RGFW_gamepads[i] = 0; + RGFW_gamepadCount--; + + win->event.type = RGFW_gamepadDisconnected; + win->event.gamepad = (u16)i; + RGFW_gamepadCallback(win, i, 0); + return 1; + } + + if (RGFW_gamepads[i] == 0) { + RGFW_gamepads[i] = 1; + RGFW_gamepadCount++; + + char str[] = "Microsoft X-Box (XInput device)"; + RGFW_MEMCPY(RGFW_gamepads_name[i], str, sizeof(str)); + RGFW_gamepads_name[i][sizeof(RGFW_gamepads_name[i]) - 1] = '\0'; + win->event.type = RGFW_gamepadConnected; + win->event.gamepad = i; + RGFW_gamepads_type[i] = RGFW_gamepadMicrosoft; + + RGFW_gamepadCallback(win, i, 1); + 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 && + state.Gamepad.sThumbLX > -INPUT_DEADZONE) && + (state.Gamepad.sThumbLY < INPUT_DEADZONE && + state.Gamepad.sThumbLY > -INPUT_DEADZONE)) + { + state.Gamepad.sThumbLX = 0; + state.Gamepad.sThumbLY = 0; + } + + if ((state.Gamepad.sThumbRX < INPUT_DEADZONE && + state.Gamepad.sThumbRX > -INPUT_DEADZONE) && + (state.Gamepad.sThumbRY < INPUT_DEADZONE && + state.Gamepad.sThumbRY > -INPUT_DEADZONE)) + { + state.Gamepad.sThumbRX = 0; + state.Gamepad.sThumbRY = 0; + } + + e->axisesCount = 2; + RGFW_point axis1 = RGFW_POINT(((float)state.Gamepad.sThumbLX / 32768.0f) * 100, ((float)state.Gamepad.sThumbLY / -32768.0f) * 100); + RGFW_point axis2 = RGFW_POINT(((float)state.Gamepad.sThumbRX / 32768.0f) * 100, ((float)state.Gamepad.sThumbRY / -32768.0f) * 100); + + if (axis1.x != e->axis[0].x || axis1.y != e->axis[0].y){ + win->event.whichAxis = 0; + + e->type = RGFW_gamepadAxisMove; + e->axis[0] = axis1; + RGFW_gamepadAxes[i][0] = e->axis[0]; + + RGFW_gamepadAxisCallback(win, e->gamepad, e->axis, e->axisesCount, e->whichAxis); + return 1; + } + + if (axis2.x != e->axis[1].x || axis2.y != e->axis[1].y) { + win->event.whichAxis = 1; + e->type = RGFW_gamepadAxisMove; + e->axis[1] = axis2; + RGFW_gamepadAxes[i][1] = e->axis[1]; + + RGFW_gamepadAxisCallback(win, e->gamepad, e->axis, e->axisesCount, e->whichAxis); + return 1; + } + } + + #endif + + return 0; } -void RGFW_waitForEvent(i32 waitMS) { +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, QS_ALLINPUT); } u8 RGFW_rgfwToKeyChar(u32 rgfw_keycode) { - UINT vsc = RGFW_rgfwToApiKey(rgfw_keycode); /* Should return a Windows VK_* code */ + UINT vsc = RGFW_rgfwToApiKey(rgfw_keycode); // Should return a Windows VK_* code BYTE keyboardState[256] = {0}; if (!GetKeyboardState(keyboardState)) @@ -9899,17 +7272,273 @@ u8 RGFW_rgfwToKeyChar(u32 rgfw_keycode) { return (u8)charBuffer[0]; } -void RGFW_pollEvents(void) { - RGFW_resetPrevState(); - MSG msg; - while (PeekMessageA(&msg, NULL, 0u, 0u, PM_REMOVE)) { - TranslateMessage(&msg); - DispatchMessageA(&msg); +RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { + if (win == NULL || ((win->_flags & RGFW_windowFreeOnClose) && (win->_flags & RGFW_EVENT_QUIT))) return NULL; + RGFW_event* ev = RGFW_window_checkEventCore(win); + if (ev) { + return ev; + } + + static HDROP drop; + if (win->event.type == RGFW_DNDInit) { + 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); + + u32 i; + for (i = 0; i < win->event.droppedFilesCount; i++) { + UINT length = DragQueryFileW(drop, i, NULL, 0); + if (length == 0) + continue; + + WCHAR buffer[RGFW_MAX_PATH * 2]; + if (length > (RGFW_MAX_PATH * 2) - 1) + length = RGFW_MAX_PATH * 2; + + DragQueryFileW(drop, i, buffer, length + 1); + + char* str = RGFW_createUTF8FromWideStringWin32(buffer); + if (str != NULL) + RGFW_MEMCPY(win->event.droppedFiles[i], str, length + 1); + + win->event.droppedFiles[i][RGFW_MAX_PATH - 1] = '\0'; + } + + DragFinish(drop); + RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); + + win->event.type = RGFW_DND; + return &win->event; } + + if (RGFW_checkXInput(win, &win->event)) + return &win->event; + + static BYTE keyboardState[256]; + GetKeyboardState(keyboardState); + + MSG msg; + if (PeekMessageA(&msg, NULL, 0u, 0u, PM_REMOVE)) { + if (msg.hwnd != win->src.window && msg.hwnd != NULL) { + TranslateMessage(&msg); + DispatchMessageA(&msg); + return RGFW_window_checkEvent(win); + } + } else { + return NULL; + } + + switch (msg.message) { + case WM_MOUSELEAVE: + win->event.type = RGFW_mouseLeave; + win->_flags |= RGFW_MOUSE_LEFT; + RGFW_mouseNotifyCallback(win, win->event.point, 0); + break; + case WM_SYSKEYUP: case WM_KEYUP: { + i32 scancode = (HIWORD(msg.lParam) & (KF_EXTENDED | 0xff)); + if (scancode == 0) + scancode = (i32)MapVirtualKeyW((UINT)msg.wParam, MAPVK_VK_TO_VSC); + + switch (scancode) { + case 0x54: scancode = 0x137; break; /* Alt+PrtS */ + case 0x146: scancode = 0x45; break; /* Ctrl+Pause */ + case 0x136: scancode = 0x36; break; /* CJK IME sets the extended bit for right Shift */ + default: break; + } + + win->event.key = (u8)RGFW_apiKeyToRGFW((u32) scancode); + + if (msg.wParam == VK_CONTROL) { + if (HIWORD(msg.lParam) & KF_EXTENDED) + win->event.key = RGFW_controlR; + else win->event.key = RGFW_controlL; + } + + wchar_t charBuffer; + ToUnicodeEx((UINT)msg.wParam, (UINT)scancode, keyboardState, (wchar_t*)&charBuffer, 1, 0, NULL); + + win->event.keyChar = (u8)charBuffer; + + RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; + win->event.type = RGFW_keyReleased; + RGFW_keyboard[win->event.key].current = 0; + + RGFW_updateKeyMods(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001), (GetKeyState(VK_SCROLL) & 0x0001)); + + RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, 0); + break; + } + case WM_SYSKEYDOWN: case WM_KEYDOWN: { + i32 scancode = (HIWORD(msg.lParam) & (KF_EXTENDED | 0xff)); + if (scancode == 0) + scancode = (i32)MapVirtualKeyW((u32)msg.wParam, MAPVK_VK_TO_VSC); + + switch (scancode) { + case 0x54: scancode = 0x137; break; /* Alt+PrtS */ + case 0x146: scancode = 0x45; break; /* Ctrl+Pause */ + case 0x136: scancode = 0x36; break; /* CJK IME sets the extended bit for right Shift */ + default: break; + } + + win->event.key = (u8)RGFW_apiKeyToRGFW((u32) scancode); + if (msg.wParam == VK_CONTROL) { + if (HIWORD(msg.lParam) & KF_EXTENDED) + win->event.key = RGFW_controlR; + else win->event.key = RGFW_controlL; + } + + wchar_t charBuffer; + ToUnicodeEx((UINT)msg.wParam, (UINT)scancode, keyboardState, &charBuffer, 1, 0, NULL); + win->event.keyChar = (u8)charBuffer; + + RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; + + win->event.type = RGFW_keyPressed; + win->event.repeat = RGFW_isPressed(win, win->event.key); + RGFW_keyboard[win->event.key].current = 1; + RGFW_updateKeyMods(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001), (GetKeyState(VK_SCROLL) & 0x0001)); + + RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, 1); + break; + } + case WM_MOUSEMOVE: { + if ((win->_flags & RGFW_HOLD_MOUSE)) + break; + + win->event.type = RGFW_mousePosChanged; + + i32 x = GET_X_LPARAM(msg.lParam); + i32 y = GET_Y_LPARAM(msg.lParam); + + RGFW_mousePosCallback(win, win->event.point, win->event.vector); + + if (win->_flags & RGFW_MOUSE_LEFT) { + win->_flags &= ~(u32)RGFW_MOUSE_LEFT; + win->event.type = RGFW_mouseEnter; + RGFW_mouseNotifyCallback(win, win->event.point, 1); + } + + win->event.point.x = x; + win->event.point.y = y; + win->_lastMousePoint = RGFW_POINT(x, y); + + break; + } + case WM_INPUT: { + if (!(win->_flags & RGFW_HOLD_MOUSE)) + break; + + unsigned size = sizeof(RAWINPUT); + static RAWINPUT raw; + + 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; + + if (raw.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { + POINT pos = {0, 0}; + int width, height; + + if (raw.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) { + pos.x += GetSystemMetrics(SM_XVIRTUALSCREEN); + pos.y += GetSystemMetrics(SM_YVIRTUALSCREEN); + width = GetSystemMetrics(SM_CXVIRTUALSCREEN); + height = GetSystemMetrics(SM_CYVIRTUALSCREEN); + } + else { + width = GetSystemMetrics(SM_CXSCREEN); + height = GetSystemMetrics(SM_CYSCREEN); + } + + pos.x += (int) (((float)raw.data.mouse.lLastX / 65535.f) * (float)width); + pos.y += (int) (((float)raw.data.mouse.lLastY / 65535.f) * (float)height); + ScreenToClient(win->src.window, &pos); + + win->event.vector.x = pos.x - win->_lastMousePoint.x; + win->event.vector.y = pos.y - win->_lastMousePoint.y; + } else { + win->event.vector.x = raw.data.mouse.lLastX; + win->event.vector.y = raw.data.mouse.lLastY; + } + + win->event.type = RGFW_mousePosChanged; + win->_lastMousePoint.x += win->event.vector.x; + win->_lastMousePoint.y += win->event.vector.y; + win->event.point = win->_lastMousePoint; + RGFW_mousePosCallback(win, win->event.point, win->event.vector); + break; + } + case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: case WM_XBUTTONDOWN: + if (msg.message == WM_XBUTTONDOWN) + win->event.button = RGFW_mouseMisc1 + (GET_XBUTTON_WPARAM(msg.wParam) == XBUTTON2); + else win->event.button = (msg.message == WM_LBUTTONDOWN) ? RGFW_mouseLeft : + (msg.message == WM_RBUTTONDOWN) ? RGFW_mouseRight : RGFW_mouseMiddle; + + win->event.type = RGFW_mouseButtonPressed; + 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_LBUTTONUP: case WM_RBUTTONUP: case WM_MBUTTONUP: case WM_XBUTTONUP: + if (msg.message == WM_XBUTTONUP) + win->event.button = RGFW_mouseMisc1 + (GET_XBUTTON_WPARAM(msg.wParam) == XBUTTON2); + else win->event.button = (msg.message == WM_LBUTTONUP) ? RGFW_mouseLeft : + (msg.message == WM_RBUTTONUP) ? RGFW_mouseRight : RGFW_mouseMiddle; + win->event.type = RGFW_mouseButtonReleased; + 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_MOUSEWHEEL: + if (msg.wParam > 0) + win->event.button = RGFW_mouseScrollUp; + else + win->event.button = RGFW_mouseScrollDown; + + 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; + + win->event.type = RGFW_mouseButtonPressed; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); + break; + case WM_DROPFILES: { + win->event.type = RGFW_DNDInit; + + drop = (HDROP) msg.wParam; + POINT pt; + + /* Move the mouse to the position of the drop */ + DragQueryPoint(drop, &pt); + + win->event.point.x = pt.x; + win->event.point.y = pt.y; + + RGFW_dndInitCallback(win, win->event.point); + } + break; + default: + TranslateMessage(&msg); + DispatchMessageA(&msg); + return RGFW_window_checkEvent(win); + } + + TranslateMessage(&msg); + DispatchMessageA(&msg); + + return &win->event; } RGFW_bool RGFW_window_isHidden(RGFW_window* win) { RGFW_ASSERT(win != NULL); + return IsWindowVisible(win->src.window) == 0 && !RGFW_window_isMinimized(win); } @@ -9917,9 +7546,9 @@ RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { RGFW_ASSERT(win != NULL); #ifndef __cplusplus - WINDOWPLACEMENT placement = {0}; + WINDOWPLACEMENT placement = { 0 }; #else - WINDOWPLACEMENT placement = {}; + WINDOWPLACEMENT placement = { }; #endif GetWindowPlacement(win->src.window, &placement); return placement.showCmd == SW_SHOWMINIMIZED; @@ -9929,9 +7558,9 @@ RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { RGFW_ASSERT(win != NULL); #ifndef __cplusplus - WINDOWPLACEMENT placement = {0}; + WINDOWPLACEMENT placement = { 0 }; #else - WINDOWPLACEMENT placement = {}; + WINDOWPLACEMENT placement = { }; #endif GetWindowPlacement(win->src.window, &placement); return placement.showCmd == SW_SHOWMAXIMIZED || IsZoomed(win->src.window); @@ -9939,49 +7568,51 @@ RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { typedef struct { int iIndex; HMONITOR hMonitor; RGFW_monitor* monitors; } RGFW_mInfo; #ifndef RGFW_NO_MONITOR -RGFW_monitor RGFW_win32_createMonitor(HMONITOR src); -RGFW_monitor RGFW_win32_createMonitor(HMONITOR src) { +RGFW_monitor win32CreateMonitor(HMONITOR src); +RGFW_monitor win32CreateMonitor(HMONITOR src) { RGFW_monitor monitor; - RGFW_MEMSET(&monitor, 0, sizeof(monitor)); + MONITORINFOEX monitorInfo; - MONITORINFOEXW monitorInfo; - monitorInfo.cbSize = sizeof(MONITORINFOEXW); - GetMonitorInfoW(src, (LPMONITORINFO)&monitorInfo); + monitorInfo.cbSize = sizeof(MONITORINFOEX); + GetMonitorInfoA(src, (LPMONITORINFO)&monitorInfo); /* get the monitor's index */ - DISPLAY_DEVICEW dd; + DISPLAY_DEVICEA dd; dd.cb = sizeof(dd); DWORD deviceNum; - for (deviceNum = 0; EnumDisplayDevicesW(NULL, deviceNum, &dd, 0); deviceNum++) { + for (deviceNum = 0; EnumDisplayDevicesA(NULL, deviceNum, &dd, 0); deviceNum++) { if (!(dd.StateFlags & DISPLAY_DEVICE_ACTIVE)) continue; - DEVMODEW dm; + DEVMODEA dm; ZeroMemory(&dm, sizeof(dm)); dm.dmSize = sizeof(dm); - if (EnumDisplaySettingsW(dd.DeviceName, ENUM_CURRENT_SETTINGS, &dm)) { + if (EnumDisplaySettingsA(dd.DeviceName, ENUM_CURRENT_SETTINGS, &dm)) { monitor.mode.refreshRate = dm.dmDisplayFrequency; RGFW_splitBPP(dm.dmBitsPerPel, &monitor.mode); } - DISPLAY_DEVICEW mdd; + DISPLAY_DEVICEA mdd; mdd.cb = sizeof(mdd); - if (EnumDisplayDevicesW(dd.DeviceName, (DWORD)deviceNum, &mdd, 0)) { - RGFW_createUTF8FromWideStringWin32(mdd.DeviceString, monitor.name, sizeof(monitor.name)); + if (EnumDisplayDevicesA(dd.DeviceName, (DWORD)deviceNum, &mdd, 0)) { + RGFW_STRNCPY(monitor.name, mdd.DeviceString, sizeof(monitor.name) - 1); monitor.name[sizeof(monitor.name) - 1] = '\0'; break; } } + + + monitor.x = monitorInfo.rcWork.left; monitor.y = monitorInfo.rcWork.top; - monitor.mode.w = (i32)(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left); - monitor.mode.h = (i32)(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top); + monitor.mode.area.w = (u32)(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left); + monitor.mode.area.h = (u32)(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top); - HDC hdc = CreateDCW(monitorInfo.szDevice, NULL, NULL, NULL); + HDC hdc = CreateDC(monitorInfo.szDevice, NULL, NULL, NULL); /* get pixels per inch */ float dpiX = (float)GetDeviceCaps(hdc, LOGPIXELSX); float dpiY = (float)GetDeviceCaps(hdc, LOGPIXELSX); @@ -10007,7 +7638,7 @@ RGFW_monitor RGFW_win32_createMonitor(HMONITOR src) { } #endif - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, "monitor found"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); return monitor; } #endif /* RGFW_NO_MONITOR */ @@ -10024,7 +7655,7 @@ BOOL CALLBACK GetMonitorHandle(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMon if (info->iIndex >= 6) return FALSE; - info->monitors[info->iIndex] = RGFW_win32_createMonitor(hMonitor); + info->monitors[info->iIndex] = win32CreateMonitor(hMonitor); info->iIndex++; return TRUE; @@ -10032,9 +7663,9 @@ BOOL CALLBACK GetMonitorHandle(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMon RGFW_monitor RGFW_getPrimaryMonitor(void) { #ifdef __cplusplus - return RGFW_win32_createMonitor(MonitorFromPoint({0, 0}, MONITOR_DEFAULTTOPRIMARY)); + return win32CreateMonitor(MonitorFromPoint({ 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); #else - return RGFW_win32_createMonitor(MonitorFromPoint((POINT){0, 0}, MONITOR_DEFAULTTOPRIMARY)); + return win32CreateMonitor(MonitorFromPoint((POINT) { 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); #endif } @@ -10052,7 +7683,7 @@ RGFW_monitor* RGFW_getMonitors(size_t* len) { RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { HMONITOR src = MonitorFromWindow(win->src.window, MONITOR_DEFAULTTOPRIMARY); - return RGFW_win32_createMonitor(src); + return win32CreateMonitor(src); } RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { @@ -10074,7 +7705,7 @@ RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW if (strcmp(dd.DeviceName, (const char*)monitorInfo.szDevice) != 0) continue; - + DEVMODEA dm; ZeroMemory(&dm, sizeof(dm)); dm.dmSize = sizeof(dm); @@ -10082,8 +7713,8 @@ RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW if (EnumDisplaySettingsA(dd.DeviceName, ENUM_CURRENT_SETTINGS, &dm)) { if (request & RGFW_monitorScale) { dm.dmFields |= DM_PELSWIDTH | DM_PELSHEIGHT; - dm.dmPelsWidth = (u32)mode.w; - dm.dmPelsHeight = (u32)mode.h; + dm.dmPelsWidth = mode.area.w; + dm.dmPelsHeight = mode.area.h; } if (request & RGFW_monitorRefresh) { @@ -10096,8 +7727,8 @@ RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW dm.dmBitsPerPel = (DWORD)(mode.red + mode.green + mode.blue); } - if (ChangeDisplaySettingsExA(dd.DeviceName, &dm, NULL, CDS_TEST, NULL) == DISP_CHANGE_SUCCESSFUL) { - if (ChangeDisplaySettingsExA(dd.DeviceName, &dm, NULL, CDS_UPDATEREGISTRY, NULL) == DISP_CHANGE_SUCCESSFUL) + if (ChangeDisplaySettingsExA((LPCSTR)dd.DeviceName, (DEVMODE *)&dm, NULL, CDS_TEST, NULL) == DISP_CHANGE_SUCCESSFUL) { + if (ChangeDisplaySettingsExA((LPCSTR)dd.DeviceName, (DEVMODE *)&dm, NULL, CDS_UPDATEREGISTRY, NULL) == DISP_CHANGE_SUCCESSFUL) return RGFW_TRUE; return RGFW_FALSE; } else return RGFW_FALSE; @@ -10108,15 +7739,17 @@ RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW } #endif -HICON RGFW_loadHandleImage(u8* data, i32 w, i32 h, RGFW_format format, BOOL icon); -HICON RGFW_loadHandleImage(u8* data, i32 w, i32 h, RGFW_format format, BOOL icon) { +HICON RGFW_loadHandleImage(u8* src, i32 c, RGFW_area a, BOOL icon); +HICON RGFW_loadHandleImage(u8* src, i32 c, RGFW_area a, BOOL icon) { + size_t channels = (size_t)c; + BITMAPV5HEADER bi; ZeroMemory(&bi, sizeof(bi)); bi.bV5Size = sizeof(bi); - bi.bV5Width = (i32)w; - bi.bV5Height = -((LONG) h); + bi.bV5Width = (i32)a.w; + bi.bV5Height = -((LONG) a.h); bi.bV5Planes = 1; - bi.bV5BitCount = (WORD)32; + bi.bV5BitCount = (WORD)(channels * 8); bi.bV5Compression = BI_RGB; HDC dc = GetDC(NULL); u8* target = NULL; @@ -10125,16 +7758,26 @@ HICON RGFW_loadHandleImage(u8* data, i32 w, i32 h, RGFW_format format, BOOL icon (BITMAPINFO*) &bi, DIB_RGB_COLORS, (void**) &target, NULL, (DWORD) 0); - RGFW_copyImageData(target, w, h, RGFW_formatBGRA8, data, format); + size_t x, y; + for (y = 0; y < a.h; y++) { + for (x = 0; x < a.w; x++) { + size_t index = (y * 4 * (size_t)a.w) + x * channels; + target[index] = src[index + 2]; + target[index + 1] = src[index + 1]; + target[index + 2] = src[index]; + target[index + 3] = src[index + 3]; + } + } + ReleaseDC(NULL, dc); - HBITMAP mask = CreateBitmap((i32)w, (i32)h, 1, 1, NULL); + HBITMAP mask = CreateBitmap((i32)a.w, (i32)a.h, 1, 1, NULL); ICONINFO ii; ZeroMemory(&ii, sizeof(ii)); ii.fIcon = icon; - ii.xHotspot = (u32)w / 2; - ii.yHotspot = (u32)h / 2; + ii.xHotspot = a.w / 2; + ii.yHotspot = a.h / 2; ii.hbmMask = mask; ii.hbmColor = color; @@ -10145,8 +7788,9 @@ HICON RGFW_loadHandleImage(u8* data, i32 w, i32 h, RGFW_format format, BOOL icon return handle; } -RGFW_mouse* RGFW_loadMouse(u8* data, i32 w, i32 h, RGFW_format format) { - HCURSOR cursor = (HCURSOR) RGFW_loadHandleImage(data, w, h, format, FALSE); + +void* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { + HCURSOR cursor = (HCURSOR) RGFW_loadHandleImage(icon, channels, a, FALSE); return cursor; } @@ -10184,12 +7828,16 @@ void RGFW_window_hide(RGFW_window* win) { } void RGFW_window_show(RGFW_window* win) { - if (win->internal.flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); + if (win->_flags & RGFW_windowFocusOnShow) RGFW_window_focus(win); ShowWindow(win->src.window, SW_RESTORE); } #define RGFW_FREE_LIBRARY(x) if (x != NULL) FreeLibrary(x); x = NULL; -void RGFW_deinitPlatform(void) { +void RGFW_deinit(void) { + #ifndef RGFW_NO_XINPUT + RGFW_FREE_LIBRARY(RGFW_XInput_dll); + #endif + #ifndef RGFW_NO_DPI RGFW_FREE_LIBRARY(RGFW_Shcore_dll); #endif @@ -10202,35 +7850,55 @@ void RGFW_deinitPlatform(void) { #endif RGFW_FREE_LIBRARY(RGFW_wgl_dll); + _RGFW.root = NULL; - RGFW_freeMouse(_RGFW->hiddenMouse); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, "global context deinitialized"); + RGFW_freeMouse(_RGFW.hiddenMouse); + _RGFW.windowCount = -1; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context deinitialized"); } -void RGFW_window_closePlatform(RGFW_window* win) { +void RGFW_window_close(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + #ifdef RGFW_BUFFER + DeleteDC(win->src.hdcMem); + DeleteObject(win->src.bitmap); + #endif + + if ((win->_flags & RGFW_windowNoInitAPI) == 0) RGFW_window_freeOpenGL(win); RemovePropW(win->src.window, L"RGFW"); ReleaseDC(win->src.window, win->src.hdc); /*!< delete device context */ DestroyWindow(win->src.window); /*!< delete window */ if (win->src.hIconSmall) DestroyIcon(win->src.hIconSmall); if (win->src.hIconBig) DestroyIcon(win->src.hIconBig); + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a window was freed"); + _RGFW.windowCount--; + if (_RGFW.windowCount == 0) RGFW_deinit(); + + RGFW_clipboard_switch(NULL); + RGFW_FREE(win->event.droppedFiles); + if ((win->_flags & RGFW_WINDOW_ALLOC)) { + RGFW_FREE(win); + win = NULL; + } } -void RGFW_window_move(RGFW_window* win, i32 x, i32 y) { +void RGFW_window_move(RGFW_window* win, RGFW_point v) { RGFW_ASSERT(win != NULL); - win->x = x; - win->y = y; - SetWindowPos(win->src.window, HWND_TOP, win->x, win->y, 0, 0, SWP_NOSIZE); + win->r.x = v.x; + win->r.y = v.y; + SetWindowPos(win->src.window, HWND_TOP, win->r.x, win->r.y, 0, 0, SWP_NOSIZE); } -void RGFW_window_resize(RGFW_window* win, i32 w, i32 h) { +void RGFW_window_resize(RGFW_window* win, RGFW_area a) { RGFW_ASSERT(win != NULL); - win->w = w; - win->h = h; - SetWindowPos(win->src.window, HWND_TOP, 0, 0, win->w + (i32)win->src.offsetW, win->h + (i32)win->src.offsetH, SWP_NOMOVE); + win->r.w = (i32)a.w; + win->r.h = (i32)a.h; + SetWindowPos(win->src.window, HWND_TOP, 0, 0, win->r.w, win->r.h + (i32)win->src.hOffset, SWP_NOMOVE); } @@ -10268,13 +7936,15 @@ void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { } #endif -RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_icon type) { +RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* src, RGFW_area a, i32 channels, u8 type) { RGFW_ASSERT(win != NULL); #ifndef RGFW_WIN95 + RGFW_UNUSED(channels); + if (win->src.hIconSmall && (type & RGFW_iconWindow)) DestroyIcon(win->src.hIconSmall); if (win->src.hIconBig && (type & RGFW_iconTaskbar)) DestroyIcon(win->src.hIconBig); - if (data == NULL) { + if (src == NULL) { HICON defaultIcon = LoadIcon(NULL, IDI_APPLICATION); if (type & RGFW_iconWindow) SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)defaultIcon); @@ -10284,17 +7954,18 @@ RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_f } if (type & RGFW_iconWindow) { - win->src.hIconSmall = RGFW_loadHandleImage(data, w, h, format, TRUE); + win->src.hIconSmall = RGFW_loadHandleImage(src, channels, a, TRUE); SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)win->src.hIconSmall); } if (type & RGFW_iconTaskbar) { - win->src.hIconBig = RGFW_loadHandleImage(data, w, h, format, TRUE); + win->src.hIconBig = RGFW_loadHandleImage(src, channels, a, TRUE); SendMessage(win->src.window, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)win->src.hIconBig); } return RGFW_TRUE; #else - RGFW_UNUSED(img); - RGFW_UNUSED(type); + RGFW_UNUSED(src); + RGFW_UNUSED(a); + RGFW_UNUSED(channels); return RGFW_FALSE; #endif } @@ -10326,7 +7997,7 @@ RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { if (textLen > 1) wcstombs(str, wstr, (size_t)(textLen)); - str[textLen - 1] = '\0'; + str[textLen] = '\0'; } } @@ -10354,7 +8025,7 @@ void RGFW_writeClipboard(const char* text, u32 textLen) { MultiByteToWideChar(CP_UTF8, 0, text, -1, buffer, (i32)textLen); GlobalUnlock(object); - if (!OpenClipboard(_RGFW->root->src.window)) { + if (!OpenClipboard(_RGFW.root->src.window)) { GlobalFree(object); return; } @@ -10364,307 +8035,94 @@ void RGFW_writeClipboard(const char* text, u32 textLen) { CloseClipboard(); } -void RGFW_window_moveMouse(RGFW_window* win, i32 x, i32 y) { +void RGFW_window_moveMouse(RGFW_window* win, RGFW_point p) { RGFW_ASSERT(win != NULL); - win->internal.lastMouseX = x - win->x; - win->internal.lastMouseX = y - win->y; - SetCursorPos(x, y); + win->_lastMousePoint = RGFW_POINT(p.x - win->r.x, p.y - win->r.y); + SetCursorPos(p.x, p.y); } #ifdef RGFW_OPENGL -RGFW_bool RGFW_extensionSupportedPlatform_OpenGL(const char * extension, size_t len) { - const char* extensions = NULL; - - RGFW_proc proc = RGFW_getProcAddress_OpenGL("wglGetExtensionsStringARB"); - RGFW_proc proc2 = RGFW_getProcAddress_OpenGL("wglGetExtensionsStringEXT"); - - if (proc) - extensions = ((const char* (*)(HDC))proc)(wglGetCurrentDC()); - else if (proc2) - extensions = ((const char*(*)(void))proc2)(); - return extensions != NULL && RGFW_extensionSupportedStr(extensions, extension, len); -} - -RGFW_proc RGFW_getProcAddress_OpenGL(const char* procname) { - RGFW_proc proc = (RGFW_proc)wglGetProcAddress(procname); - if (proc) - return proc; - - return (RGFW_proc) GetProcAddress(RGFW_wgl_dll, procname); -} - -void RGFW_win32_loadOpenGLFuncs(HWND dummyWin) { - if (wglSwapIntervalEXT != NULL && wglChoosePixelFormatARB != NULL && wglChoosePixelFormatARB != NULL) - return; - - HDC dummy_dc = GetDC(dummyWin); - u32 pfd_flags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; - - PIXELFORMATDESCRIPTOR pfd = {sizeof(pfd), 1, pfd_flags, PFD_TYPE_RGBA, 32, 8, PFD_MAIN_PLANE, 32, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 32, 8, 0, PFD_MAIN_PLANE, 0, 0, 0, 0}; - - int dummy_pixel_format = ChoosePixelFormat(dummy_dc, &pfd); - SetPixelFormat(dummy_dc, dummy_pixel_format, &pfd); - - HGLRC dummy_context = wglCreateContext(dummy_dc); - - HGLRC cur = wglGetCurrentContext(); - wglMakeCurrent(dummy_dc, dummy_context); - - wglCreateContextAttribsARB = ((PFNWGLCREATECONTEXTATTRIBSARBPROC(WINAPI *)(const char*)) wglGetProcAddress)("wglCreateContextAttribsARB"); - wglChoosePixelFormatARB = ((PFNWGLCHOOSEPIXELFORMATARBPROC(WINAPI *)(const char*)) wglGetProcAddress)("wglChoosePixelFormatARB"); - - wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)(RGFW_proc)wglGetProcAddress("wglSwapIntervalEXT"); - if (wglSwapIntervalEXT == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to load swap interval function"); - } - - wglMakeCurrent(dummy_dc, cur); - wglDeleteContext(dummy_context); - ReleaseDC(dummyWin, dummy_dc); -} - -#define WGL_ACCELERATION_ARB 0x2003 -#define WGL_FULL_ACCELERATION_ARB 0x2027 -#define WGL_DRAW_TO_WINDOW_ARB 0x2001 -#define WGL_PIXEL_TYPE_ARB 0x2013 -#define WGL_TYPE_RGBA_ARB 0x202b -#define WGL_SUPPORT_OPENGL_ARB 0x2010 -#define WGL_COLOR_BITS_ARB 0x2014 -#define WGL_DOUBLE_BUFFER_ARB 0x2011 -#define WGL_ALPHA_BITS_ARB 0x201b -#define WGL_DEPTH_BITS_ARB 0x2022 -#define WGL_STENCIL_BITS_ARB 0x2023 -#define WGL_STEREO_ARB 0x2012 -#define WGL_AUX_BUFFERS_ARB 0x2024 -#define WGL_RED_BITS_ARB 0x2015 -#define WGL_GREEN_BITS_ARB 0x2017 -#define WGL_BLUE_BITS_ARB 0x2019 -#define WGL_ACCUM_RED_BITS_ARB 0x201e -#define WGL_ACCUM_GREEN_BITS_ARB 0x201f -#define WGL_ACCUM_BLUE_BITS_ARB 0x2020 -#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021 -#define WGL_COLORSPACE_SRGB_EXT 0x3089 -#define WGL_CONTEXT_OPENGL_NO_ERROR_ARB 0x31b3 -#define WGL_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097 -#define WGL_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB 0x0000 -#define WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098 -#define WGL_CONTEXT_FLAGS_ARB 0x2094 -#define WGL_ACCESS_READ_WRITE_NV 0x00000001 -#define WGL_COVERAGE_SAMPLES_NV 0x2042 -#define WGL_CONTEXT_ES_PROFILE_BIT_EXT 0x00000004 -#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 -#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 -#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 -#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 -#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 -#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20A9 -#define WGL_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097 -#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 -#define WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004 - -RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { - const char flushControl[] = "WGL_ARB_context_flush_control"; - const char noError[] = "WGL_ARB_create_context_no_error"; - const char robustness[] = "WGL_ARB_create_context_robustness"; - - win->src.ctx.native = ctx; - win->src.gfxType = RGFW_gfxNativeOpenGL; - - PIXELFORMATDESCRIPTOR pfd; - pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); - pfd.nVersion = 1; - pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; - pfd.iPixelType = PFD_TYPE_RGBA; - pfd.iLayerType = PFD_MAIN_PLANE; - pfd.cColorBits = 32; - pfd.cAlphaBits = 8; - pfd.cDepthBits = 24; - pfd.cStencilBits = (BYTE)hints->stencil; - pfd.cAuxBuffers = (BYTE)hints->auxBuffers; - if (hints->stereo) pfd.dwFlags |= PFD_STEREO; - - /* try to create the pixel format we want for OpenGL and then try to create an OpenGL context for the specified version */ - if (hints->renderer == RGFW_glSoftware) - pfd.dwFlags |= PFD_GENERIC_FORMAT | PFD_GENERIC_ACCELERATED; - - /* get pixel format, default to a basic pixel format */ - int pixel_format = ChoosePixelFormat(win->src.hdc, &pfd); - if (wglChoosePixelFormatARB != NULL) { - i32 pixel_format_attribs[50]; - RGFW_attribStack stack; - RGFW_attribStack_init(&stack, pixel_format_attribs, 50); - - RGFW_attribStack_pushAttribs(&stack, WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB); - RGFW_attribStack_pushAttribs(&stack, WGL_DRAW_TO_WINDOW_ARB, 1); - RGFW_attribStack_pushAttribs(&stack, WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB); - RGFW_attribStack_pushAttribs(&stack, WGL_SUPPORT_OPENGL_ARB, 1); - RGFW_attribStack_pushAttribs(&stack, WGL_COLOR_BITS_ARB, 32); - RGFW_attribStack_pushAttribs(&stack, WGL_DOUBLE_BUFFER_ARB, 1); - RGFW_attribStack_pushAttribs(&stack, WGL_ALPHA_BITS_ARB, hints->alpha); - RGFW_attribStack_pushAttribs(&stack, WGL_DEPTH_BITS_ARB, hints->depth); - RGFW_attribStack_pushAttribs(&stack, WGL_STENCIL_BITS_ARB, hints->stencil); - RGFW_attribStack_pushAttribs(&stack, WGL_STEREO_ARB, hints->stereo); - RGFW_attribStack_pushAttribs(&stack, WGL_AUX_BUFFERS_ARB, hints->auxBuffers); - RGFW_attribStack_pushAttribs(&stack, WGL_RED_BITS_ARB, hints->red); - RGFW_attribStack_pushAttribs(&stack, WGL_GREEN_BITS_ARB, hints->blue); - RGFW_attribStack_pushAttribs(&stack, WGL_BLUE_BITS_ARB, hints->green); - RGFW_attribStack_pushAttribs(&stack, WGL_ACCUM_RED_BITS_ARB, hints->accumRed); - RGFW_attribStack_pushAttribs(&stack, WGL_ACCUM_GREEN_BITS_ARB, hints->accumGreen); - RGFW_attribStack_pushAttribs(&stack, WGL_ACCUM_BLUE_BITS_ARB, hints->accumBlue); - RGFW_attribStack_pushAttribs(&stack, WGL_ACCUM_ALPHA_BITS_ARB, hints->accumAlpha); - - if(hints->sRGB) { - if (hints->profile != RGFW_glES) - RGFW_attribStack_pushAttribs(&stack, WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, 1); - else - RGFW_attribStack_pushAttribs(&stack, WGL_COLORSPACE_SRGB_EXT, hints->sRGB); - } - - RGFW_attribStack_pushAttribs(&stack, WGL_COVERAGE_SAMPLES_NV, hints->samples); - - RGFW_attribStack_pushAttribs(&stack, 0, 0); - - int new_pixel_format; - UINT num_formats; - wglChoosePixelFormatARB(win->src.hdc, pixel_format_attribs, 0, 1, &new_pixel_format, &num_formats); - if (!num_formats) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to create a pixel format for WGL"); - else pixel_format = new_pixel_format; - } - - PIXELFORMATDESCRIPTOR suggested; - if (!DescribePixelFormat(win->src.hdc, pixel_format, sizeof(suggested), &suggested) || - !SetPixelFormat(win->src.hdc, pixel_format, &pfd)) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to set the WGL pixel format"); - - if (wglCreateContextAttribsARB != NULL) { - /* create OpenGL/WGL context for the specified version */ - i32 attribs[40]; - RGFW_attribStack stack; - RGFW_attribStack_init(&stack, attribs, 50); - - - i32 mask = 0; - switch (hints->profile) { - case RGFW_glES: mask |= WGL_CONTEXT_ES_PROFILE_BIT_EXT; break; - case RGFW_glCompatibility: mask |= WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; break; - case RGFW_glCore: mask |= WGL_CONTEXT_CORE_PROFILE_BIT_ARB; break; - default: mask |= WGL_CONTEXT_CORE_PROFILE_BIT_ARB; break; - } - - RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_PROFILE_MASK_ARB, mask); - - if (hints->minor || hints->major) { - RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_MAJOR_VERSION_ARB, hints->major); - RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_MINOR_VERSION_ARB, hints->minor); - } - - if (RGFW_extensionSupportedPlatform_OpenGL(noError, sizeof(noError))) - RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_OPENGL_NO_ERROR_ARB, hints->noError); - - if (RGFW_extensionSupportedPlatform_OpenGL(flushControl, sizeof(flushControl))) { - if (hints->releaseBehavior == RGFW_glReleaseFlush) { - RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_RELEASE_BEHAVIOR_ARB, WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB); /* WGL_CONTEXT_RELEASE_BEHAVIOR_ARB */ - } else if (hints->releaseBehavior == RGFW_glReleaseNone) { - RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_RELEASE_BEHAVIOR_ARB, WGL_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB); - } - } - - i32 flags = 0; - if (hints->debug) flags |= WGL_CONTEXT_DEBUG_BIT_ARB; - if (hints->robustness && RGFW_extensionSupportedPlatform_OpenGL(robustness, sizeof(robustness))) flags |= WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB; - if (flags) { - RGFW_attribStack_pushAttribs(&stack, WGL_CONTEXT_FLAGS_ARB, flags); - } - - - RGFW_attribStack_pushAttribs(&stack, 0, 0); - - win->src.ctx.native->ctx = (HGLRC)wglCreateContextAttribsARB(win->src.hdc, NULL, attribs); - } - - if (wglCreateContextAttribsARB == NULL || win->src.ctx.native->ctx == NULL) { /* fall back to a default context (probably OpenGL 2 or something) */ - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to create an accelerated OpenGL Context."); - win->src.ctx.native->ctx = wglCreateContext(win->src.hdc); - } - - ReleaseDC(win->src.window, win->src.hdc); - win->src.hdc = GetDC(win->src.window); - - if (hints->share) { - wglShareLists((HGLRC)RGFW_getCurrentContext_OpenGL(), hints->share->ctx); - } - - wglMakeCurrent(win->src.hdc, win->src.ctx.native->ctx); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context initalized."); - return RGFW_TRUE; -} - -void RGFW_window_deleteContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx) { - wglDeleteContext((HGLRC) ctx->ctx); /*!< delete OpenGL context */ - win->src.ctx.native->ctx = NULL; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context freed."); -} - -void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win) { +void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { if (win == NULL) wglMakeCurrent(NULL, NULL); else - wglMakeCurrent(win->src.hdc, (HGLRC) win->src.ctx.native->ctx); -} -void* RGFW_getCurrentContext_OpenGL(void) { - return wglGetCurrentContext(); -} -void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { - RGFW_ASSERT(win->src.ctx.native); - SwapBuffers(win->src.hdc); + wglMakeCurrent(win->src.hdc, (HGLRC) win->src.ctx); } +void* RGFW_getCurrent_OpenGL(void) { return wglGetCurrentContext(); } +void RGFW_window_swapBuffers_OpenGL(RGFW_window* win){ SwapBuffers(win->src.hdc); } +#endif -void RGFW_window_swapInterval_OpenGL(RGFW_window* win, i32 swapInterval) { +#ifndef RGFW_EGL +void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { RGFW_ASSERT(win != NULL); +#if defined(RGFW_OPENGL) if (wglSwapIntervalEXT == NULL || wglSwapIntervalEXT(swapInterval) == FALSE) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to set swap interval"); + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to set swap interval"); +#else + RGFW_UNUSED(swapInterval); +#endif } #endif -RGFW_bool RGFW_createUTF8FromWideStringWin32(const WCHAR* source, char* output, size_t max) { - i32 size = 0; - if (source == NULL) { - return RGFW_FALSE; +void RGFW_window_swapBuffers_software(RGFW_window* win) { +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + if (win->buffer != win->src.bitmapBits) + memcpy(win->src.bitmapBits, win->buffer, win->bufferSize.w * win->bufferSize.h * 4); + + RGFW_RGB_to_BGR(win, win->src.bitmapBits); + BitBlt(win->src.hdc, 0, 0, win->r.w, win->r.h, win->src.hdcMem, 0, 0, SRCCOPY); +#else + RGFW_UNUSED(win); +#endif +} + +char* RGFW_createUTF8FromWideStringWin32(const WCHAR* source) { + if (source == NULL) { + return NULL; } - size = WideCharToMultiByte(CP_UTF8, 0, source, -1, NULL, 0, NULL, NULL); + i32 size = WideCharToMultiByte(CP_UTF8, 0, source, -1, NULL, 0, NULL, NULL); if (!size) { - return RGFW_FALSE; + return NULL; } - if (size > (i32)max) - size = (i32)max; + static char target[RGFW_MAX_PATH * 2]; + if (size > RGFW_MAX_PATH * 2) + size = RGFW_MAX_PATH * 2; - if (!WideCharToMultiByte(CP_UTF8, 0, source, -1, output, size, NULL, NULL)) { - return RGFW_FALSE; + target[size] = 0; + + if (!WideCharToMultiByte(CP_UTF8, 0, source, -1, target, size, NULL, NULL)) { + return NULL; } - output[size] = 0; - return RGFW_TRUE; + return target; } -#ifdef RGFW_WEBGPU -WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance instance) { - WGPUSurfaceDescriptor surfaceDesc = {0}; - WGPUSurfaceSourceWindowsHWND fromHwnd = {0}; - fromHwnd.chain.sType = WGPUSType_SurfaceSourceWindowsHWND; - fromHwnd.hwnd = window->src.window; /* Get HWND from RGFW window source */ - if (!fromHwnd.hwnd) { - fprintf(stderr, "RGFW Error: HWND is NULL for Windows window.\n"); - return NULL; - } - fromHwnd.hinstance = GetModuleHandle(NULL); /* Get current process HINSTANCE */ +u64 RGFW_getTimerFreq(void) { + static u64 frequency = 0; + if (frequency == 0) QueryPerformanceFrequency((LARGE_INTEGER*)&frequency); - surfaceDesc.nextInChain = (WGPUChainedStruct*)&fromHwnd.chain; - return wgpuInstanceCreateSurface(instance, &surfaceDesc); + return frequency; } + +u64 RGFW_getTimerValue(void) { + u64 value; + QueryPerformanceCounter((LARGE_INTEGER*)&value); + return value; +} + +void RGFW_sleep(u64 ms) { + Sleep((u32)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 /* RGFW_WINDOWS */ /* @@ -10693,7 +8151,6 @@ WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance i #include #include -#ifndef __OBJC__ typedef CGRect NSRect; typedef CGPoint NSPoint; typedef CGSize NSSize; @@ -10703,46 +8160,106 @@ typedef unsigned long NSUInteger; typedef long NSInteger; typedef NSInteger NSModalResponse; -typedef enum NSApplicationActivationPolicy { - NSApplicationActivationPolicyRegular, - NSApplicationActivationPolicyAccessory, - NSApplicationActivationPolicyProhibited -} NSApplicationActivationPolicy; +#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 -typedef RGFW_ENUM(u32, NSBackingStoreType) { - NSBackingStoreRetained = 0, - NSBackingStoreNonretained = 1, - NSBackingStoreBuffered = 2 +#define NSAlloc(nsclass) objc_msgSend_id((id)nsclass, sel_registerName("alloc")) +#define objc_msgSend_bool(x, y) ((BOOL (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_void(x, y) ((void (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_void_id(x, y, z) ((void (*)(id, SEL, id))objc_msgSend) ((id)x, (SEL)y, (id)z) +#define objc_msgSend_uint(x, y) ((NSUInteger (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_void_bool(x, y, z) ((void (*)(id, SEL, BOOL))objc_msgSend) ((id)(x), (SEL)y, (BOOL)z) +#define objc_msgSend_bool_void(x, y) ((BOOL (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_void_SEL(x, y, z) ((void (*)(id, SEL, SEL))objc_msgSend) ((id)(x), (SEL)y, (SEL)z) +#define objc_msgSend_id(x, y) ((id (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) +#define objc_msgSend_id_id(x, y, z) ((id (*)(id, SEL, id))objc_msgSend) ((id)(x), (SEL)y, (id)z) +#define objc_msgSend_id_bool(x, y, z) ((BOOL (*)(id, SEL, id))objc_msgSend) ((id)(x), (SEL)y, (id)z) +#define objc_msgSend_int(x, y, z) ((id (*)(id, SEL, int))objc_msgSend) ((id)(x), (SEL)y, (int)z) +#define objc_msgSend_arr(x, y, z) ((id (*)(id, SEL, int))objc_msgSend) ((id)(x), (SEL)y, (int)z) +#define objc_msgSend_ptr(x, y, z) ((id (*)(id, SEL, void*))objc_msgSend) ((id)(x), (SEL)y, (void*)z) +#define objc_msgSend_class(x, y) ((id (*)(Class, SEL))objc_msgSend) ((Class)(x), (SEL)y) +#define objc_msgSend_class_char(x, y, z) ((id (*)(Class, SEL, char*))objc_msgSend) ((Class)(x), (SEL)y, (char*)z) + +id NSApp = NULL; + +#define NSRelease(obj) objc_msgSend_void((id)obj, sel_registerName("release")) +id NSString_stringWithUTF8String(const char* str); +id 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(id str); +const char* NSString_to_char(id str) { + return ((const char* (*)(id, SEL)) objc_msgSend) ((id)(id)str, sel_registerName("UTF8String")); +} + +void si_impl_func_to_SEL_with_name(const char* class_name, const char* register_name, void* function); +void si_impl_func_to_SEL_with_name(const char* class_name, const char* register_name, void* function) { + Class selected_class; + + if (RGFW_STRNCMP(class_name, "NSView", 6) == 0) { + selected_class = objc_getClass("ViewClass"); + } else if (RGFW_STRNCMP(class_name, "NSWindow", 8) == 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) +#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":", (void*)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":", (void*)function) + +unsigned char* NSBitmapImageRep_bitmapData(id imageRep); +unsigned char* NSBitmapImageRep_bitmapData(id imageRep) { + return ((unsigned char* (*)(id, SEL))objc_msgSend) ((id)imageRep, sel_registerName("bitmapData")); +} + +typedef RGFW_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 = (1 << 8), + NSBitmapFormatThirtyTwoBitLittleEndian = (1 << 9), + NSBitmapFormatSixteenBitBigEndian = (1 << 10), + NSBitmapFormatThirtyTwoBitBigEndian = (1 << 11) }; -typedef RGFW_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 -}; +id 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); +id 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) { + SEL func = sel_registerName("initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel:"); -#define NSPasteboardTypeString "public.utf8-plain-text" + return (id) ((id(*)(id, SEL, unsigned char**, NSInteger, NSInteger, NSInteger, NSInteger, bool, bool, id, NSBitmapFormat, NSInteger, NSInteger))objc_msgSend) + (NSAlloc((id)objc_getClass("NSBitmapImageRep")), func, planes, width, height, bps, spp, alpha, isPlanar, NSString_stringWithUTF8String(colorSpaceName), bitmapFormat, rowBytes, pixelBits); +} -typedef RGFW_ENUM(i32, NSDragOperation) { - NSDragOperationNone = 0, - NSDragOperationCopy = 1, - NSDragOperationLink = 2, - NSDragOperationGeneric = 4, - NSDragOperationPrivate = 8, - NSDragOperationMove = 16, - NSDragOperationDelete = 32, - NSDragOperationEvery = (int)ULONG_MAX -}; +id NSColor_colorWithSRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha); +id NSColor_colorWithSRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) { + void* nsclass = objc_getClass("NSColor"); + SEL func = sel_registerName("colorWithSRGBRed:green:blue:alpha:"); + return ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend) + ((id)nsclass, func, red, green, blue, alpha); +} typedef RGFW_ENUM(NSInteger, NSOpenGLContextParameter) { NSOpenGLContextParameterSwapInterval = 222, /* 1 param. 0 -> Don't sync, 1 -> Sync to vertical retrace */ @@ -10772,151 +8289,15 @@ typedef RGFW_ENUM(NSInteger, NSWindowButton) { NSWindowDocumentVersionsButton = 6, NSWindowFullScreenButton = 7, }; - -#define NSPasteboardTypeURL "public.url" -#define NSPasteboardTypeFileURL "public.file-url" -#define NSTrackingMouseEnteredAndExited 0x01 -#define NSTrackingMouseMoved 0x02 -#define NSTrackingCursorUpdate 0x04 -#define NSTrackingActiveWhenFirstResponder 0x10 -#define NSTrackingActiveInKeyWindow 0x20 -#define NSTrackingActiveInActiveApp 0x40 -#define NSTrackingActiveAlways 0x80 -#define NSTrackingAssumeInside 0x100 -#define NSTrackingInVisibleRect 0x200 -#define NSTrackingEnabledDuringMouseDrag 0x400 -enum { - NSOpenGLPFAAllRenderers = 1, /* choose from all available renderers */ - NSOpenGLPFATripleBuffer = 3, /* choose a triple buffered pixel format */ - NSOpenGLPFADoubleBuffer = 5, /* choose a double buffered pixel format */ - NSOpenGLPFAAuxBuffers = 7, /* number of aux buffers */ - NSOpenGLPFAColorSize = 8, /* number of color buffer bits */ - NSOpenGLPFAAlphaSize = 11, /* number of alpha component bits */ - NSOpenGLPFADepthSize = 12, /* number of depth buffer bits */ - NSOpenGLPFAStencilSize = 13, /* number of stencil buffer bits */ - NSOpenGLPFAAccumSize = 14, /* number of accum buffer bits */ - NSOpenGLPFAMinimumPolicy = 51, /* never choose smaller buffers than requested */ - NSOpenGLPFAMaximumPolicy = 52, /* choose largest buffers of type requested */ - NSOpenGLPFASampleBuffers = 55, /* number of multi sample buffers */ - NSOpenGLPFASamples = 56, /* number of samples per multi sample buffer */ - NSOpenGLPFAAuxDepthStencil = 57, /* each aux buffer has its own depth stencil */ - NSOpenGLPFAColorFloat = 58, /* color buffers store floating point pixels */ - NSOpenGLPFAMultisample = 59, /* choose multisampling */ - NSOpenGLPFASupersample = 60, /* choose supersampling */ - NSOpenGLPFASampleAlpha = 61, /* request alpha filtering */ - NSOpenGLPFARendererID = 70, /* request renderer by ID */ - NSOpenGLPFANoRecovery = 72, /* disable all failure recovery systems */ - NSOpenGLPFAAccelerated = 73, /* choose a hardware accelerated renderer */ - NSOpenGLPFAClosestPolicy = 74, /* choose the closest color buffer to request */ - NSOpenGLPFABackingStore = 76, /* back buffer contents are valid after swap */ - NSOpenGLPFAScreenMask = 84, /* bit mask of supported physical screens */ - NSOpenGLPFAAllowOfflineRenderers = 96, /* allow use of offline renderers */ - NSOpenGLPFAAcceleratedCompute = 97, /* choose a hardware accelerated compute device */ - NSOpenGLPFAOpenGLProfile = 99, /* specify an OpenGL Profile to use */ - NSOpenGLProfileVersionLegacy = 0x1000, /* The requested profile is a legacy (pre-OpenGL 3.0) profile. */ - NSOpenGLProfileVersion3_2Core = 0x3200, /* The 3.2 Profile of OpenGL */ - NSOpenGLProfileVersion4_1Core = 0x3200, /* The 4.1 profile of OpenGL */ - NSOpenGLPFAVirtualScreenCount = 128, /* number of virtual screens in this format */ - NSOpenGLPFAStereo = 6, - NSOpenGLPFAOffScreen = 53, - NSOpenGLPFAFullScreen = 54, - NSOpenGLPFASingleRenderer = 71, - NSOpenGLPFARobust = 75, - NSOpenGLPFAMPSafe = 78, - NSOpenGLPFAWindow = 80, - NSOpenGLPFAMultiScreen = 81, - NSOpenGLPFACompliant = 83, - NSOpenGLPFAPixelBuffer = 90, - NSOpenGLPFARemotePixelBuffer = 91, -}; - -typedef RGFW_ENUM(u32, NSEventType) { /* various types of events */ - NSEventTypeApplicationDefined = 15, -}; -typedef unsigned long long NSEventMask; - -typedef enum NSEventModifierFlags { - NSEventModifierFlagCapsLock = 1 << 16, - NSEventModifierFlagShift = 1 << 17, - NSEventModifierFlagControl = 1 << 18, - NSEventModifierFlagOption = 1 << 19, - NSEventModifierFlagCommand = 1 << 20, - NSEventModifierFlagNumericPad = 1 << 21 -} NSEventModifierFlags; - -typedef RGFW_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 = (1 << 8), - NSBitmapFormatThirtyTwoBitLittleEndian = (1 << 9), - NSBitmapFormatSixteenBitBigEndian = (1 << 10), - NSBitmapFormatThirtyTwoBitBigEndian = (1 << 11) -}; - -#else -#import -#include -#endif /* notdef __OBJC__ */ - -#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(x, y) ((BOOL (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) -#define objc_msgSend_void(x, y) ((void (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) -#define objc_msgSend_void_id(x, y, z) ((void (*)(id, SEL, id))objc_msgSend) ((id)x, (SEL)y, (id)z) -#define objc_msgSend_uint(x, y) ((NSUInteger (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) -#define objc_msgSend_void_bool(x, y, z) ((void (*)(id, SEL, BOOL))objc_msgSend) ((id)(x), (SEL)y, (BOOL)z) -#define objc_msgSend_bool_void(x, y) ((BOOL (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) -#define objc_msgSend_void_SEL(x, y, z) ((void (*)(id, SEL, SEL))objc_msgSend) ((id)(x), (SEL)y, (SEL)z) -#define objc_msgSend_id(x, y) ((id (*)(id, SEL))objc_msgSend) ((id)(x), (SEL)y) -#define objc_msgSend_id_id(x, y, z) ((id (*)(id, SEL, id))objc_msgSend) ((id)(x), (SEL)y, (id)z) -#define objc_msgSend_id_bool(x, y, z) ((BOOL (*)(id, SEL, id))objc_msgSend) ((id)(x), (SEL)y, (id)z) -#define objc_msgSend_int(x, y, z) ((id (*)(id, SEL, int))objc_msgSend) ((id)(x), (SEL)y, (int)z) -#define objc_msgSend_arr(x, y, z) ((id (*)(id, SEL, int))objc_msgSend) ((id)(x), (SEL)y, (int)z) -#define objc_msgSend_ptr(x, y, z) ((id (*)(id, SEL, void*))objc_msgSend) ((id)(x), (SEL)y, (void*)z) -#define objc_msgSend_class(x, y) ((id (*)(Class, SEL))objc_msgSend) ((Class)(x), (SEL)y) -#define objc_msgSend_class_char(x, y, z) ((id (*)(Class, SEL, char*))objc_msgSend) ((Class)(x), (SEL)y, (char*)z) - -#define NSRelease(obj) objc_msgSend_void((id)obj, sel_registerName("release")) -RGFWDEF id NSString_stringWithUTF8String(const char* str); -id NSString_stringWithUTF8String(const char* str) { - return ((id(*)(id, SEL, const char*))objc_msgSend) ((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), str); +void NSOpenGLContext_setValues(id context, const int* vals, NSOpenGLContextParameter param); +void NSOpenGLContext_setValues(id context, const int* vals, NSOpenGLContextParameter param) { + ((void (*)(id, SEL, const int*, NSOpenGLContextParameter))objc_msgSend) + (context, sel_registerName("setValues:forParameter:"), vals, param); } - -const char* NSString_to_char(id str); -const char* NSString_to_char(id str) { - return ((const char* (*)(id, SEL)) objc_msgSend) ((id)(id)str, sel_registerName("UTF8String")); -} - -unsigned char* NSBitmapImageRep_bitmapData(id imageRep); -unsigned char* NSBitmapImageRep_bitmapData(id imageRep) { - return ((unsigned char* (*)(id, SEL))objc_msgSend) ((id)imageRep, sel_registerName("bitmapData")); -} - -id 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); -id 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) { - SEL func = sel_registerName("initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel:"); - - return (id) ((id(*)(id, SEL, unsigned char**, NSInteger, NSInteger, NSInteger, NSInteger, bool, bool, id, NSBitmapFormat, NSInteger, NSInteger))objc_msgSend) - (NSAlloc((id)objc_getClass("NSBitmapImageRep")), func, planes, width, height, bps, spp, alpha, isPlanar, NSString_stringWithUTF8String(colorSpaceName), bitmapFormat, rowBytes, pixelBits); -} - -id NSColor_colorWithSRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha); -id NSColor_colorWithSRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) { - Class nsclass = objc_getClass("NSColor"); - SEL func = sel_registerName("colorWithSRGBRed:green:blue:alpha:"); - return ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend) - ((id)nsclass, func, red, green, blue, alpha); +void* NSOpenGLPixelFormat_initWithAttributes(const uint32_t* attribs); +void* NSOpenGLPixelFormat_initWithAttributes(const uint32_t* attribs) { + return (void*) ((id(*)(id, SEL, const uint32_t*))objc_msgSend) + (NSAlloc((id)objc_getClass("NSOpenGLPixelFormat")), sel_registerName("initWithAttributes:"), attribs); } id NSPasteboard_generalPasteboard(void); @@ -10937,7 +8318,7 @@ id* cstrToNSStringArray(char** strs, size_t len) { const char* NSPasteboard_stringForType(id pasteboard, NSPasteboardType dataType, size_t* len); const char* NSPasteboard_stringForType(id pasteboard, NSPasteboardType dataType, size_t* len) { SEL func = sel_registerName("stringForType:"); - id nsstr = NSString_stringWithUTF8String((const char*)dataType); + id nsstr = NSString_stringWithUTF8String(dataType); id nsString = ((id(*)(id, SEL, id))objc_msgSend)(pasteboard, func, nsstr); const char* str = NSString_to_char(nsString); if (len != NULL) @@ -10947,7 +8328,10 @@ const char* NSPasteboard_stringForType(id pasteboard, NSPasteboardType dataType, id c_array_to_NSArray(void* array, size_t len); id c_array_to_NSArray(void* array, size_t len) { - return ((id (*)(id, SEL, void*, NSUInteger))objc_msgSend) (NSAlloc(objc_getClass("NSArray")), sel_registerName("initWithObjects:count:"), array, 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); } @@ -10977,98 +8361,129 @@ NSInteger NSPasteBoard_declareTypes(id pasteboard, NSPasteboardType* newTypes, s #define NSRetain(obj) objc_msgSend_void((id)obj, sel_registerName("retain")) +typedef enum NSApplicationActivationPolicy { + NSApplicationActivationPolicyRegular, + NSApplicationActivationPolicyAccessory, + NSApplicationActivationPolicyProhibited +} NSApplicationActivationPolicy; + +typedef RGFW_ENUM(u32, NSBackingStoreType) { + NSBackingStoreRetained = 0, + NSBackingStoreNonretained = 1, + NSBackingStoreBuffered = 2 +}; + +typedef RGFW_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 +}; + +NSPasteboardType const NSPasteboardTypeString = "public.utf8-plain-text"; /* Replaces NSStringPasteboardType */ + + +typedef RGFW_ENUM(i32, NSDragOperation) { + NSDragOperationNone = 0, + NSDragOperationCopy = 1, + NSDragOperationLink = 2, + NSDragOperationGeneric = 4, + NSDragOperationPrivate = 8, + NSDragOperationMove = 16, + NSDragOperationDelete = 32, + NSDragOperationEvery = (int)ULONG_MAX +}; + +void* NSArray_objectAtIndex(id array, NSUInteger index) { + SEL func = sel_registerName("objectAtIndex:"); + return ((id(*)(id, SEL, NSUInteger))objc_msgSend)(array, func, index); +} + +id NSWindow_contentView(id window) { + SEL func = sel_registerName("contentView"); + return objc_msgSend_id(window, func); +} + /* End of cocoa wrapper */ -static id RGFW__osxCustomInitWithRGFWWindow(id self, SEL _cmd, RGFW_window* win) { - RGFW_UNUSED(_cmd); - struct objc_super s = { self, class_getSuperclass(object_getClass(self)) }; - self = ((id (*)(struct objc_super*, SEL))objc_msgSendSuper)(&s, sel_registerName("init")); +#ifdef RGFW_OPENGL +/* MacOS opengl API spares us yet again (there are no extensions) */ +RGFW_bool RGFW_extensionSupportedPlatform(const char * extension, size_t len) { RGFW_UNUSED(extension); RGFW_UNUSED(len); return RGFW_FALSE; } +CFBundleRef RGFWnsglFramework = NULL; - if (self != nil) { - object_setInstanceVariable(self, "RGFW_window", win); - object_setInstanceVariable(self, "trackingArea", nil); +RGFW_proc RGFW_getProcAddress(const char* procname) { + if (RGFWnsglFramework == NULL) + RGFWnsglFramework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); - object_setInstanceVariable( - self, "markedText", - ((id (*)(id, SEL))objc_msgSend)( - ((id (*)(Class, SEL))objc_msgSend)(objc_getClass("NSMutableAttributedString"), sel_registerName("alloc")), - sel_registerName("init") - ) - ); + CFStringRef symbolName = CFStringCreateWithCString(kCFAllocatorDefault, procname, kCFStringEncodingASCII); - ((void (*)(id, SEL))objc_msgSend)(self, sel_registerName("updateTrackingAreas")); + RGFW_proc symbol = (RGFW_proc)CFBundleGetFunctionPointerForName(RGFWnsglFramework, symbolName); - ((void (*)(id, SEL, id))objc_msgSend)( - self, sel_registerName("registerForDraggedTypes:"), - ((id (*)(Class, SEL, id))objc_msgSend)( - objc_getClass("NSArray"), - sel_registerName("arrayWithObject:"), - ((id (*)(Class, SEL, const char*))objc_msgSend)( - objc_getClass("NSString"), - sel_registerName("stringWithUTF8String:"), - "public.url" - ) - ) - ); - } + CFRelease(symbolName); - return self; + return symbol; +} +#endif + +id NSWindow_delegate(RGFW_window* win) { + return (id) objc_msgSend_id((id)win->src.window, sel_registerName("delegate")); } -static u32 RGFW_OnClose(id self) { +u32 RGFW_OnClose(id self) { RGFW_window* win = NULL; object_getInstanceVariable(self, (const char*)"RGFW_window", (void**)&win); if (win == NULL) return true; - RGFW_window_setShouldClose(win, RGFW_TRUE); - RGFW_eventQueuePushEx(e.type = RGFW_quit; e.common.win = win); + RGFW_eventQueuePushEx(e.type = RGFW_quit; e._win = win); RGFW_windowQuitCallback(win); return false; } /* NOTE(EimaMei): Fixes the constant clicking when the app is running under a terminal. */ -static bool RGFW__osxAcceptsFirstResponder(void) { return true; } -static bool RGFW__osxPerformKeyEquivalent(id event) { RGFW_UNUSED(event); return true; } +bool acceptsFirstResponder(void) { return true; } +bool performKeyEquivalent(id event) { RGFW_UNUSED(event); return true; } -static NSDragOperation RGFW__osxDraggingEntered(id self, SEL sel, id sender) { +NSDragOperation draggingEntered(id self, SEL sel, id sender) { RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); return NSDragOperationCopy; } -static NSDragOperation RGFW__osxDraggingUpdated(id self, SEL sel, id sender) { +NSDragOperation draggingUpdated(id self, SEL sel, id sender) { RGFW_UNUSED(sel); RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL || (!(win->internal.flags & RGFW_windowAllowDND))) + if (win == NULL || (!(win->_flags & RGFW_windowAllowDND))) return 0; - if (!(win->internal.enabledEvents & RGFW_dataDragFlag)) return NSDragOperationCopy; NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(sender, sel_registerName("draggingLocation")); - RGFW_eventQueuePushEx(e.type = RGFW_dataDrag; - e.mouse.x = (i32)p.x; e.mouse.y = (i32)(win->h - p.y); - e.common.win = win); + RGFW_eventQueuePushEx(e.type = RGFW_DNDInit; + e.point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); + e._win = win); - _RGFW->windowState.win = win; - _RGFW->windowState.dataDragging = RGFW_TRUE; - _RGFW->windowState.dropX = (i32)p.x; - _RGFW->windowState.dropY = (i32)(win->h - p.y); - - RGFW_dataDragCallback(win, (i32) p.x, (i32) (win->h - p.y)); + RGFW_dndInitCallback(win, win->event.point); return NSDragOperationCopy; } -static bool RGFW__osxPrepareForDragOperation(id self) { +bool prepareForDragOperation(id self) { RGFW_window* win = NULL; object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL || (!(win->internal.enabledEvents & RGFW_dataDropFlag))) + if (win == NULL) return true; - if (!(win->internal.flags & RGFW_windowAllowDND)) { + if (!(win->_flags & RGFW_windowAllowDND)) { return false; } @@ -11078,13 +8493,14 @@ static bool RGFW__osxPrepareForDragOperation(id self) { void RGFW__osxDraggingEnded(id self, SEL sel, id sender); void RGFW__osxDraggingEnded(id self, SEL sel, id sender) { RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); return; } -static bool RGFW__osxPerformDragOperation(id self, SEL sel, id sender) { +/* NOTE(EimaMei): Usually, you never need 'id self, SEL cmd' for C -> Obj-C methods. This isn't the case. */ +bool performDragOperation(id self, SEL sel, id sender) { RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); RGFW_window* win = NULL; object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL || (!(win->internal.enabledEvents & RGFW_dataDropFlag))) + if (win == NULL) return false; /* id pasteBoard = objc_msgSend_id(sender, sel_registerName("draggingPasteboard")); */ @@ -11099,7 +8515,7 @@ static bool RGFW__osxPerformDragOperation(id self, SEL sel, id sender) { /* Check if the pasteboard contains file URLs */ if (objc_msgSend_id_bool(types, sel_registerName("containsObject:"), fileURLsType) == 0) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, "No files found on the pasteboard."); + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errClipboard, RGFW_DEBUG_CTX(win, 0), "No files found on the pasteboard."); return 0; } @@ -11109,54 +8525,49 @@ static bool RGFW__osxPerformDragOperation(id self, SEL sel, id sender) { if (count == 0) return 0; - RGFW_event event; - event.drop.files = (char**)(void*)_RGFW->files; - - u32 i; - for (i = 0; i < (u32)count; i++) { + int i; + for (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")); - RGFW_STRNCPY(event.drop.files[i], filePath, RGFW_MAX_PATH - 1); - event.drop.files[i][RGFW_MAX_PATH - 1] = '\0'; + RGFW_STRNCPY(win->event.droppedFiles[i], filePath, RGFW_MAX_PATH - 1); + win->event.droppedFiles[i][RGFW_MAX_PATH - 1] = '\0'; } - - event.drop.count = (size_t)count; - RGFW_eventQueuePushEx(e.type = RGFW_dataDrop; - e.drop.count = (size_t)count; - e.drop.files = event.drop.files; - e.common.win = win); - - _RGFW->windowState.win = win; - _RGFW->windowState.dataDrop = RGFW_TRUE; - _RGFW->windowState.filesCount = event.drop.count; - RGFW_dataDropCallback(win, event.drop.files, event.drop.count); + NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(sender, sel_registerName("draggingLocation")); + + win->event.droppedFilesCount = (size_t)count; + RGFW_eventQueuePushEx(e.type = RGFW_DND; + e.point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); + e.droppedFilesCount = (size_t)count; + e._win = win); + + RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); return false; } #ifndef RGFW_NO_IOKIT #include +#include -u32 RGFW_osx_getFallbackRefreshRate(CGDirectDisplayID displayID); u32 RGFW_osx_getFallbackRefreshRate(CGDirectDisplayID displayID) { u32 refreshRate = 0; io_iterator_t it; io_service_t service; CFNumberRef indexRef, clockRef, countRef; - u32 clock, count; + uint32_t clock, count; -#ifdef kIOMainPortDefault +#ifdef kIOMainPortDefault if (IOServiceGetMatchingServices(kIOMainPortDefault, IOServiceMatching("IOFramebuffer"), &it) != 0) -#elif defined(kIOMasterPortDefault) +#elif defined(kIOMasterPortDefault) if (IOServiceGetMatchingServices(kIOMainPortDefault, IOServiceMatching("IOFramebuffer"), &it) != 0) #endif return RGFW_FALSE; while ((service = IOIteratorNext(it)) != 0) { - u32 index; + uint32_t index; indexRef = (CFNumberRef)IORegistryEntryCreateCFProperty(service, CFSTR("IOFramebufferOpenGLIndex"), kCFAllocatorDefault, kNilOptions); if (indexRef == 0) continue; - + if (CFNumberGetValue(indexRef, kCFNumberIntType, &index) && CGOpenGLDisplayMaskToDisplayID(1 << index) == displayID) { CFRelease(indexRef); break; @@ -11171,8 +8582,7 @@ u32 RGFW_osx_getFallbackRefreshRate(CGDirectDisplayID displayID) { if (CFNumberGetValue(clockRef, kCFNumberIntType, &clock) && clock) { countRef = (CFNumberRef)IORegistryEntryCreateCFProperty(service, CFSTR("IOFBCurrentPixelCount"), kCFAllocatorDefault, kNilOptions); if (countRef && CFNumberGetValue(countRef, kCFNumberIntType, &count) && count) { - float rate = (float)((double)clock / (double) count); - refreshRate = (u32)RGFW_ROUND(rate); + refreshRate = (u32)RGFW_ROUND(clock / (double) count); CFRelease(countRef); } } @@ -11183,6 +8593,201 @@ u32 RGFW_osx_getFallbackRefreshRate(CGDirectDisplayID displayID) { IOObjectRelease(it); return refreshRate; } + +IOHIDDeviceRef RGFW_osxControllers[4] = {NULL}; + +size_t findControllerIndex(IOHIDDeviceRef device) { + size_t i; + for (i = 0; i < 4; i++) + if (RGFW_osxControllers[i] == device) + return i; + return (size_t)-1; +} + +void RGFW__osxInputValueChangedCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value) { + RGFW_UNUSED(context); RGFW_UNUSED(result); RGFW_UNUSED(sender); + IOHIDElementRef element = IOHIDValueGetElement(value); + + IOHIDDeviceRef device = IOHIDElementGetDevice(element); + size_t index = findControllerIndex(device); + if (index == (size_t)-1) return; + + uint32_t usagePage = IOHIDElementGetUsagePage(element); + uint32_t usage = IOHIDElementGetUsage(element); + + CFIndex intValue = IOHIDValueGetIntegerValue(value); + + u8 RGFW_osx2RGFWSrc[2][RGFW_gamepadFinal] = {{ + 0, RGFW_gamepadSelect, RGFW_gamepadL3, RGFW_gamepadR3, RGFW_gamepadStart, + RGFW_gamepadUp, RGFW_gamepadRight, RGFW_gamepadDown, RGFW_gamepadLeft, + RGFW_gamepadL2, RGFW_gamepadR2, RGFW_gamepadL1, RGFW_gamepadR1, + RGFW_gamepadY, RGFW_gamepadB, RGFW_gamepadA, RGFW_gamepadX, RGFW_gamepadHome}, + {0, RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadR3, RGFW_gamepadX, + RGFW_gamepadY, RGFW_gamepadRight, RGFW_gamepadL1, RGFW_gamepadR1, + RGFW_gamepadL2, RGFW_gamepadR2, RGFW_gamepadDown, RGFW_gamepadStart, + RGFW_gamepadUp, RGFW_gamepadL3, RGFW_gamepadSelect, RGFW_gamepadStart, RGFW_gamepadHome} + }; + + u8* RGFW_osx2RGFW = RGFW_osx2RGFWSrc[0]; + if (RGFW_gamepads_type[index] == RGFW_gamepadMicrosoft) + RGFW_osx2RGFW = RGFW_osx2RGFWSrc[1]; + + switch (usagePage) { + case kHIDPage_Button: { + u8 button = 0; + if (usage < sizeof(RGFW_osx2RGFW)) + button = RGFW_osx2RGFW[usage]; + + RGFW_gamepadButtonCallback(_RGFW.root, (u16)index, button, (u8)intValue); + RGFW_gamepadPressed[index][button].prev = RGFW_gamepadPressed[index][button].current; + RGFW_gamepadPressed[index][button].current = RGFW_BOOL(intValue); + RGFW_eventQueuePushEx(e.type = intValue ? RGFW_gamepadButtonPressed: RGFW_gamepadButtonReleased; + e.button = button; + e.gamepad = (u16)index; + e._win = _RGFW.root); + break; + } + case kHIDPage_GenericDesktop: { + CFIndex logicalMin = IOHIDElementGetLogicalMin(element); + CFIndex logicalMax = IOHIDElementGetLogicalMax(element); + + if (logicalMax <= logicalMin) return; + if (intValue < logicalMin) intValue = logicalMin; + if (intValue > logicalMax) intValue = logicalMax; + + i8 axisValue = (i8)(-100.0 + ((intValue - logicalMin) * 200.0) / (logicalMax - logicalMin)); + + u8 whichAxis = 0; + switch (usage) { + case kHIDUsage_GD_X: RGFW_gamepadAxes[index][0].x = axisValue; whichAxis = 0; break; + case kHIDUsage_GD_Y: RGFW_gamepadAxes[index][0].y = axisValue; whichAxis = 0; break; + case kHIDUsage_GD_Z: RGFW_gamepadAxes[index][1].x = axisValue; whichAxis = 1; break; + case kHIDUsage_GD_Rz: RGFW_gamepadAxes[index][1].y = axisValue; whichAxis = 1; break; + default: return; + } + + RGFW_event e; + e.type = RGFW_gamepadAxisMove; + e.gamepad = (u16)index; + e.whichAxis = whichAxis; + e._win = _RGFW.root; + for (size_t i = 0; i < 4; i++) + e.axis[i] = RGFW_gamepadAxes[index][i]; + + RGFW_eventQueuePush(e); + + RGFW_gamepadAxisCallback(_RGFW.root, (u16)index, RGFW_gamepadAxes[index], 2, whichAxis); + } + } +} + +void RGFW__osxDeviceAddedCallback(void* context, IOReturn result, void *sender, IOHIDDeviceRef device) { + RGFW_UNUSED(context); RGFW_UNUSED(result); RGFW_UNUSED(sender); + CFTypeRef usageRef = (CFTypeRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDPrimaryUsageKey)); + int usage = 0; + if (usageRef) + CFNumberGetValue((CFNumberRef)usageRef, kCFNumberIntType, (void*)&usage); + + if (usage != kHIDUsage_GD_Joystick && usage != kHIDUsage_GD_GamePad && usage != kHIDUsage_GD_MultiAxisController) { + return; + } + + size_t i; + for (i = 0; i < 4; i++) { + if (RGFW_osxControllers[i] != NULL) + continue; + + RGFW_osxControllers[i] = device; + + IOHIDDeviceRegisterInputValueCallback(device, RGFW__osxInputValueChangedCallback, NULL); + + CFStringRef deviceName = (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)); + if (deviceName) + CFStringGetCString(deviceName, RGFW_gamepads_name[i], sizeof(RGFW_gamepads_name[i]), kCFStringEncodingUTF8); + + RGFW_gamepads_type[i] = RGFW_gamepadUnknown; + if (RGFW_STRSTR(RGFW_gamepads_name[i], "Microsoft") || RGFW_STRSTR(RGFW_gamepads_name[i], "X-Box") || RGFW_STRSTR(RGFW_gamepads_name[i], "Xbox")) + RGFW_gamepads_type[i] = RGFW_gamepadMicrosoft; + else if (RGFW_STRSTR(RGFW_gamepads_name[i], "PlayStation") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS3") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS4") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS5")) + RGFW_gamepads_type[i] = RGFW_gamepadSony; + else if (RGFW_STRSTR(RGFW_gamepads_name[i], "Nintendo")) + RGFW_gamepads_type[i] = RGFW_gamepadNintendo; + else if (RGFW_STRSTR(RGFW_gamepads_name[i], "Logitech")) + RGFW_gamepads_type[i] = RGFW_gamepadLogitech; + + RGFW_gamepads[i] = (u16)i; + RGFW_gamepadCount++; + + RGFW_eventQueuePushEx(e.type = RGFW_gamepadConnected; + e.gamepad = (u16)i; + e._win = _RGFW.root); + + RGFW_gamepadCallback(_RGFW.root, (u16)i, 1); + break; + } +} + +void RGFW__osxDeviceRemovedCallback(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) { + RGFW_UNUSED(context); RGFW_UNUSED(result); RGFW_UNUSED(sender); RGFW_UNUSED(device); + CFNumberRef usageRef = (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDPrimaryUsageKey)); + int usage = 0; + if (usageRef) + CFNumberGetValue(usageRef, kCFNumberIntType, &usage); + + if (usage != kHIDUsage_GD_Joystick && usage != kHIDUsage_GD_GamePad && usage != kHIDUsage_GD_MultiAxisController) { + return; + } + + size_t index = findControllerIndex(device); + if (index != (size_t)-1) + RGFW_osxControllers[index] = NULL; + + RGFW_eventQueuePushEx(e.type = RGFW_gamepadDisconnected; + e.gamepad = (u16)index; + e._win = _RGFW.root); + RGFW_gamepadCallback(_RGFW.root, (u16)index, 0); + + RGFW_gamepadCount--; +} + +RGFWDEF void RGFW_osxInitIOKit(void); +void RGFW_osxInitIOKit(void) { + IOHIDManagerRef hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + if (!hidManager) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errIOKit, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to create IOHIDManager."); + return; + } + + CFMutableDictionaryRef matchingDictionary = CFDictionaryCreateMutable( + kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks + ); + if (!matchingDictionary) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errIOKit, RGFW_DEBUG_CTX(_RGFW.root, 0), "Failed to create matching dictionary for IOKit."); + CFRelease(hidManager); + return; + } + + CFDictionarySetValue( + matchingDictionary, + CFSTR(kIOHIDDeviceUsagePageKey), + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, (int[]){kHIDPage_GenericDesktop}) + ); + + IOHIDManagerSetDeviceMatching(hidManager, matchingDictionary); + + IOHIDManagerRegisterDeviceMatchingCallback(hidManager, RGFW__osxDeviceAddedCallback, NULL); + IOHIDManagerRegisterDeviceRemovalCallback(hidManager, RGFW__osxDeviceRemovedCallback, NULL); + + IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + + IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone); + + /* Execute the run loop once in order to register any initially-attached joysticks */ + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); +} #endif void RGFW_moveToMacOSResourceDir(void) { @@ -11211,94 +8816,83 @@ void RGFW_moveToMacOSResourceDir(void) { } -static void RGFW__osxWindowDeminiaturize(id self, SEL sel) { +void RGFW__osxWindowDeminiaturize(id self, SEL sel) { RGFW_UNUSED(sel); RGFW_window* win = NULL; object_getInstanceVariable(self, "RGFW_window", (void**)&win); if (win == NULL) return; - win->internal.flags |= RGFW_windowMinimize; - if (!(win->internal.enabledEvents & RGFW_windowMinimizedFlag)) return; - RGFW_eventQueuePushEx(e.type = RGFW_windowRestored; e.common.win = win); - RGFW_windowRestoredCallback(win, win->x, win->y, win->w, win->h); + win->_flags |= RGFW_windowMinimize; + RGFW_eventQueuePushEx(e.type = RGFW_windowRestored; e._win = win); + RGFW_windowRestoredCallback(win, win->r); } -static void RGFW__osxWindowMiniaturize(id self, SEL sel) { +void RGFW__osxWindowMiniaturize(id self, SEL sel) { RGFW_UNUSED(sel); RGFW_window* win = NULL; object_getInstanceVariable(self, "RGFW_window", (void**)&win); if (win == NULL) return; - win->internal.flags &= ~(u32)RGFW_windowMinimize; - if (!(win->internal.enabledEvents & RGFW_windowMinimizedFlag)) return; - RGFW_eventQueuePushEx(e.type = RGFW_windowMinimized; e.common.win = win); - RGFW_windowMinimizedCallback(win); + win->_flags &= ~(u32)RGFW_windowMinimize; + RGFW_eventQueuePushEx(e.type = RGFW_windowMinimized; e._win = win); + RGFW_windowMinimizedCallback(win, win->r); } -static void RGFW__osxWindowBecameKey(id self, SEL sel) { +void RGFW__osxWindowBecameKey(id self, SEL sel) { RGFW_UNUSED(sel); RGFW_window* win = NULL; object_getInstanceVariable(self, "RGFW_window", (void**)&win); if (win == NULL) return; + win->_flags |= RGFW_windowFocus; + RGFW_eventQueuePushEx(e.type = RGFW_focusIn; e._win = win); - win->internal.inFocus = RGFW_TRUE; - if ((win->internal.holdMouse)) RGFW_window_holdMouse(win); - if (!(win->internal.enabledEvents & RGFW_focusInFlag)) return; - - RGFW_eventQueuePushEx(e.type = RGFW_focusIn; e.common.win = win); RGFW_focusCallback(win, RGFW_TRUE); + + if ((win->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(win, RGFW_AREA(win->r.w, win->r.h)); } -static void RGFW__osxWindowResignKey(id self, SEL sel) { +void RGFW__osxWindowResignKey(id self, SEL sel) { RGFW_UNUSED(sel); RGFW_window* win = NULL; object_getInstanceVariable(self, "RGFW_window", (void**)&win); if (win == NULL) return; RGFW_window_focusLost(win); - if (!(win->internal.enabledEvents & RGFW_focusOutFlag)) return; - - RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e.common.win = win); + RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e._win = win); RGFW_focusCallback(win, RGFW_FALSE); } -static void RGFW__osxDidWindowResize(id self, SEL _cmd, id notification) { - RGFW_UNUSED(_cmd); RGFW_UNUSED(notification); +NSSize RGFW__osxWindowResize(id self, SEL sel, NSSize frameSize) { + RGFW_UNUSED(sel); + RGFW_window* win = NULL; object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL) return; + if (win == NULL) return frameSize; - NSRect frame; - if (win->src.view) frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.view, sel_registerName("frame")); - else return; - - if (frame.size.width == 0 || frame.size.height == 0) return; - win->w = (i32)frame.size.width; - win->h = (i32)frame.size.height; + win->r.w = (i32)frameSize.width; + win->r.h = (i32)frameSize.height; RGFW_monitor mon = RGFW_window_getMonitor(win); - if ((i32)mon.mode.w == win->w && (i32)mon.mode.h - 102 <= win->h) { - win->internal.flags |= RGFW_windowMaximize; - if (!(win->internal.enabledEvents & RGFW_windowMaximizedFlag)) return; - RGFW_eventQueuePushEx(e.type = RGFW_windowMaximized; e.common.win = win); - RGFW_windowMaximizedCallback(win, 0, 0, win->w, win->h); - } else if (win->internal.flags & RGFW_windowMaximize) { - win->internal.flags &= ~(u32)RGFW_windowMaximize; - if (!(win->internal.enabledEvents & RGFW_windowRestoredFlag)) return; - RGFW_eventQueuePushEx(e.type = RGFW_windowRestored; e.common.win = win); - RGFW_windowRestoredCallback(win, win->x, win->y, win->w, win->h); + if ((i32)mon.mode.area.w == win->r.w && (i32)mon.mode.area.h - 102 <= win->r.h) { + win->_flags |= RGFW_windowMaximize; + RGFW_eventQueuePushEx(e.type = RGFW_windowMaximized; e._win = win); + RGFW_windowMaximizedCallback(win, win->r); + } else if (win->_flags & RGFW_windowMaximize) { + win->_flags &= ~(u32)RGFW_windowMaximize; + RGFW_eventQueuePushEx(e.type = RGFW_windowRestored; e._win = win); + RGFW_windowRestoredCallback(win, win->r); } - if (!(win->internal.enabledEvents & RGFW_windowResizedFlag)) return; - RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e.common.win = win); - RGFW_windowResizedCallback(win, win->w, win->h); + RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e._win = win); + RGFW_windowResizedCallback(win, win->r); + return frameSize; } -static void RGFW__osxWindowMove(id self, SEL sel) { +void RGFW__osxWindowMove(id self, SEL sel) { RGFW_UNUSED(sel); RGFW_window* win = NULL; @@ -11306,601 +8900,289 @@ static void RGFW__osxWindowMove(id self, SEL sel) { if (win == NULL) return; NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.window, sel_registerName("frame")); - win->x = (i32) frame.origin.x; - win->y = (i32) frame.origin.y; + win->r.x = (i32) frame.origin.x; + win->r.y = (i32) frame.origin.y; - if (!(win->internal.enabledEvents & RGFW_windowMovedFlag)) return; - RGFW_eventQueuePushEx(e.type = RGFW_windowMoved; e.common.win = win); - RGFW_windowMovedCallback(win, win->x, win->y); + RGFW_eventQueuePushEx(e.type = RGFW_windowMoved; e._win = win); + RGFW_windowMovedCallback(win, win->r); } -static void RGFW__osxViewDidChangeBackingProperties(id self, SEL _cmd) { +void RGFW__osxViewDidChangeBackingProperties(id self, SEL _cmd) { RGFW_UNUSED(_cmd); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL || !(win->internal.enabledEvents & RGFW_scaleUpdatedFlag)) return; + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; RGFW_monitor mon = RGFW_window_getMonitor(win); RGFW_scaleUpdatedCallback(win, mon.scaleX, mon.scaleY); - RGFW_eventQueuePushEx(e.type = RGFW_scaleUpdated; e.scale.x = mon.scaleX; e.scale.y = mon.scaleY ; e.common.win = win); + RGFW_eventQueuePushEx(e.type = RGFW_scaleUpdated; e.scaleX = mon.scaleX; e.scaleY = mon.scaleY ; e._win = win); } -static BOOL RGFW__osxWantsUpdateLayer(id self, SEL _cmd) { RGFW_UNUSED(self); RGFW_UNUSED(_cmd); return YES; } - -static void RGFW__osxUpdateLayer(id self, SEL _cmd) { - RGFW_UNUSED(self); RGFW_UNUSED(_cmd); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL || !(win->internal.enabledEvents & RGFW_windowRefreshFlag)) return; - RGFW_windowRefreshCallback(win); -} - -static void RGFW__osxDrawRect(id self, SEL _cmd, CGRect rect) { +void RGFW__osxDrawRect(id self, SEL _cmd, CGRect rect) { RGFW_UNUSED(rect); RGFW_UNUSED(_cmd); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL || !(win->internal.enabledEvents & RGFW_scaleUpdatedFlag)) return; + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void**)&win); + if (win == NULL) return; - RGFW_eventQueuePushEx(e.type = RGFW_windowRefresh; e.common.win = win); - RGFW_windowRefreshCallback(win); + RGFW_eventQueuePushEx(e.type = RGFW_windowRefresh; e._win = win); + RGFW_windowRefreshCallback(win); } -static void RGFW__osxMouseEntered(id self, SEL _cmd, id event) { - RGFW_UNUSED(_cmd); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL || !(win->internal.enabledEvents & RGFW_mouseEnterFlag)) return; - - win->internal.mouseInside = RGFW_TRUE; - _RGFW->windowState.win = win; - _RGFW->windowState.mouseEnter = RGFW_TRUE; - - RGFW_event e; - e.type = RGFW_mouseEnter; - NSPoint p = ((NSPoint(*)(id, SEL))objc_msgSend)(event, sel_registerName("locationInWindow")); - e.mouse.x = (i32)p.x; - e.mouse.y = (i32)(win->h - p.y); - e.common.win = win; - - RGFW_eventQueuePush(&e); - RGFW_mouseNotifyCallback(win, e.mouse.x, e.mouse.y, 1); +void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area) { + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + win->buffer = buffer; + win->bufferSize = area; + win->_flags |= RGFW_BUFFER_ALLOC; + #ifdef RGFW_OSMESA + win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); + OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); + OSMesaPixelStore(OSMESA_Y_UP, 0); + #endif + #else + RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); /*!< if buffer rendering is not being used */ + #endif } -static void RGFW__osxMouseExited(id self, SEL _cmd, id event) { - RGFW_UNUSED(_cmd); RGFW_UNUSED(event); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL || !(win->internal.enabledEvents & RGFW_mouseLeaveFlag)) return; - - - win->internal.mouseInside = RGFW_FALSE; - _RGFW->windowState.winLeave = win; - _RGFW->windowState.mouseLeave = RGFW_TRUE; - - RGFW_event e; - e.type = RGFW_mouseLeave; - e.mouse.x = 0; - e.mouse.y = 0; - e.common.win = win; - - RGFW_eventQueuePush(&e); - RGFW_mouseNotifyCallback(win, e.mouse.x, e.mouse.y, 0); -} - -static void RGFW__osxKeyDown(id self, SEL _cmd, id event) { - RGFW_UNUSED(_cmd); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL || !(win->internal.enabledEvents & RGFW_keyPressedFlag)) return; - - RGFW_event e; - u32 key = (u16)((u32(*)(id, SEL))objc_msgSend)(event, sel_registerName("keyCode")); - u32 mappedKey = (u32)*(((char*)(const char*)NSString_to_char(((id(*)(id, SEL))objc_msgSend)(event, sel_registerName("charactersIgnoringModifiers"))))); - if ((u8)mappedKey == 239) mappedKey = 0; - - e.key.sym = (u8)mappedKey; - e.key.value = (u8)RGFW_apiKeyToRGFW(key); - _RGFW->keyboard[e.key.value].prev = _RGFW->keyboard[e.key.value].current; - e.type = RGFW_keyPressed; - e.key.repeat = RGFW_window_isKeyPressed(win, e.key.value); - _RGFW->keyboard[e.key.value].current = 1; - e.common.win = win; - - RGFW_eventQueuePush(&e); - RGFW_keyCallback(win, e.key.value, e.key.sym, win->internal.mod, e.key.repeat, 1); -} - -static void RGFW__osxKeyUp(id self, SEL _cmd, id event) { - RGFW_UNUSED(_cmd); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL || !(win->internal.enabledEvents & RGFW_keyReleasedFlag)) return; - - RGFW_event e; - u32 key = (u16)((u32(*)(id, SEL))objc_msgSend)(event, sel_registerName("keyCode")); - u32 mappedKey = (u32)*(((char*)(const char*)NSString_to_char(((id(*)(id, SEL))objc_msgSend)(event, sel_registerName("charactersIgnoringModifiers"))))); - if ((u8)mappedKey == 239) mappedKey = 0; - - e.key.sym = (u8)mappedKey; - e.key.value = (u8)RGFW_apiKeyToRGFW(key); - _RGFW->keyboard[e.key.value].prev = _RGFW->keyboard[e.key.value].current; - e.type = RGFW_keyReleased; - e.key.repeat = RGFW_window_isKeyDown(win, (u8)e.key.value); - _RGFW->keyboard[e.key.value].current = 0; - e.common.win = win; - - RGFW_eventQueuePush(&e); - RGFW_keyCallback(win, e.key.value, e.key.sym, win->internal.mod, e.key.repeat, 0); -} - -static void RGFW__osxFlagsChanged(id self, SEL _cmd, id event) { - RGFW_UNUSED(_cmd); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL) return; - - RGFW_event e; - u32 flags = (u32)((u32(*)(id, SEL))objc_msgSend)(event, sel_registerName("modifierFlags")); - RGFW_updateKeyModsEx(win, - ((u32)(flags & NSEventModifierFlagCapsLock) % 255), - ((flags & NSEventModifierFlagNumericPad) % 255), - ((flags & NSEventModifierFlagControl) % 255), - ((flags & NSEventModifierFlagOption) % 255), - ((flags & NSEventModifierFlagShift) % 255), - ((flags & NSEventModifierFlagCommand) % 255), 0); - u8 i; - for (i = 0; i < 9; i++) - _RGFW->keyboard[i + RGFW_capsLock].prev = _RGFW->keyboard[i + RGFW_capsLock].current; - - for (i = 0; i < 5; i++) { - u32 shift = (1 << (i + 16)); - u32 key = i + RGFW_capsLock; - if ((flags & shift) && !RGFW_window_isKeyDown(win, (u8)key)) { - _RGFW->keyboard[key].current = 1; - if (key != RGFW_capsLock) - _RGFW->keyboard[key + 4].current = 1; - e.type = RGFW_keyPressed; - e.key.value = (u8)key; - break; - } - if (!(flags & shift) && RGFW_window_isKeyDown(win, (u8)key)) { - _RGFW->keyboard[key].current = 0; - if (key != RGFW_capsLock) - _RGFW->keyboard[key + 4].current = 0; - e.type = RGFW_keyReleased; - e.key.value = (u8)key; - break; - } - } - e.key.repeat = RGFW_window_isKeyDown(win, (u8)e.key.value); - e.common.win = win; - - if (!(win->internal.enabledEvents & (RGFW_BIT(e.type)))) return; - RGFW_eventQueuePush(&e); - RGFW_keyCallback(win, e.key.value, e.key.sym, win->internal.mod, e.key.repeat, e.type == RGFW_keyPressed); -} - -static void RGFW__osxMouseMoved(id self, SEL _cmd, id event) { - RGFW_UNUSED(_cmd); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL || !(win->internal.enabledEvents & RGFW_windowMovedFlag)) return; - - RGFW_event e; - e.type = RGFW_mousePosChanged; - NSPoint p = ((NSPoint(*)(id, SEL))objc_msgSend)(event, sel_registerName("locationInWindow")); - e.mouse.x = (i32)p.x; - e.mouse.y = (i32)(win->h - p.y); - p.x = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("deltaX")); - p.y = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("deltaY")); - e.mouse.vecX = (float)p.x; - e.mouse.vecY = (float)p.y; - _RGFW->vectorX = e.mouse.vecX; - _RGFW->vectorY = e.mouse.vecY; - win->internal.lastMouseX = e.mouse.x; - win->internal.lastMouseY = e.mouse.y; - e.common.win = win; - - RGFW_eventQueuePush(&e); - RGFW_mousePosCallback(win, e.mouse.x, e.mouse.y, e.mouse.vecX, e.mouse.vecY); -} - -static void RGFW__osxMouseDown(id self, SEL _cmd, id event) { - RGFW_UNUSED(_cmd); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL || !(win->internal.enabledEvents & RGFW_mouseButtonPressedFlag)) return; - - RGFW_event e; - u32 buttonNumber = (u32)((u32(*)(id, SEL))objc_msgSend)(event, sel_registerName("buttonNumber")); - switch (buttonNumber) { - case 0: e.button.value = RGFW_mouseLeft; break; - case 1: e.button.value = RGFW_mouseRight; break; - case 2: e.button.value = RGFW_mouseMiddle; break; - default: e.button.value = (u8)buttonNumber; - } - e.type = RGFW_mouseButtonPressed; - _RGFW->mouseButtons[e.button.value].prev = _RGFW->mouseButtons[e.button.value].current; - _RGFW->mouseButtons[e.button.value].current = 1; - e.common.win = win; - - RGFW_eventQueuePush(&e); - RGFW_mouseButtonCallback(win, e.button.value, 1); -} - -static void RGFW__osxMouseUp(id self, SEL _cmd, id event) { - RGFW_UNUSED(_cmd); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL|| !(win->internal.enabledEvents & RGFW_mouseButtonReleasedFlag)) return; - - RGFW_event e; - u32 buttonNumber = (u32)((u32(*)(id, SEL))objc_msgSend)(event, sel_registerName("buttonNumber")); - switch (buttonNumber) { - case 0: e.button.value = RGFW_mouseLeft; break; - case 1: e.button.value = RGFW_mouseRight; break; - case 2: e.button.value = RGFW_mouseMiddle; break; - default: e.button.value = (u8)buttonNumber; - } - e.type = RGFW_mouseButtonReleased; - _RGFW->mouseButtons[e.button.value].prev = _RGFW->mouseButtons[e.button.value].current; - _RGFW->mouseButtons[e.button.value].current = 0; - e.common.win = win; - - RGFW_eventQueuePush(&e); - RGFW_mouseButtonCallback(win, e.button.value, 0); -} - -static void RGFW__osxScrollWheel(id self, SEL _cmd, id event) { - RGFW_UNUSED(_cmd); - RGFW_window* win = NULL; - object_getInstanceVariable(self, "RGFW_window", (void**)&win); - if (win == NULL|| !(win->internal.enabledEvents & RGFW_mouseScroll)) return; - - RGFW_event e; - float deltaX = (float)((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("deltaX")); - float deltaY = (float)((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(event, sel_registerName("deltaY")); - - e.type = RGFW_mouseScroll; - e.scroll.x = deltaX; - e.scroll.y = deltaY; - e.common.win = win; - _RGFW->scrollX = e.scroll.x; - _RGFW->scrollY = e.scroll.y; - - RGFW_eventQueuePush(&e); - RGFW_mouseScrollCallback(win, deltaX, deltaY); -} - -RGFW_bool RGFW_createSurfacePtr(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { - surface->data = data; - surface->w = w; - surface->h = h; - surface->format = format; - surface->native.format = RGFW_formatRGBA8; - return RGFW_TRUE; -} - -void RGFW_surface_freePtr(RGFW_surface* surface) { RGFW_UNUSED(surface); } - -void RGFW_window_blitSurface(RGFW_window* win, RGFW_surface* surface) { - RGFW_copyImageData(surface->data, surface->w, RGFW_MIN(win->h, surface->h), surface->native.format, surface->data, surface->format); - - size_t depth = (surface->format >= RGFW_formatRGBA8) ? 4 : 3; - id image = ((id (*)(Class, SEL))objc_msgSend)(objc_getClass("NSImage"), sel_getUid("alloc")); - NSSize size = (NSSize){(double)surface->w, (double)surface->h}; - image = ((id (*)(id, SEL, NSSize))objc_msgSend)((id)image, sel_getUid("initWithSize:"), size); - - int minX = RGFW_MIN(win->w, surface->w); - int minY = RGFW_MIN(win->h, surface->h); - - id rep = NSBitmapImageRep_initWithBitmapData(&surface->data, minX, minY, 8, (i32)depth, (depth == 4), false, "NSDeviceRGBColorSpace", 1 << 1, (u32)surface->w * (u32)depth, 8 * (u32)depth); - RGFW_copyImageData(NSBitmapImageRep_bitmapData(rep), minX, minY , RGFW_formatRGBA8, surface->data, surface->format); - ((void (*)(id, SEL, id))objc_msgSend)((id)image, sel_getUid("addRepresentation:"), rep); - - id contentView = ((id (*)(id, SEL))objc_msgSend)((id)win->src.window, sel_getUid("contentView")); - ((void (*)(id, SEL, BOOL))objc_msgSend)(contentView, sel_getUid("setWantsLayer:"), YES); - id layer = ((id (*)(id, SEL))objc_msgSend)(contentView, sel_getUid("layer")); - - ((void (*)(id, SEL, id))objc_msgSend)(layer, sel_getUid("setContents:"), (id)image); - ((void (*)(id, SEL, BOOL))objc_msgSend)(contentView, sel_getUid("setNeedsDisplay:"), YES); - - NSRelease(rep); - NSRelease(image); -} - -void* RGFW_window_getView_OSX(RGFW_window* win) { return win->src.view; } - -void RGFW_window_setLayer_OSX(RGFW_window* win, void* layer) { +void RGFW_window_cocoaSetLayer(RGFW_window* win, void* layer) { objc_msgSend_void_id((id)win->src.view, sel_registerName("setLayer"), (id)layer); } -void* RGFW_getLayer_OSX(void) { +void* RGFW_cocoaGetLayer(void) { return objc_msgSend_class((id)objc_getClass("CAMetalLayer"), (SEL)sel_registerName("layer")); } -void* RGFW_window_getWindow_OSX(RGFW_window* win) { return win->src.window; } -void RGFW_initKeycodesPlatform(void) { - _RGFW->keycodes[0x1D] = RGFW_0; - _RGFW->keycodes[0x12] = RGFW_1; - _RGFW->keycodes[0x13] = RGFW_2; - _RGFW->keycodes[0x14] = RGFW_3; - _RGFW->keycodes[0x15] = RGFW_4; - _RGFW->keycodes[0x17] = RGFW_5; - _RGFW->keycodes[0x16] = RGFW_6; - _RGFW->keycodes[0x1A] = RGFW_7; - _RGFW->keycodes[0x1C] = RGFW_8; - _RGFW->keycodes[0x19] = RGFW_9; - _RGFW->keycodes[0x00] = RGFW_a; - _RGFW->keycodes[0x0B] = RGFW_b; - _RGFW->keycodes[0x08] = RGFW_c; - _RGFW->keycodes[0x02] = RGFW_d; - _RGFW->keycodes[0x0E] = RGFW_e; - _RGFW->keycodes[0x03] = RGFW_f; - _RGFW->keycodes[0x05] = RGFW_g; - _RGFW->keycodes[0x04] = RGFW_h; - _RGFW->keycodes[0x22] = RGFW_i; - _RGFW->keycodes[0x26] = RGFW_j; - _RGFW->keycodes[0x28] = RGFW_k; - _RGFW->keycodes[0x25] = RGFW_l; - _RGFW->keycodes[0x2E] = RGFW_m; - _RGFW->keycodes[0x2D] = RGFW_n; - _RGFW->keycodes[0x1F] = RGFW_o; - _RGFW->keycodes[0x23] = RGFW_p; - _RGFW->keycodes[0x0C] = RGFW_q; - _RGFW->keycodes[0x0F] = RGFW_r; - _RGFW->keycodes[0x01] = RGFW_s; - _RGFW->keycodes[0x11] = RGFW_t; - _RGFW->keycodes[0x20] = RGFW_u; - _RGFW->keycodes[0x09] = RGFW_v; - _RGFW->keycodes[0x0D] = RGFW_w; - _RGFW->keycodes[0x07] = RGFW_x; - _RGFW->keycodes[0x10] = RGFW_y; - _RGFW->keycodes[0x06] = RGFW_z; - _RGFW->keycodes[0x27] = RGFW_apostrophe; - _RGFW->keycodes[0x2A] = RGFW_backSlash; - _RGFW->keycodes[0x2B] = RGFW_comma; - _RGFW->keycodes[0x18] = RGFW_equals; - _RGFW->keycodes[0x32] = RGFW_backtick; - _RGFW->keycodes[0x21] = RGFW_bracket; - _RGFW->keycodes[0x1B] = RGFW_minus; - _RGFW->keycodes[0x2F] = RGFW_period; - _RGFW->keycodes[0x1E] = RGFW_closeBracket; - _RGFW->keycodes[0x29] = RGFW_semicolon; - _RGFW->keycodes[0x2C] = RGFW_slash; - _RGFW->keycodes[0x0A] = RGFW_world1; - _RGFW->keycodes[0x33] = RGFW_backSpace; - _RGFW->keycodes[0x39] = RGFW_capsLock; - _RGFW->keycodes[0x75] = RGFW_delete; - _RGFW->keycodes[0x7D] = RGFW_down; - _RGFW->keycodes[0x77] = RGFW_end; - _RGFW->keycodes[0x24] = RGFW_enter; - _RGFW->keycodes[0x35] = RGFW_escape; - _RGFW->keycodes[0x7A] = RGFW_F1; - _RGFW->keycodes[0x78] = RGFW_F2; - _RGFW->keycodes[0x63] = RGFW_F3; - _RGFW->keycodes[0x76] = RGFW_F4; - _RGFW->keycodes[0x60] = RGFW_F5; - _RGFW->keycodes[0x61] = RGFW_F6; - _RGFW->keycodes[0x62] = RGFW_F7; - _RGFW->keycodes[0x64] = RGFW_F8; - _RGFW->keycodes[0x65] = RGFW_F9; - _RGFW->keycodes[0x6D] = RGFW_F10; - _RGFW->keycodes[0x67] = RGFW_F11; - _RGFW->keycodes[0x6F] = RGFW_F12; - _RGFW->keycodes[0x69] = RGFW_printScreen; - _RGFW->keycodes[0x6B] = RGFW_F14; - _RGFW->keycodes[0x71] = RGFW_F15; - _RGFW->keycodes[0x6A] = RGFW_F16; - _RGFW->keycodes[0x40] = RGFW_F17; - _RGFW->keycodes[0x4F] = RGFW_F18; - _RGFW->keycodes[0x50] = RGFW_F19; - _RGFW->keycodes[0x5A] = RGFW_F20; - _RGFW->keycodes[0x73] = RGFW_home; - _RGFW->keycodes[0x72] = RGFW_insert; - _RGFW->keycodes[0x7B] = RGFW_left; - _RGFW->keycodes[0x3A] = RGFW_altL; - _RGFW->keycodes[0x3B] = RGFW_controlL; - _RGFW->keycodes[0x38] = RGFW_shiftL; - _RGFW->keycodes[0x37] = RGFW_superL; - _RGFW->keycodes[0x6E] = RGFW_menu; - _RGFW->keycodes[0x47] = RGFW_numLock; - _RGFW->keycodes[0x79] = RGFW_pageDown; - _RGFW->keycodes[0x74] = RGFW_pageUp; - _RGFW->keycodes[0x7C] = RGFW_right; - _RGFW->keycodes[0x3D] = RGFW_altR; - _RGFW->keycodes[0x3E] = RGFW_controlR; - _RGFW->keycodes[0x3C] = RGFW_shiftR; - _RGFW->keycodes[0x36] = RGFW_superR; - _RGFW->keycodes[0x31] = RGFW_space; - _RGFW->keycodes[0x30] = RGFW_tab; - _RGFW->keycodes[0x7E] = RGFW_up; - _RGFW->keycodes[0x52] = RGFW_kp0; - _RGFW->keycodes[0x53] = RGFW_kp1; - _RGFW->keycodes[0x54] = RGFW_kp2; - _RGFW->keycodes[0x55] = RGFW_kp3; - _RGFW->keycodes[0x56] = RGFW_kp4; - _RGFW->keycodes[0x57] = RGFW_kp5; - _RGFW->keycodes[0x58] = RGFW_kp6; - _RGFW->keycodes[0x59] = RGFW_kp7; - _RGFW->keycodes[0x5B] = RGFW_kp8; - _RGFW->keycodes[0x5C] = RGFW_kp9; - _RGFW->keycodes[0x45] = RGFW_kpSlash; - _RGFW->keycodes[0x41] = RGFW_kpPeriod; - _RGFW->keycodes[0x4B] = RGFW_kpSlash; - _RGFW->keycodes[0x4C] = RGFW_kpReturn; - _RGFW->keycodes[0x51] = RGFW_kpEqual; - _RGFW->keycodes[0x43] = RGFW_kpMultiply; - _RGFW->keycodes[0x4E] = RGFW_kpMinus; +NSPasteboardType const NSPasteboardTypeURL = "public.url"; +NSPasteboardType const NSPasteboardTypeFileURL = "public.file-url"; + +id RGFW__osx_generateViewClass(const char* subclass, RGFW_window* win) { + Class customViewClass; + customViewClass = objc_allocateClassPair(objc_getClass(subclass), "RGFWCustomView", 0); + + class_addIvar( customViewClass, "RGFW_window", sizeof(RGFW_window*), (u8)rint(log2(sizeof(RGFW_window*))), "L"); + class_addMethod(customViewClass, sel_registerName("drawRect:"), (IMP)RGFW__osxDrawRect, "v@:{CGRect=ffff}"); + class_addMethod(customViewClass, sel_registerName("viewDidChangeBackingProperties"), (IMP)RGFW__osxViewDidChangeBackingProperties, ""); + + id customView = objc_msgSend_id(NSAlloc(customViewClass), sel_registerName("init")); + object_setInstanceVariable(customView, "RGFW_window", win); + + return customView; } -i32 RGFW_initPlatform(void) { - class_addMethod(objc_getClass("NSObject"), sel_registerName("windowShouldClose:"), (IMP)(void*)RGFW_OnClose, 0); +#ifndef RGFW_EGL +void RGFW_window_initOpenGL(RGFW_window* win) { +#ifdef RGFW_OPENGL + void* attrs = RGFW_initFormatAttribs(); + void* format = NSOpenGLPixelFormat_initWithAttributes((uint32_t*)attrs); + + if (format == NULL) { + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "Failed to load pixel format for OpenGL"); + win->_flags |= RGFW_windowOpenglSoftware; + void* subAttrs = RGFW_initFormatAttribs(); + format = NSOpenGLPixelFormat_initWithAttributes((uint32_t*)subAttrs); + + if (format == NULL) + RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenglContext, RGFW_DEBUG_CTX(win, 0), "and loading software rendering OpenGL failed"); + else + RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, RGFW_DEBUG_CTX(win, 0), "Switching to software rendering"); + } + + /* the pixel format can be passed directly to opengl context creation to create a context + this is because the format also includes information about the opengl version (which may be a bad thing) */ + + win->src.view = (id) ((id(*)(id, SEL, NSRect, uint32_t*))objc_msgSend) (RGFW__osx_generateViewClass("NSOpenGLView", win), + sel_registerName("initWithFrame:pixelFormat:"), (NSRect){{0, 0}, {win->r.w, win->r.h}}, (uint32_t*)format); + + objc_msgSend_void(win->src.view, sel_registerName("prepareOpenGL")); + win->src.ctx = objc_msgSend_id(win->src.view, sel_registerName("openGLContext")); + + if (win->_flags & RGFW_windowTransparent) { + i32 opacity = 0; + #define NSOpenGLCPSurfaceOpacity 236 + NSOpenGLContext_setValues((id)win->src.ctx, &opacity, NSOpenGLCPSurfaceOpacity); + } + + objc_msgSend_void(win->src.ctx, sel_registerName("makeCurrentContext")); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context initalized"); +#else + RGFW_UNUSED(win); +#endif +} + +void RGFW_window_freeOpenGL(RGFW_window* win) { +#ifdef RGFW_OPENGL + if (win->src.ctx == NULL) return; + objc_msgSend_void(win->src.ctx, sel_registerName("release")); + win->src.ctx = NULL; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context freed"); +#else + RGFW_UNUSED(win); +#endif +} +#endif + + +i32 RGFW_init(void) { +#if defined(RGFW_C89) || defined(__cplusplus) + if (_RGFW_init) return 0; + _RGFW_init = RGFW_TRUE; + _RGFW.root = NULL; _RGFW.current = NULL; _RGFW.windowCount = -1; _RGFW.eventLen = 0; _RGFW.eventIndex = 0; +#endif + + /* NOTE(EimaMei): Why does Apple hate good code? Like wtf, who thought of methods being a great idea??? + Imagine a universe, where MacOS had a proper system API (we would probably have like 20% better performance). + */ + si_func_to_SEL_with_name("NSObject", "windowShouldClose", (void*)RGFW_OnClose); /* NOTE(EimaMei): Fixes the 'Boop' sfx from constantly playing each time you click a key. Only a problem when running in the terminal. */ - class_addMethod(objc_getClass("NSWindowClass"), sel_registerName("acceptsFirstResponder:"), (IMP)(void*)RGFW__osxAcceptsFirstResponder, 0); - class_addMethod(objc_getClass("NSWindowClass"), sel_registerName("performKeyEquivalent:"), (IMP)(void*)RGFW__osxPerformKeyEquivalent, 0); + si_func_to_SEL("NSWindow", acceptsFirstResponder); + si_func_to_SEL("NSWindow", performKeyEquivalent); - _RGFW->NSApp = objc_msgSend_id((id)objc_getClass("NSApplication"), sel_registerName("sharedApplication")); + if (NSApp == NULL) { + NSApp = objc_msgSend_id((id)objc_getClass("NSApplication"), sel_registerName("sharedApplication")); - ((void (*)(id, SEL, NSUInteger))objc_msgSend) - ((id)_RGFW->NSApp, sel_registerName("setActivationPolicy:"), NSApplicationActivationPolicyRegular); + ((void (*)(id, SEL, NSUInteger))objc_msgSend) + (NSApp, sel_registerName("setActivationPolicy:"), NSApplicationActivationPolicyRegular); - _RGFW->customViewClasses[0] = objc_allocateClassPair(objc_getClass("NSView"), "RGFWCustomView", 0); - _RGFW->customViewClasses[1] = objc_allocateClassPair(objc_getClass("NSOpenGLView"), "RGFWOpenGLCustomView", 0); - for (size_t i = 0; i < 2; i++) { - class_addIvar((Class)_RGFW->customViewClasses[i], "RGFW_window", sizeof(RGFW_window*), sizeof(RGFW_window*), "L"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("drawRect:"), (IMP)RGFW__osxDrawRect, "v@:{CGRect=ffff}"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("viewDidChangeBackingProperties"), (IMP)RGFW__osxViewDidChangeBackingProperties, "v@:"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseDown:"), (IMP)RGFW__osxMouseDown, "v@:@"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("rightMouseDown:"), (IMP)RGFW__osxMouseDown, "v@:@"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("otherMouseDown:"), (IMP)RGFW__osxMouseDown, "v@:@"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseUp:"), (IMP)RGFW__osxMouseUp, "v@:@"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("rightMouseUp:"), (IMP)RGFW__osxMouseUp, "v@:@"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("otherMouseUp:"), (IMP)RGFW__osxMouseUp, "v@:@"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("scrollWheel:"), (IMP)RGFW__osxScrollWheel, "v@:@"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseDragged:"), (IMP)RGFW__osxMouseMoved, "v@:@"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("rightMouseDragged:"), (IMP)RGFW__osxMouseMoved, "v@:@"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("otherMouseDragged:"), (IMP)RGFW__osxMouseMoved, "v@:@"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("keyDown:"), (IMP)RGFW__osxKeyDown, "v@:@"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("keyUp:"), (IMP)RGFW__osxKeyUp, "v@:@"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseMoved:"), (IMP)RGFW__osxMouseMoved, "v@:@"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseEntered:"), (IMP)RGFW__osxMouseEntered, "v@:@"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("mouseExited:"), (IMP)RGFW__osxMouseExited, "v@:@"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("flagsChanged:"), (IMP)RGFW__osxFlagsChanged, "v@:@"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_getUid("acceptsFirstResponder"), (IMP)RGFW__osxAcceptsFirstResponder, "B@:"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("initWithRGFWWindow:"), (IMP)RGFW__osxCustomInitWithRGFWWindow, "@@:{CGRect={CGPoint=dd}{CGSize=dd}}"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("wantsUpdateLayer"), (IMP)RGFW__osxWantsUpdateLayer, "B@:"); - class_addMethod((Class)_RGFW->customViewClasses[i], sel_registerName("updateLayer"), (IMP)RGFW__osxUpdateLayer, "v@:"); - objc_registerClassPair((Class)_RGFW->customViewClasses[i]); + #ifndef RGFW_NO_IOKIT + RGFW_osxInitIOKit(); + #endif } - _RGFW->customWindowDelegateClass = objc_allocateClassPair(objc_getClass("NSObject"), "RGFWWindowDelegate", 0); - class_addIvar((Class)_RGFW->customWindowDelegateClass, "RGFW_window", sizeof(RGFW_window*), sizeof(RGFW_window*), "L"); - class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidResize:"), (IMP)RGFW__osxDidWindowResize, "v@:@"); - class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidMove:"), (IMP) RGFW__osxWindowMove, ""); - class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidMiniaturize:"), (IMP) RGFW__osxWindowMiniaturize, ""); - class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidDeminiaturize:"), (IMP) RGFW__osxWindowDeminiaturize, ""); - class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidBecomeKey:"), (IMP) RGFW__osxWindowBecameKey, ""); - class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("windowDidResignKey:"), (IMP) RGFW__osxWindowResignKey, ""); - class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("draggingEntered:"), (IMP)RGFW__osxDraggingEntered, "l@:@"); - class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("draggingUpdated:"), (IMP)RGFW__osxDraggingUpdated, "l@:@"); - class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("draggingExited:"), (IMP)RGFW__osxDraggingEnded, "v@:@"); - class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("draggingEnded:"), (IMP)RGFW__osxDraggingEnded, "v@:@"); - class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("prepareForDragOperation:"), (IMP)RGFW__osxPrepareForDragOperation, "B@:@"); - class_addMethod((Class)_RGFW->customWindowDelegateClass, sel_registerName("performDragOperation:"), (IMP)RGFW__osxPerformDragOperation, "B@:@"); - objc_registerClassPair((Class)_RGFW->customWindowDelegateClass); - return 0; + + _RGFW.windowCount = 0; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context initialized"); + return 0; } -void RGFW_osx_initView(RGFW_window* win) { - NSRect contentRect; - contentRect.origin.x = 0; - contentRect.origin.y = 0; - contentRect.size.width = (double)win->w; - contentRect.size.height = (double)win->h; - ((void(*)(id, SEL, CGRect))objc_msgSend)((id)win->src.view, sel_registerName("setFrame:"), contentRect); +RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { + static u8 RGFW_loaded = 0; + RGFW_window_basic_init(win, rect, flags); - - if (RGFW_COCOA_FRAME_NAME) - objc_msgSend_ptr(win->src.view, sel_registerName("setFrameAutosaveName:"), RGFW_COCOA_FRAME_NAME); - - object_setInstanceVariable((id)win->src.view, "RGFW_window", win); - objc_msgSend_void_id((id)win->src.window, sel_registerName("setContentView:"), win->src.view); - objc_msgSend_void_bool(win->src.view, sel_registerName("setWantsLayer:"), true); - objc_msgSend_int((id)win->src.view, sel_registerName("setLayerContentsPlacement:"), 4); - - id trackingArea = objc_msgSend_id(objc_getClass("NSTrackingArea"), sel_registerName("alloc")); - trackingArea = ((id (*)(id, SEL, NSRect, NSUInteger, id, id))objc_msgSend)( - trackingArea, - sel_registerName("initWithRect:options:owner:userInfo:"), - contentRect, - NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect, - (id)win->src.view, - nil - ); - - ((void (*)(id, SEL, id))objc_msgSend)((id)win->src.view, sel_registerName("addTrackingArea:"), trackingArea); - ((void (*)(id, SEL))objc_msgSend)(trackingArea, sel_registerName("release")); -} - -RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, RGFW_window* win) { - /* RR Create an autorelease pool */ + /* RR Create an autorelease pool */ id pool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); pool = objc_msgSend_id(pool, sel_registerName("init")); RGFW_window_setMouseDefault(win); NSRect windowRect; - windowRect.origin.x = (double)win->x; - windowRect.origin.y = (double)win->y; - windowRect.size.width = (double)win->w; - windowRect.size.height = (double)win->h; - NSBackingStoreType macArgs = (NSBackingStoreType)(NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSBackingStoreBuffered | NSWindowStyleMaskTitled); + windowRect.origin.x = win->r.x; + windowRect.origin.y = win->r.y; + windowRect.size.width = win->r.w; + windowRect.size.height = win->r.h; + + NSBackingStoreType macArgs = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSBackingStoreBuffered | NSWindowStyleMaskTitled; if (!(flags & RGFW_windowNoResize)) - macArgs = (NSBackingStoreType)(macArgs | (NSBackingStoreType)NSWindowStyleMaskResizable); + macArgs |= NSWindowStyleMaskResizable; if (!(flags & RGFW_windowNoBorder)) - macArgs = (NSBackingStoreType)(macArgs | (NSBackingStoreType)NSWindowStyleMaskTitled); + macArgs |= NSWindowStyleMaskTitled; { void* nsclass = objc_getClass("NSWindow"); SEL func = sel_registerName("initWithContentRect:styleMask:backing:defer:"); win->src.window = ((id(*)(id, SEL, NSRect, NSWindowStyleMask, NSBackingStoreType, bool))objc_msgSend) - (NSAlloc(nsclass), func, windowRect, (NSWindowStyleMask)macArgs, macArgs, false); + (NSAlloc(nsclass), func, windowRect, macArgs, macArgs, false); } id str = NSString_stringWithUTF8String(name); objc_msgSend_void_id((id)win->src.window, sel_registerName("setTitle:"), str); - id delegate = objc_msgSend_id(NSAlloc((Class)_RGFW->customWindowDelegateClass), sel_registerName("init")); - object_setInstanceVariable(delegate, "RGFW_window", win); + if ((flags & RGFW_windowNoInitAPI) == 0) { + RGFW_window_initOpenGL(win); + RGFW_window_initBuffer(win); + } - objc_msgSend_void_id((id)win->src.window, sel_registerName("setDelegate:"), delegate); + #ifdef RGFW_OPENGL + else + #endif + { + NSRect contentRect = (NSRect){{0, 0}, {win->r.w, win->r.h}}; + win->src.view = ((id(*)(id, SEL, NSRect))objc_msgSend) (NSAlloc(objc_getClass("NSView")), sel_registerName("initWithFrame:"), contentRect); + } - if (flags & RGFW_windowAllowDND) { - win->internal.flags |= RGFW_windowAllowDND; - - NSPasteboardType types[] = {NSPasteboardTypeURL, NSPasteboardTypeFileURL, NSPasteboardTypeString}; - NSregisterForDraggedTypes((id)win->src.window, types, 3); - } - - objc_msgSend_void_bool((id)win->src.window, sel_registerName("setAcceptsMouseMovedEvents:"), true); + void* contentView = NSWindow_contentView((id)win->src.window); + objc_msgSend_void_bool(contentView, sel_registerName("setWantsLayer:"), true); + objc_msgSend_int((id)win->src.view, sel_registerName("setLayerContentsPlacement:"), 4); + objc_msgSend_void_id((id)win->src.window, sel_registerName("setContentView:"), win->src.view); if (flags & RGFW_windowTransparent) { objc_msgSend_void_bool(win->src.window, sel_registerName("setOpaque:"), false); objc_msgSend_void_id((id)win->src.window, sel_registerName("setBackgroundColor:"), - NSColor_colorWithSRGB(0, 0, 0, 0)); + NSColor_colorWithSRGB(0, 0, 0, 0)); } - /* Show the window */ - objc_msgSend_void_bool((id)_RGFW->NSApp, sel_registerName("activateIgnoringOtherApps:"), true); + Class delegateClass = objc_allocateClassPair(objc_getClass("NSObject"), "WindowDelegate", 0); - if (_RGFW->root == NULL) { + class_addIvar( + delegateClass, "RGFW_window", + sizeof(RGFW_window*), (u8)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("windowWillMove:"), (IMP) RGFW__osxWindowMove, ""); + class_addMethod(delegateClass, sel_registerName("windowDidMove:"), (IMP) RGFW__osxWindowMove, ""); + class_addMethod(delegateClass, sel_registerName("windowDidMiniaturize:"), (IMP) RGFW__osxWindowMiniaturize, ""); + class_addMethod(delegateClass, sel_registerName("windowDidDeminiaturize:"), (IMP) RGFW__osxWindowDeminiaturize, ""); + class_addMethod(delegateClass, sel_registerName("windowDidBecomeKey:"), (IMP) RGFW__osxWindowBecameKey, ""); + class_addMethod(delegateClass, sel_registerName("windowDidResignKey:"), (IMP) RGFW__osxWindowResignKey, ""); + class_addMethod(delegateClass, sel_registerName("draggingEntered:"), (IMP)draggingEntered, "l@:@"); + class_addMethod(delegateClass, sel_registerName("draggingUpdated:"), (IMP)draggingUpdated, "l@:@"); + class_addMethod(delegateClass, sel_registerName("draggingExited:"), (IMP)RGFW__osxDraggingEnded, "v@:@"); + class_addMethod(delegateClass, sel_registerName("draggingEnded:"), (IMP)RGFW__osxDraggingEnded, "v@:@"); + class_addMethod(delegateClass, sel_registerName("prepareForDragOperation:"), (IMP)prepareForDragOperation, "B@:@"); + class_addMethod(delegateClass, sel_registerName("performDragOperation:"), (IMP)performDragOperation, "B@:@"); + + id delegate = objc_msgSend_id(NSAlloc(delegateClass), sel_registerName("init")); + + if (RGFW_COCOA_FRAME_NAME) + objc_msgSend_ptr(win->src.view, sel_registerName("setFrameAutosaveName:"), RGFW_COCOA_FRAME_NAME); + + object_setInstanceVariable(delegate, "RGFW_window", win); + + objc_msgSend_void_id((id)win->src.window, sel_registerName("setDelegate:"), delegate); + + if (flags & RGFW_windowAllowDND) { + win->_flags |= RGFW_windowAllowDND; + + NSPasteboardType types[] = {NSPasteboardTypeURL, NSPasteboardTypeFileURL, NSPasteboardTypeString}; + NSregisterForDraggedTypes((id)win->src.window, types, 3); + } + + RGFW_window_setFlags(win, flags); + + /* Show the window */ + objc_msgSend_void_bool(NSApp, sel_registerName("activateIgnoringOtherApps:"), true); + ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("makeKeyAndOrderFront:"), NULL); + RGFW_window_show(win); + + if (!RGFW_loaded) { objc_msgSend_void(win->src.window, sel_registerName("makeMainWindow")); + + RGFW_loaded = 1; } objc_msgSend_void(win->src.window, sel_registerName("makeKeyWindow")); - objc_msgSend_void((id)_RGFW->NSApp, sel_registerName("finishLaunching")); + objc_msgSend_void(NSApp, sel_registerName("finishLaunching")); NSRetain(win->src.window); - NSRetain(_RGFW->NSApp); + NSRetain(NSApp); - win->src.view = ((id(*)(id, SEL, RGFW_window*))objc_msgSend) (NSAlloc((Class)_RGFW->customViewClasses[0]), sel_registerName("initWithRGFWWindow:"), win); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); return win; } void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.window, sel_registerName("frame")); NSRect content = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.view, sel_registerName("frame")); - double offset = 0; + float offset = 0; - RGFW_setBit(&win->internal.flags, RGFW_windowNoBorder, !border); - NSBackingStoreType storeType = (NSBackingStoreType)(NSWindowStyleMaskBorderless | NSWindowStyleMaskFullSizeContentView); + RGFW_setBit(&win->_flags, RGFW_windowNoBorder, !border); + NSBackingStoreType storeType = NSWindowStyleMaskBorderless | NSWindowStyleMaskFullSizeContentView; if (border) - storeType = (NSBackingStoreType)(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable); - if (!(win->internal.flags & RGFW_windowNoResize)) { - storeType = (NSBackingStoreType)(storeType | (NSBackingStoreType)NSWindowStyleMaskResizable); + storeType = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable; + if (!(win->_flags & RGFW_windowNoResize)) { + storeType |= NSWindowStyleMaskResizable; } ((void (*)(id, SEL, NSBackingStoreType))objc_msgSend)((id)win->src.window, sel_registerName("setStyleMask:"), storeType); @@ -11910,25 +9192,84 @@ void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { id titleBarView = objc_msgSend_id(miniaturizeButton, sel_registerName("superview")); objc_msgSend_void_bool(titleBarView, sel_registerName("setHidden:"), true); - offset = (double)(frame.size.height - content.size.height); + offset = (float)(frame.size.height - content.size.height); } - RGFW_window_resize(win, win->w, win->h + (i32)offset); - win->h -= (i32)offset; + RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h + offset)); + win->r.h -= (i32)offset; } -RGFW_bool RGFW_getGlobalMouse(i32* x, i32* y) { - RGFW_ASSERT(_RGFW->root != NULL); +RGFW_area RGFW_getScreenSize(void) { + static CGDirectDisplayID display = 0; + + if (display == 0) + display = CGMainDisplayID(); + + return RGFW_AREA(CGDisplayPixelsWide(display), CGDisplayPixelsHigh(display)); +} + +RGFW_point RGFW_getGlobalMousePoint(void) { + RGFW_ASSERT(_RGFW.root != NULL); CGEventRef e = CGEventCreate(NULL); CGPoint point = CGEventGetLocation(e); CFRelease(e); - if (x) *x = (i32)point.x; - if (y) *y = (i32)point.y; - return RGFW_TRUE; + return RGFW_POINT((u32) point.x, (u32) point.y); /*!< the point is loaded during event checks */ } +typedef RGFW_ENUM(u32, NSEventType) { /* various types of events */ + NSEventTypeLeftMouseDown = 1, + NSEventTypeLeftMouseUp = 2, + NSEventTypeRightMouseDown = 3, + NSEventTypeRightMouseUp = 4, + NSEventTypeMouseMoved = 5, + NSEventTypeLeftMouseDragged = 6, + NSEventTypeRightMouseDragged = 7, + NSEventTypeMouseEntered = 8, + NSEventTypeMouseExited = 9, + NSEventTypeKeyDown = 10, + NSEventTypeKeyUp = 11, + NSEventTypeFlagsChanged = 12, + NSEventTypeAppKitDefined = 13, + NSEventTypeSystemDefined = 14, + NSEventTypeApplicationDefined = 15, + NSEventTypePeriodic = 16, + NSEventTypeCursorUpdate = 17, + NSEventTypeScrollWheel = 22, + NSEventTypeTabletPoint = 23, + NSEventTypeTabletProximity = 24, + NSEventTypeOtherMouseDown = 25, + NSEventTypeOtherMouseUp = 26, + NSEventTypeOtherMouseDragged = 27, + /* The following event types are available on some hardware on 10.5.2 and later */ + NSEventTypeGesture = 29, + NSEventTypeMagnify = 30, + NSEventTypeSwipe = 31, + NSEventTypeRotate = 18, + NSEventTypeBeginGesture = 19, + NSEventTypeEndGesture = 20, + + NSEventTypeSmartMagnify = 32, + NSEventTypeQuickLook = 33, + + NSEventTypePressure = 34, + NSEventTypeDirectTouch = 37, + + NSEventTypeChangeMode = 38, +}; + +typedef unsigned long long NSEventMask; + +typedef enum NSEventModifierFlags { + NSEventModifierFlagCapsLock = 1 << 16, + NSEventModifierFlagShift = 1 << 17, + NSEventModifierFlagControl = 1 << 18, + NSEventModifierFlagOption = 1 << 19, + NSEventModifierFlagCommand = 1 << 20, + 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")); @@ -11938,12 +9279,14 @@ void RGFW_stopCheckEvents(void) { NSEventTypeApplicationDefined, (NSPoint){0, 0}, (NSEventModifierFlags)0, NULL, (NSInteger)0, NULL, 0, 0, 0); ((void (*)(id, SEL, id, bool))objc_msgSend) - ((id)_RGFW->NSApp, sel_registerName("postEvent:atStart:"), e, 1); + (NSApp, sel_registerName("postEvent:atStart:"), e, 1); objc_msgSend_bool_void(eventPool, sel_registerName("drain")); } -void RGFW_waitForEvent(i32 waitMS) { +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")); @@ -11952,12 +9295,12 @@ void RGFW_waitForEvent(i32 waitMS) { SEL eventFunc = sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"); id e = (id) ((id(*)(id, SEL, NSEventMask, void*, id, bool))objc_msgSend) - ((id)_RGFW->NSApp, eventFunc, + (NSApp, eventFunc, ULONG_MAX, date, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); if (e) { ((void (*)(id, SEL, id, bool))objc_msgSend) - ((id)_RGFW->NSApp, sel_registerName("postEvent:atStart:"), e, 1); + (NSApp, sel_registerName("postEvent:atStart:"), e, 1); } objc_msgSend_bool_void(eventPool, sel_registerName("drain")); @@ -11967,66 +9310,251 @@ u8 RGFW_rgfwToKeyChar(u32 rgfw_keycode) { return (u8)rgfw_keycode; /* TODO */ } -void RGFW_pollEvents(void) { - /* - * TODO look to see if all these events can be replaced with callbacks - * callbacks seem to give better info on mac's api - */ +RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { + if (win == NULL || ((win->_flags & RGFW_windowFreeOnClose) && (win->_flags & RGFW_EVENT_QUIT))) return NULL; - RGFW_resetPrevState(); + objc_msgSend_void((id)win->src.mouse, sel_registerName("set")); + RGFW_event* ev = RGFW_window_checkEventCore(win); + if (ev) { + ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); + return ev; + } id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); + SEL eventFunc = sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"); - while (1) { - void* date = NULL; - id e = (id) ((id(*)(id, SEL, NSEventMask, void*, id, bool))objc_msgSend) - ((id)_RGFW->NSApp, eventFunc, ULONG_MAX, date, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); + void* date = NULL; - if (e == NULL) { - objc_msgSend_void_id((id)_RGFW->NSApp, sel_registerName("sendEvent:"), e); - ((void(*)(id, SEL))objc_msgSend)((id)_RGFW->NSApp, sel_registerName("updateWindows")); + id e = (id) ((id(*)(id, SEL, NSEventMask, void*, id, bool))objc_msgSend) + (NSApp, eventFunc, ULONG_MAX, date, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); + + if (e == NULL) { + objc_msgSend_bool_void(eventPool, sel_registerName("drain")); + objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), e); + ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); + 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_void_id(NSApp, sel_registerName("sendEvent:"), e); + objc_msgSend_bool_void(eventPool, sel_registerName("drain")); + ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); + return NULL; + } + + 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 = 0; + + u32 type = (u32)objc_msgSend_uint(e, sel_registerName("type")); + switch (type) { + case NSEventTypeMouseEntered: { + win->event.type = RGFW_mouseEnter; + NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(e, sel_registerName("locationInWindow")); + + win->event.point = RGFW_POINT((i32) p.x, (i32) (win->r.h - p.y)); + RGFW_mouseNotifyCallback(win, win->event.point, 1); break; } - RGFW_event event; - RGFW_MEMSET(&event, 0, sizeof(event)); - objc_msgSend_void_id((id)_RGFW->NSApp, sel_registerName("sendEvent:"), e); - ((void(*)(id, SEL))objc_msgSend)((id)_RGFW->NSApp, sel_registerName("updateWindows")); + 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")); + + u32 mappedKey = (u32)*(((char*)(const char*) NSString_to_char(objc_msgSend_id(e, sel_registerName("charactersIgnoringModifiers"))))); + if (((u8)mappedKey) == 239) + mappedKey = 0; + + win->event.keyChar = (u8)mappedKey; + + win->event.key = (u8)RGFW_apiKeyToRGFW(key); + RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; + + win->event.type = RGFW_keyPressed; + win->event.repeat = RGFW_isPressed(win, win->event.key); + RGFW_keyboard[win->event.key].current = 1; + + RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, 1); + break; + } + + case NSEventTypeKeyUp: { + u32 key = (u16) objc_msgSend_uint(e, sel_registerName("keyCode")); + u32 mappedKey = (u32)*(((char*)(const char*) NSString_to_char(objc_msgSend_id(e, sel_registerName("charactersIgnoringModifiers"))))); + if (((u8)mappedKey) == 239) + mappedKey = 0; + + win->event.keyChar = (u8)mappedKey; + + win->event.key = (u8)RGFW_apiKeyToRGFW(key); + + RGFW_keyboard[win->event.key].prev = RGFW_keyboard[win->event.key].current; + + win->event.type = RGFW_keyReleased; + + RGFW_keyboard[win->event.key].current = 0; + RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, 0); + break; + } + + case NSEventTypeFlagsChanged: { + u32 flags = (u32)objc_msgSend_uint(e, sel_registerName("modifierFlags")); + RGFW_updateKeyModsPro(win, ((u32)(flags & NSEventModifierFlagCapsLock) % 255), ((flags & NSEventModifierFlagNumericPad) % 255), + ((flags & NSEventModifierFlagControl) % 255), ((flags & NSEventModifierFlagOption) % 255), + ((flags & NSEventModifierFlagShift) % 255), ((flags & NSEventModifierFlagCommand) % 255), 0); + 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 & shift) && !RGFW_wasPressed(win, (u8)key)) { + RGFW_keyboard[key].current = 1; + + if (key != RGFW_capsLock) + RGFW_keyboard[key+ 4].current = 1; + + win->event.type = RGFW_keyPressed; + win->event.key = (u8)key; + break; + } + + if (!(flags & shift) && RGFW_wasPressed(win, (u8)key)) { + RGFW_keyboard[key].current = 0; + + if (key != RGFW_capsLock) + RGFW_keyboard[key + 4].current = 0; + + win->event.type = RGFW_keyReleased; + win->event.key = (u8)key; + break; + } + } + + RGFW_keyCallback(win, win->event.key, win->event.keyChar, win->event.keyMod, win->event.type == RGFW_keyPressed); + + break; + } + case NSEventTypeLeftMouseDragged: + case NSEventTypeOtherMouseDragged: + case NSEventTypeRightMouseDragged: + case NSEventTypeMouseMoved: { + win->event.type = RGFW_mousePosChanged; + NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(e, sel_registerName("locationInWindow")); + win->event.point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); + + p.x = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaX")); + p.y = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaY")); + win->event.vector = RGFW_POINT((i32)p.x, (i32)p.y); + + win->_lastMousePoint = win->event.point; + RGFW_mousePosCallback(win, win->event.point, win->event.vector); + break; + } + case NSEventTypeLeftMouseDown: case NSEventTypeRightMouseDown: case NSEventTypeOtherMouseDown: { + u32 buttonNumber = (u32)objc_msgSend_uint(e, sel_registerName("buttonNumber")); + switch (buttonNumber) { + case 0: win->event.button = RGFW_mouseLeft; break; + case 1: win->event.button = RGFW_mouseRight; break; + case 2: win->event.button = RGFW_mouseMiddle; break; + default: win->event.button = (u8)buttonNumber; + } + + win->event.type = RGFW_mouseButtonPressed; + 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: case NSEventTypeRightMouseUp: case NSEventTypeOtherMouseUp: { + u32 buttonNumber = (u32)objc_msgSend_uint(e, sel_registerName("buttonNumber")); + switch (buttonNumber) { + case 0: win->event.button = RGFW_mouseLeft; break; + case 1: win->event.button = RGFW_mouseRight; break; + case 2: win->event.button = RGFW_mouseMiddle; break; + default: win->event.button = (u8)buttonNumber; + } + 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 NSEventTypeScrollWheel: { + double deltaY = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaY")); + + if (deltaY > 0) { + win->event.button = RGFW_mouseScrollUp; + } + else if (deltaY < 0) { + win->event.button = RGFW_mouseScrollDown; + } + + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 1; + + win->event.scroll = deltaY; + + win->event.type = RGFW_mouseButtonPressed; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); + break; + } + + default: + objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), e); + ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); + return RGFW_window_checkEvent(win); } + 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, i32 x, i32 y) { +void RGFW_window_move(RGFW_window* win, RGFW_point v) { RGFW_ASSERT(win != NULL); - win->x = x; - win->y = y; - ((void(*)(id,SEL,NSPoint))objc_msgSend)((id)win->src.window, sel_registerName("setFrameOrigin:"), (NSPoint){(double)x, (double)y}); + win->r.x = v.x; + win->r.y = v.y; + ((void(*)(id, SEL, NSRect, bool, bool))objc_msgSend) + ((id)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, i32 w, i32 h) { +void RGFW_window_resize(RGFW_window* win, RGFW_area a) { RGFW_ASSERT(win != NULL); NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.window, sel_registerName("frame")); NSRect content = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)((id)win->src.view, sel_registerName("frame")); float offset = (float)(frame.size.height - content.size.height); - win->w = w; - win->h = h; + win->r.w = (i32)a.w; + win->r.h = (i32)a.h; - - ((void(*)(id, SEL, CGRect))objc_msgSend)((id)win->src.view, sel_registerName("setFrame:"), (NSRect){{0, 0}, {(double)win->w, (double)win->h}}); ((void(*)(id, SEL, NSRect, bool, bool))objc_msgSend) - ((id)win->src.window, sel_registerName("setFrame:display:animate:"), (NSRect){{(double)win->x, (double)win->y}, {(double)win->w, (double)win->h + (double)offset}}, true, true); + ((id)win->src.window, sel_registerName("setFrame:display:animate:"), (NSRect){{win->r.x, win->r.y}, {win->r.w, win->r.h + offset}}, true, true); } void RGFW_window_focus(RGFW_window* win) { RGFW_ASSERT(win); - objc_msgSend_void_bool((id)_RGFW->NSApp, sel_registerName("activateIgnoringOtherApps:"), true); + objc_msgSend_void_bool(NSApp, sel_registerName("activateIgnoringOtherApps:"), true); ((void (*)(id, SEL))objc_msgSend)((id)win->src.window, sel_registerName("makeKeyWindow")); } @@ -12038,38 +9566,25 @@ void RGFW_window_raise(RGFW_window* win) { void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { RGFW_ASSERT(win != NULL); - if (fullscreen && (win->internal.flags & RGFW_windowFullscreen)) return; - if (!fullscreen && !(win->internal.flags & RGFW_windowFullscreen)) return; + if (fullscreen && (win->_flags & RGFW_windowFullscreen)) return; + if (!fullscreen && !(win->_flags & RGFW_windowFullscreen)) return; if (fullscreen) { - if (!(win->internal.flags & RGFW_windowFullscreen)) { - return; - } - - win->internal.oldX = win->x; - win->internal.oldY = win->y; - win->internal.oldW = win->w; - win->internal.oldH = win->h; + win->_oldRect = win->r; RGFW_monitor mon = RGFW_window_getMonitor(win); - win->x = mon.x; - win->y = mon.y; - win->w = mon.mode.w; - win->h = mon.mode.h; - win->internal.flags |= RGFW_windowFullscreen; - RGFW_window_resize(win, mon.mode.w, mon.mode.h); - RGFW_window_move(win, mon.x, mon.y); + win->r = RGFW_RECT(0, 0, mon.x, mon.y); + win->_flags |= RGFW_windowFullscreen; + RGFW_window_resize(win, RGFW_AREA(mon.mode.area.w, mon.mode.area.h)); + RGFW_window_move(win, RGFW_POINT(0, 0)); } objc_msgSend_void_SEL(win->src.window, sel_registerName("toggleFullScreen:"), NULL); if (!fullscreen) { - win->x = win->internal.oldX; - win->y = win->internal.oldY; - win->w = win->internal.oldW; - win->h = win->internal.oldH; - win->internal.flags &= ~(u32)RGFW_windowFullscreen; + win->r = win->_oldRect; + win->_flags &= ~(u32)RGFW_windowFullscreen; - RGFW_window_resize(win, win->w, win->h); - RGFW_window_move(win, win->x, win->y); + RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h)); + RGFW_window_move(win, RGFW_POINT(win->r.x, win->r.y)); } } @@ -12077,7 +9592,7 @@ void RGFW_window_maximize(RGFW_window* win) { RGFW_ASSERT(win != NULL); if (RGFW_window_isMaximized(win)) return; - win->internal.flags |= RGFW_windowMaximize; + win->_flags |= RGFW_windowMaximize; objc_msgSend_void_SEL(win->src.window, sel_registerName("zoom:"), NULL); } @@ -12130,75 +9645,81 @@ void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { } #endif -void RGFW_window_setAspectRatio(RGFW_window* win, i32 w, i32 h) { - if (w == 0 && h == 0) { w = 1; h = 1; }; +void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { + if (a.w == 0 && a.h == 0) a = RGFW_AREA(1, 1); ((void (*)(id, SEL, NSSize))objc_msgSend) - ((id)win->src.window, sel_registerName("setContentAspectRatio:"), (NSSize){(CGFloat)w, (CGFloat)h}); + ((id)win->src.window, sel_registerName("setContentAspectRatio:"), (NSSize){a.w, a.h}); } -void RGFW_window_setMinSize(RGFW_window* win, i32 w, i32 h) { - ((void (*)(id, SEL, NSSize))objc_msgSend) ((id)win->src.window, sel_registerName("setMinSize:"), (NSSize){(CGFloat)w, (CGFloat)h}); +void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { + ((void (*)(id, SEL, NSSize))objc_msgSend) + ((id)win->src.window, sel_registerName("setMinSize:"), (NSSize){a.w, a.h}); } -void RGFW_window_setMaxSize(RGFW_window* win, i32 w, i32 h) { - if (w == 0 && h == 0) { - RGFW_monitor mon = RGFW_window_getMonitor(win); - w = mon.mode.w; - h = mon.mode.h; +void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { + if (a.w == 0 && a.h == 0) { + a = RGFW_getScreenSize(); } ((void (*)(id, SEL, NSSize))objc_msgSend) - ((id)win->src.window, sel_registerName("setMaxSize:"), (NSSize){(CGFloat)w, (CGFloat)h}); + ((id)win->src.window, sel_registerName("setMaxSize:"), (NSSize){a.w, a.h}); } -RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_icon type) { +RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, RGFW_area area, i32 channels, u8 type) { RGFW_ASSERT(win != NULL); RGFW_UNUSED(type); if (data == NULL) { - objc_msgSend_void_id((id)_RGFW->NSApp, sel_registerName("setApplicationIconImage:"), NULL); + objc_msgSend_void_id(NSApp, sel_registerName("setApplicationIconImage:"), NULL); return RGFW_TRUE; } - id representation = NSBitmapImageRep_initWithBitmapData(NULL, w, h, 8, (NSInteger)4, true, false, "NSCalibratedRGBColorSpace", 1 << 1, w * 4, 32); - RGFW_copyImageData(NSBitmapImageRep_bitmapData(representation), w, h, RGFW_formatRGBA8, data, format); + /* code by EimaMei: Make a bitmap representation, then copy the loaded image into it. */ + id representation = NSBitmapImageRep_initWithBitmapData(NULL, area.w, area.h, 8, channels, (channels == 4), false, "NSCalibratedRGBColorSpace", 1 << 1, area.w * (u32)channels, 8 * (u32)channels); + RGFW_MEMCPY(NSBitmapImageRep_bitmapData(representation), data, area.w * area.h * (u32)channels); - id dock_image = ((id(*)(id, SEL, NSSize))objc_msgSend) (NSAlloc((id)objc_getClass("NSImage")), sel_registerName("initWithSize:"), ((NSSize){(CGFloat)w, (CGFloat)h})); + /* Add ze representation. */ + id dock_image = ((id(*)(id, SEL, NSSize))objc_msgSend) (NSAlloc((id)objc_getClass("NSImage")), sel_registerName("initWithSize:"), ((NSSize){area.w, area.h})); objc_msgSend_void_id(dock_image, sel_registerName("addRepresentation:"), representation); - objc_msgSend_void_id((id)_RGFW->NSApp, sel_registerName("setApplicationIconImage:"), dock_image); - + /* Finally, set the dock image to it. */ + objc_msgSend_void_id(NSApp, sel_registerName("setApplicationIconImage:"), dock_image); + /* Free the garbage. */ NSRelease(dock_image); NSRelease(representation); return RGFW_TRUE; } -id NSCursor_arrowStr(const char* str); id NSCursor_arrowStr(const char* str) { void* nclass = objc_getClass("NSCursor"); SEL func = sel_registerName(str); return (id) objc_msgSend_id(nclass, func); } -RGFW_mouse* RGFW_loadMouse(u8* data, i32 w, i32 h, RGFW_format format) { - if (data == NULL) { +RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { + if (icon == NULL) { objc_msgSend_void(NSCursor_arrowStr("arrowCursor"), sel_registerName("set")); return NULL; } - id representation = (id)NSBitmapImageRep_initWithBitmapData(NULL, w, h, 8, (NSInteger)4, true, false, "NSCalibratedRGBColorSpace", 1 << 1, w * 4, 32); - RGFW_copyImageData(NSBitmapImageRep_bitmapData(representation), w, h, RGFW_formatRGBA8, data, format); + /* NOTE(EimaMei): Code by yours truly. */ + /* Make a bitmap representation, then copy the loaded image into it. */ + id representation = (id)NSBitmapImageRep_initWithBitmapData(NULL, a.w, a.h, 8, channels, (channels == 4), false, "NSCalibratedRGBColorSpace", 1 << 1, a.w * (u32)channels, 8 * (u32)channels); + RGFW_MEMCPY(NSBitmapImageRep_bitmapData(representation), icon, a.w * a.h * (u32)channels); - id cursor_image = ((id(*)(id, SEL, NSSize))objc_msgSend) (NSAlloc((id)objc_getClass("NSImage")), sel_registerName("initWithSize:"), ((NSSize){(CGFloat)w, (CGFloat)h})); + /* Add ze representation. */ + id cursor_image = ((id(*)(id, SEL, NSSize))objc_msgSend) (NSAlloc((id)objc_getClass("NSImage")), sel_registerName("initWithSize:"), ((NSSize){a.w, a.h})); objc_msgSend_void_id(cursor_image, sel_registerName("addRepresentation:"), representation); + /* Finally, set the cursor image. */ id cursor = (id) ((id(*)(id, SEL, id, NSPoint))objc_msgSend) (NSAlloc(objc_getClass("NSCursor")), sel_registerName("initWithImage:hotSpot:"), cursor_image, (NSPoint){0.0, 0.0}); + /* Free the garbage. */ NSRelease(cursor_image); NSRelease(representation); @@ -12251,19 +9772,18 @@ void RGFW_releaseCursor(RGFW_window* win) { CGAssociateMouseAndMouseCursorPosition(1); } -void RGFW_captureCursor(RGFW_window* win) { +void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); - CGWarpMouseCursorPosition((CGPoint){(CGFloat)(win->x + (win->w / 2)), (CGFloat)(win->y + (win->h / 2))}); + CGWarpMouseCursorPosition((CGPoint){r.x + (r.w / 2), r.y + (r.h / 2)}); CGAssociateMouseAndMouseCursorPosition(0); } -void RGFW_window_moveMouse(RGFW_window* win, i32 x, i32 y) { +void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v) { RGFW_UNUSED(win); - win->internal.lastMouseX = x - win->x; - win->internal.lastMouseY = y - win->y; - CGWarpMouseCursorPosition((CGPoint){(CGFloat)x, (CGFloat)y}); + win->_lastMousePoint = RGFW_POINT(v.x - win->r.x, v.y - win->r.y); + CGWarpMouseCursorPosition((CGPoint){v.x, v.y}); } @@ -12272,7 +9792,7 @@ void RGFW_window_hide(RGFW_window* win) { } void RGFW_window_show(RGFW_window* win) { - if (win->internal.flags & RGFW_windowFocusOnShow) + if (win->_flags & RGFW_windowFocusOnShow) ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("makeKeyAndOrderFront:"), NULL); ((id(*)(id, SEL, SEL))objc_msgSend)((id)win->src.window, sel_registerName("orderFront:"), NULL); @@ -12298,7 +9818,6 @@ RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { return b; } -id RGFW_getNSScreenForDisplayID(CGDirectDisplayID display); id RGFW_getNSScreenForDisplayID(CGDirectDisplayID display) { Class NSScreenClass = objc_getClass("NSScreen"); @@ -12320,7 +9839,8 @@ id RGFW_getNSScreenForDisplayID(CGDirectDisplayID display) { return NULL; } -u32 RGFW_osx_getRefreshRate(CGDirectDisplayID display, CGDisplayModeRef mode); +u32 RGFW_osx_getFallbackRefreshRate(CGDirectDisplayID displayID); + u32 RGFW_osx_getRefreshRate(CGDirectDisplayID display, CGDisplayModeRef mode) { if (mode) { u32 refreshRate = (u32)CGDisplayModeGetRefreshRate(mode); @@ -12336,7 +9856,6 @@ u32 RGFW_osx_getRefreshRate(CGDirectDisplayID display, CGDisplayModeRef mode) { return 60; } -RGFW_monitor RGFW_NSCreateMonitor(CGDirectDisplayID display, id screen); RGFW_monitor RGFW_NSCreateMonitor(CGDirectDisplayID display, id screen) { RGFW_monitor monitor; @@ -12346,8 +9865,7 @@ RGFW_monitor RGFW_NSCreateMonitor(CGDirectDisplayID display, id screen) { CGRect bounds = CGDisplayBounds(display); monitor.x = (i32)bounds.origin.x; monitor.y = (i32)bounds.origin.y; - monitor.mode.w = (i32) bounds.size.width; - monitor.mode.h = (i32) bounds.size.height; + monitor.mode.area = RGFW_AREA((int) bounds.size.width, (int) bounds.size.height); monitor.mode.red = 8; monitor.mode.green = 8; monitor.mode.blue = 8; @@ -12359,8 +9877,8 @@ RGFW_monitor RGFW_NSCreateMonitor(CGDirectDisplayID display, id screen) { monitor.physW = (float)screenSizeMM.width / 25.4f; monitor.physH = (float)screenSizeMM.height / 25.4f; - float ppi_width = (monitor.mode.w/monitor.physW); - float ppi_height = (monitor.mode.h/monitor.physH); + float ppi_width = (monitor.mode.area.w/monitor.physW); + float ppi_height = (monitor.mode.area.h/monitor.physH); monitor.pixelRatio = (float)((CGFloat (*)(id, SEL))abi_objc_msgSend_fpret) (screen, sel_registerName("backingScaleFactor")); float dpi = 96.0f * monitor.pixelRatio; @@ -12368,7 +9886,7 @@ RGFW_monitor RGFW_NSCreateMonitor(CGDirectDisplayID display, id screen) { monitor.scaleX = ((i32)(((float) (ppi_width) / dpi) * 10.0f)) / 10.0f; monitor.scaleY = ((i32)(((float) (ppi_height) / dpi) * 10.0f)) / 10.0f; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, "monitor found"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoMonitor, RGFW_DEBUG_CTX_MON(monitor), "monitor found"); return monitor; } @@ -12393,10 +9911,10 @@ RGFW_monitor* RGFW_getMonitors(size_t* len) { } RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { - CGPoint point = { (CGFloat)mon.x, (CGFloat)mon.y }; + CGPoint point = { mon.x, mon.y }; CGDirectDisplayID display; - u32 displayCount = 0; + uint32_t displayCount = 0; CGError err = CGGetDisplaysWithPoint(point, 1, &display, &displayCount); if (err != kCGErrorSuccess || displayCount != 1) return RGFW_FALSE; @@ -12411,8 +9929,7 @@ RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW CGDisplayModeRef cmode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i); RGFW_monitorMode foundMode; - foundMode.w = (i32)CGDisplayModeGetWidth(cmode); - foundMode.h = (i32)CGDisplayModeGetHeight(cmode); + foundMode.area = RGFW_AREA(CGDisplayModeGetWidth(cmode), CGDisplayModeGetHeight(cmode)); foundMode.refreshRate = RGFW_osx_getRefreshRate(display, cmode); foundMode.red = 8; foundMode.green = 8; foundMode.blue = 8; @@ -12471,203 +9988,104 @@ void RGFW_writeClipboard(const char* text, u32 textLen) { SEL func = sel_registerName("setString:forType:"); ((bool (*)(id, SEL, id, id))objc_msgSend) - (NSPasteboard_generalPasteboard(), func, NSString_stringWithUTF8String(text), NSString_stringWithUTF8String((const char*)NSPasteboardTypeString)); + (NSPasteboard_generalPasteboard(), func, NSString_stringWithUTF8String(text), NSString_stringWithUTF8String(NSPasteboardTypeString)); } -#ifdef RGFW_OPENGL -void NSOpenGLContext_setValues(id context, const int* vals, NSOpenGLContextParameter param); -void NSOpenGLContext_setValues(id context, const int* vals, NSOpenGLContextParameter param) { - ((void (*)(id, SEL, const int*, NSOpenGLContextParameter))objc_msgSend) - (context, sel_registerName("setValues:forParameter:"), vals, param); -} - - -/* MacOS OpenGL API spares us yet again (there are no extensions) */ -RGFW_bool RGFW_extensionSupportedPlatform_OpenGL(const char * extension, size_t len) { RGFW_UNUSED(extension); RGFW_UNUSED(len); return RGFW_FALSE; } - -RGFW_proc RGFW_getProcAddress_OpenGL(const char* procname) { - static CFBundleRef RGFWnsglFramework = NULL; - if (RGFWnsglFramework == NULL) - RGFWnsglFramework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); - - CFStringRef symbolName = CFStringCreateWithCString(kCFAllocatorDefault, procname, kCFStringEncodingASCII); - - RGFW_proc symbol = (RGFW_proc)CFBundleGetFunctionPointerForName(RGFWnsglFramework, symbolName); - - CFRelease(symbolName); - - return symbol; -} - -RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { - win->src.ctx.native = ctx; - win->src.gfxType = RGFW_gfxNativeOpenGL; - - i32 attribs[40]; - size_t render_type_index = 0; - { - RGFW_attribStack stack; - RGFW_attribStack_init(&stack, attribs, 40); - - i32 colorBits = (i32)(hints->red + hints->green + hints->blue + hints->alpha) / 4; - RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAColorSize, colorBits); - - RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAAlphaSize, hints->alpha); - RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFADepthSize, hints->depth); - RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAStencilSize, hints->stencil); - RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAAuxBuffers, hints->auxBuffers); - RGFW_attribStack_pushAttrib(&stack, NSOpenGLPFAClosestPolicy); - if (hints->samples) { - RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFASampleBuffers, 1); - RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFASamples, hints->samples); - } else RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFASampleBuffers, 0); - - if (hints->doubleBuffer) - RGFW_attribStack_pushAttrib(&stack, NSOpenGLPFADoubleBuffer); - - #ifdef RGFW_COCOA_GRAPHICS_SWITCHING - RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAAllowOfflineRenderers, kCGLPFASupportsAutomaticGraphicsSwitching) - #endif - #if MAC_OS_X_VERSION_MAX_ALLOWED < 101200 - if (hints->stereo]) RGFW_attribStack_pushAttrib(&stack, NSOpenGLPFAStereo); - #endif - - /* macOS has the surface attribs and the OpenGL attribs connected for some reason maybe this is to give macOS more control to limit openGL/the OpenGL version? */ - RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAOpenGLProfile, - (hints->major >= 4) ? NSOpenGLProfileVersion4_1Core : (hints->major >= 3) ? - NSOpenGLProfileVersion3_2Core : NSOpenGLProfileVersionLegacy); - - if (hints->major <= 2) { - i32 accumSize = (i32)(hints->accumRed + hints->accumGreen + hints->accumBlue + hints->accumAlpha) / 4; - RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFAAccumSize, accumSize); - } - - if (hints->renderer == RGFW_glSoftware) { - RGFW_attribStack_pushAttribs(&stack, NSOpenGLPFARendererID, kCGLRendererGenericFloatID); - } else { - RGFW_attribStack_pushAttrib(&stack, NSOpenGLPFAAccelerated); - } - render_type_index = stack.count - 1; - - RGFW_attribStack_pushAttribs(&stack, 0, 0); - } - - void* format = (void*) ((id(*)(id, SEL, const u32*))objc_msgSend) (NSAlloc((id)objc_getClass("NSOpenGLPixelFormat")), sel_registerName("initWithAttributes:"), (u32*)attribs); - if (format == NULL) { - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "Failed to load pixel format for OpenGL"); - - assert(render_type_index + 3 < (sizeof(attribs) / sizeof(attribs[0]))); - attribs[render_type_index] = NSOpenGLPFARendererID; - attribs[render_type_index + 1] = kCGLRendererGenericFloatID; - attribs[render_type_index + 3] = 0; - - format = (void*) ((id(*)(id, SEL, const u32*))objc_msgSend) (NSAlloc((id)objc_getClass("NSOpenGLPixelFormat")), sel_registerName("initWithAttributes:"), (u32*)attribs); - if (format == NULL) - RGFW_sendDebugInfo(RGFW_typeError, RGFW_errOpenGLContext, "and loading software rendering OpenGL failed"); + #ifdef RGFW_OPENGL + void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { + if (win != NULL) + objc_msgSend_void(win->src.ctx, sel_registerName("makeCurrentContext")); else - RGFW_sendDebugInfo(RGFW_typeWarning, RGFW_warningOpenGL, "Switching to software rendering"); + objc_msgSend_id(objc_getClass("NSOpenGLContext"), sel_registerName("clearCurrentContext")); + } + void* RGFW_getCurrent_OpenGL(void) { + return objc_msgSend_id(objc_getClass("NSOpenGLContext"), sel_registerName("currentContext")); } - /* the pixel format can be passed directly to OpenGL context creation to create a context - this is because the format also includes information about the OpenGL version (which may be a bad thing) */ + void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { + objc_msgSend_void(win->src.ctx, sel_registerName("flushBuffer")); + } + #endif - if (win->src.view) - NSRelease(win->src.view); - win->src.view = (id) ((id(*)(id, SEL, NSRect, u32*))objc_msgSend) (NSAlloc(_RGFW->customViewClasses[1]), - sel_registerName("initWithFrame:pixelFormat:"), (NSRect){{0, 0}, {(double)win->w, (double)win->h}}, (u32*)format); + #if !defined(RGFW_EGL) - id share = NULL; - if (hints->share) { - share = (id)hints->share->ctx; + void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { + RGFW_ASSERT(win != NULL); + #if defined(RGFW_OPENGL) + + NSOpenGLContext_setValues((id)win->src.ctx, &swapInterval, 222); + #else + RGFW_UNUSED(swapInterval); + #endif } - win->src.ctx.native->ctx = ((id (*)(id, SEL, id, id))objc_msgSend)(NSAlloc(objc_getClass("NSOpenGLContext")), - sel_registerName("initWithFormat:shareContext:"), - (id)format, share); + #endif - objc_msgSend_void_id(win->src.view, sel_registerName("setOpenGLContext:"), win->src.ctx.native->ctx); - if (win->internal.flags & RGFW_windowTransparent) { - i32 opacity = 0; - #define NSOpenGLCPSurfaceOpacity 236 - NSOpenGLContext_setValues((id)win->src.ctx.native->ctx, &opacity, (NSOpenGLContextParameter)NSOpenGLCPSurfaceOpacity); +void RGFW_window_swapBuffers_software(RGFW_window* win) { +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + RGFW_RGB_to_BGR(win, win->buffer); + i32 channels = 4; + id image = ((id (*)(Class, SEL))objc_msgSend)(objc_getClass("NSImage"), sel_getUid("alloc")); + NSSize size = (NSSize){win->bufferSize.w, win->bufferSize.h}; + image = ((id (*)(id, SEL, NSSize))objc_msgSend)((id)image, sel_getUid("initWithSize:"), size); - } + id rep = NSBitmapImageRep_initWithBitmapData(&win->buffer, win->r.w, win->r.h , 8, channels, (channels == 4), false, + "NSDeviceRGBColorSpace", 1 << 1, (u32)win->bufferSize.w * (u32)channels, 8 * (u32)channels); + ((void (*)(id, SEL, id))objc_msgSend)((id)image, sel_getUid("addRepresentation:"), rep); - objc_msgSend_void(win->src.ctx.native->ctx, sel_registerName("makeCurrentContext")); + id contentView = ((id (*)(id, SEL))objc_msgSend)((id)win->src.window, sel_getUid("contentView")); + ((void (*)(id, SEL, BOOL))objc_msgSend)(contentView, sel_getUid("setWantsLayer:"), YES); + id layer = ((id (*)(id, SEL))objc_msgSend)(contentView, sel_getUid("layer")); - objc_msgSend_void_id((id)win->src.window, sel_registerName("setContentView:"), win->src.view); - objc_msgSend_void_bool(win->src.view, sel_registerName("setWantsLayer:"), true); - objc_msgSend_int((id)win->src.view, sel_registerName("setLayerContentsPlacement:"), 4); + ((void (*)(id, SEL, id))objc_msgSend)(layer, sel_getUid("setContents:"), (id)image); + ((void (*)(id, SEL, BOOL))objc_msgSend)(contentView, sel_getUid("setNeedsDisplay:"), YES); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context initalized."); - return RGFW_TRUE; -} - -void RGFW_window_deleteContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx) { - objc_msgSend_void(ctx->ctx, sel_registerName("release")); - win->src.ctx.native->ctx = NULL; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context freed."); -} - -void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win) { - if (win) RGFW_ASSERT(win->src.ctx.native); - if (win != NULL) - objc_msgSend_void(win->src.ctx.native->ctx, sel_registerName("makeCurrentContext")); - else - objc_msgSend_id(objc_getClass("NSOpenGLContext"), sel_registerName("clearCurrentContext")); -} -void* RGFW_getCurrentContext_OpenGL(void) { - return objc_msgSend_id(objc_getClass("NSOpenGLContext"), sel_registerName("currentContext")); -} - -void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { - RGFW_ASSERT(win && win->src.ctx.native); - objc_msgSend_void(win->src.ctx.native->ctx, sel_registerName("flushBuffer")); -} -void RGFW_window_swapInterval_OpenGL(RGFW_window* win, i32 swapInterval) { - RGFW_ASSERT(win != NULL && win->src.ctx.native != NULL); - NSOpenGLContext_setValues((id)win->src.ctx.native->ctx, &swapInterval, (NSOpenGLContextParameter)222); -} -#endif - -void RGFW_deinitPlatform(void) { } - -void RGFW_window_closePlatform(RGFW_window* win) { - NSRelease(win->src.view); -} - -#ifdef RGFW_WEBGPU -WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance instance) { - WGPUSurfaceDescriptor surfaceDesc = {0}; - id* nsView = (id*)window->src.view; - if (!nsView) { - fprintf(stderr, "RGFW Error: NSView is NULL for macOS window.\n"); - return NULL; - } - - ((void (*)(id, SEL, BOOL))objc_msgSend)(nsView, sel_registerName("setWantsLayer:"), YES); - id layer = ((id (*)(id, SEL))objc_msgSend)(nsView, sel_registerName("layer")); - - void* metalLayer = RGFW_getLayer_OSX(); - if (metalLayer == NULL) { - return NULL; - } - ((void (*)(id, SEL, id))objc_msgSend)((id)nsView, sel_registerName("setLayer:"), metalLayer); - layer = metalLayer; /* Use the newly created layer */ - - /* At this point, 'layer' should be a valid CAMetalLayer* */ - WGPUSurfaceSourceMetalLayer fromMetal = {0}; - fromMetal.chain.sType = WGPUSType_SurfaceSourceMetalLayer; -#ifdef __OBJC__ - fromMetal.layer = (__bridge CAMetalLayer*)layer; /* Use __bridge for ARC compatibility if mixing C/Obj-C */ + NSRelease(rep); + NSRelease(image); #else - fromMetal.layer = layer; + RGFW_UNUSED(win); #endif - - surfaceDesc.nextInChain = (WGPUChainedStruct*)&fromMetal.chain; - return wgpuInstanceCreateSurface(instance, &surfaceDesc); } -#endif + +void RGFW_deinit(void) { + _RGFW.windowCount = -1; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context deinitialized"); +} + +void RGFW_window_close(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + NSRelease(win->src.view); + if ((win->_flags & RGFW_windowNoInitAPI) == 0) RGFW_window_freeOpenGL(win); + + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + if ((win->_flags & RGFW_BUFFER_ALLOC)) + RGFW_FREE(win->buffer); + #endif + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context deinitialized"); + _RGFW.windowCount--; + if (_RGFW.windowCount == 0) RGFW_deinit(); + + RGFW_clipboard_switch(NULL); + RGFW_FREE(win->event.droppedFiles); + if ((win->_flags & RGFW_WINDOW_ALLOC)) { + RGFW_FREE(win); + win = NULL; + } +} + +u64 RGFW_getTimerFreq(void) { + static u64 freq = 0; + if (freq == 0) { + mach_timebase_info_data_t info; + mach_timebase_info(&info); + freq = (u64)((info.denom * 1e9) / info.numer); + } + + return freq; +} + +u64 RGFW_getTimerValue(void) { return (u64)mach_absolute_time(); } #endif /* RGFW_MACOS */ @@ -12683,40 +10101,33 @@ WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance i EM_BOOL Emscripten_on_resize(int eventType, const EmscriptenUiEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - if (!(_RGFW->root->internal.enabledEvents & RGFW_windowResizedFlag)) return EM_TRUE; - - RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e.common.win = _RGFW->root); - RGFW_windowResizedCallback(_RGFW->root, E->windowInnerWidth, E->windowInnerHeight); + RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e._win = _RGFW.root); + RGFW_windowResizedCallback(_RGFW.root, RGFW_RECT(0, 0, E->windowInnerWidth, E->windowInnerHeight)); return EM_TRUE; } EM_BOOL Emscripten_on_fullscreenchange(int eventType, const EmscriptenFullscreenChangeEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - - if (!(_RGFW->root->internal.enabledEvents & RGFW_windowResizedFlag)) return EM_TRUE; - static u8 fullscreen = RGFW_FALSE; - static i32 originalW, originalH; + static RGFW_rect ogRect; if (fullscreen == RGFW_FALSE) { - originalW = _RGFW->root->w; - originalH = _RGFW->root->h; + ogRect = _RGFW.root->r; } fullscreen = !fullscreen; - RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e.common.win = _RGFW->root); - _RGFW->root->w = E->screenWidth; - _RGFW->root->h = E->screenHeight; + RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e._win = _RGFW.root); + _RGFW.root->r = RGFW_RECT(0, 0, E->screenWidth, E->screenHeight); EM_ASM("Module.canvas.focus();"); if (fullscreen == RGFW_FALSE) { - _RGFW->root->w = originalW; - _RGFW->root->h = originalH; + _RGFW.root->r = RGFW_RECT(0, 0, ogRect.w, ogRect.h); + /* emscripten_request_fullscreen("#canvas", 0); */ } else { #if __EMSCRIPTEN_major__ >= 1 && __EMSCRIPTEN_minor__ >= 29 && __EMSCRIPTEN_tiny__ >= 0 EmscriptenFullscreenStrategy FSStrat = {0}; - FSStrat.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; + FSStrat.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; /* EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT : EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; */ FSStrat.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF; FSStrat.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; emscripten_request_fullscreen_strategy("#canvas", 1, &FSStrat); @@ -12725,111 +10136,97 @@ EM_BOOL Emscripten_on_fullscreenchange(int eventType, const EmscriptenFullscreen #endif } - emscripten_set_canvas_element_size("#canvas", _RGFW->root->w, _RGFW->root->h); - RGFW_windowResizedCallback(_RGFW->root, _RGFW->root->w, _RGFW->root->h); + emscripten_set_canvas_element_size("#canvas", _RGFW.root->r.w, _RGFW.root->r.h); + + RGFW_windowResizedCallback(_RGFW.root, _RGFW.root->r); return EM_TRUE; } + + EM_BOOL Emscripten_on_focusin(int eventType, const EmscriptenFocusEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(E); - if (!(_RGFW->root->internal.enabledEvents & RGFW_focusInFlag)) return EM_TRUE; + RGFW_eventQueuePushEx(e.type = RGFW_focusIn; e._win = _RGFW.root); + _RGFW.root->_flags |= RGFW_windowFocus; + RGFW_focusCallback(_RGFW.root, 1); - RGFW_eventQueuePushEx(e.type = RGFW_focusIn; e.common.win = _RGFW->root); - _RGFW->root->internal.inFocus = RGFW_TRUE; - RGFW_focusCallback(_RGFW->root, 1); - - if ((_RGFW->root->internal.holdMouse)) RGFW_window_holdMouse(_RGFW->root); + if ((_RGFW.root->_flags & RGFW_HOLD_MOUSE)) RGFW_window_mouseHold(_RGFW.root, RGFW_AREA(_RGFW.root->r.w, _RGFW.root->r.h)); return EM_TRUE; } EM_BOOL Emscripten_on_focusout(int eventType, const EmscriptenFocusEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(E); - if (!(_RGFW->root->internal.enabledEvents & RGFW_focusOutFlag)) return EM_TRUE; - - RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e.common.win = _RGFW->root); - RGFW_window_focusLost(_RGFW->root); - RGFW_focusCallback(_RGFW->root, 0); + RGFW_eventQueuePushEx(e.type = RGFW_focusOut; e._win = _RGFW.root); + RGFW_window_focusLost(_RGFW.root); + RGFW_focusCallback(_RGFW.root, 0); return EM_TRUE; } EM_BOOL Emscripten_on_mousemove(int eventType, const EmscriptenMouseEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - - if (!(_RGFW->root->internal.enabledEvents & RGFW_mousePosChangedFlag)) return EM_TRUE; - RGFW_eventQueuePushEx(e.type = RGFW_mousePosChanged; - e.mouse.x = E->targetX; e.mouse.y = E->targetY; - e.mouse.vecX = E->movementX; e.mouse.vecY = E->movementY; - e.common.win = _RGFW->root); + e.point = RGFW_POINT(E->targetX, E->targetY); + e.vector = RGFW_POINT(E->movementX, E->movementY); + e._win = _RGFW.root); - _RGFW->vectorX = E->movementX; - _RGFW->vectorY = E->movementY; - _RGFW->root->internal.lastMouseX = E->targetX; - _RGFW->root->internal.lastMouseY = E->targetY; - RGFW_mousePosCallback(_RGFW->root, E->targetX, E->targetY, E->movementX, E->movementY); + _RGFW.root->_lastMousePoint = RGFW_POINT(E->targetX, E->targetY); + RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(E->targetX, E->targetY), RGFW_POINT(E->movementX, E->movementY)); return EM_TRUE; } EM_BOOL Emscripten_on_mousedown(int eventType, const EmscriptenMouseEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - if (!(_RGFW->root->internal.enabledEvents & RGFW_mouseButtonPressedFlag)) return EM_TRUE; - int button = E->button; if (button > 2) button += 2; RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonPressed; - e.mouse.x = E->targetX; e.mouse.y = E->targetY; - e.mouse.vecX = E->movementX; e.mouse.vecY = E->movementY; - e.button.value = (u8)button; - e.common.win = _RGFW->root); - _RGFW->vectorX = E->movementX; - _RGFW->vectorY = E->movementY; - _RGFW->mouseButtons[button].prev = _RGFW->mouseButtons[button].current; - _RGFW->mouseButtons[button].current = 1; + e.point = RGFW_POINT(E->targetX, E->targetY); + e.vector = RGFW_POINT(E->movementX, E->movementY); + e.button = (u8)button; + e.scroll = 0; + e._win = _RGFW.root); + RGFW_mouseButtons[button].prev = RGFW_mouseButtons[button].current; + RGFW_mouseButtons[button].current = 1; - RGFW_mouseButtonCallback(_RGFW->root, button, 1); + RGFW_mouseButtonCallback(_RGFW.root, button, 0, 1); return EM_TRUE; } EM_BOOL Emscripten_on_mouseup(int eventType, const EmscriptenMouseEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - if (!(_RGFW->root->internal.enabledEvents & RGFW_mouseButtonReleasedFlag)) return EM_TRUE; - int button = E->button; if (button > 2) button += 2; RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonReleased; - e.mouse.x = E->targetX; e.mouse.y = E->targetY; - e.mouse.vecX = E->movementX; e.mouse.vecY = E->movementY; - e.button.value = (u8)button; - e.common.win = _RGFW->root); - _RGFW->vectorX = E->movementX; - _RGFW->vectorY = E->movementY; - _RGFW->mouseButtons[button].prev = _RGFW->mouseButtons[button].current; - _RGFW->mouseButtons[button].current = 0; + e.point = RGFW_POINT(E->targetX, E->targetY); + e.vector = RGFW_POINT(E->movementX, E->movementY); + e.button = (u8)button; + e.scroll = 0; + e._win = _RGFW.root); + RGFW_mouseButtons[button].prev = RGFW_mouseButtons[button].current; + RGFW_mouseButtons[button].current = 0; - RGFW_mouseButtonCallback(_RGFW->root, button, 0); + RGFW_mouseButtonCallback(_RGFW.root, button, 0, 0); return EM_TRUE; } EM_BOOL Emscripten_on_wheel(int eventType, const EmscriptenWheelEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - if (!(_RGFW->root->internal.enabledEvents & RGFW_mouseScrollFlag)) return EM_TRUE; - - _RGFW->scrollX = E->deltaX; - _RGFW->scrollY = E->deltaY; + int button = RGFW_mouseScrollUp + (E->deltaY < 0); RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonPressed; - e.scroll.x = E->deltaX; - e.scroll.y = E->deltaY; - ); - RGFW_mouseScrollCallback(_RGFW->root, E->deltaX, E->deltaY); + e.button = (u8)button; + e.scroll = (double)(E->deltaY < 0 ? 1 : -1); + e._win = _RGFW.root); + RGFW_mouseButtons[button].prev = RGFW_mouseButtons[button].current; + RGFW_mouseButtons[button].current = 1; + RGFW_mouseButtonCallback(_RGFW.root, button, E->deltaY < 0 ? 1 : -1, 1); return EM_TRUE; } @@ -12837,44 +10234,35 @@ EM_BOOL Emscripten_on_wheel(int eventType, const EmscriptenWheelEvent* E, void* EM_BOOL Emscripten_on_touchstart(int eventType, const EmscriptenTouchEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - if (!(_RGFW->root->internal.enabledEvents & RGFW_mouseButtonPressedFlag)) return EM_TRUE; - size_t i; for (i = 0; i < (size_t)E->numTouches; i++) { RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonPressed; - e.mouse.x = E->touches[i].targetX; e.mouse.y = E->touches[i].targetY; - e.button.value = RGFW_mouseLeft; - e.common.win = _RGFW->root); + e.point = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); + e.button = RGFW_mouseLeft; + e._win = _RGFW.root); - _RGFW->mouseButtons[RGFW_mouseLeft].prev = _RGFW->mouseButtons[RGFW_mouseLeft].current; - _RGFW->mouseButtons[RGFW_mouseLeft].current = 1; + RGFW_mouseButtons[RGFW_mouseLeft].prev = RGFW_mouseButtons[RGFW_mouseLeft].current; + RGFW_mouseButtons[RGFW_mouseLeft].current = 1; - _RGFW->root->internal.lastMouseX = E->touches[i].targetX; - _RGFW->root->internal.lastMouseX = E->touches[i].targetY; - RGFW_mousePosCallback(_RGFW->root, E->touches[i].targetX, E->touches[i].targetY, 0, 0); - RGFW_mouseButtonCallback(_RGFW->root, RGFW_mouseLeft, 1); + _RGFW.root->_lastMousePoint = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); + RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY), _RGFW.root->event.vector); + RGFW_mouseButtonCallback(_RGFW.root, RGFW_mouseLeft, 0, 1); } return EM_TRUE; } - EM_BOOL Emscripten_on_touchmove(int eventType, const EmscriptenTouchEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - if (!(_RGFW->root->internal.enabledEvents & RGFW_mousePosChangedFlag)) return EM_TRUE; - size_t i; for (i = 0; i < (size_t)E->numTouches; i++) { RGFW_eventQueuePushEx(e.type = RGFW_mousePosChanged; - e.mouse.x = E->touches[i].targetX; - e.mouse.y = E->touches[i].targetY; - e.mouse.x = E->touches[i].targetX; e.mouse.y = E->touches[i].targetY; - e.button.value = RGFW_mouseLeft; - e.common.win = _RGFW->root); + e.point = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); + e.button = RGFW_mouseLeft; + e._win = _RGFW.root); - _RGFW->root->internal.lastMouseX = E->touches[i].targetX; - _RGFW->root->internal.lastMouseX = E->touches[i].targetY; - RGFW_mousePosCallback(_RGFW->root, E->touches[i].targetX, E->touches[i].targetY, 0, 0); + _RGFW.root->_lastMousePoint = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); + RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY), _RGFW.root->event.vector); } return EM_TRUE; } @@ -12882,563 +10270,60 @@ EM_BOOL Emscripten_on_touchmove(int eventType, const EmscriptenTouchEvent* E, vo EM_BOOL Emscripten_on_touchend(int eventType, const EmscriptenTouchEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - if (!(_RGFW->root->internal.enabledEvents & RGFW_mouseButtonReleasedFlag)) return EM_TRUE; - size_t i; for (i = 0; i < (size_t)E->numTouches; i++) { RGFW_eventQueuePushEx(e.type = RGFW_mouseButtonReleased; - e.mouse.x = E->touches[i].targetX; e.mouse.y = E->touches[i].targetY; - e.button.value = RGFW_mouseLeft; - e.common.win = _RGFW->root); + e.point = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); + e.button = RGFW_mouseLeft; + e._win = _RGFW.root); - _RGFW->mouseButtons[RGFW_mouseLeft].prev = _RGFW->mouseButtons[RGFW_mouseLeft].current; - _RGFW->mouseButtons[RGFW_mouseLeft].current = 0; + RGFW_mouseButtons[RGFW_mouseLeft].prev = RGFW_mouseButtons[RGFW_mouseLeft].current; + RGFW_mouseButtons[RGFW_mouseLeft].current = 0; - _RGFW->root->internal.lastMouseX = E->touches[i].targetX; - _RGFW->root->internal.lastMouseY = E->touches[i].targetY; - RGFW_mousePosCallback(_RGFW->root, E->touches[i].targetX, E->touches[i].targetY, 0, 0); - RGFW_mouseButtonCallback(_RGFW->root, RGFW_mouseLeft, 0); + _RGFW.root->_lastMousePoint = RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY); + RGFW_mousePosCallback(_RGFW.root, RGFW_POINT(E->touches[i].targetX, E->touches[i].targetY), _RGFW.root->event.vector); + RGFW_mouseButtonCallback(_RGFW.root, RGFW_mouseLeft, 0, 0); } return EM_TRUE; } EM_BOOL Emscripten_on_touchcancel(int eventType, const EmscriptenTouchEvent* E, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); return EM_TRUE; } -u32 RGFW_WASMPhysicalToRGFW(u32 hash); +EM_BOOL Emscripten_on_gamepad(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); -void EMSCRIPTEN_KEEPALIVE RGFW_handleKeyEvent(char* key, char* code, RGFW_bool press) { - const char* iCode = code; + if (gamepadEvent->index >= 4) + return 0; - u32 hash = 0; - while(*iCode) hash = ((hash ^ 0x7E057D79U) << 3) ^ (unsigned int)*iCode++; - - u32 physicalKey = RGFW_WASMPhysicalToRGFW(hash); - - u8 mappedKey = (u8)(*((u32*)key)); - - if (*((u16*)key) != mappedKey) { - mappedKey = 0; - if (*((u32*)key) == *((u32*)"Tab")) mappedKey = RGFW_tab; + size_t i = gamepadEvent->index; + if (gamepadEvent->connected) { + RGFW_STRNCPY(RGFW_gamepads_name[gamepadEvent->index], gamepadEvent->id, sizeof(RGFW_gamepads_name[gamepadEvent->index]) - 1); + RGFW_gamepads_name[gamepadEvent->index][sizeof(RGFW_gamepads_name[gamepadEvent->index]) - 1] = '\0'; + RGFW_gamepads_type[i] = RGFW_gamepadUnknown; + if (RGFW_STRSTR(RGFW_gamepads_name[i], "Microsoft") || RGFW_STRSTR(RGFW_gamepads_name[i], "X-Box")) + RGFW_gamepads_type[i] = RGFW_gamepadMicrosoft; + else if (RGFW_STRSTR(RGFW_gamepads_name[i], "PlayStation") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS3") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS4") || RGFW_STRSTR(RGFW_gamepads_name[i], "PS5")) + RGFW_gamepads_type[i] = RGFW_gamepadSony; + else if (RGFW_STRSTR(RGFW_gamepads_name[i], "Nintendo")) + RGFW_gamepads_type[i] = RGFW_gamepadNintendo; + else if (RGFW_STRSTR(RGFW_gamepads_name[i], "Logitech")) + RGFW_gamepads_type[i] = RGFW_gamepadLogitech; + RGFW_gamepadCount++; + } else { + RGFW_gamepadCount--; } - if (!(press ? (_RGFW->root->internal.enabledEvents & RGFW_keyPressedFlag) : (_RGFW->root->internal.enabledEvents & RGFW_keyReleasedFlag))) return; + RGFW_eventQueuePushEx(e.type = (RGFW_eventType)(gamepadEvent->connected ? RGFW_gamepadConnected : RGFW_gamepadConnected); + e.gamepad = (u16)gamepadEvent->index; + e._win = _RGFW.root); - RGFW_eventQueuePushEx(e.type = (RGFW_eventType)(press ? RGFW_keyPressed : RGFW_keyReleased); - e.key.value = (u8)physicalKey; - e.key.sym = (u8)mappedKey; - e.key.mod = _RGFW->root->internal.mod; - e.key.repeat = RGFW_window_isKeyDown(_RGFW->root, (u8)physicalKey); - e.common.win = _RGFW->root); + RGFW_gamepadCallback(_RGFW.root, gamepadEvent->index, gamepadEvent->connected); + RGFW_gamepads[gamepadEvent->index] = gamepadEvent->connected; - _RGFW->keyboard[physicalKey].prev = _RGFW->keyboard[physicalKey].current; - _RGFW->keyboard[physicalKey].current = press; - - RGFW_keyCallback(_RGFW->root, physicalKey, mappedKey, _RGFW->root->internal.mod, RGFW_window_isKeyDown(_RGFW->root, (u8)physicalKey), press); + return 1; /* The event was consumed by the callback handler */ } -void EMSCRIPTEN_KEEPALIVE RGFW_handleKeyMods(RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll) { - RGFW_updateKeyModsEx(_RGFW->root, capital, numlock, control, alt, shift, super, scroll); -} - -void EMSCRIPTEN_KEEPALIVE Emscripten_onDrop(size_t count) { - if (!(_RGFW->root->internal.flags & RGFW_windowAllowDND)) - return; - - if (!(_RGFW->root->internal.enabledEvents & RGFW_dataDropFlag)) return; - - RGFW_eventQueuePushEx(e.type = RGFW_dataDrop; - e.drop.count = count; - e.common.win = _RGFW->root); - - _RGFW->windowState.win = _RGFW->root; - _RGFW->windowState.dataDrop = RGFW_TRUE; - _RGFW->windowState.filesCount = count; - RGFW_dataDropCallback(_RGFW->root, _RGFW->files, count); -} - -void RGFW_stopCheckEvents(void) { - _RGFW->stopCheckEvents_bool = RGFW_TRUE; -} - -RGFW_bool RGFW_createSurfacePtr(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { - surface->data = data; - surface->w = w; - surface->h = h; - surface->format = format; - return RGFW_TRUE; -} - -void RGFW_window_blitSurface(RGFW_window* win, RGFW_surface* surface) { - /* TODO: Needs fixing. */ - RGFW_copyImageData(surface->data, surface->w, RGFW_MIN(win->h, surface->h), RGFW_formatRGBA8, surface->data, surface->format); - EM_ASM_({ - var data = Module.HEAPU8.slice($0, $0 + $1 * $2 * 4); - let context = document.getElementById("canvas").getContext("2d"); - let image = context.getImageData(0, 0, $1, $2); - image.data.set(data); - context.putImageData(image, 0, $4 - $2); - }, surface->data, surface->w, surface->h, RGFW_MIN(win->h, surface->w), RGFW_MIN(win->h, surface->h)); -} - -void RGFW_surface_freePtr(RGFW_surface* surface) { } - -void EMSCRIPTEN_KEEPALIVE RGFW_makeSetValue(size_t index, char* file) { - /* This seems like a terrible idea, don't replicate this unless you hate yourself or the OS */ - /* TODO: find a better way to do this - */ - RGFW_STRNCPY((char*)_RGFW->files[index], file, RGFW_MAX_PATH - 1); - _RGFW->files[index][RGFW_MAX_PATH - 1] = '\0'; -} - -#include -#include -#include -#include - -void EMSCRIPTEN_KEEPALIVE RGFW_mkdir(char* name) { mkdir(name, 0755); } - -void EMSCRIPTEN_KEEPALIVE RGFW_writeFile(const char *path, const char *data, size_t len) { - FILE* file = fopen(path, "w+"); - if (file == NULL) - return; - - fwrite(data, sizeof(char), len, file); - fclose(file); -} - -void RGFW_initKeycodesPlatform(void) { - _RGFW->keycodes[DOM_VK_BACK_QUOTE] = RGFW_backtick; - _RGFW->keycodes[DOM_VK_0] = RGFW_0; - _RGFW->keycodes[DOM_VK_1] = RGFW_1; - _RGFW->keycodes[DOM_VK_2] = RGFW_2; - _RGFW->keycodes[DOM_VK_3] = RGFW_3; - _RGFW->keycodes[DOM_VK_4] = RGFW_4; - _RGFW->keycodes[DOM_VK_5] = RGFW_5; - _RGFW->keycodes[DOM_VK_6] = RGFW_6; - _RGFW->keycodes[DOM_VK_7] = RGFW_7; - _RGFW->keycodes[DOM_VK_8] = RGFW_8; - _RGFW->keycodes[DOM_VK_9] = RGFW_9; - _RGFW->keycodes[DOM_VK_SPACE] = RGFW_space; - _RGFW->keycodes[DOM_VK_A] = RGFW_a; - _RGFW->keycodes[DOM_VK_B] = RGFW_b; - _RGFW->keycodes[DOM_VK_C] = RGFW_c; - _RGFW->keycodes[DOM_VK_D] = RGFW_d; - _RGFW->keycodes[DOM_VK_E] = RGFW_e; - _RGFW->keycodes[DOM_VK_F] = RGFW_f; - _RGFW->keycodes[DOM_VK_G] = RGFW_g; - _RGFW->keycodes[DOM_VK_H] = RGFW_h; - _RGFW->keycodes[DOM_VK_I] = RGFW_i; - _RGFW->keycodes[DOM_VK_J] = RGFW_j; - _RGFW->keycodes[DOM_VK_K] = RGFW_k; - _RGFW->keycodes[DOM_VK_L] = RGFW_l; - _RGFW->keycodes[DOM_VK_M] = RGFW_m; - _RGFW->keycodes[DOM_VK_N] = RGFW_n; - _RGFW->keycodes[DOM_VK_O] = RGFW_o; - _RGFW->keycodes[DOM_VK_P] = RGFW_p; - _RGFW->keycodes[DOM_VK_Q] = RGFW_q; - _RGFW->keycodes[DOM_VK_R] = RGFW_r; - _RGFW->keycodes[DOM_VK_S] = RGFW_s; - _RGFW->keycodes[DOM_VK_T] = RGFW_t; - _RGFW->keycodes[DOM_VK_U] = RGFW_u; - _RGFW->keycodes[DOM_VK_V] = RGFW_v; - _RGFW->keycodes[DOM_VK_W] = RGFW_w; - _RGFW->keycodes[DOM_VK_X] = RGFW_x; - _RGFW->keycodes[DOM_VK_Y] = RGFW_y; - _RGFW->keycodes[DOM_VK_Z] = RGFW_z; - _RGFW->keycodes[DOM_VK_PERIOD] = RGFW_period; - _RGFW->keycodes[DOM_VK_COMMA] = RGFW_comma; - _RGFW->keycodes[DOM_VK_SLASH] = RGFW_slash; - _RGFW->keycodes[DOM_VK_OPEN_BRACKET] = RGFW_bracket; - _RGFW->keycodes[DOM_VK_CLOSE_BRACKET] = RGFW_closeBracket; - _RGFW->keycodes[DOM_VK_SEMICOLON] = RGFW_semicolon; - _RGFW->keycodes[DOM_VK_QUOTE] = RGFW_apostrophe; - _RGFW->keycodes[DOM_VK_BACK_SLASH] = RGFW_backSlash; - _RGFW->keycodes[DOM_VK_RETURN] = RGFW_return; - _RGFW->keycodes[DOM_VK_DELETE] = RGFW_delete; - _RGFW->keycodes[DOM_VK_NUM_LOCK] = RGFW_numLock; - _RGFW->keycodes[DOM_VK_DIVIDE] = RGFW_kpSlash; - _RGFW->keycodes[DOM_VK_MULTIPLY] = RGFW_kpMultiply; - _RGFW->keycodes[DOM_VK_SUBTRACT] = RGFW_kpMinus; - _RGFW->keycodes[DOM_VK_NUMPAD1] = RGFW_kp1; - _RGFW->keycodes[DOM_VK_NUMPAD2] = RGFW_kp2; - _RGFW->keycodes[DOM_VK_NUMPAD3] = RGFW_kp3; - _RGFW->keycodes[DOM_VK_NUMPAD4] = RGFW_kp4; - _RGFW->keycodes[DOM_VK_NUMPAD5] = RGFW_kp5; - _RGFW->keycodes[DOM_VK_NUMPAD6] = RGFW_kp6; - _RGFW->keycodes[DOM_VK_NUMPAD9] = RGFW_kp9; - _RGFW->keycodes[DOM_VK_NUMPAD0] = RGFW_kp0; - _RGFW->keycodes[DOM_VK_DECIMAL] = RGFW_kpPeriod; - _RGFW->keycodes[DOM_VK_RETURN] = RGFW_kpReturn; - _RGFW->keycodes[DOM_VK_HYPHEN_MINUS] = RGFW_minus; - _RGFW->keycodes[DOM_VK_EQUALS] = RGFW_equals; - _RGFW->keycodes[DOM_VK_BACK_SPACE] = RGFW_backSpace; - _RGFW->keycodes[DOM_VK_TAB] = RGFW_tab; - _RGFW->keycodes[DOM_VK_CAPS_LOCK] = RGFW_capsLock; - _RGFW->keycodes[DOM_VK_SHIFT] = RGFW_shiftL; - _RGFW->keycodes[DOM_VK_CONTROL] = RGFW_controlL; - _RGFW->keycodes[DOM_VK_ALT] = RGFW_altL; - _RGFW->keycodes[DOM_VK_META] = RGFW_superL; - _RGFW->keycodes[DOM_VK_F1] = RGFW_F1; - _RGFW->keycodes[DOM_VK_F2] = RGFW_F2; - _RGFW->keycodes[DOM_VK_F3] = RGFW_F3; - _RGFW->keycodes[DOM_VK_F4] = RGFW_F4; - _RGFW->keycodes[DOM_VK_F5] = RGFW_F5; - _RGFW->keycodes[DOM_VK_F6] = RGFW_F6; - _RGFW->keycodes[DOM_VK_F7] = RGFW_F7; - _RGFW->keycodes[DOM_VK_F8] = RGFW_F8; - _RGFW->keycodes[DOM_VK_F9] = RGFW_F9; - _RGFW->keycodes[DOM_VK_F10] = RGFW_F10; - _RGFW->keycodes[DOM_VK_F11] = RGFW_F11; - _RGFW->keycodes[DOM_VK_F12] = RGFW_F12; - _RGFW->keycodes[DOM_VK_UP] = RGFW_up; - _RGFW->keycodes[DOM_VK_DOWN] = RGFW_down; - _RGFW->keycodes[DOM_VK_LEFT] = RGFW_left; - _RGFW->keycodes[DOM_VK_RIGHT] = RGFW_right; - _RGFW->keycodes[DOM_VK_INSERT] = RGFW_insert; - _RGFW->keycodes[DOM_VK_END] = RGFW_end; - _RGFW->keycodes[DOM_VK_PAGE_UP] = RGFW_pageUp; - _RGFW->keycodes[DOM_VK_PAGE_DOWN] = RGFW_pageDown; - _RGFW->keycodes[DOM_VK_ESCAPE] = RGFW_escape; - _RGFW->keycodes[DOM_VK_HOME] = RGFW_home; - _RGFW->keycodes[DOM_VK_SCROLL_LOCK] = RGFW_scrollLock; - _RGFW->keycodes[DOM_VK_PRINTSCREEN] = RGFW_printScreen; - _RGFW->keycodes[DOM_VK_PAUSE] = RGFW_pause; - _RGFW->keycodes[DOM_VK_F13] = RGFW_F13; - _RGFW->keycodes[DOM_VK_F14] = RGFW_F14; - _RGFW->keycodes[DOM_VK_F15] = RGFW_F15; - _RGFW->keycodes[DOM_VK_F16] = RGFW_F16; - _RGFW->keycodes[DOM_VK_F17] = RGFW_F17; - _RGFW->keycodes[DOM_VK_F18] = RGFW_F18; - _RGFW->keycodes[DOM_VK_F19] = RGFW_F19; - _RGFW->keycodes[DOM_VK_F20] = RGFW_F20; - _RGFW->keycodes[DOM_VK_F21] = RGFW_F21; - _RGFW->keycodes[DOM_VK_F22] = RGFW_F22; - _RGFW->keycodes[DOM_VK_F23] = RGFW_F23; - _RGFW->keycodes[DOM_VK_F24] = RGFW_F24; -} - -i32 RGFW_initPlatform(void) { return 0; } - -RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, RGFW_window* win) { - emscripten_set_canvas_element_size("#canvas", win->w, win->h); - emscripten_set_window_title(name); - - /* load callbacks */ - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_resize); - emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, EM_FALSE, Emscripten_on_fullscreenchange); - emscripten_set_mousemove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousemove); - emscripten_set_touchstart_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchstart); - emscripten_set_touchend_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchend); - emscripten_set_touchmove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchmove); - emscripten_set_touchcancel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchcancel); - emscripten_set_mousedown_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousedown); - emscripten_set_mouseup_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mouseup); - emscripten_set_wheel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_wheel); - emscripten_set_focusin_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusin); - emscripten_set_focusout_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusout); - - if (flags & RGFW_windowAllowDND) { - win->internal.flags |= RGFW_windowAllowDND; - } - - EM_ASM({ - window.addEventListener("keydown", - (event) => { - var key = stringToNewUTF8(event.key); var code = stringToNewUTF8(event.code); - Module._RGFW_handleKeyMods(event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("Control"), event.getModifierState("Alt"), event.getModifierState("Shift"), event.getModifierState("Meta"), event.getModifierState("ScrollLock")); - Module._RGFW_handleKeyEvent(key, code, 1); - _free(key); _free(code); - }, - true); - window.addEventListener("keyup", - (event) => { - var key = stringToNewUTF8(event.key); var code = stringToNewUTF8(event.code); - Module._RGFW_handleKeyMods(event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("Control"), event.getModifierState("Alt"), event.getModifierState("Shift"), event.getModifierState("Meta"), event.getModifierState("ScrollLock")); - Module._RGFW_handleKeyEvent(key, code, 0); - _free(key); _free(code); - }, - true); - }); - - EM_ASM({ - var canvas = document.getElementById('canvas'); - canvas.addEventListener('drop', function(e) { - e.preventDefault(); - if (e.dataTransfer.file < 0) - return; - - var filenamesArray = []; - var count = e.dataTransfer.files.length; - - /* Read and save the files to emscripten's files */ - var drop_dir = '.rgfw_dropped_files'; - Module._RGFW_mkdir(drop_dir); - - for (var i = 0; i < count; i++) { - var file = e.dataTransfer.files[i]; - - var path = '/' + drop_dir + '/' + file.name.replace("//", '_'); - var reader = new FileReader(); - - reader.onloadend = (e) => { - if (reader.readyState != 2) { - out('failed to read dropped file: '+file.name+': '+reader.error); - } - else { - var data = e.target.result; - - Module._RGFW_writeFile(path, new Uint8Array(data), file.size); - } - }; - - reader.readAsArrayBuffer(file); - /* This works weird on modern OpenGL */ - var filename = stringToNewUTF8(path); - - filenamesArray.push(filename); - - Module._RGFW_makeSetValue(i, filename); - } - - Module._Emscripten_onDrop(count); - - for (var i = 0; i < count; ++i) { - _free(filenamesArray[i]); - } - }, true); - - canvas.addEventListener('dragover', function(e) { e.preventDefault(); return false; }, true); - }); - - return win; -} - -u8 RGFW_rgfwToKeyChar(u32 rgfw_keycode) { - return (u8)rgfw_keycode; /* TODO */ -} - -void RGFW_pollEvents(void) { - emscripten_sleep(0); - RGFW_resetPrevState(); -} - -void RGFW_window_resize(RGFW_window* win, i32 w, i32 h) { - RGFW_UNUSED(win); - emscripten_set_canvas_element_size("#canvas", w, h); -} - -/* NOTE: I don't know if this is possible */ -void RGFW_window_moveMouse(RGFW_window* win, i32 x, i32 y) { RGFW_UNUSED(win); RGFW_UNUSED(x); RGFW_UNUSED(y); } -/* this one might be possible but it looks iffy */ -RGFW_mouse* RGFW_loadMouse(u8* data, i32 w, i32 h, RGFW_format format) { RGFW_UNUSED(data); RGFW_UNUSED(w); RGFW_UNUSED(h); RGFW_UNUSED(format); return NULL; } - -void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { RGFW_UNUSED(win); RGFW_UNUSED(mouse); } -void RGFW_freeMouse(RGFW_mouse* mouse) { RGFW_UNUSED(mouse); } - -RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { - static const char cursors[16][16] = { - "default", "default", "text", "crosshair", - "pointer", "ew-resize", "ns-resize", "nwse-resize", "nesw-resize", - "move", "not-allowed" - }; - - RGFW_UNUSED(win); - EM_ASM( { document.getElementById("canvas").style.cursor = UTF8ToString($0); }, cursors[mouse]); - return RGFW_TRUE; -} - -RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { - return RGFW_window_setMouseStandard(win, RGFW_mouseNormal); -} - -void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show) { - RGFW_window_showMouseFlags(win, show); - if (show) - RGFW_window_setMouseDefault(win); - else - EM_ASM(document.getElementById('canvas').style.cursor = 'none';); -} - -RGFW_bool RGFW_getGlobalMouse(i32* x, i32* y) { - if(x) *x = EM_ASM_INT({ - return window.mouseX || 0; - }); - if (y) *y = EM_ASM_INT({ - return window.mouseY || 0; - }); - return RGFW_TRUE; -} - -void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool 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); -} - - -RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { - RGFW_UNUSED(str); RGFW_UNUSED(strCapacity); - /* - placeholder code for later - I'm not sure if this is possible do the the async stuff - */ - return 0; -} - -#ifdef RGFW_OPENGL -RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { - win->src.ctx.native = ctx; - win->src.gfxType = RGFW_gfxNativeOpenGL; - - EmscriptenWebGLContextAttributes attrs; - attrs.alpha = hints->alpha; - attrs.depth = hints->depth; - attrs.stencil = hints->stencil; - attrs.antialias = hints->samples; - attrs.premultipliedAlpha = EM_TRUE; - attrs.preserveDrawingBuffer = EM_FALSE; - - if (hints->doubleBuffer == 0) - attrs.renderViaOffscreenBackBuffer = 0; - else - attrs.renderViaOffscreenBackBuffer = hints->auxBuffers; - - attrs.failIfMajorPerformanceCaveat = EM_FALSE; - attrs.majorVersion = (hints->major == 0) ? 1 : hints->major; - attrs.minorVersion = hints->minor; - - attrs.enableExtensionsByDefault = EM_TRUE; - attrs.explicitSwapControl = EM_TRUE; - - emscripten_webgl_init_context_attributes(&attrs); - win->src.ctx.native->ctx = emscripten_webgl_create_context("#canvas", &attrs); - emscripten_webgl_make_context_current(win->src.ctx.native->ctx); - - #ifdef LEGACY_GL_EMULATION - EM_ASM("Module.useWebGL = true; GLImmediate.init();"); - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context initalized."); - #endif - return RGFW_TRUE; -} - -void RGFW_window_deleteContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx) { - emscripten_webgl_destroy_context(ctx->ctx); - win->src.ctx.native->ctx = 0; - RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, "OpenGL context freed."); -} - -void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win) { - if (win) RGFW_ASSERT(win->src.ctx.native); - if (win == NULL) - emscripten_webgl_make_context_current(0); - else - emscripten_webgl_make_context_current(win->src.ctx.native->ctx); -} - -void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { - RGFW_ASSERT(win && win->src.ctx.native); - emscripten_webgl_commit_frame(); -} -void* RGFW_getCurrentContext_OpenGL(void) { return (void*)emscripten_webgl_get_current_context(); } - -RGFW_bool RGFW_extensionSupportedPlatform_OpenGL(const char* extension, size_t len) { - return EM_ASM_INT({ - var ext = UTF8ToString($0, $1); - var canvas = document.querySelector('canvas'); - var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); - if (!gl) return 0; - - var supported = gl.getSupportedExtensions(); - return supported && supported.includes(ext) ? 1 : 0; - }, extension, len); - return RGFW_FALSE; -} - -RGFW_proc RGFW_getProcAddress_OpenGL(const char* procname) { - return (RGFW_proc)emscripten_webgl_get_proc_address(procname); - return NULL; -} - -#endif - -void RGFW_window_swapInterval_OpenGL(RGFW_window* win, i32 swapInterval) { RGFW_UNUSED(win); RGFW_UNUSED(swapInterval); } - -void RGFW_deinitPlatform(void) { } - -void RGFW_window_closePlatform(RGFW_window* win) { } - -int RGFW_innerWidth(void) { return EM_ASM_INT({ return window.innerWidth; }); } -int RGFW_innerHeight(void) { return EM_ASM_INT({ return window.innerHeight; }); } - -void RGFW_releaseCursor(RGFW_window* win) { - RGFW_UNUSED(win); - emscripten_exit_pointerlock(); -} - -void RGFW_captureCursor(RGFW_window* win) { - RGFW_UNUSED(win); - emscripten_request_pointerlock("#canvas", 1); -} - - -void RGFW_window_setName(RGFW_window* win, const char* name) { - RGFW_UNUSED(win); - emscripten_set_window_title(name); -} - -void RGFW_window_maximize(RGFW_window* win) { - RGFW_ASSERT(win != NULL); - - RGFW_monitor mon = RGFW_window_getMonitor(win); - RGFW_window_move(win, 0, 0); - RGFW_window_resize(win, mon.mode.w, mon.mode.h); -} - -void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { - RGFW_ASSERT(win != NULL); - if (fullscreen) { - win->internal.flags |= RGFW_windowFullscreen; - EM_ASM( Module.requestFullscreen(false, true); ); - return; - } - win->internal.flags &= ~(u32)RGFW_windowFullscreen; - EM_ASM( Module.exitFullscreen(false, true); ); -} - -void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { - RGFW_UNUSED(win); - EM_ASM({ - var element = document.getElementById("canvas"); - if (element) - element.style.opacity = $1; - }, "elementId", opacity); -} - -#ifdef RGFW_WEBGPU -WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance instance) { - WGPUSurfaceDescriptor surfaceDesc = {0}; - WGPUEmscriptenSurfaceSourceCanvasHTMLSelector canvasDesc = {0}; - canvasDesc.chain.sType = WGPUSType_EmscriptenSurfaceSourceCanvasHTMLSelector; - canvasDesc.selector = (WGPUStringView){.data = "#canvas", .length = 7}; - - surfaceDesc.nextInChain = &canvasDesc.chain; - return wgpuInstanceCreateSurface(instance, &surfaceDesc); -} -#endif - -u32 RGFW_WASMPhysicalToRGFW(u32 hash) { +u32 RGFW_wASMPhysicalToRGFW(u32 hash) { switch(hash) { /* 0x0000 */ case 0x67243A2DU /* Escape */: return RGFW_escape; /* 0x0001 */ case 0x67251058U /* Digit0 */: return RGFW_0; /* 0x0002 */ @@ -13493,7 +10378,7 @@ u32 RGFW_WASMPhysicalToRGFW(u32 hash) { case 0x672FFAD4U /* Period */: return RGFW_period; /* 0x0034 */ case 0x92E0A438U /* Slash */: return RGFW_slash; /* 0x0035 */ case 0xC5A6BF7CU /* ShiftRight */: return RGFW_shiftR; - case 0x5D64DA91U /* NumpadMultiply */: return RGFW_kpMultiply; + case 0x5D64DA91U /* NumpadMultiply */: return RGFW_multiply; case 0xC914958CU /* AltLeft */: return RGFW_altL; /* 0x0038 */ case 0x92E09CB5U /* Space */: return RGFW_space; /* 0x0039 */ case 0xB8FAE73BU /* CapsLock */: return RGFW_capsLock; /* 0x003A */ @@ -13507,32 +10392,21 @@ u32 RGFW_WASMPhysicalToRGFW(u32 hash) { case 0x7174B780U /* F8 */: return RGFW_F8; /* 0x0042 */ case 0x7174B781U /* F9 */: return RGFW_F9; /* 0x0043 */ case 0x7B8E57B0U /* F10 */: return RGFW_F10; /* 0x0044 */ - case 0xC925FCDFU /* Numpad7 */: return RGFW_kpMultiply; /* 0x0047 */ - case 0xC925FCD0U /* Numpad8 */: return RGFW_kp8; /* 0x0048 */ - case 0xC925FCD1U /* Numpad9 */: return RGFW_kp9; /* 0x0049 */ + case 0xC925FCDFU /* Numpad7 */: return RGFW_multiply; /* 0x0047 */ + case 0xC925FCD0U /* Numpad8 */: return RGFW_KP_8; /* 0x0048 */ + case 0xC925FCD1U /* Numpad9 */: return RGFW_KP_9; /* 0x0049 */ case 0x5EA3E8A4U /* NumpadSubtract */: return RGFW_minus; /* 0x004A */ - case 0xC925FCDCU /* Numpad4 */: return RGFW_kp4; /* 0x004B */ - case 0xC925FCDDU /* Numpad5 */: return RGFW_kp5; /* 0x004C */ - case 0xC925FCDEU /* Numpad6 */: return RGFW_kp6; /* 0x004D */ - case 0xC925FCD9U /* Numpad1 */: return RGFW_kp1; /* 0x004F */ - case 0xC925FCDAU /* Numpad2 */: return RGFW_kp2; /* 0x0050 */ - case 0xC925FCDBU /* Numpad3 */: return RGFW_kp3; /* 0x0051 */ - case 0xC925FCD8U /* Numpad0 */: return RGFW_kp0; /* 0x0052 */ + case 0xC925FCDCU /* Numpad4 */: return RGFW_KP_4; /* 0x004B */ + case 0xC925FCDDU /* Numpad5 */: return RGFW_KP_5; /* 0x004C */ + case 0xC925FCDEU /* Numpad6 */: return RGFW_KP_6; /* 0x004D */ + case 0xC925FCD9U /* Numpad1 */: return RGFW_KP_1; /* 0x004F */ + case 0xC925FCDAU /* Numpad2 */: return RGFW_KP_2; /* 0x0050 */ + case 0xC925FCDBU /* Numpad3 */: return RGFW_KP_3; /* 0x0051 */ + case 0xC925FCD8U /* Numpad0 */: return RGFW_KP_0; /* 0x0052 */ case 0x95852DACU /* NumpadDecimal */: return RGFW_period; /* 0x0053 */ case 0x7B8E57B1U /* F11 */: return RGFW_F11; /* 0x0057 */ case 0x7B8E57B2U /* F12 */: return RGFW_F12; /* 0x0058 */ - case 0x7B8E57B3U /* F13 */: return DOM_PK_F13; /* 0x0064 */ - case 0x7B8E57B4U /* F14 */: return DOM_PK_F14; /* 0x0065 */ - case 0x7B8E57B5U /* F15 */: return DOM_PK_F15; /* 0x0066 */ - case 0x7B8E57B6U /* F16 */: return DOM_PK_F16; /* 0x0067 */ - case 0x7B8E57B7U /* F17 */: return DOM_PK_F17; /* 0x0068 */ - case 0x7B8E57B8U /* F18 */: return DOM_PK_F18; /* 0x0069 */ - case 0x7B8E57B9U /* F19 */: return DOM_PK_F19; /* 0x006A */ - case 0x7B8E57A8U /* F20 */: return DOM_PK_F20; /* 0x006B */ - case 0x7B8E57A9U /* F21 */: return DOM_PK_F21; /* 0x006C */ - case 0x7B8E57AAU /* F22 */: return DOM_PK_F22; /* 0x006D */ - case 0x7B8E57ABU /* F23 */: return DOM_PK_F23; /* 0x006E */ - case 0x7393FBACU /* NumpadEqual */: return RGFW_kpReturn; + case 0x7393FBACU /* NumpadEqual */: return RGFW_KP_Return; case 0xB88EBF7CU /* AltRight */: return RGFW_altR; /* 0xE038 */ case 0xC925873BU /* NumLock */: return RGFW_numLock; /* 0xE045 */ case 0x2C595F45U /* Home */: return RGFW_home; /* 0xE047 */ @@ -13547,28 +10421,602 @@ u32 RGFW_WASMPhysicalToRGFW(u32 hash) { case 0x6725C50DU /* Delete */: return RGFW_delete; /* 0xE053 */ case 0x6723658CU /* OSLeft */: return RGFW_superL; /* 0xE05B */ case 0x39643F7CU /* MetaRight */: return RGFW_superR; /* 0xE05C */ - case 0x380B9C8CU /* NumpadAdd */: return DOM_PK_NUMPAD_ADD; /* 0x004E */ - default: return DOM_PK_UNKNOWN; } return 0; } +void EMSCRIPTEN_KEEPALIVE RGFW_handleKeyEvent(char* key, char* code, RGFW_bool press) { + const char* iCode = code; + + u32 hash = 0; + while(*iCode) hash = ((hash ^ 0x7E057D79U) << 3) ^ (unsigned int)*iCode++; + + u32 physicalKey = RGFW_wASMPhysicalToRGFW(hash); + + u8 mappedKey = (u8)(*((u32*)key)); + + if (*((u16*)key) != mappedKey) { + mappedKey = 0; + if (*((u32*)key) == *((u32*)"Tab")) mappedKey = RGFW_tab; + } + + RGFW_eventQueuePushEx(e.type = (RGFW_eventType)(press ? RGFW_keyPressed : RGFW_keyReleased); + e.key = (u8)physicalKey; + e.keyChar = (u8)mappedKey; + e.keyMod = _RGFW.root->event.keyMod; + e._win = _RGFW.root); + + RGFW_keyboard[physicalKey].prev = RGFW_keyboard[physicalKey].current; + RGFW_keyboard[physicalKey].current = press; + + RGFW_keyCallback(_RGFW.root, physicalKey, mappedKey, _RGFW.root->event.keyMod, press); +} + +void EMSCRIPTEN_KEEPALIVE RGFW_handleKeyMods(RGFW_bool capital, RGFW_bool numlock, RGFW_bool control, RGFW_bool alt, RGFW_bool shift, RGFW_bool super, RGFW_bool scroll) { + RGFW_updateKeyModsPro(_RGFW.root, capital, numlock, control, alt, shift, super, scroll); +} + +void EMSCRIPTEN_KEEPALIVE Emscripten_onDrop(size_t count) { + if (!(_RGFW.root->_flags & RGFW_windowAllowDND)) + return; + + _RGFW.root->event.droppedFilesCount = count; + RGFW_eventQueuePushEx(e.type = RGFW_DND; + e.droppedFilesCount = count; + e._win = _RGFW.root); + RGFW_dndCallback(_RGFW.root, _RGFW.root->event.droppedFiles, count); +} + +RGFW_bool 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); + if (waitMS == 0) return; + + u32 start = (u32)(((u64)RGFW_getTimeNS()) / 1e+6); + + while ((_RGFW.eventLen == 0) && RGFW_stopCheckEvents_bool == RGFW_FALSE && (RGFW_getTimeNS() / 1e+6) - start < waitMS) + emscripten_sleep(0); + + RGFW_stopCheckEvents_bool = RGFW_FALSE; +} + +void RGFW_window_initBufferPtr(RGFW_window* win, u8* buffer, RGFW_area area){ + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + win->buffer = buffer; + win->bufferSize = area; + #ifdef RGFW_OSMESA + win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); + OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, area.w, area.h); + OSMesaPixelStore(OSMESA_Y_UP, 0); + #endif + #else + RGFW_UNUSED(win); RGFW_UNUSED(buffer); RGFW_UNUSED(area); /*!< if buffer rendering is not being used */ + #endif +} + +void EMSCRIPTEN_KEEPALIVE RGFW_makeSetValue(size_t index, char* file) { + /* This seems like a terrible idea, don't replicate this unless you hate yourself or the OS */ + /* TODO: find a better way to do this + */ + RGFW_STRNCPY((char*)_RGFW.root->event.droppedFiles[index], file, RGFW_MAX_PATH - 1); + _RGFW.root->event.droppedFiles[index][RGFW_MAX_PATH - 1] = '\0'; +} + +#include +#include +#include +#include + +void EMSCRIPTEN_KEEPALIVE RGFW_mkdir(char* name) { mkdir(name, 0755); } + +void EMSCRIPTEN_KEEPALIVE RGFW_writeFile(const char *path, const char *data, size_t len) { + FILE* file = fopen(path, "w+"); + if (file == NULL) + return; + + fwrite(data, sizeof(char), len, file); + fclose(file); +} + +void RGFW_window_initOpenGL(RGFW_window* win) { +#if defined(RGFW_OPENGL) && !defined(RGFW_WEBGPU) && !defined(RGFW_OSMESA) && !defined(RGFW_BUFFER) + EmscriptenWebGLContextAttributes attrs; + attrs.alpha = RGFW_GL_HINTS[RGFW_glDepth]; + attrs.depth = RGFW_GL_HINTS[RGFW_glAlpha]; + attrs.stencil = RGFW_GL_HINTS[RGFW_glStencil]; + attrs.antialias = RGFW_GL_HINTS[RGFW_glSamples]; + attrs.premultipliedAlpha = EM_TRUE; + attrs.preserveDrawingBuffer = EM_FALSE; + + if (RGFW_GL_HINTS[RGFW_glDoubleBuffer] == 0) + attrs.renderViaOffscreenBackBuffer = 0; + else + attrs.renderViaOffscreenBackBuffer = RGFW_GL_HINTS[RGFW_glAuxBuffers]; + + attrs.failIfMajorPerformanceCaveat = EM_FALSE; + attrs.majorVersion = (RGFW_GL_HINTS[RGFW_glMajor] == 0) ? 1 : RGFW_GL_HINTS[RGFW_glMajor]; + attrs.minorVersion = RGFW_GL_HINTS[RGFW_glMinor]; + + 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();"); + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context initalized"); + #endif + glViewport(0, 0, win->r.w, win->r.h); +#endif +} + +void RGFW_window_freeOpenGL(RGFW_window* win) { +#if defined(RGFW_OPENGL) && !defined(RGFW_WEBGPU) && !defined(RGFW_OSMESA) && !defined(RGFW_OSMESA) + if (win->src.ctx == 0) return; + emscripten_webgl_destroy_context(win->src.ctx); + win->src.ctx = 0; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoOpenGL, RGFW_DEBUG_CTX(win, 0), "opengl context freed"); +#elif defined(RGFW_OPENGL) && defined(RGFW_OSMESA) + if(win->src.ctx == 0) return; + OSMesaDestroyContext(win->src.ctx); + win->src.ctx = 0; +#else + RGFW_UNUSED(win); +#endif +} + +i32 RGFW_init(void) { +#if defined(RGFW_C89) || defined(__cplusplus) + if (_RGFW_init) return 0; + _RGFW_init = RGFW_TRUE; + _RGFW.root = NULL; _RGFW.current = NULL; _RGFW.windowCount = -2; _RGFW.eventLen = 0; _RGFW.eventIndex = 0; +#endif + + _RGFW.windowCount = 0; + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context initialized"); + return 0; +} + +RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowFlags flags, RGFW_window* win) { + RGFW_window_basic_init(win, rect, flags); + RGFW_window_initOpenGL(win); + + #if defined(RGFW_WEBGPU) + win->src.ctx = wgpuCreateInstance(NULL); + win->src.device = emscripten_webgpu_get_device(); + win->src.queue = wgpuDeviceGetQueue(win->src.device); + #endif + + emscripten_set_canvas_element_size("#canvas", rect.w, rect.h); + emscripten_set_window_title(name); + + /* load callbacks */ + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_resize); + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, EM_FALSE, Emscripten_on_fullscreenchange); + emscripten_set_mousemove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousemove); + emscripten_set_touchstart_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchstart); + emscripten_set_touchend_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchend); + emscripten_set_touchmove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchmove); + emscripten_set_touchcancel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchcancel); + emscripten_set_mousedown_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousedown); + emscripten_set_mouseup_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mouseup); + emscripten_set_wheel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_wheel); + emscripten_set_focusin_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusin); + emscripten_set_focusout_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusout); + emscripten_set_gamepadconnected_callback(NULL, 1, Emscripten_on_gamepad); + emscripten_set_gamepaddisconnected_callback(NULL, 1, Emscripten_on_gamepad); + + if (flags & RGFW_windowAllowDND) { + win->_flags |= RGFW_windowAllowDND; + } + + EM_ASM({ + window.addEventListener("keydown", + (event) => { + var key = stringToNewUTF8(event.key); var code = stringToNewUTF8(event.code); + Module._RGFW_handleKeyMods(event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("Control"), event.getModifierState("Alt"), event.getModifierState("Shift"), event.getModifierState("Meta"), event.getModifierState("ScrollLock")); + Module._RGFW_handleKeyEvent(key, code, 1); + _free(key); _free(code); + }, + true); + window.addEventListener("keyup", + (event) => { + var key = stringToNewUTF8(event.key); var code = stringToNewUTF8(event.code); + Module._RGFW_handleKeyMods(event.getModifierState("CapsLock"), event.getModifierState("NumLock"), event.getModifierState("Control"), event.getModifierState("Alt"), event.getModifierState("Shift"), event.getModifierState("Meta"), event.getModifierState("ScrollLock")); + Module._RGFW_handleKeyEvent(key, code, 0); + _free(key); _free(code); + }, + true); + }); + + EM_ASM({ + var canvas = document.getElementById('canvas'); + canvas.addEventListener('drop', function(e) { + e.preventDefault(); + if (e.dataTransfer.file < 0) + return; + + var filenamesArray = []; + var count = e.dataTransfer.files.length; + + /* Read and save the files to emscripten's files */ + var drop_dir = '.rgfw_dropped_files'; + Module._RGFW_mkdir(drop_dir); + + for (var i = 0; i < count; i++) { + var file = e.dataTransfer.files[i]; + + var path = '/' + drop_dir + '/' + file.name.replace("//", '_'); + var reader = new FileReader(); + + reader.onloadend = (e) => { + if (reader.readyState != 2) { + out('failed to read dropped file: '+file.name+': '+reader.error); + } + else { + var data = e.target.result; + + _RGFW_writeFile(path, new Uint8Array(data), file.size); + } + }; + + reader.readAsArrayBuffer(file); + /* This works weird on modern opengl */ + var filename = stringToNewUTF8(path); + + filenamesArray.push(filename); + + Module._RGFW_makeSetValue(i, filename); + } + + Module._Emscripten_onDrop(count); + + for (var i = 0; i < count; ++i) { + _free(filenamesArray[i]); + } + }, true); + + canvas.addEventListener('dragover', function(e) { e.preventDefault(); return false; }, true); + }); + + RGFW_window_setFlags(win, flags); + + if ((flags & RGFW_windowNoInitAPI) == 0) { + RGFW_window_initBuffer(win); + } + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a new window was created"); + return win; +} + +u8 RGFW_rgfwToKeyChar(u32 rgfw_keycode) { + return (u8)rgfw_keycode; /* TODO */ +} + +RGFW_event* RGFW_window_checkEvent(RGFW_window* win) { + if (win == NULL || ((win->_flags & RGFW_windowFreeOnClose) && (win->_flags & RGFW_EVENT_QUIT))) return NULL; + RGFW_event* ev = RGFW_window_checkEventCore(win); + if (ev) return ev; + + emscripten_sample_gamepad_data(); + /* check gamepads */ + int i; + for (i = 0; (i < emscripten_get_num_gamepads()) && (i < 4); i++) { + if (RGFW_gamepads[i] == 0) + continue; + EmscriptenGamepadEvent gamepadState; + + if (emscripten_get_gamepad_status(i, &gamepadState) != EMSCRIPTEN_RESULT_SUCCESS) + break; + + /* Register buttons data for every connected gamepad */ + int j; + for (j = 0; (j < gamepadState.numButtons) && (j < 16); j++) { + u32 map[] = { + RGFW_gamepadA, RGFW_gamepadB, RGFW_gamepadX, RGFW_gamepadY, + RGFW_gamepadL1, RGFW_gamepadR1, RGFW_gamepadL2, RGFW_gamepadR2, + RGFW_gamepadSelect, RGFW_gamepadStart, + RGFW_gamepadL3, RGFW_gamepadR3, + RGFW_gamepadUp, RGFW_gamepadDown, RGFW_gamepadLeft, RGFW_gamepadRight, RGFW_gamepadHome + }; + + + u32 button = map[j]; + if (button == 404) + continue; + + if (RGFW_gamepadPressed[i][button].current != gamepadState.digitalButton[j]) { + if (gamepadState.digitalButton[j]) + win->event.type = RGFW_gamepadButtonPressed; + else + win->event.type = RGFW_gamepadButtonReleased; + + win->event.gamepad = i; + win->event.button = map[j]; + + RGFW_gamepadPressed[i][button].prev = RGFW_gamepadPressed[i][button].current; + RGFW_gamepadPressed[i][button].current = gamepadState.digitalButton[j]; + + RGFW_gamepadButtonCallback(win, win->event.gamepad, win->event.button, gamepadState.digitalButton[j]); + return &win->event; + } + } + + for (j = 0; (j < gamepadState.numAxes) && (j < 4); j += 2) { + win->event.axisesCount = gamepadState.numAxes / 2; + if (RGFW_gamepadAxes[i][(size_t)(j / 2)].x != (i8)(gamepadState.axis[j] * 100.0f) || + RGFW_gamepadAxes[i][(size_t)(j / 2)].y != (i8)(gamepadState.axis[j + 1] * 100.0f) + ) { + + RGFW_gamepadAxes[i][(size_t)(j / 2)].x = (i8)(gamepadState.axis[j] * 100.0f); + RGFW_gamepadAxes[i][(size_t)(j / 2)].y = (i8)(gamepadState.axis[j + 1] * 100.0f); + win->event.axis[(size_t)(j / 2)] = RGFW_gamepadAxes[i][(size_t)(j / 2)]; + + win->event.type = RGFW_gamepadAxisMove; + win->event.gamepad = i; + win->event.whichAxis = j / 2; + + RGFW_gamepadAxisCallback(win, win->event.gamepad, win->event.axis, win->event.axisesCount, win->event.whichAxis); + return &win->event; + } + } + } + + return NULL; +} + +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 */ +RGFW_mouse* RGFW_loadMouse(u8* icon, RGFW_area a, i32 channels) { RGFW_UNUSED(channels); RGFW_UNUSED(a); RGFW_UNUSED(icon); return NULL; } + +void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { RGFW_UNUSED(win); RGFW_UNUSED(mouse); } +void RGFW_freeMouse(RGFW_mouse* mouse) { RGFW_UNUSED(mouse); } + +RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { + static const char cursors[16][16] = { + "default", "default", "text", "crosshair", + "pointer", "ew-resize", "ns-resize", "nwse-resize", "nesw-resize", + "move", "not-allowed" + }; + + RGFW_UNUSED(win); + EM_ASM( { document.getElementById("canvas").style.cursor = UTF8ToString($0); }, cursors[mouse]); + return RGFW_TRUE; +} + +RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { + return RGFW_window_setMouseStandard(win, RGFW_mouseNormal); +} + +void RGFW_window_showMouse(RGFW_window* win, RGFW_bool show) { + RGFW_window_showMouseFlags(win, 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, RGFW_bool 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); +} + + +RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { + RGFW_UNUSED(str); RGFW_UNUSED(strCapacity); + /* + placeholder code for later + I'm not sure if this is possible do the the async stuff + */ + return 0; +} + +void RGFW_window_swapBuffers_software(RGFW_window* win) { +#if defined(RGFW_OSMESA) + EM_ASM_({ + var data = Module.HEAPU8.slice($0, $0 + $1 * $2 * 4); + let context = document.getElementById("canvas").getContext("2d"); + let image = context.getImageData(0, 0, $1, $2); + image.data.set(data); + context.putImageData(image, 0, $4 - $2); + }, win->buffer, win->bufferSize.w, win->bufferSize.h, win->r.w, win->r.h); +#elif defined(RGFW_BUFFER) + EM_ASM_({ + var data = Module.HEAPU8.slice($0, $0 + $1 * $2 * 4); + let context = document.getElementById("canvas").getContext("2d"); + let image = context.getImageData(0, 0, $1, $2); + image.data.set(data); + context.putImageData(image, 0, 0); + }, win->buffer, win->bufferSize.w, win->bufferSize.h, win->r.w, win->r.h); + emscripten_sleep(0); +#else + RGFW_UNUSED(win); +#endif +} + +void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { +#if !defined(RGFW_WEBGPU) && !(defined(RGFW_OSMESA) || defined(RGFW_BUFFER)) + if (win == NULL) + emscripten_webgl_make_context_current(0); + else + emscripten_webgl_make_context_current(win->src.ctx); +#endif +} + + +void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { +#ifndef RGFW_WEBGPU + emscripten_webgl_commit_frame(); + +#endif + emscripten_sleep(0); +} + +#ifndef RGFW_WEBGPU +void* RGFW_getCurrent_OpenGL(void) { return (void*)emscripten_webgl_get_current_context(); } +#endif + +#ifndef RGFW_EGL +void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { RGFW_UNUSED(win); RGFW_UNUSED(swapInterval); } +#endif + +void RGFW_deinit(void) { _RGFW.windowCount = -1; RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoGlobal, RGFW_DEBUG_CTX(NULL, 0), "global context deinitialized"); } + +void RGFW_window_close(RGFW_window* win) { + if ((win->_flags & RGFW_windowNoInitAPI) == 0) RGFW_window_freeOpenGL(win); + + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + if ((win->_flags & RGFW_BUFFER_ALLOC)) + RGFW_FREE(win->buffer); + #endif + + RGFW_sendDebugInfo(RGFW_typeInfo, RGFW_infoWindow, RGFW_DEBUG_CTX(win, 0), "a window was freed"); + _RGFW.windowCount--; + if (_RGFW.windowCount == 0) RGFW_deinit(); + + RGFW_clipboard_switch(NULL); + RGFW_FREE(win->event.droppedFiles); + if ((win->_flags & RGFW_WINDOW_ALLOC)) { + RGFW_FREE(win); + win = NULL; + } +} + +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()); +} + +RGFW_bool RGFW_extensionSupportedPlatform(const char* extension, size_t len) { +#ifdef RGFW_OPENGL + return EM_ASM_INT({ + var ext = UTF8ToString($0, $1); + var canvas = document.querySelector('canvas'); + var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); + if (!gl) return 0; + + var supported = gl.getSupportedExtensions(); + return supported && supported.includes(ext) ? 1 : 0; + }, extension, len); +#else + return RGFW_FALSE; +#endif +} + +RGFW_proc RGFW_getProcAddress(const char* procname) { +#ifdef RGFW_OPENGL + return (RGFW_proc)emscripten_webgl_get_proc_address(procname); +#else + return NULL +#endif +} + +void RGFW_sleep(u64 milisecond) { + emscripten_sleep(milisecond); +} + +u64 RGFW_getTimerFreq(void) { return (u64)1000; } +u64 RGFW_getTimerValue(void) { return emscripten_get_now() * 1e+6; } + +void RGFW_releaseCursor(RGFW_window* win) { + RGFW_UNUSED(win); + emscripten_exit_pointerlock(); +} + +void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { + RGFW_UNUSED(win); RGFW_UNUSED(r); + + emscripten_request_pointerlock("#canvas", 1); +} + + +void RGFW_window_setName(RGFW_window* win, const char* name) { + RGFW_UNUSED(win); + emscripten_set_window_title(name); +} + +void RGFW_window_maximize(RGFW_window* win) { + RGFW_ASSERT(win != NULL); + + RGFW_area screen = RGFW_getScreenSize(); + RGFW_window_move(win, RGFW_POINT(0, 0)); + RGFW_window_resize(win, screen); +} + +void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { + RGFW_ASSERT(win != NULL); + if (fullscreen) { + win->_flags |= RGFW_windowFullscreen; + EM_ASM( Module.requestFullscreen(false, true); ); + return; + } + win->_flags &= ~(u32)RGFW_windowFullscreen; + EM_ASM( Module.exitFullscreen(false, true); ); +} + +void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { + RGFW_UNUSED(win); + EM_ASM({ + var element = document.getElementById("canvas"); + if (element) + element.style.opacity = $1; + }, "elementId", opacity); +} + /* unsupported functions */ void RGFW_window_focus(RGFW_window* win) { RGFW_UNUSED(win); } void RGFW_window_raise(RGFW_window* win) { RGFW_UNUSED(win); } RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { RGFW_UNUSED(mon); RGFW_UNUSED(mode); RGFW_UNUSED(request); return RGFW_FALSE; } RGFW_monitor* RGFW_getMonitors(size_t* len) { RGFW_UNUSED(len); return NULL; } RGFW_monitor RGFW_getPrimaryMonitor(void) { return (RGFW_monitor){}; } -void RGFW_window_move(RGFW_window* win, i32 x, i32 y) { RGFW_UNUSED(win); RGFW_UNUSED(x); RGFW_UNUSED(y); } -void RGFW_window_setAspectRatio(RGFW_window* win, i32 w, i32 h) { RGFW_UNUSED(win); RGFW_UNUSED(w); RGFW_UNUSED(h); } -void RGFW_window_setMinSize(RGFW_window* win, i32 w, i32 h) { RGFW_UNUSED(win); RGFW_UNUSED(w); RGFW_UNUSED(h); } -void RGFW_window_setMaxSize(RGFW_window* win, i32 w, i32 h) { RGFW_UNUSED(win); RGFW_UNUSED(w); RGFW_UNUSED(h); } +void RGFW_window_move(RGFW_window* win, RGFW_point v) { RGFW_UNUSED(win); RGFW_UNUSED(v); } +void RGFW_window_setAspectRatio(RGFW_window* win, RGFW_area a) { RGFW_UNUSED(win); RGFW_UNUSED(a); } +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_setFloating(RGFW_window* win, RGFW_bool floating) { RGFW_UNUSED(win); RGFW_UNUSED(floating); } void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { RGFW_UNUSED(win); RGFW_UNUSED(border); } -RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, RGFW_icon type) { RGFW_UNUSED(win); RGFW_UNUSED(data); RGFW_UNUSED(w); RGFW_UNUSED(h); RGFW_UNUSED(format); RGFW_UNUSED(type); return RGFW_FALSE; } +RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* icon, RGFW_area a, i32 channels, u8 type) { RGFW_UNUSED(win); RGFW_UNUSED(icon); RGFW_UNUSED(a); RGFW_UNUSED(channels); RGFW_UNUSED(type); return RGFW_FALSE; } void RGFW_window_hide(RGFW_window* win) { RGFW_UNUSED(win); } void RGFW_window_show(RGFW_window* win) {RGFW_UNUSED(win); } RGFW_bool RGFW_window_isHidden(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } @@ -13576,338 +11024,43 @@ RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { RGFW_UNUSED(win); return R RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } RGFW_bool RGFW_window_isFloating(RGFW_window* win) { RGFW_UNUSED(win); return RGFW_FALSE; } RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { RGFW_UNUSED(win); return (RGFW_monitor){}; } -void RGFW_waitForEvent(i32 waitMS) { RGFW_UNUSED(waitMS); } #endif /* end of web asm defines */ -/* - * RGFW function pointer backend, made to allow you to compile for Wayland but fallback to X11 -*/ -#ifdef RGFW_DYNAMIC -typedef RGFW_window* (*RGFW_createWindowPlatform_ptr)(const char* name, RGFW_windowFlags flags, RGFW_window* win); -typedef RGFW_bool (*RGFW_getMouse_ptr)(i32* x, i32* y); -typedef u8 (*RGFW_rgfwToKeyChar_ptr)(u32 key); -typedef void (*RGFW_pollEvents_ptr)(void); -typedef void (*RGFW_window_move_ptr)(RGFW_window* win, i32 x, i32 y); -typedef void (*RGFW_window_resize_ptr)(RGFW_window* win, i32 w, i32 h); -typedef void (*RGFW_window_setAspectRatio_ptr)(RGFW_window* win, i32 w, i32 h); -typedef void (*RGFW_window_setMinSize_ptr)(RGFW_window* win, i32 w, i32 h); -typedef void (*RGFW_window_setMaxSize_ptr)(RGFW_window* win, i32 w, i32 h); -typedef void (*RGFW_window_maximize_ptr)(RGFW_window* win); -typedef void (*RGFW_window_focus_ptr)(RGFW_window* win); -typedef void (*RGFW_window_raise_ptr)(RGFW_window* win); -typedef void (*RGFW_window_setFullscreen_ptr)(RGFW_window* win, RGFW_bool fullscreen); -typedef void (*RGFW_window_setFloating_ptr)(RGFW_window* win, RGFW_bool floating); -typedef void (*RGFW_window_setOpacity_ptr)(RGFW_window* win, u8 opacity); -typedef void (*RGFW_window_minimize_ptr)(RGFW_window* win); -typedef void (*RGFW_window_restore_ptr)(RGFW_window* win); -typedef RGFW_bool (*RGFW_window_isFloating_ptr)(RGFW_window* win); -typedef void (*RGFW_window_setName_ptr)(RGFW_window* win, const char* name); -typedef void (*RGFW_window_setMousePassthrough_ptr)(RGFW_window* win, RGFW_bool passthrough); -typedef RGFW_bool (*RGFW_window_setIconEx_ptr)(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, u8 type); -typedef RGFW_mouse* (*RGFW_loadMouse_ptr)(u8* data, i32 w, i32 h, RGFW_format format); -typedef void (*RGFW_window_setMouse_ptr)(RGFW_window* win, RGFW_mouse* mouse); -typedef void (*RGFW_window_moveMouse_ptr)(RGFW_window* win, i32 x, i32 y); -typedef RGFW_bool (*RGFW_window_setMouseDefault_ptr)(RGFW_window* win); -typedef RGFW_bool (*RGFW_window_setMouseStandard_ptr)(RGFW_window* win, u8 mouse); -typedef void (*RGFW_window_hide_ptr)(RGFW_window* win); -typedef void (*RGFW_window_show_ptr)(RGFW_window* win); -typedef RGFW_ssize_t (*RGFW_readClipboardPtr_ptr)(char* str, size_t strCapacity); -typedef void (*RGFW_writeClipboard_ptr)(const char* text, u32 textLen); -typedef RGFW_bool (*RGFW_window_isHidden_ptr)(RGFW_window* win); -typedef RGFW_bool (*RGFW_window_isMinimized_ptr)(RGFW_window* win); -typedef RGFW_bool (*RGFW_window_isMaximized_ptr)(RGFW_window* win); -typedef RGFW_monitor* (*RGFW_getMonitors_ptr)(size_t* len); -typedef RGFW_monitor (*RGFW_getPrimaryMonitor_ptr)(void); -typedef RGFW_bool (*RGFW_monitor_requestMode_ptr)(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request); -typedef RGFW_monitor (*RGFW_window_getMonitor_ptr)(RGFW_window* win); -typedef void (*RGFW_window_closePlatform_ptr)(RGFW_window* win); -typedef RGFW_bool (*RGFW_createSurfacePtr_ptr)(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface); -typedef void (*RGFW_window_blitSurface_ptr)(RGFW_window* win, RGFW_surface* surface); -typedef void (*RGFW_surface_freePtr_ptr)(RGFW_surface* surface); -typedef void (*RGFW_freeMouse_ptr)(RGFW_mouse* mouse); -typedef void (*RGFW_window_setBorder_ptr)(RGFW_window* win, RGFW_bool border); -typedef void (*RGFW_releaseCursor_ptr)(RGFW_window* win); -typedef void (*RGFW_captureCursor_ptr)(RGFW_window* win); -#ifdef RGFW_OPENGL -typedef void (*RGFW_window_makeCurrentContext_OpenGL_ptr)(RGFW_window* win); -typedef void* (*RGFW_getCurrentContext_OpenGL_ptr)(void); -typedef void (*RGFW_window_swapBuffers_OpenGL_ptr)(RGFW_window* win); -typedef void (*RGFW_window_swapInterval_OpenGL_ptr)(RGFW_window* win, i32 swapInterval); -typedef RGFW_bool (*RGFW_extensionSupportedPlatform_OpenGL_ptr)(const char* extension, size_t len); -typedef RGFW_proc (*RGFW_getProcAddress_OpenGL_ptr)(const char* procname); -typedef RGFW_bool (*RGFW_window_createContextPtr_OpenGL_ptr)(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints); -typedef void (*RGFW_window_deleteContextPtr_OpenGL_ptr)(RGFW_window* win, RGFW_glContext* ctx); -#endif -#ifdef RGFW_WEBGPU -typedef WGPUSurface (*RGFW_window_createSurface_WebGPU_ptr)(RGFW_window* window, WGPUInstance instance); -#endif +/* unix (macOS, linux, web asm) only stuff */ +#if defined(RGFW_X11) || defined(RGFW_MACOS) || defined(RGFW_WASM) || defined(RGFW_WAYLAND) +#ifndef RGFW_NO_THREADS +#include -/* Structure to hold all function pointers */ -typedef struct RGFW_FunctionPointers { - RGFW_createSurfacePtr_ptr createSurfacePtr; - RGFW_window_blitSurface_ptr window_blitSurface; - RGFW_surface_freePtr_ptr surface_freePtr; - RGFW_freeMouse_ptr freeMouse; - RGFW_window_setBorder_ptr window_setBorder; - RGFW_releaseCursor_ptr releaseCursor; - RGFW_captureCursor_ptr captureCursor; - RGFW_createWindowPlatform_ptr createWindowPlatform; - RGFW_getMouse_ptr getGlobalMouse; - RGFW_rgfwToKeyChar_ptr rgfwToKeyChar; - RGFW_pollEvents_ptr pollEvents; - RGFW_window_move_ptr window_move; - RGFW_window_resize_ptr window_resize; - RGFW_window_setAspectRatio_ptr window_setAspectRatio; - RGFW_window_setMinSize_ptr window_setMinSize; - RGFW_window_setMaxSize_ptr window_setMaxSize; - RGFW_window_maximize_ptr window_maximize; - RGFW_window_focus_ptr window_focus; - RGFW_window_raise_ptr window_raise; - RGFW_window_setFullscreen_ptr window_setFullscreen; - RGFW_window_setFloating_ptr window_setFloating; - RGFW_window_setOpacity_ptr window_setOpacity; - RGFW_window_minimize_ptr window_minimize; - RGFW_window_restore_ptr window_restore; - RGFW_window_isFloating_ptr window_isFloating; - RGFW_window_setName_ptr window_setName; - RGFW_window_setMousePassthrough_ptr window_setMousePassthrough; - RGFW_window_setIconEx_ptr window_setIconEx; - RGFW_loadMouse_ptr loadMouse; - RGFW_window_setMouse_ptr window_setMouse; - RGFW_window_moveMouse_ptr window_moveMouse; - RGFW_window_setMouseDefault_ptr window_setMouseDefault; - RGFW_window_setMouseStandard_ptr window_setMouseStandard; - RGFW_window_hide_ptr window_hide; - RGFW_window_show_ptr window_show; - RGFW_readClipboardPtr_ptr readClipboardPtr; - RGFW_writeClipboard_ptr writeClipboard; - RGFW_window_isHidden_ptr window_isHidden; - RGFW_window_isMinimized_ptr window_isMinimized; - RGFW_window_isMaximized_ptr window_isMaximized; - RGFW_getMonitors_ptr getMonitors; - RGFW_getPrimaryMonitor_ptr getPrimaryMonitor; - RGFW_monitor_requestMode_ptr monitor_requestMode; - RGFW_window_getMonitor_ptr window_getMonitor; - RGFW_window_closePlatform_ptr window_closePlatform; -#ifdef RGFW_OPENGL - RGFW_extensionSupportedPlatform_OpenGL_ptr extensionSupportedPlatform_OpenGL; - RGFW_getProcAddress_OpenGL_ptr getProcAddress_OpenGL; - RGFW_window_createContextPtr_OpenGL_ptr window_createContextPtr_OpenGL; - RGFW_window_deleteContextPtr_OpenGL_ptr window_deleteContextPtr_OpenGL; - RGFW_window_makeCurrentContext_OpenGL_ptr window_makeCurrentContext_OpenGL; - RGFW_getCurrentContext_OpenGL_ptr getCurrentContext_OpenGL; - RGFW_window_swapBuffers_OpenGL_ptr window_swapBuffers_OpenGL; - RGFW_window_swapInterval_OpenGL_ptr window_swapInterval_OpenGL; -#endif -#ifdef RGFW_WEBGPU - RGFW_window_createSurface_WebGPU_ptr window_createSurface_WebGPU; -#endif -} RGFW_functionPointers; - -RGFW_functionPointers RGFW_api; - -RGFW_bool RGFW_createSurfacePtr(u8* data, i32 w, i32 h, RGFW_format format, RGFW_surface* surface) { return RGFW_api.createSurfacePtr(data, w, h, format, surface); } -void RGFW_surface_freePtr(RGFW_surface* surface) { RGFW_api.surface_freePtr(surface); } -void RGFW_freeMouse(RGFW_mouse* mouse) { RGFW_api.freeMouse(mouse); } -void RGFW_window_blitSurface(RGFW_window* win, RGFW_surface* surface) { RGFW_api.window_blitSurface(win, surface); } -void RGFW_window_setBorder(RGFW_window* win, RGFW_bool border) { RGFW_api.window_setBorder(win, border); } -void RGFW_releaseCursor(RGFW_window* win) { RGFW_api.releaseCursor(win); } -void RGFW_captureCursor(RGFW_window* win) { RGFW_api.captureCursor(win); } -RGFW_window* RGFW_createWindowPlatform(const char* name, RGFW_windowFlags flags, RGFW_window* win) { RGFW_init(); return RGFW_api.createWindowPlatform(name, flags, win); } -RGFW_bool RGFW_getGlobalMouse(i32* x, i32* y) { return RGFW_api.getGlobalMouse(x, y); } -u8 RGFW_rgfwToKeyChar(u32 key) { return RGFW_api.rgfwToKeyChar(key); } -void RGFW_pollEvents(void) { RGFW_api.pollEvents(); } -void RGFW_window_move(RGFW_window* win, i32 x, i32 y) { RGFW_api.window_move(win, x, y); } -void RGFW_window_resize(RGFW_window* win, i32 w, i32 h) { RGFW_api.window_resize(win, w, h); } -void RGFW_window_setAspectRatio(RGFW_window* win, i32 w, i32 h) { RGFW_api.window_setAspectRatio(win, w, h); } -void RGFW_window_setMinSize(RGFW_window* win, i32 w, i32 h) { RGFW_api.window_setMinSize(win, w, h); } -void RGFW_window_setMaxSize(RGFW_window* win, i32 w, i32 h) { RGFW_api.window_setMaxSize(win, w, h); } -void RGFW_window_maximize(RGFW_window* win) { RGFW_api.window_maximize(win); } -void RGFW_window_focus(RGFW_window* win) { RGFW_api.window_focus(win); } -void RGFW_window_raise(RGFW_window* win) { RGFW_api.window_raise(win); } -void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { RGFW_api.window_setFullscreen(win, fullscreen); } -void RGFW_window_setFloating(RGFW_window* win, RGFW_bool floating) { RGFW_api.window_setFloating(win, floating); } -void RGFW_window_setOpacity(RGFW_window* win, u8 opacity) { RGFW_api.window_setOpacity(win, opacity); } -void RGFW_window_minimize(RGFW_window* win) { RGFW_api.window_minimize(win); } -void RGFW_window_restore(RGFW_window* win) { RGFW_api.window_restore(win); } -RGFW_bool RGFW_window_isFloating(RGFW_window* win) { return RGFW_api.window_isFloating(win); } -void RGFW_window_setName(RGFW_window* win, const char* name) { RGFW_api.window_setName(win, name); } - -#ifndef RGFW_NO_PASSTHROUGH -void RGFW_window_setMousePassthrough(RGFW_window* win, RGFW_bool passthrough) { RGFW_api.window_setMousePassthrough(win, passthrough); } -#endif - -RGFW_bool RGFW_window_setIconEx(RGFW_window* win, u8* data, i32 w, i32 h, RGFW_format format, u8 type) { return RGFW_api.window_setIconEx(win, data, w, h, format, type); } -RGFW_mouse* RGFW_loadMouse(u8* data, i32 w, i32 h, RGFW_format format) { return RGFW_api.loadMouse(data, w, h, format); } -void RGFW_window_setMouse(RGFW_window* win, RGFW_mouse* mouse) { RGFW_api.window_setMouse(win, mouse); } -void RGFW_window_moveMouse(RGFW_window* win, i32 x, i32 y) { RGFW_api.window_moveMouse(win, x, y); } -RGFW_bool RGFW_window_setMouseDefault(RGFW_window* win) { return RGFW_api.window_setMouseDefault(win); } -RGFW_bool RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { return RGFW_api.window_setMouseStandard(win, mouse); } -void RGFW_window_hide(RGFW_window* win) { RGFW_api.window_hide(win); } -void RGFW_window_show(RGFW_window* win) { RGFW_api.window_show(win); } -RGFW_ssize_t RGFW_readClipboardPtr(char* str, size_t strCapacity) { return RGFW_api.readClipboardPtr(str, strCapacity); } -void RGFW_writeClipboard(const char* text, u32 textLen) { RGFW_api.writeClipboard(text, textLen); } -RGFW_bool RGFW_window_isHidden(RGFW_window* win) { return RGFW_api.window_isHidden(win); } -RGFW_bool RGFW_window_isMinimized(RGFW_window* win) { return RGFW_api.window_isMinimized(win); } -RGFW_bool RGFW_window_isMaximized(RGFW_window* win) { return RGFW_api.window_isMaximized(win); } -RGFW_monitor* RGFW_getMonitors(size_t* len) { return RGFW_api.getMonitors(len); } -RGFW_monitor RGFW_getPrimaryMonitor(void) { return RGFW_api.getPrimaryMonitor(); } -RGFW_bool RGFW_monitor_requestMode(RGFW_monitor mon, RGFW_monitorMode mode, RGFW_modeRequest request) { return RGFW_api.monitor_requestMode(mon, mode, request); } -RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { return RGFW_api.window_getMonitor(win); } -void RGFW_window_closePlatform(RGFW_window* win) { RGFW_api.window_closePlatform(win); } - -#ifdef RGFW_OPENGL -RGFW_bool RGFW_extensionSupportedPlatform_OpenGL(const char* extension, size_t len) { return RGFW_api.extensionSupportedPlatform_OpenGL(extension, len); } -RGFW_proc RGFW_getProcAddress_OpenGL(const char* procname) { return RGFW_api.getProcAddress_OpenGL(procname); } -RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { return RGFW_api.window_createContextPtr_OpenGL(win, ctx, hints); } -void RGFW_window_deleteContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx) { RGFW_api.window_deleteContextPtr_OpenGL(win, ctx); } -void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win) { RGFW_api.window_makeCurrentContext_OpenGL(win); } -void* RGFW_getCurrentContext_OpenGL(void) { return RGFW_api.getCurrentContext_OpenGL(); } -void RGFW_window_swapBuffers_OpenGL(RGFW_window* win) { RGFW_api.window_swapBuffers_OpenGL(win); } -void RGFW_window_swapInterval_OpenGL(RGFW_window* win, i32 swapInterval) { RGFW_api.window_swapInterval_OpenGL(win, swapInterval); } -#endif - -#ifdef RGFW_WEBGPU -WGPUSurface RGFW_window_createSurface_WebGPU(RGFW_window* window, WGPUInstance instance) { return RGFW_api.window_createSurface_WebGPU(window, instance); } -#endif -#endif /* RGFW_DYNAMIC */ - -/* - * start of X11 AND wayland defines - * this allows a single executable to support x11 AND wayland - * falling back to x11 if wayland fails to initalize -*/ -#if defined(RGFW_WAYLAND) && defined(RGFW_X11) -void RGFW_load_X11(void) { - RGFW_api.createSurfacePtr = RGFW_createSurfacePtr_X11; - RGFW_api.window_blitSurface = RGFW_window_blitSurface_X11; - RGFW_api.surface_freePtr = RGFW_surface_freePtr_X11; - RGFW_api.freeMouse = RGFW_freeMouse_X11; - RGFW_api.window_setBorder = RGFW_window_setBorder_X11; - RGFW_api.releaseCursor = RGFW_releaseCursor_X11; - RGFW_api.captureCursor = RGFW_captureCursor_X11; - RGFW_api.createWindowPlatform = RGFW_createWindowPlatform_X11; - RGFW_api.getGlobalMouse = RGFW_getGlobalMouse_X11; - RGFW_api.rgfwToKeyChar = RGFW_rgfwToKeyChar_X11; - RGFW_api.pollEvents = RGFW_pollEvents_X11; - RGFW_api.window_move = RGFW_window_move_X11; - RGFW_api.window_resize = RGFW_window_resize_X11; - RGFW_api.window_setAspectRatio = RGFW_window_setAspectRatio_X11; - RGFW_api.window_setMinSize = RGFW_window_setMinSize_X11; - RGFW_api.window_setMaxSize = RGFW_window_setMaxSize_X11; - RGFW_api.window_maximize = RGFW_window_maximize_X11; - RGFW_api.window_focus = RGFW_window_focus_X11; - RGFW_api.window_raise = RGFW_window_raise_X11; - RGFW_api.window_setFullscreen = RGFW_window_setFullscreen_X11; - RGFW_api.window_setFloating = RGFW_window_setFloating_X11; - RGFW_api.window_setOpacity = RGFW_window_setOpacity_X11; - RGFW_api.window_minimize = RGFW_window_minimize_X11; - RGFW_api.window_restore = RGFW_window_restore_X11; - RGFW_api.window_isFloating = RGFW_window_isFloating_X11; - RGFW_api.window_setName = RGFW_window_setName_X11; -#ifndef RGFW_NO_PASSTHROUGH - RGFW_api.window_setMousePassthrough = RGFW_window_setMousePassthrough_X11; -#endif - RGFW_api.window_setIconEx = RGFW_window_setIconEx_X11; - RGFW_api.loadMouse = RGFW_loadMouse_X11; - RGFW_api.window_setMouse = RGFW_window_setMouse_X11; - RGFW_api.window_moveMouse = RGFW_window_moveMouse_X11; - RGFW_api.window_setMouseDefault = RGFW_window_setMouseDefault_X11; - RGFW_api.window_setMouseStandard = RGFW_window_setMouseStandard_X11; - RGFW_api.window_hide = RGFW_window_hide_X11; - RGFW_api.window_show = RGFW_window_show_X11; - RGFW_api.readClipboardPtr = RGFW_readClipboardPtr_X11; - RGFW_api.writeClipboard = RGFW_writeClipboard_X11; - RGFW_api.window_isHidden = RGFW_window_isHidden_X11; - RGFW_api.window_isMinimized = RGFW_window_isMinimized_X11; - RGFW_api.window_isMaximized = RGFW_window_isMaximized_X11; - RGFW_api.getMonitors = RGFW_getMonitors_X11; - RGFW_api.getPrimaryMonitor = RGFW_getPrimaryMonitor_X11; - RGFW_api.monitor_requestMode = RGFW_monitor_requestMode_X11; - RGFW_api.window_getMonitor = RGFW_window_getMonitor_X11; - RGFW_api.window_closePlatform = RGFW_window_closePlatform_X11; -#ifdef RGFW_OPENGL - RGFW_api.extensionSupportedPlatform_OpenGL = RGFW_extensionSupportedPlatform_OpenGL_X11; - RGFW_api.getProcAddress_OpenGL = RGFW_getProcAddress_OpenGL_X11; - RGFW_api.window_createContextPtr_OpenGL = RGFW_window_createContextPtr_OpenGL_X11; - RGFW_api.window_deleteContextPtr_OpenGL = RGFW_window_deleteContextPtr_OpenGL_X11; - RGFW_api.window_makeCurrentContext_OpenGL = RGFW_window_makeCurrentContext_OpenGL_X11; - RGFW_api.getCurrentContext_OpenGL = RGFW_getCurrentContext_OpenGL_X11; - RGFW_api.window_swapBuffers_OpenGL = RGFW_window_swapBuffers_OpenGL_X11; - RGFW_api.window_swapInterval_OpenGL = RGFW_window_swapInterval_OpenGL_X11; -#endif -#ifdef RGFW_WEBGPU - RGFW_api.window_createSurface_WebGPU = RGFW_window_createSurface_WebGPU_X11; -#endif +RGFW_thread RGFW_createThread(RGFW_threadFunc_ptr ptr, void* args) { + RGFW_thread t; + pthread_create((pthread_t*) &t, NULL, *ptr, args); + return t; } +void RGFW_cancelThread(RGFW_thread thread) { pthread_cancel((pthread_t) thread); } +void RGFW_joinThread(RGFW_thread thread) { pthread_join((pthread_t) thread, NULL); } -void RGFW_load_Wayland(void) { - RGFW_api.createSurfacePtr = RGFW_createSurfacePtr_Wayland; - RGFW_api.window_blitSurface = RGFW_window_blitSurface_Wayland; - RGFW_api.surface_freePtr = RGFW_surface_freePtr_Wayland; - RGFW_api.freeMouse = RGFW_freeMouse_Wayland; - RGFW_api.window_setBorder = RGFW_window_setBorder_Wayland; - RGFW_api.releaseCursor = RGFW_releaseCursor_Wayland; - RGFW_api.captureCursor = RGFW_captureCursor_Wayland; - RGFW_api.createWindowPlatform = RGFW_createWindowPlatform_Wayland; - RGFW_api.getGlobalMouse = RGFW_getGlobalMouse_Wayland; - RGFW_api.rgfwToKeyChar = RGFW_rgfwToKeyChar_Wayland; - RGFW_api.pollEvents = RGFW_pollEvents_Wayland; - RGFW_api.window_move = RGFW_window_move_Wayland; - RGFW_api.window_resize = RGFW_window_resize_Wayland; - RGFW_api.window_setAspectRatio = RGFW_window_setAspectRatio_Wayland; - RGFW_api.window_setMinSize = RGFW_window_setMinSize_Wayland; - RGFW_api.window_setMaxSize = RGFW_window_setMaxSize_Wayland; - RGFW_api.window_maximize = RGFW_window_maximize_Wayland; - RGFW_api.window_focus = RGFW_window_focus_Wayland; - RGFW_api.window_raise = RGFW_window_raise_Wayland; - RGFW_api.window_setFullscreen = RGFW_window_setFullscreen_Wayland; - RGFW_api.window_setFloating = RGFW_window_setFloating_Wayland; - RGFW_api.window_setOpacity = RGFW_window_setOpacity_Wayland; - RGFW_api.window_minimize = RGFW_window_minimize_Wayland; - RGFW_api.window_restore = RGFW_window_restore_Wayland; - RGFW_api.window_isFloating = RGFW_window_isFloating_Wayland; - RGFW_api.window_setName = RGFW_window_setName_Wayland; -#ifndef RGFW_NO_PASSTHROUGH - RGFW_api.window_setMousePassthrough = RGFW_window_setMousePassthrough_Wayland; +#if defined(__linux__) +void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { pthread_setschedprio((pthread_t)thread, priority); } +#else +void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { RGFW_UNUSED(thread); RGFW_UNUSED(priority); } #endif - RGFW_api.window_setIconEx = RGFW_window_setIconEx_Wayland; - RGFW_api.loadMouse = RGFW_loadMouse_Wayland; - RGFW_api.window_setMouse = RGFW_window_setMouse_Wayland; - RGFW_api.window_moveMouse = RGFW_window_moveMouse_Wayland; - RGFW_api.window_setMouseDefault = RGFW_window_setMouseDefault_Wayland; - RGFW_api.window_setMouseStandard = RGFW_window_setMouseStandard_Wayland; - RGFW_api.window_hide = RGFW_window_hide_Wayland; - RGFW_api.window_show = RGFW_window_show_Wayland; - RGFW_api.readClipboardPtr = RGFW_readClipboardPtr_Wayland; - RGFW_api.writeClipboard = RGFW_writeClipboard_Wayland; - RGFW_api.window_isHidden = RGFW_window_isHidden_Wayland; - RGFW_api.window_isMinimized = RGFW_window_isMinimized_Wayland; - RGFW_api.window_isMaximized = RGFW_window_isMaximized_Wayland; - RGFW_api.getMonitors = RGFW_getMonitors_Wayland; - RGFW_api.getPrimaryMonitor = RGFW_getPrimaryMonitor_Wayland; - RGFW_api.monitor_requestMode = RGFW_monitor_requestMode_Wayland; - RGFW_api.window_getMonitor = RGFW_window_getMonitor_Wayland; - RGFW_api.window_closePlatform = RGFW_window_closePlatform_Wayland; -#ifdef RGFW_OPENGL - RGFW_api.extensionSupportedPlatform_OpenGL = RGFW_extensionSupportedPlatform_OpenGL_Wayland; - RGFW_api.getProcAddress_OpenGL = RGFW_getProcAddress_OpenGL_Wayland; - RGFW_api.window_createContextPtr_OpenGL = RGFW_window_createContextPtr_OpenGL_Wayland; - RGFW_api.window_deleteContextPtr_OpenGL = RGFW_window_deleteContextPtr_OpenGL_Wayland; - RGFW_api.window_makeCurrentContext_OpenGL = RGFW_window_makeCurrentContext_OpenGL_Wayland; - RGFW_api.getCurrentContext_OpenGL = RGFW_getCurrentContext_OpenGL_Wayland; - RGFW_api.window_swapBuffers_OpenGL = RGFW_window_swapBuffers_OpenGL_Wayland; - RGFW_api.window_swapInterval_OpenGL = RGFW_window_swapInterval_OpenGL_Wayland; -#endif -#ifdef RGFW_WEBGPU - RGFW_api.window_createSurface_WebGPU = RGFW_window_createSurface_WebGPU_Wayland; #endif + +#ifndef RGFW_WASM +void RGFW_sleep(u64 ms) { + struct timespec time; + time.tv_sec = 0; + time.tv_nsec = (long int)((double)ms * 1e+6); + + #ifndef RGFW_NO_UNIX_CLOCK + nanosleep(&time, NULL); + #endif } -#endif /* wayland AND x11 */ -/* end of X11 AND wayland defines */ +#endif +#endif /* end of unix / mac stuff */ #endif /* RGFW_IMPLEMENTATION */ #if defined(__cplusplus) && !defined(__EMSCRIPTEN__) @@ -13917,4 +11070,3 @@ void RGFW_load_Wayland(void) { #if _MSC_VER #pragma warning( pop ) #endif - diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 2671538d8..a1b13856b 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -48,6 +48,11 @@ * **********************************************************************************************/ +#ifndef RAYLIB_H /* this should never actually happen, it's only here for IDEs */ +#include "raylib.h" +#include "../rcore.c" +#endif + #if defined(PLATFORM_WEB_RGFW) #define RGFW_NO_GL_HEADER #endif From f031b2f4f4c343871f6fca812677b918cd2c157b Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 16 Dec 2025 18:20:02 +0100 Subject: [PATCH 235/430] Alignment with other platform backends, avoid unneeded includes --- src/platforms/rcore_desktop_rgfw.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index a1b13856b..2671538d8 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -48,11 +48,6 @@ * **********************************************************************************************/ -#ifndef RAYLIB_H /* this should never actually happen, it's only here for IDEs */ -#include "raylib.h" -#include "../rcore.c" -#endif - #if defined(PLATFORM_WEB_RGFW) #define RGFW_NO_GL_HEADER #endif From 33adda198366e560afa59a806dd8db2609261e40 Mon Sep 17 00:00:00 2001 From: dtasada <83500532+dtasada@users.noreply.github.com> Date: Tue, 16 Dec 2025 18:24:53 +0100 Subject: [PATCH 236/430] fixed build errors with zig. now compatible with zig master 0.16.0-dev.1593+c13857e50. still backwards compatible with 0.15.1 (#5415) --- build.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.zig b/build.zig index 5d2902111..239b10f9e 100644 --- a/build.zig +++ b/build.zig @@ -106,9 +106,9 @@ const config_h_flags = outer: { if (std.mem.startsWith(u8, line, "//")) continue; if (std.mem.startsWith(u8, line, "#if")) continue; - var flag = std.mem.trimLeft(u8, line, " \t"); // Trim whitespace + var flag = std.mem.trimStart(u8, line, " \t"); // Trim whitespace flag = flag["#define ".len - 1 ..]; // Remove #define - flag = std.mem.trimLeft(u8, flag, " \t"); // Trim whitespace + flag = std.mem.trimStart(u8, flag, " \t"); // Trim whitespace flag = flag[0 .. std.mem.indexOf(u8, flag, " ") orelse continue]; // Flag is only one word, so capture till space flag = "-D" ++ flag; // Prepend with -D @@ -193,7 +193,7 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. // No GLFW required on PLATFORM_DRM if (options.platform != .drm) { - raylib.addIncludePath(b.path("src/external/glfw/include")); + raylib.root_module.addIncludePath(b.path("src/external/glfw/include")); } var c_source_files: std.ArrayList([]const u8) = try .initCapacity(b.allocator, 2); @@ -224,7 +224,7 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. raylib.root_module.addCMacro(options.opengl_version.toCMacroStr(), ""); } - raylib.addIncludePath(b.path("src/platforms")); + raylib.root_module.addIncludePath(b.path("src/platforms")); switch (target.result.os.tag) { .windows => { switch (options.platform) { From 1c94e948733b69e857f1f7014e72ca331c99e089 Mon Sep 17 00:00:00 2001 From: caszu <109808097+caszuu@users.noreply.github.com> Date: Tue, 16 Dec 2025 18:26:20 +0100 Subject: [PATCH 237/430] [rcore] Implement `FLAG_WINDOW_ALWAYS_RUN` on Android (#5414) --- src/platforms/rcore_android.c | 126 ++++++++++++++++++++++++++++++++-- 1 file changed, 122 insertions(+), 4 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index bc8a25f8f..575807b9a 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -357,13 +357,125 @@ void RestoreWindow(void) // Set window configuration state using flags void SetWindowState(unsigned int flags) { - TRACELOG(LOG_WARNING, "SetWindowState() not available on target platform"); + if (!CORE.Window.ready) TRACELOG(LOG_WARNING, "WINDOW: SetWindowState does nothing before window initialization, Use \"SetConfigFlags\" instead"); + + // State change: FLAG_WINDOW_ALWAYS_RUN + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) + { + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); + } + + // Setting other window flags is not supported on android + + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_VSYNC_HINT) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_BORDERLESS_WINDOWED_MODE) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_FULLSCREEN_MODE) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_UNDECORATED) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_HIDDEN) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_MAXIMIZED) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_MINIMIZED) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_TRANSPARENT) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_HIGHDPI) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_MOUSE_PASSTHROUGH) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_MSAA_4X_HINT) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_INTERLACED_HINT) not available on target platform"); + } } // Clear window configuration state flags void ClearWindowState(unsigned int flags) { - TRACELOG(LOG_WARNING, "ClearWindowState() not available on target platform"); + // State change: FLAG_WINDOW_ALWAYS_RUN + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) + { + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); + } + + // Clearing other window flags is not supported on android + + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_VSYNC_HINT) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_BORDERLESS_WINDOWED_MODE) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_FULLSCREEN_MODE) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_UNDECORATED) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_HIDDEN) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_MAXIMIZED) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_MINIMIZED) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_TRANSPARENT) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_HIGHDPI) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_MOUSE_PASSTHROUGH) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_MSAA_4X_HINT) not available on target platform"); + } + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_INTERLACED_HINT) not available on target platform"); + } } // Set icon for window @@ -601,6 +713,12 @@ void DisableCursor(void) // Swap back buffer with front buffer (screen drawing) void SwapScreenBuffer(void) { + if (platform.surface == EGL_NO_SURFACE) + { + TRACELOG(LOG_WARNING, "SwapScreenBuffer() called with no window, skipping frame"); + return; + } + eglSwapBuffers(platform.device, platform.surface); } @@ -740,8 +858,8 @@ void PollInputEvents(void) int pollEvents = 0; // Poll Events (registered events) until we reach TIMEOUT which indicates there are no events left to poll - // NOTE: Activity is paused if not enabled (platform.appEnabled) - while ((pollResult = ALooper_pollOnce(platform.appEnabled? 0 : -1, NULL, &pollEvents, ((void **)&platform.source)) > ALOOPER_POLL_TIMEOUT)) + // NOTE: Activity is paused if not enabled (platform.appEnabled) and always run flag is not set (FLAG_WINDOW_ALWAYS_RUN) + while ((pollResult = ALooper_pollOnce((platform.appEnabled || FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN))? 0 : -1, NULL, &pollEvents, ((void **)&platform.source)) > ALOOPER_POLL_TIMEOUT)) { // Process this event if (platform.source != NULL) platform.source->process(platform.app, platform.source); From 7a5e8aa3a5a95d99e242791b6cda2839281b23d5 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 16 Dec 2025 18:30:33 +0100 Subject: [PATCH 238/430] Update rcore_android.c --- src/platforms/rcore_android.c | 122 ++-------------------------------- 1 file changed, 4 insertions(+), 118 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 575807b9a..7b8d3e052 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -358,124 +358,16 @@ void RestoreWindow(void) void SetWindowState(unsigned int flags) { if (!CORE.Window.ready) TRACELOG(LOG_WARNING, "WINDOW: SetWindowState does nothing before window initialization, Use \"SetConfigFlags\" instead"); - + // State change: FLAG_WINDOW_ALWAYS_RUN - if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) - { - FLAG_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); - } - - // Setting other window flags is not supported on android - - if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) - { - TRACELOG(LOG_WARNING, "SetWindowState(FLAG_VSYNC_HINT) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) - { - TRACELOG(LOG_WARNING, "SetWindowState(FLAG_BORDERLESS_WINDOWED_MODE) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) - { - TRACELOG(LOG_WARNING, "SetWindowState(FLAG_FULLSCREEN_MODE) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) - { - TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_UNDECORATED) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) - { - TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_HIDDEN) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) - { - TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_MAXIMIZED) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) - { - TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_MINIMIZED) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) - { - TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_TRANSPARENT) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) - { - TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_HIGHDPI) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) - { - TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_MOUSE_PASSTHROUGH) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) - { - TRACELOG(LOG_WARNING, "SetWindowState(FLAG_MSAA_4X_HINT) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) - { - TRACELOG(LOG_WARNING, "SetWindowState(FLAG_INTERLACED_HINT) not available on target platform"); - } + if (!FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); } // Clear window configuration state flags void ClearWindowState(unsigned int flags) { // State change: FLAG_WINDOW_ALWAYS_RUN - if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) - { - FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); - } - - // Clearing other window flags is not supported on android - - if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) - { - TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_VSYNC_HINT) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) - { - TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_BORDERLESS_WINDOWED_MODE) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) - { - TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_FULLSCREEN_MODE) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) - { - TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_UNDECORATED) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) - { - TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_HIDDEN) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) - { - TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_MAXIMIZED) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) - { - TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_MINIMIZED) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) - { - TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_TRANSPARENT) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) - { - TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_HIGHDPI) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) - { - TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_MOUSE_PASSTHROUGH) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) - { - TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_MSAA_4X_HINT) not available on target platform"); - } - if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) - { - TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_INTERLACED_HINT) not available on target platform"); - } + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); } // Set icon for window @@ -713,13 +605,7 @@ void DisableCursor(void) // Swap back buffer with front buffer (screen drawing) void SwapScreenBuffer(void) { - if (platform.surface == EGL_NO_SURFACE) - { - TRACELOG(LOG_WARNING, "SwapScreenBuffer() called with no window, skipping frame"); - return; - } - - eglSwapBuffers(platform.device, platform.surface); + if (platform.surface != EGL_NO_SURFACE) eglSwapBuffers(platform.device, platform.surface); } //---------------------------------------------------------------------------------- From 80ad96acc2837f26d2b40a52ce4d6bbfc1c28cd1 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 16 Dec 2025 18:33:07 +0100 Subject: [PATCH 239/430] Fix #5413 --- examples/text/text_strings_management.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/text/text_strings_management.c b/examples/text/text_strings_management.c index 6c110e6ef..d2e349279 100644 --- a/examples/text/text_strings_management.c +++ b/examples/text/text_strings_management.c @@ -316,7 +316,7 @@ void SliceTextParticle(TextParticle *tp, int particlePos, int sliceLength, TextP void SliceTextParticleByChar(TextParticle *tp, char charToSlice, TextParticle *tps, int *particleCount) { int tokenCount = 0; - const char **tokens = TextSplit(tp->text, charToSlice, &tokenCount); + char **tokens = TextSplit(tp->text, charToSlice, &tokenCount); if (tokenCount > 1) { From 7553e9d58640ba0c3a2fc185a9cb458f00fae06e Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 16 Dec 2025 19:36:01 +0100 Subject: [PATCH 240/430] REVIEWED: Gamepads on latest `SDL2 2.32.8` and `SDL3 3.3.6` #5403 --- .gitignore | 4 ++ src/platforms/rcore_desktop_sdl.c | 85 +++++++++++++++++-------------- 2 files changed, 50 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index e5f6faf4d..f7b2cccf6 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,10 @@ packages/ *.h.pch ./*.obj +# Ignore SDL libs for testing +src/external/SDL2 +src/external/SDL3 + # Emscripten emsdk diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 995336ec0..7316f3c14 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -57,7 +57,7 @@ // SDL base library (window/rendered, input, timing... functionality) #ifdef USING_SDL3_PROJECT #include "SDL3/SDL.h" -#elif USING_SDL2_PROJECT +#elif defined(USING_SDL2_PROJECT) #include "SDL2/SDL.h" #else #include "SDL.h" @@ -71,7 +71,7 @@ // SDL OpenGL functionality (if required, instead of internal renderer) #ifdef USING_SDL3_PROJECT #include "SDL3/SDL_opengl.h" - #elif USING_SDL2_PROJECT + #elif defined(USING_SDL2_PROJECT) #include "SDL2/SDL_opengl.h" #else #include "SDL_opengl.h" @@ -1041,7 +1041,7 @@ int GetMonitorPhysicalWidth(int monitor) SDL_DisplayMode mode; SDL_GetCurrentDisplayMode(monitor, &mode); // Calculate size on inches, then convert to millimeter - if (ddpi > 0.0f) width = (mode.w/ddpi)*25.4f; + if (ddpi > 0.0f) width = (int)((mode.w/ddpi)*25.4f); } else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); @@ -1065,7 +1065,7 @@ int GetMonitorPhysicalHeight(int monitor) SDL_DisplayMode mode; SDL_GetCurrentDisplayMode(monitor, &mode); // Calculate size on inches, then convert to millimeter - if (ddpi > 0.0f) height = (mode.h/ddpi)*25.4f; + if (ddpi > 0.0f) height = (int)((mode.h/ddpi)*25.4f); } else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); @@ -1127,14 +1127,15 @@ Vector2 GetWindowScaleDPI(void) { Vector2 scale = { 1.0f, 1.0f }; -#ifndef USING_VERSION_SDL3 - // NOTE: SDL_GetWindowDisplayScale was only added on SDL3 +#if defined(USING_VERSION_SDL3) + // NOTE: SDL_GetWindowDisplayScale added on SDL3 // REF: https://wiki.libsdl.org/SDL3/SDL_GetWindowDisplayScale - // TODO: Implement the window scale factor calculation manually - TRACELOG(LOG_WARNING, "GetWindowScaleDPI() not implemented on target platform"); -#else scale.x = SDL_GetWindowDisplayScale(platform.window); scale.y = scale.x; +#else + // NOTE: SDL_GetWindowDisplayScale not available on SDL2 + // TODO: Implement the window scale factor calculation manually + TRACELOG(LOG_WARNING, "GetWindowScaleDPI() not implemented on target platform"); #endif return scale; @@ -1195,7 +1196,7 @@ Image GetClipboardImage(void) if (fileData) { - image = LoadImageFromMemory(imageExtensions[i], fileData, dataSize); + image = LoadImageFromMemory(imageExtensions[i], fileData, (int)dataSize); if (IsImageValid(image)) { TRACELOG(LOG_INFO, "Clipboard: Got image from clipboard successfully: %s", imageExtensions[i]); @@ -1454,7 +1455,7 @@ void PollInputEvents(void) } break; - // Window events are also polled (Minimized, maximized, close...) + // Window events are also polled (minimized, maximized, close...) #ifndef USING_VERSION_SDL3 // SDL3 states: @@ -1488,7 +1489,8 @@ void PollInputEvents(void) CORE.Window.resizedLastFrame = true; #ifndef USING_VERSION_SDL3 - // Manually detect if the window was maximized (due to SDL2 restore being unreliable on some platforms) to remove the FLAG_WINDOW_MAXIMIZED accordingly + // Manually detect if the window was maximized (due to SDL2 restore being unreliable on some platforms) + // to remove the FLAG_WINDOW_MAXIMIZED accordingly if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) { int borderTop = 0; @@ -1504,14 +1506,8 @@ void PollInputEvents(void) #endif } break; - case SDL_WINDOWEVENT_ENTER: - { - CORE.Input.Mouse.cursorOnScreen = true; - } break; - case SDL_WINDOWEVENT_LEAVE: - { - CORE.Input.Mouse.cursorOnScreen = false; - } break; + case SDL_WINDOWEVENT_ENTER: CORE.Input.Mouse.cursorOnScreen = true; break; + case SDL_WINDOWEVENT_LEAVE: CORE.Input.Mouse.cursorOnScreen = false; break; case SDL_WINDOWEVENT_MINIMIZED: { @@ -1750,7 +1746,11 @@ void PollInputEvents(void) { int button = -1; + #if defined(USING_VERSION_SDL3) switch (event.gbutton.button) + #else + switch (event.jbutton.button) + #endif { case SDL_CONTROLLER_BUTTON_Y: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; case SDL_CONTROLLER_BUTTON_B: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; @@ -1778,7 +1778,11 @@ void PollInputEvents(void) { for (int i = 0; i < MAX_GAMEPADS; i++) { + #if defined(USING_VERSION_SDL3) if (platform.gamepadId[i] == event.gbutton.which) + #else + if (platform.gamepadId[i] == event.jbutton.which) + #endif { CORE.Input.Gamepad.currentButtonState[i][button] = 1; CORE.Input.Gamepad.lastButtonPressed = button; @@ -1791,7 +1795,11 @@ void PollInputEvents(void) { int button = -1; + #if defined(USING_VERSION_SDL3) switch (event.gbutton.button) + #else + switch (event.jbutton.button) + #endif { case SDL_CONTROLLER_BUTTON_Y: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; case SDL_CONTROLLER_BUTTON_B: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; @@ -1819,7 +1827,11 @@ void PollInputEvents(void) { for (int i = 0; i < MAX_GAMEPADS; i++) { + #if defined(USING_VERSION_SDL3) if (platform.gamepadId[i] == event.gbutton.which) + #else + if (platform.gamepadId[i] == event.jbutton.which) + #endif { CORE.Input.Gamepad.currentButtonState[i][button] = 0; if (CORE.Input.Gamepad.lastButtonPressed == button) CORE.Input.Gamepad.lastButtonPressed = 0; @@ -2054,28 +2066,23 @@ int InitPlatform(void) platform.gamepadId[i] = -1; // Set all gamepad initial instance ids as invalid to not conflict with instance id zero } - int numJoysticks = 0; - SDL_JoystickID *joysticks = SDL_GetJoysticks(&numJoysticks); // array of joystick IDs, they do not start from 0 + int numJoysticks = SDL_NumJoysticks(); - if (joysticks) + for (int i = 0; (i < numJoysticks) && (i < MAX_GAMEPADS); i++) { - for (int i = 0; (i < numJoysticks) && (i < MAX_GAMEPADS); i++) - { - platform.gamepad[i] = SDL_GameControllerOpen(joysticks[i]); - platform.gamepadId[i] = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(platform.gamepad[i])); + platform.gamepad[i] = SDL_GameControllerOpen(i); + platform.gamepadId[i] = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(platform.gamepad[i])); - if (platform.gamepad[i]) - { - CORE.Input.Gamepad.ready[i] = true; - 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_GameControllerNameForIndex(i), MAX_GAMEPAD_NAME_LENGTH - 1); - CORE.Input.Gamepad.name[i][MAX_GAMEPAD_NAME_LENGTH - 1] = '\0'; - } - else TRACELOG(LOG_WARNING, "PLATFORM: Unable to open game controller [ERROR: %s]", SDL_GetError()); + if (platform.gamepad[i]) + { + CORE.Input.Gamepad.ready[i] = true; + 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_GameControllerNameForIndex(i), MAX_GAMEPAD_NAME_LENGTH - 1); + CORE.Input.Gamepad.name[i][MAX_GAMEPAD_NAME_LENGTH - 1] = '\0'; } - SDL_free(joysticks); + else TRACELOG(LOG_WARNING, "PLATFORM: Unable to open game controller [ERROR: %s]", SDL_GetError()); } // Disable mouse events being interpreted as touch events @@ -2196,7 +2203,7 @@ static void UpdateTouchPointsSDL(SDL_TouchFingerEvent event) for (int i = 0; i < CORE.Input.Touch.pointCount; i++) { SDL_Finger *finger = SDL_GetTouchFinger(event.touchId, i); - CORE.Input.Touch.pointId[i] = finger->id; + CORE.Input.Touch.pointId[i] = (int)finger->id; CORE.Input.Touch.position[i].x = finger->x*CORE.Window.screen.width; CORE.Input.Touch.position[i].y = finger->y*CORE.Window.screen.height; CORE.Input.Touch.currentTouchState[i] = 1; From 6d562e5e87887777f1fedcda5ea481f0e372f0f6 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 17 Dec 2025 19:20:18 +0100 Subject: [PATCH 241/430] REVIEWED: HiggDPI content scaling on changing monitors with different DPI #5335 #5356 Note that high-dpi awareness must be enabled by users and `CORE.Window.render` reports the scaled framebuffer size, while `CORE.Window.screen` reports the logical size. `ToggleBorderlessWindow()` has also been reviewed to be consistent with scaling, if monitor physical display size is reported as 1920x1080 but there is a content scale of 1.5, then the borderless fullscreen window will be 1280x720, with the 1920x1080 framebuffer --- examples/core/core_highdpi_testbed.c | 33 ++++++- src/platforms/rcore_desktop_glfw.c | 131 +++++++++++++++++---------- src/rcore.c | 26 ++---- 3 files changed, 120 insertions(+), 70 deletions(-) diff --git a/examples/core/core_highdpi_testbed.c b/examples/core/core_highdpi_testbed.c index a341d081c..7710e7595 100644 --- a/examples/core/core_highdpi_testbed.c +++ b/examples/core/core_highdpi_testbed.c @@ -30,6 +30,10 @@ int main(void) SetConfigFlags(FLAG_WINDOW_HIGHDPI | FLAG_WINDOW_RESIZABLE); InitWindow(screenWidth, screenHeight, "raylib [core] example - highdpi testbed"); + Vector2 scaleDpi = GetWindowScaleDPI(); + Vector2 mousePos = GetMousePosition(); + int currentMonitor = GetCurrentMonitor(); + int gridSpacing = 40; // Grid spacing in pixels SetTargetFPS(60); @@ -40,7 +44,9 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - // TODO: Update variables / Implement example logic at this point + mousePos = GetMousePosition(); + currentMonitor = GetCurrentMonitor(); + scaleDpi = GetWindowScaleDPI(); //---------------------------------------------------------------------------------- // Draw @@ -50,11 +56,30 @@ int main(void) ClearBackground(RAYWHITE); // Draw grid - for (int h = 0; h < 20; h++) DrawLine(0, h*gridSpacing, GetRenderWidth(), h*gridSpacing, LIGHTGRAY); - for (int v = 0; v < 40; v++) DrawLine(v*gridSpacing, 0, v*gridSpacing, GetScreenHeight(), LIGHTGRAY); + for (int h = 0; h < 20; h++) + { + DrawText(TextFormat("%02i", h*gridSpacing), 4, h*gridSpacing - 4, 10, GRAY); + DrawLine(24, h*gridSpacing, GetScreenWidth(), h*gridSpacing, LIGHTGRAY); + } + for (int v = 0; v < 40; v++) + { + DrawText(TextFormat("%02i", v*gridSpacing), v*gridSpacing - 10, 4, 10, GRAY); + DrawLine(v*gridSpacing, 20, v*gridSpacing, GetScreenHeight(), LIGHTGRAY); + } // Draw UI info - DrawText(TextFormat("SCREEN SIZE: %ix%i", GetScreenWidth(), GetScreenHeight()), 10, 10, 20, BLACK); + DrawText(TextFormat("CURRENT MONITOR: %i/%i (%ix%i)", currentMonitor + 1, GetMonitorCount(), + GetMonitorWidth(currentMonitor), GetMonitorHeight(currentMonitor)), 50, 50, 20, DARKGRAY); + DrawText(TextFormat("SCREEN SIZE: %ix%i", GetScreenWidth(), GetScreenHeight()), 50, 90, 20, DARKGRAY); + DrawText(TextFormat("RENDER SIZE: %ix%i", GetRenderWidth(), GetRenderHeight()), 50, 130, 20, DARKGRAY); + DrawText(TextFormat("SCALE FACTOR: %.1fx%.1f", scaleDpi.x, scaleDpi.y), 50, 170, 20, GRAY); + + // Draw mouse position + DrawCircleV(GetMousePosition(), 20, MAROON); + DrawRectangle(mousePos.x - 25, mousePos.y, 50, 2, BLACK); + DrawRectangle(mousePos.x, mousePos.y - 25, 2, 50, BLACK); + DrawText(TextFormat("[%i,%i]", GetMouseX(), GetMouseY()), mousePos.x - 44, + (mousePos.y > GetScreenHeight() - 60)? mousePos.y - 46 : mousePos.y + 30, 20, BLACK); EndDrawing(); //---------------------------------------------------------------------------------- diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index d6ed11c2f..5a26a0b6e 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -134,9 +134,10 @@ static void ErrorCallback(int error, const char *description); // Window callbacks events static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized -static void WindowPosCallback(GLFWwindow* window, int x, int y); // GLFW3 WindowPos Callback, runs when window is moved +static void FramebufferSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 FramebufferSize Callback, runs when window is resized +static void WindowPosCallback(GLFWwindow *window, int x, int y); // GLFW3 WindowPos Callback, runs when window is moved static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored -static void WindowMaximizeCallback(GLFWwindow* window, int maximized); // GLFW3 Window Maximize Callback, runs when window is maximized +static void WindowMaximizeCallback(GLFWwindow *window, int maximized); // GLFW3 Window Maximize Callback, runs when window is maximized static void WindowFocusCallback(GLFWwindow *window, int focused); // GLFW3 WindowFocus Callback, runs when window get/lose focus static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); // GLFW3 Window Drop Callback, runs when drop files into window static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float scaley); // GLFW3 Window Content Scale Callback, runs when window changes scale @@ -205,7 +206,6 @@ void ToggleFullscreen(void) glfwSetWindowMonitor(platform.handle, monitor, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); } - } else { @@ -231,22 +231,22 @@ void ToggleBorderlessWindowed(void) bool wasOnFullscreen = false; if (CORE.Window.fullscreen) { - // fullscreen already saves the previous position so it does not need to be set here again + // Fullscreen already saves the previous position so it does not need to be set here again ToggleFullscreen(); wasOnFullscreen = true; } - const int monitor = GetCurrentMonitor(); - int monitorCount; + int monitorCount = 0; GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + const int monitor = GetCurrentMonitor(); if ((monitor >= 0) && (monitor < monitorCount)) { const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); - if (mode) + if (mode != NULL) { - if (!IsWindowState(FLAG_BORDERLESS_WINDOWED_MODE)) + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) { // Store screen position and size // NOTE: If it was on fullscreen, screen position was already stored, so skip setting it here @@ -286,6 +286,14 @@ void ToggleBorderlessWindowed(void) glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_TRUE); FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); + // Make sure to restore size to HighDPI + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + Vector2 scaleDpi = GetWindowScaleDPI(); + CORE.Window.previousScreen.width *= scaleDpi.x; + CORE.Window.previousScreen.height *= scaleDpi.y; + } + // Return previous screen size and position // NOTE: The order matters here, it must set size first, then set position, otherwise the screen will be positioned incorrectly glfwSetWindowMonitor( @@ -475,13 +483,13 @@ void ClearWindowState(unsigned int flags) // NOTE: This must be handled before FLAG_FULLSCREEN_MODE because ToggleBorderlessWindowed() needs to get some fullscreen values if fullscreen is running if ((FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) && (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE))) { - ToggleBorderlessWindowed(); // NOTE: Window state flag updated inside function + ToggleBorderlessWindowed(); // NOTE: Window state flag updated inside function } // State change: FLAG_FULLSCREEN_MODE if ((FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) && (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE))) { - ToggleFullscreen(); // NOTE: Window state flag updated inside function + ToggleFullscreen(); // NOTE: Window state flag updated inside function } // State change: FLAG_WINDOW_RESIZABLE @@ -1329,7 +1337,8 @@ void PollInputEvents(void) CORE.Window.resizedLastFrame = false; - if ((CORE.Window.eventWaiting) || (IsWindowState(FLAG_WINDOW_MINIMIZED) && !IsWindowState(FLAG_WINDOW_ALWAYS_RUN))) + if ((CORE.Window.eventWaiting) || + (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED) && !FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN))) { glfwWaitEvents(); // Wait for in input events before continue (drawing is paused) CORE.Time.previous = GetTime(); @@ -1436,17 +1445,15 @@ int InitPlatform(void) // HACK: Most of this was written before GLFW_SCALE_FRAMEBUFFER existed and // was enabled by default. Disabling it gets back the old behavior. A - // complete fix will require removing a lot of CORE.Window.render - // manipulation code + // complete fix will require removing a lot of CORE.Window.render manipulation code // NOTE: This currently doesn't work on macOS(see #5185), so we skip it there // when FLAG_WINDOW_HIGHDPI is *unset* #if !defined(__APPLE__) - glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE); + glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE); #endif if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { - // since we skipped it before, now make sure to set this on macOS #if defined(__APPLE__) glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE); #endif @@ -1639,6 +1646,7 @@ int InitPlatform(void) return -1; } + // NOTE: Not considering scale factor now, considered below CORE.Window.render.width = CORE.Window.screen.width; CORE.Window.render.height = CORE.Window.screen.height; } @@ -1666,11 +1674,11 @@ int InitPlatform(void) int fbWidth = CORE.Window.screen.width; int fbHeight = CORE.Window.screen.height; + #if !defined(__APPLE__) if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { // NOTE: On APPLE platforms system should manage window/input scaling and also framebuffer scaling - // Framebuffer scaling should be activated with: glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); - #if !defined(__APPLE__) + // Framebuffer scaling is activated with: glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); glfwGetFramebufferSize(platform.handle, &fbWidth, &fbHeight); // Screen scaling matrix is required in case desired screen area is different from display area @@ -1678,8 +1686,8 @@ int InitPlatform(void) // Mouse input scaling for the new screen size SetMouseScale((float)CORE.Window.screen.width/fbWidth, (float)CORE.Window.screen.height/fbHeight); - #endif } + #endif CORE.Window.render.width = fbWidth; CORE.Window.render.height = fbHeight; @@ -1735,28 +1743,24 @@ int InitPlatform(void) // Initialize input events callbacks //---------------------------------------------------------------------------- // Set window callback events - glfwSetWindowSizeCallback(platform.handle, WindowSizeCallback); // NOTE: Resizing not allowed by default! + glfwSetWindowSizeCallback(platform.handle, WindowSizeCallback); // NOTE: Resizing is not enabled by default + glfwSetFramebufferSizeCallback(platform.handle, FramebufferSizeCallback); glfwSetWindowPosCallback(platform.handle, WindowPosCallback); glfwSetWindowMaximizeCallback(platform.handle, WindowMaximizeCallback); glfwSetWindowIconifyCallback(platform.handle, WindowIconifyCallback); glfwSetWindowFocusCallback(platform.handle, WindowFocusCallback); glfwSetDropCallback(platform.handle, WindowDropCallback); - - if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) - { - glfwSetWindowContentScaleCallback(platform.handle, WindowContentScaleCallback); - } + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) glfwSetWindowContentScaleCallback(platform.handle, WindowContentScaleCallback); // Set input callback events glfwSetKeyCallback(platform.handle, KeyCallback); glfwSetCharCallback(platform.handle, CharCallback); glfwSetMouseButtonCallback(platform.handle, MouseButtonCallback); - glfwSetCursorPosCallback(platform.handle, MouseCursorPosCallback); // Track mouse position changes + glfwSetCursorPosCallback(platform.handle, MouseCursorPosCallback); // Track mouse position changes glfwSetScrollCallback(platform.handle, MouseScrollCallback); glfwSetCursorEnterCallback(platform.handle, CursorEnterCallback); glfwSetJoystickCallback(JoystickCallback); - - glfwSetInputMode(platform.handle, GLFW_LOCK_KEY_MODS, GLFW_TRUE); // Enable lock keys modifiers (CAPS, NUM) + glfwSetInputMode(platform.handle, GLFW_LOCK_KEY_MODS, GLFW_TRUE); // Enable lock keys modifiers (CAPS, NUM) // Retrieve gamepad names for (int i = 0; i < MAX_GAMEPADS; i++) @@ -1814,79 +1818,108 @@ void ClosePlatform(void) #endif } -// GLFW3 Error Callback, runs on GLFW3 error +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +// NOTE: Those functions are only required for current platform +//---------------------------------------------------------------------------------- + +// GLFW3: Error callback, runs on GLFW3 error static void ErrorCallback(int error, const char *description) { TRACELOG(LOG_WARNING, "GLFW: Error: %i Description: %s", error, description); } -// GLFW3 WindowSize Callback, runs when window is resizedLastFrame +// GLFW3: Window size change callback, runs when window is resized // NOTE: Window resizing not enabled by default, use SetConfigFlags() static void WindowSizeCallback(GLFWwindow *window, int width, int height) +{ + // Nothing to do for now on window resize... +} + +// GLFW3: Framebuffer size change callback, runs when framebuffer is resized +static void FramebufferSizeCallback(GLFWwindow *window, int width, int height) { // WARNING: On window minimization, callback is called, // but we don't want to change internal screen values, it breaks things if ((width == 0) || (height == 0)) return; // Reset viewport and projection matrix for new size + // NOTE: Stores current render size: CORE.Window.render SetupViewport(width, height); + // Set render size CORE.Window.currentFbo.width = width; CORE.Window.currentFbo.height = height; CORE.Window.resizedLastFrame = true; if (IsWindowFullscreen()) return; - // if we are doing automatic DPI scaling, then the "screen" size is divided by the window scale - if (IsWindowState(FLAG_WINDOW_HIGHDPI)) + // Check if render size was actually scaled for high-dpi + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { - width = (int)(width/GetWindowScaleDPI().x); - height = (int)(height/GetWindowScaleDPI().y); + Vector2 scaleDpi = GetWindowScaleDPI(); + width = (int)((float)width/scaleDpi.x); + height = (int)((float)height/scaleDpi.y); } - // Set render size - CORE.Window.render.width = width; - CORE.Window.render.height = height; - // Set current screen size CORE.Window.screen.width = width; CORE.Window.screen.height = height; // WARNING: If using a render texture, it is not scaled to new size } -static void WindowPosCallback(GLFWwindow* window, int x, int y) + +// GLFW3: Window position callback, runs when window position changes +static void WindowPosCallback(GLFWwindow *window, int x, int y) { // Set current window position CORE.Window.position.x = x; CORE.Window.position.y = y; } + +// GLFW3: Window content scale callback, runs on monitor content scale change detected static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float scaley) { + float fbWidth = (float)CORE.Window.screen.width*scalex; + float fbHeight = (float)CORE.Window.screen.height*scaley; + +#if !defined(__APPLE__) + // NOTE: On APPLE platforms system should manage window/input scaling and also framebuffer scaling + // Framebuffer scaling is activated with: glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); CORE.Window.screenScale = MatrixScale(scalex, scaley, 1.0f); + + // Mouse input scaling for the new screen size + SetMouseScale(1.0f/scalex, 1.0f/scaley); +#endif + + CORE.Window.render.width = (int)fbWidth; + CORE.Window.render.height = (int)fbHeight; + CORE.Window.currentFbo.width = (int)fbWidth; + CORE.Window.currentFbo.height = (int)fbHeight; } -// GLFW3 WindowIconify Callback, runs when window is minimized/restored +// GLFW3: Window iconify callback, runs when window is minimized/restored static void WindowIconifyCallback(GLFWwindow *window, int iconified) { if (iconified) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // The window was iconified else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // The window was restored } -// GLFW3 WindowMaximize Callback, runs when window is maximized/restored +// GLFW3: Window maximize callback, runs when window is maximized/restored static void WindowMaximizeCallback(GLFWwindow *window, int maximized) { if (maximized) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // The window was maximized else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); // The window was restored } -// GLFW3 WindowFocus Callback, runs when window get/lose focus +// GLFW3: Window focus callback, runs when window get/lose focus static void WindowFocusCallback(GLFWwindow *window, int focused) { if (focused) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window was focused else FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window lost focus } -// GLFW3 Window Drop Callback, runs when drop files into window +// GLFW3: Window drop callback, runs when files are dropped into window static void WindowDropCallback(GLFWwindow *window, int count, const char **paths) { if (count > 0) @@ -1914,7 +1947,7 @@ static void WindowDropCallback(GLFWwindow *window, int count, const char **paths } } -// GLFW3 Keyboard Callback, runs on key pressed +// GLFW3: Keyboard callback, runs on key pressed static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) { if (key < 0) return; // Security check, macOS fn key generates -1 @@ -1941,7 +1974,7 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i if ((key == CORE.Input.Keyboard.exitKey) && (action == GLFW_PRESS)) glfwSetWindowShouldClose(platform.handle, GLFW_TRUE); } -// GLFW3 Char Callback, get unicode codepoint value +// GLFW3: Char callback, runs on key pressed to get unicode codepoint value static void CharCallback(GLFWwindow *window, unsigned int codepoint) { // NOTE: Registers any key down considering OS keyboard layout but @@ -1958,7 +1991,7 @@ static void CharCallback(GLFWwindow *window, unsigned int codepoint) } } -// GLFW3 Mouse Button Callback, runs on mouse button pressed +// GLFW3: Mouse button callback, runs on mouse button pressed static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods) { // WARNING: GLFW could only return GLFW_PRESS (1) or GLFW_RELEASE (0) for now, @@ -1994,7 +2027,7 @@ static void MouseButtonCallback(GLFWwindow *window, int button, int action, int #endif } -// GLFW3 Cursor Position Callback, runs on mouse move +// GLFW3: Cursor position callback, runs on mouse movement static void MouseCursorPosCallback(GLFWwindow *window, double x, double y) { CORE.Input.Mouse.currentPosition.x = (float)x; @@ -2025,20 +2058,20 @@ static void MouseCursorPosCallback(GLFWwindow *window, double x, double y) #endif } -// GLFW3 Scrolling Callback, runs on mouse wheel +// GLFW3: Mouse wheel scroll callback, runs on mouse wheel changes static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset) { CORE.Input.Mouse.currentWheelMove = (Vector2){ (float)xoffset, (float)yoffset }; } -// GLFW3 CursorEnter Callback, when cursor enters the window +// GLFW3: Cursor ennter callback, when cursor enters the window static void CursorEnterCallback(GLFWwindow *window, int enter) { if (enter) CORE.Input.Mouse.cursorOnScreen = true; else CORE.Input.Mouse.cursorOnScreen = false; } -// GLFW3 Joystick Connected/Disconnected Callback +// GLFW3: Joystick connected/disconnected callback static void JoystickCallback(int jid, int event) { if (event == GLFW_CONNECTED) diff --git a/src/rcore.c b/src/rcore.c index 19228cc8e..23d125b5a 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -816,30 +816,22 @@ int GetScreenHeight(void) // Get current render width which is equal to screen width*dpi scale int GetRenderWidth(void) { - if (CORE.Window.usingFbo) return CORE.Window.currentFbo.width; - int width = 0; -#if defined(__APPLE__) - Vector2 scale = GetWindowScaleDPI(); - width = (int)((float)CORE.Window.render.width*scale.x); -#else - width = CORE.Window.render.width; -#endif + + if (CORE.Window.usingFbo) return CORE.Window.currentFbo.width; + else width = CORE.Window.render.width; + return width; } // Get current screen height which is equal to screen height*dpi scale int GetRenderHeight(void) { - if (CORE.Window.usingFbo) return CORE.Window.currentFbo.height; - int height = 0; -#if defined(__APPLE__) - Vector2 scale = GetWindowScaleDPI(); - height = (int)((float)CORE.Window.render.height*scale.y); -#else - height = CORE.Window.render.height; -#endif + + if (CORE.Window.usingFbo) return CORE.Window.currentFbo.height; + else height = CORE.Window.render.height; + return height; } @@ -1833,7 +1825,7 @@ void TakeScreenshot(const char *fileName) // Apply a scale if we are doing HIGHDPI auto-scaling Vector2 scale = { 1.0f, 1.0f }; - if (IsWindowState(FLAG_WINDOW_HIGHDPI)) scale = GetWindowScaleDPI(); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) scale = GetWindowScaleDPI(); unsigned char *imgData = rlReadScreenPixels((int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); Image image = { imgData, (int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y), 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; From 4b760091da1da052a23f180a7e01c5d763d2875f Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 17 Dec 2025 21:23:25 +0100 Subject: [PATCH 242/430] REVIEWED: Window scaling with HighDPI on macOS #5059 --- examples/core/core_highdpi_testbed.c | 2 ++ src/platforms/rcore_desktop_glfw.c | 26 ++++++++++++-------------- src/rcore.c | 7 ------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/examples/core/core_highdpi_testbed.c b/examples/core/core_highdpi_testbed.c index 7710e7595..6a036bbfc 100644 --- a/examples/core/core_highdpi_testbed.c +++ b/examples/core/core_highdpi_testbed.c @@ -47,6 +47,8 @@ int main(void) mousePos = GetMousePosition(); currentMonitor = GetCurrentMonitor(); scaleDpi = GetWindowScaleDPI(); + + if (IsKeyPressed(KEY_SPACE)) ToggleBorderlessWindowed(); //---------------------------------------------------------------------------------- // Draw diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 5a26a0b6e..fc839070a 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -246,7 +246,7 @@ void ToggleBorderlessWindowed(void) if (mode != NULL) { - if (!FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) { // Store screen position and size // NOTE: If it was on fullscreen, screen position was already stored, so skip setting it here @@ -286,6 +286,7 @@ void ToggleBorderlessWindowed(void) glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_TRUE); FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); + #if !defined(__APPLE__) // Make sure to restore size to HighDPI if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { @@ -293,6 +294,7 @@ void ToggleBorderlessWindowed(void) CORE.Window.previousScreen.width *= scaleDpi.x; CORE.Window.previousScreen.height *= scaleDpi.y; } + #endif // Return previous screen size and position // NOTE: The order matters here, it must set size first, then set position, otherwise the screen will be positioned incorrectly @@ -1443,22 +1445,14 @@ int InitPlatform(void) if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TRANSPARENT)) glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); // Transparent framebuffer else glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_FALSE); // Opaque framebuffer - // HACK: Most of this was written before GLFW_SCALE_FRAMEBUFFER existed and - // was enabled by default. Disabling it gets back the old behavior. A - // complete fix will require removing a lot of CORE.Window.render manipulation code - // NOTE: This currently doesn't work on macOS(see #5185), so we skip it there - // when FLAG_WINDOW_HIGHDPI is *unset* -#if !defined(__APPLE__) - glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE); -#endif - if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { #if defined(__APPLE__) glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE); #endif // Resize window content area based on the monitor content scale - // NOTE: This hint only has an effect on platforms where screen coordinates and pixels always map 1:1 such as Windows and X11 + // NOTE: This hint only has an effect on platforms where screen coordinates and + // pixels always map 1:1 such as Windows and X11 // On platforms like macOS the resolution of the framebuffer is changed independently of the window size glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); // Scale content area based on the monitor content scale where window is placed on #if defined(__APPLE__) @@ -1674,7 +1668,6 @@ int InitPlatform(void) int fbWidth = CORE.Window.screen.width; int fbHeight = CORE.Window.screen.height; - #if !defined(__APPLE__) if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { // NOTE: On APPLE platforms system should manage window/input scaling and also framebuffer scaling @@ -1683,11 +1676,11 @@ int InitPlatform(void) // Screen scaling matrix is required in case desired screen area is different from display area CORE.Window.screenScale = MatrixScale((float)fbWidth/CORE.Window.screen.width, (float)fbHeight/CORE.Window.screen.height, 1.0f); - +#if !defined(__APPLE__) // Mouse input scaling for the new screen size SetMouseScale((float)CORE.Window.screen.width/fbWidth, (float)CORE.Window.screen.height/fbHeight); +#endif } - #endif CORE.Window.render.width = fbWidth; CORE.Window.render.height = fbHeight; @@ -1834,11 +1827,14 @@ static void ErrorCallback(int error, const char *description) static void WindowSizeCallback(GLFWwindow *window, int width, int height) { // Nothing to do for now on window resize... + //TRACELOG(LOG_INFO, "GLFW3: Window size callback called [%i,%i]", width, height); } // GLFW3: Framebuffer size change callback, runs when framebuffer is resized static void FramebufferSizeCallback(GLFWwindow *window, int width, int height) { + //TRACELOG(LOG_INFO, "GLFW3: Window framebuffer size callback called [%i,%i]", width, height); + // WARNING: On window minimization, callback is called, // but we don't want to change internal screen values, it breaks things if ((width == 0) || (height == 0)) return; @@ -1880,6 +1876,8 @@ static void WindowPosCallback(GLFWwindow *window, int x, int y) // GLFW3: Window content scale callback, runs on monitor content scale change detected static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float scaley) { + TRACELOG(LOG_INFO, "GLFW3: Window content scale changed, scale: [%.2f,%.2f]", scalex, scaley); + float fbWidth = (float)CORE.Window.screen.width*scalex; float fbHeight = (float)CORE.Window.screen.height*scaley; diff --git a/src/rcore.c b/src/rcore.c index 23d125b5a..1f47efcb9 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -3815,14 +3815,7 @@ void SetupViewport(int width, int height) CORE.Window.render.height = height; // Set viewport width and height - // NOTE: We consider render size (scaled) and offset in case black bars are required and - // render area does not match full display area (this situation is only applicable on fullscreen mode) -#if defined(__APPLE__) - Vector2 scale = GetWindowScaleDPI(); - rlViewport(CORE.Window.renderOffset.x/2*scale.x, CORE.Window.renderOffset.y/2*scale.y, (CORE.Window.render.width)*scale.x, (CORE.Window.render.height)*scale.y); -#else rlViewport(CORE.Window.renderOffset.x/2, CORE.Window.renderOffset.y/2, CORE.Window.render.width, CORE.Window.render.height); -#endif rlMatrixMode(RL_PROJECTION); // Switch to projection matrix rlLoadIdentity(); // Reset current matrix (projection) From ca578b8b08a338722058ca60c675ae6c42497d80 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 18 Dec 2025 17:03:53 +0100 Subject: [PATCH 243/430] Update raylib.sln --- projects/VS2022/raylib.sln | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index f0ff823da..8a6350a36 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -5543,7 +5543,7 @@ Global {19CA0070-B4B2-4394-90B7-D0C259AA35BA} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {2CCCD9E4-9058-4291-BD89-39C979F0CA1E} = {278D8859-20B1-428F-8448-064F46E1F021} {9DB1F875-6E65-4195-B23F-ED8095C0B99C} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A} - {52BA9067-A5FC-4CE8-82AD-7204ECFDEF9F} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} + {52BA9067-A5FC-4CE8-82AD-7204ECFDEF9F} = {AF5BEC5C-1F2B-4DA8-B12D-D09FE569237C} {8E132D5A-2C00-48D0-8747-97E41356F26F} = {278D8859-20B1-428F-8448-064F46E1F021} {A4662163-83E7-4309-8CAA-B0BF13655FE6} = {AF5BEC5C-1F2B-4DA8-B12D-D09FE569237C} {5F4B766F-DD52-4B53-B6C3-BC7611E17F20} = {278D8859-20B1-428F-8448-064F46E1F021} @@ -5559,7 +5559,7 @@ Global {124935CC-73BB-489E-92E8-4F922A85DB5D} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {AC215730-2B5F-4498-B7F5-5DB80AEFCA5F} = {278D8859-20B1-428F-8448-064F46E1F021} {0835E6BF-0170-4E99-A55C-E06E1EF4C3B2} = {278D8859-20B1-428F-8448-064F46E1F021} - {EA4AD5A7-DB95-43C0-9A67-2D94146BCF91} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} + {EA4AD5A7-DB95-43C0-9A67-2D94146BCF91} = {278D8859-20B1-428F-8448-064F46E1F021} {1ACC8236-EF4E-44B0-BD0C-AB1D95D5890F} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {9DE2FC01-A839-4F89-8319-9071D4C54821} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {2F578155-D51F-4C03-AB7F-5C5122CA46CC} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} From 720dd22491cdc4a29ec0ea87b572d9380872198c Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 18 Dec 2025 17:04:58 +0100 Subject: [PATCH 244/430] REVIEWED: `rlLoadTexture()`, un complete texture do to issue on mipmap loading #5416 --- src/rlgl.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 97f892eb5..45a68053c 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -3353,8 +3353,8 @@ unsigned int rlLoadTexture(const void *data, int width, int height, int format, mipWidth /= 2; mipHeight /= 2; - mipOffset += mipSize; // Increment offset position to next mipmap - if (data != NULL) dataPtr += mipSize; // Increment data pointer to next mipmap + mipOffset += mipSize; // Increment offset position to next mipmap + if (data != NULL) dataPtr += mipSize; // Increment data pointer to next mipmap // Security check for NPOT textures if (mipWidth < 1) mipWidth = 1; @@ -3392,8 +3392,14 @@ unsigned int rlLoadTexture(const void *data, int width, int height, int format, glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - // Define thee maximum number of mipmap levels to be used, 0 is default texture size + // Define the maximum number of mipmap levels to be used, 0 is base texture size + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mipmapCount - 1); + + // Check if the loaded texture with mipmaps is complete, + // uncomplete textures will draw in black if mipmap filtering is required + //GLint complete = 0; + //glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_IMMUTABLE_FORMAT, &complete); } #endif @@ -5232,7 +5238,7 @@ static int rlGetPixelDataSize(int width, int height, int format) // Most compressed formats works on 4x4 blocks, // if texture is smaller, minimum dataSize is 8 or 16 - if ((width < 4) && (height < 4)) + if ((width <= 4) && (height <= 4)) { if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; else if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; From 66392fe0ae36420a470caacf41778f575a11c8d5 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 19 Dec 2025 00:06:44 +0100 Subject: [PATCH 245/430] REVIEWED: `rlGetPixelDataSize()`, correct compressed data size calculation per blocks #5416 --- src/rlgl.h | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 45a68053c..cda64896c 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -5224,24 +5224,36 @@ static int rlGetPixelDataSize(int width, int height, int format) case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: // 8 bytes per each 4x4 block + { + int blockWidth = (width + 3)/4; + int blockHeight = (height + 3)/4; + dataSize = blockWidth*blockHeight*8; + } break; case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: - case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; - case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; + case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: // 16 bytes per each 4x4 block + { + int blockWidth = (width + 3)/4; + int blockHeight = (height + 3)/4; + dataSize = blockWidth*blockHeight*16; + } break; + case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: // 4 bytes per each 4x4 block + { + int blockWidth = (width + 3)/4; + int blockHeight = (height + 3)/4; + dataSize = blockWidth*blockHeight*4; + } break; default: break; } - double bytesPerPixel = (double)bpp/8.0; - 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 - if ((width <= 4) && (height <= 4)) + // Compute dataSize for uncompressed texture data (no blocks) + if ((format >= RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) && + (format <= RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16)) { - if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; - else if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; + double bytesPerPixel = (double)bpp/8.0; + dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes } return dataSize; From f16fb065eaa9efbb02fcfc1bc5e43c835792c9da Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 19 Dec 2025 01:15:34 +0100 Subject: [PATCH 246/430] Update rcore_template.c --- src/platforms/rcore_template.c | 89 +++------------------------------- 1 file changed, 8 insertions(+), 81 deletions(-) diff --git a/src/platforms/rcore_template.c b/src/platforms/rcore_template.c index bc03a3cdb..1f8c5242b 100644 --- a/src/platforms/rcore_template.c +++ b/src/platforms/rcore_template.c @@ -54,11 +54,6 @@ typedef struct { // TODO: Define the platform specific variables required - // Display data - EGLDisplay device; // Native display device (physical screen connection) - EGLSurface surface; // Surface to draw on, framebuffers (connected to context) - EGLContext context; // Graphic context, mode in which drawing can be done - EGLConfig config; // Graphic config } PlatformData; //---------------------------------------------------------------------------------- @@ -346,10 +341,10 @@ void SwapScreenBuffer(void) double GetTime(void) { double time = 0.0; + struct timespec ts = { 0 }; clock_gettime(CLOCK_MONOTONIC, &ts); unsigned long long int nanoSeconds = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; - time = (double)(nanoSeconds - CORE.Time.base)*1e-9; // Elapsed time since InitTimer() return time; @@ -366,7 +361,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: Load url using default browser } } @@ -462,86 +457,18 @@ int InitPlatform(void) CORE.Window.fullscreen = true; FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); - EGLint samples = 0; - EGLint sampleBuffer = 0; if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) { - samples = 4; - sampleBuffer = 1; + // TODO: Enable MSAA + TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); } - const EGLint framebufferAttribs[] = - { - EGL_RENDERABLE_TYPE, (rlGetVersion() == RL_OPENGL_ES_30)? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT, // Type of context support - EGL_RED_SIZE, 8, // RED color bit depth (alternative: 5) - EGL_GREEN_SIZE, 8, // GREEN color bit depth (alternative: 6) - EGL_BLUE_SIZE, 8, // BLUE color bit depth (alternative: 5) - //EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI) - EGL_DEPTH_SIZE, 16, // Depth buffer size (Required to use Depth testing!) - //EGL_STENCIL_SIZE, 8, // Stencil buffer size - EGL_SAMPLE_BUFFERS, sampleBuffer, // Activate MSAA - EGL_SAMPLES, samples, // 4x Antialiasing if activated (Free on MALI GPUs) - EGL_NONE - }; + // TODO: Init display and graphic device - const EGLint contextAttribs[] = - { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - - EGLint numConfigs = 0; - - // Get an EGL device connection - platform.device = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (platform.device == EGL_NO_DISPLAY) - { - TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); - return false; - } - - // Initialize the EGL device connection - if (eglInitialize(platform.device, NULL, NULL) == EGL_FALSE) - { - // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred. - TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); - return false; - } - - // Get an appropriate EGL framebuffer configuration - eglChooseConfig(platform.device, framebufferAttribs, &platform.config, 1, &numConfigs); - - // Set rendering API - eglBindAPI(EGL_OPENGL_ES_API); - - // Create an EGL rendering context - platform.context = eglCreateContext(platform.device, platform.config, EGL_NO_CONTEXT, contextAttribs); - if (platform.context == EGL_NO_CONTEXT) - { - TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context"); - return -1; - } - - // Create an EGL window surface - EGLint displayFormat = 0; - - // EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is guaranteed to be accepted by ANativeWindow_setBuffersGeometry() - // As soon as we picked a EGLConfig, we can safely reconfigure the ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID - eglGetConfigAttrib(platform.device, platform.config, EGL_NATIVE_VISUAL_ID, &displayFormat); - - // Android specific call - ANativeWindow_setBuffersGeometry(platform.app->window, 0, 0, displayFormat); // Force use of native display size - - platform.surface = eglCreateWindowSurface(platform.device, platform.config, platform.app->window, NULL); - - // There must be at least one frame displayed before the buffers are swapped - eglSwapInterval(platform.device, 1); - - EGLBoolean result = eglMakeCurrent(platform.device, platform.surface, platform.surface, platform.context); - - // Check surface and context activation - if (result != EGL_FALSE) + // TODO: Check display, device and context activation + bool result = true; + if (result) { CORE.Window.ready = true; From 13f9112d8c069ed333acf72c2c1b94a0533c6dc1 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 19 Dec 2025 01:16:34 +0100 Subject: [PATCH 247/430] Update rcore_desktop_sdl.c --- src/platforms/rcore_desktop_sdl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 7316f3c14..add1de6ad 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -679,6 +679,7 @@ void ClearWindowState(unsigned int flags) { TRACELOG(LOG_WARNING, "ClearWindowState() - FLAG_WINDOW_TRANSPARENT is not supported on PLATFORM_DESKTOP_SDL"); } + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) { // NOTE: There also doesn't seem to be a feature to disable high DPI once enabled TRACELOG(LOG_WARNING, "ClearWindowState() - FLAG_WINDOW_HIGHDPI is not supported on PLATFORM_DESKTOP_SDL"); @@ -1474,7 +1475,7 @@ void PollInputEvents(void) const int height = event.window.data2; SetupViewport(width, height); // if we are doing automatic DPI scaling, then the "screen" size is divided by the window scale - if (IsWindowState(FLAG_WINDOW_HIGHDPI)) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { CORE.Window.screen.width = (int)(width/GetWindowScaleDPI().x); CORE.Window.screen.height = (int)(height/GetWindowScaleDPI().y); From b9446863d7b75e8b056186e3cca5a4f47837462d Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 20 Dec 2025 22:36:44 +0100 Subject: [PATCH 248/430] REXM: RENAMED: `core_high_dpi` --> `core_highdpi_demo` --- examples/Makefile | 2 +- examples/Makefile.Web | 4 +- examples/README.md | 2 +- .../{core_high_dpi.c => core_highdpi_demo.c} | 4 +- ...ore_high_dpi.png => core_highdpi_demo.png} | Bin examples/examples_list.txt | 2 +- ..._dpi.vcxproj => core_highdpi_demo.vcxproj} | 1138 ++++++++--------- projects/VS2022/raylib.sln | 2 +- 8 files changed, 577 insertions(+), 577 deletions(-) rename examples/core/{core_high_dpi.c => core_highdpi_demo.c} (98%) rename examples/core/{core_high_dpi.png => core_highdpi_demo.png} (100%) rename projects/VS2022/examples/{core_high_dpi.vcxproj => core_highdpi_demo.vcxproj} (97%) diff --git a/examples/Makefile b/examples/Makefile index 06a0c2729..bc2afbb3c 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -531,7 +531,7 @@ CORE = \ core/core_delta_time \ core/core_directory_files \ core/core_drop_files \ - core/core_high_dpi \ + core/core_highdpi_demo \ core/core_highdpi_testbed \ core/core_input_actions \ core/core_input_gamepad \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 7101ed7e3..f36113f15 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -519,7 +519,7 @@ CORE = \ core/core_delta_time \ core/core_directory_files \ core/core_drop_files \ - core/core_high_dpi \ + core/core_highdpi_demo \ core/core_highdpi_testbed \ core/core_input_actions \ core/core_input_gamepad \ @@ -783,7 +783,7 @@ core/core_directory_files: core/core_directory_files.c core/core_drop_files: core/core_drop_files.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) -core/core_high_dpi: core/core_high_dpi.c +core/core_highdpi_demo: core/core_highdpi_demo.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) core/core_highdpi_testbed: core/core_highdpi_testbed.c diff --git a/examples/README.md b/examples/README.md index d64ed7608..367bfa0ab 100644 --- a/examples/README.md +++ b/examples/README.md @@ -61,7 +61,7 @@ Examples using raylib [core](../src/rcore.c) module platform functionality: wind | [core_smooth_pixelperfect](core/core_smooth_pixelperfect.c) | core_smooth_pixelperfect | ⭐⭐⭐☆ | 3.7 | 4.0 | [Giancamillo Alessandroni](https://github.com/NotManyIdeasDev) | | [core_random_sequence](core/core_random_sequence.c) | core_random_sequence | ⭐☆☆☆ | 5.0 | 5.0 | [Dalton Overmyer](https://github.com/REDl3east) | | [core_automation_events](core/core_automation_events.c) | core_automation_events | ⭐⭐⭐☆ | 5.0 | 5.0 | [Ramon Santamaria](https://github.com/raysan5) | -| [core_high_dpi](core/core_high_dpi.c) | core_high_dpi | ⭐⭐☆☆ | 5.0 | 5.5 | [Jonathan Marler](https://github.com/marler8997) | +| [core_highdpi_demo](core/core_highdpi_demo.c) | core_highdpi_demo | ⭐⭐☆☆ | 5.0 | 5.5 | [Jonathan Marler](https://github.com/marler8997) | | [core_render_texture](core/core_render_texture.c) | core_render_texture | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | | [core_undo_redo](core/core_undo_redo.c) | core_undo_redo | ⭐⭐⭐☆ | 5.5 | 5.6 | [Ramon Santamaria](https://github.com/raysan5) | | [core_viewport_scaling](core/core_viewport_scaling.c) | core_viewport_scaling | ⭐⭐☆☆ | 5.5 | 5.5 | [Agnis Aldiņš](https://github.com/nezvers) | diff --git a/examples/core/core_high_dpi.c b/examples/core/core_highdpi_demo.c similarity index 98% rename from examples/core/core_high_dpi.c rename to examples/core/core_highdpi_demo.c index 312f0c3e2..b1f706d98 100644 --- a/examples/core/core_high_dpi.c +++ b/examples/core/core_highdpi_demo.c @@ -1,6 +1,6 @@ /******************************************************************************************* * -* raylib [core] example - high dpi +* raylib [core] example - highdpi demo * * Example complexity rating: [★★☆☆] 2/4 * @@ -33,7 +33,7 @@ int main(void) const int screenHeight = 450; SetConfigFlags(FLAG_WINDOW_HIGHDPI | FLAG_WINDOW_RESIZABLE); - InitWindow(screenWidth, screenHeight, "raylib [core] example - high dpi"); + InitWindow(screenWidth, screenHeight, "raylib [core] example - highdpi demo"); SetWindowMinSize(450, 450); int logicalGridDescY = 120; diff --git a/examples/core/core_high_dpi.png b/examples/core/core_highdpi_demo.png similarity index 100% rename from examples/core/core_high_dpi.png rename to examples/core/core_highdpi_demo.png diff --git a/examples/examples_list.txt b/examples/examples_list.txt index 925ad9454..3310cf2d2 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -43,7 +43,7 @@ core;core_custom_frame_control;★★★★;4.0;4.0;2021;2025;"Ramon Santamaria" core;core_smooth_pixelperfect;★★★☆;3.7;4.0;2021;2025;"Giancamillo Alessandroni";@NotManyIdeasDev core;core_random_sequence;★☆☆☆;5.0;5.0;2023;2025;"Dalton Overmyer";@REDl3east core;core_automation_events;★★★☆;5.0;5.0;2023;2025;"Ramon Santamaria";@raysan5 -core;core_high_dpi;★★☆☆;5.0;5.5;2025;2025;"Jonathan Marler";@marler8997 +core;core_highdpi_demo;★★☆☆;5.0;5.5;2025;2025;"Jonathan Marler";@marler8997 core;core_render_texture;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 core;core_undo_redo;★★★☆;5.5;5.6;2025;2025;"Ramon Santamaria";@raysan5 core;core_viewport_scaling;★★☆☆;5.5;5.5;2025;2025;"Agnis Aldiņš";@nezvers diff --git a/projects/VS2022/examples/core_high_dpi.vcxproj b/projects/VS2022/examples/core_highdpi_demo.vcxproj similarity index 97% rename from projects/VS2022/examples/core_high_dpi.vcxproj rename to projects/VS2022/examples/core_highdpi_demo.vcxproj index 11e1b41c0..8a7cdce40 100644 --- a/projects/VS2022/examples/core_high_dpi.vcxproj +++ b/projects/VS2022/examples/core_highdpi_demo.vcxproj @@ -1,569 +1,569 @@ - - - - - Debug.DLL - ARM64 - - - Debug.DLL - Win32 - - - Debug.DLL - x64 - - - Debug - ARM64 - - - Debug - Win32 - - - Debug - x64 - - - Release.DLL - ARM64 - - - Release.DLL - Win32 - - - Release.DLL - x64 - - - Release - ARM64 - - - Release - Win32 - - - Release - x64 - - - - {BCB71111-8505-4B35-8CEF-EC6115DC9D4D} - Win32Proj - core_high_dpi - 10.0 - core_high_dpi - - - - Application - true - $(DefaultPlatformToolset) - Unicode - - - Application - true - $(DefaultPlatformToolset) - Unicode - - - Application - true - $(DefaultPlatformToolset) - Unicode - - - Application - true - $(DefaultPlatformToolset) - Unicode - - - Application - true - $(DefaultPlatformToolset) - Unicode - - - Application - true - $(DefaultPlatformToolset) - Unicode - - - Application - false - $(DefaultPlatformToolset) - true - Unicode - - - Application - false - $(DefaultPlatformToolset) - true - Unicode - - - Application - false - $(DefaultPlatformToolset) - true - Unicode - - - Application - false - $(DefaultPlatformToolset) - true - Unicode - - - Application - false - $(DefaultPlatformToolset) - true - Unicode - - - Application - false - $(DefaultPlatformToolset) - true - Unicode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ - $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ - - - true - $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ - $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ - - - true - $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ - $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ - - - true - $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ - $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ - - - true - $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ - $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ - - - true - $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ - $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ - - - false - $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ - $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ - - - false - $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ - $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ - - - false - $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ - $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ - - - false - $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ - $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ - - - false - $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ - $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ - - - false - $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ - $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ - - - $(SolutionDir)..\..\examples\core - WindowsLocalDebugger - - - $(SolutionDir)..\..\examples\core - WindowsLocalDebugger - - - $(SolutionDir)..\..\examples\core - WindowsLocalDebugger - - - $(SolutionDir)..\..\examples\core - WindowsLocalDebugger - - - $(SolutionDir)..\..\examples\core - WindowsLocalDebugger - - - $(SolutionDir)..\..\examples\core - WindowsLocalDebugger - - - $(SolutionDir)..\..\examples\core - WindowsLocalDebugger - - - $(SolutionDir)..\..\examples\core - WindowsLocalDebugger - - - $(SolutionDir)..\..\examples\core - WindowsLocalDebugger - - - $(SolutionDir)..\..\examples\core - WindowsLocalDebugger - - - $(SolutionDir)..\..\examples\core - WindowsLocalDebugger - - - $(SolutionDir)..\..\examples\core - WindowsLocalDebugger - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) - CompileAsC - $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) - - - Console - true - $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ - raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) - CompileAsC - $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) - /FS %(AdditionalOptions) - - - Console - true - $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ - raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) - CompileAsC - $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) - /FS %(AdditionalOptions) - - - Console - true - $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ - raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) - CompileAsC - $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) - - - Console - true - $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ - raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - - - xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" - Copy Debug DLL to output directory - - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) - CompileAsC - $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) - - - Console - true - $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ - raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - - - xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" - Copy Debug DLL to output directory - - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) - CompileAsC - $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) - - - Console - true - $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ - raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - - - xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" - Copy Debug DLL to output directory - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP - $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) - CompileAsC - true - - - Console - true - true - true - raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP - $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) - CompileAsC - true - - - Console - true - true - true - raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP - $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) - CompileAsC - true - - - Console - true - true - true - raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP - $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) - CompileAsC - true - - - Console - true - true - true - raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ - - - xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" - - - Copy Release DLL to output directory - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP - $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) - CompileAsC - true - - - Console - true - true - true - raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ - - - xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" - - - Copy Release DLL to output directory - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP - $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) - CompileAsC - true - - - Console - true - true - true - raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ - - - xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" - - - Copy Release DLL to output directory - - - - - - - - - - - {e89d61ac-55de-4482-afd4-df7242ebc859} - - - - - - + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {BCB71111-8505-4B35-8CEF-EC6115DC9D4D} + Win32Proj + core_highdpi_demo + 10.0 + core_highdpi_demo + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index 8a6350a36..50fef1cf5 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -57,7 +57,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_custom_logging", "exam EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_drop_files", "examples\core_drop_files.vcxproj", "{0199E349-0701-40BC-8A7F-06A54FFA3E7C}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_high_dpi", "examples\core_high_dpi.vcxproj", "{BCB71111-8505-4B35-8CEF-EC6115DC9D4D}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_highdpi_demo", "examples\core_highdpi_demo.vcxproj", "{BCB71111-8505-4B35-8CEF-EC6115DC9D4D}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_input_gamepad", "examples\core_input_gamepad.vcxproj", "{8F19E3DA-8929-4000-87B5-3CA6929636CC}" EndProject From 3212becc915a8a491712c7c7dc81ec5581cca240 Mon Sep 17 00:00:00 2001 From: SabeDoesThings <122580233+SabeDoesThings@users.noreply.github.com> Date: Sun, 21 Dec 2025 13:15:38 -0600 Subject: [PATCH 249/430] Update BINDINGS.md (#5421) --- BINDINGS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BINDINGS.md b/BINDINGS.md index 07f590d7d..ee7da46f4 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -76,6 +76,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [raylib-rs](https://github.com/raylib-rs/raylib-rs) | **5.5** | [Rust](https://www.rust-lang.org) | Zlib | | [raylib-ruby](https://github.com/wilsonsilva/raylib-ruby) | 4.5 | [Ruby](https://www.ruby-lang.org) | Zlib | | [Relib](https://github.com/RedCubeDev-ByteSpace/Relib) | 3.5 | [ReCT](https://github.com/RedCubeDev-ByteSpace/ReCT) | **???** | +| [ringraylib5](https://github.com/ring-lang/ring/tree/master/extensions/ringraylib5) | **5.0** | [Ring](https://ring-lang.github.io/) | **???** | | [racket-raylib](https://github.com/eutro/racket-raylib) | **5.5** | [Racket](https://racket-lang.org) | MIT/Apache-2.0 | | [raylib-swift](https://github.com/STREGAsGate/Raylib) | 4.0 | [Swift](https://swift.org) | MIT | | [raylib-scopes](https://github.com/salotz/raylib-scopes) | auto | [Scopes](http://scopes.rocks) | MIT | From 85167509751622bdf6c53ad559b036dcbcba476d Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 22 Dec 2025 20:29:57 +0100 Subject: [PATCH 250/430] Remove internal function --- src/platforms/rcore_desktop_glfw.c | 41 +++++++++++++++--------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index fc839070a..4e4e473d3 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -156,8 +156,6 @@ static void *AllocateWrapper(size_t size, void *user); static void *ReallocateWrapper(void *block, size_t size, void *user); // GLFW3 GLFWreallocatefun, wrapps around RL_REALLOC macro static void DeallocateWrapper(void *block, void *user); // GLFW3 GLFWdeallocatefun, wraps around RL_FREE macro -static void SetDimensionsFromMonitor(GLFWmonitor *monitor); // Set screen dimensions from monitor/display dimensions - //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- @@ -1535,11 +1533,20 @@ int InitPlatform(void) monitor = glfwGetPrimaryMonitor(); if (!monitor) { - TRACELOG(LOG_WARNING, "GLFW: Failed to get primary monitor"); - return -1; + TRACELOG(LOG_WARNING, "GLFW: Failed to get primary monitor"); + return -1; } - SetDimensionsFromMonitor(monitor); + // Set dimensions from monitor + GLFWvidmode *mode = glfwGetVideoMode(monitor); + + // Default display resolution to that of the current mode + CORE.Window.display.width = mode->width; + CORE.Window.display.height = mode->height; + + // Set screen width/height to the display width/height if they are 0 + if (CORE.Window.screen.width == 0) CORE.Window.screen.width = CORE.Window.display.width; + if (CORE.Window.screen.height == 0) CORE.Window.screen.height = CORE.Window.display.height; // Remember center for switching from fullscreen to window if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width)) @@ -1628,7 +1635,15 @@ int InitPlatform(void) if (monitorIndex < monitorCount) { monitor = monitors[monitorIndex]; - SetDimensionsFromMonitor(monitor); + GLFWvidmode *mode = glfwGetVideoMode(monitor); + + // Default display resolution to that of the current mode + CORE.Window.display.width = mode->width; + CORE.Window.display.height = mode->height; + + // Set screen width/height to the display width/height if they are 0 + if (CORE.Window.screen.width == 0) CORE.Window.screen.width = CORE.Window.display.width; + if (CORE.Window.screen.height == 0) CORE.Window.screen.height = CORE.Window.display.height; if (requestWindowedFullscreen) glfwSetWindowSize(platform.handle, CORE.Window.screen.width, CORE.Window.screen.height); } @@ -2085,20 +2100,6 @@ static void JoystickCallback(int jid, int event) } } -// Set screen dimensions from monitor/display dimensions -static void SetDimensionsFromMonitor(GLFWmonitor *monitor) -{ - const GLFWvidmode *mode = glfwGetVideoMode(monitor); - - // Default display resolution to that of the current mode - CORE.Window.display.width = mode->width; - CORE.Window.display.height = mode->height; - - // Set screen width/height to the display width/height if they are 0 - if (CORE.Window.screen.width == 0) CORE.Window.screen.width = CORE.Window.display.width; - if (CORE.Window.screen.height == 0) CORE.Window.screen.height = CORE.Window.display.height; -} - #ifdef _WIN32 # define WIN32_CLIPBOARD_IMPLEMENTATION # include "../external/win32_clipboard.h" From e4baf682abae07ea64d028b66259524834a8d74e Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 22 Dec 2025 20:30:11 +0100 Subject: [PATCH 251/430] Update rtext.c --- src/rtext.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/rtext.c b/src/rtext.c index f04f9ade4..7c25fde0b 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -700,7 +700,11 @@ GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSiz switch (type) { case FONT_DEFAULT: - case FONT_BITMAP: glyphs[k].image.data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, cp, &cpWidth, &cpHeight, &glyphs[k].offsetX, &glyphs[k].offsetY); break; + case FONT_BITMAP: + { + glyphs[k].image.data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, cp, + &cpWidth, &cpHeight, &glyphs[k].offsetX, &glyphs[k].offsetY); + } break; case FONT_SDF: { if (cp != 32) From f27f2d097f92ee38235f88a9fe01829469c5f454 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 22 Dec 2025 22:48:08 +0100 Subject: [PATCH 252/430] REVIEWED: HighDPI support on macOS (when requested by app) Tested on two monitors with different DPI configuration, for HigDPI enabled and not, including window resizing (with framebuffer resizing if required). Verified mouse coordinates follow the requested screen size. --- src/platforms/rcore_desktop_glfw.c | 65 +++++++++++++++++------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 4e4e473d3..8a2abf6c0 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -135,12 +135,12 @@ static void ErrorCallback(int error, const char *description); // Window callbacks events static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized static void FramebufferSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 FramebufferSize Callback, runs when window is resized +static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float scaley); // GLFW3 Window Content Scale Callback, runs when window changes scale static void WindowPosCallback(GLFWwindow *window, int x, int y); // GLFW3 WindowPos Callback, runs when window is moved static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored static void WindowMaximizeCallback(GLFWwindow *window, int maximized); // GLFW3 Window Maximize Callback, runs when window is maximized static void WindowFocusCallback(GLFWwindow *window, int focused); // GLFW3 WindowFocus Callback, runs when window get/lose focus static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); // GLFW3 Window Drop Callback, runs when drop files into window -static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float scaley); // GLFW3 Window Content Scale Callback, runs when window changes scale // Input callbacks events static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods); // GLFW3 Keyboard Callback, runs on key pressed @@ -1024,8 +1024,8 @@ Vector2 GetWindowPosition(void) // Get window scale DPI factor for current monitor Vector2 GetWindowScaleDPI(void) { - Vector2 scale = { 0 }; - glfwGetWindowContentScale(platform.handle, &scale.x, &scale.y); + Vector2 scale = { 1.0f, 1.0f }; + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) glfwGetWindowContentScale(platform.handle, &scale.x, &scale.y); return scale; } @@ -1452,12 +1452,18 @@ int InitPlatform(void) // NOTE: This hint only has an effect on platforms where screen coordinates and // pixels always map 1:1 such as Windows and X11 // On platforms like macOS the resolution of the framebuffer is changed independently of the window size - glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); // Scale content area based on the monitor content scale where window is placed on + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); #if defined(__APPLE__) glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); #endif } - else glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); + else + { + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); +#if defined(__APPLE__) + glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE); +#endif + } // Mouse passthrough if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE); @@ -1527,9 +1533,7 @@ int InitPlatform(void) GLFWmonitor *monitor = NULL; if (CORE.Window.fullscreen) { - // According to glfwCreateWindow(), if the user does not have a choice, fullscreen applications - // should default to the primary monitor - + // NOTE: Fullscreen applications default to the primary monitor monitor = glfwGetPrimaryMonitor(); if (!monitor) { @@ -1538,7 +1542,7 @@ int InitPlatform(void) } // Set dimensions from monitor - GLFWvidmode *mode = glfwGetVideoMode(monitor); + const GLFWvidmode *mode = glfwGetVideoMode(monitor); // Default display resolution to that of the current mode CORE.Window.display.width = mode->width; @@ -1635,7 +1639,7 @@ int InitPlatform(void) if (monitorIndex < monitorCount) { monitor = monitors[monitorIndex]; - GLFWvidmode *mode = glfwGetVideoMode(monitor); + const GLFWvidmode *mode = glfwGetVideoMode(monitor); // Default display resolution to that of the current mode CORE.Window.display.width = mode->width; @@ -1846,6 +1850,7 @@ static void WindowSizeCallback(GLFWwindow *window, int width, int height) } // GLFW3: Framebuffer size change callback, runs when framebuffer is resized +// WARNING: If FLAG_WINDOW_HIGHDPI is set, WindowContentScaleCallback() is called before this function static void FramebufferSizeCallback(GLFWwindow *window, int width, int height) { //TRACELOG(LOG_INFO, "GLFW3: Window framebuffer size callback called [%i,%i]", width, height); @@ -1863,32 +1868,26 @@ static void FramebufferSizeCallback(GLFWwindow *window, int width, int height) CORE.Window.currentFbo.height = height; CORE.Window.resizedLastFrame = true; - if (IsWindowFullscreen()) return; - // Check if render size was actually scaled for high-dpi if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { + // Set screen size to logical pixel size, considering content scaling Vector2 scaleDpi = GetWindowScaleDPI(); - width = (int)((float)width/scaleDpi.x); - height = (int)((float)height/scaleDpi.y); + CORE.Window.screen.width = (int)((float)width/scaleDpi.x); + CORE.Window.screen.height = (int)((float)height/scaleDpi.y); + } + else + { + // Set screen size to render size (physical pixel size) + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; } - - // Set current screen size - CORE.Window.screen.width = width; - CORE.Window.screen.height = height; // WARNING: If using a render texture, it is not scaled to new size } -// GLFW3: Window position callback, runs when window position changes -static void WindowPosCallback(GLFWwindow *window, int x, int y) -{ - // Set current window position - CORE.Window.position.x = x; - CORE.Window.position.y = y; -} - // GLFW3: Window content scale callback, runs on monitor content scale change detected +// WARNING: If FLAG_WINDOW_HIGHDPI is not set, this function is not called static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float scaley) { TRACELOG(LOG_INFO, "GLFW3: Window content scale changed, scale: [%.2f,%.2f]", scalex, scaley); @@ -1896,13 +1895,13 @@ static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float s float fbWidth = (float)CORE.Window.screen.width*scalex; float fbHeight = (float)CORE.Window.screen.height*scaley; -#if !defined(__APPLE__) // NOTE: On APPLE platforms system should manage window/input scaling and also framebuffer scaling // Framebuffer scaling is activated with: glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); CORE.Window.screenScale = MatrixScale(scalex, scaley, 1.0f); +#if !defined(__APPLE__) // Mouse input scaling for the new screen size - SetMouseScale(1.0f/scalex, 1.0f/scaley); + SetMouseScale((float)CORE.Window.screen.width/fbWidth, (float)CORE.Window.screen.height/fbHeight); #endif CORE.Window.render.width = (int)fbWidth; @@ -1911,6 +1910,16 @@ static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float s CORE.Window.currentFbo.height = (int)fbHeight; } +// GLFW3: Window position callback, runs when window position changes +static void WindowPosCallback(GLFWwindow *window, int x, int y) +{ + TRACELOG(LOG_INFO, "GLFW3: Window position changed"); + + // Set current window position + CORE.Window.position.x = x; + CORE.Window.position.y = y; +} + // GLFW3: Window iconify callback, runs when window is minimized/restored static void WindowIconifyCallback(GLFWwindow *window, int iconified) { From aa2884bd7808bc949ec72e6a65db093cc9bf6c24 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 22 Dec 2025 22:50:38 +0100 Subject: [PATCH 253/430] Update rcore_desktop_glfw.c --- src/platforms/rcore_desktop_glfw.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 8a2abf6c0..efa146fd0 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1913,8 +1913,6 @@ static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float s // GLFW3: Window position callback, runs when window position changes static void WindowPosCallback(GLFWwindow *window, int x, int y) { - TRACELOG(LOG_INFO, "GLFW3: Window position changed"); - // Set current window position CORE.Window.position.x = x; CORE.Window.position.y = y; From 6a701b2679883823f04fed10f301fefcca1adaec Mon Sep 17 00:00:00 2001 From: caszu <109808097+caszuu@users.noreply.github.com> Date: Tue, 23 Dec 2025 15:37:08 +0100 Subject: [PATCH 254/430] fix android SetWindowState (#5424) --- src/platforms/rcore_android.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 7b8d3e052..20a85a6a4 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -360,7 +360,7 @@ void SetWindowState(unsigned int flags) if (!CORE.Window.ready) TRACELOG(LOG_WARNING, "WINDOW: SetWindowState does nothing before window initialization, Use \"SetConfigFlags\" instead"); // State change: FLAG_WINDOW_ALWAYS_RUN - if (!FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); } // Clear window configuration state flags From 0a4583ca5468e48a3e7b7ea9ca4a8055272e524e Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 23 Dec 2025 11:10:55 -0500 Subject: [PATCH 255/430] [rl_gputex.h] Possibly fixed the swizzling in `rl_load_dds_from_memory()` function (#5422) * Possibly fixed the swizzling bug * Removed examples, and generation. --- src/external/rl_gputex.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/external/rl_gputex.h b/src/external/rl_gputex.h index 29500f3cf..78d618db5 100644 --- a/src/external/rl_gputex.h +++ b/src/external/rl_gputex.h @@ -308,7 +308,7 @@ void *rl_load_dds_from_memory(const unsigned char *file_data, unsigned int file_ unsigned char alpha = 0; // NOTE: Data comes as A1R5G5B5, it must be reordered to R5G5B5A1 - for (int i = 0; i < image_pixel_size; i++) + for (int i = 0; i < data_size/sizeof(unsigned short); i++) { alpha = ((unsigned short *)image_data)[i] >> 15; ((unsigned short *)image_data)[i] = ((unsigned short *)image_data)[i] << 1; @@ -328,7 +328,7 @@ void *rl_load_dds_from_memory(const unsigned char *file_data, unsigned int file_ unsigned char alpha = 0; // NOTE: Data comes as A4R4G4B4, it must be reordered R4G4B4A4 - for (int i = 0; i < image_pixel_size; i++) + for (int i = 0; i < data_size/sizeof(unsigned short); i++) { alpha = ((unsigned short *)image_data)[i] >> 12; ((unsigned short *)image_data)[i] = ((unsigned short *)image_data)[i] << 4; @@ -362,7 +362,7 @@ void *rl_load_dds_from_memory(const unsigned char *file_data, unsigned int file_ // NOTE: Data comes as A8R8G8B8, it must be reordered R8G8B8A8 (view next comment) // DirecX understand ARGB as a 32bit DWORD but the actual memory byte alignment is BGRA // So, we must realign B8G8R8A8 to R8G8B8A8 - for (int i = 0; i < image_pixel_size*4; i += 4) + for (int i = 0; i < data_size; i += 4) { blue = ((unsigned char *)image_data)[i]; ((unsigned char *)image_data)[i] = ((unsigned char *)image_data)[i + 2]; From ddb827fb6faa963a5c67dcc79d17d2d65e77c5e7 Mon Sep 17 00:00:00 2001 From: Kivi <35783191+KiviTK@users.noreply.github.com> Date: Wed, 24 Dec 2025 08:59:51 +0100 Subject: [PATCH 256/430] Fixed LoadCodepoints declaring a new local variable shadowing `codpoints` (#5430) --- src/rtext.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rtext.c b/src/rtext.c index 7c25fde0b..e4b439d28 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -2090,7 +2090,7 @@ int *LoadCodepoints(const char *text, int *count) int textLength = TextLength(text); // Allocate a big enough buffer to store as many codepoints as text bytes - int *codepoints = (int *)RL_CALLOC(textLength, sizeof(int)); + codepoints = (int *)RL_CALLOC(textLength, sizeof(int)); int codepointSize = 0; for (int i = 0; i < textLength; codepointCount++) From a1e84caa8c26b36d6bfbc4a64b731fdeae1dacf2 Mon Sep 17 00:00:00 2001 From: Krzysztof Szenk Date: Wed, 24 Dec 2025 09:04:41 +0100 Subject: [PATCH 257/430] RGFW also requires RGBA8 images as window icons, as raylib already reports in raylib.h (#5431) --- src/platforms/rcore_desktop_rgfw.c | 52 ++++++++---------------------- 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 2671538d8..39ac8fb32 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -522,46 +522,15 @@ void ClearWindowState(unsigned int flags) } } -int RGFW_formatToChannels(int format) -{ - switch (format) - { - case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: - case PIXELFORMAT_UNCOMPRESSED_R16: // 16 bpp (1 channel - half float) - case PIXELFORMAT_UNCOMPRESSED_R32: // 32 bpp (1 channel - float) - return 1; - case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: // 8*2 bpp (2 channels) - case PIXELFORMAT_UNCOMPRESSED_R5G6B5: // 16 bpp - case PIXELFORMAT_UNCOMPRESSED_R8G8B8: // 24 bpp - case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: // 16 bpp (1 bit alpha) - case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: // 16 bpp (4 bit alpha) - case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: // 32 bpp - return 2; - case PIXELFORMAT_UNCOMPRESSED_R32G32B32: // 32*3 bpp (3 channels - float) - case PIXELFORMAT_UNCOMPRESSED_R16G16B16: // 16*3 bpp (3 channels - half float) - case PIXELFORMAT_COMPRESSED_DXT1_RGB: // 4 bpp (no alpha) - case PIXELFORMAT_COMPRESSED_ETC1_RGB: // 4 bpp - case PIXELFORMAT_COMPRESSED_ETC2_RGB: // 4 bpp - case PIXELFORMAT_COMPRESSED_PVRT_RGB: // 4 bpp - return 3; - case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: // 32*4 bpp (4 channels - float) - case PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: // 16*4 bpp (4 channels - half float) - case PIXELFORMAT_COMPRESSED_DXT1_RGBA: // 4 bpp (1 bit alpha) - case PIXELFORMAT_COMPRESSED_DXT3_RGBA: // 8 bpp - case PIXELFORMAT_COMPRESSED_DXT5_RGBA: // 8 bpp - case PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: // 8 bpp - case PIXELFORMAT_COMPRESSED_PVRT_RGBA: // 4 bpp - case PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: // 8 bpp - case PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: // 2 bpp - return 4; - default: return 4; - } -} - // Set icon for window void SetWindowIcon(Image image) { - RGFW_window_setIcon(platform.window, (u8 *)image.data, RGFW_AREA(image.width, image.height), RGFW_formatToChannels(image.format)); + if (image.format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) + { + TRACELOG(LOG_WARNING, "RGFW: Window icon image must be in R8G8B8A8 pixel format"); + return; + } + RGFW_window_setIcon(platform.window, (u8 *)image.data, RGFW_AREA(image.width, image.height), 4); } // Set icon for window @@ -578,12 +547,17 @@ void SetWindowIcons(Image *images, int count) for (int i = 0; i < count; i++) { + if (images[i].format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) + { + TRACELOG(LOG_WARNING, "RGFW: Window icon image must be in R8G8B8A8 pixel format"); + continue; + } if ((bigIcon == NULL) || ((images[i].width > bigIcon->width) && (images[i].height > bigIcon->height))) bigIcon = &images[i]; if ((smallIcon == NULL) || ((images[i].width < smallIcon->width) && (images[i].height > smallIcon->height))) smallIcon = &images[i]; } - if (smallIcon != NULL) RGFW_window_setIconEx(platform.window, (u8 *)smallIcon->data, RGFW_AREA(smallIcon->width, smallIcon->height), RGFW_formatToChannels(smallIcon->format), RGFW_iconWindow); - if (bigIcon != NULL) RGFW_window_setIconEx(platform.window, (u8 *)bigIcon->data, RGFW_AREA(bigIcon->width, bigIcon->height), RGFW_formatToChannels(bigIcon->format), RGFW_iconTaskbar); + if (smallIcon != NULL) RGFW_window_setIconEx(platform.window, (u8 *)smallIcon->data, RGFW_AREA(smallIcon->width, smallIcon->height), 4, RGFW_iconWindow); + if (bigIcon != NULL) RGFW_window_setIconEx(platform.window, (u8 *)bigIcon->data, RGFW_AREA(bigIcon->width, bigIcon->height), 4, RGFW_iconTaskbar); } } From 05f42aa119d53049f92aae4e60c3f325d4f52a6b Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 24 Dec 2025 18:02:04 +0100 Subject: [PATCH 258/430] Update core_highdpi_testbed.c --- examples/core/core_highdpi_testbed.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/core/core_highdpi_testbed.c b/examples/core/core_highdpi_testbed.c index 6a036bbfc..bf103fb31 100644 --- a/examples/core/core_highdpi_testbed.c +++ b/examples/core/core_highdpi_testbed.c @@ -27,7 +27,7 @@ int main(void) const int screenWidth = 800; const int screenHeight = 450; - SetConfigFlags(FLAG_WINDOW_HIGHDPI | FLAG_WINDOW_RESIZABLE); + SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI); InitWindow(screenWidth, screenHeight, "raylib [core] example - highdpi testbed"); Vector2 scaleDpi = GetWindowScaleDPI(); From ced84333a9f7647f039b568e75af546b30e8a986 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 24 Dec 2025 18:02:24 +0100 Subject: [PATCH 259/430] Update rl_gputex.h --- src/external/rl_gputex.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/external/rl_gputex.h b/src/external/rl_gputex.h index 78d618db5..9c1092695 100644 --- a/src/external/rl_gputex.h +++ b/src/external/rl_gputex.h @@ -339,7 +339,7 @@ void *rl_load_dds_from_memory(const unsigned char *file_data, unsigned int file_ } } } - else if ((header->ddspf.flags == 0x40) && (header->ddspf.rgb_bit_count == 24)) // DDS_RGB, no compressed + else if ((header->ddspf.flags == 0x40) && (header->ddspf.rgb_bit_count == 24)) // DDS_RGB, no compressed { int data_size = image_pixel_size*3*sizeof(unsigned char); if (header->mipmap_count > 1) data_size = data_size + data_size/3; From 9103f6e0557f615c2295febff69c86a80fc0d2b9 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 24 Dec 2025 18:58:20 +0100 Subject: [PATCH 260/430] ADDED: New platform backend for Web: `Emscripten`, not dependant on GLFW.js -WIP- --- src/platforms/rcore_web_emscripten.c | 1701 ++++++++++++++++++++++++++ 1 file changed, 1701 insertions(+) create mode 100644 src/platforms/rcore_web_emscripten.c diff --git a/src/platforms/rcore_web_emscripten.c b/src/platforms/rcore_web_emscripten.c new file mode 100644 index 000000000..1ed719631 --- /dev/null +++ b/src/platforms/rcore_web_emscripten.c @@ -0,0 +1,1701 @@ +/********************************************************************************************** +* +* rcore_web_emscripten - Functions to manage window, graphics device and inputs +* +* PLATFORM: WEB - EMSCRIPTEN +* - HTML5 (WebAssembly) +* +* LIMITATIONS: +* - TBD +* +* POSSIBLE IMPROVEMENTS: +* - TBD +* +* 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- +* +* DEPENDENCIES: +* - emscripten: Allow interaction between browser API and C +* - gestures: Gestures system for touch-ready devices (or simulated from mouse inputs) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2025 Ramon Santamaria (@raysan5) and contributors +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include // Emscripten functionality for C +#include // Emscripten HTML5 library + +#include // Required for: timespec, nanosleep(), select() - POSIX + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#if (_POSIX_C_SOURCE < 199309L) + #undef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 199309L // Required for: CLOCK_MONOTONIC if compiled with c99 without gnu ext. +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +typedef struct { + char canvasId[64]; // Current canvas id + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE glContext; // OpenGL context + unsigned int *pixels; // Pointer to pixel data buffer (RGBA 32bit format) +} PlatformData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +extern CoreData CORE; // Global CORE state context + +static PlatformData platform = { 0 }; // Platform specific data + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static const char cursorLUT[11][12] = { + "default", // 0 MOUSE_CURSOR_DEFAULT + "default", // 1 MOUSE_CURSOR_ARROW + "text", // 2 MOUSE_CURSOR_IBEAM + "crosshair", // 3 MOUSE_CURSOR_CROSSHAIR + "pointer", // 4 MOUSE_CURSOR_POINTING_HAND + "ew-resize", // 5 MOUSE_CURSOR_RESIZE_EW + "ns-resize", // 6 MOUSE_CURSOR_RESIZE_NS + "nwse-resize", // 7 MOUSE_CURSOR_RESIZE_NWSE + "nesw-resize", // 8 MOUSE_CURSOR_RESIZE_NESW + "move", // 9 MOUSE_CURSOR_RESIZE_ALL + "not-allowed" // 10 MOUSE_CURSOR_NOT_ALLOWED +}; + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +int InitPlatform(void); // Initialize platform (graphics, inputs and more) +void ClosePlatform(void); // Close platform + +// Emscripten window callback events +static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData); +static EM_BOOL EmscriptenFocusCallback(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData); +static EM_BOOL EmscriptenVisibilityChangeCallback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData); +static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); +// TODO: Implement GLFW3 alternative for drop callback, runs when drop files into browser/canvas +//static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); + +// Emscripten input callback events +static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyboardEvent, void *userData); +static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); +static EM_BOOL EmscriptenMouseMoveCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); +static EM_BOOL EmscriptenMouseWheelCallback(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData); +static EM_BOOL EmscriptenPointerlockCallback(int eventType, const EmscriptenPointerlockChangeEvent *pointerlockChangeEvent, void *userData); +static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); +static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData); + +// JS: Set the canvas id provided by the module configuration +EM_JS(void, SetCanvasIdJs, (char *out, int outSize), { + var canvasId = "#" + Module.canvas.id; + stringToUTF8(canvasId, out, outSize); +}); + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +// NOTE: Functions declaration is provided by raylib.h + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Window and Graphics Device +//---------------------------------------------------------------------------------- + +// Check if application should close +// This will always return false on a web-build as web builds have no control over this functionality +// Sleep is handled in EndDrawing() for synchronous code +bool WindowShouldClose(void) +{ + // Emscripten Asyncify is required to run synchronous code in asynchronous JS + // REF: https://emscripten.org/docs/porting/asyncify.html + + // WindowShouldClose() is not called on a web-ready raylib application if using emscripten_set_main_loop() + // and encapsulating one frame execution on a UpdateDrawFrame() function, + // allowing the browser to manage execution asynchronously + + // Optionally we can manage the time we give-control-back-to-browser if required, + // but it seems below line could generate stuttering on some browsers + emscripten_sleep(12); + + return false; +} + +// Toggle fullscreen mode +void ToggleFullscreen(void) +{ + bool enterFullscreen = false; + + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) enterFullscreen = false; + else if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) enterFullscreen = true; + else + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int canvasStyleWidth = EM_ASM_INT( { return parseInt(Module.canvas.style.width); }, 0); + if (canvasStyleWidth > canvasWidth) enterFullscreen = false; + else enterFullscreen = true; + } + + EM_ASM(document.exitFullscreen();); + + CORE.Window.fullscreen = false; + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } + else enterFullscreen = true; + + if (enterFullscreen) + { + // NOTE: The setTimeouts handle the browser mode change delay + EM_ASM + ( + setTimeout(function() + { + Module.requestFullscreen(false, false); + }, 100); + ); + CORE.Window.fullscreen = true; + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + } + + // NOTE: Old notes below: + /* + EM_ASM + ( + // This strategy works well while using raylib minimal web shell for emscripten, + // it re-scales the canvas to fullscreen using monitor resolution, for tools this + // is a good strategy but maybe games prefer to keep current canvas resolution and + // display it in fullscreen, adjusting monitor resolution if possible + if (document.fullscreenElement) document.exitFullscreen(); + else Module.requestFullscreen(true, true); //false, true); + ); + */ + // EM_ASM(Module.requestFullscreen(false, false);); + /* + if (!CORE.Window.fullscreen) + { + // Option 1: Request fullscreen for the canvas element + // This option does not seem to work at all: + // emscripten_request_pointerlock() and emscripten_request_fullscreen() are affected by web security, + // the user must click once on the canvas to hide the pointer or transition to full screen + //emscripten_request_fullscreen("#canvas", false); + + // Option 2: Request fullscreen for the canvas element with strategy + // This option does not seem to work at all + // REF: https://github.com/emscripten-core/emscripten/issues/5124 + // EmscriptenFullscreenStrategy strategy = { + // .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH, //EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, + // .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, + // .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT, + // .canvasResizedCallback = EmscriptenWindowResizedCallback, + // .canvasResizedCallbackUserData = NULL + // }; + //emscripten_request_fullscreen_strategy("#canvas", EM_FALSE, &strategy); + + // Option 3: Request fullscreen for the canvas element with strategy + // It works as expected but only inside the browser (client area) + EmscriptenFullscreenStrategy strategy = { + .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, + .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, + .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT, + .canvasResizedCallback = EmscriptenWindowResizedCallback, + .canvasResizedCallbackUserData = NULL + }; + emscripten_enter_soft_fullscreen("#canvas", &strategy); + + int width = 0; + int height = 0; + emscripten_get_canvas_element_size("#canvas", &width, &height); + TRACELOG(LOG_WARNING, "Emscripten: Enter fullscreen: Canvas size: %i x %i", width, height); + + CORE.Window.fullscreen = true; // Toggle fullscreen flag + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + } + else + { + //emscripten_exit_fullscreen(); + //emscripten_exit_soft_fullscreen(); + + int width, height; + emscripten_get_canvas_element_size("#canvas", &width, &height); + TRACELOG(LOG_WARNING, "Emscripten: Exit fullscreen: Canvas size: %i x %i", width, height); + + CORE.Window.fullscreen = false; // Toggle fullscreen flag + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + } + */ +} + +// Toggle borderless windowed mode +void ToggleBorderlessWindowed(void) +{ + bool enterBorderless = false; + + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) enterBorderless = false; + else if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) enterBorderless = true; + else + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int screenWidth = EM_ASM_INT( { return screen.width; }, 0); + if (screenWidth == canvasWidth) enterBorderless = false; + else enterBorderless = true; + } + + EM_ASM(document.exitFullscreen();); + + CORE.Window.fullscreen = false; + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } + else enterBorderless = true; + + if (enterBorderless) + { + // 1. The setTimeouts handle the browser mode change delay + // 2. The style unset handles the possibility of a width="value%" like on the default shell.html file + EM_ASM + ( + setTimeout(function() + { + Module.requestFullscreen(false, true); + setTimeout(function() + { + canvas.style.width="unset"; + }, 100); + }, 100); + ); + FLAG_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } +} + +// Set window state: maximized, if resizable +void MaximizeWindow(void) +{ + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) && !FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) + { + const int tabWidth = EM_ASM_INT( return window.innerWidth; ); + const int tabHeight = EM_ASM_INT( return window.innerHeight; ); + + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + } +} + +// Set window state: minimized +void MinimizeWindow(void) +{ + TRACELOG(LOG_WARNING, "MinimizeWindow() not available on target platform"); +} + +// Restore window from being minimized/maximized +void RestoreWindow(void) +{ + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) && FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) + { + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + } +} + +// Set window configuration state using flags +void SetWindowState(unsigned int flags) +{ + if (!CORE.Window.ready) TRACELOG(LOG_WARNING, "WINDOW: SetWindowState does nothing before window initialization, Use \"SetConfigFlags\" instead"); + + // Check previous state and requested state to apply required changes + // NOTE: In most cases the functions already change the flags internally + + // State change: FLAG_VSYNC_HINT + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_VSYNC_HINT) not available on target platform"); + } + + // State change: FLAG_BORDERLESS_WINDOWED_MODE + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + // NOTE: Window state flag updated inside ToggleBorderlessWindowed() function + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int canvasStyleWidth = EM_ASM_INT( { return parseInt(Module.canvas.style.width); }, 0); + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) || canvasStyleWidth > canvasWidth) ToggleBorderlessWindowed(); + } + else ToggleBorderlessWindowed(); + } + + // State change: FLAG_FULLSCREEN_MODE + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) + { + // NOTE: Window state flag updated inside ToggleFullscreen() function + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int screenWidth = EM_ASM_INT( { return screen.width; }, 0); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE) || (screenWidth == canvasWidth)) ToggleFullscreen(); + } + else ToggleFullscreen(); + } + + // State change: FLAG_WINDOW_RESIZABLE + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) && FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) + { + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE); + } + + // State change: FLAG_WINDOW_UNDECORATED + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_UNDECORATED) not available on target platform"); + } + + // State change: FLAG_WINDOW_HIDDEN + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_HIDDEN) not available on target platform"); + } + + // State change: FLAG_WINDOW_MINIMIZED + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_MINIMIZED) not available on target platform"); + } + + // State change: FLAG_WINDOW_MAXIMIZED + if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED) != FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) && FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) + { + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) + { + const int tabWidth = EM_ASM_INT( return window.innerWidth; ); + const int tabHeight = EM_ASM_INT( return window.innerHeight; ); + + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + } + } + + // State change: FLAG_WINDOW_UNFOCUSED + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_UNFOCUSED) not available on target platform"); + } + + // State change: FLAG_WINDOW_TOPMOST + if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_TOPMOST) not available on target platform"); + } + + // State change: FLAG_WINDOW_ALWAYS_RUN + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_ALWAYS_RUN) not available on target platform"); + } + + // The following states can not be changed after window creation + // NOTE: Review for PLATFORM_WEB + + // State change: FLAG_WINDOW_TRANSPARENT + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_TRANSPARENT) not available on target platform"); + } + + // State change: FLAG_WINDOW_HIGHDPI + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_HIGHDPI) not available on target platform"); + } + + // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_MOUSE_PASSTHROUGH) not available on target platform"); + } + + // State change: FLAG_MSAA_4X_HINT + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_MSAA_4X_HINT) not available on target platform"); + } + + // State change: FLAG_INTERLACED_HINT + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) + { + TRACELOG(LOG_WARNING, "SetWindowState(FLAG_INTERLACED_HINT) not available on target platform"); + } +} + +// Clear window configuration state flags +void ClearWindowState(unsigned int flags) +{ + // Check previous state and requested state to apply required changes + // NOTE: In most cases the functions already change the flags internally + + // State change: FLAG_VSYNC_HINT + if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_VSYNC_HINT) not available on target platform"); + } + + // State change: FLAG_BORDERLESS_WINDOWED_MODE + if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) + { + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int screenWidth = EM_ASM_INT( { return screen.width; }, 0); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE) || (screenWidth == canvasWidth)) EM_ASM(document.exitFullscreen();); + } + + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } + + // State change: FLAG_FULLSCREEN_MODE + if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) + { + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0); + const int canvasStyleWidth = EM_ASM_INT( { return parseInt(Module.canvas.style.width); }, 0); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE) || (canvasStyleWidth > canvasWidth)) EM_ASM(document.exitFullscreen();); + } + + CORE.Window.fullscreen = false; + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + } + + // State change: FLAG_WINDOW_RESIZABLE + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) && FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) + { + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_RESIZABLE); + } + + // State change: FLAG_WINDOW_HIDDEN + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_HIDDEN) not available on target platform"); + } + + // State change: FLAG_WINDOW_MINIMIZED + if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_MINIMIZED) not available on target platform"); + } + + // State change: FLAG_WINDOW_MAXIMIZED + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED) && FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) + { + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) + { + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + } + } + + // State change: FLAG_WINDOW_UNDECORATED + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_UNDECORATED) not available on target platform"); + } + + // State change: FLAG_WINDOW_UNFOCUSED + if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_UNFOCUSED) not available on target platform"); + } + + // State change: FLAG_WINDOW_TOPMOST + if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_TOPMOST) not available on target platform"); + } + + // State change: FLAG_WINDOW_ALWAYS_RUN + if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_ALWAYS_RUN) not available on target platform"); + } + + // The following states can not be changed after window creation + // NOTE: Review for PLATFORM_WEB + + // State change: FLAG_WINDOW_TRANSPARENT + if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_TRANSPARENT) not available on target platform"); + } + + // State change: FLAG_WINDOW_HIGHDPI + if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_HIGHDPI) not available on target platform"); + } + + // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH + if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_MOUSE_PASSTHROUGH) not available on target platform"); + } + + // State change: FLAG_MSAA_4X_HINT + if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_MSAA_4X_HINT) not available on target platform"); + } + + // State change: FLAG_INTERLACED_HINT + if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT)) + { + TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_INTERLACED_HINT) not available on target platform"); + } +} + +// Set icon for window +void SetWindowIcon(Image image) +{ + TRACELOG(LOG_WARNING, "SetWindowIcon() not available on target platform"); +} + +// Set icon for window, multiple images +void SetWindowIcons(Image *images, int count) +{ + TRACELOG(LOG_WARNING, "SetWindowIcons() not available on target platform"); +} + +// Set title for window +void SetWindowTitle(const char *title) +{ + CORE.Window.title = title; + emscripten_set_window_title(title); +} + +// Set window position on screen (windowed mode) +void SetWindowPosition(int x, int y) +{ + TRACELOG(LOG_WARNING, "SetWindowPosition() not available on target platform"); +} + +// Set monitor for the current window +void SetWindowMonitor(int monitor) +{ + TRACELOG(LOG_WARNING, "SetWindowMonitor() not available on target platform"); +} + +// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMinSize(int width, int height) +{ + CORE.Window.screenMin.width = width; + CORE.Window.screenMin.height = height; + + // Trigger the resize event once to update the window minimum width and height + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != 0) EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); +} + +// Set window maximum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMaxSize(int width, int height) +{ + CORE.Window.screenMax.width = width; + CORE.Window.screenMax.height = height; + + // Trigger the resize event once to update the window maximum width and height + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != 0) EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); +} + +// Set window dimensions +void SetWindowSize(int width, int height) +{ + // When resizing the canvas, several elements must be considered: + // - CSS canvas size: Web layout size, logical pixels + // - Canvas contained framebuffer resolution + // * Browser monitor, device pixel ratio (HighDPI) + + double canvasCssWidth = 0.0; + double canvasCssHeight = 0.0; + emscripten_get_element_css_size(platform.canvasId, &canvasCssWidth, &canvasCssHeight); + + // NOTE: emscripten_get_canvas_element_size() returns canvas framebuffer size, not CSS canvas size + + // Get device pixel ratio + // TODO: Should DPI be considered at this point? + double dpr = emscripten_get_device_pixel_ratio(); + + // Set canvas framebuffer size + emscripten_set_canvas_element_size(platform.canvasId, width*dpr, height*dpr); + + // Set canvas CSS size + // TODO: Consider canvas CSS style if already scaled 100% + EM_ASM({ Module.canvas.style.width = $0; }, width*dpr); + EM_ASM({ Module.canvas.style.height = $0; }, height*dpr); + + SetupViewport(width*dpr, height*dpr); // Reset viewport and projection matrix for new size +} + +// Set window opacity, value opacity is between 0.0 and 1.0 +void SetWindowOpacity(float opacity) +{ + if (opacity >= 1.0f) opacity = 1.0f; + else if (opacity <= 0.0f) opacity = 0.0f; + + EM_ASM({ Module.canvas.style.opacity = $0; }, opacity); +} + +// Set window focused +void SetWindowFocused(void) +{ + TRACELOG(LOG_WARNING, "SetWindowFocused() not available on target platform"); +} + +// Get native window handle +void *GetWindowHandle(void) +{ + TRACELOG(LOG_WARNING, "GetWindowHandle() not implemented on target platform"); + return NULL; +} + +// Get number of monitors +int GetMonitorCount(void) +{ + TRACELOG(LOG_WARNING, "GetMonitorCount() not implemented on target platform"); + return 1; +} + +// Get current monitor where window is placed +int GetCurrentMonitor(void) +{ + TRACELOG(LOG_WARNING, "GetCurrentMonitor() not implemented on target platform"); + return 0; +} + +// Get selected monitor position +Vector2 GetMonitorPosition(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPosition() not implemented on target platform"); + return (Vector2){ 0, 0 }; +} + +// Get selected monitor width (currently used by monitor) +int GetMonitorWidth(int monitor) +{ + // Get the width of the user's entire screen in CSS logical pixels, + // no physical pixels, it would require multiplying by device pixel ratio + // NOTE: Returned value is limited to the current monitor where the browser window is located + int width = 0; + width = EM_ASM_INT( { return window.screen.width; }, 0); + return width; +} + +// Get selected monitor height (currently used by monitor) +int GetMonitorHeight(int monitor) +{ + // Get the height of the user's entire screen in CSS logical pixels, + // no physical pixels, it would require multiplying by device pixel ratio + // NOTE: Returned value is limited to the current monitor where the browser window is located + int height = 0; + height = EM_ASM_INT( { return window.screen.height; }, 0); + return height; +} + +// Get selected monitor physical width in millimetres +int GetMonitorPhysicalWidth(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth() not implemented on target platform"); + return 0; +} + +// Get selected monitor physical height in millimetres +int GetMonitorPhysicalHeight(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight() not implemented on target platform"); + return 0; +} + +// Get selected monitor refresh rate +int GetMonitorRefreshRate(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorRefreshRate() not implemented on target platform"); + return 0; +} + +// Get the human-readable, UTF-8 encoded name of the selected monitor +const char *GetMonitorName(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorName() not implemented on target platform"); + return ""; +} + +// Get window position XY on monitor +Vector2 GetWindowPosition(void) +{ + // Browser window position, top-left corner relative to the physical screen origin, expressed in CSS logical pixels + // NOTE: Returned position is relative to the current monitor where the browser window is located + Vector2 position = { 0, 0 }; + position.x = (float)EM_ASM_INT( { return window.screenX; }, 0); + position.y = (float)EM_ASM_INT( { return window.screenY; }, 0); + return position; +} + +// Get current monitor device pixel ratio +Vector2 GetWindowScaleDPI(void) +{ + // Get device pixel ratio + // NOTE: Returned scale is relative to the current monitor where the browser window is located + Vector2 scale = { 1.0f, 1.0f }; + scale.x = (float)EM_ASM_DOUBLE( { return window.devicePixelRatio; } ); + scale.y = scale.x; + return scale; +} + +// Set clipboard text content +void SetClipboardText(const char *text) +{ + // Security check to (partially) avoid malicious code + if (strchr(text, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided Clipboard could be potentially malicious, avoid [\'] character"); + else EM_ASM({ navigator.clipboard.writeText(UTF8ToString($0)); }, text); +} + +// Get clipboard text content +// NOTE: returned string is allocated and freed by GLFW +const char *GetClipboardText(void) +{ +/* + // Accessing clipboard data from browser is tricky due to security reasons + // The method to use is navigator.clipboard.readText() but this is an asynchronous method + // that will return at some moment after the function is called with the required data + emscripten_run_script_string("navigator.clipboard.readText() \ + .then(text => { document.getElementById('clipboard').innerText = text; console.log('Pasted content: ', text); }) \ + .catch(err => { console.error('Failed to read clipboard contents: ', err); });" + ); + + // The main issue is getting that data, one approach could be using ASYNCIFY and wait + // for the data but it requires adding Asyncify emscripten library on compilation + + // Another approach could be just copy the data in a HTML text field and try to retrieve it + // later on if available... and clean it for future accesses +*/ + return NULL; +} + +// Get clipboard image +Image GetClipboardImage(void) +{ + Image image = { 0 }; + + // NOTE: In theory, the new navigator.clipboard.read() can be used to return arbitrary data from clipboard (2024) + // REF: https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/read + TRACELOG(LOG_WARNING, "GetClipboardImage() not implemented on target platform"); + + return image; +} + +// Show mouse cursor +void ShowCursor(void) +{ + if (CORE.Input.Mouse.cursorHidden) + { + EM_ASM( { Module.canvas.style.cursor = UTF8ToString($0); }, cursorLUT[CORE.Input.Mouse.cursor]); + + CORE.Input.Mouse.cursorHidden = false; + } +} + +// Hides mouse cursor +void HideCursor(void) +{ + if (!CORE.Input.Mouse.cursorHidden) + { + EM_ASM(Module.canvas.style.cursor = 'none';); + + CORE.Input.Mouse.cursorHidden = true; + } +} + +// Enables cursor (unlock cursor) +void EnableCursor(void) +{ + emscripten_exit_pointerlock(); + + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + // NOTE: CORE.Input.Mouse.cursorLocked handled by EmscriptenPointerlockCallback() +} + +// Disables cursor (lock cursor) +void DisableCursor(void) +{ + emscripten_request_pointerlock(platform.canvasId, 1); + + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + // NOTE: CORE.Input.Mouse.cursorLocked handled by EmscriptenPointerlockCallback() +} + +// Swap back buffer with front buffer (screen drawing) +void SwapScreenBuffer(void) +{ +#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) + // Update framebuffer + rlCopyFramebuffer(0, 0, CORE.Window.render.width, CORE.Window.render.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, platform.pixels); + + // Copy framebuffer data into canvas + EM_ASM({ + const width = $0; + const height = $1; + const ptr = $2; + + // Get canvas and 2d context created + const canvas = Module.canvas; + //const canvas = Module['canvas']; + const ctx = canvas.getContext('2d'); + + if (!Module.__img || (Module.__img.width !== width) || (Module.__img.height !== height)) { + Module.__img = ctx.createImageData(width, height); + } + + const src = HEAPU8.subarray(ptr, ptr + width*height*4); // RGBA (4 bytes) + Module.__img.data.set(src); + ctx.putImageData(Module.__img, 0, 0); + + }, CORE.Window.screen.width, CORE.Window.screen.height, platform.pixels); +#endif +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Misc +//---------------------------------------------------------------------------------- + +// Get elapsed time measure in seconds since InitTimer() +double GetTime(void) +{ + double time = 0.0; + /* + struct timespec ts = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long long int nanoSeconds = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; + time = (double)(nanoSeconds - CORE.Time.base)*1e-9; // Elapsed time since InitTimer() + */ + time = emscripten_get_now()*1000.0; + + return time; +} + +// Open URL with default system browser (if available) +// NOTE: This function is only safe to use if you control the URL given +// A user could craft a malicious string performing another action +// Only call this function yourself not with user input or make sure to check the string yourself +void OpenURL(const char *url) +{ + // Security check to (partially) avoid malicious code on target platform + if (strchr(url, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'] character"); + else emscripten_run_script(TextFormat("window.open('%s', '_blank')", url)); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Inputs +//---------------------------------------------------------------------------------- + +// Set internal gamepad mappings +int SetGamepadMappings(const char *mappings) +{ + TRACELOG(LOG_INFO, "SetGamepadMappings not implemented in rcore_web.c"); + + return 0; +} + +// Set gamepad vibration +void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration) +{ + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (duration > 0.0f)) + { + if (leftMotor < 0.0f) leftMotor = 0.0f; + if (leftMotor > 1.0f) leftMotor = 1.0f; + if (rightMotor < 0.0f) rightMotor = 0.0f; + if (rightMotor > 1.0f) rightMotor = 1.0f; + if (duration > MAX_GAMEPAD_VIBRATION_TIME) duration = MAX_GAMEPAD_VIBRATION_TIME; + duration *= 1000.0f; // Convert duration to ms + + // NOTE: [2024.10.21] Current browser support: + // - vibrationActuator API: Chrome, Edge, Opera, Safari, Android Chrome, Android Webview + // - hapticActuators API: Firefox + EM_ASM({ + try { navigator.getGamepads()[$0].vibrationActuator.playEffect('dual-rumble', { startDelay: 0, duration: $3, weakMagnitude: $1, strongMagnitude: $2 }); } + catch (e) + { + try { navigator.getGamepads()[$0].hapticActuators[0].pulse($2, $3); } + catch (e) { } + } + }, gamepad, leftMotor, rightMotor, duration); + } +} + +// Set mouse position XY +void SetMousePosition(int x, int y) +{ + // WARNING: Not supported by browser for security reasons +} + +// Set mouse cursor +void SetMouseCursor(int cursor) +{ + if (CORE.Input.Mouse.cursor != cursor) + { + if (!CORE.Input.Mouse.cursorLocked) EM_ASM( { Module.canvas.style.cursor = UTF8ToString($0); }, cursorLUT[cursor]); + CORE.Input.Mouse.cursor = cursor; + } +} + +// Get physical key name +const char *GetKeyName(int key) +{ + // TODO: Browser can definitely provide a key name e->key + TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform"); + return ""; +} + +// Register all input events +void PollInputEvents(void) +{ +#if defined(SUPPORT_GESTURES_SYSTEM) + // NOTE: Gestures update must be called every frame to reset gestures correctly + // because ProcessGestureEvent() is just called on an event, not every frame + UpdateGestures(); +#endif + + // Reset keys/chars pressed registered + CORE.Input.Keyboard.keyPressedQueueCount = 0; + CORE.Input.Keyboard.charPressedQueueCount = 0; + + // Reset last gamepad button/axis registered state + CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN + //CORE.Input.Gamepad.axisCount = 0; + + // Keyboard/Mouse input polling (automatically managed by GLFW3 through callback) + + // Register previous keys states + for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) + { + CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + } + + // Register previous mouse states + for (int i = 0; i < MAX_MOUSE_BUTTONS; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; + + // Register previous mouse wheel state + CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; + CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f }; + + // Register previous mouse position + CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; + + // Register previous touch states + for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; + + // Reset touch positions + // TODO: It resets on target platform the mouse position and not filled again until a move-event, + // so, if mouse is not moved it returns a (0, 0) position... this behaviour should be reviewed! + //for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 }; + + // Get number of gamepads connected + int numGamepads = 0; + if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) numGamepads = emscripten_get_num_gamepads(); + + for (int i = 0; (i < numGamepads) && (i < MAX_GAMEPADS); i++) + { + // Register previous gamepad button states + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; + + EmscriptenGamepadEvent gamepadState = { 0 }; + int result = emscripten_get_gamepad_status(i, &gamepadState); + + if (result == EMSCRIPTEN_RESULT_SUCCESS) + { + // Register buttons data for every connected gamepad + for (int j = 0; (j < gamepadState.numButtons) && (j < MAX_GAMEPAD_BUTTONS); j++) + { + GamepadButton button = -1; + + // Gamepad Buttons reference: https://www.w3.org/TR/gamepad/#gamepad-interface + switch (j) + { + case 0: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; + case 1: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; + case 2: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; + case 3: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; + case 4: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; + case 5: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; + case 6: button = GAMEPAD_BUTTON_LEFT_TRIGGER_2; break; + case 7: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_2; break; + case 8: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break; + case 9: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; + case 10: button = GAMEPAD_BUTTON_LEFT_THUMB; break; + case 11: button = GAMEPAD_BUTTON_RIGHT_THUMB; break; + case 12: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break; + case 13: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; + case 14: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; + case 15: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; + default: break; + } + + if (button + 1 != 0) // Check for valid button + { + if (gamepadState.digitalButton[j] == 1) + { + CORE.Input.Gamepad.currentButtonState[i][button] = 1; + CORE.Input.Gamepad.lastButtonPressed = button; + } + else CORE.Input.Gamepad.currentButtonState[i][button] = 0; + } + + //TRACELOGD("INPUT: Gamepad %d, button %d: Digital: %d, Analog: %g", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]); + } + + // Register axis data for every connected gamepad + for (int j = 0; (j < gamepadState.numAxes) && (j < MAX_GAMEPAD_AXES); j++) + { + CORE.Input.Gamepad.axisState[i][j] = gamepadState.axis[j]; + } + + CORE.Input.Gamepad.axisCount[i] = gamepadState.numAxes; + } + } + + CORE.Window.resizedLastFrame = false; +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- + +// Initialize platform: graphics, inputs and more +int InitPlatform(void) +{ + SetCanvasIdJs(platform.canvasId, 64); // Get the current canvas id + + // Initialize graphic device: display/window and graphic context + //---------------------------------------------------------------------------- + emscripten_set_canvas_element_size(platform.canvasId, CORE.Window.screen.width, CORE.Window.screen.height); + EmscriptenWebGLContextAttributes attribs = { 0 }; + emscripten_webgl_init_context_attributes(&attribs); + attribs.alpha = EM_TRUE; + attribs.depth = EM_TRUE; + attribs.stencil = EM_FALSE; + attribs.antialias = EM_FALSE; + + // Check window creation flags + //if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) CORE.Window.fullscreen = true; + + // Disable FLAG_WINDOW_MINIMIZED, not supported + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); + + // Disable FLAG_WINDOW_MAXIMIZED, not supported + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); + + // Disable FLAG_WINDOW_TOPMOST, not supported + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_TOPMOST); + + // NOTE: Some other flags are not supported on HTML5 + + // TODO: Scale content area based on the monitor content scale where window is placed on + + // Request MSAA (usually x4 on WebGL 1.0) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) attribs.antialias = EM_TRUE; + + // Check selection OpenGL version + if (rlGetVersion() == RL_OPENGL_11_SOFTWARE) + { + // Avoid creating a WebGL canvas, create 2d canvas for software rendering + emscripten_set_canvas_element_size(platform.canvasId, CORE.Window.screen.width, CORE.Window.screen.height); + EM_ASM({ + const canvas = document.getElementById(platform.canvasId); + Module.canvas = canvas; + }); + + // Load memory framebuffer with desired screen size + platform.pixels = (unsigned int *)RL_CALLOC(CORE.Window.screen.width*CORE.Window.screen.height, sizeof(unsigned int)); + } + else if (rlGetVersion() == RL_OPENGL_ES_20) // Request OpenGL ES 2.0 context --> WebGL 1.0 + { + attribs.majorVersion = 1; // WebGL 1.0 requested + attribs.minorVersion = 0; + + // Create WebGL context + platform.glContext = emscripten_webgl_create_context(platform.canvasId, &attribs); + if (platform.glContext == 0) return 0; + + emscripten_webgl_make_context_current(platform.glContext); + } + else if (rlGetVersion() == RL_OPENGL_ES_30) // Request OpenGL ES 3.0 context --> WebGL 2.0 + { + attribs.majorVersion = 2; // WebGL 2.0 requested + attribs.minorVersion = 0; + + // Create WebGL context + platform.glContext = emscripten_webgl_create_context(platform.canvasId, &attribs); + if (platform.glContext == 0) return 0; + + emscripten_webgl_make_context_current(platform.glContext); + } + + // NOTE: Getting video modes is not implemented in emscripten GLFW3 version + CORE.Window.display.width = CORE.Window.screen.width; + CORE.Window.display.height = CORE.Window.screen.height; + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + + // Set default window title + emscripten_set_window_title((CORE.Window.title != 0)? CORE.Window.title : " "); + + // Check context activation + if ((platform.glContext != 0) || (platform.pixels != NULL)) + { + CORE.Window.ready = true; + + int fbWidth = CORE.Window.screen.width; + int fbHeight = CORE.Window.screen.height; + + CORE.Window.render.width = fbWidth; + CORE.Window.render.height = fbHeight; + CORE.Window.currentFbo.width = fbWidth; + CORE.Window.currentFbo.height = fbHeight; + + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); + TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + } + else + { + TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphics device"); + return -1; + } + + // Load OpenGL extensions + // NOTE: GL procedures address loader is required to load extensions + if (platform.glContext != 0) rlLoadExtensions(emscripten_webgl_get_proc_address); + //---------------------------------------------------------------------------- + + // Initialize events callbacks + //---------------------------------------------------------------------------- + // Setup window/canvas events callbacks + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenFullscreenChangeCallback); + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback); + emscripten_set_blur_callback(platform.canvasId, NULL, 1, EmscriptenFocusCallback); + emscripten_set_focus_callback(platform.canvasId, NULL, 1, EmscriptenFocusCallback); + emscripten_set_visibilitychange_callback(NULL, 1, EmscriptenVisibilityChangeCallback); + + // Setup input events + emscripten_set_keypress_callback(platform.canvasId, NULL, 1, EmscriptenKeyboardCallback); + emscripten_set_keydown_callback(platform.canvasId, NULL, 1, EmscriptenKeyboardCallback); + emscripten_set_keyup_callback(platform.canvasId, NULL, 1, EmscriptenKeyboardCallback); + + emscripten_set_click_callback(platform.canvasId, NULL, 1, EmscriptenMouseCallback); + //emscripten_set_dblclick_callback(platform.canvasId, NULL, 1, EmscriptenMouseCallback); + emscripten_set_mousedown_callback(platform.canvasId, NULL, 1, EmscriptenMouseCallback); + emscripten_set_mouseup_callback(platform.canvasId, NULL, 1, EmscriptenMouseCallback); + emscripten_set_mousemove_callback(platform.canvasId, NULL, 1, EmscriptenMouseCallback); + emscripten_set_mousemove_callback(platform.canvasId, NULL, 1, EmscriptenMouseMoveCallback); + emscripten_set_wheel_callback(platform.canvasId, NULL, 1, EmscriptenMouseWheelCallback); + emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenPointerlockCallback); + + emscripten_set_touchstart_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchend_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchmove_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); + emscripten_set_touchcancel_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); + + emscripten_set_gamepadconnected_callback(NULL, 1, EmscriptenGamepadCallback); + emscripten_set_gamepaddisconnected_callback(NULL, 1, EmscriptenGamepadCallback); + + // Trigger resize callback to force initial size + EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); + //---------------------------------------------------------------------------- + + // Initialize timing system + //---------------------------------------------------------------------------- + InitTimer(); + //---------------------------------------------------------------------------- + + // Initialize storage system + //---------------------------------------------------------------------------- + CORE.Storage.basePath = GetWorkingDirectory(); + //---------------------------------------------------------------------------- + + TRACELOG(LOG_INFO, "PLATFORM: WEB: Initialized successfully"); + + return 0; +} + +// Close platform +// NOTE: Platform closing is managed by browser, so, +// this function is actually not required, but still +// implementing some logic behaviour +void ClosePlatform(void) +{ + if (platform.pixels != NULL) RL_FREE(platform.pixels); + if (platform.glContext != 0) emscripten_webgl_destroy_context(platform.glContext); +} + +// Emscripten callback functions, called on specific browser events +//------------------------------------------------------------------------------------------------------- +// Emscripten: Called on resize event +static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData) +{ + // Don't resize non-resizeable windows + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) return 1; +/* + // Set current screen size + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + 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; + } +*/ + // This event is called whenever the window changes sizes, + // so the size of the canvas object is explicitly retrieved below + int width = EM_ASM_INT( return window.innerWidth; ); + int height = EM_ASM_INT( return window.innerHeight; ); + + if (width < (int)CORE.Window.screenMin.width) width = CORE.Window.screenMin.width; + else if ((width > (int)CORE.Window.screenMax.width) && (CORE.Window.screenMax.width > 0)) width = CORE.Window.screenMax.width; + + if (height < (int)CORE.Window.screenMin.height) height = CORE.Window.screenMin.height; + else if ((height > (int)CORE.Window.screenMax.height) && (CORE.Window.screenMax.height > 0)) height = CORE.Window.screenMax.height; + + emscripten_set_canvas_element_size(platform.canvasId, width, height); + + SetupViewport(width, height); // Reset viewport and projection matrix for new size + + CORE.Window.currentFbo.width = width; + CORE.Window.currentFbo.height = height; + CORE.Window.resizedLastFrame = true; + + if (IsWindowFullscreen()) return 1; + + // Set current screen size + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + + // NOTE: Postprocessing texture is not scaled to new size + + return 0; +} + +// Emscripten: Called on windows focus change events +static EM_BOOL EmscriptenFocusCallback(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData) +{ + EM_BOOL consumed = 1; + + switch (eventType) + { + case EMSCRIPTEN_EVENT_BLUR: FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); break; // The canvas lost focus + case EMSCRIPTEN_EVENT_FOCUS: FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); break; + default: consumed = 0; break; + } + + return consumed; +} + +// Emscripten: Called on visibility change events +static EM_BOOL EmscriptenVisibilityChangeCallback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData) +{ + if (visibilityChangeEvent->hidden) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // The window was hidden + else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // The window was restored + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on fullscreen change events +// TODO: Review fullscreen strategy +static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData) +{ + // NOTE: Reset the fullscreen flags if the user left fullscreen manually by pressing the Escape key + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (!wasFullscreen) + { + CORE.Window.fullscreen = false; + FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); + } + + return 1; // The event was consumed by the callback handler +} + +/* +// GLFW3: Called on file-drop over the window +// TODO: Implement Emscripten (or HTML5/JS) alternative +static void WindowDropCallback(GLFWwindow *window, int count, const char **paths) +{ + if (count > 0) + { + // In case previous dropped filepaths have not been freed, we free them + if (CORE.Window.dropFileCount > 0) + { + for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) RL_FREE(CORE.Window.dropFilepaths[i]); + + RL_FREE(CORE.Window.dropFilepaths); + + CORE.Window.dropFileCount = 0; + CORE.Window.dropFilepaths = NULL; + } + + // WARNING: Paths are freed by GLFW when the callback returns, we must keep an internal copy + CORE.Window.dropFileCount = count; + CORE.Window.dropFilepaths = (char **)RL_CALLOC(CORE.Window.dropFileCount, sizeof(char *)); + + for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) + { + CORE.Window.dropFilepaths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); + strcpy(CORE.Window.dropFilepaths[i], paths[i]); + } + } +} +*/ + +// Emscripten: Called on key events +// TODO: keyCodes should be mapped to raylib/GLFW3 Key values +static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyboardEvent, void *userData) +{ + switch (eventType) + { + case EMSCRIPTEN_EVENT_KEYPRESS: + { + if (keyboardEvent->repeat) CORE.Input.Keyboard.keyRepeatInFrame[keyboardEvent->keyCode] = 1; + } break; + case EMSCRIPTEN_EVENT_KEYDOWN: + { + CORE.Input.Keyboard.currentKeyState[keyboardEvent->keyCode] = 1; + } break; + case EMSCRIPTEN_EVENT_KEYUP: + { + CORE.Input.Keyboard.currentKeyState[keyboardEvent->keyCode] = 0; + } break; + default: break; + } + + // TODO: Add char codes + //unsigned int charCode + // Check if there is space available in the queue for characters to be added + /* + if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE) + { + // Add character to the queue + CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = keyboardEvent->charCode; + CORE.Input.Keyboard.charPressedQueueCount++; + } + */ + /* + // Check if there is space available in the key queue + if ((CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE) && (eventType == EMSCRIPTEN_EVENT_KEYPRESS)) + { + // Add character to the queue + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keyboardEvent->keyCode; + CORE.Input.Keyboard.keyPressedQueueCount++; + } + + // Check the exit key to set close window + //if ((keyboardEvent->keyCode == CORE.Input.Keyboard.exitKey) && (eventType == EMSCRIPTEN_EVENT_KEYPRESS)) CORE.Window.shouldClose = true; + */ + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on mouse input events +static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) +{ + switch (eventType) + { + case EMSCRIPTEN_EVENT_MOUSEENTER: CORE.Input.Mouse.cursorOnScreen = true; break; + case EMSCRIPTEN_EVENT_MOUSELEAVE: CORE.Input.Mouse.cursorOnScreen = false; break; + case EMSCRIPTEN_EVENT_MOUSEDOWN: + { + // NOTE: Emscripten and raylib buttons indices are not aligned + if (mouseEvent->button == 0) CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_LEFT] = 1; + else if (mouseEvent->button == 1) CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_MIDDLE] = 1; + else if (mouseEvent->button == 2) CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_RIGHT] = 1; + + //CORE.Input.Touch.currentTouchState[button] = action; + } break; + case EMSCRIPTEN_EVENT_MOUSEUP: + { + if (mouseEvent->button == 0) CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_LEFT] = 0; + else if (mouseEvent->button == 1) CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_MIDDLE] = 0; + else if (mouseEvent->button == 2) CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_RIGHT] = 0; + } break; + default: break; + } + +#if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + // Register touch actions + if ((CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_LEFT] == 1) && (CORE.Input.Mouse.previousButtonState[MOUSE_BUTTON_LEFT] == 0)) gestureEvent.touchAction = TOUCH_ACTION_DOWN; + else if ((CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_LEFT] == 0) && (CORE.Input.Mouse.previousButtonState[MOUSE_BUTTON_LEFT] == 1)) gestureEvent.touchAction = TOUCH_ACTION_UP; + + // NOTE: TOUCH_ACTION_MOVE event is registered in MouseMoveCallback() + + // Assign a pointer ID + gestureEvent.pointId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0] = GetMousePosition(); + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures-system for processing + // Prevent calling ProcessGestureEvent() when Emscripten is present and there's a touch gesture, so EmscriptenTouchCallback() can handle it itself + if (GetMouseX() != 0 || GetMouseY() != 0) ProcessGestureEvent(gestureEvent); +#endif + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on mouse move events +static EM_BOOL EmscriptenMouseMoveCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) +{ + if (CORE.Input.Mouse.cursorLocked) + { + CORE.Input.Mouse.previousPosition.x = CORE.Input.Mouse.lockedPosition.x - mouseEvent->movementX; + CORE.Input.Mouse.previousPosition.y = CORE.Input.Mouse.lockedPosition.y - mouseEvent->movementY; + } + else + { + // Get mouse position in canvas CSS pixels + float mouseCssX = (float)mouseEvent->canvasX; + float mouseCssY = (float)mouseEvent->canvasY; + + // Get canvas sizes + double cssWidth = 0.0; + double cssHeight = 0.0; + emscripten_get_element_css_size(platform.canvasId, &cssWidth, &cssHeight); + + int fbWidth = 0; + int fbHeight = 0; + emscripten_get_canvas_element_size(platform.canvasId, &fbWidth, &fbHeight); + + // Convert CSS to framebuffer coordinates + float scaleX = (float)fbWidth/(float)cssWidth; + float scaleY = (float)fbHeight/(float)cssHeight; + + int mouseX = (int)(mouseCssX*scaleX); + int mouseY = (int)(mouseCssY*scaleY); + + CORE.Input.Mouse.currentPosition.x = mouseX;//(float)mouseEvent->canvasX; + CORE.Input.Mouse.currentPosition.y = mouseY;//(float)mouseEvent->canvasY; + + // Shorter alternative: + //double dpr = emscripten_get_device_pixel_ratio(); + //int mouseX = (int)(e->canvasX*dpr); + //int mouseY = (int)(e->canvasY*dpr); + + CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; + } + +#if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) + // Process mouse events as touches to be able to use mouse-gestures + GestureEvent gestureEvent = { 0 }; + + gestureEvent.touchAction = TOUCH_ACTION_MOVE; + + // Assign a pointer ID + gestureEvent.pointId[0] = 0; + + // Register touch points count + gestureEvent.pointCount = 1; + + // Register touch points position, only one point registered + gestureEvent.position[0] = CORE.Input.Touch.position[0]; + + // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height + gestureEvent.position[0].x /= (float)GetScreenWidth(); + gestureEvent.position[0].y /= (float)GetScreenHeight(); + + // Gesture data is sent to gestures-system for processing + ProcessGestureEvent(gestureEvent); +#endif + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on mouse wheel events +static EM_BOOL EmscriptenMouseWheelCallback(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData) +{ + if (eventType == EMSCRIPTEN_EVENT_WHEEL) + { + CORE.Input.Mouse.currentWheelMove.x = (float)wheelEvent->deltaX; + CORE.Input.Mouse.currentWheelMove.y = (float)wheelEvent->deltaY; + } + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on pointer lock events +static EM_BOOL EmscriptenPointerlockCallback(int eventType, const EmscriptenPointerlockChangeEvent *pointerlockChangeEvent, void *userData) +{ + CORE.Input.Mouse.cursorLocked = EM_ASM_INT( { if (document.pointerLockElement) return 1; }, 0); + + if (CORE.Input.Mouse.cursorLocked) + { + CORE.Input.Mouse.lockedPosition = CORE.Input.Mouse.currentPosition; + CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.lockedPosition; + } + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on connect/disconnect gamepads events +static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) +{ + /* + TRACELOGD("%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"", + eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state", + gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping); + + for (int i = 0; i < gamepadEvent->numAxes; i++) TRACELOGD("Axis %d: %g", i, gamepadEvent->axis[i]); + for (int i = 0; i < gamepadEvent->numButtons; i++) TRACELOGD("Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); + */ + + if (gamepadEvent->connected && (gamepadEvent->index < MAX_GAMEPADS)) + { + CORE.Input.Gamepad.ready[gamepadEvent->index] = true; + snprintf(CORE.Input.Gamepad.name[gamepadEvent->index], MAX_GAMEPAD_NAME_LENGTH, "%s", gamepadEvent->id); + } + else CORE.Input.Gamepad.ready[gamepadEvent->index] = false; + + return 1; // The event was consumed by the callback handler +} + +// Emscripten: Called on touch input events +static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) +{ + // Register touch points count + CORE.Input.Touch.pointCount = touchEvent->numTouches; + + double canvasWidth = 0.0; + double canvasHeight = 0.0; + // NOTE: emscripten_get_canvas_element_size() returns canvas.width and canvas.height but + // we are looking for actual CSS size: canvas.style.width and canvas.style.height + // EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight); + emscripten_get_element_css_size(platform.canvasId, &canvasWidth, &canvasHeight); + + for (int i = 0; (i < CORE.Input.Touch.pointCount) && (i < MAX_TOUCH_POINTS); i++) + { + // Register touch points id + CORE.Input.Touch.pointId[i] = touchEvent->touches[i].identifier; + + // Register touch points position + CORE.Input.Touch.position[i] = (Vector2){touchEvent->touches[i].targetX, touchEvent->touches[i].targetY}; + + // Normalize gestureEvent.position[x] for CORE.Window.screen.width and CORE.Window.screen.height + CORE.Input.Touch.position[i].x *= ((float)GetScreenWidth()/(float)canvasWidth); + CORE.Input.Touch.position[i].y *= ((float)GetScreenHeight()/(float)canvasHeight); + + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) CORE.Input.Touch.currentTouchState[i] = 1; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) CORE.Input.Touch.currentTouchState[i] = 0; + } + + // Update mouse position if we detect a single touch + if (CORE.Input.Touch.pointCount == 1) + { + CORE.Input.Mouse.currentPosition.x = CORE.Input.Touch.position[0].x; + CORE.Input.Mouse.currentPosition.y = CORE.Input.Touch.position[0].y; + } + +#if defined(SUPPORT_GESTURES_SYSTEM) + GestureEvent gestureEvent = { 0 }; + gestureEvent.pointCount = CORE.Input.Touch.pointCount; + + // Register touch actions + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.touchAction = TOUCH_ACTION_DOWN; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.touchAction = TOUCH_ACTION_UP; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.touchAction = TOUCH_ACTION_MOVE; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL) gestureEvent.touchAction = TOUCH_ACTION_CANCEL; + + for (int i = 0; (i < gestureEvent.pointCount) && (i < MAX_TOUCH_POINTS); i++) + { + gestureEvent.pointId[i] = CORE.Input.Touch.pointId[i]; + gestureEvent.position[i] = CORE.Input.Touch.position[i]; + + // Normalize gestureEvent.position[i] + gestureEvent.position[i].x /= (float)GetScreenWidth(); + gestureEvent.position[i].y /= (float)GetScreenHeight(); + } + + // Gesture data is sent to gestures system for processing + ProcessGestureEvent(gestureEvent); +#endif + + if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) + { + // Identify the EMSCRIPTEN_EVENT_TOUCHEND and remove it from the list + for (int i = 0; i < CORE.Input.Touch.pointCount; i++) + { + if (touchEvent->touches[i].isChanged) + { + // Move all touch points one position up + for (int j = i; j < CORE.Input.Touch.pointCount - 1; j++) + { + CORE.Input.Touch.pointId[j] = CORE.Input.Touch.pointId[j + 1]; + CORE.Input.Touch.position[j] = CORE.Input.Touch.position[j + 1]; + } + // Decrease touch points count to remove the last one + CORE.Input.Touch.pointCount--; + break; + } + } + // Clamp pointCount to avoid negative values + if (CORE.Input.Touch.pointCount < 0) CORE.Input.Touch.pointCount = 0; + } + + return 1; // The event was consumed by the callback handler +} +//------------------------------------------------------------------------------------------------------- + +// EOF From fc843dc5572379482377ba4f33f6f53f47e3f69a Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 24 Dec 2025 19:21:43 +0100 Subject: [PATCH 261/430] Create SECURITY.md --- SECURITY.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..48a825e37 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,18 @@ +# Security Policy + +## Supported Versions + +Most considerations of errors and defects can be handled using the project Issues and/or Discussions. + +| Version | Supported | +| ------- | ------------------ | +| 6.0.x | :white_check_mark: | +| < 5.5 | :x: | + +## Reporting a Vulnerability + +Discovered vulnerability can be directly reported using the project Issues and/or Discussions. + +_TODO: Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc._ From 20dd4641c8caff962b5037bf1f1eb5471ae3598e Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 24 Dec 2025 19:35:06 +0100 Subject: [PATCH 262/430] REVIEWED: Potential security concerns while copying unbounded text data between strings Note that issue has been reported by CodeQL static analysis system --- src/platforms/rcore_desktop_glfw.c | 2 +- src/platforms/rcore_desktop_sdl.c | 8 ++++---- src/platforms/rcore_web.c | 2 +- src/platforms/rcore_web_emscripten.c | 2 +- src/rtext.c | 17 +++++++++++------ 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index efa146fd0..471050839 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1962,7 +1962,7 @@ static void WindowDropCallback(GLFWwindow *window, int count, const char **paths for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) { CORE.Window.dropFilepaths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); - strcpy(CORE.Window.dropFilepaths[i], paths[i]); + strncpy(CORE.Window.dropFilepaths[i], paths[i], MAX_FILEPATH_LENGTH - 1); } } } diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index add1de6ad..612707cea 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -1431,9 +1431,9 @@ void PollInputEvents(void) // Event memory is now managed by SDL, so you should not free the data in SDL_EVENT_DROP_FILE, // and if you want to hold onto the text in SDL_EVENT_TEXT_EDITING and SDL_EVENT_TEXT_INPUT events, // you should make a copy of it. SDL_TEXTINPUTEVENT_TEXT_SIZE is no longer necessary and has been removed - strcpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.data); + strncpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.data, MAX_FILEPATH_LENGTH - 1); #else - strcpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.file); + strncpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.file, MAX_FILEPATH_LENGTH - 1); SDL_free(event.drop.file); #endif @@ -1444,9 +1444,9 @@ void PollInputEvents(void) CORE.Window.dropFilepaths[CORE.Window.dropFileCount] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); #if defined(USING_VERSION_SDL3) - strcpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.data); + strncpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.data, MAX_FILEPATH_LENGTH - 1); #else - strcpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.file); + strncpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.file, MAX_FILEPATH_LENGTH - 1); SDL_free(event.drop.file); #endif diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index 934f778c3..adfdace74 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -1531,7 +1531,7 @@ static void WindowDropCallback(GLFWwindow *window, int count, const char **paths for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) { CORE.Window.dropFilepaths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); - strcpy(CORE.Window.dropFilepaths[i], paths[i]); + strncpy(CORE.Window.dropFilepaths[i], paths[i], MAX_FILEPATH_LENGTH - 1); } } } diff --git a/src/platforms/rcore_web_emscripten.c b/src/platforms/rcore_web_emscripten.c index 1ed719631..25b477734 100644 --- a/src/platforms/rcore_web_emscripten.c +++ b/src/platforms/rcore_web_emscripten.c @@ -1387,7 +1387,7 @@ static void WindowDropCallback(GLFWwindow *window, int count, const char **paths for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) { CORE.Window.dropFilepaths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); - strcpy(CORE.Window.dropFilepaths[i], paths[i]); + strncpy(CORE.Window.dropFilepaths[i], paths[i], MAX_FILEPATH_LENGTH - 1); } } } diff --git a/src/rtext.c b/src/rtext.c index e4b439d28..453ed4507 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1597,14 +1597,13 @@ float TextToFloat(const char *text) #if defined(SUPPORT_TEXT_MANIPULATION) // Copy one string to another, returns bytes copied +// NOTE: Alternative implementation to strcpy(dst, src) from C standard library int TextCopy(char *dst, const char *src) { int bytes = 0; if ((src != NULL) && (dst != NULL)) { - // NOTE: Alternative: use strcpy(dst, src) - while (*src != '\0') { *dst = *src; @@ -1717,11 +1716,13 @@ char *TextReplace(const char *text, const char *search, const char *replacement) { char *insertPoint = NULL; // Next insert point char *temp = NULL; // Temp pointer + int textLen = 0; // Text string length int searchLen = 0; // Search string length of (the string to remove) int replaceLen = 0; // Replacement length (the string to replace by) int lastReplacePos = 0; // Distance between next search and end of last replace int count = 0; // Number of replacements + textLen = TextLength(text); searchLen = TextLength(search); if (searchLen == 0) return NULL; // Empty search causes infinite loop during count @@ -1732,7 +1733,8 @@ char *TextReplace(const char *text, const char *search, const char *replacement) for (count = 0; (temp = strstr(insertPoint, search)); count++) insertPoint = temp + searchLen; // Allocate returning string and point temp to it - temp = result = (char *)RL_MALLOC(TextLength(text) + (replaceLen - searchLen)*count + 1); + int tempLen = textLen + (replaceLen - searchLen)*count + 1; + temp = result = (char *)RL_MALLOC(tempLen); if (!result) return NULL; // Memory could not be allocated @@ -1744,13 +1746,16 @@ char *TextReplace(const char *text, const char *search, const char *replacement) { insertPoint = (char *)strstr(text, search); lastReplacePos = (int)(insertPoint - text); - temp = strncpy(temp, text, lastReplacePos) + lastReplacePos; - temp = strcpy(temp, replacement) + replaceLen; + temp = strncpy(temp, text, tempLen - 1) + lastReplacePos; + tempLen -= lastReplacePos; + temp = strncpy(temp, replacement, tempLen - 1) + replaceLen; + tempLen -= replaceLen; + text += lastReplacePos + searchLen; // Move to next "end of replace" } // Copy remaind text part after replacement to result (pointed by moving temp) - strcpy(temp, text); + strncpy(temp, text, tempLen - 1); } return result; From 101502103a559afdff6938dc0c739eb5d0870912 Mon Sep 17 00:00:00 2001 From: Dan Vu Date: Wed, 24 Dec 2025 20:58:40 +0100 Subject: [PATCH 263/430] Fixed FLAG_IS_SET to check if all bits in the flag are set in the value (#5441) --- src/rcore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rcore.c b/src/rcore.c index 1f47efcb9..6f16b605b 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -273,7 +273,7 @@ #define FLAG_SET(n, f) ((n) |= (f)) #define FLAG_CLEAR(n, f) ((n) &= ~(f)) #define FLAG_TOGGLE(n, f) ((n) ^= (f)) -#define FLAG_IS_SET(n, f) (((n) & (f)) > 0) +#define FLAG_IS_SET(n, f) (((n) & (f)) == (f)) //---------------------------------------------------------------------------------- // Types and Structures Definition From 5e14ac5a2ed19071e8159455f5534aedf6b301dd Mon Sep 17 00:00:00 2001 From: Alvin De Cruz Date: Sat, 27 Dec 2025 03:42:32 +0800 Subject: [PATCH 264/430] #5387 - Fix keyboard input detected as gamepad on some Android devices (#5439) * [rcore][android] Fix keyboard input detected as gamepad on some devices (#5387) * [core] Add keyboard vs gamepad input test example (#5387) --- .../core/core_input_keyboard_gamepad_test.c | 173 ++++++++++++++++++ src/platforms/rcore_android.c | 7 +- 2 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 examples/core/core_input_keyboard_gamepad_test.c diff --git a/examples/core/core_input_keyboard_gamepad_test.c b/examples/core/core_input_keyboard_gamepad_test.c new file mode 100644 index 000000000..d1f9106f7 --- /dev/null +++ b/examples/core/core_input_keyboard_gamepad_test.c @@ -0,0 +1,173 @@ +/******************************************************************************************* +* +* raylib [core] example - Keyboard vs Gamepad Input Test +* +* Example complexity rating: [★☆☆☆] 1/4 +* +* This example is a diagnostic tool to verify that keyboard input is not +* incorrectly detected as gamepad input on Android devices. +* +* Issue reference: https://github.com/raysan5/raylib/issues/5387 +* +* 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) 2025 raylib contributors +* +********************************************************************************************/ + +#include "raylib.h" + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [core] example - keyboard vs gamepad test"); + + Vector2 ballPosition = { (float)screenWidth/2, (float)screenHeight/2 }; + int lastKeyPressed = 0; + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) + { + // Update + //---------------------------------------------------------------------------------- + + // Track keyboard input + if (IsKeyDown(KEY_RIGHT)) ballPosition.x += 4.0f; + if (IsKeyDown(KEY_LEFT)) ballPosition.x -= 4.0f; + if (IsKeyDown(KEY_UP)) ballPosition.y -= 4.0f; + if (IsKeyDown(KEY_DOWN)) ballPosition.y += 4.0f; + + // Keep ball on screen + if (ballPosition.x < 25) ballPosition.x = 25; + if (ballPosition.x > screenWidth - 25) ballPosition.x = screenWidth - 25; + if (ballPosition.y < 25) ballPosition.y = 25; + if (ballPosition.y > screenHeight - 25) ballPosition.y = screenHeight - 25; + + // Track last key pressed + int key = GetKeyPressed(); + if (key != 0) lastKeyPressed = key; + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + // Title + DrawText("KEYBOARD vs GAMEPAD INPUT TEST", 180, 10, 20, DARKGRAY); + DrawText("Issue #5387: Keyboard detected as gamepad on some Android devices", 120, 35, 14, GRAY); + + // Divider + DrawLine(0, 60, screenWidth, 60, LIGHTGRAY); + + // Keyboard section + DrawText("KEYBOARD INPUT", 20, 75, 18, DARKBLUE); + DrawRectangle(20, 100, 360, 80, Fade(BLUE, 0.1f)); + + DrawText(TextFormat("Arrow Keys: [%s] [%s] [%s] [%s]", + IsKeyDown(KEY_UP) ? "UP" : "--", + IsKeyDown(KEY_DOWN) ? "DN" : "--", + IsKeyDown(KEY_LEFT) ? "LT" : "--", + IsKeyDown(KEY_RIGHT) ? "RT" : "--"), 30, 110, 16, BLACK); + + DrawText(TextFormat("Last Key Pressed: %d", lastKeyPressed), 30, 135, 16, DARKGRAY); + DrawText(TextFormat("Any Key Down: %s", (IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || + IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT)) ? "YES" : "NO"), 30, 155, 16, DARKGRAY); + + // Gamepad section + DrawText("GAMEPAD STATUS", 420, 75, 18, DARKGREEN); + DrawRectangle(420, 100, 360, 80, Fade(GREEN, 0.1f)); + + bool gamepadReady = IsGamepadAvailable(0); + DrawText(TextFormat("Gamepad 0 Available: %s", gamepadReady ? "YES" : "NO"), + 430, 110, 16, gamepadReady ? RED : DARKGREEN); + + if (gamepadReady) + { + DrawText(TextFormat("D-Pad: [%s] [%s] [%s] [%s]", + IsGamepadButtonDown(0, GAMEPAD_BUTTON_LEFT_FACE_UP) ? "UP" : "--", + IsGamepadButtonDown(0, GAMEPAD_BUTTON_LEFT_FACE_DOWN) ? "DN" : "--", + IsGamepadButtonDown(0, GAMEPAD_BUTTON_LEFT_FACE_LEFT) ? "LT" : "--", + IsGamepadButtonDown(0, GAMEPAD_BUTTON_LEFT_FACE_RIGHT) ? "RT" : "--"), + 430, 135, 16, RED); + + DrawText(TextFormat("Gamepad Name: %.20s", GetGamepadName(0)), 430, 155, 14, DARKGRAY); + } + else + { + DrawText("No gamepad detected", 430, 135, 16, DARKGREEN); + } + + // Divider + DrawLine(0, 190, screenWidth, 190, LIGHTGRAY); + + // Test result section + DrawText("TEST RESULT", 20, 200, 18, MAROON); + + bool keyboardActive = IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || + IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT); + + if (keyboardActive && gamepadReady) + { + // BUG DETECTED: Keyboard is triggering gamepad detection + DrawRectangle(20, 225, 760, 50, Fade(RED, 0.3f)); + DrawText("BUG DETECTED: Keyboard input is being detected as gamepad!", 30, 235, 18, RED); + DrawText("The fix for issue #5387 may not be working correctly.", 30, 258, 14, DARKGRAY); + } + else if (keyboardActive && !gamepadReady) + { + // CORRECT: Keyboard works without triggering gamepad + DrawRectangle(20, 225, 760, 50, Fade(GREEN, 0.3f)); + DrawText("PASS: Keyboard input detected correctly (no phantom gamepad)", 30, 235, 18, DARKGREEN); + DrawText("Issue #5387 fix is working as expected.", 30, 258, 14, DARKGRAY); + } + else if (!keyboardActive && gamepadReady) + { + // Gamepad is connected (might be real or might be bug on idle) + DrawRectangle(20, 225, 760, 50, Fade(ORANGE, 0.3f)); + DrawText("INFO: Gamepad detected - press keyboard keys to test", 30, 235, 18, ORANGE); + DrawText("If gamepad stays active while pressing keyboard = BUG", 30, 258, 14, DARKGRAY); + } + else + { + // Idle state + DrawRectangle(20, 225, 760, 50, Fade(GRAY, 0.1f)); + DrawText("WAITING: Press arrow keys to test keyboard input", 30, 235, 18, GRAY); + DrawText("Gamepad should NOT become available when pressing keyboard keys", 30, 258, 14, DARKGRAY); + } + + // Ball controlled by keyboard + DrawText("Ball Control (Arrow Keys):", 20, 295, 16, DARKGRAY); + DrawCircleV(ballPosition, 25, MAROON); + DrawCircleLines((int)ballPosition.x, (int)ballPosition.y, 25, DARKGRAY); + + // Instructions + DrawRectangle(0, screenHeight - 45, screenWidth, 45, Fade(BLACK, 0.05f)); + DrawText("Instructions: Press keyboard arrow keys - the ball should move and gamepad should stay 'NO'", + 20, screenHeight - 35, 14, DARKGRAY); + DrawText("If gamepad becomes 'YES' while pressing keyboard = issue #5387 is NOT fixed", + 20, screenHeight - 18, 14, DARKGRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 20a85a6a4..f3911d41b 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -1238,8 +1238,11 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) //int32_t AKeyEvent_getMetaState(event); // Handle gamepad button presses and releases - if (FLAG_IS_SET(source, AINPUT_SOURCE_JOYSTICK) || - FLAG_IS_SET(source, AINPUT_SOURCE_GAMEPAD)) + // NOTE: Skip gamepad handling if this is a keyboard event, as some devices + // report both AINPUT_SOURCE_KEYBOARD and AINPUT_SOURCE_GAMEPAD flags + if ((FLAG_IS_SET(source, AINPUT_SOURCE_JOYSTICK) || + FLAG_IS_SET(source, AINPUT_SOURCE_GAMEPAD)) && + !FLAG_IS_SET(source, AINPUT_SOURCE_KEYBOARD)) { // For now we'll assume a single gamepad which we "detect" on its input event CORE.Input.Gamepad.ready[0] = true; From aee6734cffb5666bdd04115c60db6257bcd5401e Mon Sep 17 00:00:00 2001 From: Dino <84743074+LeapersEdge@users.noreply.github.com> Date: Fri, 26 Dec 2025 20:46:09 +0100 Subject: [PATCH 265/430] fix: set correct default axes for gamepads that are not connected (inside rcore_desktop_glfw.c) (#5444) * fix: set correct default axes for gamepads that are not connected `glfwGetGamepadState` will set all gamepad state variables to 0.0 if required gamepad is not connected, but `RecordAutomationEvent()` inside rcore.c expects trigger axes to be -1.0f when gamepad is not connected. Since SDL and RGFW return -1.0f in such case, this change is aligning it with them * updated comment in rcore_desktop_glfw.c --- src/platforms/rcore_desktop_glfw.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 471050839..368dd5de8 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1266,8 +1266,14 @@ void PollInputEvents(void) // Get current gamepad state // NOTE: There is no callback available, so we get it manually GLFWgamepadstate state = { 0 }; - glfwGetGamepadState(i, &state); // This remapps all gamepads so they have their buttons mapped like an xbox controller - + int isGamepadConnected = glfwGetGamepadState(i, &state); // This remapps all gamepads so they have their buttons mapped like an xbox controller + if (!isGamepadConnected) + { + // setting axes to expected resting value instead of GLFW's 0.0f default when gamepad isnt connected + state.axes[GAMEPAD_AXIS_LEFT_TRIGGER] = -1.0f; + state.axes[GAMEPAD_AXIS_RIGHT_TRIGGER] = -1.0f; + } + const unsigned char *buttons = state.buttons; for (int k = 0; (buttons != NULL) && (k < MAX_GAMEPAD_BUTTONS); k++) From 64bd27bd08aa7bf25d4daa6b5d28d3aa6ae4bb12 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 26 Dec 2025 20:49:03 +0100 Subject: [PATCH 266/430] Update rcore_desktop_glfw.c --- src/platforms/rcore_desktop_glfw.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 368dd5de8..824184368 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1266,10 +1266,10 @@ void PollInputEvents(void) // Get current gamepad state // NOTE: There is no callback available, so we get it manually GLFWgamepadstate state = { 0 }; - int isGamepadConnected = glfwGetGamepadState(i, &state); // This remapps all gamepads so they have their buttons mapped like an xbox controller - if (!isGamepadConnected) + int result = glfwGetGamepadState(i, &state); // This remaps all gamepads so they have their buttons mapped like an xbox controller + if (result == GLFW_FALSE) // No joystick is connected, no gamepad mapping or an error occurred { - // setting axes to expected resting value instead of GLFW's 0.0f default when gamepad isnt connected + // Setting axes to expected resting value instead of GLFW 0.0f default when gamepad is not connected state.axes[GAMEPAD_AXIS_LEFT_TRIGGER] = -1.0f; state.axes[GAMEPAD_AXIS_RIGHT_TRIGGER] = -1.0f; } From 25a54d87e6ebeb70ef20443fb4e010195b958ead Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 26 Dec 2025 21:09:53 +0100 Subject: [PATCH 267/430] Update rcore_desktop_win32.c --- src/platforms/rcore_desktop_win32.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 29702921f..ce9d86cc2 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -1877,13 +1877,23 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara } break; case WM_DPICHANGED: { + // Get current dpi scale factor + float scalex = HIWORD(wParam)/96.0f; + float scaley = LOWORD(wParam)/96.0f; + RECT *suggestedRect = (RECT *)lparam; // Never set the window size to anything other than the suggested rect here // Doing so can cause a window to stutter between monitors when transitioning between them - int result = (int)SetWindowPos(hwnd, NULL, suggestedRect->left, suggestedRect->top, - suggestedRect->right - suggestedRect->left, suggestedRect->bottom - suggestedRect->top, SWP_NOZORDER | SWP_NOACTIVATE); + int result = (int)SetWindowPos(hwnd, NULL, + suggestedRect->left, suggestedRect->top, + suggestedRect->right - suggestedRect->left, + suggestedRect->bottom - suggestedRect->top, + SWP_NOZORDER | SWP_NOACTIVATE); + if (result == 0) TRACELOG(LOG_ERROR, "Failed to set window position [ERROR: %lu]", GetLastError()); + + // TODO: Update screen data, render size, screen scaling, viewport... } break; case WM_SETCURSOR: From 84dfe6a4cf6ab18c8e7d7c2701a93eae5c28dbc0 Mon Sep 17 00:00:00 2001 From: TheLazyIndianTechie Date: Sat, 27 Dec 2025 18:52:24 +0530 Subject: [PATCH 268/430] [rmodels] Fix glTF animation framerate calculation (#4472) (#5445) - Changed GLTF_ANIMDELAY (17ms, ~58.82fps) to GLTF_FRAMERATE (60.0fps) - Updated frameCount calculation: (animDuration * 60) instead of (animDuration * 1000 / 17) - Updated time calculation: j / 60.0f instead of (j * 17) / 1000.0f This fixes animation frame count misalignment when importing glTF models exported at standard 60fps. Animations that were 27+ frames shorter than expected on 1350-frame sequences will now import correctly. --- src/rmodels.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index 40af4afc4..665b94147 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -6353,7 +6353,7 @@ static bool GetPoseAtTimeGLTF(cgltf_interpolation_type interpolationType, cgltf_ return true; } -#define GLTF_ANIMDELAY 17 // Animation frames delay, (~1000 ms/60 FPS = 16.666666* ms) +#define GLTF_FRAMERATE 60.0f // glTF animation framerate (frames per second) static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCount) { @@ -6473,13 +6473,13 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCo if (animData.name != NULL) strncpy(animations[i].name, animData.name, sizeof(animations[i].name) - 1); - animations[i].frameCount = (int)(animDuration*1000.0f/GLTF_ANIMDELAY) + 1; + animations[i].frameCount = (int)(animDuration*GLTF_FRAMERATE) + 1; animations[i].framePoses = (Transform **)RL_MALLOC(animations[i].frameCount*sizeof(Transform *)); for (int j = 0; j < animations[i].frameCount; j++) { animations[i].framePoses[j] = (Transform *)RL_MALLOC(animations[i].boneCount*sizeof(Transform)); - float time = ((float) j*GLTF_ANIMDELAY)/1000.0f; + float time = (float)j / GLTF_FRAMERATE; for (int k = 0; k < animations[i].boneCount; k++) { From 538bf820374db46493f46e89f228934ca686726e Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 27 Dec 2025 14:35:38 +0100 Subject: [PATCH 269/430] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 875792f18..815088fc0 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ contributors ------------ - + license From e4491b40b52078370fe4b4e088d17bde6d562310 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 27 Dec 2025 14:43:46 +0100 Subject: [PATCH 270/430] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 815088fc0..694937a70 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ contributors ------------ - + license From 05f5143603ba4db9b3157e981926a42c55c4c766 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 27 Dec 2025 15:05:18 +0100 Subject: [PATCH 271/430] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 694937a70..37e37c7c4 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ contributors ------------ - + license From da1a76604f76c82c490f7cf0d063fae49326c1ed Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 28 Dec 2025 16:05:42 +0100 Subject: [PATCH 272/430] REMOVED: `CORE.Window.fullscreen`, using available flag instead --- src/platforms/rcore_android.c | 1 - src/platforms/rcore_desktop_glfw.c | 20 ++++++++----------- src/platforms/rcore_desktop_rgfw.c | 10 ++++------ src/platforms/rcore_desktop_sdl.c | 11 ++--------- src/platforms/rcore_template.c | 1 - src/platforms/rcore_web.c | 20 +++++-------------- src/platforms/rcore_web_emscripten.c | 12 ++---------- src/rcore.c | 29 ++++++++++++++-------------- 8 files changed, 35 insertions(+), 69 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index f3911d41b..cca4f4d39 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -885,7 +885,6 @@ void ClosePlatform(void) // NOTE: returns false in case graphic device could not be created static int InitGraphicsDevice(void) { - CORE.Window.fullscreen = true; FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); EGLint samples = 0; diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 824184368..ed8b1b542 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -176,7 +176,7 @@ bool WindowShouldClose(void) // Toggle fullscreen mode void ToggleFullscreen(void) { - if (!CORE.Window.fullscreen) + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) { // Store previous window position (in case we exit fullscreen) CORE.Window.previousPosition = CORE.Window.position; @@ -192,8 +192,6 @@ void ToggleFullscreen(void) { TRACELOG(LOG_WARNING, "GLFW: Failed to get monitor"); - CORE.Window.fullscreen = false; - FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); glfwSetWindowMonitor(platform.handle, NULL, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); } @@ -666,7 +664,7 @@ void SetWindowMonitor(int monitor) if ((monitor >= 0) && (monitor < monitorCount)) { - if (CORE.Window.fullscreen) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) { TRACELOG(LOG_INFO, "GLFW: Selected fullscreen monitor: [%i] %s", monitor, glfwGetMonitorName(monitors[monitor])); @@ -1422,8 +1420,6 @@ int InitPlatform(void) unsigned int requestedWindowFlags = CORE.Window.flags; // Check window creation flags - if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) CORE.Window.fullscreen = true; - if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window else glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); // Window initially hidden @@ -1536,11 +1532,14 @@ int InitPlatform(void) // REF: https://github.com/raysan5/raylib/issues/1554 glfwSetJoystickCallback(NULL); - GLFWmonitor *monitor = NULL; - if (CORE.Window.fullscreen) + if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + + // Init window in fullscreen mode if requested + // NOTE: Keeping original screen size for toggle + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) { // NOTE: Fullscreen applications default to the primary monitor - monitor = glfwGetPrimaryMonitor(); + GLFWmonitor *monitor = glfwGetPrimaryMonitor(); if (!monitor) { TRACELOG(LOG_WARNING, "GLFW: Failed to get primary monitor"); @@ -1614,9 +1613,6 @@ int InitPlatform(void) TRACELOG(LOG_WARNING, "GLFW: Failed to initialize Window"); return -1; } - - // NOTE: Full-screen change, not working properly... - //glfwSetWindowMonitor(platform.handle, glfwGetPrimaryMonitor(), 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); } else { diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 39ac8fb32..04e461ded 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -290,14 +290,13 @@ bool WindowShouldClose(void) // Toggle fullscreen mode void ToggleFullscreen(void) { - if (!CORE.Window.fullscreen) + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) { // Store previous window position (in case we exit fullscreen) CORE.Window.previousPosition = CORE.Window.position; CORE.Window.previousScreen = CORE.Window.screen; platform.mon = RGFW_window_getMonitor(platform.window); - CORE.Window.fullscreen = true; FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); RGFW_monitor_scaleToWindow(platform.mon, platform.window); @@ -305,7 +304,6 @@ void ToggleFullscreen(void) } else { - CORE.Window.fullscreen = false; FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); if (platform.mon.mode.area.w) @@ -331,7 +329,9 @@ void ToggleFullscreen(void) // Toggle borderless windowed mode void ToggleBorderlessWindowed(void) { - if (CORE.Window.fullscreen) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) ToggleFullscreen(); + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) { CORE.Window.previousPosition = CORE.Window.position; CORE.Window.previousScreen = CORE.Window.screen; @@ -348,8 +348,6 @@ void ToggleBorderlessWindowed(void) CORE.Window.position = CORE.Window.previousPosition; RGFW_window_resize(platform.window, RGFW_AREA(CORE.Window.previousScreen.width, CORE.Window.previousScreen.height)); } - - CORE.Window.fullscreen = !CORE.Window.fullscreen; } // Set window state: maximized, if resizable diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 612707cea..952268ca6 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -472,13 +472,11 @@ void ToggleFullscreen(void) { SDL_SetWindowFullscreen(platform.window, 0); FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); - CORE.Window.fullscreen = false; } else { SDL_SetWindowFullscreen(platform.window, SDL_WINDOW_FULLSCREEN); FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); - CORE.Window.fullscreen = true; } } else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); @@ -554,7 +552,7 @@ void SetWindowState(unsigned int flags) #endif { SDL_SetWindowFullscreen(platform.window, SDL_WINDOW_FULLSCREEN); - CORE.Window.fullscreen = true; + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); } else TRACELOG(LOG_WARNING, "SDL: Failed to find selected monitor"); } @@ -644,7 +642,6 @@ void ClearWindowState(unsigned int flags) if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) { SDL_SetWindowFullscreen(platform.window, 0); - CORE.Window.fullscreen = false; } if (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) { @@ -1937,11 +1934,7 @@ int InitPlatform(void) FLAG_SET(flags, SDL_WINDOW_MOUSE_CAPTURE); // Window has mouse captured // Check window creation flags - if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) - { - CORE.Window.fullscreen = true; - FLAG_SET(flags, SDL_WINDOW_FULLSCREEN); - } + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) FLAG_SET(flags, SDL_WINDOW_FULLSCREEN); //if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) FLAG_SET(flags, SDL_WINDOW_HIDDEN); if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED)) FLAG_SET(flags, SDL_WINDOW_BORDERLESS); diff --git a/src/platforms/rcore_template.c b/src/platforms/rcore_template.c index 1f8c5242b..b22d3f2f5 100644 --- a/src/platforms/rcore_template.c +++ b/src/platforms/rcore_template.c @@ -454,7 +454,6 @@ int InitPlatform(void) // raylib uses OpenGL so, platform should create that kind of connection // Below example illustrates that process using EGL library //---------------------------------------------------------------------------- - CORE.Window.fullscreen = true; FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT)) diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index adfdace74..e138de302 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -204,7 +204,6 @@ void ToggleFullscreen(void) EM_ASM(document.exitFullscreen();); - CORE.Window.fullscreen = false; FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); } @@ -213,14 +212,12 @@ void ToggleFullscreen(void) if (enterFullscreen) { // NOTE: The setTimeouts handle the browser mode change delay - EM_ASM - ( - setTimeout(function() - { + EM_ASM( + setTimeout(function(){ Module.requestFullscreen(false, false); }, 100); ); - CORE.Window.fullscreen = true; + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); } @@ -238,7 +235,7 @@ void ToggleFullscreen(void) */ // EM_ASM(Module.requestFullscreen(false, false);); /* - if (!CORE.Window.fullscreen) + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) { // Option 1: Request fullscreen for the canvas element // This option does not seem to work at all: @@ -274,7 +271,6 @@ void ToggleFullscreen(void) emscripten_get_canvas_element_size(platform.canvasId, &width, &height); TRACELOG(LOG_WARNING, "Emscripten: Enter fullscreen: Canvas size: %i x %i", width, height); - CORE.Window.fullscreen = true; // Toggle fullscreen flag FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); } else @@ -286,7 +282,6 @@ void ToggleFullscreen(void) emscripten_get_canvas_element_size(platform.canvasId, &width, &height); TRACELOG(LOG_WARNING, "Emscripten: Exit fullscreen: Canvas size: %i x %i", width, height); - CORE.Window.fullscreen = false; // Toggle fullscreen flag FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); } */ @@ -313,7 +308,6 @@ void ToggleBorderlessWindowed(void) EM_ASM(document.exitFullscreen();); - CORE.Window.fullscreen = false; FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); } @@ -545,7 +539,6 @@ void ClearWindowState(unsigned int flags) if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE) || (canvasStyleWidth > canvasWidth)) EM_ASM(document.exitFullscreen();); } - CORE.Window.fullscreen = false; FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); } @@ -1155,8 +1148,6 @@ int InitPlatform(void) // glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers // Check window creation flags - if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) CORE.Window.fullscreen = true; - if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window else glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); // Window initially hidden @@ -1260,7 +1251,7 @@ int InitPlatform(void) // TODO: Consider requesting another type of canvas, not a WebGL one --> Replace GLFW-web by Emscripten? platform.pixels = (unsigned int *)RL_CALLOC(CORE.Window.screen.width*CORE.Window.screen.height, sizeof(unsigned int)); #else - if (CORE.Window.fullscreen) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) { // remember center for switchinging from fullscreen to window if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width)) @@ -1830,7 +1821,6 @@ static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const Emscripte const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); if (!wasFullscreen) { - CORE.Window.fullscreen = false; FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); } diff --git a/src/platforms/rcore_web_emscripten.c b/src/platforms/rcore_web_emscripten.c index 25b477734..aeead6d9b 100644 --- a/src/platforms/rcore_web_emscripten.c +++ b/src/platforms/rcore_web_emscripten.c @@ -167,7 +167,6 @@ void ToggleFullscreen(void) EM_ASM(document.exitFullscreen();); - CORE.Window.fullscreen = false; FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); } @@ -183,7 +182,7 @@ void ToggleFullscreen(void) Module.requestFullscreen(false, false); }, 100); ); - CORE.Window.fullscreen = true; + FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); } @@ -201,7 +200,7 @@ void ToggleFullscreen(void) */ // EM_ASM(Module.requestFullscreen(false, false);); /* - if (!CORE.Window.fullscreen) + if (!FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) { // Option 1: Request fullscreen for the canvas element // This option does not seem to work at all: @@ -237,7 +236,6 @@ void ToggleFullscreen(void) emscripten_get_canvas_element_size("#canvas", &width, &height); TRACELOG(LOG_WARNING, "Emscripten: Enter fullscreen: Canvas size: %i x %i", width, height); - CORE.Window.fullscreen = true; // Toggle fullscreen flag FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); } else @@ -249,7 +247,6 @@ void ToggleFullscreen(void) emscripten_get_canvas_element_size("#canvas", &width, &height); TRACELOG(LOG_WARNING, "Emscripten: Exit fullscreen: Canvas size: %i x %i", width, height); - CORE.Window.fullscreen = false; // Toggle fullscreen flag FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); } */ @@ -275,7 +272,6 @@ void ToggleBorderlessWindowed(void) EM_ASM(document.exitFullscreen();); - CORE.Window.fullscreen = false; FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); } @@ -494,7 +490,6 @@ void ClearWindowState(unsigned int flags) if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE) || (canvasStyleWidth > canvasWidth)) EM_ASM(document.exitFullscreen();); } - CORE.Window.fullscreen = false; FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); } @@ -1117,8 +1112,6 @@ int InitPlatform(void) attribs.antialias = EM_FALSE; // Check window creation flags - //if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) CORE.Window.fullscreen = true; - // Disable FLAG_WINDOW_MINIMIZED, not supported if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); @@ -1354,7 +1347,6 @@ static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const Emscripte const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); if (!wasFullscreen) { - CORE.Window.fullscreen = false; FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); } diff --git a/src/rcore.c b/src/rcore.c index 6f16b605b..d0a57048d 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -287,20 +287,19 @@ typedef struct CoreData { const char *title; // Window text title const pointer unsigned int flags; // Configuration flags (bit based), keeps window state bool ready; // Check if window has been initialized successfully - bool fullscreen; // Check if fullscreen mode is enabled bool shouldClose; // Check if window set for closing bool resizedLastFrame; // Check if window has been resized last frame bool eventWaiting; // Wait for events before ending frame bool usingFbo; // Using FBO (RenderTexture) for rendering instead of default framebuffer - Point position; // Window position (required on fullscreen toggle) - Point previousPosition; // Window previous position (required on borderless windowed toggle) Size display; // Display width and height (monitor, device-screen, LCD, ...) - Size screen; // Screen width and height (used render area) - Size previousScreen; // Screen previous width and height (required on borderless windowed toggle) - Size currentFbo; // Current render width and height (depends on active fbo) - Size render; // Framebuffer width and height (render area, including black bars if required) - Point renderOffset; // Offset from render area (must be divided by 2) + Size screen; // Screen current width and height + Point position; // Window current position + Size previousScreen; // Screen previous width and height (required on fullscreen/borderless-windowed toggle) + Point previousPosition; // Window previous position (required on fullscreeen/borderless-windowed toggle) + Size render; // Screen framebuffer width and height + Point renderOffset; // Screen framebuffer render offset (Not required anymore?) + Size currentFbo; // Current framebuffer render width and height (depends on active render texture) Size screenMin; // Screen minimum width and height (for resizable window) Size screenMax; // Screen maximum width and height (for resizable window) Matrix screenScale; // Matrix to scale screen (framebuffer rendering) @@ -762,31 +761,31 @@ bool IsWindowReady(void) // Check if window is currently fullscreen bool IsWindowFullscreen(void) { - return CORE.Window.fullscreen; + return FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); } // Check if window is currently hidden bool IsWindowHidden(void) { - return (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)); + return FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN); } // Check if window has been minimized bool IsWindowMinimized(void) { - return (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)); + return FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); } // Check if window has been maximized bool IsWindowMaximized(void) { - return (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)); + return FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); } // Check if window has the focus bool IsWindowFocused(void) { - return (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)); + return !FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); } // Check if window has been resizedLastFrame @@ -798,7 +797,7 @@ bool IsWindowResized(void) // Check if one specific window flag is enabled bool IsWindowState(unsigned int flag) { - return (FLAG_IS_SET(CORE.Window.flags, flag)); + return FLAG_IS_SET(CORE.Window.flags, flag); } // Get current screen width @@ -1100,7 +1099,7 @@ void BeginScissorMode(int x, int y, int width, int height) rlScissor((int)(x*scale.x), (int)(GetScreenHeight()*scale.y - (((y + height)*scale.y))), (int)(width*scale.x), (int)(height*scale.y)); } #else - if (!CORE.Window.usingFbo && (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI))) + if (!CORE.Window.usingFbo && FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { Vector2 scale = GetWindowScaleDPI(); rlScissor((int)(x*scale.x), (int)(CORE.Window.currentFbo.height - (y + height)*scale.y), (int)(width*scale.x), (int)(height*scale.y)); From 37bc3f50120f752bc2767a32a11f1fcffd6906e0 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 28 Dec 2025 16:07:59 +0100 Subject: [PATCH 273/430] REMOVED: `SetupFramebuffer()`, most platforms do not need it any more Kept only for platforms that could potentially need it --- src/platforms/rcore_android.c | 80 +++++++++++++++++++++++++++++ src/platforms/rcore_desktop_rgfw.c | 12 ++--- src/platforms/rcore_desktop_win32.c | 3 +- src/platforms/rcore_drm.c | 80 +++++++++++++++++++++++++++++ src/rcore.c | 79 ---------------------------- 5 files changed, 164 insertions(+), 90 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index cca4f4d39..f150bf638 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -267,6 +267,8 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd); static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); // Process Android inputs static GamepadButton AndroidTranslateGamepadButton(int button); // Map Android gamepad button to raylib gamepad button +static void SetupFramebuffer(int width, int height); // Setup main framebuffer (required by InitPlatform()) + //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- @@ -1419,4 +1421,82 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) return 0; } +// Compute framebuffer size relative to screen size and display size +// NOTE: Global variables CORE.Window.render.width/CORE.Window.render.height and CORE.Window.renderOffset.x/CORE.Window.renderOffset.y can be modified +static void SetupFramebuffer(int width, int height) +{ + // Calculate CORE.Window.render.width and CORE.Window.render.height, we have the display size (input params) and the desired screen size (global var) + if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Downscaling required: Screen size (%ix%i) is bigger than display size (%ix%i)", CORE.Window.screen.width, CORE.Window.screen.height, CORE.Window.display.width, CORE.Window.display.height); + + // Downscaling to fit display with border-bars + float widthRatio = (float)CORE.Window.display.width/(float)CORE.Window.screen.width; + float heightRatio = (float)CORE.Window.display.height/(float)CORE.Window.screen.height; + + if (widthRatio <= heightRatio) + { + CORE.Window.render.width = CORE.Window.display.width; + CORE.Window.render.height = (int)round((float)CORE.Window.screen.height*widthRatio); + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = (CORE.Window.display.height - CORE.Window.render.height); + } + else + { + CORE.Window.render.width = (int)round((float)CORE.Window.screen.width*heightRatio); + CORE.Window.render.height = CORE.Window.display.height; + CORE.Window.renderOffset.x = (CORE.Window.display.width - CORE.Window.render.width); + CORE.Window.renderOffset.y = 0; + } + + // Screen scaling required + float scaleRatio = (float)CORE.Window.render.width/(float)CORE.Window.screen.width; + CORE.Window.screenScale = MatrixScale(scaleRatio, scaleRatio, 1.0f); + + // NOTE: We render to full display resolution! + // We just need to calculate above parameters for downscale matrix and offsets + CORE.Window.render.width = CORE.Window.display.width; + CORE.Window.render.height = CORE.Window.display.height; + + TRACELOG(LOG_WARNING, "DISPLAY: Downscale matrix generated, content will be rendered at (%ix%i)", CORE.Window.render.width, CORE.Window.render.height); + } + else if ((CORE.Window.screen.width < CORE.Window.display.width) || (CORE.Window.screen.height < CORE.Window.display.height)) + { + // Required screen size is smaller than display size + TRACELOG(LOG_INFO, "DISPLAY: Upscaling required: Screen size (%ix%i) smaller than display size (%ix%i)", CORE.Window.screen.width, CORE.Window.screen.height, CORE.Window.display.width, CORE.Window.display.height); + + if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) + { + CORE.Window.screen.width = CORE.Window.display.width; + CORE.Window.screen.height = CORE.Window.display.height; + } + + // Upscaling to fit display with border-bars + float displayRatio = (float)CORE.Window.display.width/(float)CORE.Window.display.height; + float screenRatio = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; + + if (displayRatio <= screenRatio) + { + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = (int)round((float)CORE.Window.screen.width/displayRatio); + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = (CORE.Window.render.height - CORE.Window.screen.height); + } + else + { + CORE.Window.render.width = (int)round((float)CORE.Window.screen.height*displayRatio); + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.renderOffset.x = (CORE.Window.render.width - CORE.Window.screen.width); + CORE.Window.renderOffset.y = 0; + } + } + else + { + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = 0; + } +} + // EOF diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 04e461ded..558b6de55 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -383,7 +383,7 @@ void SetWindowState(unsigned int flags) } if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) { - if (!CORE.Window.fullscreen) ToggleFullscreen(); + ToggleFullscreen(); } if (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) { @@ -457,7 +457,7 @@ void ClearWindowState(unsigned int flags) } if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE)) { - if (CORE.Window.fullscreen) ToggleFullscreen(); + ToggleFullscreen(); } if (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) { @@ -508,7 +508,7 @@ void ClearWindowState(unsigned int flags) } if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE)) { - if (CORE.Window.fullscreen) ToggleBorderlessWindowed(); + ToggleBorderlessWindowed(); } if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT)) { @@ -1256,13 +1256,11 @@ int InitPlatform(void) // Check window creation flags if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) { - CORE.Window.fullscreen = true; FLAG_SET(flags, RGFW_windowFullscreen); } if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) { - CORE.Window.fullscreen = true; FLAG_SET(flags, RGFW_windowedFullscreen); } @@ -1313,10 +1311,6 @@ int InitPlatform(void) CORE.Window.display.width = CORE.Window.screen.width; CORE.Window.display.height = CORE.Window.screen.height; #endif - // TODO: Is this needed by raylib now? - // If so, rcore_desktop_sdl should be updated too - //SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); - if (FLAG_IS_SET(CORE.Window.flags, FLAG_VSYNC_HINT)) RGFW_window_swapInterval(platform.window, 1); RGFW_window_makeCurrent(platform.window); diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index ce9d86cc2..973fafa68 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -2049,8 +2049,7 @@ static void HandleWindowResize(HWND hwnd, int *width, int *height) // TODO: Update framebuffer on resize CORE.Window.currentFbo.width = (int)clientSize.cx; CORE.Window.currentFbo.height = (int)clientSize.cy; - //glViewport(0, 0, clientSize.cx, clientSize.cy); - //SetupFramebuffer(0, 0); + //SetupViewport(0, 0, clientSize.cx, clientSize.cy); SetupViewport(clientSize.cx, clientSize.cy); CORE.Window.resizedLastFrame = true; diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 640799b0a..0aeab3ab4 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -265,6 +265,8 @@ static int FindMatchingConnectorMode(const drmModeConnector *connector, const dr static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search exactly matching DRM connector mode in connector's list static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search the nearest matching DRM connector mode in connector's list +static void SetupFramebuffer(int width, int height); // Setup main framebuffer (required by InitPlatform()) + //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- @@ -2479,4 +2481,82 @@ static int FindNearestConnectorMode(const drmModeConnector *connector, uint widt return nearestIndex; } +// Compute framebuffer size relative to screen size and display size +// NOTE: Global variables CORE.Window.render.width/CORE.Window.render.height and CORE.Window.renderOffset.x/CORE.Window.renderOffset.y can be modified +static void SetupFramebuffer(int width, int height) +{ + // Calculate CORE.Window.render.width and CORE.Window.render.height, we have the display size (input params) and the desired screen size (global var) + if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Downscaling required: Screen size (%ix%i) is bigger than display size (%ix%i)", CORE.Window.screen.width, CORE.Window.screen.height, CORE.Window.display.width, CORE.Window.display.height); + + // Downscaling to fit display with border-bars + float widthRatio = (float)CORE.Window.display.width/(float)CORE.Window.screen.width; + float heightRatio = (float)CORE.Window.display.height/(float)CORE.Window.screen.height; + + if (widthRatio <= heightRatio) + { + CORE.Window.render.width = CORE.Window.display.width; + CORE.Window.render.height = (int)round((float)CORE.Window.screen.height*widthRatio); + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = (CORE.Window.display.height - CORE.Window.render.height); + } + else + { + CORE.Window.render.width = (int)round((float)CORE.Window.screen.width*heightRatio); + CORE.Window.render.height = CORE.Window.display.height; + CORE.Window.renderOffset.x = (CORE.Window.display.width - CORE.Window.render.width); + CORE.Window.renderOffset.y = 0; + } + + // Screen scaling required + float scaleRatio = (float)CORE.Window.render.width/(float)CORE.Window.screen.width; + CORE.Window.screenScale = MatrixScale(scaleRatio, scaleRatio, 1.0f); + + // NOTE: We render to full display resolution! + // We just need to calculate above parameters for downscale matrix and offsets + CORE.Window.render.width = CORE.Window.display.width; + CORE.Window.render.height = CORE.Window.display.height; + + TRACELOG(LOG_WARNING, "DISPLAY: Downscale matrix generated, content will be rendered at (%ix%i)", CORE.Window.render.width, CORE.Window.render.height); + } + else if ((CORE.Window.screen.width < CORE.Window.display.width) || (CORE.Window.screen.height < CORE.Window.display.height)) + { + // Required screen size is smaller than display size + TRACELOG(LOG_INFO, "DISPLAY: Upscaling required: Screen size (%ix%i) smaller than display size (%ix%i)", CORE.Window.screen.width, CORE.Window.screen.height, CORE.Window.display.width, CORE.Window.display.height); + + if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) + { + CORE.Window.screen.width = CORE.Window.display.width; + CORE.Window.screen.height = CORE.Window.display.height; + } + + // Upscaling to fit display with border-bars + float displayRatio = (float)CORE.Window.display.width/(float)CORE.Window.display.height; + float screenRatio = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; + + if (displayRatio <= screenRatio) + { + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = (int)round((float)CORE.Window.screen.width/displayRatio); + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = (CORE.Window.render.height - CORE.Window.screen.height); + } + else + { + CORE.Window.render.width = (int)round((float)CORE.Window.screen.height*displayRatio); + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.renderOffset.x = (CORE.Window.render.width - CORE.Window.screen.width); + CORE.Window.renderOffset.y = 0; + } + } + else + { + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.renderOffset.x = 0; + CORE.Window.renderOffset.y = 0; + } +} + // EOF diff --git a/src/rcore.c b/src/rcore.c index d0a57048d..565eb915c 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -492,7 +492,6 @@ extern int InitPlatform(void); // Initialize platform (graphics, inputs extern void ClosePlatform(void); // Close platform static void InitTimer(void); // Initialize timer, hi-resolution if available (required by InitPlatform()) -static void SetupFramebuffer(int width, int height); // Setup main framebuffer (required by InitPlatform()) static void SetupViewport(int width, int height); // Set viewport for a provided width and height static void ScanDirectoryFiles(const char *basePath, FilePathList *list, const char *filter); // Scan all files and directories in a base path @@ -3827,84 +3826,6 @@ void SetupViewport(int width, int height) rlLoadIdentity(); // Reset current matrix (modelview) } -// Compute framebuffer size relative to screen size and display size -// NOTE: Global variables CORE.Window.render.width/CORE.Window.render.height and CORE.Window.renderOffset.x/CORE.Window.renderOffset.y can be modified -void SetupFramebuffer(int width, int height) -{ - // Calculate CORE.Window.render.width and CORE.Window.render.height, we have the display size (input params) and the desired screen size (global var) - if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height)) - { - TRACELOG(LOG_WARNING, "DISPLAY: Downscaling required: Screen size (%ix%i) is bigger than display size (%ix%i)", CORE.Window.screen.width, CORE.Window.screen.height, CORE.Window.display.width, CORE.Window.display.height); - - // Downscaling to fit display with border-bars - float widthRatio = (float)CORE.Window.display.width/(float)CORE.Window.screen.width; - float heightRatio = (float)CORE.Window.display.height/(float)CORE.Window.screen.height; - - if (widthRatio <= heightRatio) - { - CORE.Window.render.width = CORE.Window.display.width; - CORE.Window.render.height = (int)round((float)CORE.Window.screen.height*widthRatio); - CORE.Window.renderOffset.x = 0; - CORE.Window.renderOffset.y = (CORE.Window.display.height - CORE.Window.render.height); - } - else - { - CORE.Window.render.width = (int)round((float)CORE.Window.screen.width*heightRatio); - CORE.Window.render.height = CORE.Window.display.height; - CORE.Window.renderOffset.x = (CORE.Window.display.width - CORE.Window.render.width); - CORE.Window.renderOffset.y = 0; - } - - // Screen scaling required - float scaleRatio = (float)CORE.Window.render.width/(float)CORE.Window.screen.width; - CORE.Window.screenScale = MatrixScale(scaleRatio, scaleRatio, 1.0f); - - // NOTE: We render to full display resolution! - // We just need to calculate above parameters for downscale matrix and offsets - CORE.Window.render.width = CORE.Window.display.width; - CORE.Window.render.height = CORE.Window.display.height; - - TRACELOG(LOG_WARNING, "DISPLAY: Downscale matrix generated, content will be rendered at (%ix%i)", CORE.Window.render.width, CORE.Window.render.height); - } - else if ((CORE.Window.screen.width < CORE.Window.display.width) || (CORE.Window.screen.height < CORE.Window.display.height)) - { - // Required screen size is smaller than display size - TRACELOG(LOG_INFO, "DISPLAY: Upscaling required: Screen size (%ix%i) smaller than display size (%ix%i)", CORE.Window.screen.width, CORE.Window.screen.height, CORE.Window.display.width, CORE.Window.display.height); - - if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) - { - CORE.Window.screen.width = CORE.Window.display.width; - CORE.Window.screen.height = CORE.Window.display.height; - } - - // Upscaling to fit display with border-bars - float displayRatio = (float)CORE.Window.display.width/(float)CORE.Window.display.height; - float screenRatio = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; - - if (displayRatio <= screenRatio) - { - CORE.Window.render.width = CORE.Window.screen.width; - CORE.Window.render.height = (int)round((float)CORE.Window.screen.width/displayRatio); - CORE.Window.renderOffset.x = 0; - CORE.Window.renderOffset.y = (CORE.Window.render.height - CORE.Window.screen.height); - } - else - { - CORE.Window.render.width = (int)round((float)CORE.Window.screen.height*displayRatio); - CORE.Window.render.height = CORE.Window.screen.height; - CORE.Window.renderOffset.x = (CORE.Window.render.width - CORE.Window.screen.width); - CORE.Window.renderOffset.y = 0; - } - } - else - { - CORE.Window.render.width = CORE.Window.screen.width; - CORE.Window.render.height = CORE.Window.screen.height; - CORE.Window.renderOffset.x = 0; - CORE.Window.renderOffset.y = 0; - } -} - // Scan all files and directories in a base path // WARNING: files.paths[] must be previously allocated and // contain enough space to store all required paths From 1d8e011eee6129005648b3dd105a19f355d82a41 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 28 Dec 2025 16:08:08 +0100 Subject: [PATCH 274/430] Update rcore_drm.c --- src/platforms/rcore_drm.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 0aeab3ab4..366477aac 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -1149,7 +1149,6 @@ int InitPlatform(void) // Initialize graphic device: display/window and graphic context //---------------------------------------------------------------------------- - CORE.Window.fullscreen = true; FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); #if defined(DEFAULT_GRAPHIC_DEVICE_DRM) From 8cfb99f275dcd48ce6c91a289d2c9914bae2035b Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 28 Dec 2025 16:08:19 +0100 Subject: [PATCH 275/430] Minor comment tweaks --- src/rcore.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 565eb915c..ea38300f1 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -315,17 +315,17 @@ typedef struct CoreData { struct { struct { int exitKey; // Default exit key - char currentKeyState[MAX_KEYBOARD_KEYS]; // Registers current frame key state - char previousKeyState[MAX_KEYBOARD_KEYS]; // Registers previous frame key state + char currentKeyState[MAX_KEYBOARD_KEYS]; // Registers current frame key state + char previousKeyState[MAX_KEYBOARD_KEYS]; // Registers previous frame key state // NOTE: Since key press logic involves comparing previous vs currrent key state, // key repeats needs to be handled specially - char keyRepeatInFrame[MAX_KEYBOARD_KEYS]; // Registers key repeats for current frame + char keyRepeatInFrame[MAX_KEYBOARD_KEYS]; // Registers key repeats for current frame - int keyPressedQueue[MAX_KEY_PRESSED_QUEUE]; // Input keys queue + int keyPressedQueue[MAX_KEY_PRESSED_QUEUE]; // Input keys queue int keyPressedQueueCount; // Input keys queue count - int charPressedQueue[MAX_CHAR_PRESSED_QUEUE]; // Input characters queue (unicode) + int charPressedQueue[MAX_CHAR_PRESSED_QUEUE]; // Input characters queue (unicode) int charPressedQueueCount; // Input characters queue count } Keyboard; @@ -341,8 +341,8 @@ typedef struct CoreData { bool cursorLocked; // Track if cursor is locked (disabled) bool cursorOnScreen; // Tracks if cursor is inside client area - char currentButtonState[MAX_MOUSE_BUTTONS]; // Registers current mouse button state - char previousButtonState[MAX_MOUSE_BUTTONS]; // Registers previous mouse button state + char currentButtonState[MAX_MOUSE_BUTTONS]; // Registers current mouse button state + char previousButtonState[MAX_MOUSE_BUTTONS]; // Registers previous mouse button state Vector2 currentWheelMove; // Registers current mouse wheel variation Vector2 previousWheelMove; // Registers previous mouse wheel variation From 297dcc07b850beafc3ad79609f762e54c0be7f84 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 28 Dec 2025 16:08:34 +0100 Subject: [PATCH 276/430] Update core_highdpi_testbed.c --- examples/core/core_highdpi_testbed.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/core/core_highdpi_testbed.c b/examples/core/core_highdpi_testbed.c index bf103fb31..d34951fc0 100644 --- a/examples/core/core_highdpi_testbed.c +++ b/examples/core/core_highdpi_testbed.c @@ -49,6 +49,7 @@ int main(void) scaleDpi = GetWindowScaleDPI(); if (IsKeyPressed(KEY_SPACE)) ToggleBorderlessWindowed(); + if (IsKeyPressed(KEY_F)) ToggleFullscreen(); //---------------------------------------------------------------------------------- // Draw @@ -58,12 +59,12 @@ int main(void) ClearBackground(RAYWHITE); // Draw grid - for (int h = 0; h < 20; h++) + for (int h = 0; h < GetScreenHeight()/gridSpacing + 1; h++) { DrawText(TextFormat("%02i", h*gridSpacing), 4, h*gridSpacing - 4, 10, GRAY); DrawLine(24, h*gridSpacing, GetScreenWidth(), h*gridSpacing, LIGHTGRAY); } - for (int v = 0; v < 40; v++) + for (int v = 0; v < GetScreenWidth()/gridSpacing + 1; v++) { DrawText(TextFormat("%02i", v*gridSpacing), v*gridSpacing - 10, 4, 10, GRAY); DrawLine(v*gridSpacing, 20, v*gridSpacing, GetScreenHeight(), LIGHTGRAY); @@ -76,6 +77,10 @@ int main(void) DrawText(TextFormat("RENDER SIZE: %ix%i", GetRenderWidth(), GetRenderHeight()), 50, 130, 20, DARKGRAY); DrawText(TextFormat("SCALE FACTOR: %.1fx%.1f", scaleDpi.x, scaleDpi.y), 50, 170, 20, GRAY); + // Draw reference rectangles, top-left and bottom-right corners + DrawRectangle(0, 0, 30, 60, RED); + DrawRectangle(GetScreenWidth() - 30, GetScreenHeight() - 60, 30, 60, BLUE); + // Draw mouse position DrawCircleV(GetMousePosition(), 20, MAROON); DrawRectangle(mousePos.x - 25, mousePos.y, 50, 2, BLACK); From 2cf8983e18c3a5869d0a6fa00549bb65e923d606 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 28 Dec 2025 16:11:42 +0100 Subject: [PATCH 277/430] WARNING: REDESIGNED: Fullscreen modes, use current display resolution Considering multi-monitor and multi-ppi configurations Fullscreen-exclusive scales to available display resolution, ignoring content scaling Windowed-borderless scales to available logical resolution considering HighDPI **if requested** --- src/platforms/rcore_desktop_glfw.c | 226 +++++++++++++---------------- 1 file changed, 103 insertions(+), 123 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index ed8b1b542..9b360771f 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -178,41 +178,56 @@ void ToggleFullscreen(void) { if (!FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) { - // Store previous window position (in case we exit fullscreen) + // Store previous screen data (in case exiting fullscreen) CORE.Window.previousPosition = CORE.Window.position; + CORE.Window.previousScreen = CORE.Window.screen; + // Use current monitor the window is on to get fullscreen required size int monitorCount = 0; int monitorIndex = GetCurrentMonitor(); GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); - - // Use current monitor, so we correctly get the display the window is on GLFWmonitor *monitor = (monitorIndex < monitorCount)? monitors[monitorIndex] : NULL; - if (monitor == NULL) + if (monitor != NULL) { - TRACELOG(LOG_WARNING, "GLFW: Failed to get monitor"); + // Get current monitor video mode + const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitorIndex]); + CORE.Window.display.width = mode->width; + CORE.Window.display.height = mode->height; + CORE.Window.position = (Point){ 0, 0 }; + CORE.Window.screen = (Size){ CORE.Window.display.width, CORE.Window.display.height }; - glfwSetWindowMonitor(platform.handle, NULL, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); - } - else - { - CORE.Window.fullscreen = true; + // Set fullscreen flag to be processed on FramebufferSizeCallback() accordingly FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + // WARNING: This function launches FramebufferSizeCallback() glfwSetWindowMonitor(platform.handle, monitor, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); } + else TRACELOG(LOG_WARNING, "GLFW: Failed to get monitor"); } else { - CORE.Window.fullscreen = false; + // Restore previous window position and size + CORE.Window.position = CORE.Window.previousPosition; + CORE.Window.screen = CORE.Window.previousScreen; + + // Set fullscreen flag to be processed on FramebufferSizeCallback() accordingly + // and considered by GetWindowScaleDPI() FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE); - glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.previousPosition.x, CORE.Window.previousPosition.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); +#if !defined(__APPLE__) + // Make sure to restore render size considering HighDPI scaling + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + Vector2 scaleDpi = GetWindowScaleDPI(); + CORE.Window.screen.width *= scaleDpi.x; + CORE.Window.screen.height *= scaleDpi.y; + } +#endif - // we update the window position right away - CORE.Window.position.x = CORE.Window.previousPosition.x; - CORE.Window.position.y = CORE.Window.previousPosition.y; + glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, + CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); } // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) @@ -224,13 +239,8 @@ void ToggleFullscreen(void) void ToggleBorderlessWindowed(void) { // Leave fullscreen before attempting to set borderless windowed mode - bool wasOnFullscreen = false; - if (CORE.Window.fullscreen) - { - // Fullscreen already saves the previous position so it does not need to be set here again - ToggleFullscreen(); - wasOnFullscreen = true; - } + // NOTE: Fullscreen already saves the previous position so it does not need to be set again later + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) ToggleFullscreen(); int monitorCount = 0; GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); @@ -246,7 +256,7 @@ void ToggleBorderlessWindowed(void) { // Store screen position and size // NOTE: If it was on fullscreen, screen position was already stored, so skip setting it here - if (!wasOnFullscreen) CORE.Window.previousPosition = CORE.Window.position; + CORE.Window.previousPosition = CORE.Window.position; CORE.Window.previousScreen = CORE.Window.screen; // Set undecorated flag @@ -261,15 +271,8 @@ void ToggleBorderlessWindowed(void) const int monitorHeight = mode->height; // Set screen position and size - glfwSetWindowMonitor( - platform.handle, - monitors[monitor], - monitorPosX, - monitorPosY, - monitorWidth, - monitorHeight, - mode->refreshRate - ); + glfwSetWindowMonitor(platform.handle, monitors[monitor], monitorPosX, monitorPosY, + monitorWidth, monitorHeight, mode->refreshRate); // Refocus window glfwFocusWindow(platform.handle); @@ -278,39 +281,32 @@ void ToggleBorderlessWindowed(void) } else { + // Restore previous screen values + CORE.Window.position = CORE.Window.previousPosition; + CORE.Window.screen = CORE.Window.previousScreen; + // Remove undecorated flag glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_TRUE); FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); #if !defined(__APPLE__) - // Make sure to restore size to HighDPI + // Make sure to restore size considering HighDPI scaling if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { Vector2 scaleDpi = GetWindowScaleDPI(); - CORE.Window.previousScreen.width *= scaleDpi.x; - CORE.Window.previousScreen.height *= scaleDpi.y; + CORE.Window.screen.width *= scaleDpi.x; + CORE.Window.screen.height *= scaleDpi.y; } #endif - // Return previous screen size and position - // NOTE: The order matters here, it must set size first, then set position, otherwise the screen will be positioned incorrectly - glfwSetWindowMonitor( - platform.handle, - NULL, - CORE.Window.previousPosition.x, - CORE.Window.previousPosition.y, - CORE.Window.previousScreen.width, - CORE.Window.previousScreen.height, - mode->refreshRate - ); + // Return to previous screen size and position + glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, + CORE.Window.screen.width, CORE.Window.screen.height, mode->refreshRate); // Refocus window glfwFocusWindow(platform.handle); FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); - - CORE.Window.position.x = CORE.Window.previousPosition.x; - CORE.Window.position.y = CORE.Window.previousPosition.y; } } else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); @@ -1023,7 +1019,8 @@ Vector2 GetWindowPosition(void) Vector2 GetWindowScaleDPI(void) { Vector2 scale = { 1.0f, 1.0f }; - if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) glfwGetWindowContentScale(platform.handle, &scale.x, &scale.y); + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI) && !FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) + glfwGetWindowContentScale(platform.handle, &scale.x, &scale.y); return scale; } @@ -1553,59 +1550,20 @@ int InitPlatform(void) CORE.Window.display.width = mode->width; CORE.Window.display.height = mode->height; - // Set screen width/height to the display width/height if they are 0 + // Check if user requested some screen size + if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) + { + // Set some default screen size in case user decides to exit fullscreen mode + CORE.Window.previousScreen.width = 800; + CORE.Window.previousScreen.height = 450; + CORE.Window.previousPosition.x = CORE.Window.display.width/2 - 800/2; + CORE.Window.previousPosition.y = CORE.Window.display.height/2 - 450/2; + } + + // Set screen width/height to the display width/height if (CORE.Window.screen.width == 0) CORE.Window.screen.width = CORE.Window.display.width; if (CORE.Window.screen.height == 0) CORE.Window.screen.height = CORE.Window.display.height; - // Remember center for switching from fullscreen to window - if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width)) - { - // If screen width/height equal to the display, we can't calculate the window pos for toggling full-screened/windowed - // Toggling full-screened/windowed with pos(0, 0) can cause problems in some platforms, such as X11 - CORE.Window.position.x = CORE.Window.display.width/4; - CORE.Window.position.y = CORE.Window.display.height/4; - } - else - { - CORE.Window.position.x = CORE.Window.display.width/2 - CORE.Window.screen.width/2; - CORE.Window.position.y = CORE.Window.display.height/2 - CORE.Window.screen.height/2; - } - - if (CORE.Window.position.x < 0) CORE.Window.position.x = 0; - if (CORE.Window.position.y < 0) CORE.Window.position.y = 0; - - // Obtain recommended CORE.Window.display.width/CORE.Window.display.height from a valid videomode for the monitor - int count = 0; - const GLFWvidmode *modes = glfwGetVideoModes(monitor, &count); - - // Get closest video mode to desired CORE.Window.screen.width/CORE.Window.screen.height - for (int i = 0; i < count; i++) - { - if ((unsigned int)modes[i].width >= CORE.Window.screen.width) - { - if ((unsigned int)modes[i].height >= CORE.Window.screen.height) - { - CORE.Window.display.width = modes[i].width; - CORE.Window.display.height = modes[i].height; - break; - } - } - } - - TRACELOG(LOG_INFO, "SYSTEM: Closest fullscreen videomode: %i x %i", CORE.Window.display.width, CORE.Window.display.height); - - // NOTE: ISSUE: Closest videomode could not match monitor aspect-ratio, for example, - // for a desired screen size of 800x450 (16:9), closest supported videomode is 800x600 (4:3), - // framebuffer is rendered correctly but once displayed on a 16:9 monitor, it gets stretched - // by the sides to fit all monitor space... - - // Try to setup the most appropriate fullscreen framebuffer for the requested screenWidth/screenHeight - // It considers device display resolution mode and setups a framebuffer with black bars if required (render size/offset) - // Modified global variables: CORE.Window.screen.width/CORE.Window.screen.height - CORE.Window.render.width/CORE.Window.render.height - CORE.Window.renderOffset.x/CORE.Window.renderOffset.y - CORE.Window.screenScale - // TODO: It is a quite cumbersome solution to display size vs requested size, it should be reviewed or removed... - // HighDPI monitors are properly considered in a following similar function: SetupViewport() - SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); - platform.handle = glfwCreateWindow(CORE.Window.display.width, CORE.Window.display.height, (CORE.Window.title != 0)? CORE.Window.title : " ", monitor, NULL); if (!platform.handle) { @@ -1616,14 +1574,11 @@ int InitPlatform(void) } else { - // No-fullscreen window creation - bool requestWindowedFullscreen = (CORE.Window.screen.height == 0) && (CORE.Window.screen.width == 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; + if (CORE.Window.screen.width == 0) CORE.Window.screen.width = 1; + if (CORE.Window.screen.height == 0) CORE.Window.screen.height = 1; - platform.handle = glfwCreateWindow(creationWidth, creationHeight, (CORE.Window.title != 0)? CORE.Window.title : " ", NULL, NULL); + platform.handle = glfwCreateWindow(CORE.Window.screen.width, CORE.Window.screen.height, (CORE.Window.title != 0)? CORE.Window.title : " ", NULL, NULL); if (!platform.handle) { glfwTerminate(); @@ -1632,7 +1587,7 @@ int InitPlatform(void) } // After the window was created, determine the monitor that the window manager assigned - // Derive display sizes, and, if possible, window size in case it was zero at beginning + // Derive display sizes and, if possible, window size in case it was zero at beginning int monitorCount = 0; int monitorIndex = GetCurrentMonitor(); @@ -1640,7 +1595,7 @@ int InitPlatform(void) if (monitorIndex < monitorCount) { - monitor = monitors[monitorIndex]; + GLFWmonitor *monitor = monitors[monitorIndex]; const GLFWvidmode *mode = glfwGetVideoMode(monitor); // Default display resolution to that of the current mode @@ -1651,7 +1606,7 @@ int InitPlatform(void) if (CORE.Window.screen.width == 0) CORE.Window.screen.width = CORE.Window.display.width; if (CORE.Window.screen.height == 0) CORE.Window.screen.height = CORE.Window.display.height; - if (requestWindowedFullscreen) glfwSetWindowSize(platform.handle, CORE.Window.screen.width, CORE.Window.screen.height); + glfwSetWindowSize(platform.handle, CORE.Window.screen.width, CORE.Window.screen.height); } else { @@ -1693,6 +1648,8 @@ int InitPlatform(void) { // NOTE: On APPLE platforms system should manage window/input scaling and also framebuffer scaling // Framebuffer scaling is activated with: glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); + + // Get current framebuffer size, on high-dpi it could be bigger than screen size glfwGetFramebufferSize(platform.handle, &fbWidth, &fbHeight); // Screen scaling matrix is required in case desired screen area is different from display area @@ -1726,6 +1683,11 @@ int InitPlatform(void) if (!CORE.Window.ready) { TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphic device"); return -1; } else { + int monitorCount = 0; + int monitorIndex = GetCurrentMonitor(); + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + GLFWmonitor *monitor = monitors[monitorIndex]; + // Try to center window on screen but avoiding window-bar outside of screen int monitorX = 0; int monitorY = 0; @@ -1733,7 +1695,7 @@ int InitPlatform(void) int monitorHeight = 0; glfwGetMonitorWorkarea(monitor, &monitorX, &monitorY, &monitorWidth, &monitorHeight); - // Here CORE.Window.render.width/height should be used instead of + // TODO: Here CORE.Window.render.width/height should be used instead of // CORE.Window.screen.width/height to center the window correctly when the high dpi flag is enabled int posX = monitorX + (monitorWidth - (int)CORE.Window.render.width)/2; int posY = monitorY + (monitorHeight - (int)CORE.Window.render.height)/2; @@ -1855,7 +1817,7 @@ static void WindowSizeCallback(GLFWwindow *window, int width, int height) // WARNING: If FLAG_WINDOW_HIGHDPI is set, WindowContentScaleCallback() is called before this function static void FramebufferSizeCallback(GLFWwindow *window, int width, int height) { - //TRACELOG(LOG_INFO, "GLFW3: Window framebuffer size callback called [%i,%i]", width, height); + TRACELOG(LOG_INFO, "GLFW3: Window framebuffer size callback called [%i,%i]", width, height); // WARNING: On window minimization, callback is called, // but we don't want to change internal screen values, it breaks things @@ -1870,19 +1832,38 @@ static void FramebufferSizeCallback(GLFWwindow *window, int width, int height) CORE.Window.currentFbo.height = height; CORE.Window.resizedLastFrame = true; - // Check if render size was actually scaled for high-dpi - if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) - { - // Set screen size to logical pixel size, considering content scaling - Vector2 scaleDpi = GetWindowScaleDPI(); - CORE.Window.screen.width = (int)((float)width/scaleDpi.x); - CORE.Window.screen.height = (int)((float)height/scaleDpi.y); - } - else + if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) { + // On fullscreen mode, strategy is ignoring high-dpi and + // use the all available display size + // Set screen size to render size (physical pixel size) CORE.Window.screen.width = width; CORE.Window.screen.height = height; + CORE.Window.screenScale = MatrixScale(1.0f, 1.0f, 1.0f); + SetMouseScale(1.0f, 1.0f); + } + else // Window mode (including borderless window) + { + // Check if render size was actually scaled for high-dpi + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) + { + // Set screen size to logical pixel size, considering content scaling + Vector2 scaleDpi = GetWindowScaleDPI(); + CORE.Window.screen.width = (int)((float)width/scaleDpi.x); + CORE.Window.screen.height = (int)((float)height/scaleDpi.y); + CORE.Window.screenScale = MatrixScale(scaleDpi.x, scaleDpi.y, 1.0f); +#if !defined(__APPLE__) + // Mouse input scaling for the new screen size + SetMouseScale(1.0f/scaleDpi.x, 1.0f/scaleDpi.y); +#endif + } + else + { + // Set screen size to render size (physical pixel size) + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + } } // WARNING: If using a render texture, it is not scaled to new size @@ -1903,13 +1884,12 @@ static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float s #if !defined(__APPLE__) // Mouse input scaling for the new screen size - SetMouseScale((float)CORE.Window.screen.width/fbWidth, (float)CORE.Window.screen.height/fbHeight); + SetMouseScale(1.0f/scalex, 1.0f/scaley); #endif CORE.Window.render.width = (int)fbWidth; CORE.Window.render.height = (int)fbHeight; - CORE.Window.currentFbo.width = (int)fbWidth; - CORE.Window.currentFbo.height = (int)fbHeight; + CORE.Window.currentFbo = CORE.Window.render; } // GLFW3: Window position callback, runs when window position changes From 11c248aa820ffd35a8684ad68b521fd101095418 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 28 Dec 2025 16:20:43 +0100 Subject: [PATCH 278/430] Update rcore_web.c --- src/platforms/rcore_web.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index e138de302..b52ca1bf0 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -1290,18 +1290,6 @@ int InitPlatform(void) TRACELOG(LOG_WARNING, "SYSTEM: Closest fullscreen videomode: %i x %i", CORE.Window.display.width, CORE.Window.display.height); - // NOTE: ISSUE: Closest videomode could not match monitor aspect-ratio, for example, - // for a desired screen size of 800x450 (16:9), closest supported videomode is 800x600 (4:3), - // framebuffer is rendered correctly but once displayed on a 16:9 monitor, it gets stretched - // by the sides to fit all monitor space... - - // Try to setup the most appropriate fullscreen framebuffer for the requested screenWidth/screenHeight - // It considers device display resolution mode and setups a framebuffer with black bars if required (render size/offset) - // Modified global variables: CORE.Window.screen.width/CORE.Window.screen.height - CORE.Window.render.width/CORE.Window.render.height - CORE.Window.renderOffset.x/CORE.Window.renderOffset.y - CORE.Window.screenScale - // TODO: It is a quite cumbersome solution to display size vs requested size, it should be reviewed or removed... - // HighDPI monitors are properly considered in a following similar function: SetupViewport() - SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); - platform.handle = glfwCreateWindow(CORE.Window.display.width, CORE.Window.display.height, (CORE.Window.title != 0)? CORE.Window.title : " ", glfwGetPrimaryMonitor(), NULL); // NOTE: Full-screen change, not working properly... From c0c8ee9dc8240e2567aed5e1b37f8d9261f400ea Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 28 Dec 2025 18:15:47 +0100 Subject: [PATCH 279/430] Update rcore_desktop_glfw.c --- src/platforms/rcore_desktop_glfw.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 9b360771f..ef5f5ed4f 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -267,12 +267,15 @@ void ToggleBorderlessWindowed(void) int monitorPosX = 0; int monitorPosY = 0; glfwGetMonitorPos(monitors[monitor], &monitorPosX, &monitorPosY); - const int monitorWidth = mode->width; - const int monitorHeight = mode->height; + CORE.Window.position.x = monitorPosX; + CORE.Window.position.x = monitorPosY; + + CORE.Window.screen.width = mode->width; + CORE.Window.screen.height = mode->height; // Set screen position and size - glfwSetWindowMonitor(platform.handle, monitors[monitor], monitorPosX, monitorPosY, - monitorWidth, monitorHeight, mode->refreshRate); + glfwSetWindowMonitor(platform.handle, monitors[monitor], CORE.Window.position.x, CORE.Window.position.y, + CORE.Window.screen.width, CORE.Window.screen.height, mode->refreshRate); // Refocus window glfwFocusWindow(platform.handle); @@ -1817,7 +1820,7 @@ static void WindowSizeCallback(GLFWwindow *window, int width, int height) // WARNING: If FLAG_WINDOW_HIGHDPI is set, WindowContentScaleCallback() is called before this function static void FramebufferSizeCallback(GLFWwindow *window, int width, int height) { - TRACELOG(LOG_INFO, "GLFW3: Window framebuffer size callback called [%i,%i]", width, height); + //TRACELOG(LOG_INFO, "GLFW3: Window framebuffer size callback called [%i,%i]", width, height); // WARNING: On window minimization, callback is called, // but we don't want to change internal screen values, it breaks things @@ -1873,7 +1876,7 @@ static void FramebufferSizeCallback(GLFWwindow *window, int width, int height) // WARNING: If FLAG_WINDOW_HIGHDPI is not set, this function is not called static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float scaley) { - TRACELOG(LOG_INFO, "GLFW3: Window content scale changed, scale: [%.2f,%.2f]", scalex, scaley); + //TRACELOG(LOG_INFO, "GLFW3: Window content scale changed, scale: [%.2f,%.2f]", scalex, scaley); float fbWidth = (float)CORE.Window.screen.width*scalex; float fbHeight = (float)CORE.Window.screen.height*scaley; From 4176c518c74b3571bef7113dbb90a4a656e3012f Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 28 Dec 2025 18:40:44 +0100 Subject: [PATCH 280/430] Update rcore_desktop_glfw.c --- src/platforms/rcore_desktop_glfw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index ef5f5ed4f..f5b3711c5 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -268,7 +268,7 @@ void ToggleBorderlessWindowed(void) int monitorPosY = 0; glfwGetMonitorPos(monitors[monitor], &monitorPosX, &monitorPosY); CORE.Window.position.x = monitorPosX; - CORE.Window.position.x = monitorPosY; + CORE.Window.position.y = monitorPosY; CORE.Window.screen.width = mode->width; CORE.Window.screen.height = mode->height; From 8871d7648d6edb2fb3de70f5feba613df84ecf92 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 28 Dec 2025 19:49:38 +0100 Subject: [PATCH 281/430] Update core_highdpi_testbed.c --- examples/core/core_highdpi_testbed.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/core/core_highdpi_testbed.c b/examples/core/core_highdpi_testbed.c index d34951fc0..8142a1a1b 100644 --- a/examples/core/core_highdpi_testbed.c +++ b/examples/core/core_highdpi_testbed.c @@ -33,6 +33,7 @@ int main(void) Vector2 scaleDpi = GetWindowScaleDPI(); Vector2 mousePos = GetMousePosition(); int currentMonitor = GetCurrentMonitor(); + Vector2 windowPos = GetWindowPosition(); int gridSpacing = 40; // Grid spacing in pixels @@ -47,6 +48,7 @@ int main(void) mousePos = GetMousePosition(); currentMonitor = GetCurrentMonitor(); scaleDpi = GetWindowScaleDPI(); + windowPos = GetWindowPosition(); if (IsKeyPressed(KEY_SPACE)) ToggleBorderlessWindowed(); if (IsKeyPressed(KEY_F)) ToggleFullscreen(); @@ -73,9 +75,10 @@ int main(void) // Draw UI info DrawText(TextFormat("CURRENT MONITOR: %i/%i (%ix%i)", currentMonitor + 1, GetMonitorCount(), GetMonitorWidth(currentMonitor), GetMonitorHeight(currentMonitor)), 50, 50, 20, DARKGRAY); - DrawText(TextFormat("SCREEN SIZE: %ix%i", GetScreenWidth(), GetScreenHeight()), 50, 90, 20, DARKGRAY); - DrawText(TextFormat("RENDER SIZE: %ix%i", GetRenderWidth(), GetRenderHeight()), 50, 130, 20, DARKGRAY); - DrawText(TextFormat("SCALE FACTOR: %.1fx%.1f", scaleDpi.x, scaleDpi.y), 50, 170, 20, GRAY); + DrawText(TextFormat("WINDOW POSITION: %ix%i", windowPos.x, windowPos.y), 50, 90, 20, DARKGRAY); + DrawText(TextFormat("SCREEN SIZE: %ix%i", GetScreenWidth(), GetScreenHeight()), 50, 130, 20, DARKGRAY); + DrawText(TextFormat("RENDER SIZE: %ix%i", GetRenderWidth(), GetRenderHeight()), 50, 170, 20, DARKGRAY); + DrawText(TextFormat("SCALE FACTOR: %.1fx%.1f", scaleDpi.x, scaleDpi.y), 50, 210, 20, GRAY); // Draw reference rectangles, top-left and bottom-right corners DrawRectangle(0, 0, 30, 60, RED); From 8a75439c255088a0cc4c12becb42de57900ade08 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 28 Dec 2025 19:51:04 +0100 Subject: [PATCH 282/430] REVIEWED: Fullscreen modes on Linux (X11 over XWayland) It does not work as expected... :( --- src/platforms/rcore_desktop_glfw.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index f5b3711c5..88120be78 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -196,11 +196,16 @@ void ToggleFullscreen(void) CORE.Window.display.height = mode->height; CORE.Window.position = (Point){ 0, 0 }; - CORE.Window.screen = (Size){ CORE.Window.display.width, CORE.Window.display.height }; + CORE.Window.screen = CORE.Window.display; // Set fullscreen flag to be processed on FramebufferSizeCallback() accordingly FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); + // NOTE: X11 requires undecorating the window before switching to + // fullscreen to avoid issues with framebuffer scaling + glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_FALSE); + FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); + // WARNING: This function launches FramebufferSizeCallback() glfwSetWindowMonitor(platform.handle, monitor, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); } @@ -226,8 +231,14 @@ void ToggleFullscreen(void) } #endif + // WARNING: This function launches FramebufferSizeCallback() glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + + // NOTE: X11 requires restoring the decorated window after switching from + // fullscreen to avoid issues with framebuffer scaling + glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_TRUE); + FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); } // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) From 6450a48c750862aa40a24b34187b98dff87aabd2 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 28 Dec 2025 20:03:51 +0100 Subject: [PATCH 283/430] Update core_highdpi_testbed.c --- examples/core/core_highdpi_testbed.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/core/core_highdpi_testbed.c b/examples/core/core_highdpi_testbed.c index 8142a1a1b..a925527db 100644 --- a/examples/core/core_highdpi_testbed.c +++ b/examples/core/core_highdpi_testbed.c @@ -75,7 +75,7 @@ int main(void) // Draw UI info DrawText(TextFormat("CURRENT MONITOR: %i/%i (%ix%i)", currentMonitor + 1, GetMonitorCount(), GetMonitorWidth(currentMonitor), GetMonitorHeight(currentMonitor)), 50, 50, 20, DARKGRAY); - DrawText(TextFormat("WINDOW POSITION: %ix%i", windowPos.x, windowPos.y), 50, 90, 20, DARKGRAY); + DrawText(TextFormat("WINDOW POSITION: %ix%i", (int)windowPos.x, (int)windowPos.y), 50, 90, 20, DARKGRAY); DrawText(TextFormat("SCREEN SIZE: %ix%i", GetScreenWidth(), GetScreenHeight()), 50, 130, 20, DARKGRAY); DrawText(TextFormat("RENDER SIZE: %ix%i", GetRenderWidth(), GetRenderHeight()), 50, 170, 20, DARKGRAY); DrawText(TextFormat("SCALE FACTOR: %.1fx%.1f", scaleDpi.x, scaleDpi.y), 50, 210, 20, GRAY); From 890ca8d6870585f9e3499eb0e7a327af2cabaa1b Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 28 Dec 2025 20:04:44 +0100 Subject: [PATCH 284/430] REVIEWED: `GetWindowPosition()`, return internal value --- src/platforms/rcore_desktop_glfw.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 88120be78..1593f0306 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1021,12 +1021,7 @@ const char *GetMonitorName(int monitor) // Get window position XY on monitor Vector2 GetWindowPosition(void) { - int x = 0; - int y = 0; - - glfwGetWindowPos(platform.handle, &x, &y); - - return (Vector2){ (float)x, (float)y }; + return (Vector2){ (float)CORE.Window.position.x, (float)CORE.Window.position.y }; } // Get window scale DPI factor for current monitor From a334a54eacc90b93ce68e1bcb092ce1d08088a93 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 28 Dec 2025 20:09:15 +0100 Subject: [PATCH 285/430] Update rcore_desktop_glfw.c --- src/platforms/rcore_desktop_glfw.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 1593f0306..1741187e4 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -275,12 +275,7 @@ void ToggleBorderlessWindowed(void) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); // Get monitor position and size - int monitorPosX = 0; - int monitorPosY = 0; - glfwGetMonitorPos(monitors[monitor], &monitorPosX, &monitorPosY); - CORE.Window.position.x = monitorPosX; - CORE.Window.position.y = monitorPosY; - + glfwGetMonitorPos(monitors[monitor], &CORE.Window.position.x, &CORE.Window.position.y); CORE.Window.screen.width = mode->width; CORE.Window.screen.height = mode->height; From eb3cc183ccce0670054c3accad61e759515bbe58 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 28 Dec 2025 20:29:01 +0100 Subject: [PATCH 286/430] REVIEWED: FIXED: Windows fullscreen, after breaking it due to X11/Wayland changes --- src/platforms/rcore_desktop_glfw.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 1741187e4..42e8f3867 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -201,11 +201,12 @@ void ToggleFullscreen(void) // Set fullscreen flag to be processed on FramebufferSizeCallback() accordingly FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE); +#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND) // NOTE: X11 requires undecorating the window before switching to // fullscreen to avoid issues with framebuffer scaling glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_FALSE); FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); - +#endif // WARNING: This function launches FramebufferSizeCallback() glfwSetWindowMonitor(platform.handle, monitor, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); } @@ -235,10 +236,12 @@ void ToggleFullscreen(void) glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); +#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND) // NOTE: X11 requires restoring the decorated window after switching from // fullscreen to avoid issues with framebuffer scaling glfwSetWindowAttrib(platform.handle, GLFW_DECORATED, GLFW_TRUE); FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNDECORATED); +#endif } // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) From 8f8346048ce12596094dbd4f63c004fb1d5587e5 Mon Sep 17 00:00:00 2001 From: Meowster <142757105+meowstr@users.noreply.github.com> Date: Sun, 28 Dec 2025 17:50:10 -0500 Subject: [PATCH 287/430] Add chicken scheme to BINDINGS.md (#5449) * Add chicken scheme to BINDINGS.md * Fix typo --- BINDINGS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BINDINGS.md b/BINDINGS.md index ee7da46f4..37274b3be 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -21,6 +21,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [claw-raylib](https://github.com/bohonghuang/claw-raylib) | **auto** | [Common Lisp](https://common-lisp.net) | Apache-2.0 | | [raylib](https://github.com/fosskers/raylib) | 5.5 | [Common Lisp](https://common-lisp.net) | MPL-2.0 | | [chez-raylib](https://github.com/Yunoinsky/chez-raylib) | **auto** | [Chez Scheme](https://cisco.github.io/ChezScheme) | GPLv3 | +| [chicken-raylib](https://github.com/meowstr/chicken-raylib) | 5.5 | [CHICKEN Scheme](https://wiki.call-cc.org) | MIT | | [CLIPSraylib](https://github.com/mrryanjohnston/CLIPSraylib) | **auto** | [CLIPS](https://www.clipsrules.net/) | MIT | | [raylib-cr](https://github.com/sol-vin/raylib-cr) | 4.6-dev (5e1a81) | [Crystal](https://crystal-lang.org) | Apache-2.0 | | [ray-cyber](https://github.com/fubark/ray-cyber) | **5.0** | [Cyber](https://cyberscript.dev) | MIT | From 58d414bcf878bfa11c6afc871e9d6444d0f0e307 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 29 Dec 2025 12:39:40 +0100 Subject: [PATCH 288/430] REVIEWED: `InitPlatform()`, code simplification --- src/platforms/rcore_desktop_glfw.c | 57 ++++++++++++++---------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 42e8f3867..a1c0024aa 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1565,13 +1565,18 @@ int InitPlatform(void) CORE.Window.previousScreen.height = 450; CORE.Window.previousPosition.x = CORE.Window.display.width/2 - 800/2; CORE.Window.previousPosition.y = CORE.Window.display.height/2 - 450/2; + + // Set screen width/height to the display width/height + if (CORE.Window.screen.width == 0) CORE.Window.screen.width = CORE.Window.display.width; + if (CORE.Window.screen.height == 0) CORE.Window.screen.height = CORE.Window.display.height; + } + else + { + CORE.Window.previousScreen = CORE.Window.screen; + CORE.Window.screen = CORE.Window.display; } - // Set screen width/height to the display width/height - if (CORE.Window.screen.width == 0) CORE.Window.screen.width = CORE.Window.display.width; - if (CORE.Window.screen.height == 0) CORE.Window.screen.height = CORE.Window.display.height; - - platform.handle = glfwCreateWindow(CORE.Window.display.width, CORE.Window.display.height, (CORE.Window.title != 0)? CORE.Window.title : " ", monitor, NULL); + platform.handle = glfwCreateWindow(CORE.Window.screen.width, CORE.Window.screen.height, (CORE.Window.title != 0)? CORE.Window.title : " ", monitor, NULL); if (!platform.handle) { glfwTerminate(); @@ -1630,13 +1635,13 @@ int InitPlatform(void) glfwMakeContextCurrent(platform.handle); result = glfwGetError(NULL); + if ((result != GLFW_NO_WINDOW_CONTEXT) && (result != GLFW_PLATFORM_ERROR)) CORE.Window.ready = true; // Checking context activation - // Check context activation - if ((result != GLFW_NO_WINDOW_CONTEXT) && (result != GLFW_PLATFORM_ERROR)) + if (CORE.Window.ready) { - CORE.Window.ready = true; + // Setup additional windows configs and register required window size info - glfwSwapInterval(0); // No V-Sync by default + glfwSwapInterval(0); // No V-Sync by default // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) // NOTE: V-Sync can be enabled by graphic driver configuration, it doesn't need @@ -1677,25 +1682,13 @@ int InitPlatform(void) TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); - } - else - { - TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphics device"); - return -1; - } - if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) MinimizeWindow(); - - // If graphic device is no properly initialized, we end program - if (!CORE.Window.ready) { TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphic device"); return -1; } - else - { + // Try to center window on screen but avoiding window-bar outside of screen int monitorCount = 0; int monitorIndex = GetCurrentMonitor(); GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); GLFWmonitor *monitor = monitors[monitorIndex]; - // Try to center window on screen but avoiding window-bar outside of screen int monitorX = 0; int monitorY = 0; int monitorWidth = 0; @@ -1704,15 +1697,19 @@ int InitPlatform(void) // TODO: Here CORE.Window.render.width/height should be used instead of // CORE.Window.screen.width/height to center the window correctly when the high dpi flag is enabled - int posX = monitorX + (monitorWidth - (int)CORE.Window.render.width)/2; - int posY = monitorY + (monitorHeight - (int)CORE.Window.render.height)/2; - if (posX < monitorX) posX = monitorX; - if (posY < monitorY) posY = monitorY; - SetWindowPosition(posX, posY); + CORE.Window.position.x = monitorX + (monitorWidth - (int)CORE.Window.screen.width)/2; + CORE.Window.position.y = monitorY + (monitorHeight - (int)CORE.Window.screen.height)/2; + //if (CORE.Window.position.x < monitorX) CORE.Window.position.x = monitorX; + //if (CORE.Window.position.y < monitorY) CORE.Window.position.y = monitorY; - // Update CORE.Window.position here so it is correct from the start - CORE.Window.position.x = posX; - CORE.Window.position.y = posY; + SetWindowPosition(CORE.Window.position.x, CORE.Window.position.y); + + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) MinimizeWindow(); + } + else + { + TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphics device"); + return -1; } // Apply window flags requested previous to initialization From 00f42e419913c0165d27b74eaa6369717c89540c Mon Sep 17 00:00:00 2001 From: Padmadev D <128023777+padmadevd@users.noreply.github.com> Date: Mon, 29 Dec 2025 17:20:12 +0530 Subject: [PATCH 289/430] [rcore] [android] fixed gesture system not reporting GESTURE_NONE (#5452) in android gesture system is not reporting GESTURE_NONE, specified in the issue https://github.com/raysan5/raylib/issues/5010 so, automatically GESTURE_SWIPE, TAP, DOUBLE_TAP, also will not be reported. in this commit it is fixed. --- src/platforms/rcore_android.c | 106 +++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 46 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index f150bf638..cc012c4fe 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -1336,30 +1336,17 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) } } - if ((flags == AMOTION_EVENT_ACTION_POINTER_UP) || (flags == AMOTION_EVENT_ACTION_UP) || (flags == AMOTION_EVENT_ACTION_HOVER_EXIT)) - { - // One of the touchpoints is released, remove it from touch point arrays - if (flags == AMOTION_EVENT_ACTION_HOVER_EXIT) - { - // If the touchPoint is hover, remove it from hoverPoints - for (int i = 0; i < MAX_TOUCH_POINTS; i++) - { - if (touchRaw.hoverPoints[i] == touchRaw.pointId[pointerIndex]) - { - touchRaw.hoverPoints[i] = -1; - break; - } - } - } - for (int i = pointerIndex; (i < touchRaw.pointCount - 1) && (i < MAX_TOUCH_POINTS - 1); i++) - { - touchRaw.pointId[i] = touchRaw.pointId[i+1]; - touchRaw.position[i] = touchRaw.position[i+1]; - } - touchRaw.pointCount--; - } +#if defined(SUPPORT_GESTURES_SYSTEM) + GestureEvent gestureEvent = { 0 }; + + gestureEvent.pointCount = 0; + + // Register touch actions + if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.touchAction = TOUCH_ACTION_DOWN; + else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.touchAction = TOUCH_ACTION_UP; + else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.touchAction = TOUCH_ACTION_MOVE; + else if (flags == AMOTION_EVENT_ACTION_CANCEL) gestureEvent.touchAction = TOUCH_ACTION_CANCEL; - int pointCount = 0; for (int i = 0; (i < touchRaw.pointCount) && (i < MAX_TOUCH_POINTS); i++) { // If the touchPoint is hover, Ignore it @@ -1375,35 +1362,62 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) } if (hover) continue; - CORE.Input.Touch.pointId[pointCount] = touchRaw.pointId[i]; - CORE.Input.Touch.position[pointCount] = touchRaw.position[i]; - pointCount++; - } - CORE.Input.Touch.pointCount = pointCount; - -#if defined(SUPPORT_GESTURES_SYSTEM) - GestureEvent gestureEvent = { 0 }; - - gestureEvent.pointCount = CORE.Input.Touch.pointCount; - - // Register touch actions - if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.touchAction = TOUCH_ACTION_DOWN; - else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.touchAction = TOUCH_ACTION_UP; - else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.touchAction = TOUCH_ACTION_MOVE; - else if (flags == AMOTION_EVENT_ACTION_CANCEL) gestureEvent.touchAction = TOUCH_ACTION_CANCEL; - - for (int i = 0; (i < gestureEvent.pointCount) && (i < MAX_TOUCH_POINTS); i++) - { - gestureEvent.pointId[i] = CORE.Input.Touch.pointId[i]; - gestureEvent.position[i] = CORE.Input.Touch.position[i]; - gestureEvent.position[i].x /= (float)GetScreenWidth(); - gestureEvent.position[i].y /= (float)GetScreenHeight(); + gestureEvent.pointId[gestureEvent.pointCount] = touchRaw.pointId[i]; + gestureEvent.position[gestureEvent.pointCount] = touchRaw.position[i]; + gestureEvent.position[gestureEvent.pointCount].x /= (float)GetScreenWidth(); + gestureEvent.position[gestureEvent.pointCount].y /= (float)GetScreenHeight(); + gestureEvent.pointCount++; } // Gesture data is sent to gestures system for processing ProcessGestureEvent(gestureEvent); #endif + if (flags == AMOTION_EVENT_ACTION_HOVER_EXIT) + { + // Hover exited. So, remove it from hoverPoints + for (int i = 0; i < MAX_TOUCH_POINTS; i++) + { + if (touchRaw.hoverPoints[i] == touchRaw.pointId[pointerIndex]) + { + touchRaw.hoverPoints[i] = -1; + break; + } + } + } + + if ((flags == AMOTION_EVENT_ACTION_POINTER_UP) || (flags == AMOTION_EVENT_ACTION_UP)) + { + // One of the touchpoints is released, remove it from touch point arrays + for (int i = pointerIndex; (i < touchRaw.pointCount - 1) && (i < MAX_TOUCH_POINTS - 1); i++) + { + touchRaw.pointId[i] = touchRaw.pointId[i+1]; + touchRaw.position[i] = touchRaw.position[i+1]; + } + touchRaw.pointCount--; + } + + CORE.Input.Touch.pointCount = 0; + for (int i = 0; (i < touchRaw.pointCount) && (i < MAX_TOUCH_POINTS); i++) + { + // If the touchPoint is hover, Ignore it + bool hover = false; + for (int j = 0; j < MAX_TOUCH_POINTS; j++) + { + // Check if the touchPoint is in hoverPointers + if (touchRaw.hoverPoints[j] == touchRaw.pointId[i]) + { + hover = true; + break; + } + } + if (hover) continue; + + CORE.Input.Touch.pointId[CORE.Input.Touch.pointCount] = touchRaw.pointId[i]; + CORE.Input.Touch.position[CORE.Input.Touch.pointCount] = touchRaw.position[i]; + CORE.Input.Touch.pointCount++; + } + // When all touchpoints are tapped and released really quickly, this event is generated if (flags == AMOTION_EVENT_ACTION_CANCEL) CORE.Input.Touch.pointCount = 0; From 1c6f6831613163d3724e15b322f45a458a011354 Mon Sep 17 00:00:00 2001 From: MULTi <78434796+MULTidll@users.noreply.github.com> Date: Mon, 29 Dec 2025 17:24:30 +0530 Subject: [PATCH 290/430] [rcore][drm] Improved touch input handling and multitouch support, closes #4842 (#5447) * Improved touch input handling and multitouch support in drm platform * revert * made some fixes for the touch issue in drm platform * updated touch input handling by adding multitouch support * improved how it handles the multitouch * added cleanup * Remove touch last update tracking to simplify touch input handling * improved multitouch support by tracking touch positions and IDs for each slot * Better touch input handling * Increase maximum touch points from 8 to 10 and enhance touchscreen prioritization logic * Refactor touch input handling to use slot index as ID for stability and simplify touch clearing logic * Improve touch input handling by activating slot 0 based on mouse click or touch events * touch event handling to use tracking ID for unique touch identification * Add multitouch detection to PollMouseEvents for improved touch handling * Fix conditional formatting in PollMouseEvents for clarity * Refactor conditional statements in PollMouseEvents and InitPlatform for improved readability * Fix formatting in PollMouseEvents for improved readability --- src/config.h | 2 +- src/platforms/rcore_drm.c | 253 +++++++++++++++++++++++++++++++------- 2 files changed, 212 insertions(+), 43 deletions(-) diff --git a/src/config.h b/src/config.h index b749f8952..9f54edd23 100644 --- a/src/config.h +++ b/src/config.h @@ -105,7 +105,7 @@ #define MAX_GAMEPAD_AXES 8 // Maximum number of axes supported (per gamepad) #define MAX_GAMEPAD_BUTTONS 32 // Maximum number of buttons supported (per gamepad) #define MAX_GAMEPAD_VIBRATION_TIME 2.0f // Maximum vibration time in seconds -#define MAX_TOUCH_POINTS 8 // Maximum number of touch points supported +#define MAX_TOUCH_POINTS 10 // Maximum number of touch points supported #define MAX_KEY_PRESSED_QUEUE 16 // Maximum number of keys in the key input queue #define MAX_CHAR_PRESSED_QUEUE 16 // Maximum number of characters in the char input queue diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 366477aac..b97eac5f5 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -135,8 +135,12 @@ typedef struct { char currentButtonStateEvdev[MAX_MOUSE_BUTTONS]; // Holds the new mouse state for the next polling event to grab bool cursorRelative; // Relative cursor mode int mouseFd; // File descriptor for the evdev mouse/touch/gestures + bool mouseIsTouch; // Check if the current mouse device is actually a touchscreen Rectangle absRange; // Range of values for absolute pointing devices (touchscreens) int touchSlot; // Hold the touch slot number of the currently being sent multitouch block + bool touchActive[MAX_TOUCH_POINTS]; // Track which touch points are currently active + Vector2 touchPosition[MAX_TOUCH_POINTS]; // Track touch positions for each slot + int touchId[MAX_TOUCH_POINTS]; // Track touch IDs for each slot // Gamepad data int gamepadStreamFd[MAX_GAMEPADS]; // Gamepad device file descriptor @@ -1115,9 +1119,6 @@ void PollInputEvents(void) // Register previous touch states for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; - // Reset touch positions to invalid state - for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ -1, -1 }; - // Map touch position to mouse position for convenience // NOTE: For DRM touchscreen devices, this mapping is disabled to avoid false touch detection // CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; @@ -1565,7 +1566,11 @@ int InitPlatform(void) if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) MinimizeWindow(); // If graphic device is no properly initialized, we end program - if (!CORE.Window.ready) { TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphic device"); return -1; } + if (!CORE.Window.ready) + { + TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphic device"); + return -1; + } else SetWindowPosition(GetMonitorWidth(GetCurrentMonitor())/2 - CORE.Window.screen.width/2, GetMonitorHeight(GetCurrentMonitor())/2 - CORE.Window.screen.height/2); // Set some default window flags @@ -1883,7 +1888,14 @@ static void InitEvdevInput(void) { CORE.Input.Touch.position[i].x = -1; CORE.Input.Touch.position[i].y = -1; + platform.touchActive[i] = false; + platform.touchPosition[i].x = -1; + platform.touchPosition[i].y = -1; + platform.touchId[i] = -1; } + + // Initialize touch slot + platform.touchSlot = 0; // Reset keyboard key state for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) @@ -2047,17 +2059,49 @@ static void ConfigureEvdevDevice(char *device) const char *deviceKindStr = "unknown"; if (isMouse || isTouch) { - deviceKindStr = "mouse"; - if (platform.mouseFd != -1) close(platform.mouseFd); - platform.mouseFd = fd; + bool prioritize = false; - if (absAxisCount > 0) + // Priority logic: Touchscreens override Mice. + // 1. No device set yet? Take it. + if (platform.mouseFd == -1) prioritize = true; + // 2. Current is Mouse, New is Touch? Upgrade to Touch. + else if (isTouch && !platform.mouseIsTouch) prioritize = true; + // 3. Current is Touch, New is Touch? Use the new one (Last one found wins, standard behavior). + else if (isTouch && platform.mouseIsTouch) prioritize = true; + // 4. Current is Mouse, New is Mouse? Use the new one. + else if (!isTouch && !platform.mouseIsTouch) prioritize = true; + // 5. Current is Touch, New is Mouse? IGNORE the mouse. Keep the touchscreen. + else prioritize = false; + + if (prioritize) { - platform.absRange.x = absinfo[ABS_X].info.minimum; - platform.absRange.width = absinfo[ABS_X].info.maximum - absinfo[ABS_X].info.minimum; + deviceKindStr = isTouch ? "touchscreen" : "mouse"; + + if (platform.mouseFd != -1) + { + TRACELOG(LOG_INFO, "INPUT: Overwriting previous input device with new %s", deviceKindStr); + close(platform.mouseFd); + } + + platform.mouseFd = fd; + platform.mouseIsTouch = isTouch; - platform.absRange.y = absinfo[ABS_Y].info.minimum; - platform.absRange.height = absinfo[ABS_Y].info.maximum - absinfo[ABS_Y].info.minimum; + if (absAxisCount > 0) + { + platform.absRange.x = absinfo[ABS_X].info.minimum; + platform.absRange.width = absinfo[ABS_X].info.maximum - absinfo[ABS_X].info.minimum; + + platform.absRange.y = absinfo[ABS_Y].info.minimum; + platform.absRange.height = absinfo[ABS_Y].info.maximum - absinfo[ABS_Y].info.minimum; + } + + TRACELOG(LOG_INFO, "INPUT: Initialized input device %s as %s", device, deviceKindStr); + } + else + { + TRACELOG(LOG_INFO, "INPUT: Ignoring device %s (keeping higher priority %s device)", device, platform.mouseIsTouch ? "touchscreen" : "mouse"); + close(fd); + return; } } else if (isGamepad && !isMouse && !isKeyboard && (platform.gamepadCount < MAX_GAMEPADS)) @@ -2231,6 +2275,7 @@ static void PollMouseEvents(void) struct input_event event = { 0 }; int touchAction = -1; // 0-TOUCH_ACTION_UP, 1-TOUCH_ACTION_DOWN, 2-TOUCH_ACTION_MOVE + static bool isMultitouch = false; // Detect if device supports MT events // Try to read data from the mouse/touch/gesture and only continue if successful while (read(fd, &event, sizeof(event)) == (int)sizeof(event)) @@ -2276,54 +2321,118 @@ static void PollMouseEvents(void) if (event.code == ABS_X) { CORE.Input.Mouse.currentPosition.x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; // Scale according to absRange - CORE.Input.Touch.position[0].x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; // Scale according to absRange - - touchAction = 2; // TOUCH_ACTION_MOVE + + // Update single touch position only if it's active and no MT events are being used + if ((platform.touchActive[0]) && (!isMultitouch)) + { + platform.touchPosition[0].x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; + if (touchAction == -1) touchAction = 2; // TOUCH_ACTION_MOVE + } } if (event.code == ABS_Y) { CORE.Input.Mouse.currentPosition.y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; // Scale according to absRange - CORE.Input.Touch.position[0].y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; // Scale according to absRange - - touchAction = 2; // TOUCH_ACTION_MOVE + + // Update single touch position only if it's active and no MT events are being used + if ((platform.touchActive[0]) && (!isMultitouch)) + { + platform.touchPosition[0].y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; + if (touchAction == -1) touchAction = 2; // TOUCH_ACTION_MOVE + } } // Multitouch movement - if (event.code == ABS_MT_SLOT) platform.touchSlot = event.value; // Remember the slot number for the folowing events - - if (event.code == ABS_MT_POSITION_X) + if ((event.code) == (ABS_MT_SLOT)) { - if (platform.touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[platform.touchSlot].x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; // Scale according to absRange + platform.touchSlot = event.value; + isMultitouch = true; } - if (event.code == ABS_MT_POSITION_Y) + if ((event.code) == (ABS_MT_POSITION_X)) { - if (platform.touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[platform.touchSlot].y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; // Scale according to absRange - } - - if (event.code == ABS_MT_TRACKING_ID) - { - if ((event.value < 0) && (platform.touchSlot < MAX_TOUCH_POINTS)) + isMultitouch = true; + if ((platform.touchSlot) < (MAX_TOUCH_POINTS)) { - // Touch has ended for this point - CORE.Input.Touch.position[platform.touchSlot].x = -1; - CORE.Input.Touch.position[platform.touchSlot].y = -1; + platform.touchPosition[platform.touchSlot].x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; + + // If this slot is active, it's a move. If not, we are just updating the buffer for when it becomes active. + // Only set to MOVE if we haven't already detected a DOWN or UP event this frame + if (platform.touchActive[platform.touchSlot] && touchAction == -1) touchAction = 2; // TOUCH_ACTION_MOVE + } + } + + if ((event.code) == (ABS_MT_POSITION_Y)) + { + if ((platform.touchSlot) < (MAX_TOUCH_POINTS)) + { + platform.touchPosition[platform.touchSlot].y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; + + // If this slot is active, it's a move. If not, we are just updating the buffer for when it becomes active. + // Only set to MOVE if we haven't already detected a DOWN or UP event this frame + if (platform.touchActive[platform.touchSlot] && touchAction == -1) touchAction = 2; // TOUCH_ACTION_MOVE + } + } + + if ((event.code) == (ABS_MT_TRACKING_ID)) + { + if ((platform.touchSlot) < (MAX_TOUCH_POINTS)) + { + if (event.value >= 0) + { + + platform.touchActive[platform.touchSlot] = true; + platform.touchId[platform.touchSlot] = event.value; // Use Tracking ID for unique IDs + + touchAction = 1; // TOUCH_ACTION_DOWN + } + else + { + // Touch has ended for this point + platform.touchActive[platform.touchSlot] = false; + platform.touchPosition[platform.touchSlot].x = -1; + platform.touchPosition[platform.touchSlot].y = -1; + platform.touchId[platform.touchSlot] = -1; + + // Force UP action if we haven't already set a DOWN action + // (DOWN takes priority over UP if both happen in one frame, though rare) + if (touchAction != 1) touchAction = 0; // TOUCH_ACTION_UP + } + } + } + + // Handle ABS_MT_PRESSURE (0x3a) if available, as some devices use it for lift-off + #ifndef ABS_MT_PRESSURE + #define ABS_MT_PRESSURE 0x3a + #endif + if ((event.code) == (ABS_MT_PRESSURE)) + { + if ((platform.touchSlot) < (MAX_TOUCH_POINTS)) + { + if (event.value <= 0) // Pressure 0 means lift + { + platform.touchActive[platform.touchSlot] = false; + platform.touchPosition[platform.touchSlot].x = -1; + platform.touchPosition[platform.touchSlot].y = -1; + platform.touchId[platform.touchSlot] = -1; + if (touchAction != 1) touchAction = 0; // TOUCH_ACTION_UP + } } } // Touchscreen tap - if (event.code == ABS_PRESSURE) + if ((event.code) == (ABS_PRESSURE)) { int previousMouseLeftButtonState = platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT]; - if (!event.value && previousMouseLeftButtonState) + if ((!event.value) && (previousMouseLeftButtonState)) { platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 0; - touchAction = 0; // TOUCH_ACTION_UP + + if (touchAction != 1) touchAction = 0; // TOUCH_ACTION_UP } - if (event.value && !previousMouseLeftButtonState) + if ((event.value) && (!previousMouseLeftButtonState)) { platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 1; touchAction = 1; // TOUCH_ACTION_DOWN @@ -2340,8 +2449,46 @@ static void PollMouseEvents(void) { platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = event.value; - if (event.value > 0) touchAction = 1; // TOUCH_ACTION_DOWN - else touchAction = 0; // TOUCH_ACTION_UP + if (event.value > 0) + { + bool activateSlot0 = false; + + if (event.code == BTN_LEFT) + { + activateSlot0 = true; // Mouse click always activates + } + else if (event.code == BTN_TOUCH) + { + bool anyActive = false; + for (int i = 0; i < MAX_TOUCH_POINTS; i++) { + if (platform.touchActive[i]) { anyActive = true; break; } + } + if (!anyActive) activateSlot0 = true; + } + + if (activateSlot0) + { + platform.touchActive[0] = true; + platform.touchId[0] = 0; + } + + touchAction = 1; // TOUCH_ACTION_DOWN + } + else + { + // Only clear touch 0 for actual mouse clicks (BTN_LEFT) + if (event.code == BTN_LEFT) + { + platform.touchActive[0] = false; + platform.touchPosition[0].x = -1; + platform.touchPosition[0].y = -1; + } + else if (event.code == BTN_TOUCH) + { + platform.touchSlot = 0; // Reset slot index to 0 + } + touchAction = 0; // TOUCH_ACTION_UP + } } if (event.code == BTN_RIGHT) platform.currentButtonStateEvdev[MOUSE_BUTTON_RIGHT] = event.value; @@ -2362,11 +2509,33 @@ static void PollMouseEvents(void) if (CORE.Input.Mouse.currentPosition.y > CORE.Window.screen.height/CORE.Input.Mouse.scale.y) CORE.Input.Mouse.currentPosition.y = CORE.Window.screen.height/CORE.Input.Mouse.scale.y; } - // Update touch point count - CORE.Input.Touch.pointCount = 0; + // Repack active touches into CORE.Input.Touch + int k = 0; for (int i = 0; i < MAX_TOUCH_POINTS; i++) { - if (CORE.Input.Touch.position[i].x >= 0) CORE.Input.Touch.pointCount++; + if (platform.touchActive[i]) + { + CORE.Input.Touch.position[k] = platform.touchPosition[i]; + CORE.Input.Touch.pointId[k] = platform.touchId[i]; + k++; + } + } + CORE.Input.Touch.pointCount = k; + + // Clear remaining slots + for (int i = k; i < MAX_TOUCH_POINTS; i++) + { + CORE.Input.Touch.position[i].x = -1; + CORE.Input.Touch.position[i].y = -1; + CORE.Input.Touch.pointId[i] = -1; + } + + // Debug logging + static int lastTouchCount = 0; + if (CORE.Input.Touch.pointCount != lastTouchCount && (touchAction == 0 || touchAction == 1)) + { + TRACELOG(LOG_DEBUG, "TOUCH: Count changed from %d to %d (action: %d)", lastTouchCount, CORE.Input.Touch.pointCount, touchAction); + lastTouchCount = CORE.Input.Touch.pointCount; } #if defined(SUPPORT_GESTURES_SYSTEM) @@ -2558,4 +2727,4 @@ static void SetupFramebuffer(int width, int height) } } -// EOF +// EOF \ No newline at end of file From 2b48cf67936eace2a4aa58f7e22c34170883f0a8 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 29 Dec 2025 13:06:05 +0100 Subject: [PATCH 291/430] Formating review --- src/platforms/rcore_drm.c | 104 ++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 60 deletions(-) diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index b97eac5f5..103d81975 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -2061,21 +2061,21 @@ static void ConfigureEvdevDevice(char *device) { bool prioritize = false; - // Priority logic: Touchscreens override Mice. - // 1. No device set yet? Take it. + // Priority logic: touchscreens override Mice + // 1. No device set yet? Take it if (platform.mouseFd == -1) prioritize = true; - // 2. Current is Mouse, New is Touch? Upgrade to Touch. + // 2. Current is mouse, new is touch? Upgrade to touch else if (isTouch && !platform.mouseIsTouch) prioritize = true; - // 3. Current is Touch, New is Touch? Use the new one (Last one found wins, standard behavior). + // 3. Current is touch, new is touch? Use the new one (last one found wins, standard behavior) else if (isTouch && platform.mouseIsTouch) prioritize = true; - // 4. Current is Mouse, New is Mouse? Use the new one. + // 4. Current is mouse, new is mouse? Use the new one else if (!isTouch && !platform.mouseIsTouch) prioritize = true; - // 5. Current is Touch, New is Mouse? IGNORE the mouse. Keep the touchscreen. + // 5. Current is touch, new is mouse? Ignore the mouse, keep the touchscreen else prioritize = false; if (prioritize) { - deviceKindStr = isTouch ? "touchscreen" : "mouse"; + deviceKindStr = isTouch? "touchscreen" : "mouse"; if (platform.mouseFd != -1) { @@ -2172,18 +2172,15 @@ static void PollKeyboardEvents(void) // If the event was a key, we know a working keyboard is connected, so disable the SSH keyboard platform.eventKeyboardMode = true; #endif - // Keyboard keys appear for codes 1 to 255, ignore everthing else if ((event.code >= 1) && (event.code <= 255)) { - // Lookup the scancode in the keymap to get a keycode keycode = linuxToRaylibMap[event.code]; // Make sure we got a valid keycode if ((keycode > 0) && (keycode < MAX_KEYBOARD_KEYS)) { - // WARNING: https://www.kernel.org/doc/Documentation/input/input.txt // Event interface: 'value' is the value the event carries. Either a relative change for EV_REL, // absolute new value for EV_ABS (joysticks ...), or 0 for EV_KEY for release, 1 for keypress and 2 for autorepeat @@ -2232,16 +2229,15 @@ static void PollGamepadEvents(void) { if (event.code < KEYMAP_SIZE) { - short keycodeRaylib = linuxToRaylibMap[event.code]; + short keycode = linuxToRaylibMap[event.code]; // raylib keycode - TRACELOG(LOG_DEBUG, "INPUT: Gamepad %2i: KEY_%s Keycode(linux): %4i Keycode(raylib): %4i", i, (event.value == 0)? "UP" : "DOWN", event.code, keycodeRaylib); + TRACELOG(LOG_DEBUG, "INPUT: Gamepad %2i: KEY_%s Keycode(linux): %4i Keycode(raylib): %4i", i, (event.value == 0)? "UP" : "DOWN", event.code, keycode); - if ((keycodeRaylib != 0) && (keycodeRaylib < MAX_GAMEPAD_BUTTONS)) + if ((keycode != 0) && (keycode < MAX_GAMEPAD_BUTTONS)) { // 1 - button pressed, 0 - button released - CORE.Input.Gamepad.currentButtonState[i][keycodeRaylib] = event.value; - - CORE.Input.Gamepad.lastButtonPressed = (event.value == 1)? keycodeRaylib : GAMEPAD_BUTTON_UNKNOWN; + CORE.Input.Gamepad.currentButtonState[i][keycode] = event.value; + CORE.Input.Gamepad.lastButtonPressed = (event.value == 1)? keycode : GAMEPAD_BUTTON_UNKNOWN; } } } @@ -2323,7 +2319,7 @@ static void PollMouseEvents(void) CORE.Input.Mouse.currentPosition.x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; // Scale according to absRange // Update single touch position only if it's active and no MT events are being used - if ((platform.touchActive[0]) && (!isMultitouch)) + if (platform.touchActive[0] && !isMultitouch) { platform.touchPosition[0].x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; if (touchAction == -1) touchAction = 2; // TOUCH_ACTION_MOVE @@ -2335,7 +2331,7 @@ static void PollMouseEvents(void) CORE.Input.Mouse.currentPosition.y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; // Scale according to absRange // Update single touch position only if it's active and no MT events are being used - if ((platform.touchActive[0]) && (!isMultitouch)) + if (platform.touchActive[0] && !isMultitouch) { platform.touchPosition[0].y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; if (touchAction == -1) touchAction = 2; // TOUCH_ACTION_MOVE @@ -2343,16 +2339,16 @@ static void PollMouseEvents(void) } // Multitouch movement - if ((event.code) == (ABS_MT_SLOT)) + if (event.code == ABS_MT_SLOT) { platform.touchSlot = event.value; isMultitouch = true; } - if ((event.code) == (ABS_MT_POSITION_X)) + if (event.code == ABS_MT_POSITION_X) { isMultitouch = true; - if ((platform.touchSlot) < (MAX_TOUCH_POINTS)) + if (platform.touchSlot < MAX_TOUCH_POINTS) { platform.touchPosition[platform.touchSlot].x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; @@ -2362,9 +2358,9 @@ static void PollMouseEvents(void) } } - if ((event.code) == (ABS_MT_POSITION_Y)) + if (event.code == ABS_MT_POSITION_Y) { - if ((platform.touchSlot) < (MAX_TOUCH_POINTS)) + if (platform.touchSlot < MAX_TOUCH_POINTS) { platform.touchPosition[platform.touchSlot].y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; @@ -2374,9 +2370,9 @@ static void PollMouseEvents(void) } } - if ((event.code) == (ABS_MT_TRACKING_ID)) + if (event.code == ABS_MT_TRACKING_ID) { - if ((platform.touchSlot) < (MAX_TOUCH_POINTS)) + if (platform.touchSlot < MAX_TOUCH_POINTS) { if (event.value >= 0) { @@ -2384,7 +2380,7 @@ static void PollMouseEvents(void) platform.touchActive[platform.touchSlot] = true; platform.touchId[platform.touchSlot] = event.value; // Use Tracking ID for unique IDs - touchAction = 1; // TOUCH_ACTION_DOWN + touchAction = 1; // TOUCH_ACTION_DOWN } else { @@ -2396,18 +2392,18 @@ static void PollMouseEvents(void) // Force UP action if we haven't already set a DOWN action // (DOWN takes priority over UP if both happen in one frame, though rare) - if (touchAction != 1) touchAction = 0; // TOUCH_ACTION_UP + if (touchAction != 1) touchAction = 0; // TOUCH_ACTION_UP } } } // Handle ABS_MT_PRESSURE (0x3a) if available, as some devices use it for lift-off #ifndef ABS_MT_PRESSURE - #define ABS_MT_PRESSURE 0x3a + #define ABS_MT_PRESSURE 0x3a #endif - if ((event.code) == (ABS_MT_PRESSURE)) + if (event.code == ABS_MT_PRESSURE) { - if ((platform.touchSlot) < (MAX_TOUCH_POINTS)) + if (platform.touchSlot < MAX_TOUCH_POINTS) { if (event.value <= 0) // Pressure 0 means lift { @@ -2415,30 +2411,28 @@ static void PollMouseEvents(void) platform.touchPosition[platform.touchSlot].x = -1; platform.touchPosition[platform.touchSlot].y = -1; platform.touchId[platform.touchSlot] = -1; - if (touchAction != 1) touchAction = 0; // TOUCH_ACTION_UP + if (touchAction != 1) touchAction = 0; // TOUCH_ACTION_UP } } } // Touchscreen tap - if ((event.code) == (ABS_PRESSURE)) + if (event.code == ABS_PRESSURE) { int previousMouseLeftButtonState = platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT]; - if ((!event.value) && (previousMouseLeftButtonState)) + if (!event.value && previousMouseLeftButtonState) { platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 0; - - if (touchAction != 1) touchAction = 0; // TOUCH_ACTION_UP + if (touchAction != 1) touchAction = 0; // TOUCH_ACTION_UP } - if ((event.value) && (!previousMouseLeftButtonState)) + if (event.value && !previousMouseLeftButtonState) { platform.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 1; - touchAction = 1; // TOUCH_ACTION_DOWN + touchAction = 1; // TOUCH_ACTION_DOWN } } - } // Button parsing @@ -2453,16 +2447,15 @@ static void PollMouseEvents(void) { bool activateSlot0 = false; - if (event.code == BTN_LEFT) - { - activateSlot0 = true; // Mouse click always activates - } + if (event.code == BTN_LEFT) activateSlot0 = true; // Mouse click always activates else if (event.code == BTN_TOUCH) { bool anyActive = false; - for (int i = 0; i < MAX_TOUCH_POINTS; i++) { + for (int i = 0; i < MAX_TOUCH_POINTS; i++) + { if (platform.touchActive[i]) { anyActive = true; break; } } + if (!anyActive) activateSlot0 = true; } @@ -2472,7 +2465,7 @@ static void PollMouseEvents(void) platform.touchId[0] = 0; } - touchAction = 1; // TOUCH_ACTION_DOWN + touchAction = 1; // TOUCH_ACTION_DOWN } else { @@ -2483,10 +2476,8 @@ static void PollMouseEvents(void) platform.touchPosition[0].x = -1; platform.touchPosition[0].y = -1; } - else if (event.code == BTN_TOUCH) - { - platform.touchSlot = 0; // Reset slot index to 0 - } + else if (event.code == BTN_TOUCH) platform.touchSlot = 0; // Reset slot index to 0 + touchAction = 0; // TOUCH_ACTION_UP } } @@ -2503,10 +2494,12 @@ static void PollMouseEvents(void) if (!CORE.Input.Mouse.cursorLocked) { if (CORE.Input.Mouse.currentPosition.x < 0) CORE.Input.Mouse.currentPosition.x = 0; - if (CORE.Input.Mouse.currentPosition.x > CORE.Window.screen.width/CORE.Input.Mouse.scale.x) CORE.Input.Mouse.currentPosition.x = CORE.Window.screen.width/CORE.Input.Mouse.scale.x; + if (CORE.Input.Mouse.currentPosition.x > CORE.Window.screen.width/CORE.Input.Mouse.scale.x) + CORE.Input.Mouse.currentPosition.x = CORE.Window.screen.width/CORE.Input.Mouse.scale.x; if (CORE.Input.Mouse.currentPosition.y < 0) CORE.Input.Mouse.currentPosition.y = 0; - if (CORE.Input.Mouse.currentPosition.y > CORE.Window.screen.height/CORE.Input.Mouse.scale.y) CORE.Input.Mouse.currentPosition.y = CORE.Window.screen.height/CORE.Input.Mouse.scale.y; + if (CORE.Input.Mouse.currentPosition.y > CORE.Window.screen.height/CORE.Input.Mouse.scale.y) + CORE.Input.Mouse.currentPosition.y = CORE.Window.screen.height/CORE.Input.Mouse.scale.y; } // Repack active touches into CORE.Input.Touch @@ -2520,6 +2513,7 @@ static void PollMouseEvents(void) k++; } } + CORE.Input.Touch.pointCount = k; // Clear remaining slots @@ -2529,20 +2523,11 @@ static void PollMouseEvents(void) CORE.Input.Touch.position[i].y = -1; CORE.Input.Touch.pointId[i] = -1; } - - // Debug logging - static int lastTouchCount = 0; - if (CORE.Input.Touch.pointCount != lastTouchCount && (touchAction == 0 || touchAction == 1)) - { - TRACELOG(LOG_DEBUG, "TOUCH: Count changed from %d to %d (action: %d)", lastTouchCount, CORE.Input.Touch.pointCount, touchAction); - lastTouchCount = CORE.Input.Touch.pointCount; - } #if defined(SUPPORT_GESTURES_SYSTEM) if (touchAction > -1) { GestureEvent gestureEvent = { 0 }; - gestureEvent.touchAction = touchAction; gestureEvent.pointCount = CORE.Input.Touch.pointCount; @@ -2553,7 +2538,6 @@ static void PollMouseEvents(void) } ProcessGestureEvent(gestureEvent); - touchAction = -1; } #endif From 752373867741e044244df0e64695463598a4a742 Mon Sep 17 00:00:00 2001 From: Hamza RAHAL <77698738+hmz-rhl@users.noreply.github.com> Date: Tue, 30 Dec 2025 20:11:37 +0100 Subject: [PATCH 292/430] Add hilbert curve example (#5454) --- examples/shapes/shapes_hilbert_curve.c | 187 +++++++++++++++++++++++ examples/shapes/shapes_hilbert_curve.png | Bin 0 -> 15288 bytes 2 files changed, 187 insertions(+) create mode 100644 examples/shapes/shapes_hilbert_curve.c create mode 100644 examples/shapes/shapes_hilbert_curve.png diff --git a/examples/shapes/shapes_hilbert_curve.c b/examples/shapes/shapes_hilbert_curve.c new file mode 100644 index 000000000..1af263b34 --- /dev/null +++ b/examples/shapes/shapes_hilbert_curve.c @@ -0,0 +1,187 @@ +/******************************************************************************************* +* +* raylib [shapes] example - hilbert curve example +* +* Example complexity rating: [★★★☆] 3/4 +* +* Example originally created with raylib 5.6, last time updated with raylib 5.6 +* +* Example contributed by Hamza RAHAL (@hmz-rhl) +* +* 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) 2025 Hamza RAHAL (@hmz-rhl) +* +********************************************************************************************/ + + +#include "raylib.h" +#include "raymath.h" +#include +#include + +const int screenWidth = 800; + +const int screenHeight = 450; + +int order = 2; + +int total; + +int counter = 0; + +Vector2 *hilbertPath = 0; + +const Vector2 hilbertPoints[4] = +{ + [0] = { + .x = 0, + .y = 0 + }, + [1] = { + .x = 0, + .y = 1 + }, + [2] = { + .x = 1, + .y = 1 + }, + [3] = { + .x = 1, + .y = 0 + }, +}; + +//------------------------------------------------------------------------------------ +// Module Functions Declaration +//------------------------------------------------------------------------------------ +Vector2 Hilbert(int index); + +void InitHilbertPath(void); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - hilbert curve example"); + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + + InitHilbertPath(); + + //-------------------------------------------------------------------------------------- + + // Main game loop + //-------------------------------------------------------------------------------------- + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + if ((IsKeyPressed(KEY_UP)) && (order < 8)) + { + counter = 0; + ++order; + InitHilbertPath(); + } + else if((IsKeyPressed(KEY_DOWN)) && (order > 1)) + { + counter = 0; + --order; + InitHilbertPath(); + } + //---------------------------------------------------------------------------------- + + // Draw + //-------------------------------------------------------------------------- + BeginDrawing(); + DrawText(TextFormat("(press UP or DOWN to change)\norder : %d", order), screenWidth/2 + 70, 25, 20, WHITE); + + if(counter < total) + { + ClearBackground(BLACK); + for (int i = 1; i <= counter; i++) + { + DrawLineV(hilbertPath[i], hilbertPath[i-1], ColorFromHSV(((float)i / total) * 360.0f, 1.0f, 1.0f)); + } + counter += 1; + } + EndDrawing(); + //-------------------------------------------------------------------------- + } + //-------------------------------------------------------------------------------------- + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + MemFree(hilbertPath); + //-------------------------------------------------------------------------------------- + return 0; +} + +//------------------------------------------------------------------------------------ +// Module Functions Definition +//------------------------------------------------------------------------------------ + +// calculate U positions +Vector2 Hilbert(int index) +{ + + int hiblertIndex = index&3; + Vector2 vect = hilbertPoints[hiblertIndex]; + float temp; + int len; + + for (int j = 1; j < order; j++) + { + index = index>>2; + hiblertIndex = index&3; + len = 1<L25DS|?4aZm(`)LLPMDx!!?6_5^(B9X^{>D>eiiCHMqA9n2hvy(|6d+v9> z^L^*uJxTQTTu0Y4(nAo0?(XL5gCJUF2!d8pb-+6d0=}z75QkWI*EPNo9|Qyn1VeRf zJ-&;8qpkT8r_ZY37~Q~FY$J;13->&O*&Qd%Da9E~x=6zXmEiid!o`|DB>o|s#kSY7 z%E((s7DnfM3TB*wSKE@sz-~qjJ+w5(2Li8Jq0QgJ`o*+yokn>)$ z#vFh})-PA!oESQ?<-!OxOuQ|%1#gKl zYm@4D%i^~9Lj7p-w3JYnB~G8p7L>)-(&zXzyi*uqHt)*k&DPs~$xS`m`m;<oWjORZHLiB+ky1)41HvpmEb-3Ttwi$t6pJR%;r7L>z(W^JxRROGiUGx~lJN=0{s$POl@ zoQyo)z)kn+%`y*nP4_*F+mA}N=eU@*<=)8;sLpT+J~~$~*6)cOul;0(Tz9?Wxu=H# z^Gcf45;q}9M0o<{SbVP%kA%hTx``BrH2JStTM+3tYvu2q^k<(elid!s2)q=y$?w;u zO-4=w;dcq(OQ!^$C|%XMp}EA}E`3&H#tPG$8(B}kGxs6x>eNjNW|@Gq)NEy;*Miv$u^?JQJlQ;t>On+i}qPtYIU1@G}x5#$Nn87Z_{k{39|YCCCU`pj+xSXRgj{iT@s|Q+<^Tj z-jK=P{D4q7q#P`9Iv2I0O2qU+?nlEkD{4 zW?@Gv0)$yaO{KJi*b_SRFGN6u8>ZmCj#HJIdfP#*HX#xnN49fn%u3wU9x%^xW!EMyi?2*D^TWYx&?KJ)X~)P1*GT zjsu`o-yD&DD>`iIoFPpJqnfkXZ2J>6ZQlAKISorcke4gqOX9(c$qtY=pCaX+dXX&Y z06ut!F?2N+cy;6Ej#$YUO-jScYjK~T$6_SgaZYFdH$AClcaQQ`BANNjPRE5<6raE0 zGl0np7P|}x{}*aHJK?v4_YA(G04R&?ZI_UIODGLeNFh3)v|$5M4fxS~(lZk;igu z=ua3CY@_I^#o9gA(xB?>MXASWQtaJ>YWvcvD_R#2rf(8sBO$i-w{<3PjN$2GRDYi7YR#G7!=H^;T`H#S3 z(^=f>mM4NLvyI~WX>vmoD``!ukj44?9^7(t0uJOLeGRjg_rGQ6E1_Ck(te07wn zkHLKZm`w(A@{Uwx;Az?^EMmgsH6QxH5Qxk}6d(#Oy%%x;ashH-;uIFDBhiCG6d(#O zJ8{Sb$OXs+LK7hhub?0>?C~E@Q$r9;5&O literal 0 HcmV?d00001 From 6dfaf9fe7edfee08eac7bfabcc3dabcc5147530d Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 30 Dec 2025 21:19:54 +0100 Subject: [PATCH 293/430] REVIEWED: example `shapes_hilbert_curve` #5454 Make it more didactic and dynamic, avoid global variables --- examples/shapes/shapes_hilbert_curve.c | 233 ++++++++++++----------- examples/shapes/shapes_hilbert_curve.png | Bin 15288 -> 16975 bytes 2 files changed, 121 insertions(+), 112 deletions(-) diff --git a/examples/shapes/shapes_hilbert_curve.c b/examples/shapes/shapes_hilbert_curve.c index 1af263b34..8ea03c7c5 100644 --- a/examples/shapes/shapes_hilbert_curve.c +++ b/examples/shapes/shapes_hilbert_curve.c @@ -6,7 +6,7 @@ * * Example originally created with raylib 5.6, last time updated with raylib 5.6 * -* Example contributed by Hamza RAHAL (@hmz-rhl) +* Example contributed by Hamza RAHAL (@hmz-rhl) 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 @@ -17,48 +17,18 @@ #include "raylib.h" -#include "raymath.h" -#include -#include -const int screenWidth = 800; +#define RAYGUI_IMPLEMENTATION +#include "raygui.h" -const int screenHeight = 450; - -int order = 2; - -int total; - -int counter = 0; - -Vector2 *hilbertPath = 0; - -const Vector2 hilbertPoints[4] = -{ - [0] = { - .x = 0, - .y = 0 - }, - [1] = { - .x = 0, - .y = 1 - }, - [2] = { - .x = 1, - .y = 1 - }, - [3] = { - .x = 1, - .y = 0 - }, -}; +#include // Required for: calloc(), free() //------------------------------------------------------------------------------------ // Module Functions Declaration //------------------------------------------------------------------------------------ -Vector2 Hilbert(int index); - -void InitHilbertPath(void); +static Vector2 *LoadHilbertPath(int order, float size, int *strokeCount); +static void UnloadHilbertPath(Vector2 *hilbertPath); +static Vector2 ComputeHilbertStep(int order, int index); //------------------------------------------------------------------------------------ // Program main entry point @@ -67,13 +37,23 @@ int main(void) { // Initialization //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; - InitWindow(screenWidth, screenHeight, "raylib [shapes] example - hilbert curve example"); + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - hilbert curve"); - SetTargetFPS(60); // Set our game to run at 60 frames-per-second - - InitHilbertPath(); + int order = 2; + float size = GetScreenHeight(); + int strokeCount = 0; + Vector2 *hilbertPath = LoadHilbertPath(order, size, &strokeCount); + int prevOrder = order; + int prevSize = (int)size; // NOTE: Size from slider is float but for comparison we use int + int counter = 0; + float thick = 2.0f; + bool animate = true; + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- // Main game loop @@ -82,34 +62,52 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - if ((IsKeyPressed(KEY_UP)) && (order < 8)) + // Check if order or size have changed to regenerate + // NOTE: Size from slider is float but for comparison we use int + if ((prevOrder != order) || (prevSize != (int)size)) { - counter = 0; - ++order; - InitHilbertPath(); - } - else if((IsKeyPressed(KEY_DOWN)) && (order > 1)) - { - counter = 0; - --order; - InitHilbertPath(); + UnloadHilbertPath(hilbertPath); + hilbertPath = LoadHilbertPath(order, size, &strokeCount); + + if (animate) counter = 0; + else counter = strokeCount; + + prevOrder = order; + prevSize = size; } //---------------------------------------------------------------------------------- // Draw //-------------------------------------------------------------------------- BeginDrawing(); - DrawText(TextFormat("(press UP or DOWN to change)\norder : %d", order), screenWidth/2 + 70, 25, 20, WHITE); - if(counter < total) - { - ClearBackground(BLACK); - for (int i = 1; i <= counter; i++) + ClearBackground(RAYWHITE); + + if (counter < strokeCount) { - DrawLineV(hilbertPath[i], hilbertPath[i-1], ColorFromHSV(((float)i / total) * 360.0f, 1.0f, 1.0f)); + // Draw Hilbert path animation, one stroke every frame + for (int i = 1; i <= counter; i++) + { + DrawLineEx(hilbertPath[i], hilbertPath[i - 1], thick, ColorFromHSV(((float)i/strokeCount)*360.0f, 1.0f, 1.0f)); + } + + counter += 1; } - counter += 1; - } + else + { + // Draw full Hilbert path + for (int i = 1; i < strokeCount; i++) + { + DrawLineEx(hilbertPath[i], hilbertPath[i - 1], thick, ColorFromHSV(((float)i/strokeCount)*360.0f, 1.0f, 1.0f)); + } + } + + // Draw UI using raygui + GuiCheckBox((Rectangle){ 450, 50, 20, 20 }, "ANIMATE GENERATION ON CHANGE", &animate); + GuiSpinner((Rectangle){ 585, 100, 180, 30 }, "HILBERT CURVE ORDER: ", &order, 2, 8, false); + GuiSlider((Rectangle){ 524, 150, 240, 24 }, "THICKNESS: ", NULL, &thick, 1.0f, 10.0f); + GuiSlider((Rectangle){ 524, 190, 240, 24 }, "TOTAL SIZE: ", NULL, &size, 10.0f, GetScreenHeight()*1.5f); + EndDrawing(); //-------------------------------------------------------------------------- } @@ -117,8 +115,9 @@ int main(void) // De-Initialization //-------------------------------------------------------------------------------------- + UnloadHilbertPath(hilbertPath); + CloseWindow(); // Close window and OpenGL context - MemFree(hilbertPath); //-------------------------------------------------------------------------------------- return 0; } @@ -126,62 +125,72 @@ int main(void) //------------------------------------------------------------------------------------ // Module Functions Definition //------------------------------------------------------------------------------------ - -// calculate U positions -Vector2 Hilbert(int index) +// Load the whole Hilbert Path (including each U and their link) +static Vector2 *LoadHilbertPath(int order, float size, int *strokeCount) { + int N = 1 << order; + float len = size/N; + *strokeCount = N*N; - int hiblertIndex = index&3; - Vector2 vect = hilbertPoints[hiblertIndex]; - float temp; - int len; - - for (int j = 1; j < order; j++) + Vector2 *hilbertPath = (Vector2 *)RL_CALLOC(*strokeCount, sizeof(Vector2)); + + for (int i = 0; i < *strokeCount; i++) { - index = index>>2; - hiblertIndex = index&3; - len = 1<> 2; + hilbertIndex = index&3; + len = 1 << j; + + switch (hilbertIndex) + { + case 0: + { + temp = vect.x; + vect.x = vect.y; + vect.y = temp; + } break; + case 2: vect.x += len; + case 1: vect.y += len; break; + case 3: + { + temp = len - 1 - vect.x; + vect.x = 2*len - 1 - vect.y; + vect.y = temp; + } break; + default: break; + } + } + + return vect; } diff --git a/examples/shapes/shapes_hilbert_curve.png b/examples/shapes/shapes_hilbert_curve.png index cbb3d0753b154bac0cc160d140ab5c27e59dbddb..af99cbc493115d0de96f50ce8391f5f6aae85028 100644 GIT binary patch literal 16975 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 FKLA35BUk_c literal 15288 zcmeHOYfuwc6y6O88B8z^js}!REJC&87$JZKffz7?%0r~;pshg=BZ$=&5kVfYAru<~ zw8a6cg>L25DS|?4aZm(`)LLPMDx!!?6_5^(B9X^{>D>eiiCHMqA9n2hvy(|6d+v9> z^L^*uJxTQTTu0Y4(nAo0?(XL5gCJUF2!d8pb-+6d0=}z75QkWI*EPNo9|Qyn1VeRf zJ-&;8qpkT8r_ZY37~Q~FY$J;13->&O*&Qd%Da9E~x=6zXmEiid!o`|DB>o|s#kSY7 z%E((s7DnfM3TB*wSKE@sz-~qjJ+w5(2Li8Jq0QgJ`o*+yokn>)$ z#vFh})-PA!oESQ?<-!OxOuQ|%1#gKl zYm@4D%i^~9Lj7p-w3JYnB~G8p7L>)-(&zXzyi*uqHt)*k&DPs~$xS`m`m;<oWjORZHLiB+ky1)41HvpmEb-3Ttwi$t6pJR%;r7L>z(W^JxRROGiUGx~lJN=0{s$POl@ zoQyo)z)kn+%`y*nP4_*F+mA}N=eU@*<=)8;sLpT+J~~$~*6)cOul;0(Tz9?Wxu=H# z^Gcf45;q}9M0o<{SbVP%kA%hTx``BrH2JStTM+3tYvu2q^k<(elid!s2)q=y$?w;u zO-4=w;dcq(OQ!^$C|%XMp}EA}E`3&H#tPG$8(B}kGxs6x>eNjNW|@Gq)NEy;*Miv$u^?JQJlQ;t>On+i}qPtYIU1@G}x5#$Nn87Z_{k{39|YCCCU`pj+xSXRgj{iT@s|Q+<^Tj z-jK=P{D4q7q#P`9Iv2I0O2qU+?nlEkD{4 zW?@Gv0)$yaO{KJi*b_SRFGN6u8>ZmCj#HJIdfP#*HX#xnN49fn%u3wU9x%^xW!EMyi?2*D^TWYx&?KJ)X~)P1*GT zjsu`o-yD&DD>`iIoFPpJqnfkXZ2J>6ZQlAKISorcke4gqOX9(c$qtY=pCaX+dXX&Y z06ut!F?2N+cy;6Ej#$YUO-jScYjK~T$6_SgaZYFdH$AClcaQQ`BANNjPRE5<6raE0 zGl0np7P|}x{}*aHJK?v4_YA(G04R&?ZI_UIODGLeNFh3)v|$5M4fxS~(lZk;igu z=ua3CY@_I^#o9gA(xB?>MXASWQtaJ>YWvcvD_R#2rf(8sBO$i-w{<3PjN$2GRDYi7YR#G7!=H^;T`H#S3 z(^=f>mM4NLvyI~WX>vmoD``!ukj44?9^7(t0uJOLeGRjg_rGQ6E1_Ck(te07wn zkHLKZm`w(A@{Uwx;Az?^EMmgsH6QxH5Qxk}6d(#Oy%%x;ashH-;uIFDBhiCG6d(#O zJ8{Sb$OXs+LK7hhub?0>?C~E@Q$r9;5&O From ebf2f61425899735c48f70dc5737c2b03a137e80 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 30 Dec 2025 22:03:36 +0100 Subject: [PATCH 294/430] Delete core_input_keyboard_gamepad_test.c --- .../core/core_input_keyboard_gamepad_test.c | 173 ------------------ 1 file changed, 173 deletions(-) delete mode 100644 examples/core/core_input_keyboard_gamepad_test.c diff --git a/examples/core/core_input_keyboard_gamepad_test.c b/examples/core/core_input_keyboard_gamepad_test.c deleted file mode 100644 index d1f9106f7..000000000 --- a/examples/core/core_input_keyboard_gamepad_test.c +++ /dev/null @@ -1,173 +0,0 @@ -/******************************************************************************************* -* -* raylib [core] example - Keyboard vs Gamepad Input Test -* -* Example complexity rating: [★☆☆☆] 1/4 -* -* This example is a diagnostic tool to verify that keyboard input is not -* incorrectly detected as gamepad input on Android devices. -* -* Issue reference: https://github.com/raysan5/raylib/issues/5387 -* -* 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) 2025 raylib contributors -* -********************************************************************************************/ - -#include "raylib.h" - -//------------------------------------------------------------------------------------ -// Program main entry point -//------------------------------------------------------------------------------------ -int main(void) -{ - // Initialization - //-------------------------------------------------------------------------------------- - const int screenWidth = 800; - const int screenHeight = 450; - - InitWindow(screenWidth, screenHeight, "raylib [core] example - keyboard vs gamepad test"); - - Vector2 ballPosition = { (float)screenWidth/2, (float)screenHeight/2 }; - int lastKeyPressed = 0; - - SetTargetFPS(60); - //-------------------------------------------------------------------------------------- - - // Main game loop - while (!WindowShouldClose()) - { - // Update - //---------------------------------------------------------------------------------- - - // Track keyboard input - if (IsKeyDown(KEY_RIGHT)) ballPosition.x += 4.0f; - if (IsKeyDown(KEY_LEFT)) ballPosition.x -= 4.0f; - if (IsKeyDown(KEY_UP)) ballPosition.y -= 4.0f; - if (IsKeyDown(KEY_DOWN)) ballPosition.y += 4.0f; - - // Keep ball on screen - if (ballPosition.x < 25) ballPosition.x = 25; - if (ballPosition.x > screenWidth - 25) ballPosition.x = screenWidth - 25; - if (ballPosition.y < 25) ballPosition.y = 25; - if (ballPosition.y > screenHeight - 25) ballPosition.y = screenHeight - 25; - - // Track last key pressed - int key = GetKeyPressed(); - if (key != 0) lastKeyPressed = key; - //---------------------------------------------------------------------------------- - - // Draw - //---------------------------------------------------------------------------------- - BeginDrawing(); - - ClearBackground(RAYWHITE); - - // Title - DrawText("KEYBOARD vs GAMEPAD INPUT TEST", 180, 10, 20, DARKGRAY); - DrawText("Issue #5387: Keyboard detected as gamepad on some Android devices", 120, 35, 14, GRAY); - - // Divider - DrawLine(0, 60, screenWidth, 60, LIGHTGRAY); - - // Keyboard section - DrawText("KEYBOARD INPUT", 20, 75, 18, DARKBLUE); - DrawRectangle(20, 100, 360, 80, Fade(BLUE, 0.1f)); - - DrawText(TextFormat("Arrow Keys: [%s] [%s] [%s] [%s]", - IsKeyDown(KEY_UP) ? "UP" : "--", - IsKeyDown(KEY_DOWN) ? "DN" : "--", - IsKeyDown(KEY_LEFT) ? "LT" : "--", - IsKeyDown(KEY_RIGHT) ? "RT" : "--"), 30, 110, 16, BLACK); - - DrawText(TextFormat("Last Key Pressed: %d", lastKeyPressed), 30, 135, 16, DARKGRAY); - DrawText(TextFormat("Any Key Down: %s", (IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || - IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT)) ? "YES" : "NO"), 30, 155, 16, DARKGRAY); - - // Gamepad section - DrawText("GAMEPAD STATUS", 420, 75, 18, DARKGREEN); - DrawRectangle(420, 100, 360, 80, Fade(GREEN, 0.1f)); - - bool gamepadReady = IsGamepadAvailable(0); - DrawText(TextFormat("Gamepad 0 Available: %s", gamepadReady ? "YES" : "NO"), - 430, 110, 16, gamepadReady ? RED : DARKGREEN); - - if (gamepadReady) - { - DrawText(TextFormat("D-Pad: [%s] [%s] [%s] [%s]", - IsGamepadButtonDown(0, GAMEPAD_BUTTON_LEFT_FACE_UP) ? "UP" : "--", - IsGamepadButtonDown(0, GAMEPAD_BUTTON_LEFT_FACE_DOWN) ? "DN" : "--", - IsGamepadButtonDown(0, GAMEPAD_BUTTON_LEFT_FACE_LEFT) ? "LT" : "--", - IsGamepadButtonDown(0, GAMEPAD_BUTTON_LEFT_FACE_RIGHT) ? "RT" : "--"), - 430, 135, 16, RED); - - DrawText(TextFormat("Gamepad Name: %.20s", GetGamepadName(0)), 430, 155, 14, DARKGRAY); - } - else - { - DrawText("No gamepad detected", 430, 135, 16, DARKGREEN); - } - - // Divider - DrawLine(0, 190, screenWidth, 190, LIGHTGRAY); - - // Test result section - DrawText("TEST RESULT", 20, 200, 18, MAROON); - - bool keyboardActive = IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || - IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT); - - if (keyboardActive && gamepadReady) - { - // BUG DETECTED: Keyboard is triggering gamepad detection - DrawRectangle(20, 225, 760, 50, Fade(RED, 0.3f)); - DrawText("BUG DETECTED: Keyboard input is being detected as gamepad!", 30, 235, 18, RED); - DrawText("The fix for issue #5387 may not be working correctly.", 30, 258, 14, DARKGRAY); - } - else if (keyboardActive && !gamepadReady) - { - // CORRECT: Keyboard works without triggering gamepad - DrawRectangle(20, 225, 760, 50, Fade(GREEN, 0.3f)); - DrawText("PASS: Keyboard input detected correctly (no phantom gamepad)", 30, 235, 18, DARKGREEN); - DrawText("Issue #5387 fix is working as expected.", 30, 258, 14, DARKGRAY); - } - else if (!keyboardActive && gamepadReady) - { - // Gamepad is connected (might be real or might be bug on idle) - DrawRectangle(20, 225, 760, 50, Fade(ORANGE, 0.3f)); - DrawText("INFO: Gamepad detected - press keyboard keys to test", 30, 235, 18, ORANGE); - DrawText("If gamepad stays active while pressing keyboard = BUG", 30, 258, 14, DARKGRAY); - } - else - { - // Idle state - DrawRectangle(20, 225, 760, 50, Fade(GRAY, 0.1f)); - DrawText("WAITING: Press arrow keys to test keyboard input", 30, 235, 18, GRAY); - DrawText("Gamepad should NOT become available when pressing keyboard keys", 30, 258, 14, DARKGRAY); - } - - // Ball controlled by keyboard - DrawText("Ball Control (Arrow Keys):", 20, 295, 16, DARKGRAY); - DrawCircleV(ballPosition, 25, MAROON); - DrawCircleLines((int)ballPosition.x, (int)ballPosition.y, 25, DARKGRAY); - - // Instructions - DrawRectangle(0, screenHeight - 45, screenWidth, 45, Fade(BLACK, 0.05f)); - DrawText("Instructions: Press keyboard arrow keys - the ball should move and gamepad should stay 'NO'", - 20, screenHeight - 35, 14, DARKGRAY); - DrawText("If gamepad becomes 'YES' while pressing keyboard = issue #5387 is NOT fixed", - 20, screenHeight - 18, 14, DARKGRAY); - - EndDrawing(); - //---------------------------------------------------------------------------------- - } - - // De-Initialization - //-------------------------------------------------------------------------------------- - CloseWindow(); - //-------------------------------------------------------------------------------------- - - return 0; -} From 6e70dece560e344089b11597efe97847ce435310 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 30 Dec 2025 22:05:37 +0100 Subject: [PATCH 295/430] Update minshell.html --- src/minshell.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/minshell.html b/src/minshell.html index ec7158841..6e2f137eb 100644 --- a/src/minshell.html +++ b/src/minshell.html @@ -54,6 +54,12 @@ // 'Ask where to save each file before downloading' - which you can set true/false. // If you enable this setting it would always ask you and bring the SaveAsDialog saveAs(blob, localFSname); + + // Alternative implementation to avoid FileSaver.js + //const link = document.createElement("a"); + //link.href = URL.createObjectURL(blob); + //link.download = localFSname; + //link.click(); } From fa1d4eb7fa01a37df79e7a0643cbb0f590e4dd84 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 30 Dec 2025 22:06:05 +0100 Subject: [PATCH 296/430] Update shapes_hilbert_curve.c --- examples/shapes/shapes_hilbert_curve.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shapes/shapes_hilbert_curve.c b/examples/shapes/shapes_hilbert_curve.c index 8ea03c7c5..3f368ca03 100644 --- a/examples/shapes/shapes_hilbert_curve.c +++ b/examples/shapes/shapes_hilbert_curve.c @@ -1,6 +1,6 @@ /******************************************************************************************* * -* raylib [shapes] example - hilbert curve example +* raylib [shapes] example - hilbert curve * * Example complexity rating: [★★★☆] 3/4 * From f260f5fdd019a1eacab7f2d5edd91a9c2eb6a7a2 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 30 Dec 2025 22:06:18 +0100 Subject: [PATCH 297/430] Update Makefile --- examples/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/Makefile b/examples/Makefile index bc2afbb3c..9983d8705 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -575,6 +575,7 @@ SHAPES = \ shapes/shapes_easings_box \ shapes/shapes_easings_rectangles \ shapes/shapes_following_eyes \ + shapes/shapes_hilbert_curve \ shapes/shapes_kaleidoscope \ shapes/shapes_lines_bezier \ shapes/shapes_lines_drawing \ From 695f3535333594b4cb244e4ac1537e2affbe13d1 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 30 Dec 2025 22:07:23 +0100 Subject: [PATCH 298/430] Update Makefile.Web --- examples/Makefile.Web | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/Makefile.Web b/examples/Makefile.Web index f36113f15..2e74bd211 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -563,6 +563,7 @@ SHAPES = \ shapes/shapes_easings_box \ shapes/shapes_easings_rectangles \ shapes/shapes_following_eyes \ + shapes/shapes_hilbert_curve \ shapes/shapes_kaleidoscope \ shapes/shapes_lines_bezier \ shapes/shapes_lines_drawing \ @@ -914,6 +915,9 @@ shapes/shapes_easings_rectangles: shapes/shapes_easings_rectangles.c shapes/shapes_following_eyes: shapes/shapes_following_eyes.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) +shapes/shapes_hilbert_curve: shapes/shapes_hilbert_curve.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + shapes/shapes_kaleidoscope: shapes/shapes_kaleidoscope.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) From 4054fc42f382ee4f161e953fbee30ddbee03d6cf Mon Sep 17 00:00:00 2001 From: RANDRIA Luca Date: Wed, 31 Dec 2025 00:09:20 +0300 Subject: [PATCH 299/430] Remove stdio.h unused header (#5456) --- examples/text/text_words_alignment.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/text/text_words_alignment.c b/examples/text/text_words_alignment.c index a558d5b11..dbd9cd03e 100644 --- a/examples/text/text_words_alignment.c +++ b/examples/text/text_words_alignment.c @@ -19,8 +19,6 @@ #include "raymath.h" // Required for: Lerp() -#include - typedef enum TextAlignment { TEXT_ALIGN_LEFT = 0, TEXT_ALIGN_TOP = 0, From 0c3e10b262127a162841df2d61b64143c525c2f7 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 30 Dec 2025 22:49:43 +0100 Subject: [PATCH 300/430] REVIEWED: `FileExists()`, using macro --- src/rcore.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index ea38300f1..af761cbb2 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -205,11 +205,13 @@ #define GETCWD _getcwd // NOTE: MSDN recommends not to use getcwd(), chdir() #define CHDIR _chdir #define MKDIR(dir) _mkdir(dir) + #define ACCESS(fn) _access(fn, 0) #else #include // Required for: getch(), chdir(), mkdir(), access() #define GETCWD getcwd #define CHDIR chdir #define MKDIR(dir) mkdir(dir, 0777) + #define ACCESS(fn) access(fn, F_OK) #endif //---------------------------------------------------------------------------------- @@ -1972,11 +1974,7 @@ bool FileExists(const char *fileName) { bool result = false; -#if defined(_WIN32) - if (_access(fileName, 0) != -1) result = true; -#else - if (access(fileName, F_OK) != -1) result = true; -#endif + if (ACCESS(fileName) != -1) result = true; // NOTE: Alternatively, stat() can be used instead of access() //#include From 9b183e0c5e5786a8a92e34e6a8f941586c12c39d Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 30 Dec 2025 23:21:10 +0100 Subject: [PATCH 301/430] REXM: Update examples and reports --- examples/Makefile.Web | 12 +- examples/README.md | 7 +- .../examples/shapes_hilbert_curve.vcxproj | 569 ++++++++++++++++++ projects/VS2022/raylib.sln | 29 +- tools/rexm/reports/examples_issues.md | 13 +- tools/rexm/reports/examples_validation.md | 17 +- 6 files changed, 622 insertions(+), 25 deletions(-) create mode 100644 projects/VS2022/examples/shapes_hilbert_curve.vcxproj diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 2e74bd211..5718088ac 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -1371,15 +1371,15 @@ shaders/shaders_fog_rendering: shaders/shaders_fog_rendering.c shaders/shaders_game_of_life: shaders/shaders_game_of_life.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file shaders/resources/shaders/glsl100/game_of_life.fs@resources/shaders/glsl100/game_of_life.fs \ - --preload-file shaders/resources/game_of_life/acorn.png@resources/game_of_life/acorn.png \ - --preload-file shaders/resources/game_of_life/breeder.png@resources/game_of_life/breeder.png \ + --preload-file shaders/resources/game_of_life/r_pentomino.png@resources/game_of_life/r_pentomino.png \ --preload-file shaders/resources/game_of_life/glider.png@resources/game_of_life/glider.png \ - --preload-file shaders/resources/game_of_life/glider_gun.png@resources/game_of_life/glider_gun.png \ + --preload-file shaders/resources/game_of_life/acorn.png@resources/game_of_life/acorn.png \ + --preload-file shaders/resources/game_of_life/spaceships.png@resources/game_of_life/spaceships.png \ + --preload-file shaders/resources/game_of_life/still_lifes.png@resources/game_of_life/still_lifes.png \ --preload-file shaders/resources/game_of_life/oscillators.png@resources/game_of_life/oscillators.png \ --preload-file shaders/resources/game_of_life/puffer_train.png@resources/game_of_life/puffer_train.png \ - --preload-file shaders/resources/game_of_life/r_pentomino.png@resources/game_of_life/r_pentomino.png \ - --preload-file shaders/resources/game_of_life/spaceships.png@resources/game_of_life/spaceships.png \ - --preload-file shaders/resources/game_of_life/still_lifes.png@resources/game_of_life/still_lifes.png + --preload-file shaders/resources/game_of_life/glider_gun.png@resources/game_of_life/glider_gun.png \ + --preload-file shaders/resources/game_of_life/breeder.png@resources/game_of_life/breeder.png shaders/shaders_hot_reloading: shaders/shaders_hot_reloading.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ diff --git a/examples/README.md b/examples/README.md index 367bfa0ab..d9b03669d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -17,7 +17,7 @@ You may find it easier to use than other toolchains, especially when it comes to - `zig build [module]` to compile all examples for a module (e.g. `zig build core`) - `zig build [example]` to compile _and run_ a particular example (e.g. `zig build core_basic_window`) -## EXAMPLES COLLECTION [TOTAL: 205] +## EXAMPLES COLLECTION [TOTAL: 206] ### category: core [47] @@ -69,11 +69,11 @@ Examples using raylib [core](../src/rcore.c) module platform functionality: wind | [core_directory_files](core/core_directory_files.c) | core_directory_files | ⭐☆☆☆ | 5.5 | 5.6 | [Hugo ARNAL](https://github.com/hugoarnal) | | [core_highdpi_testbed](core/core_highdpi_testbed.c) | core_highdpi_testbed | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | | [core_screen_recording](core/core_screen_recording.c) | core_screen_recording | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | -| [core_clipboard_text](core/core_clipboard_text.c) | core_clipboard_text | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ananth S](https://github.com/Ananth1839) | +| [core_clipboard_text](core/core_clipboard_text.c) | core_clipboard_text | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Ananth S](https://github.com/Ananth1839) | | [core_text_file_loading](core/core_text_file_loading.c) | core_text_file_loading | ⭐☆☆☆ | 5.5 | 5.6 | [Aanjishnu Bhattacharyya](https://github.com/NimComPoo-04) | | [core_compute_hash](core/core_compute_hash.c) | core_compute_hash | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | -### category: shapes [38] +### category: shapes [39] Examples using raylib shapes drawing functionality, provided by raylib [shapes](../src/rshapes.c) module. @@ -117,6 +117,7 @@ Examples using raylib shapes drawing functionality, provided by raylib [shapes]( | [shapes_rlgl_triangle](shapes/shapes_rlgl_triangle.c) | shapes_rlgl_triangle | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Robin](https://github.com/RobinsAviary) | | [shapes_ball_physics](shapes/shapes_ball_physics.c) | shapes_ball_physics | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [David Buzatto](https://github.com/davidbuzatto) | | [shapes_penrose_tile](shapes/shapes_penrose_tile.c) | shapes_penrose_tile | ⭐⭐⭐⭐️ | 5.5 | 5.6-dev | [David Buzatto](https://github.com/davidbuzatto) | +| [shapes_hilbert_curve](shapes/shapes_hilbert_curve.c) | shapes_hilbert_curve | ⭐⭐⭐☆ | 5.6 | 5.6 | [Hamza RAHAL](https://github.com/hmz-rhl) | ### category: textures [29] diff --git a/projects/VS2022/examples/shapes_hilbert_curve.vcxproj b/projects/VS2022/examples/shapes_hilbert_curve.vcxproj new file mode 100644 index 000000000..8fcbfab5f --- /dev/null +++ b/projects/VS2022/examples/shapes_hilbert_curve.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {DC163251-16C3-4B72-B965-ACDBA0F02BD1} + Win32Proj + shapes_hilbert_curve + 10.0 + shapes_hilbert_curve + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index 50fef1cf5..df2633843 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -431,6 +431,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "text_strings_management", " EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "textures_cellular_automata", "examples\textures_cellular_automata.vcxproj", "{0A0FC982-6E31-401F-BA77-3C5E8AB02C68}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_hilbert_curve", "examples\shapes_hilbert_curve.vcxproj", "{DC163251-16C3-4B72-B965-ACDBA0F02BD1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug.DLL|ARM64 = Debug.DLL|ARM64 @@ -5365,6 +5367,30 @@ Global {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Release|x64.Build.0 = Release|x64 {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Release|x86.ActiveCfg = Release|Win32 {0A0FC982-6E31-401F-BA77-3C5E8AB02C68}.Release|x86.Build.0 = Release|Win32 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Debug|ARM64.Build.0 = Debug|ARM64 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Debug|x64.ActiveCfg = Debug|x64 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Debug|x64.Build.0 = Debug|x64 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Debug|x86.ActiveCfg = Debug|Win32 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Debug|x86.Build.0 = Debug|Win32 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release|ARM64.ActiveCfg = Release|ARM64 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release|ARM64.Build.0 = Release|ARM64 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release|x64.ActiveCfg = Release|x64 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release|x64.Build.0 = Release|x64 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release|x86.ActiveCfg = Release|Win32 + {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5532,7 +5558,7 @@ Global {C54703BF-D68A-480D-BE27-49B62E45D582} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {9CD8BCAD-F212-4BCC-BA98-899743CE3279} = {CC132A4D-D081-4C26-BFB9-AB11984054F8} {0981CA28-E4A5-4DF1-987F-A41D09131EFC} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} - {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} + {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {278D8859-20B1-428F-8448-064F46E1F021} {6BFF72EA-7362-4A3B-B6E5-9A3655BBBDA3} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {6777EC3C-077C-42FC-B4AD-B799CE55CCE4} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A} {A61DAD9C-271C-4E95-81AA-DB4CD58564D4} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} @@ -5582,6 +5608,7 @@ Global {7883D076-CA8F-4FF7-8B5D-0DFF41CEF8FC} = {278D8859-20B1-428F-8448-064F46E1F021} {1F4722E7-F78E-413F-A106-D3490211EA57} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A} {0A0FC982-6E31-401F-BA77-3C5E8AB02C68} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} + {DC163251-16C3-4B72-B965-ACDBA0F02BD1} = {278D8859-20B1-428F-8448-064F46E1F021} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} diff --git a/tools/rexm/reports/examples_issues.md b/tools/rexm/reports/examples_issues.md index 14e7a61c5..081170806 100644 --- a/tools/rexm/reports/examples_issues.md +++ b/tools/rexm/reports/examples_issues.md @@ -21,10 +21,9 @@ Example elements validated: | **EXAMPLE NAME** | [C] | [CAT]| [INFO]|[PNG]|[WPNG]| [RES]| [MK] |[MKWEB]| [VCX]| [SOL]|[RDME]|[JS] | [WOUT]|[WMETA]| |:---------------------------------|:---:|:----:|:-----:|:---:|:----:|:----:|:----:|:-----:|:----:|:----:|:----:|:---:|:-----:|:-----:| | core_highdpi_testbed | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shaders_game_of_life | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | -| rlgl_standalone | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | -| rlgl_compute_shader | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | -| easings_testbed | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ❌ | ✔ | ❌ | ✔ | ✔ | -| embedded_files_loading | ✔ | ❌ | ❌ | ✔ | ✔ | ❌ | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | -| web_basic_window | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| rlgl_standalone | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| rlgl_compute_shader | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| easings_testbed | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | +| embedded_files_loading | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| web_basic_window | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | diff --git a/tools/rexm/reports/examples_validation.md b/tools/rexm/reports/examples_validation.md index 45c195415..6770f3c37 100644 --- a/tools/rexm/reports/examples_validation.md +++ b/tools/rexm/reports/examples_validation.md @@ -56,7 +56,7 @@ Example elements validated: | core_smooth_pixelperfect | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_random_sequence | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_automation_events | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| core_high_dpi | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_highdpi_demo | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_render_texture | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_undo_redo | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_viewport_scaling | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -209,7 +209,7 @@ Example elements validated: | shaders_lightmap_rendering | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_rounded_rectangle | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_depth_rendering | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shaders_game_of_life | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | +| shaders_game_of_life | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | audio_module_playing | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | audio_music_stream | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | audio_raw_stream | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -219,9 +219,10 @@ Example elements validated: | audio_sound_multi | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | audio_sound_positioning | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | audio_spectrum_visualizer | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| rlgl_standalone | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | -| rlgl_compute_shader | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | -| easings_testbed | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ❌ | ✔ | ❌ | ✔ | ✔ | -| embedded_files_loading | ✔ | ❌ | ❌ | ✔ | ✔ | ❌ | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | -| web_basic_window | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| rlgl_standalone | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| rlgl_compute_shader | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| easings_testbed | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | +| embedded_files_loading | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| web_basic_window | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_hilbert_curve | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | From e534f14419b2c6ab67f4b3a99102d2bb3231f5cb Mon Sep 17 00:00:00 2001 From: CosmosShell Date: Wed, 31 Dec 2025 00:05:39 -0800 Subject: [PATCH 302/430] Fix window width calculation by adding wOffset (#5457) --- src/external/RGFW.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/external/RGFW.h b/src/external/RGFW.h index 7205bf9d8..0ab3858cf 100644 --- a/src/external/RGFW.h +++ b/src/external/RGFW.h @@ -6522,8 +6522,8 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) if (win->src.aspectRatio.w != 0 && win->src.aspectRatio.h != 0) { double aspectRatio = (double)win->src.aspectRatio.w / win->src.aspectRatio.h; - int width = windowRect.right - windowRect.left; - int height = windowRect.bottom - windowRect.top; + int width = (windowRect.right - windowRect.left) - win->src.wOffset; + int height = (windowRect.bottom - windowRect.top) - win->src.hOffset; int newHeight = (int)(width / aspectRatio); int newWidth = (int)(height * aspectRatio); @@ -6968,6 +6968,7 @@ RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowF DestroyWindow(dummyWin); win->src.hOffset = (u32)(windowRect.bottom - windowRect.top) - (u32)(clientRect.bottom - clientRect.top); + win->src.wOffset = (u32)(windowRect.right - windowRect.left) - (u32)(clientRect.right - clientRect.left); win->src.window = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->r.x, win->r.y, win->r.w, win->r.h + (i32)win->src.hOffset, 0, 0, inh, 0); SetPropW(win->src.window, L"RGFW", win); RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h)); /* so WM_GETMINMAXINFO gets called again */ From 4af95a3a84129c4dc83da45f83ebbb05936e9d4a Mon Sep 17 00:00:00 2001 From: Alvin De Cruz Date: Wed, 31 Dec 2025 17:33:17 +0800 Subject: [PATCH 303/430] Use eglGetPlatformDisplayEXT on DRM platform for Mali compatibility (#5446) --- src/platforms/rcore_drm.c | 50 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 103d81975..ff0118ac5 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -89,6 +89,10 @@ #define EGL_OPENGL_ES3_BIT 0x40 #endif +#ifndef EGL_PLATFORM_GBM_KHR + #define EGL_PLATFORM_GBM_KHR 0x31D7 +#endif + //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -1416,7 +1420,30 @@ int InitPlatform(void) EGLint numConfigs = 0; // Get an EGL device connection - platform.device = eglGetDisplay((EGLNativeDisplayType)platform.gbmDevice); + // Try eglGetPlatformDisplayEXT for better compatibility with some drivers (e.g. Mali Midgard) + // REF: https://github.com/raysan5/raylib/issues/5378 + platform.device = EGL_NO_DISPLAY; + const char *eglClientExtensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + + if (eglClientExtensions != NULL) + { + if (strstr(eglClientExtensions, "EGL_EXT_platform_base") != NULL) + { + PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = + (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); + + if (eglGetPlatformDisplayEXT != NULL) + { + platform.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, platform.gbmDevice, NULL); + } + } + } + + if (platform.device == EGL_NO_DISPLAY) + { + platform.device = eglGetDisplay((EGLNativeDisplayType)platform.gbmDevice); + } + if (platform.device == EGL_NO_DISPLAY) { TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); @@ -1496,8 +1523,25 @@ int InitPlatform(void) } // Create an EGL window surface - platform.surface = eglCreateWindowSurface(platform.device, platform.config, (EGLNativeWindowType)platform.gbmSurface, NULL); - if (EGL_NO_SURFACE == platform.surface) + platform.surface = EGL_NO_SURFACE; + + if ((eglClientExtensions != NULL) && (strstr(eglClientExtensions, "EGL_EXT_platform_base") != NULL)) + { + PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC eglCreatePlatformWindowSurfaceEXT = + (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"); + + if (eglCreatePlatformWindowSurfaceEXT != NULL) + { + platform.surface = eglCreatePlatformWindowSurfaceEXT(platform.device, platform.config, platform.gbmSurface, NULL); + } + } + + if (platform.surface == EGL_NO_SURFACE) + { + platform.surface = eglCreateWindowSurface(platform.device, platform.config, (EGLNativeWindowType)platform.gbmSurface, NULL); + } + + if (platform.surface == EGL_NO_SURFACE) { TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL window surface: 0x%04x", eglGetError()); return -1; From 25ce6465d580652f1149e8698f14b60339230093 Mon Sep 17 00:00:00 2001 From: McDubh <103212704+mcdubhghlas@users.noreply.github.com> Date: Wed, 31 Dec 2025 03:58:58 -0600 Subject: [PATCH 304/430] Added SSE to MatrixMultiply. (#5427) --- src/raymath.h | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/raymath.h b/src/raymath.h index 32dfd2b0a..67756a6d0 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -170,6 +170,11 @@ typedef struct float16 { #include // Required for: sinf(), cosf(), tan(), atan2f(), sqrtf(), floor(), fminf(), fmaxf(), fabsf() +#if defined(__SSE__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 1) + #include + #define RAYMATH_SSE_ENABLED +#endif + //---------------------------------------------------------------------------------- // Module Functions Definition - Utils math //---------------------------------------------------------------------------------- @@ -1647,7 +1652,63 @@ RMAPI Matrix MatrixSubtract(Matrix left, Matrix right) RMAPI Matrix MatrixMultiply(Matrix left, Matrix right) { Matrix result = { 0 }; +#ifdef RAYMATH_SSE_ENABLED + // Load left side and right side. + __m128 c0 = _mm_set_ps(right.m12, right.m8, right.m4, right.m0); + __m128 c1 = _mm_set_ps(right.m13, right.m9, right.m5, right.m1); + __m128 c2 = _mm_set_ps(right.m14, right.m10, right.m6, right.m2); + __m128 c3 = _mm_set_ps(right.m15, right.m11, right.m7, right.m3); + // Transpose so c0..c3 become *rows* of the right matrix in semantic order. + _MM_TRANSPOSE4_PS(c0, c1, c2, c3); + __m128 row; + float tmp[4]; + + // Row 0 of result: [m0, m1, m2, m3] + row = _mm_mul_ps(_mm_set1_ps(left.m0), c0); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m1), c1)); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m2), c2)); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m3), c3)); + _mm_storeu_ps(tmp, row); + result.m0 = tmp[0]; + result.m1 = tmp[1]; + result.m2 = tmp[2]; + result.m3 = tmp[3]; + + // Row 1 of result: [m4, m5, m6, m7] + row = _mm_mul_ps(_mm_set1_ps(left.m4), c0); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m5), c1)); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m6), c2)); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m7), c3)); + _mm_storeu_ps(tmp, row); + result.m4 = tmp[0]; + result.m5 = tmp[1]; + result.m6 = tmp[2]; + result.m7 = tmp[3]; + + // Row 2 of result: [m8, m9, m10, m11] + row = _mm_mul_ps(_mm_set1_ps(left.m8), c0); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m9), c1)); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m10), c2)); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m11), c3)); + _mm_storeu_ps(tmp, row); + result.m8 = tmp[0]; + result.m9 = tmp[1]; + result.m10 = tmp[2]; + result.m11 = tmp[3]; + + // Row 3 of result: [m12, m13, m14, m15] + row = _mm_mul_ps(_mm_set1_ps(left.m12), c0); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m13), c1)); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m14), c2)); + row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m15), c3)); + _mm_storeu_ps(tmp, row); + result.m12 = tmp[0]; + result.m13 = tmp[1]; + result.m14 = tmp[2]; + result.m15 = tmp[3]; + +#else result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; @@ -1664,7 +1725,7 @@ RMAPI Matrix MatrixMultiply(Matrix left, Matrix right) result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; - +#endif return result; } From 02cca28b5f1b9acab403bfdad09605426bb18a23 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 31 Dec 2025 11:08:10 +0100 Subject: [PATCH 305/430] REVIEWED: `eglGetPlatformDisplay()` usage --- src/platforms/rcore_drm.c | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index ff0118ac5..c5b74b956 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -1420,30 +1420,26 @@ int InitPlatform(void) EGLint numConfigs = 0; // Get an EGL device connection - // Try eglGetPlatformDisplayEXT for better compatibility with some drivers (e.g. Mali Midgard) - // REF: https://github.com/raysan5/raylib/issues/5378 + // NOTE: eglGetPlatformDisplay() is preferred over eglGetDisplay() legacy call platform.device = EGL_NO_DISPLAY; +#if defined(EGL_VERSION_1_5) + platform.device = eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, platform.gbmDevice, NULL); +#else + // Check if extension is available for eglGetPlatformDisplayEXT() + // NOTE: Better compatibility with some drivers (e.g. Mali Midgard) const char *eglClientExtensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); - if (eglClientExtensions != NULL) { if (strstr(eglClientExtensions, "EGL_EXT_platform_base") != NULL) { - PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = - (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); - - if (eglGetPlatformDisplayEXT != NULL) - { - platform.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, platform.gbmDevice, NULL); - } + PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); + if (eglGetPlatformDisplayEXT != NULL) platform.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, platform.gbmDevice, NULL); } } - if (platform.device == EGL_NO_DISPLAY) - { - platform.device = eglGetDisplay((EGLNativeDisplayType)platform.gbmDevice); - } - + // In case extension not found or display could not be retrieved, try useing legacy version + if (platform.device == EGL_NO_DISPLAY) platform.device = eglGetDisplay((EGLNativeDisplayType)platform.gbmDevice); +#endif if (platform.device == EGL_NO_DISPLAY) { TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); From 66755da4c8f6ae2f2be9accc1563077756f03dc7 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 31 Dec 2025 11:08:17 +0100 Subject: [PATCH 306/430] REVIEWED: `eglGetPlatformDisplay()` usage --- src/platforms/rcore_android.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index cc012c4fe..e1dba72c8 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -919,7 +919,27 @@ static int InitGraphicsDevice(void) EGLint numConfigs = 0; // Get an EGL device connection - platform.device = eglGetDisplay(EGL_DEFAULT_DISPLAY); + // NOTE: eglGetPlatformDisplay() is preferred over eglGetDisplay() legacy call + platform.device = EGL_NO_DISPLAY; +#if defined(EGL_VERSION_1_5) + platform.device = eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, platform.gbmDevice, NULL); +#else + // Check if extension is available for eglGetPlatformDisplayEXT() + // NOTE: Better compatibility with some drivers (e.g. Mali Midgard) + const char *eglClientExtensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (eglClientExtensions != NULL) + { + if (strstr(eglClientExtensions, "EGL_EXT_platform_base") != NULL) + { + PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); + if (eglGetPlatformDisplayEXT != NULL) platform.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, platform.gbmDevice, NULL); + } + } + + // In case extension not found or display could not be retrieved, try useing legacy version + if (platform.device == EGL_NO_DISPLAY) platform.device = eglGetDisplay(EGL_DEFAULT_DISPLAY); +#endif + if (platform.device == EGL_NO_DISPLAY) { TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); From c124f2552bbdcd9030e57f22283f5dd6570bf484 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 31 Dec 2025 11:22:26 +0100 Subject: [PATCH 307/430] REVIEWED: SIMD instrinsics must be explicitly enabled by developer, only SSE supported at the moment #5316 --- src/raymath.h | 60 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/src/raymath.h b/src/raymath.h index 67756a6d0..8d5b1b2a9 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -19,17 +19,22 @@ * * CONFIGURATION: * #define RAYMATH_IMPLEMENTATION -* Generates the implementation of the library into the included file. +* Generates the implementation of the library into the included file * If not defined, the library is in header only mode and can be included in other headers -* or source files without problems. But only ONE file should hold the implementation. +* or source files without problems. But only ONE file should hold the implementation * * #define RAYMATH_STATIC_INLINE -* Define static inline functions code, so #include header suffices for use. -* This may use up lots of memory. +* Define static inline functions code, so #include header suffices for use +* This may use up lots of memory * * #define RAYMATH_DISABLE_CPP_OPERATORS * Disables C++ operator overloads for raymath types. * +* #define RAYMATH_USE_SIMD_INTRINSICS +* Try to enable SIMD intrinsics for MatrixMultiply() +* Note that users enabling it must be aware of the target platform where application will +* run to support the selected SIMD intrinsic, for now, only SSE is supported +* * LICENSE: zlib/libpng * * Copyright (c) 2015-2025 Ramon Santamaria (@raysan5) @@ -79,7 +84,6 @@ #endif #endif - //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -170,9 +174,35 @@ typedef struct float16 { #include // Required for: sinf(), cosf(), tan(), atan2f(), sqrtf(), floor(), fminf(), fmaxf(), fabsf() -#if defined(__SSE__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 1) - #include - #define RAYMATH_SSE_ENABLED +#if defined(RAYMATH_USE_SIMD_INTRINSICS) + // SIMD is used on the most costly raymath function MatrixMultiply() + // NOTE: Only SSE intrinsics support implemented + // TODO: Consider support for other SIMD instrinsics + /* + #if defined(__SSE4_2__) + #define SW_HAS_SSE42 + #include + #elif defined(__SSE4_1__) + #define SW_HAS_SSE41 + #include + #elif defined(__SSSE3__) + #define SW_HAS_SSSE3 + #include + #elif defined(__SSE3__) + #define SW_HAS_SSE3 + #include + #elif defined(__SSE2__) || (defined(_M_AMD64) || defined(_M_X64)) // SSE2 x64 + #define SW_HAS_SSE2 + #include + #elif defined(__SSE__) + #define SW_HAS_SSE + #include + #endif + */ + #if defined(__SSE__) || defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1)) + #include + #define RAYMATH_SSE_ENABLED + #endif #endif //---------------------------------------------------------------------------------- @@ -1652,18 +1682,20 @@ RMAPI Matrix MatrixSubtract(Matrix left, Matrix right) RMAPI Matrix MatrixMultiply(Matrix left, Matrix right) { Matrix result = { 0 }; -#ifdef RAYMATH_SSE_ENABLED - // Load left side and right side. + +#if defined(RAYMATH_SSE_ENABLED) + // Load left side and right side __m128 c0 = _mm_set_ps(right.m12, right.m8, right.m4, right.m0); __m128 c1 = _mm_set_ps(right.m13, right.m9, right.m5, right.m1); __m128 c2 = _mm_set_ps(right.m14, right.m10, right.m6, right.m2); __m128 c3 = _mm_set_ps(right.m15, right.m11, right.m7, right.m3); - // Transpose so c0..c3 become *rows* of the right matrix in semantic order. + + // Transpose so c0..c3 become *rows* of the right matrix in semantic order _MM_TRANSPOSE4_PS(c0, c1, c2, c3); + float tmp[4] = { 0 }; __m128 row; - float tmp[4]; - + // Row 0 of result: [m0, m1, m2, m3] row = _mm_mul_ps(_mm_set1_ps(left.m0), c0); row = _mm_add_ps(row, _mm_mul_ps(_mm_set1_ps(left.m1), c1)); @@ -1707,7 +1739,6 @@ RMAPI Matrix MatrixMultiply(Matrix left, Matrix right) result.m13 = tmp[1]; result.m14 = tmp[2]; result.m15 = tmp[3]; - #else result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; @@ -1726,6 +1757,7 @@ RMAPI Matrix MatrixMultiply(Matrix left, Matrix right) result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; #endif + return result; } From 0133a4e6c6966e71567ecd663a1cdf43413042f5 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Wed, 31 Dec 2025 11:45:29 -0800 Subject: [PATCH 308/430] Make CameraMove up and right work with Z up cameras like the other functions do. (#5458) --- src/rcamera.h | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/rcamera.h b/src/rcamera.h index 3e9f83095..d67669a7b 100644 --- a/src/rcamera.h +++ b/src/rcamera.h @@ -252,8 +252,13 @@ void CameraMoveForward(Camera *camera, float distance, bool moveInWorldPlane) if (moveInWorldPlane) { - // Project vector onto world plane - forward.y = 0; + // Project vector onto world plane (the plane defined by the up vector) + if (fabsf(camera->up.z) > 0) + forward.z = 0; + else if (fabsf(camera->up.x) > 0) + forward.x = 0; + else + forward.y = 0; forward = Vector3Normalize(forward); } @@ -285,8 +290,14 @@ void CameraMoveRight(Camera *camera, float distance, bool moveInWorldPlane) if (moveInWorldPlane) { - // Project vector onto world plane - right.y = 0; + // Project vector onto world plane (the plane defined by the up vector) + if (fabsf(camera->up.z) > 0) + right.z = 0; + else if (fabsf(camera->up.x) > 0) + right.x = 0; + else + right.y = 0; + right = Vector3Normalize(right); } From 2377506843c9d397cdc5123841730ae8e53a5d84 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 31 Dec 2025 20:50:27 +0100 Subject: [PATCH 309/430] Update rcamera.h --- src/rcamera.h | 51 ++++++++++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/src/rcamera.h b/src/rcamera.h index d67669a7b..12f3a9e09 100644 --- a/src/rcamera.h +++ b/src/rcamera.h @@ -50,14 +50,14 @@ // Function specifiers in case library is build/used as a shared library (Windows) // NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll #if defined(_WIN32) -#if defined(BUILD_LIBTYPE_SHARED) -#if defined(__TINYC__) -#define __declspec(x) __attribute__((x)) -#endif -#define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) -#elif defined(USE_LIBTYPE_SHARED) -#define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) -#endif + #if defined(BUILD_LIBTYPE_SHARED) + #if defined(__TINYC__) + #define __declspec(x) __attribute__((x)) + #endif + #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) + #endif #endif #ifndef RLAPI @@ -191,19 +191,21 @@ RLAPI Matrix GetCameraProjectionMatrix(Camera *camera, float aspect); // IsKeyDown() // IsKeyPressed() // GetFrameTime() + +#include // Required for: fabsf() //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define CAMERA_MOVE_SPEED 5.4f // Units per second -#define CAMERA_ROTATION_SPEED 0.03f -#define CAMERA_PAN_SPEED 0.2f +#define CAMERA_MOVE_SPEED 5.4f // Units per second +#define CAMERA_ROTATION_SPEED 0.03f +#define CAMERA_PAN_SPEED 0.2f // Camera mouse movement sensitivity -#define CAMERA_MOUSE_MOVE_SENSITIVITY 0.003f +#define CAMERA_MOUSE_MOVE_SENSITIVITY 0.003f // Camera orbital speed in CAMERA_ORBITAL mode -#define CAMERA_ORBITAL_SPEED 0.5f // Radians per second +#define CAMERA_ORBITAL_SPEED 0.5f // Radians per second //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -253,12 +255,10 @@ void CameraMoveForward(Camera *camera, float distance, bool moveInWorldPlane) if (moveInWorldPlane) { // Project vector onto world plane (the plane defined by the up vector) - if (fabsf(camera->up.z) > 0) - forward.z = 0; - else if (fabsf(camera->up.x) > 0) - forward.x = 0; - else - forward.y = 0; + if (fabsf(camera->up.z) > 0) forward.z = 0; + else if (fabsf(camera->up.x) > 0) forward.x = 0; + else forward.y = 0; + forward = Vector3Normalize(forward); } @@ -291,12 +291,9 @@ void CameraMoveRight(Camera *camera, float distance, bool moveInWorldPlane) if (moveInWorldPlane) { // Project vector onto world plane (the plane defined by the up vector) - if (fabsf(camera->up.z) > 0) - right.z = 0; - else if (fabsf(camera->up.x) > 0) - right.x = 0; - else - right.y = 0; + if (fabsf(camera->up.z) > 0) right.z = 0; + else if (fabsf(camera->up.x) > 0) right.x = 0; + else right.y = 0; right = Vector3Normalize(right); } @@ -356,7 +353,7 @@ void CameraYaw(Camera *camera, float angle, bool rotateAroundTarget) // - lockView prevents camera overrotation (aka "somersaults") // - rotateAroundTarget defines if rotation is around target or around its position // - rotateUp rotates the up direction as well (typically only usefull in CAMERA_FREE) -// NOTE: angle must be provided in radians +// NOTE: [angle] must be provided in radians void CameraPitch(Camera *camera, float angle, bool lockView, bool rotateAroundTarget, bool rotateUp) { // Up direction @@ -393,7 +390,7 @@ void CameraPitch(Camera *camera, float angle, bool lockView, bool rotateAroundTa // Move position relative to target camera->position = Vector3Subtract(camera->target, targetPosition); } - else // rotate around camera.position + else // Rotate around camera.position { // Move target relative to position camera->target = Vector3Add(camera->position, targetPosition); From 83377a34884dd85ce6b72bf3b50247585e0bc843 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 31 Dec 2025 22:29:12 +0100 Subject: [PATCH 310/430] Update examples_list.txt --- examples/examples_list.txt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/examples/examples_list.txt b/examples/examples_list.txt index 3310cf2d2..96d64ca84 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -1,11 +1,13 @@ # -# raylib examples list used to generate/update collection -# examples must be provided as: ;;;;;;;""; +# raylib examples list with available .c example files +# +# WARNING: List is not ordered by example name but by the display order on web, +# so it can not be automatically generated scanning available .c code files, only updated +# new examples are added at the end of each category; it's up to the user to reorder them as desired +# +# examples data is listed as: ;;;;;;;""; # # This list is used as the main reference by [rexm] tool for examples collection validation and management -# New examples must be added to this list and any possible rename must be made on this list first -# -# WARNING: List is not ordered by example name but by the display order on web # core;core_basic_window;★☆☆☆;1.0;1.0;2013;2025;"Ramon Santamaria";@raysan5 core;core_delta_time;★☆☆☆;5.5;5.6-dev;2025;2025;"Robin";@RobinsAviary @@ -92,6 +94,7 @@ shapes;shapes_rlgl_color_wheel;★★★☆;5.6-dev;5.6-dev;2025;2025;"Robin";@R shapes;shapes_rlgl_triangle;★★☆☆;5.6-dev;5.6-dev;2025;2025;"Robin";@RobinsAviary shapes;shapes_ball_physics;★★☆☆;5.6-dev;5.6-dev;2025;2025;"David Buzatto";@davidbuzatto shapes;shapes_penrose_tile;★★★★;5.5;5.6-dev;2025;2025;"David Buzatto";@davidbuzatto +shapes;shapes_hilbert_curve;★★★☆;5.6;5.6;2025;2025;"Hamza RAHAL";@hmz-rhl textures;textures_logo_raylib;★☆☆☆;1.0;1.0;2014;2025;"Ramon Santamaria";@raysan5 textures;textures_srcrec_dstrec;★★★☆;1.3;1.3;2015;2025;"Ramon Santamaria";@raysan5 textures;textures_image_drawing;★★☆☆;1.4;1.4;2016;2025;"Ramon Santamaria";@raysan5 From f805e6cae82b945ff02c2381b8ab6d99e6a8f8cb Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 31 Dec 2025 22:46:36 +0100 Subject: [PATCH 311/430] REXM: Update `Makefile.Web` before trying to rebuild new example for web --- tools/rexm/rexm.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index aa491ff23..10b91e82f 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1226,6 +1226,18 @@ int main(int argc, char *argv[]) // Actions to fix/review anything possible from validation results //------------------------------------------------------------------------------------------------ + // Update files: Makefile, Makefile.Web, README.md, examples.js + // Solves: VALID_NOT_IN_MAKEFILE, VALID_NOT_IN_MAKEFILE_WEB, VALID_NOT_IN_README, VALID_NOT_IN_JS + // WARNING: Makefile.Web needs to be updated before trying to rebuild web example! + UpdateRequiredFiles(); + for (int i = 0; i < exCollectionCount; i++) + { + exCollection[i].status &= ~VALID_NOT_IN_MAKEFILE; + exCollection[i].status &= ~VALID_NOT_IN_MAKEFILE_WEB; + exCollection[i].status &= ~VALID_NOT_IN_README; + exCollection[i].status &= ~VALID_NOT_IN_JS; + } + // Check examples "status" information for (int i = 0; i < exCollectionCount; i++) { @@ -1325,17 +1337,6 @@ int main(int argc, char *argv[]) } } } - - // Update files: Makefile, Makefile.Web, README.md, examples.js - // Solves: VALID_NOT_IN_MAKEFILE, VALID_NOT_IN_MAKEFILE_WEB, VALID_NOT_IN_README, VALID_NOT_IN_JS - UpdateRequiredFiles(); - for (int i = 0; i < exCollectionCount; i++) - { - exCollection[i].status &= ~VALID_NOT_IN_MAKEFILE; - exCollection[i].status &= ~VALID_NOT_IN_MAKEFILE_WEB; - exCollection[i].status &= ~VALID_NOT_IN_README; - exCollection[i].status &= ~VALID_NOT_IN_JS; - } //------------------------------------------------------------------------------------------------ } From ab1d9b38304ff30b6207565e4038e11200c6cbbd Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 31 Dec 2025 22:47:16 +0100 Subject: [PATCH 312/430] REXM: Check example exists (compilation worked) before trying to run it --- tools/rexm/rexm.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 10b91e82f..aab316503 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1592,11 +1592,16 @@ int main(int argc, char *argv[]) FileRemove(TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName)); // STEP 3: Run example on browser - // WARNING: Example download is asynchronous so reading fails on next step - // when looking for a file that could not have been downloaded yet - ChangeDirectory(TextFormat("%s", exBasePath)); - if (i == 0) system("start python -m http.server 8080"); // Init localhost just once - system(TextFormat("start explorer \"http:\\localhost:8080/%s/%s.html", exCategory, exName)); + if (FileExists(TextFormat("%s/%s/%s.html", exBasePath, exCategory, exName)) && + FileExists(TextFormat("%s/%s/%s.wasm", exBasePath, exCategory, exName)) && + FileExists(TextFormat("%s/%s/%s.js", exBasePath, exCategory, exName))) + { + // WARNING: Example download is asynchronous so reading fails on next step + // when looking for a file that could not have been downloaded yet + ChangeDirectory(TextFormat("%s", exBasePath)); + if (i == 0) system("start python -m http.server 8080"); // Init localhost just once + system(TextFormat("start explorer \"http:\\localhost:8080/%s/%s.html", exCategory, exName)); + } // NOTE: Example .log is automatically downloaded into system Downloads directory on browser-example exectution From cac02ab0639cdd70b99031c0f3ac98176de7c4b1 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 31 Dec 2025 22:48:07 +0100 Subject: [PATCH 313/430] REXM: REVIEWED: Add new example to collection list at the end of its category, instead of adding it at the end of the file --- tools/rexm/rexm.c | 115 ++++++++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 49 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index aab316503..36152e560 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -207,8 +207,8 @@ static void UpdateSourceMetadata(const char *exSrcPath, const rlExampleInfo *inf // Update generated Web example .html file metadata static void UpdateWebMetadata(const char *exHtmlPath, const char *exFilePath); -// Check if text string is a list of strings -static bool TextInList(const char *text, const char **list, int listCount); +// Check if text string is in a list of strings and get index, -1 if not found +static int GetTextListIndex(const char *text, const char **list, int listCount); //------------------------------------------------------------------------------------ // Program main entry point @@ -1003,6 +1003,9 @@ int main(int argc, char *argv[]) VALID_INVALID_CATEGORY */ + // Validate and update examples collection list + // NOTE: New .c examples found are added at the end of its category + //--------------------------------------------------------------------------------------------------- // Scan available example .c files and add to collection missing ones // NOTE: Source of truth is what we have in the examples directories (on validation/update) LOG("INFO: Scanning available example (.c) files to be added to collection...\n"); @@ -1010,14 +1013,66 @@ int main(int argc, char *argv[]) // Load examples collection list file (raylib/examples/examples_list.txt) char *exList = LoadFileText(exCollectionFilePath); + int exListLen = (int)strlen(exList); + char *exListUpdated = (char *)RL_CALLOC(REXM_MAX_BUFFER_SIZE, 1); bool listUpdated = false; - int exListLen = (int)strlen(exList); - strcpy(exListUpdated, exList); + // Add new examples to the collection list if not found + // WARNING: Added to the end of category, order defines place on raylib webpage + for (unsigned int i = 0; i < clist.count; i++) + { + // NOTE: Skipping "examples_template" from checks + if (!TextIsEqual(GetFileNameWithoutExt(clist.paths[i]), "examples_template") && + (TextFindIndex(exList, GetFileNameWithoutExt(clist.paths[i])) == -1)) + { + // Get new example data + rlExampleInfo *exInfo = LoadExampleInfo(clist.paths[i]); - // Copy examples list into an update list - // NOTE: Checking and removing duplicate entries + // Get example category, -1 if not found in list + int catIndex = GetTextListIndex(exInfo->category, exCategories, REXM_MAX_EXAMPLE_CATEGORIES); + + if (catIndex > -1) + { + int nextCatIndex = catIndex + 1; + if (nextCatIndex > (REXM_MAX_EXAMPLE_CATEGORIES - 1)) nextCatIndex = -1; // EOF + + // Find position to add new example on list, just before the following category + // Category order: core, shapes, textures, text, models, shaders, audio, [others] + int exListNextCatIndex = -1; + if (nextCatIndex != -1) exListNextCatIndex = TextFindIndex(exList, exCategories[nextCatIndex]); + else exListNextCatIndex = exListLen; // EOF + + strncpy(exListUpdated, exList, exListNextCatIndex); + + // Get example difficulty stars + char starsText[16] = { 0 }; + for (int s = 0; s < 4; s++) + { + // NOTE: Every UTF-8 star are 3 bytes + if (s < exInfo->stars) strcpy(starsText + 3*s, "★"); + else strcpy(starsText + 3*s, "☆"); + } + + // Add new example to the list + int exListNewExLen = sprintf(exListUpdated + exListNextCatIndex, + TextFormat("%s;%s;%s;%s;%s;%i;%i;\"%s\";@%s\n", + exInfo->category, exInfo->name, starsText, exInfo->verCreated, + exInfo->verUpdated, exInfo->yearCreated, exInfo->yearReviewed, + exInfo->author, exInfo->authorGitHub)); + + // Add the following examples to the end of collection list + strncpy(exListUpdated + exListNextCatIndex + exListNewExLen, exList + exListNextCatIndex, exListLen - exListNextCatIndex); + + listUpdated = true; + } + + UnloadExampleInfo(exInfo); + } + } + + /* + // Check and remove duplicate example entries int lineCount = 0; char **exListLines = LoadTextLines(exList, &lineCount); int exListUpdatedOffset = 0; @@ -1031,46 +1086,7 @@ int main(int argc, char *argv[]) } UnloadTextLines(exListLines, lineCount); - - for (unsigned int i = 0; i < clist.count; i++) - { - // NOTE: Skipping "examples_template" from checks - if (!TextIsEqual(GetFileNameWithoutExt(clist.paths[i]), "examples_template") && - (TextFindIndex(exList, GetFileNameWithoutExt(clist.paths[i])) == -1)) - { - // TODO: Examples to be added in the list should be added at the end of their categories, - // not at the end of the file... - - // Add example to the examples collection list - // WARNING: Added to the end of the list, order must be set by users and - // defines placement on raylib webpage - rlExampleInfo *exInfo = LoadExampleInfo(clist.paths[i]); - - // Validate example category - // TODO: Should [others] category be considered? - if (TextInList(exInfo->category, exCategories, REXM_MAX_EXAMPLE_CATEGORIES))// && !TextIsEqual(exInfo->category, "others")) - { - // Get example difficulty stars - char starsText[16] = { 0 }; - for (int s = 0; s < 4; s++) - { - // NOTE: Every UTF-8 star are 3 bytes - if (s < exInfo->stars) strcpy(starsText + 3*s, "★"); - else strcpy(starsText + 3*s, "☆"); - } - - exListLen += sprintf(exListUpdated + exListLen, - TextFormat("%s;%s;%s;%s;%s;%i;%i;\"%s\";@%s\n", - exInfo->category, exInfo->name, starsText, exInfo->verCreated, - exInfo->verUpdated, exInfo->yearCreated, exInfo->yearReviewed, - exInfo->author, exInfo->authorGitHub)); - - listUpdated = true; - } - - UnloadExampleInfo(exInfo); - } - } + */ if (listUpdated) SaveFileText(exCollectionFilePath, exListUpdated); @@ -1078,6 +1094,7 @@ int main(int argc, char *argv[]) RL_FREE(exListUpdated); UnloadDirectoryFiles(clist); + //--------------------------------------------------------------------------------------------------- // Check all examples in collection [examples_list.txt] -> Source of truth! LOG("INFO: Validating examples in collection...\n"); @@ -2918,13 +2935,13 @@ static void UpdateWebMetadata(const char *exHtmlPath, const char *exFilePath) } // Check if text string is a list of strings -static bool TextInList(const char *text, const char **list, int listCount) +static int GetTextListIndex(const char *text, const char **list, int listCount) { - bool result = false; + int result = -1; for (int i = 0; i < listCount; i++) { - if (TextIsEqual(text, list[i])) { result = true; break; } + if (TextIsEqual(text, list[i])) { result = i; break; } } return result; From 95f72b162b7041ad699b59b0fdaf65e6424a26a1 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 31 Dec 2025 22:50:17 +0100 Subject: [PATCH 314/430] REVIEWED: `TextReplace()`, revert breaking change, needs to be reviewed again... -WIP- --- src/rtext.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/rtext.c b/src/rtext.c index 453ed4507..b0f77a767 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1746,16 +1746,23 @@ char *TextReplace(const char *text, const char *search, const char *replacement) { insertPoint = (char *)strstr(text, search); lastReplacePos = (int)(insertPoint - text); - temp = strncpy(temp, text, tempLen - 1) + lastReplacePos; - tempLen -= lastReplacePos; - temp = strncpy(temp, replacement, tempLen - 1) + replaceLen; - tempLen -= replaceLen; + + // TODO: Review logic to avoid strcpy() + // OK - Those lines work + temp = strncpy(temp, text, lastReplacePos) + lastReplacePos; + temp = strcpy(temp, replacement) + replaceLen; + // WRONG - But not those ones + //temp = strncpy(temp, text, tempLen - 1) + lastReplacePos; + //tempLen -= lastReplacePos; + //temp = strncpy(temp, replacement, tempLen - 1) + replaceLen; + //tempLen -= replaceLen; text += lastReplacePos + searchLen; // Move to next "end of replace" } // Copy remaind text part after replacement to result (pointed by moving temp) - strncpy(temp, text, tempLen - 1); + strcpy(temp, text); // OK + //strncpy(temp, text, tempLen - 1); // WRONG } return result; From eb4ad50d9904ff0359e303708127d3d9ba68dab2 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Wed, 31 Dec 2025 14:52:08 -0800 Subject: [PATCH 315/430] make sure that our up vector really is up in an axis before picking a world plane (#5459) --- src/rcamera.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rcamera.h b/src/rcamera.h index 12f3a9e09..72552ec15 100644 --- a/src/rcamera.h +++ b/src/rcamera.h @@ -255,8 +255,8 @@ void CameraMoveForward(Camera *camera, float distance, bool moveInWorldPlane) if (moveInWorldPlane) { // Project vector onto world plane (the plane defined by the up vector) - if (fabsf(camera->up.z) > 0) forward.z = 0; - else if (fabsf(camera->up.x) > 0) forward.x = 0; + if (fabsf(camera->up.z) > 0.7071f) forward.z = 0; + else if (fabsf(camera->up.x) > 0.7071f) forward.x = 0; else forward.y = 0; forward = Vector3Normalize(forward); @@ -291,8 +291,8 @@ void CameraMoveRight(Camera *camera, float distance, bool moveInWorldPlane) if (moveInWorldPlane) { // Project vector onto world plane (the plane defined by the up vector) - if (fabsf(camera->up.z) > 0) right.z = 0; - else if (fabsf(camera->up.x) > 0) right.x = 0; + if (fabsf(camera->up.z) > 0.7071f) right.z = 0; + else if (fabsf(camera->up.x) > 0.7071f) right.x = 0; else right.y = 0; right = Vector3Normalize(right); From 909f040dc5038fdb7b275481d8766bf8c7239346 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 1 Jan 2026 16:33:34 +0100 Subject: [PATCH 316/430] Remove trailing spaces --- src/platforms/rcore_android.c | 2 +- src/platforms/rcore_desktop_glfw.c | 20 +++++----- src/platforms/rcore_desktop_rgfw.c | 2 +- src/platforms/rcore_desktop_sdl.c | 6 +-- src/platforms/rcore_desktop_win32.c | 12 +++--- src/platforms/rcore_drm.c | 48 ++++++++++++------------ src/platforms/rcore_memory.c | 4 +- src/platforms/rcore_web.c | 16 ++++---- src/platforms/rcore_web_emscripten.c | 56 ++++++++++++++-------------- src/raudio.c | 4 +- src/rcore.c | 20 +++++----- src/rlgl.h | 2 +- src/rmodels.c | 4 +- src/rtext.c | 16 ++++---- src/rtextures.c | 4 +- 15 files changed, 108 insertions(+), 108 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index e1dba72c8..4f36f0a5b 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -360,7 +360,7 @@ void RestoreWindow(void) void SetWindowState(unsigned int flags) { if (!CORE.Window.ready) TRACELOG(LOG_WARNING, "WINDOW: SetWindowState does nothing before window initialization, Use \"SetConfigFlags\" instead"); - + // State change: FLAG_WINDOW_ALWAYS_RUN if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN); } diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index a1c0024aa..24b6d8d18 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -188,7 +188,7 @@ void ToggleFullscreen(void) GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); GLFWmonitor *monitor = (monitorIndex < monitorCount)? monitors[monitorIndex] : NULL; - if (monitor != NULL) + if (monitor != NULL) { // Get current monitor video mode const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitorIndex]); @@ -233,7 +233,7 @@ void ToggleFullscreen(void) #endif // WARNING: This function launches FramebufferSizeCallback() - glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, + glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); #if defined(_GLFW_X11) || defined(_GLFW_WAYLAND) @@ -283,7 +283,7 @@ void ToggleBorderlessWindowed(void) CORE.Window.screen.height = mode->height; // Set screen position and size - glfwSetWindowMonitor(platform.handle, monitors[monitor], CORE.Window.position.x, CORE.Window.position.y, + glfwSetWindowMonitor(platform.handle, monitors[monitor], CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, mode->refreshRate); // Refocus window @@ -312,7 +312,7 @@ void ToggleBorderlessWindowed(void) #endif // Return to previous screen size and position - glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, + glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, mode->refreshRate); // Refocus window @@ -908,7 +908,7 @@ Vector2 GetMonitorPosition(int monitor) if ((monitor >= 0) && (monitor < monitorCount)) { - int x = 0; + int x = 0; int y = 0; glfwGetMonitorPos(monitors[monitor], &x, &y); @@ -1026,7 +1026,7 @@ Vector2 GetWindowPosition(void) Vector2 GetWindowScaleDPI(void) { Vector2 scale = { 1.0f, 1.0f }; - if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI) && !FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) + if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI) && !FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) glfwGetWindowContentScale(platform.handle, &scale.x, &scale.y); return scale; } @@ -1275,7 +1275,7 @@ void PollInputEvents(void) state.axes[GAMEPAD_AXIS_LEFT_TRIGGER] = -1.0f; state.axes[GAMEPAD_AXIS_RIGHT_TRIGGER] = -1.0f; } - + const unsigned char *buttons = state.buttons; for (int k = 0; (buttons != NULL) && (k < MAX_GAMEPAD_BUTTONS); k++) @@ -1345,7 +1345,7 @@ void PollInputEvents(void) CORE.Window.resizedLastFrame = false; - if ((CORE.Window.eventWaiting) || + if ((CORE.Window.eventWaiting) || (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED) && !FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN))) { glfwWaitEvents(); // Wait for in input events before continue (drawing is paused) @@ -1455,7 +1455,7 @@ int InitPlatform(void) glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE); #endif // Resize window content area based on the monitor content scale - // NOTE: This hint only has an effect on platforms where screen coordinates and + // NOTE: This hint only has an effect on platforms where screen coordinates and // pixels always map 1:1 such as Windows and X11 // On platforms like macOS the resolution of the framebuffer is changed independently of the window size glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); @@ -1463,7 +1463,7 @@ int InitPlatform(void) glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); #endif } - else + else { glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); #if defined(__APPLE__) diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 558b6de55..b906fd103 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -330,7 +330,7 @@ void ToggleFullscreen(void) void ToggleBorderlessWindowed(void) { if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) ToggleFullscreen(); - + if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) { CORE.Window.previousPosition = CORE.Window.position; diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 952268ca6..cc3f7c6ae 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -1425,8 +1425,8 @@ void PollInputEvents(void) #if defined(USING_VERSION_SDL3) // const char *data; // The text for SDL_EVENT_DROP_TEXT and the file name for SDL_EVENT_DROP_FILE, NULL for other events - // Event memory is now managed by SDL, so you should not free the data in SDL_EVENT_DROP_FILE, - // and if you want to hold onto the text in SDL_EVENT_TEXT_EDITING and SDL_EVENT_TEXT_INPUT events, + // Event memory is now managed by SDL, so you should not free the data in SDL_EVENT_DROP_FILE, + // and if you want to hold onto the text in SDL_EVENT_TEXT_EDITING and SDL_EVENT_TEXT_INPUT events, // you should make a copy of it. SDL_TEXTINPUTEVENT_TEXT_SIZE is no longer necessary and has been removed strncpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.data, MAX_FILEPATH_LENGTH - 1); #else @@ -1487,7 +1487,7 @@ void PollInputEvents(void) CORE.Window.resizedLastFrame = true; #ifndef USING_VERSION_SDL3 - // Manually detect if the window was maximized (due to SDL2 restore being unreliable on some platforms) + // Manually detect if the window was maximized (due to SDL2 restore being unreliable on some platforms) // to remove the FLAG_WINDOW_MAXIMIZED accordingly if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) { diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 973fafa68..8d9bc6c8b 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -263,7 +263,7 @@ static bool DecoratedFromStyle(DWORD style) static DWORD MakeWindowStyle(unsigned flags) { // Flag is not needed because there are no child windows, - // but supposedly it improves efficiency, plus, windows adds this + // but supposedly it improves efficiency, plus, windows adds this // flag automatically anyway so it keeps flags in sync with the OS DWORD style = WS_CLIPSIBLINGS; @@ -1880,19 +1880,19 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara // Get current dpi scale factor float scalex = HIWORD(wParam)/96.0f; float scaley = LOWORD(wParam)/96.0f; - + RECT *suggestedRect = (RECT *)lparam; // Never set the window size to anything other than the suggested rect here // Doing so can cause a window to stutter between monitors when transitioning between them - int result = (int)SetWindowPos(hwnd, NULL, + int result = (int)SetWindowPos(hwnd, NULL, suggestedRect->left, suggestedRect->top, - suggestedRect->right - suggestedRect->left, - suggestedRect->bottom - suggestedRect->top, + suggestedRect->right - suggestedRect->left, + suggestedRect->bottom - suggestedRect->top, SWP_NOZORDER | SWP_NOACTIVATE); if (result == 0) TRACELOG(LOG_ERROR, "Failed to set window position [ERROR: %lu]", GetLastError()); - + // TODO: Update screen data, render size, screen scaling, viewport... } break; diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index c5b74b956..8db332b06 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -1606,10 +1606,10 @@ int InitPlatform(void) if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) MinimizeWindow(); // If graphic device is no properly initialized, we end program - if (!CORE.Window.ready) - { - TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphic device"); - return -1; + if (!CORE.Window.ready) + { + TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphic device"); + return -1; } else SetWindowPosition(GetMonitorWidth(GetCurrentMonitor())/2 - CORE.Window.screen.width/2, GetMonitorHeight(GetCurrentMonitor())/2 - CORE.Window.screen.height/2); @@ -1933,7 +1933,7 @@ static void InitEvdevInput(void) platform.touchPosition[i].y = -1; platform.touchId[i] = -1; } - + // Initialize touch slot platform.touchSlot = 0; @@ -2116,13 +2116,13 @@ static void ConfigureEvdevDevice(char *device) if (prioritize) { deviceKindStr = isTouch? "touchscreen" : "mouse"; - - if (platform.mouseFd != -1) + + if (platform.mouseFd != -1) { TRACELOG(LOG_INFO, "INPUT: Overwriting previous input device with new %s", deviceKindStr); close(platform.mouseFd); } - + platform.mouseFd = fd; platform.mouseIsTouch = isTouch; @@ -2134,7 +2134,7 @@ static void ConfigureEvdevDevice(char *device) platform.absRange.y = absinfo[ABS_Y].info.minimum; platform.absRange.height = absinfo[ABS_Y].info.maximum - absinfo[ABS_Y].info.minimum; } - + TRACELOG(LOG_INFO, "INPUT: Initialized input device %s as %s", device, deviceKindStr); } else @@ -2357,9 +2357,9 @@ static void PollMouseEvents(void) if (event.code == ABS_X) { CORE.Input.Mouse.currentPosition.x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; // Scale according to absRange - + // Update single touch position only if it's active and no MT events are being used - if (platform.touchActive[0] && !isMultitouch) + if (platform.touchActive[0] && !isMultitouch) { platform.touchPosition[0].x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; if (touchAction == -1) touchAction = 2; // TOUCH_ACTION_MOVE @@ -2369,9 +2369,9 @@ static void PollMouseEvents(void) if (event.code == ABS_Y) { CORE.Input.Mouse.currentPosition.y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; // Scale according to absRange - + // Update single touch position only if it's active and no MT events are being used - if (platform.touchActive[0] && !isMultitouch) + if (platform.touchActive[0] && !isMultitouch) { platform.touchPosition[0].y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; if (touchAction == -1) touchAction = 2; // TOUCH_ACTION_MOVE @@ -2379,9 +2379,9 @@ static void PollMouseEvents(void) } // Multitouch movement - if (event.code == ABS_MT_SLOT) + if (event.code == ABS_MT_SLOT) { - platform.touchSlot = event.value; + platform.touchSlot = event.value; isMultitouch = true; } @@ -2391,7 +2391,7 @@ static void PollMouseEvents(void) if (platform.touchSlot < MAX_TOUCH_POINTS) { platform.touchPosition[platform.touchSlot].x = (event.value - platform.absRange.x)*CORE.Window.screen.width/platform.absRange.width; - + // If this slot is active, it's a move. If not, we are just updating the buffer for when it becomes active. // Only set to MOVE if we haven't already detected a DOWN or UP event this frame if (platform.touchActive[platform.touchSlot] && touchAction == -1) touchAction = 2; // TOUCH_ACTION_MOVE @@ -2403,7 +2403,7 @@ static void PollMouseEvents(void) if (platform.touchSlot < MAX_TOUCH_POINTS) { platform.touchPosition[platform.touchSlot].y = (event.value - platform.absRange.y)*CORE.Window.screen.height/platform.absRange.height; - + // If this slot is active, it's a move. If not, we are just updating the buffer for when it becomes active. // Only set to MOVE if we haven't already detected a DOWN or UP event this frame if (platform.touchActive[platform.touchSlot] && touchAction == -1) touchAction = 2; // TOUCH_ACTION_MOVE @@ -2419,7 +2419,7 @@ static void PollMouseEvents(void) platform.touchActive[platform.touchSlot] = true; platform.touchId[platform.touchSlot] = event.value; // Use Tracking ID for unique IDs - + touchAction = 1; // TOUCH_ACTION_DOWN } else @@ -2429,7 +2429,7 @@ static void PollMouseEvents(void) platform.touchPosition[platform.touchSlot].x = -1; platform.touchPosition[platform.touchSlot].y = -1; platform.touchId[platform.touchSlot] = -1; - + // Force UP action if we haven't already set a DOWN action // (DOWN takes priority over UP if both happen in one frame, though rare) if (touchAction != 1) touchAction = 0; // TOUCH_ACTION_UP @@ -2486,7 +2486,7 @@ static void PollMouseEvents(void) if (event.value > 0) { bool activateSlot0 = false; - + if (event.code == BTN_LEFT) activateSlot0 = true; // Mouse click always activates else if (event.code == BTN_TOUCH) { @@ -2534,11 +2534,11 @@ static void PollMouseEvents(void) if (!CORE.Input.Mouse.cursorLocked) { if (CORE.Input.Mouse.currentPosition.x < 0) CORE.Input.Mouse.currentPosition.x = 0; - if (CORE.Input.Mouse.currentPosition.x > CORE.Window.screen.width/CORE.Input.Mouse.scale.x) + if (CORE.Input.Mouse.currentPosition.x > CORE.Window.screen.width/CORE.Input.Mouse.scale.x) CORE.Input.Mouse.currentPosition.x = CORE.Window.screen.width/CORE.Input.Mouse.scale.x; if (CORE.Input.Mouse.currentPosition.y < 0) CORE.Input.Mouse.currentPosition.y = 0; - if (CORE.Input.Mouse.currentPosition.y > CORE.Window.screen.height/CORE.Input.Mouse.scale.y) + if (CORE.Input.Mouse.currentPosition.y > CORE.Window.screen.height/CORE.Input.Mouse.scale.y) CORE.Input.Mouse.currentPosition.y = CORE.Window.screen.height/CORE.Input.Mouse.scale.y; } @@ -2553,9 +2553,9 @@ static void PollMouseEvents(void) k++; } } - + CORE.Input.Touch.pointCount = k; - + // Clear remaining slots for (int i = k; i < MAX_TOUCH_POINTS; i++) { diff --git a/src/platforms/rcore_memory.c b/src/platforms/rcore_memory.c index 1b7a55fd8..fda3fe774 100644 --- a/src/platforms/rcore_memory.c +++ b/src/platforms/rcore_memory.c @@ -472,7 +472,7 @@ void PollInputEvents(void) } // TODO: Poll input events for current platform - + // Check for key pressed to exit if (kbhit()) { @@ -513,7 +513,7 @@ int InitPlatform(void) TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); - + CORE.Window.ready = true; // TODO: Load OpenGL extensions diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index b52ca1bf0..244a53dd7 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -76,10 +76,10 @@ typedef struct { bool ourFullscreen; // Internal var to filter our handling of fullscreen vs the user handling of fullscreen int unmaximizedWidth; // Internal var to store the unmaximized window (canvas) width int unmaximizedHeight; // Internal var to store the unmaximized window (canvas) height - + char canvasId[64]; // Keep current canvas id where wasm app is running // NOTE: Useful when trying to run multiple wasms in different canvases in same webpage - + #if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) unsigned int *pixels; // Pointer to pixel data buffer (RGBA 32bit format) #endif @@ -885,7 +885,7 @@ void SwapScreenBuffer(void) #if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) // Update framebuffer rlCopyFramebuffer(0, 0, CORE.Window.render.width, CORE.Window.render.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, platform.pixels); - + // Copy framebuffer data into canvas EM_ASM({ const width = $0; @@ -1128,7 +1128,7 @@ void PollInputEvents(void) int InitPlatform(void) { SetCanvasIdJs(platform.canvasId, 64); // Get the current canvas id - + glfwSetErrorCallback(ErrorCallback); // Initialize GLFW internal global state @@ -1200,8 +1200,8 @@ int InitPlatform(void) glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) // Profiles Hint, only OpenGL 3.3 and above // Possible values: GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); // Forward Compatibility Hint: Only 3.3 and above! // glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Request OpenGL DEBUG context } @@ -1236,7 +1236,7 @@ int InitPlatform(void) // Init fullscreen toggle required var: platform.ourFullscreen = false; - + #if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) // Avoid creating a WebGL canvas, avoid calling glfwCreateWindow() emscripten_set_canvas_element_size(platform.canvasId, CORE.Window.screen.width, CORE.Window.screen.height); @@ -1244,7 +1244,7 @@ int InitPlatform(void) const canvas = document.getElementById("canvas"); Module.canvas = canvas; }); - + // Load memory framebuffer with desired screen size // NOTE: Despite using a software framebuffer for blitting, GLFW still creates a WebGL canvas, // but it is not being used, on SwapScreenBuffer() the pure software renderer is used diff --git a/src/platforms/rcore_web_emscripten.c b/src/platforms/rcore_web_emscripten.c index aeead6d9b..71dcdc2e7 100644 --- a/src/platforms/rcore_web_emscripten.c +++ b/src/platforms/rcore_web_emscripten.c @@ -636,25 +636,25 @@ void SetWindowSize(int width, int height) // - CSS canvas size: Web layout size, logical pixels // - Canvas contained framebuffer resolution // * Browser monitor, device pixel ratio (HighDPI) - - double canvasCssWidth = 0.0; + + double canvasCssWidth = 0.0; double canvasCssHeight = 0.0; emscripten_get_element_css_size(platform.canvasId, &canvasCssWidth, &canvasCssHeight); - + // NOTE: emscripten_get_canvas_element_size() returns canvas framebuffer size, not CSS canvas size - + // Get device pixel ratio // TODO: Should DPI be considered at this point? double dpr = emscripten_get_device_pixel_ratio(); // Set canvas framebuffer size emscripten_set_canvas_element_size(platform.canvasId, width*dpr, height*dpr); - + // Set canvas CSS size // TODO: Consider canvas CSS style if already scaled 100% EM_ASM({ Module.canvas.style.width = $0; }, width*dpr); EM_ASM({ Module.canvas.style.height = $0; }, height*dpr); - + SetupViewport(width*dpr, height*dpr); // Reset viewport and projection matrix for new size } @@ -704,7 +704,7 @@ Vector2 GetMonitorPosition(int monitor) // Get selected monitor width (currently used by monitor) int GetMonitorWidth(int monitor) { - // Get the width of the user's entire screen in CSS logical pixels, + // Get the width of the user's entire screen in CSS logical pixels, // no physical pixels, it would require multiplying by device pixel ratio // NOTE: Returned value is limited to the current monitor where the browser window is located int width = 0; @@ -715,7 +715,7 @@ int GetMonitorWidth(int monitor) // Get selected monitor height (currently used by monitor) int GetMonitorHeight(int monitor) { - // Get the height of the user's entire screen in CSS logical pixels, + // Get the height of the user's entire screen in CSS logical pixels, // no physical pixels, it would require multiplying by device pixel ratio // NOTE: Returned value is limited to the current monitor where the browser window is located int height = 0; @@ -865,7 +865,7 @@ void SwapScreenBuffer(void) #if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) // Update framebuffer rlCopyFramebuffer(0, 0, CORE.Window.render.width, CORE.Window.render.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, platform.pixels); - + // Copy framebuffer data into canvas EM_ASM({ const width = $0; @@ -904,7 +904,7 @@ double GetTime(void) time = (double)(nanoSeconds - CORE.Time.base)*1e-9; // Elapsed time since InitTimer() */ time = emscripten_get_now()*1000.0; - + return time; } @@ -1137,7 +1137,7 @@ int InitPlatform(void) const canvas = document.getElementById(platform.canvasId); Module.canvas = canvas; }); - + // Load memory framebuffer with desired screen size platform.pixels = (unsigned int *)RL_CALLOC(CORE.Window.screen.width*CORE.Window.screen.height, sizeof(unsigned int)); } @@ -1145,7 +1145,7 @@ int InitPlatform(void) { attribs.majorVersion = 1; // WebGL 1.0 requested attribs.minorVersion = 0; - + // Create WebGL context platform.glContext = emscripten_webgl_create_context(platform.canvasId, &attribs); if (platform.glContext == 0) return 0; @@ -1156,7 +1156,7 @@ int InitPlatform(void) { attribs.majorVersion = 2; // WebGL 2.0 requested attribs.minorVersion = 0; - + // Create WebGL context platform.glContext = emscripten_webgl_create_context(platform.canvasId, &attribs); if (platform.glContext == 0) return 0; @@ -1216,7 +1216,7 @@ int InitPlatform(void) emscripten_set_keypress_callback(platform.canvasId, NULL, 1, EmscriptenKeyboardCallback); emscripten_set_keydown_callback(platform.canvasId, NULL, 1, EmscriptenKeyboardCallback); emscripten_set_keyup_callback(platform.canvasId, NULL, 1, EmscriptenKeyboardCallback); - + emscripten_set_click_callback(platform.canvasId, NULL, 1, EmscriptenMouseCallback); //emscripten_set_dblclick_callback(platform.canvasId, NULL, 1, EmscriptenMouseCallback); emscripten_set_mousedown_callback(platform.canvasId, NULL, 1, EmscriptenMouseCallback); @@ -1225,15 +1225,15 @@ int InitPlatform(void) emscripten_set_mousemove_callback(platform.canvasId, NULL, 1, EmscriptenMouseMoveCallback); emscripten_set_wheel_callback(platform.canvasId, NULL, 1, EmscriptenMouseWheelCallback); emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenPointerlockCallback); - + emscripten_set_touchstart_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); emscripten_set_touchend_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); emscripten_set_touchmove_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); emscripten_set_touchcancel_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback); - + emscripten_set_gamepadconnected_callback(NULL, 1, EmscriptenGamepadCallback); emscripten_set_gamepaddisconnected_callback(NULL, 1, EmscriptenGamepadCallback); - + // Trigger resize callback to force initial size EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); //---------------------------------------------------------------------------- @@ -1256,7 +1256,7 @@ int InitPlatform(void) // Close platform // NOTE: Platform closing is managed by browser, so, // this function is actually not required, but still -// implementing some logic behaviour +// implementing some logic behaviour void ClosePlatform(void) { if (platform.pixels != NULL) RL_FREE(platform.pixels); @@ -1319,14 +1319,14 @@ static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent * static EM_BOOL EmscriptenFocusCallback(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData) { EM_BOOL consumed = 1; - + switch (eventType) { case EMSCRIPTEN_EVENT_BLUR: FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); break; // The canvas lost focus case EMSCRIPTEN_EVENT_FOCUS: FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); break; default: consumed = 0; break; } - + return consumed; } @@ -1335,7 +1335,7 @@ static EM_BOOL EmscriptenVisibilityChangeCallback(int eventType, const Emscripte { if (visibilityChangeEvent->hidden) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // The window was hidden else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // The window was restored - + return 1; // The event was consumed by the callback handler } @@ -1405,7 +1405,7 @@ static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboar } break; default: break; } - + // TODO: Add char codes //unsigned int charCode // Check if there is space available in the queue for characters to be added @@ -1457,7 +1457,7 @@ static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent } break; default: break; } - + #if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) // Process mouse events as touches to be able to use mouse-gestures GestureEvent gestureEvent = { 0 }; @@ -1508,7 +1508,7 @@ static EM_BOOL EmscriptenMouseMoveCallback(int eventType, const EmscriptenMouseE double cssHeight = 0.0; emscripten_get_element_css_size(platform.canvasId, &cssWidth, &cssHeight); - int fbWidth = 0; + int fbWidth = 0; int fbHeight = 0; emscripten_get_canvas_element_size(platform.canvasId, &fbWidth, &fbHeight); @@ -1518,15 +1518,15 @@ static EM_BOOL EmscriptenMouseMoveCallback(int eventType, const EmscriptenMouseE int mouseX = (int)(mouseCssX*scaleX); int mouseY = (int)(mouseCssY*scaleY); - + CORE.Input.Mouse.currentPosition.x = mouseX;//(float)mouseEvent->canvasX; CORE.Input.Mouse.currentPosition.y = mouseY;//(float)mouseEvent->canvasY; - + // Shorter alternative: //double dpr = emscripten_get_device_pixel_ratio(); //int mouseX = (int)(e->canvasX*dpr); //int mouseY = (int)(e->canvasY*dpr); - + CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; } @@ -1564,7 +1564,7 @@ static EM_BOOL EmscriptenMouseWheelCallback(int eventType, const EmscriptenWheel CORE.Input.Mouse.currentWheelMove.x = (float)wheelEvent->deltaX; CORE.Input.Mouse.currentWheelMove.y = (float)wheelEvent->deltaY; } - + return 1; // The event was consumed by the callback handler } diff --git a/src/raudio.c b/src/raudio.c index c65aaa134..e022447d7 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -2743,9 +2743,9 @@ static const char *GetFileExtension(const char *fileName) static const char *strprbrk(const char *text, const char *charset) { const char *latestMatch = NULL; - + for (; (text != NULL) && (text = strpbrk(text, charset)); latestMatch = text++) { } - + return latestMatch; } diff --git a/src/rcore.c b/src/rcore.c index af761cbb2..38bee25bf 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -320,7 +320,7 @@ typedef struct CoreData { char currentKeyState[MAX_KEYBOARD_KEYS]; // Registers current frame key state char previousKeyState[MAX_KEYBOARD_KEYS]; // Registers previous frame key state - // NOTE: Since key press logic involves comparing previous vs currrent key state, + // NOTE: Since key press logic involves comparing previous vs currrent key state, // key repeats needs to be handled specially char keyRepeatInFrame[MAX_KEYBOARD_KEYS]; // Registers key repeats for current frame @@ -817,7 +817,7 @@ int GetScreenHeight(void) int GetRenderWidth(void) { int width = 0; - + if (CORE.Window.usingFbo) return CORE.Window.currentFbo.width; else width = CORE.Window.render.width; @@ -1735,7 +1735,7 @@ int GetRandomValue(int min, int max) { TRACELOG(LOG_WARNING, "Invalid GetRandomValue() arguments, range should not be higher than %i", RAND_MAX); } - + // NOTE: This one-line approach produces a non-uniform distribution, // as stated by Donald Knuth in the book The Art of Programming, so // using below approach for more uniform results @@ -2257,7 +2257,7 @@ const char *GetApplicationDirectory(void) #if defined(_WIN32) int len = 0; - + #if defined(UNICODE) unsigned short widePath[MAX_PATH]; len = GetModuleFileNameW(NULL, (wchar_t *)widePath, MAX_PATH); @@ -2265,7 +2265,7 @@ const char *GetApplicationDirectory(void) #else len = GetModuleFileNameA(NULL, appDir, MAX_PATH); #endif - + if (len > 0) { for (int i = len; i >= 0; --i) @@ -2282,7 +2282,7 @@ const char *GetApplicationDirectory(void) appDir[0] = '.'; appDir[1] = '\\'; } - + #elif defined(__linux__) unsigned int size = sizeof(appDir); @@ -2304,7 +2304,7 @@ const char *GetApplicationDirectory(void) appDir[0] = '.'; appDir[1] = '/'; } - + #elif defined(__APPLE__) uint32_t size = sizeof(appDir); @@ -2326,7 +2326,7 @@ const char *GetApplicationDirectory(void) appDir[0] = '.'; appDir[1] = '/'; } - + #elif defined(__FreeBSD__) size_t size = sizeof(appDir); @@ -2697,7 +2697,7 @@ unsigned char *DecodeDataBase64(const char *text, int *outputSize) ['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55, ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, ['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63 }; - + *outputSize = 0; if (text == NULL) return NULL; @@ -4241,7 +4241,7 @@ const char *TextFormat(const char *text, ...) char *currentBuffer = buffers[index]; memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using - + if (text != NULL) { va_list args; diff --git a/src/rlgl.h b/src/rlgl.h index cda64896c..b10942d88 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -3391,7 +3391,7 @@ unsigned int rlLoadTexture(const void *data, int width, int height, int format, // Activate trilinear filtering if mipmaps are available glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - + // Define the maximum number of mipmap levels to be used, 0 is base texture size glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mipmapCount - 1); diff --git a/src/rmodels.c b/src/rmodels.c index 665b94147..da26a15f1 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -132,7 +132,7 @@ #ifndef MAX_MESH_VERTEX_BUFFERS #define MAX_MESH_VERTEX_BUFFERS 9 // Maximum vertex buffers (VBO) per mesh #endif -#ifndef MAX_FILEPATH_LENGTH +#ifndef MAX_FILEPATH_LENGTH #define MAX_FILEPATH_LENGTH 4096 // Maximum length for filepaths (Linux PATH_MAX default value) #endif @@ -4153,7 +4153,7 @@ RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform) // Test against all triangles in mesh for (int i = 0; i < triangleCount; i++) { - Vector3 a = { 0 }; + Vector3 a = { 0 }; Vector3 b = { 0 }; Vector3 c = { 0 }; Vector3 *vertdata = (Vector3 *)mesh.vertices; diff --git a/src/rtext.c b/src/rtext.c index b0f77a767..9c22b0993 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -700,9 +700,9 @@ GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSiz switch (type) { case FONT_DEFAULT: - case FONT_BITMAP: + case FONT_BITMAP: { - glyphs[k].image.data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, cp, + glyphs[k].image.data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, cp, &cpWidth, &cpHeight, &glyphs[k].offsetX, &glyphs[k].offsetY); } break; case FONT_SDF: @@ -1518,7 +1518,7 @@ const char *TextFormat(const char *text, ...) char *currentBuffer = buffers[index]; memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using - + if (text != NULL) { va_list args; @@ -1756,7 +1756,7 @@ char *TextReplace(const char *text, const char *search, const char *replacement) //tempLen -= lastReplacePos; //temp = strncpy(temp, replacement, tempLen - 1) + replaceLen; //tempLen -= replaceLen; - + text += lastReplacePos + searchLen; // Move to next "end of replace" } @@ -2059,7 +2059,7 @@ char *TextToCamel(const char *text) char *LoadUTF8(const int *codepoints, int length) { char *text = NULL; - + if ((codepoints != NULL) && (length > 0)) { // We allocate enough memory to fit all possible codepoints @@ -2096,7 +2096,7 @@ int *LoadCodepoints(const char *text, int *count) { int *codepoints = NULL; int codepointCount = 0; - + if (text != NULL) { int textLength = TextLength(text); @@ -2209,7 +2209,7 @@ int GetCodepoint(const char *text, int *codepointSize) 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ - + int codepoint = 0x3f; // Codepoint (defaults to '?') *codepointSize = 1; if (text == NULL) return codepoint; @@ -2504,7 +2504,7 @@ static Font LoadBMFont(const char *fileName) int charId = 0; int charX = 0; int charY = 0; - int charWidth = 0; + int charWidth = 0; int charHeight = 0; int charOffsetX = 0; int charOffsetY = 0; diff --git a/src/rtextures.c b/src/rtextures.c index 4208b40bd..37c04c439 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -1125,7 +1125,7 @@ Image GenImageCellular(int width, int height, int tileSize) Image GenImageText(int width, int height, const char *text) { Image image = { 0 }; - + int imageSize = width*height; image.width = width; image.height = height; @@ -1487,7 +1487,7 @@ Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Co Image imText = { 0 }; #if defined(SUPPORT_MODULE_RTEXT) if (text == NULL) return imText; - + int textLength = (int)strlen(text); // Get length of text in bytes int textOffsetX = 0; // Image drawing position X int textOffsetY = 0; // Offset between lines (on linebreak '\n') From 5a3391fdce046bc5473e52afbd835dd2dc127146 Mon Sep 17 00:00:00 2001 From: GlitchLens <46534888+oneafter@users.noreply.github.com> Date: Thu, 1 Jan 2026 23:35:12 +0800 Subject: [PATCH 317/430] [rtext] Fix multiple security vulnerabilities in font loading (#5433, #5434, #5436) (#5450) --- src/rtext.c | 51 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/src/rtext.c b/src/rtext.c index 9c22b0993..9801bf698 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -743,8 +743,14 @@ GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSiz stbtt_GetCodepointHMetrics(&fontInfo, cp, &glyphs[k].advanceX, NULL); glyphs[k].advanceX = (int)((float)glyphs[k].advanceX*scaleFactor); + // [Security Fix] Prevent integer overflow/negative allocation + // Issue #5436: Malicious font files may contain negative advanceX, + // causing calloc overflow or crash + if (glyphs[k].advanceX < 0) glyphs[k].advanceX = 0; + Image imSpace = { - .data = RL_CALLOC(glyphs[k].advanceX*fontSize, 2), + // Only allocate memory if width > 0, otherwise set to NULL + .data = (glyphs[k].advanceX > 0) ? RL_CALLOC(glyphs[k].advanceX*fontSize, 2) : NULL, .width = glyphs[k].advanceX, .height = fontSize, .mipmaps = 1, @@ -853,7 +859,8 @@ Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyp } #endif - atlas.data = (unsigned char *)RL_CALLOC(1, atlas.width*atlas.height); // Create a bitmap to store characters (8 bpp) + int atlasDataSize = atlas.width * atlas.height; // Save total size for bounds checking + atlas.data = (unsigned char *)RL_CALLOC(1, atlasDataSize); // Create a bitmap to store characters (8 bpp) atlas.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; atlas.mipmaps = 1; @@ -898,7 +905,17 @@ Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyp { for (int x = 0; x < glyphs[i].image.width; x++) { - ((unsigned char *)atlas.data)[(offsetY + y)*atlas.width + (offsetX + x)] = ((unsigned char *)glyphs[i].image.data)[y*glyphs[i].image.width + x]; + int destX = offsetX + x; + int destY = offsetY + y; + + // Security fix: check both lower and upper bounds + // destX >= 0: prevent heap underflow (#5434) + // destX < atlas.width: prevent heap overflow (#5433) + if (destX >= 0 && destX < atlas.width && destY >= 0 && destY < atlas.height) + { + ((unsigned char *)atlas.data)[destY * atlas.width + destX] = + ((unsigned char *)glyphs[i].image.data)[y * glyphs[i].image.width + x]; + } } } @@ -946,7 +963,15 @@ Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyp { for (int x = 0; x < glyphs[i].image.width; x++) { - ((unsigned char *)atlas.data)[(rects[i].y + padding + y)*atlas.width + (rects[i].x + padding + x)] = ((unsigned char *)glyphs[i].image.data)[y*glyphs[i].image.width + x]; + int destX = rects[i].x + padding + x; + int destY = rects[i].y + padding + y; + + // Security fix: check both lower and upper bounds + if (destX >= 0 && destX < atlas.width && destY >= 0 && destY < atlas.height) + { + ((unsigned char *)atlas.data)[destY * atlas.width + destX] = + ((unsigned char *)glyphs[i].image.data)[y * glyphs[i].image.width + x]; + } } } } @@ -960,14 +985,18 @@ Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyp #if defined(SUPPORT_FONT_ATLAS_WHITE_REC) // Add a 3x3 white rectangle at the bottom-right corner of the generated atlas, - // useful to use as the white texture to draw shapes with raylib, using this rectangle - // shapes and text can be backed into a single draw call: SetShapesTexture() - for (int i = 0, k = atlas.width*atlas.height - 1; i < 3; i++) + // useful to use as the white texture to draw shapes with raylib. + // [Security Fix] Ensure the atlas is large enough to hold a 3x3 rectangle. + // This prevents heap underflow when width < 3 or height < 3 (Fixes #5434 variant) + if (atlas.width >= 3 && atlas.height >= 3) { - ((unsigned char *)atlas.data)[k - 0] = 255; - ((unsigned char *)atlas.data)[k - 1] = 255; - ((unsigned char *)atlas.data)[k - 2] = 255; - k -= atlas.width; + for (int i = 0, k = atlas.width*atlas.height - 1; i < 3; i++) + { + ((unsigned char *)atlas.data)[k - 0] = 255; + ((unsigned char *)atlas.data)[k - 1] = 255; + ((unsigned char *)atlas.data)[k - 2] = 255; + k -= atlas.width; + } } #endif From c07d075a63ec8899884841365265040ff960f9b3 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 1 Jan 2026 16:54:44 +0100 Subject: [PATCH 318/430] REVIEWED: Security checks formatting and comments --- src/rtext.c | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/rtext.c b/src/rtext.c index 9801bf698..9359e3ea3 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -742,21 +742,19 @@ GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSiz { stbtt_GetCodepointHMetrics(&fontInfo, cp, &glyphs[k].advanceX, NULL); glyphs[k].advanceX = (int)((float)glyphs[k].advanceX*scaleFactor); - - // [Security Fix] Prevent integer overflow/negative allocation - // Issue #5436: Malicious font files may contain negative advanceX, - // causing calloc overflow or crash - if (glyphs[k].advanceX < 0) glyphs[k].advanceX = 0; - + Image imSpace = { - // Only allocate memory if width > 0, otherwise set to NULL - .data = (glyphs[k].advanceX > 0) ? RL_CALLOC(glyphs[k].advanceX*fontSize, 2) : NULL, + .data = NULL, .width = glyphs[k].advanceX, .height = fontSize, .mipmaps = 1, .format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE }; + // Only allocate space image if required + if (glyphs[k].advanceX > 0) imSpace.data = RL_CALLOC(glyphs[k].advanceX*fontSize, 1); + else glyphs[k].advanceX = 0; + glyphs[k].image = imSpace; } @@ -859,8 +857,8 @@ Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyp } #endif - int atlasDataSize = atlas.width * atlas.height; // Save total size for bounds checking - atlas.data = (unsigned char *)RL_CALLOC(1, atlasDataSize); // Create a bitmap to store characters (8 bpp) + int atlasDataSize = atlas.width*atlas.height; // Save total size for bounds checking + atlas.data = (unsigned char *)RL_CALLOC(atlasDataSize, 1); // Create a bitmap to store characters (8 bpp) atlas.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; atlas.mipmaps = 1; @@ -908,13 +906,11 @@ Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyp int destX = offsetX + x; int destY = offsetY + y; - // Security fix: check both lower and upper bounds - // destX >= 0: prevent heap underflow (#5434) - // destX < atlas.width: prevent heap overflow (#5433) - if (destX >= 0 && destX < atlas.width && destY >= 0 && destY < atlas.height) + // Security: check both lower and upper bounds + if ((destX >= 0) && (destX < atlas.width) && (destY >= 0) && (destY < atlas.height)) { - ((unsigned char *)atlas.data)[destY * atlas.width + destX] = - ((unsigned char *)glyphs[i].image.data)[y * glyphs[i].image.width + x]; + ((unsigned char *)atlas.data)[destY*atlas.width + destX] = + ((unsigned char *)glyphs[i].image.data)[y*glyphs[i].image.width + x]; } } } @@ -985,10 +981,9 @@ Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyp #if defined(SUPPORT_FONT_ATLAS_WHITE_REC) // Add a 3x3 white rectangle at the bottom-right corner of the generated atlas, - // useful to use as the white texture to draw shapes with raylib. - // [Security Fix] Ensure the atlas is large enough to hold a 3x3 rectangle. - // This prevents heap underflow when width < 3 or height < 3 (Fixes #5434 variant) - if (atlas.width >= 3 && atlas.height >= 3) + // useful to use as the white texture to draw shapes with raylib + // Security: ensure the atlas is large enough to hold a 3x3 rectangle + if ((atlas.width >= 3) && (atlas.height >= 3)) { for (int i = 0, k = atlas.width*atlas.height - 1; i < 3; i++) { From c9a456e273e9fb0581ebce4046ada7afb26a679e Mon Sep 17 00:00:00 2001 From: Jeremiah Donley <108106416+JJLDonley@users.noreply.github.com> Date: Fri, 2 Jan 2026 07:14:25 -0500 Subject: [PATCH 319/430] Add DenoRaylib550 binding to BINDINGS.md (#5462) --- BINDINGS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BINDINGS.md b/BINDINGS.md index 37274b3be..6ef57035f 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -29,6 +29,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [bindbc-raylib3](https://github.com/o3o/bindbc-raylib3) | **5.0** | [D](https://dlang.org) | BSL-1.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.5** | [D](https://dlang.org) | Zlib | +| [DenoRaylib550](https://github.com/JJLDonley/DenoRaylib550) | **5.5** | [Deno](https://deno.land) | MIT | | [rayex](https://github.com/shiryel/rayex) | 3.7 | [elixir](https://elixir-lang.org) | Apache-2.0 | | [raylib-elle](https://github.com/acquitelol/elle/blob/rewrite/std/raylib.le) | **5.5** | [Elle](https://github.com/acquitelol/elle) | GPL-3.0 | | [raylib-factor](https://github.com/factor/factor/blob/master/extra/raylib/raylib.factor) | 4.5 | [Factor](https://factorcode.org) | BSD | From 980e4d0ad3ea5fa32bb53c0de770b3ae3e4db2e2 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Fri, 2 Jan 2026 04:15:25 -0800 Subject: [PATCH 320/430] Use the size of the texture as the V scale so repeatable textures work well (#5463) --- examples/textures/textures_textured_curve.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/textures/textures_textured_curve.c b/examples/textures/textures_textured_curve.c index abf78c88a..f8d207d83 100644 --- a/examples/textures/textures_textured_curve.c +++ b/examples/textures/textures_textured_curve.c @@ -190,7 +190,7 @@ static void DrawTexturedCurve(void) Vector2 normal = Vector2Normalize((Vector2){ -delta.y, delta.x }); // The v texture coordinate of the segment (add up the length of all the segments so far) - float v = previousV + Vector2Length(delta); + float v = previousV + Vector2Length(delta) / (float)(texRoad.height * 2); // Make sure the start point has a normal if (!tangentSet) From 416af51a93772d1736b6dedc0d273a8079cf3381 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 2 Jan 2026 13:40:15 +0100 Subject: [PATCH 321/430] Update year to 2026 --- LICENSE | 2 +- examples/Makefile | 8 +++++--- examples/Makefile.Web | 4 ++-- projects/VSCode/main.c | 2 +- src/Makefile | 2 +- src/config.h | 2 +- src/external/rl_gputex.h | 2 +- src/platforms/rcore_android.c | 2 +- src/platforms/rcore_desktop_glfw.c | 2 +- src/platforms/rcore_desktop_rgfw.c | 2 +- src/platforms/rcore_desktop_sdl.c | 2 +- src/platforms/rcore_desktop_win32.c | 2 +- src/platforms/rcore_drm.c | 2 +- src/platforms/rcore_template.c | 2 +- src/platforms/rcore_web.c | 2 +- src/raudio.c | 4 ++-- src/raylib.h | 2 +- src/raymath.h | 2 +- src/rcamera.h | 2 +- src/rcore.c | 4 ++-- src/rgestures.h | 2 +- src/rglfw.c | 2 +- src/rlgl.h | 2 +- src/rmodels.c | 4 ++-- src/rshapes.c | 2 +- src/rtext.c | 4 ++-- src/rtextures.c | 4 ++-- src/utils.c | 4 ++-- src/utils.h | 2 +- tools/rexm/README.md | 2 +- tools/rexm/rexm.c | 2 +- tools/rlparser/LICENSE | 2 +- tools/rlparser/README.md | 2 +- tools/rlparser/rlparser.c | 4 ++-- 34 files changed, 46 insertions(+), 44 deletions(-) diff --git a/LICENSE b/LICENSE index e96f876a2..bc6f4b851 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) +Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) This software is provided "as-is", without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/examples/Makefile b/examples/Makefile index 9983d8705..b85a448c8 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -30,7 +30,7 @@ # > PLATFORM_ANDROID: # - Android (ARM, ARM64) # -# Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) +# Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) # # This software is provided "as-is", without any express or implied warranty. In no event # will the authors be held liable for any damages arising from the use of this software. @@ -205,10 +205,12 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW) endif endif ifeq ($(TARGET_PLATFORM),PLATFORM_ANDROID) - MAKE = mingw32-make + ifeq ($(PLATFORM_OS),WINDOWS) + MAKE = mingw32-make + endif endif ifeq ($(TARGET_PLATFORM),$(filter $(TARGET_PLATFORM),PLATFORM_WEB PLATFORM_WEB_RGFW)) - ifeq ($(OS),Windows_NT) + ifeq ($(PLATFORM_OS),WINDOWS) MAKE = mingw32-make else EMMAKE != type emmake diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 5718088ac..56d5bd83c 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -30,7 +30,7 @@ # > PLATFORM_ANDROID: # - Android (ARM, ARM64) # -# Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) +# Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) # # This software is provided "as-is", without any express or implied warranty. In no event # will the authors be held liable for any damages arising from the use of this software. @@ -208,7 +208,7 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_ANDROID) MAKE = mingw32-make endif ifeq ($(TARGET_PLATFORM),$(filter $(TARGET_PLATFORM),PLATFORM_WEB PLATFORM_WEB_RGFW)) - ifeq ($(OS),Windows_NT) + ifeq ($(PLATFORM_OS),WINDOWS) MAKE = mingw32-make else EMMAKE != type emmake diff --git a/projects/VSCode/main.c b/projects/VSCode/main.c index ea394de58..7a5d89000 100644 --- a/projects/VSCode/main.c +++ b/projects/VSCode/main.c @@ -15,7 +15,7 @@ * This example has been created using raylib 1.0 (www.raylib.com) * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) * -* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) * ********************************************************************************************/ diff --git a/src/Makefile b/src/Makefile index bc84abece..89dd759aa 100644 --- a/src/Makefile +++ b/src/Makefile @@ -33,7 +33,7 @@ # Many thanks to Milan Nikolic (@gen2brain) for implementing Android platform pipeline. # Many thanks to Emanuele Petriglia for his contribution on GNU/Linux pipeline. # -# Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) +# Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) # # This software is provided "as-is", without any express or implied warranty. In no event # will the authors be held liable for any damages arising from the use of this software. diff --git a/src/config.h b/src/config.h index 9f54edd23..9a1d22de3 100644 --- a/src/config.h +++ b/src/config.h @@ -6,7 +6,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2018-2025 Ahmad Fatoum & Ramon Santamaria (@raysan5) +* Copyright (c) 2018-2026 Ahmad Fatoum and Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/external/rl_gputex.h b/src/external/rl_gputex.h index 9c1092695..033045bc8 100644 --- a/src/external/rl_gputex.h +++ b/src/external/rl_gputex.h @@ -62,7 +62,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 4f36f0a5b..6d3d68f24 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -27,7 +27,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) and contributors +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 24b6d8d18..20b7449b8 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -30,7 +30,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) and contributors +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index b906fd103..05960c14b 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -29,7 +29,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5), Colleague Riley and contributors +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5), Colleague Riley and contributors * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index cc3f7c6ae..0279c0c28 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -29,7 +29,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) and contributors +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 8d9bc6c8b..7ef01a1a0 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -26,7 +26,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) and contributors +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 8db332b06..eedb915e2 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -29,7 +29,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) and contributors +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/platforms/rcore_template.c b/src/platforms/rcore_template.c index b22d3f2f5..87cd2e21e 100644 --- a/src/platforms/rcore_template.c +++ b/src/platforms/rcore_template.c @@ -27,7 +27,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) and contributors +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index 244a53dd7..0056849dd 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -26,7 +26,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) and contributors +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/raudio.c b/src/raudio.c index e022447d7..98326727f 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -50,7 +50,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -1134,7 +1134,7 @@ bool ExportWaveAsCode(Wave wave, const char *fileName) byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); - byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2025 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2026 Ramon Santamaria (@raysan5) //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); byteCount += sprintf(txtData + byteCount, "//////////////////////////////////////////////////////////////////////////////////\n\n"); diff --git a/src/raylib.h b/src/raylib.h index ba80e40c7..2d411f896 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -62,7 +62,7 @@ * raylib is 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) 2013-2025 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/raymath.h b/src/raymath.h index 8d5b1b2a9..6ab5e2b4c 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -37,7 +37,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2015-2025 Ramon Santamaria (@raysan5) +* Copyright (c) 2015-2026 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/rcamera.h b/src/rcamera.h index 72552ec15..82f14fecd 100644 --- a/src/rcamera.h +++ b/src/rcamera.h @@ -20,7 +20,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2022-2025 Christoph Wagner (@Crydsch) & Ramon Santamaria (@raysan5) +* Copyright (c) 2022-2026 Christoph Wagner (@Crydsch) and Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/rcore.c b/src/rcore.c index 38bee25bf..e16c6412a 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -70,7 +70,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) and contributors +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) and contributors * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -3253,7 +3253,7 @@ bool ExportAutomationEventList(AutomationEventList list, const char *fileName) byteCount += sprintf(txtData + byteCount, "# more info and bugs-report: github.com/raysan5/raylib\n"); byteCount += sprintf(txtData + byteCount, "# feedback and support: ray[at]raylib.com\n"); byteCount += sprintf(txtData + byteCount, "#\n"); - byteCount += sprintf(txtData + byteCount, "# Copyright (c) 2023-2025 Ramon Santamaria (@raysan5)\n"); + byteCount += sprintf(txtData + byteCount, "# Copyright (c) 2023-2026 Ramon Santamaria (@raysan5)\n"); byteCount += sprintf(txtData + byteCount, "#\n\n"); // Add events data diff --git a/src/rgestures.h b/src/rgestures.h index f601a4790..e6cb86300 100644 --- a/src/rgestures.h +++ b/src/rgestures.h @@ -21,7 +21,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2025 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2026 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/rglfw.c b/src/rglfw.c index b167955bc..53399aa13 100644 --- a/src/rglfw.c +++ b/src/rglfw.c @@ -7,7 +7,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2017-2025 Ramon Santamaria (@raysan5) +* Copyright (c) 2017-2026 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/rlgl.h b/src/rlgl.h index b10942d88..ab85569bb 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -89,7 +89,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2025 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2026 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/rmodels.c b/src/rmodels.c index da26a15f1..3ee429900 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -21,7 +21,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -1987,7 +1987,7 @@ bool ExportMesh(Mesh mesh, const char *fileName) byteCount += sprintf(txtData + byteCount, "# // more info and bugs-report: github.com/raysan5/raylib //\n"); byteCount += sprintf(txtData + byteCount, "# // feedback and support: ray[at]raylib.com //\n"); byteCount += sprintf(txtData + byteCount, "# // //\n"); - byteCount += sprintf(txtData + byteCount, "# // Copyright (c) 2018-2025 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "# // Copyright (c) 2018-2026 Ramon Santamaria (@raysan5) //\n"); byteCount += sprintf(txtData + byteCount, "# // //\n"); byteCount += sprintf(txtData + byteCount, "# //////////////////////////////////////////////////////////////////////////////////\n\n"); byteCount += sprintf(txtData + byteCount, "# Vertex Count: %i\n", mesh.vertexCount); diff --git a/src/rshapes.c b/src/rshapes.c index 528a362d5..e828b98bc 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -25,7 +25,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/rtext.c b/src/rtext.c index 9359e3ea3..1fd9a306d 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -34,7 +34,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -1066,7 +1066,7 @@ bool ExportFontAsCode(Font font, const char *fileName) byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); - byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2025 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2026 Ramon Santamaria (@raysan5) //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); byteCount += sprintf(txtData + byteCount, "// ---------------------------------------------------------------------------------- //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); diff --git a/src/rtextures.c b/src/rtextures.c index 37c04c439..59000940e 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -42,7 +42,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -765,7 +765,7 @@ bool ExportImageAsCode(Image image, const char *fileName) byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); - byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2025 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2026 Ramon Santamaria (@raysan5) //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); diff --git a/src/utils.c b/src/utils.c index 09158893a..82d7d0aa2 100644 --- a/src/utils.c +++ b/src/utils.c @@ -10,7 +10,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2025 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2026 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -307,7 +307,7 @@ bool ExportDataAsCode(const unsigned char *data, int dataSize, const char *fileN byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); - byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2022-2025 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2022-2026 Ramon Santamaria (@raysan5) //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); diff --git a/src/utils.h b/src/utils.h index 271d0d2c7..7d79c2188 100644 --- a/src/utils.h +++ b/src/utils.h @@ -5,7 +5,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2025 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2026 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/tools/rexm/README.md b/tools/rexm/README.md index 232ef51d6..14704b5ea 100644 --- a/tools/rexm/README.md +++ b/tools/rexm/README.md @@ -102,4 +102,4 @@ char *TextReplaceBetween(const char *text, const char *begin, const char *end, c `rexm` is an **open source** project, licensed under an unmodified [zlib/libpng license](LICENSE) -*Copyright (c) 2025 Ramon Santamaria ([@raysan5](https://github.com/raysan5))* +*Copyright (c) 2025-2026 Ramon Santamaria ([@raysan5](https://github.com/raysan5))* diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 36152e560..41aa7a85c 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -2840,7 +2840,7 @@ static void UpdateSourceMetadata(const char *exSrcPath, const rlExampleInfo *inf if (exTextUpdated[2] != NULL) exTextUpdatedPtr = exTextUpdated[2]; // Update copyright message - // String: "* Copyright (c) 2019-2025 Contributor Name (@github_user) and Ramon Santamaria (@raysan5)" + // String: "* Copyright (c) 2019-2026 Contributor Name (@github_user) and Ramon Santamaria (@raysan5)" if (info->yearCreated == info->yearReviewed) { exTextUpdated[3] = TextReplaceBetween(exTextUpdatedPtr, "Copyright (c) ", ")", diff --git a/tools/rlparser/LICENSE b/tools/rlparser/LICENSE index 7ed4b8722..4ce3bbb8d 100644 --- a/tools/rlparser/LICENSE +++ b/tools/rlparser/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2021-2025 Ramon Santamaria (@raysan5) +Copyright (c) 2021-2026 Ramon Santamaria (@raysan5) This software is provided "as-is", without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/tools/rlparser/README.md b/tools/rlparser/README.md index 0e4f9b739..cf009a891 100644 --- a/tools/rlparser/README.md +++ b/tools/rlparser/README.md @@ -19,7 +19,7 @@ Check `rlparser.c` for details about those structs. // // // more info and bugs-report: github.com/raysan5/raylib/tools/rlparser // // // -// Copyright (c) 2021-2025 Ramon Santamaria (@raysan5) // +// Copyright (c) 2021-2026 Ramon Santamaria (@raysan5) // // // ////////////////////////////////////////////////////////////////////////////////// diff --git a/tools/rlparser/rlparser.c b/tools/rlparser/rlparser.c index d5b03fa01..c291c3038 100644 --- a/tools/rlparser/rlparser.c +++ b/tools/rlparser/rlparser.c @@ -52,7 +52,7 @@ raylib-parser is 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) 2021-2025 Ramon Santamaria (@raysan5) + Copyright (c) 2021-2026 Ramon Santamaria (@raysan5) **********************************************************************************************/ @@ -1084,7 +1084,7 @@ static void ShowCommandLineInfo(void) printf("// //\n"); printf("// more info and bugs-report: github.com/raysan5/raylib/tools/rlparser //\n"); printf("// //\n"); - printf("// Copyright (c) 2021-2025 Ramon Santamaria (@raysan5) //\n"); + printf("// Copyright (c) 2021-2026 Ramon Santamaria (@raysan5) //\n"); printf("// //\n"); printf("//////////////////////////////////////////////////////////////////////////////////\n\n"); From ca89934ed5af9161f781a9b35b808b765dc40f3a Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 2 Jan 2026 13:53:20 +0100 Subject: [PATCH 322/430] Update year to 2026 --- src/platforms/rcore_memory.c | 2 +- src/platforms/rcore_web_emscripten.c | 2 +- tools/rexm/rexm.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platforms/rcore_memory.c b/src/platforms/rcore_memory.c index fda3fe774..04d343164 100644 --- a/src/platforms/rcore_memory.c +++ b/src/platforms/rcore_memory.c @@ -27,7 +27,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2025 Ramon Santamaria (@raysan5) and contributors +* Copyright (c) 2025-2026 Ramon Santamaria (@raysan5) and contributors * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/src/platforms/rcore_web_emscripten.c b/src/platforms/rcore_web_emscripten.c index 71dcdc2e7..28d530e97 100644 --- a/src/platforms/rcore_web_emscripten.c +++ b/src/platforms/rcore_web_emscripten.c @@ -25,7 +25,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2025 Ramon Santamaria (@raysan5) and contributors +* Copyright (c) 2025-2026 Ramon Santamaria (@raysan5) and contributors * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 41aa7a85c..d3ff18289 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -30,7 +30,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2025 Ramon Santamaria (@raysan5) +* Copyright (c) 2025-2026 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -1881,7 +1881,7 @@ int main(int argc, char *argv[]) printf("// rexm [raylib examples manager] - A simple command-line tool to manage raylib examples //\n"); printf("// powered by raylib v5.6-dev //\n"); printf("// //\n"); - printf("// Copyright (c) 2025 Ramon Santamaria (@raysan5) //\n"); + printf("// Copyright (c) 2025-2026 Ramon Santamaria (@raysan5) //\n"); printf("// //\n"); printf("////////////////////////////////////////////////////////////////////////////////////////////\n\n"); From 942f93db55105495fd39acfc0666997b4efb4c98 Mon Sep 17 00:00:00 2001 From: sleeptightAnsiC <91839286+sleeptightAnsiC@users.noreply.github.com> Date: Fri, 2 Jan 2026 18:36:22 +0100 Subject: [PATCH 323/430] fix(build): do not use != assignment in Makefiles (#5464) GNU Make 3.81 that ships with MacOSX does not understand '!= ...' assignment so we use ':= $(shell ...)' instead which have the same behavior here. Additionally, I have changed the use of 'type' to 'command -v' because assigning the result of 'type' to variable named 'EMMAKE' does not make much sense. I also reused this variable. For more detailed information read the linked issue. Fixes: https://github.com/raysan5/raylib/issues/5460 --- examples/Makefile | 4 ++-- examples/Makefile.Web | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/Makefile b/examples/Makefile index b85a448c8..3cbc2ffc1 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -213,9 +213,9 @@ ifeq ($(TARGET_PLATFORM),$(filter $(TARGET_PLATFORM),PLATFORM_WEB PLATFORM_WEB_R ifeq ($(PLATFORM_OS),WINDOWS) MAKE = mingw32-make else - EMMAKE != type emmake + EMMAKE := $(shell command -v emmake) ifneq (, $(EMMAKE)) - MAKE = emmake make + MAKE = $(EMMAKE) make else MAKE = mingw32-make endif diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 56d5bd83c..fe4de1330 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -211,9 +211,9 @@ ifeq ($(TARGET_PLATFORM),$(filter $(TARGET_PLATFORM),PLATFORM_WEB PLATFORM_WEB_R ifeq ($(PLATFORM_OS),WINDOWS) MAKE = mingw32-make else - EMMAKE != type emmake + EMMAKE := $(shell command -v emmake) ifneq (, $(EMMAKE)) - MAKE = emmake make + MAKE = $(EMMAKE) make else MAKE = mingw32-make endif From c92de5f108850b91f140a932ca25aa3508336d58 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 2 Jan 2026 18:43:28 +0100 Subject: [PATCH 324/430] REVIEWED: Comments about intrinsics support #5316 --- src/raymath.h | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/raymath.h b/src/raymath.h index 6ab5e2b4c..57e3dac51 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -177,26 +177,24 @@ typedef struct float16 { #if defined(RAYMATH_USE_SIMD_INTRINSICS) // SIMD is used on the most costly raymath function MatrixMultiply() // NOTE: Only SSE intrinsics support implemented - // TODO: Consider support for other SIMD instrinsics + // TODO: Consider support for other SIMD instrinsics: + // - SSEx, AVX, AVX2, FMA, NEON, RVV /* #if defined(__SSE4_2__) - #define SW_HAS_SSE42 #include + #define RAYMATH_SSE42_ENABLED #elif defined(__SSE4_1__) - #define SW_HAS_SSE41 #include + #define RAYMATH_SSE41_ENABLED #elif defined(__SSSE3__) - #define SW_HAS_SSSE3 #include + #define RAYMATH_SSSE3_ENABLED #elif defined(__SSE3__) - #define SW_HAS_SSE3 #include + #define RAYMATH_SSE3_ENABLED #elif defined(__SSE2__) || (defined(_M_AMD64) || defined(_M_X64)) // SSE2 x64 - #define SW_HAS_SSE2 #include - #elif defined(__SSE__) - #define SW_HAS_SSE - #include + #define RAYMATH_SSE2_ENABLED #endif */ #if defined(__SSE__) || defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1)) From f67e70bb4763635740cf5a3f7e334f7e0a1f2854 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Fri, 2 Jan 2026 23:59:34 -0800 Subject: [PATCH 325/430] Fix typecast warnings in rcore (#5466) --- src/platforms/rcore_desktop_glfw.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 20b7449b8..84b021573 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -227,8 +227,8 @@ void ToggleFullscreen(void) if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { Vector2 scaleDpi = GetWindowScaleDPI(); - CORE.Window.screen.width *= scaleDpi.x; - CORE.Window.screen.height *= scaleDpi.y; + CORE.Window.screen.width = (unsigned int)(CORE.Window.screen.width * scaleDpi.x); + CORE.Window.screen.height = (unsigned int)(CORE.Window.screen.height * scaleDpi.y); } #endif @@ -306,8 +306,8 @@ void ToggleBorderlessWindowed(void) if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { Vector2 scaleDpi = GetWindowScaleDPI(); - CORE.Window.screen.width *= scaleDpi.x; - CORE.Window.screen.height *= scaleDpi.y; + CORE.Window.screen.width = (unsigned int)(CORE.Window.screen.width * scaleDpi.x); + CORE.Window.screen.height = (unsigned int)(CORE.Window.screen.height * scaleDpi.y); } #endif From b00cbdaf49a7140db890d4c5621c422c83872eab Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Sat, 3 Jan 2026 13:38:51 -0800 Subject: [PATCH 326/430] Cleanup warnings in examples (#5467) --- examples/audio/audio_music_stream.c | 4 +- examples/core/core_input_gamepad.c | 4 +- examples/core/core_viewport_scaling.c | 12 +++--- examples/models/models_decals.c | 39 ++++++++++--------- examples/shapes/shapes_ball_physics.c | 16 ++++---- examples/shapes/shapes_kaleidoscope.c | 6 +-- examples/shapes/shapes_penrose_tile.c | 10 ++--- examples/text/text_inline_styling.c | 6 +-- examples/text/text_strings_management.c | 16 ++++---- examples/textures/textures_screen_buffer.c | 2 +- .../examples/shapes_hilbert_curve.vcxproj | 16 ++++---- .../examples/shapes_penrose_tile.vcxproj | 16 ++++---- .../examples/shapes_rlgl_color_wheel.vcxproj | 16 ++++---- 13 files changed, 83 insertions(+), 80 deletions(-) diff --git a/examples/audio/audio_music_stream.c b/examples/audio/audio_music_stream.c index 05ec1c2d6..6e1dfc8c5 100644 --- a/examples/audio/audio_music_stream.c +++ b/examples/audio/audio_music_stream.c @@ -113,7 +113,7 @@ int main(void) DrawText("LEFT-RIGHT for PAN CONTROL", 320, 74, 10, DARKBLUE); DrawRectangle(300, 100, 200, 12, LIGHTGRAY); DrawRectangleLines(300, 100, 200, 12, GRAY); - DrawRectangle(300 + (pan + 1.0)/2.0f*200 - 5, 92, 10, 28, DARKGRAY); + DrawRectangle((int)(300 + (pan + 1.0f)/2.0f*200 - 5), 92, 10, 28, DARKGRAY); DrawRectangle(200, 200, 400, 12, LIGHTGRAY); DrawRectangle(200, 200, (int)(timePlayed*400.0f), 12, MAROON); @@ -125,7 +125,7 @@ int main(void) DrawText("UP-DOWN for VOLUME CONTROL", 320, 334, 10, DARKGREEN); DrawRectangle(300, 360, 200, 12, LIGHTGRAY); DrawRectangleLines(300, 360, 200, 12, GRAY); - DrawRectangle(300 + volume*200 - 5, 352, 10, 28, DARKGRAY); + DrawRectangle((int)(300 + volume*200 - 5), 352, 10, 28, DARKGRAY); EndDrawing(); //---------------------------------------------------------------------------------- diff --git a/examples/core/core_input_gamepad.c b/examples/core/core_input_gamepad.c index 3c9454318..a9e0660e0 100644 --- a/examples/core/core_input_gamepad.c +++ b/examples/core/core_input_gamepad.c @@ -67,7 +67,7 @@ int main(void) if (IsKeyPressed(KEY_RIGHT)) gamepad++; Vector2 mousePosition = GetMousePosition(); - vibrateButton = (Rectangle){ 10, 70 + 20*GetGamepadAxisCount(gamepad) + 20, 75, 24 }; + vibrateButton = (Rectangle){ 10, 70.0f + 20*GetGamepadAxisCount(gamepad) + 20, 75, 24 }; if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && CheckCollisionPointRec(mousePosition, vibrateButton)) SetGamepadVibration(gamepad, 1.0, 1.0, 1.0); //---------------------------------------------------------------------------------- @@ -262,7 +262,7 @@ int main(void) // Draw vibrate button DrawRectangleRec(vibrateButton, SKYBLUE); - DrawText("VIBRATE", vibrateButton.x + 14, vibrateButton.y + 1, 10, DARKGRAY); + DrawText("VIBRATE", (int)(vibrateButton.x + 14), (int)(vibrateButton.y + 1), 10, DARKGRAY); if (GetGamepadButtonPressed() != GAMEPAD_BUTTON_UNKNOWN) DrawText(TextFormat("DETECTED BUTTON: %i", GetGamepadButtonPressed()), 10, 430, 10, RED); else DrawText("DETECTED BUTTON: NONE", 10, 430, 10, GRAY); diff --git a/examples/core/core_viewport_scaling.c b/examples/core/core_viewport_scaling.c index adcd51ea3..6ff5ac9c4 100644 --- a/examples/core/core_viewport_scaling.c +++ b/examples/core/core_viewport_scaling.c @@ -112,16 +112,16 @@ int main(void) if (CheckCollisionPointRec(mousePosition, decreaseResolutionButton) && mousePressed) { resolutionIndex = (resolutionIndex + RESOLUTION_COUNT - 1)%RESOLUTION_COUNT; - gameWidth = resolutionList[resolutionIndex].x; - gameHeight = resolutionList[resolutionIndex].y; + gameWidth = (int)resolutionList[resolutionIndex].x; + gameHeight = (int)resolutionList[resolutionIndex].y; ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target); } if (CheckCollisionPointRec(mousePosition, increaseResolutionButton) && mousePressed) { resolutionIndex = (resolutionIndex + 1)%RESOLUTION_COUNT; - gameWidth = resolutionList[resolutionIndex].x; - gameHeight = resolutionList[resolutionIndex].y; + gameWidth = (int)resolutionList[resolutionIndex].x; + gameHeight = (int)resolutionList[resolutionIndex].y; ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target); } @@ -145,7 +145,7 @@ int main(void) // Draw our scene to the render texture BeginTextureMode(target); ClearBackground(WHITE); - DrawCircle(textureMousePosition.x, textureMousePosition.y, 20.0f, LIME); + DrawCircleV(textureMousePosition, 20.0f, LIME); EndTextureMode(); // Draw render texture to main framebuffer @@ -159,7 +159,7 @@ int main(void) // Draw info box Rectangle infoRect = (Rectangle){5, 5, 330, 105}; DrawRectangleRec(infoRect, Fade(LIGHTGRAY, 0.7f)); - DrawRectangleLines(infoRect.x, infoRect.y, infoRect.width, infoRect.height, BLUE); + DrawRectangleLinesEx(infoRect, 1, BLUE); DrawText(TextFormat("Window Resolution: %d x %d", screenWidth, screenHeight), 15, 15, 10, BLACK); DrawText(TextFormat("Game Resolution: %d x %d", gameWidth, gameHeight), 15, 30, 10, BLACK); diff --git a/examples/models/models_decals.c b/examples/models/models_decals.c index f556139e1..f35794daa 100644 --- a/examples/models/models_decals.c +++ b/examples/models/models_decals.c @@ -45,7 +45,10 @@ static void FreeMeshBuilder(MeshBuilder *mb); static Mesh BuildMesh(MeshBuilder *mb); static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, float decalOffset); static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s); -#define FreeDecalMeshData() GenMeshDecal((Model){ .meshCount = -1.0f }, (Matrix){ 0 }, 0.0f, 0.0f) +inline void FreeDecalMeshData() +{ + GenMeshDecal((Model) { .meshCount = -1 }, (Matrix) { 0 }, 0.0f, 0.0f); +} static bool GuiButton(Rectangle rec, const char *label); //------------------------------------------------------------------------------------ @@ -198,12 +201,12 @@ int main(void) EndMode3D(); float yPos = 10; - float x0 = GetScreenWidth() - 300; + float x0 = GetScreenWidth() - 300.0f; float x1 = x0 + 100; float x2 = x1 + 100; - DrawText("Vertices", x1, yPos, 10, LIME); - DrawText("Triangles", x2, yPos, 10, LIME); + DrawText("Vertices", (int)x1, (int)yPos, 10, LIME); + DrawText("Triangles", (int)x2, (int)yPos, 10, LIME); yPos += 15; int vertexCount = 0; @@ -215,24 +218,24 @@ int main(void) triangleCount += model.meshes[i].triangleCount; } - DrawText("Main model", x0, yPos, 10, LIME); - DrawText(TextFormat("%d", vertexCount), x1, yPos, 10, LIME); - DrawText(TextFormat("%d", triangleCount), x2, yPos, 10, LIME); + DrawText("Main model", (int)x0, (int)yPos, 10, LIME); + DrawText(TextFormat("%d", vertexCount), (int)x1, (int)yPos, 10, LIME); + DrawText(TextFormat("%d", triangleCount), (int)x2, (int)yPos, 10, LIME); yPos += 15; for (int i = 0; i < decalCount; i++) { if (i == 20) { - DrawText("...", x0, yPos, 10, LIME); + DrawText("...", (int)x0, (int)yPos, 10, LIME); yPos += 15; } if (i < 20) { - DrawText(TextFormat("Decal #%d", i+1), x0, yPos, 10, LIME); - DrawText(TextFormat("%d", decalModels[i].meshes[0].vertexCount), x1, yPos, 10, LIME); - DrawText(TextFormat("%d", decalModels[i].meshes[0].triangleCount), x2, yPos, 10, LIME); + DrawText(TextFormat("Decal #%d", i+1), (int)x0, (int)yPos, 10, LIME); + DrawText(TextFormat("%d", decalModels[i].meshes[0].vertexCount), (int)x1, (int)yPos, 10, LIME); + DrawText(TextFormat("%d", decalModels[i].meshes[0].triangleCount), (int)x2, (int)yPos, 10, LIME); yPos += 15; } @@ -240,18 +243,18 @@ int main(void) triangleCount += decalModels[i].meshes[0].triangleCount; } - DrawText("TOTAL", x0, yPos, 10, LIME); - DrawText(TextFormat("%d", vertexCount), x1, yPos, 10, LIME); - DrawText(TextFormat("%d", triangleCount), x2, yPos, 10, LIME); + DrawText("TOTAL", (int)x0, (int)yPos, 10, LIME); + DrawText(TextFormat("%d", vertexCount), (int)x1, (int)yPos, 10, LIME); + DrawText(TextFormat("%d", triangleCount), (int)x2, (int)yPos, 10, LIME); yPos += 15; DrawText("Hold RMB to move camera", 10, 430, 10, GRAY); DrawText("(c) Character model and texture from kenney.nl", screenWidth - 260, screenHeight - 20, 10, GRAY); // UI elements - if (GuiButton((Rectangle){ 10, screenHeight - 100, 100, 60 }, showModel ? "Hide Model" : "Show Model")) showModel = !showModel; + if (GuiButton((Rectangle){ 10, screenHeight - 1000.f, 100, 60 }, showModel ? "Hide Model" : "Show Model")) showModel = !showModel; - if (GuiButton((Rectangle){ 10 + 110, screenHeight - 100, 100, 60 }, "Clear Decals")) + if (GuiButton((Rectangle){ 10 + 110, screenHeight - 100.0f, 100, 60 }, "Clear Decals")) { // Clear decals, unload all decal models for (int i = 0; i < decalCount; i++) UnloadModel(decalModels[i]); @@ -596,8 +599,8 @@ static bool GuiButton(Rectangle rec, const char *label) DrawRectangleRec(rec, bgColor); DrawRectangleLinesEx(rec, 2.0f, DARKGRAY); - float fontSize = 10.0f; - float textWidth = MeasureText(label, fontSize); + int fontSize = 10; + int textWidth = MeasureText(label, fontSize); DrawText(label, (int)(rec.x + rec.width*0.5f - textWidth*0.5f), (int)(rec.y + rec.height*0.5f - fontSize*0.5f), fontSize, DARKGRAY); diff --git a/examples/shapes/shapes_ball_physics.c b/examples/shapes/shapes_ball_physics.c index f9b620d28..0c98ccf9d 100644 --- a/examples/shapes/shapes_ball_physics.c +++ b/examples/shapes/shapes_ball_physics.c @@ -46,12 +46,12 @@ int main(void) InitWindow(screenWidth, screenHeight, "raylib [shapes] example - ball physics"); Ball balls[MAX_BALLS] = {{ - .pos = { GetScreenWidth()/2, GetScreenHeight()/2 }, + .pos = { GetScreenWidth()/2.0f, GetScreenHeight()/2.0f }, .vel = { 200, 200 }, .ppos = { 0 }, .radius = 40, - .friction = 0.99, - .elasticity = 0.9, + .friction = 0.99f, + .elasticity = 0.9f, .color = BLUE, .grabbed = false }}; @@ -110,11 +110,11 @@ int main(void) { balls[ballCount++] = (Ball){ .pos = mousePos, - .vel = { GetRandomValue(-300, 300), GetRandomValue(-300, 300) }, + .vel = { (float)GetRandomValue(-300, 300), (float)GetRandomValue(-300, 300) }, .ppos = { 0 }, - .radius = 20 + GetRandomValue(0, 30), - .friction = 0.99, - .elasticity = 0.9, + .radius = 20.0f + (float)GetRandomValue(0, 30), + .friction = 0.99f, + .elasticity = 0.9f, .color = { GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255 }, .grabbed = false }; @@ -126,7 +126,7 @@ int main(void) { for (int i = 0; i < ballCount; i++) { - if (!balls[i].grabbed) balls[i].vel = (Vector2){ GetRandomValue(-2000, 2000), GetRandomValue(-2000, 2000) }; + if (!balls[i].grabbed) balls[i].vel = (Vector2){ (float)GetRandomValue(-2000, 2000), (float)GetRandomValue(-2000, 2000) }; } } diff --git a/examples/shapes/shapes_kaleidoscope.c b/examples/shapes/shapes_kaleidoscope.c index 119fca598..31129a54c 100644 --- a/examples/shapes/shapes_kaleidoscope.c +++ b/examples/shapes/shapes_kaleidoscope.c @@ -50,9 +50,9 @@ int main(void) int symmetry = 6; float angle = 360.0f/(float)symmetry; float thickness = 3.0f; - Rectangle resetButtonRec = { screenWidth - 55, 5, 50, 25 }; - Rectangle backButtonRec = { screenWidth - 55, screenHeight - 30, 25, 25 }; - Rectangle nextButtonRec = { screenWidth - 30, screenHeight - 30, 25, 25 }; + Rectangle resetButtonRec = { screenWidth - 55.0f, 5.0f, 50, 25 }; + Rectangle backButtonRec = { screenWidth - 55.0f, screenHeight - 30.0f, 25, 25 }; + Rectangle nextButtonRec = { screenWidth - 30.0f, screenHeight - 30.0f, 25, 25 }; Vector2 mousePos = { 0 }; Vector2 prevMousePos = { 0 }; Vector2 scaleVector = { 1.0f, -1.0f }; diff --git a/examples/shapes/shapes_penrose_tile.c b/examples/shapes/shapes_penrose_tile.c index 304dca3cc..cf41852f8 100644 --- a/examples/shapes/shapes_penrose_tile.c +++ b/examples/shapes/shapes_penrose_tile.c @@ -185,12 +185,12 @@ static void BuildProductionStep(PenroseLSystem *ls) char *newProduction = (char *)RL_MALLOC(sizeof(char)*STR_MAX_SIZE); newProduction[0] = '\0'; - int productionLength = strnlen(ls->production, STR_MAX_SIZE); + int productionLength = (int)strnlen(ls->production, STR_MAX_SIZE); for (int i = 0; i < productionLength; i++) { char step = ls->production[i]; - int remainingSpace = STR_MAX_SIZE - strnlen(newProduction, STR_MAX_SIZE) - 1; + int remainingSpace = STR_MAX_SIZE - (int)strnlen(newProduction, STR_MAX_SIZE) - 1; switch (step) { case 'W': strncat(newProduction, ls->ruleW, remainingSpace); break; @@ -201,7 +201,7 @@ static void BuildProductionStep(PenroseLSystem *ls) { if (step != 'F') { - int t = strnlen(newProduction, STR_MAX_SIZE); + int t = (int)strnlen(newProduction, STR_MAX_SIZE); newProduction[t] = step; newProduction[t + 1] = '\0'; } @@ -218,7 +218,7 @@ static void BuildProductionStep(PenroseLSystem *ls) // Draw penrose tile lines static void DrawPenroseLSystem(PenroseLSystem *ls) { - Vector2 screenCenter = { GetScreenWidth()/2, GetScreenHeight()/2 }; + Vector2 screenCenter = { GetScreenWidth()/2.0f, GetScreenHeight()/2.0f }; TurtleState turtle = { .origin = { 0 }, @@ -245,7 +245,7 @@ static void DrawPenroseLSystem(PenroseLSystem *ls) Vector2 startPosScreen = { startPosWorld.x + screenCenter.x, startPosWorld.y + screenCenter.y }; Vector2 endPosScreen = { turtle.origin.x + screenCenter.x, turtle.origin.y + screenCenter.y }; - DrawLineEx(startPosScreen, endPosScreen, 2, Fade(BLACK, 0.2)); + DrawLineEx(startPosScreen, endPosScreen, 2, Fade(BLACK, 0.2f)); } repeats = 1; diff --git a/examples/text/text_inline_styling.c b/examples/text/text_inline_styling.c index 8faef30eb..81f8156b6 100644 --- a/examples/text/text_inline_styling.c +++ b/examples/text/text_inline_styling.c @@ -178,14 +178,14 @@ static void DrawTextStyled(Font font, const char *text, Vector2 position, float // Convert hex color text into actual Color unsigned int colHexValue = strtoul(colHexText, NULL, 16); if (text[i - 1] == 'c') - { + { colFront = GetColor(colHexValue); - colFront.a *= (float)color.a/255.0f; + colFront.a = (unsigned char)(colFront.a * (float)color.a/255.0f); } else if (text[i - 1] == 'b') { colBack = GetColor(colHexValue); - colBack.a *= (float)color.a/255.0f; + colBack.a *= (unsigned char)(colFront.a * (float)color.a / 255.0f); } i += (colHexCount + 1); // Skip color value retrieved and ']' diff --git a/examples/text/text_strings_management.c b/examples/text/text_strings_management.c index d2e349279..e4a7ab2af 100644 --- a/examples/text/text_strings_management.c +++ b/examples/text/text_strings_management.c @@ -133,7 +133,7 @@ int main(void) { for (int i = 0; i < particleCount; i++) { - if (!textParticles[i].grabbed) textParticles[i].vel = (Vector2){ GetRandomValue(-2000, 2000), GetRandomValue(-2000, 2000) }; + if (!textParticles[i].grabbed) textParticles[i].vel = (Vector2){ (float)GetRandomValue(-2000, 2000), (float)GetRandomValue(-2000, 2000) }; } } @@ -233,9 +233,9 @@ int main(void) for (int i = 0; i < particleCount; i++) { TextParticle *tp = &textParticles[i]; - DrawRectangle(tp->rect.x-tp->borderWidth, tp->rect.y-tp->borderWidth, tp->rect.width+tp->borderWidth*2, tp->rect.height+tp->borderWidth*2, BLACK); + DrawRectangleRec((Rectangle) { tp->rect.x - tp->borderWidth, tp->rect.y - tp->borderWidth, tp->rect.width + tp->borderWidth * 2, tp->rect.height + tp->borderWidth * 2 }, BLACK); DrawRectangleRec(tp->rect, tp->color); - DrawText(tp->text, tp->rect.x+tp->padding, tp->rect.y+tp->padding, FONT_SIZE, BLACK); + DrawText(tp->text, (int)(tp->rect.x+tp->padding), (int)(tp->rect.y+tp->padding), FONT_SIZE, BLACK); } DrawText("grab a text particle by pressing with the mouse and throw it by releasing", 10, 10, 10, DARKGRAY); @@ -265,8 +265,8 @@ void PrepareFirstTextParticle(const char* text, TextParticle *tps, int *particle { tps[0] = CreateTextParticle( text, - GetScreenWidth()/2, - GetScreenHeight()/2, + GetScreenWidth()/2.0f, + GetScreenHeight()/2.0f, RAYWHITE ); *particleCount = 1; @@ -277,12 +277,12 @@ TextParticle CreateTextParticle(const char *text, float x, float y, Color color) TextParticle tp = { .text = "", .rect = { x, y, 30, 30 }, - .vel = { GetRandomValue(-200, 200), GetRandomValue(-200, 200) }, + .vel = { (float)GetRandomValue(-200, 200), (float)GetRandomValue(-200, 200) }, .ppos = { 0 }, .padding = 5.0f, .borderWidth = 5.0f, - .friction = 0.99, - .elasticity = 0.9, + .friction = 0.99f, + .elasticity = 0.9f, .color = color, .grabbed = false }; diff --git a/examples/textures/textures_screen_buffer.c b/examples/textures/textures_screen_buffer.c index e620aab31..503b8d249 100644 --- a/examples/textures/textures_screen_buffer.c +++ b/examples/textures/textures_screen_buffer.c @@ -66,7 +66,7 @@ int main(void) // Grow flameRoot for (int x = 2; x < flameWidth; x++) { - unsigned short flame = flameRootBuffer[x]; + unsigned char flame = flameRootBuffer[x]; if (flame == 255) continue; flame += GetRandomValue(0, 2); if (flame > 255) flame = 255; diff --git a/projects/VS2022/examples/shapes_hilbert_curve.vcxproj b/projects/VS2022/examples/shapes_hilbert_curve.vcxproj index 8fcbfab5f..6c60841fb 100644 --- a/projects/VS2022/examples/shapes_hilbert_curve.vcxproj +++ b/projects/VS2022/examples/shapes_hilbert_curve.vcxproj @@ -292,7 +292,7 @@ Level3 Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNIGNS;WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) CompileAsC $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) @@ -309,7 +309,7 @@ Level3 Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNIGNS;WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) CompileAsC $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) /FS %(AdditionalOptions) @@ -345,7 +345,7 @@ Level3 Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNIGNS;WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) CompileAsC $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) @@ -366,7 +366,7 @@ Level3 Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNIGNS;WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) CompileAsC $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) @@ -410,7 +410,7 @@ MaxSpeed true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + _CRT_SECURE_NO_WARNIGNS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) CompileAsC true @@ -432,7 +432,7 @@ MaxSpeed true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + _CRT_SECURE_NO_WARNIGNS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) CompileAsC true @@ -476,7 +476,7 @@ MaxSpeed true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + _CRT_SECURE_NO_WARNIGNS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) CompileAsC true @@ -504,7 +504,7 @@ MaxSpeed true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + _CRT_SECURE_NO_WARNIGNS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) CompileAsC true diff --git a/projects/VS2022/examples/shapes_penrose_tile.vcxproj b/projects/VS2022/examples/shapes_penrose_tile.vcxproj index bde99f8c1..389bdde36 100644 --- a/projects/VS2022/examples/shapes_penrose_tile.vcxproj +++ b/projects/VS2022/examples/shapes_penrose_tile.vcxproj @@ -292,7 +292,7 @@ Level3 Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) CompileAsC $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) @@ -309,7 +309,7 @@ Level3 Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) CompileAsC $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) /FS %(AdditionalOptions) @@ -345,7 +345,7 @@ Level3 Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) CompileAsC $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) @@ -366,7 +366,7 @@ Level3 Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) CompileAsC $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) @@ -410,7 +410,7 @@ MaxSpeed true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) CompileAsC true @@ -432,7 +432,7 @@ MaxSpeed true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) CompileAsC true @@ -476,7 +476,7 @@ MaxSpeed true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) CompileAsC true @@ -504,7 +504,7 @@ MaxSpeed true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) CompileAsC true diff --git a/projects/VS2022/examples/shapes_rlgl_color_wheel.vcxproj b/projects/VS2022/examples/shapes_rlgl_color_wheel.vcxproj index b22703577..a02a2d4e2 100644 --- a/projects/VS2022/examples/shapes_rlgl_color_wheel.vcxproj +++ b/projects/VS2022/examples/shapes_rlgl_color_wheel.vcxproj @@ -292,7 +292,7 @@ Level3 Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNIGNS;WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) CompileAsC $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) @@ -309,7 +309,7 @@ Level3 Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNIGNS;WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) CompileAsC $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) /FS %(AdditionalOptions) @@ -345,7 +345,7 @@ Level3 Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNIGNS;WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) CompileAsC $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) @@ -366,7 +366,7 @@ Level3 Disabled - WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNIGNS;WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) CompileAsC $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) @@ -410,7 +410,7 @@ MaxSpeed true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + _CRT_SECURE_NO_WARNIGNS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) CompileAsC true @@ -432,7 +432,7 @@ MaxSpeed true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + _CRT_SECURE_NO_WARNIGNS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) CompileAsC true @@ -476,7 +476,7 @@ MaxSpeed true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + _CRT_SECURE_NO_WARNIGNS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) CompileAsC true @@ -504,7 +504,7 @@ MaxSpeed true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + _CRT_SECURE_NO_WARNIGNS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) CompileAsC true From a44157c2a834c4956e98afafbced96e25177b1a5 Mon Sep 17 00:00:00 2001 From: Jack Boakes <163384444+jackboakes@users.noreply.github.com> Date: Sun, 4 Jan 2026 08:41:52 +1100 Subject: [PATCH 327/430] [example] Added textures_frame_buffer_rendering (#5468) --- .../textures_frame_buffer_rendering.png | Bin 0 -> 31588 bytes .../textures/textures_framebuffer_rendering.c | 208 ++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 examples/textures/textures_frame_buffer_rendering.png create mode 100644 examples/textures/textures_framebuffer_rendering.c diff --git a/examples/textures/textures_frame_buffer_rendering.png b/examples/textures/textures_frame_buffer_rendering.png new file mode 100644 index 0000000000000000000000000000000000000000..e6829f0bd20ef747b29a0fb868ca5f2c4cc635e5 GIT binary patch literal 31588 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 Duq`3E literal 0 HcmV?d00001 diff --git a/examples/textures/textures_framebuffer_rendering.c b/examples/textures/textures_framebuffer_rendering.c new file mode 100644 index 000000000..a8b466187 --- /dev/null +++ b/examples/textures/textures_framebuffer_rendering.c @@ -0,0 +1,208 @@ +/******************************************************************************************* +* +* raylib [textures] example - framebuffer rendering +* +* Example complexity rating: [★★☆☆] 2/4 +* +* Example originally created with raylib 5.6, last time updated with raylib 5.6 +* +* Example contributed by Jack Boakes (@jackboakes) 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) 2026-2026 Jack Boakes (@jackboakes) +* +********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" + +//------------------------------------------------------------------------------------ +// Module Functions Declaration +//------------------------------------------------------------------------------------ +static void DrawCameraPrism(Camera3D camera, float aspect, Color color); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + const int splitWidth = screenWidth/2; + + InitWindow(screenWidth, screenHeight, "raylib [textures] example - framebuffer rendering"); + + // Camera to look at the 3D world + Camera3D subjectCamera = { 0 }; + subjectCamera.position = (Vector3){ 5.0f, 5.0f, 5.0f }; + subjectCamera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; + subjectCamera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; + subjectCamera.fovy = 45.0f; + subjectCamera.projection = CAMERA_PERSPECTIVE; + + // Camera to observe the subject camera and 3D world + Camera3D observerCamera = { 0 }; + observerCamera.position = (Vector3){ 10.0f, 10.0f, 10.0f }; + observerCamera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; + observerCamera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; + observerCamera.fovy = 45.0f; + observerCamera.projection = CAMERA_PERSPECTIVE; + + // Set up render textures + RenderTexture2D observerTarget = LoadRenderTexture(splitWidth, screenHeight); + Rectangle observerSource = { 0.0f, 0.0f, (float)observerTarget.texture.width, -(float)observerTarget.texture.height }; + Rectangle observerDest = { 0.0f, 0.0f, (float)splitWidth, (float)screenHeight }; + + RenderTexture2D subjectTarget = LoadRenderTexture(splitWidth, screenHeight); + Rectangle subjectSource = { 0.0f, 0.0f, (float)subjectTarget.texture.width, -(float)subjectTarget.texture.height }; + Rectangle subjectDest = { (float)splitWidth, 0.0f, (float)splitWidth, (float)screenHeight }; + const float textureAspectRatio = (float)subjectTarget.texture.width/(float)subjectTarget.texture.height; + + // Rectangles for cropping render texture + const float captureSize = 128.0f; + Rectangle cropSource = { (subjectTarget.texture.width - captureSize)/2.0f, (subjectTarget.texture.height - captureSize)/2.0f, captureSize, -captureSize }; + Rectangle cropDest = { splitWidth + 20, 20, captureSize, captureSize}; + + SetTargetFPS(60); + DisableCursor(); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + UpdateCamera(&observerCamera, CAMERA_FREE); + UpdateCamera(&subjectCamera, CAMERA_ORBITAL); + + if (IsKeyPressed(KEY_R)) observerCamera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; + + // Build LHS observer view texture + BeginTextureMode(observerTarget); + + ClearBackground(RAYWHITE); + + BeginMode3D(observerCamera); + + DrawGrid(10, 1.0f); + DrawCube((Vector3){ 0.0f, 0.0f, 0.0f }, 2.0f, 2.0f, 2.0f, GOLD); + DrawCubeWires((Vector3){ 0.0f, 0.0f, 0.0f }, 2.0f, 2.0f, 2.0f, PINK); + DrawCameraPrism(subjectCamera, textureAspectRatio, GREEN); + + EndMode3D(); + + DrawText("Observer View", 10, observerTarget.texture.height - 30, 20, BLACK); + DrawText("WASD + Mouse to Move", 10, 10, 20, DARKGRAY); + DrawText("Scroll to Zoom", 10, 30, 20, DARKGRAY); + DrawText("R to Reset Observer Target", 10, 50, 20, DARKGRAY); + + EndTextureMode(); + + // Build RHS subject view texture + BeginTextureMode(subjectTarget); + + ClearBackground(RAYWHITE); + + BeginMode3D(subjectCamera); + + DrawCube((Vector3){ 0.0f, 0.0f, 0.0f }, 2.0f, 2.0f, 2.0f, GOLD); + DrawCubeWires((Vector3){ 0.0f, 0.0f, 0.0f }, 2.0f, 2.0f, 2.0f, PINK); + DrawGrid(10, 1.0f); + + EndMode3D(); + + DrawRectangleLines((subjectTarget.texture.width - captureSize)/2, (subjectTarget.texture.height - captureSize)/2, captureSize, captureSize, GREEN); + DrawText("Subject View", 10, subjectTarget.texture.height - 30, 20, BLACK); + + EndTextureMode(); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(BLACK); + + // Draw observer texture LHS + DrawTexturePro(observerTarget.texture, observerSource, observerDest, (Vector2){0.0f, 0.0f }, 0.0f, WHITE); + + // Draw subject texture RHS + DrawTexturePro(subjectTarget.texture, subjectSource, subjectDest, (Vector2){ 0.0f, 0.0f }, 0.0f, WHITE); + + // Draw the small crop overlay on top + DrawTexturePro(subjectTarget.texture, cropSource, cropDest, (Vector2){ 0.0f, 0.0f }, 0.0f, WHITE); + DrawRectangleLinesEx(cropDest, 2, BLACK); + + // Draw split screen divider line + DrawLine(splitWidth, 0, splitWidth, screenHeight, BLACK); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadRenderTexture(observerTarget); + UnloadRenderTexture(subjectTarget); + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +static void DrawCameraPrism(Camera3D camera, float aspect, Color color) +{ + float length = Vector3Distance(camera.position, camera.target); + // Define the 4 corners of the camera's prism plane sliced at the target in Normalized Device Coordinates + Vector3 planeNDC[4] = { + { -1.0f, -1.0f, 1.0f }, // Bottom Left + { 1.0f, -1.0f, 1.0f }, // Bottom Right + { 1.0f, 1.0f, 1.0f }, // Top Right + { -1.0f, 1.0f, 1.0f } // Top Left + }; + + // Build the matrices + Matrix view = GetCameraMatrix(camera); + Matrix proj = MatrixPerspective(camera.fovy * DEG2RAD, aspect, 0.05f, length); + // Combine view and projection so we can reverse the full camera transform + Matrix viewProj = MatrixMultiply(view, proj); + // Invert the view-projection matrix to unproject points from NDC space back into world space + Matrix inverseViewProj = MatrixInvert(viewProj); + + // Transform the 4 plane corners from NDC into world space + Vector3 corners[4]; + for (int i = 0; i < 4; i++) + { + float x = planeNDC[i].x; + float y = planeNDC[i].y; + float z = planeNDC[i].z; + + // Multiply NDC position by the inverse view-projection matrix + // This produces a homogeneous (x, y, z, w) position in world space + float vx = inverseViewProj.m0*x + inverseViewProj.m4*y + inverseViewProj.m8*z + inverseViewProj.m12; + float vy = inverseViewProj.m1*x + inverseViewProj.m5*y + inverseViewProj.m9*z + inverseViewProj.m13; + float vz = inverseViewProj.m2*x + inverseViewProj.m6*y + inverseViewProj.m10*z + inverseViewProj.m14; + float vw = inverseViewProj.m3*x + inverseViewProj.m7*y + inverseViewProj.m11*z + inverseViewProj.m15; + + corners[i] = (Vector3){ vx/vw, vy/vw, vz/vw }; + } + + // Draw the far plane sliced at the target + DrawLine3D(corners[0], corners[1], color); + DrawLine3D(corners[1], corners[2], color); + DrawLine3D(corners[2], corners[3], color); + DrawLine3D(corners[3], corners[0], color); + + // Draw the prism lines from the far plane to the camera position + for (int i = 0; i < 4; i++) + { + DrawLine3D(camera.position, corners[i], color); + } +} \ No newline at end of file From c4b11a30cd77d8cbcf3380cecf3f3540257caaf9 Mon Sep 17 00:00:00 2001 From: Michael Kolupaev Date: Sat, 3 Jan 2026 13:52:04 -0800 Subject: [PATCH 328/430] Fix DrawMeshInstanced breaking if instanceTransform is unused (#5469) --- src/rmodels.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index 3ee429900..c22c0a0c9 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -1762,11 +1762,14 @@ void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, i instancesVboId = rlLoadVertexBuffer(instanceTransforms, instances*sizeof(float16), false); // Instances transformation matrices are sent to shader attribute location: SHADER_LOC_VERTEX_INSTANCE_TX - for (unsigned int i = 0; i < 4; i++) + if (material.shader.locs[SHADER_LOC_VERTEX_INSTANCE_TX] != -1) { - rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_INSTANCE_TX] + i); - rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_INSTANCE_TX] + i, 4, RL_FLOAT, 0, sizeof(Matrix), i*sizeof(Vector4)); - rlSetVertexAttributeDivisor(material.shader.locs[SHADER_LOC_VERTEX_INSTANCE_TX] + i, 1); + for (unsigned int i = 0; i < 4; i++) + { + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_INSTANCE_TX] + i); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_INSTANCE_TX] + i, 4, RL_FLOAT, 0, sizeof(Matrix), i*sizeof(Vector4)); + rlSetVertexAttributeDivisor(material.shader.locs[SHADER_LOC_VERTEX_INSTANCE_TX] + i, 1); + } } rlDisableVertexBuffer(); From af544c24b9751a4b2972e22e472578cfc30ff339 Mon Sep 17 00:00:00 2001 From: ssszcmawo Date: Sat, 3 Jan 2026 22:57:22 +0100 Subject: [PATCH 329/430] [rcore] Fix touch position automation event handling (#5470) * fix touch position automation event handling * Fix alignment of previousPosition comment in rcore.c --- src/rcore.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index e16c6412a..59563537d 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -350,11 +350,12 @@ typedef struct CoreData { } Mouse; struct { - int pointCount; // Number of touch points active - int pointId[MAX_TOUCH_POINTS]; // Point identifiers - Vector2 position[MAX_TOUCH_POINTS]; // Touch position on screen - char currentTouchState[MAX_TOUCH_POINTS]; // Registers current touch state - char previousTouchState[MAX_TOUCH_POINTS]; // Registers previous touch state + int pointCount; // Number of touch points active + int pointId[MAX_TOUCH_POINTS]; // Point identifiers + Vector2 position[MAX_TOUCH_POINTS]; // Touch position on screen + Vector2 previousPosition[MAX_TOUCH_POINTS]; // Previous touch position on screen + char currentTouchState[MAX_TOUCH_POINTS]; // Registers current touch state + char previousTouchState[MAX_TOUCH_POINTS]; // Registers previous touch state } Touch; struct { @@ -4104,22 +4105,20 @@ static void RecordAutomationEvent(void) if (currentEventList->count == currentEventList->capacity) return; // Security check - // Event type: INPUT_TOUCH_POSITION - // TODO: It requires the id! - /* - if (((int)CORE.Input.Touch.currentPosition[id].x != (int)CORE.Input.Touch.previousPosition[id].x) || - ((int)CORE.Input.Touch.currentPosition[id].y != (int)CORE.Input.Touch.previousPosition[id].y)) + // Event type: INPUT_TOUCH_POSITION + if (((int)CORE.Input.Touch.position[id].x != (int)CORE.Input.Touch.previousPosition[id].x) || + ((int)CORE.Input.Touch.position[id].y != (int)CORE.Input.Touch.previousPosition[id].y)) { currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; currentEventList->events[currentEventList->count].type = INPUT_TOUCH_POSITION; currentEventList->events[currentEventList->count].params[0] = id; - currentEventList->events[currentEventList->count].params[1] = (int)CORE.Input.Touch.currentPosition[id].x; - currentEventList->events[currentEventList->count].params[2] = (int)CORE.Input.Touch.currentPosition[id].y; + currentEventList->events[currentEventList->count].params[1] = (int)CORE.Input.Touch.position[id].x; + currentEventList->events[currentEventList->count].params[2] = (int)CORE.Input.Touch.position[id].y; TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_TOUCH_POSITION | 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]); currentEventList->count++; } - */ + if (currentEventList->count == currentEventList->capacity) return; // Security check } From 35fc8ece44f899b420c7a227b2f307ba48ff48d2 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 3 Jan 2026 23:01:37 +0100 Subject: [PATCH 330/430] Update models_decals.c --- examples/models/models_decals.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/models/models_decals.c b/examples/models/models_decals.c index f35794daa..71122dd68 100644 --- a/examples/models/models_decals.c +++ b/examples/models/models_decals.c @@ -45,10 +45,7 @@ static void FreeMeshBuilder(MeshBuilder *mb); static Mesh BuildMesh(MeshBuilder *mb); static Mesh GenMeshDecal(Model inputModel, Matrix projection, float decalSize, float decalOffset); static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s); -inline void FreeDecalMeshData() -{ - GenMeshDecal((Model) { .meshCount = -1 }, (Matrix) { 0 }, 0.0f, 0.0f); -} +static void FreeDecalMeshData(void) { GenMeshDecal((Model){ .meshCount = -1 }, (Matrix){ 0 }, 0.0f, 0.0f); } static bool GuiButton(Rectangle rec, const char *label); //------------------------------------------------------------------------------------ From 3678c2d15763c4ec906506ba9f9e790ede8d0b94 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 5 Jan 2026 20:47:25 +0100 Subject: [PATCH 331/430] REMOVE: `TRACELOGD()`, hardly ever used --- src/config.h | 2 -- src/platforms/rcore_web.c | 8 ++++---- src/platforms/rcore_web_emscripten.c | 8 ++++---- src/rlgl.h | 5 ++--- src/rtext.c | 2 +- src/rtextures.c | 4 ++-- src/utils.h | 7 ------- 7 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/config.h b/src/config.h index 9a1d22de3..1286e5082 100644 --- a/src/config.h +++ b/src/config.h @@ -287,9 +287,7 @@ // Standard file io library (stdio.h) included #define SUPPORT_STANDARD_FILEIO 1 // Show TRACELOG() output messages -// NOTE: By default LOG_DEBUG traces not shown #define SUPPORT_TRACELOG 1 -//#define SUPPORT_TRACELOG_DEBUG 1 // utils: Configuration values //------------------------------------------------------------------------------------ diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index 0056849dd..2b51804ef 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -1104,7 +1104,7 @@ void PollInputEvents(void) else CORE.Input.Gamepad.currentButtonState[i][button] = 0; } - //TRACELOGD("INPUT: Gamepad %d, button %d: Digital: %d, Analog: %g", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]); + //TRACELOG(LOG_DEBUG, "INPUT: Gamepad %d, button %d: Digital: %d, Analog: %g", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]); } // Register axis data for every connected gamepad @@ -1695,12 +1695,12 @@ static EM_BOOL EmscriptenPointerlockCallback(int eventType, const EmscriptenPoin static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) { /* - TRACELOGD("%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"", + TRACELOG(LOG_DEBUG, "%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"", eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state", gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping); - for (int i = 0; i < gamepadEvent->numAxes; i++) TRACELOGD("Axis %d: %g", i, gamepadEvent->axis[i]); - for (int i = 0; i < gamepadEvent->numButtons; i++) TRACELOGD("Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); + for (int i = 0; i < gamepadEvent->numAxes; i++) TRACELOG(LOG_DEBUG, "Axis %d: %g", i, gamepadEvent->axis[i]); + for (int i = 0; i < gamepadEvent->numButtons; i++) TRACELOG(LOG_DEBUG, "Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); */ if (gamepadEvent->connected && (gamepadEvent->index < MAX_GAMEPADS)) diff --git a/src/platforms/rcore_web_emscripten.c b/src/platforms/rcore_web_emscripten.c index 28d530e97..5fdcdbefc 100644 --- a/src/platforms/rcore_web_emscripten.c +++ b/src/platforms/rcore_web_emscripten.c @@ -1076,7 +1076,7 @@ void PollInputEvents(void) else CORE.Input.Gamepad.currentButtonState[i][button] = 0; } - //TRACELOGD("INPUT: Gamepad %d, button %d: Digital: %d, Analog: %g", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]); + //TRACELOG(LOG_DEBUG, "INPUT: Gamepad %d, button %d: Digital: %d, Analog: %g", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]); } // Register axis data for every connected gamepad @@ -1586,12 +1586,12 @@ static EM_BOOL EmscriptenPointerlockCallback(int eventType, const EmscriptenPoin static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) { /* - TRACELOGD("%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"", + TRACELOG(LOG_DEBUG, "%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"", eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state", gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping); - for (int i = 0; i < gamepadEvent->numAxes; i++) TRACELOGD("Axis %d: %g", i, gamepadEvent->axis[i]); - for (int i = 0; i < gamepadEvent->numButtons; i++) TRACELOGD("Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); + for (int i = 0; i < gamepadEvent->numAxes; i++) TRACELOG(LOG_DEBUG, "Axis %d: %g", i, gamepadEvent->axis[i]); + for (int i = 0; i < gamepadEvent->numButtons; i++) TRACELOG(LOG_DEBUG, "Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); */ if (gamepadEvent->connected && (gamepadEvent->index < MAX_GAMEPADS)) diff --git a/src/rlgl.h b/src/rlgl.h index ab85569bb..7fa22f9cc 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -132,7 +132,6 @@ // Support TRACELOG macros #ifndef TRACELOG #define TRACELOG(level, ...) (void)0 - #define TRACELOGD(...) (void)0 #endif // Allow custom memory allocators @@ -3324,7 +3323,7 @@ unsigned int rlLoadTexture(const void *data, int width, int height, int format, unsigned int glInternalFormat, glFormat, glType; rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - TRACELOGD("TEXTURE: Load mipmap level %i (%i x %i), size: %i, offset: %i", i, mipWidth, mipHeight, mipSize, mipOffset); + TRACELOG(RL_LOG_DEBUG, "TEXTURE: Load mipmap level %i (%i x %i), size: %i, offset: %i", i, mipWidth, mipHeight, mipSize, mipOffset); if (glInternalFormat != 0) { @@ -4246,7 +4245,7 @@ unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) glGetActiveUniform(id, i, sizeof(name) - 1, &namelen, &num, &type, name); name[namelen] = 0; - TRACELOGD("SHADER: [ID %i] Active uniform (%s) set at location: %i", id, name, glGetUniformLocation(id, name)); + TRACELOG(RL_LOG_DEBUG, "SHADER: [ID %i] Active uniform (%s) set at location: %i", id, name, glGetUniformLocation(id, name)); } } */ diff --git a/src/rtext.c b/src/rtext.c index 1fd9a306d..8413d62bf 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1035,7 +1035,7 @@ void UnloadFont(Font font) UnloadTexture(font.texture); RL_FREE(font.recs); - TRACELOGD("FONT: Unloaded font data from RAM and VRAM"); + TRACELOG(LOG_DEBUG, "FONT: Unloaded font data from RAM and VRAM"); } } diff --git a/src/rtextures.c b/src/rtextures.c index 59000940e..8d4128d4e 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -2395,7 +2395,7 @@ void ImageMipmaps(Image *image) if (mipWidth < 1) mipWidth = 1; if (mipHeight < 1) mipHeight = 1; - TRACELOGD("IMAGE: Next mipmap level: %i x %i - current size %i", mipWidth, mipHeight, mipSize); + TRACELOG(LOG_DEBUG, "IMAGE: Next mipmap level: %i x %i - current size %i", mipWidth, mipHeight, mipSize); mipCount++; mipSize += GetPixelDataSize(mipWidth, mipHeight, image->format); // Add mipmap size (in bytes) @@ -2432,7 +2432,7 @@ void ImageMipmaps(Image *image) if (i < image->mipmaps) continue; - TRACELOGD("IMAGE: Generating mipmap level: %i (%i x %i) - size: %i - offset: 0x%x", i, mipWidth, mipHeight, mipSize, nextmip); + TRACELOG(LOG_DEBUG, "IMAGE: Generating mipmap level: %i (%i x %i) - size: %i - offset: 0x%x", i, mipWidth, mipHeight, mipSize, nextmip); ImageResize(&imCopy, mipWidth, mipHeight); // Uses internally Mitchell cubic downscale filter memcpy(nextmip, imCopy.data, mipSize); } diff --git a/src/utils.h b/src/utils.h index 7d79c2188..9c15ac285 100644 --- a/src/utils.h +++ b/src/utils.h @@ -34,15 +34,8 @@ #if defined(SUPPORT_TRACELOG) #define TRACELOG(level, ...) TraceLog(level, __VA_ARGS__) - - #if defined(SUPPORT_TRACELOG_DEBUG) - #define TRACELOGD(...) TraceLog(LOG_DEBUG, __VA_ARGS__) - #else - #define TRACELOGD(...) (void)0 - #endif #else #define TRACELOG(level, ...) (void)0 - #define TRACELOGD(...) (void)0 #endif //---------------------------------------------------------------------------------- From c78ac657862e13898b4bc3a3b8a200a61667042c Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Tue, 6 Jan 2026 13:32:42 -0800 Subject: [PATCH 332/430] Don't require a M3d animation only file to have a mesh. There are valid use cases for animation only files that can be applied to N other meshes. (#5475) --- src/rmodels.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index c22c0a0c9..20287a935 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -7045,8 +7045,8 @@ static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, int *animCou else TRACELOG(LOG_INFO, "MODEL: [%s] M3D data loaded successfully: %i animations, %i bones, %i skins", fileName, m3d->numaction, m3d->numbone, m3d->numskin); - // No animation or bone+skin? - if (!m3d->numaction || !m3d->numbone || !m3d->numskin) + // No animation or bones, exit out. skins are not required because some people use one animation for N models + if (!m3d->numaction || !m3d->numbone) { m3d_free(m3d); UnloadFileData(fileData); From 23bc037c37545d6bcbf31231d00503339234fabf Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 7 Jan 2026 22:32:09 +0100 Subject: [PATCH 333/430] Revert change, trying to follow DRM implementation but not needed on Android #5477 --- src/platforms/rcore_android.c | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 6d3d68f24..19b686cef 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -919,26 +919,7 @@ static int InitGraphicsDevice(void) EGLint numConfigs = 0; // Get an EGL device connection - // NOTE: eglGetPlatformDisplay() is preferred over eglGetDisplay() legacy call - platform.device = EGL_NO_DISPLAY; -#if defined(EGL_VERSION_1_5) - platform.device = eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, platform.gbmDevice, NULL); -#else - // Check if extension is available for eglGetPlatformDisplayEXT() - // NOTE: Better compatibility with some drivers (e.g. Mali Midgard) - const char *eglClientExtensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); - if (eglClientExtensions != NULL) - { - if (strstr(eglClientExtensions, "EGL_EXT_platform_base") != NULL) - { - PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); - if (eglGetPlatformDisplayEXT != NULL) platform.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, platform.gbmDevice, NULL); - } - } - - // In case extension not found or display could not be retrieved, try useing legacy version - if (platform.device == EGL_NO_DISPLAY) platform.device = eglGetDisplay(EGL_DEFAULT_DISPLAY); -#endif + platform.device = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (platform.device == EGL_NO_DISPLAY) { From c256f146b4c4e6b2f796bcb9e86197be44ec57e8 Mon Sep 17 00:00:00 2001 From: ssszcmawo Date: Wed, 7 Jan 2026 22:32:58 +0100 Subject: [PATCH 334/430] added saving to memory buffer and SaveFileData for binary files (#5476) --- src/rcore.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 59563537d..52ca0e2b6 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -3230,15 +3230,25 @@ bool ExportAutomationEventList(AutomationEventList list, const char *fileName) #if defined(SUPPORT_AUTOMATION_EVENTS) // Export events as binary file - // TODO: Save to memory buffer and SaveFileData() - /* - unsigned char fileId[4] = "rAE "; - FILE *raeFile = fopen(fileName, "wb"); - fwrite(fileId, sizeof(unsigned char), 4, raeFile); - fwrite(&eventCount, sizeof(int), 1, raeFile); - fwrite(events, sizeof(AutomationEvent), eventCount, raeFile); - fclose(raeFile); - */ + + // Binary buffer size = header (file id + count) + events data + int binarySize = 4 + sizeof(int) + sizeof(AutomationEvent)*list.count; + unsigned char *binBuffer = (unsigned char* )RL_MALLOC(binarySize); + if(!binBuffer) return false; + + int offset = 0; + memcpy(binBuffer + offset, "rAE ", 4); offset += 4; + memcpy(binBuffer + offset, &list.count, sizeof(int)); offset += sizeof(int); + + if(list.count > 0) + { + memcpy(binBuffer + offset, list.events,sizeof(AutomationEvent)*list.count); + offset += sizeof(AutomationEvent)*list.count; + } + + success = SaveFileData(TextFormat("%s.rae",fileName), binBuffer, binarySize); + + RL_FREE(binBuffer); // Export events as text // NOTE: Save to memory buffer and SaveFileText() From 229f82699ba311f8ca71c977d1eb6ae3ca2b6597 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 7 Jan 2026 22:36:40 +0100 Subject: [PATCH 335/430] Reviewed change #5476 --- src/rcore.c | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 52ca0e2b6..b7fcbf4a0 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -3230,25 +3230,24 @@ bool ExportAutomationEventList(AutomationEventList list, const char *fileName) #if defined(SUPPORT_AUTOMATION_EVENTS) // Export events as binary file - - // Binary buffer size = header (file id + count) + events data - int binarySize = 4 + sizeof(int) + sizeof(AutomationEvent)*list.count; - unsigned char *binBuffer = (unsigned char* )RL_MALLOC(binarySize); - if(!binBuffer) return false; - - int offset = 0; - memcpy(binBuffer + offset, "rAE ", 4); offset += 4; - memcpy(binBuffer + offset, &list.count, sizeof(int)); offset += sizeof(int); - - if(list.count > 0) + // NOTE: Code not used, only for reference if required in the future + /* + if (list.count > 0) { - memcpy(binBuffer + offset, list.events,sizeof(AutomationEvent)*list.count); + int binarySize = 4 + sizeof(int) + sizeof(AutomationEvent)*list.count; + unsigned char *binBuffer = (unsigned char *)RL_CALLOC(binarySize, 1); + int offset = 0; + memcpy(binBuffer + offset, "rAE ", 4); + offset += 4; + memcpy(binBuffer + offset, &list.count, sizeof(int)); + offset += sizeof(int); + memcpy(binBuffer + offset, list.events, sizeof(AutomationEvent)*list.count); offset += sizeof(AutomationEvent)*list.count; + + success = SaveFileData(TextFormat("%s.rae",fileName), binBuffer, binarySize); + RL_FREE(binBuffer); } - - success = SaveFileData(TextFormat("%s.rae",fileName), binBuffer, binarySize); - - RL_FREE(binBuffer); + */ // Export events as text // NOTE: Save to memory buffer and SaveFileText() From 5e1f5d5b7429bb7d1dae2be9a99266b73de954d4 Mon Sep 17 00:00:00 2001 From: Paul de Mascarel Date: Wed, 7 Jan 2026 22:38:04 +0100 Subject: [PATCH 336/430] examples/models: optimize collision check in first_person_maze (#5478) Limit collision detection to the player surrounding cells instead of iterating the full cubicmap each frame. --- examples/models/models_first_person_maze.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/models/models_first_person_maze.c b/examples/models/models_first_person_maze.c index 43020974f..eb0db6024 100644 --- a/examples/models/models_first_person_maze.c +++ b/examples/models/models_first_person_maze.c @@ -80,12 +80,13 @@ int main(void) if (playerCellY < 0) playerCellY = 0; else if (playerCellY >= cubicmap.height) playerCellY = cubicmap.height - 1; - // Check map collisions using image data and player position - // TODO: Improvement: Just check player surrounding cells for collision - for (int y = 0; y < cubicmap.height; y++) + // Check map collisions using image data and player position against surrounding cells only + for (int y = playerCellY - 1; y <= playerCellY + 1; y++) { - for (int x = 0; x < cubicmap.width; x++) + if (y < 0 || y >= cubicmap.height) continue; + for (int x = playerCellX - 1; x <= playerCellX + 1; x++) { + if (x < 0 || x >= cubicmap.width) continue; if ((mapPixels[y*cubicmap.width + x].r == 255) && // Collision: white pixel, only check R channel (CheckCollisionCircleRec(playerPos, playerRadius, (Rectangle){ mapPosition.x - 0.5f + x*1.0f, mapPosition.z - 0.5f + y*1.0f, 1.0f, 1.0f }))) From c814625c009f7bdbca47352a5951236558937e7e Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 7 Jan 2026 22:40:28 +0100 Subject: [PATCH 337/430] Update models_first_person_maze.c --- examples/models/models_first_person_maze.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/models/models_first_person_maze.c b/examples/models/models_first_person_maze.c index eb0db6024..4c77d6121 100644 --- a/examples/models/models_first_person_maze.c +++ b/examples/models/models_first_person_maze.c @@ -83,10 +83,14 @@ int main(void) // Check map collisions using image data and player position against surrounding cells only for (int y = playerCellY - 1; y <= playerCellY + 1; y++) { - if (y < 0 || y >= cubicmap.height) continue; + // Avoid map accessing out of bounds + if ((y < 0) || (y >= cubicmap.height)) continue; + for (int x = playerCellX - 1; x <= playerCellX + 1; x++) { - if (x < 0 || x >= cubicmap.width) continue; + // Avoid map accessing out of bounds + if ((x < 0) || (x >= cubicmap.width)) continue; + if ((mapPixels[y*cubicmap.width + x].r == 255) && // Collision: white pixel, only check R channel (CheckCollisionCircleRec(playerPos, playerRadius, (Rectangle){ mapPosition.x - 0.5f + x*1.0f, mapPosition.z - 0.5f + y*1.0f, 1.0f, 1.0f }))) From 5398b8c9b06589ee3897f6c17c8739a4260d3f86 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 7 Jan 2026 23:09:51 +0100 Subject: [PATCH 338/430] Update rexm.c --- tools/rexm/rexm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index d3ff18289..9914733f0 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -2894,8 +2894,8 @@ static void UpdateWebMetadata(const char *exHtmlPath, const char *exFilePath) // Get example name: replace underscore by spaces strncpy(exName, GetFileNameWithoutExt(exHtmlPathCopy), 64 - 1); - strncpy(exTitle, exName, 64 - 1); - for (int i = 0; (i < 256) && (exTitle[i] != '\0'); i++) { if (exTitle[i] == '_') exTitle[i] = ' '; } + strcpy(exTitle, exName); + for (int i = 0; (i < 64) && (exTitle[i] != '\0'); i++) { if (exTitle[i] == '_') exTitle[i] = ' '; } // Get example category from exName: copy until first underscore for (int i = 0; (exName[i] != '_'); i++) exCategory[i] = exName[i]; From 0bcf79ce287d6180cedf7422c8a947e61247e81e Mon Sep 17 00:00:00 2001 From: Krzysztof Szenk Date: Thu, 8 Jan 2026 17:26:56 +0100 Subject: [PATCH 339/430] [#5455] Fix for: Fix window width calculation by adding wOffset (#5480) --- src/external/RGFW.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/external/RGFW.h b/src/external/RGFW.h index 0ab3858cf..c01ce1177 100644 --- a/src/external/RGFW.h +++ b/src/external/RGFW.h @@ -669,7 +669,8 @@ typedef struct RGFW_event { typedef struct RGFW_window_src { HWND window; /*!< source window */ HDC hdc; /*!< source HDC */ - u32 hOffset; /*!< height offset for window */ + i32 wOffset; /*!< width offset for window */ + i32 hOffset; /*!< height offset for window */ HICON hIconSmall, hIconBig; /*!< source window icons */ #if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) HGLRC ctx; /*!< source graphics context */ @@ -6537,11 +6538,11 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) else windowRect.bottom = windowRect.top + newHeight; } - RGFW_window_resize(win, RGFW_AREA((windowRect.right - windowRect.left), + RGFW_window_resize(win, RGFW_AREA((u32)(windowRect.right - windowRect.left) - (u32)win->src.wOffset, (u32)(windowRect.bottom - windowRect.top) - (u32)win->src.hOffset)); } - win->r.w = windowRect.right - windowRect.left; + win->r.w = (windowRect.right - windowRect.left) - (i32)win->src.wOffset; win->r.h = (windowRect.bottom - windowRect.top) - (i32)win->src.hOffset; RGFW_eventQueuePushEx(e.type = RGFW_windowResized; e._win = win); RGFW_windowResizedCallback(win, win->r); @@ -6561,12 +6562,12 @@ LRESULT CALLBACK WndProcW(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) #endif case WM_GETMINMAXINFO: { MINMAXINFO* mmi = (MINMAXINFO*) lParam; - mmi->ptMinTrackSize.x = (LONG)win->src.minSize.w; + mmi->ptMinTrackSize.x = (LONG)(win->src.minSize.w + win->src.wOffset); mmi->ptMinTrackSize.y = (LONG)(win->src.minSize.h + win->src.hOffset); if (win->src.maxSize.w == 0 && win->src.maxSize.h == 0) return DefWindowProcW(hWnd, message, wParam, lParam); - mmi->ptMaxTrackSize.x = (LONG)win->src.maxSize.w; + mmi->ptMaxTrackSize.x = (LONG)(win->src.maxSize.w + win->src.wOffset); mmi->ptMaxTrackSize.y = (LONG)(win->src.maxSize.h + win->src.hOffset); return DefWindowProcW(hWnd, message, wParam, lParam); } @@ -6969,7 +6970,7 @@ RGFW_window* RGFW_createWindowPtr(const char* name, RGFW_rect rect, RGFW_windowF win->src.hOffset = (u32)(windowRect.bottom - windowRect.top) - (u32)(clientRect.bottom - clientRect.top); win->src.wOffset = (u32)(windowRect.right - windowRect.left) - (u32)(clientRect.right - clientRect.left); - win->src.window = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->r.x, win->r.y, win->r.w, win->r.h + (i32)win->src.hOffset, 0, 0, inh, 0); + win->src.window = CreateWindowW(Class.lpszClassName, (wchar_t*)wide_name, window_style, win->r.x, win->r.y, win->r.w + (i32)win->src.wOffset, win->r.h + (i32)win->src.hOffset, 0, 0, inh, 0); SetPropW(win->src.window, L"RGFW", win); RGFW_window_resize(win, RGFW_AREA(win->r.w, win->r.h)); /* so WM_GETMINMAXINFO gets called again */ @@ -7065,7 +7066,7 @@ void RGFW_window_setFullscreen(RGFW_window* win, RGFW_bool fullscreen) { if (fullscreen == RGFW_FALSE) { RGFW_window_setBorder(win, 1); - SetWindowPos(win->src.window, HWND_NOTOPMOST, win->_oldRect.x, win->_oldRect.y, win->_oldRect.w, win->_oldRect.h + (i32)win->src.hOffset, + SetWindowPos(win->src.window, HWND_NOTOPMOST, win->_oldRect.x, win->_oldRect.y, win->_oldRect.w + (i32)win->src.wOffset, win->_oldRect.h + (i32)win->src.hOffset, SWP_NOOWNERZORDER | SWP_FRAMECHANGED); win->_flags &= ~(u32)RGFW_windowFullscreen; @@ -7899,7 +7900,7 @@ void RGFW_window_resize(RGFW_window* win, RGFW_area a) { win->r.w = (i32)a.w; win->r.h = (i32)a.h; - SetWindowPos(win->src.window, HWND_TOP, 0, 0, win->r.w, win->r.h + (i32)win->src.hOffset, SWP_NOMOVE); + SetWindowPos(win->src.window, HWND_TOP, 0, 0, win->r.w + (i32)win->src.wOffset, win->r.h + (i32)win->src.hOffset, SWP_NOMOVE); } From 16e6d325b9f787bb99dc11c6504f8b73d37706ac Mon Sep 17 00:00:00 2001 From: Marcos Paccor Date: Thu, 8 Jan 2026 18:04:09 -0300 Subject: [PATCH 340/430] [raudio] Fix freeing the wrong memory (#5481) --- src/raudio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raudio.c b/src/raudio.c index 98326727f..cfb86cdbd 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -1249,7 +1249,7 @@ void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) frameCount = (ma_uint32)ma_convert_frames(data, frameCount, formatOut, channels, sampleRate, wave->data, frameCountIn, formatIn, wave->channels, wave->sampleRate); if (frameCount == 0) { - RL_FREE(wave->data); + RL_FREE(data); TRACELOG(LOG_WARNING, "WAVE: Failed format conversion"); return; } From 5cc42c1b805cd07227d48ee73c9c21c35fb528aa Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 9 Jan 2026 19:55:26 +0100 Subject: [PATCH 341/430] Updated file name --- examples/textures/textures_framebuffer_rendering.c | 2 +- ...ering.png => textures_framebuffer_rendering.png} | Bin 2 files changed, 1 insertion(+), 1 deletion(-) rename examples/textures/{textures_frame_buffer_rendering.png => textures_framebuffer_rendering.png} (100%) diff --git a/examples/textures/textures_framebuffer_rendering.c b/examples/textures/textures_framebuffer_rendering.c index a8b466187..484192739 100644 --- a/examples/textures/textures_framebuffer_rendering.c +++ b/examples/textures/textures_framebuffer_rendering.c @@ -11,7 +11,7 @@ * 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) 2026-2026 Jack Boakes (@jackboakes) +* Copyright (c) 2026 Jack Boakes (@jackboakes) * ********************************************************************************************/ diff --git a/examples/textures/textures_frame_buffer_rendering.png b/examples/textures/textures_framebuffer_rendering.png similarity index 100% rename from examples/textures/textures_frame_buffer_rendering.png rename to examples/textures/textures_framebuffer_rendering.png From 4cf844b74ea9a3c5e5ab6cd57b4b7ff1eafe275b Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 9 Jan 2026 19:55:31 +0100 Subject: [PATCH 342/430] Update raylib.h --- src/raylib.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/raylib.h b/src/raylib.h index 2d411f896..6eae8411e 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -570,8 +570,7 @@ typedef enum { } TraceLogLevel; // Keyboard keys (US keyboard layout) -// NOTE: Use GetKeyPressed() to allow redefining -// required keys for alternative layouts +// NOTE: Use GetKeyPressed() to allow redefining required keys for alternative layouts typedef enum { KEY_NULL = 0, // Key: NULL, used for no key pressed // Alphanumeric keys From b365d23f49d352019eb29f2f4e52d481c48c70dd Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 9 Jan 2026 19:59:38 +0100 Subject: [PATCH 343/430] Update examples_list.txt --- examples/examples_list.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/examples_list.txt b/examples/examples_list.txt index 96d64ca84..ba3a16d08 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -124,6 +124,7 @@ textures;textures_screen_buffer;★★☆☆;5.5;5.5;2025;2025;"Agnis Aldiņš"; textures;textures_textured_curve;★★★☆;4.5;4.5;2022;2025;"Jeffery Myers";@JeffM2501 textures;textures_sprite_stacking;★★☆☆;5.6-dev;6.0;2025;2025;"Robin";@RobinsAviary textures;textures_cellular_automata;★★☆☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant +texture;textures_framebuffer_rendering;★★☆☆;5.6;5.6;2026;2026;"Jack Boakes";@jackboakes text;text_sprite_fonts;★☆☆☆;1.7;3.7;2017;2025;"Ramon Santamaria";@raysan5 text;text_font_spritefont;★☆☆☆;1.0;1.0;2014;2025;"Ramon Santamaria";@raysan5 text;text_font_filters;★★☆☆;1.3;4.2;2015;2025;"Ramon Santamaria";@raysan5 From cfd5c3f2abe2a54cfa5cc60806601455feafc769 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 9 Jan 2026 20:02:29 +0100 Subject: [PATCH 344/430] Update examples_list.txt --- examples/examples_list.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/examples_list.txt b/examples/examples_list.txt index ba3a16d08..c825e137d 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -124,7 +124,7 @@ textures;textures_screen_buffer;★★☆☆;5.5;5.5;2025;2025;"Agnis Aldiņš"; textures;textures_textured_curve;★★★☆;4.5;4.5;2022;2025;"Jeffery Myers";@JeffM2501 textures;textures_sprite_stacking;★★☆☆;5.6-dev;6.0;2025;2025;"Robin";@RobinsAviary textures;textures_cellular_automata;★★☆☆;5.6;5.6;2025;2025;"Jordi Santonja";@JordSant -texture;textures_framebuffer_rendering;★★☆☆;5.6;5.6;2026;2026;"Jack Boakes";@jackboakes +textures;textures_framebuffer_rendering;★★☆☆;5.6;5.6;2026;2026;"Jack Boakes";@jackboakes text;text_sprite_fonts;★☆☆☆;1.7;3.7;2017;2025;"Ramon Santamaria";@raysan5 text;text_font_spritefont;★☆☆☆;1.0;1.0;2014;2025;"Ramon Santamaria";@raysan5 text;text_font_filters;★★☆☆;1.3;4.2;2015;2025;"Ramon Santamaria";@raysan5 From 7218b674e5cab56a89ffad8e4f9ec5cea1e1c06e Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 9 Jan 2026 20:05:46 +0100 Subject: [PATCH 345/430] REXM: Updated: `textures_framebuffer_rendering` --- examples/Makefile | 1 + examples/Makefile.Web | 4 + .../textures_framebuffer_rendering.vcxproj | 569 ++++++++++++++++++ tools/rexm/reports/examples_issues.md | 1 + tools/rexm/reports/examples_validation.md | 3 +- 5 files changed, 577 insertions(+), 1 deletion(-) create mode 100644 projects/VS2022/examples/textures_framebuffer_rendering.vcxproj diff --git a/examples/Makefile b/examples/Makefile index 3cbc2ffc1..a402ae692 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -608,6 +608,7 @@ TEXTURES = \ textures/textures_bunnymark \ textures/textures_cellular_automata \ textures/textures_fog_of_war \ + textures/textures_framebuffer_rendering \ textures/textures_gif_player \ textures/textures_image_channel \ textures/textures_image_drawing \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index fe4de1330..556900a76 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -594,6 +594,7 @@ TEXTURES = \ textures/textures_bunnymark \ textures/textures_cellular_automata \ textures/textures_fog_of_war \ + textures/textures_framebuffer_rendering \ textures/textures_gif_player \ textures/textures_image_channel \ textures/textures_image_drawing \ @@ -1009,6 +1010,9 @@ textures/textures_cellular_automata: textures/textures_cellular_automata.c textures/textures_fog_of_war: textures/textures_fog_of_war.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) +textures/textures_framebuffer_rendering: textures/textures_framebuffer_rendering.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + textures/textures_gif_player: textures/textures_gif_player.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file textures/resources/scarfy_run.gif@resources/scarfy_run.gif diff --git a/projects/VS2022/examples/textures_framebuffer_rendering.vcxproj b/projects/VS2022/examples/textures_framebuffer_rendering.vcxproj new file mode 100644 index 000000000..3a7eeeb35 --- /dev/null +++ b/projects/VS2022/examples/textures_framebuffer_rendering.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E} + Win32Proj + textures_framebuffer_rendering + 10.0 + textures_framebuffer_rendering + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/tools/rexm/reports/examples_issues.md b/tools/rexm/reports/examples_issues.md index 081170806..7b39c7a0b 100644 --- a/tools/rexm/reports/examples_issues.md +++ b/tools/rexm/reports/examples_issues.md @@ -21,6 +21,7 @@ Example elements validated: | **EXAMPLE NAME** | [C] | [CAT]| [INFO]|[PNG]|[WPNG]| [RES]| [MK] |[MKWEB]| [VCX]| [SOL]|[RDME]|[JS] | [WOUT]|[WMETA]| |:---------------------------------|:---:|:----:|:-----:|:---:|:----:|:----:|:----:|:-----:|:----:|:----:|:----:|:---:|:-----:|:-----:| | core_highdpi_testbed | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_keyboard_testbed | ✔ | ✔ | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | rlgl_standalone | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | rlgl_compute_shader | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | easings_testbed | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | diff --git a/tools/rexm/reports/examples_validation.md b/tools/rexm/reports/examples_validation.md index 6770f3c37..8d3f699ca 100644 --- a/tools/rexm/reports/examples_validation.md +++ b/tools/rexm/reports/examples_validation.md @@ -105,6 +105,7 @@ Example elements validated: | shapes_rlgl_triangle | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_ball_physics | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_penrose_tile | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_hilbert_curve | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_logo_raylib | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_srcrec_dstrec | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_image_drawing | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -134,6 +135,7 @@ Example elements validated: | textures_textured_curve | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_sprite_stacking | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_cellular_automata | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| textures_framebuffer_rendering | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_sprite_fonts | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_font_spritefont | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_font_filters | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -225,4 +227,3 @@ Example elements validated: | raylib_opengl_interop | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | | embedded_files_loading | ✔ | ❌ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | web_basic_window | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_hilbert_curve | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | From 11f7db2dd8c00b397980fab82372ef17c4b9a709 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 9 Jan 2026 20:06:53 +0100 Subject: [PATCH 346/430] REXM: ADDED: `core_keyboard_testbed` --- examples/Makefile | 1 + examples/Makefile.Web | 4 + examples/README.md | 8 +- examples/core/core_keyboard_testbed.c | 333 ++++++++++ examples/core/core_keyboard_testbed.png | Bin 0 -> 17631 bytes examples/examples_list.txt | 1 + .../examples/core_keyboard_testbed.vcxproj | 569 ++++++++++++++++++ projects/VS2022/raylib.sln | 54 ++ tools/rexm/reports/examples_issues.md | 1 - tools/rexm/reports/examples_validation.md | 1 + 10 files changed, 968 insertions(+), 4 deletions(-) create mode 100644 examples/core/core_keyboard_testbed.c create mode 100644 examples/core/core_keyboard_testbed.png create mode 100644 projects/VS2022/examples/core_keyboard_testbed.vcxproj diff --git a/examples/Makefile b/examples/Makefile index a402ae692..acfcb0857 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -544,6 +544,7 @@ CORE = \ core/core_input_mouse_wheel \ core/core_input_multitouch \ core/core_input_virtual_controls \ + core/core_keyboard_testbed \ core/core_monitor_detector \ core/core_random_sequence \ core/core_random_values \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 556900a76..3841dff29 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -530,6 +530,7 @@ CORE = \ core/core_input_mouse_wheel \ core/core_input_multitouch \ core/core_input_virtual_controls \ + core/core_keyboard_testbed \ core/core_monitor_detector \ core/core_random_sequence \ core/core_random_values \ @@ -820,6 +821,9 @@ core/core_input_multitouch: core/core_input_multitouch.c core/core_input_virtual_controls: core/core_input_virtual_controls.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) +core/core_keyboard_testbed: core/core_keyboard_testbed.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + core/core_monitor_detector: core/core_monitor_detector.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) diff --git a/examples/README.md b/examples/README.md index d9b03669d..6b2c1950b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -17,9 +17,9 @@ You may find it easier to use than other toolchains, especially when it comes to - `zig build [module]` to compile all examples for a module (e.g. `zig build core`) - `zig build [example]` to compile _and run_ a particular example (e.g. `zig build core_basic_window`) -## EXAMPLES COLLECTION [TOTAL: 206] +## EXAMPLES COLLECTION [TOTAL: 208] -### category: core [47] +### category: core [48] Examples using raylib [core](../src/rcore.c) module platform functionality: window creation, inputs, drawing modes and system functionality. @@ -72,6 +72,7 @@ Examples using raylib [core](../src/rcore.c) module platform functionality: wind | [core_clipboard_text](core/core_clipboard_text.c) | core_clipboard_text | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Ananth S](https://github.com/Ananth1839) | | [core_text_file_loading](core/core_text_file_loading.c) | core_text_file_loading | ⭐☆☆☆ | 5.5 | 5.6 | [Aanjishnu Bhattacharyya](https://github.com/NimComPoo-04) | | [core_compute_hash](core/core_compute_hash.c) | core_compute_hash | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | +| [core_keyboard_testbed](core/core_keyboard_testbed.c) | core_keyboard_testbed | ⭐⭐☆☆ | 5.6 | 5.6 | [Ramon Santamaria](https://github.com/raysan5) | ### category: shapes [39] @@ -119,7 +120,7 @@ Examples using raylib shapes drawing functionality, provided by raylib [shapes]( | [shapes_penrose_tile](shapes/shapes_penrose_tile.c) | shapes_penrose_tile | ⭐⭐⭐⭐️ | 5.5 | 5.6-dev | [David Buzatto](https://github.com/davidbuzatto) | | [shapes_hilbert_curve](shapes/shapes_hilbert_curve.c) | shapes_hilbert_curve | ⭐⭐⭐☆ | 5.6 | 5.6 | [Hamza RAHAL](https://github.com/hmz-rhl) | -### category: textures [29] +### category: textures [30] Examples using raylib textures functionality, including image/textures loading/generation and drawing, provided by raylib [textures](../src/rtextures.c) module. @@ -154,6 +155,7 @@ Examples using raylib textures functionality, including image/textures loading/g | [textures_textured_curve](textures/textures_textured_curve.c) | textures_textured_curve | ⭐⭐⭐☆ | 4.5 | 4.5 | [Jeffery Myers](https://github.com/JeffM2501) | | [textures_sprite_stacking](textures/textures_sprite_stacking.c) | textures_sprite_stacking | ⭐⭐☆☆ | 5.6-dev | 6.0 | [Robin](https://github.com/RobinsAviary) | | [textures_cellular_automata](textures/textures_cellular_automata.c) | textures_cellular_automata | ⭐⭐☆☆ | 5.6 | 5.6 | [Jordi Santonja](https://github.com/JordSant) | +| [textures_framebuffer_rendering](textures/textures_framebuffer_rendering.c) | textures_framebuffer_rendering | ⭐⭐☆☆ | 5.6 | 5.6 | [Jack Boakes](https://github.com/jackboakes) | ### category: text [16] diff --git a/examples/core/core_keyboard_testbed.c b/examples/core/core_keyboard_testbed.c new file mode 100644 index 000000000..904e9c90a --- /dev/null +++ b/examples/core/core_keyboard_testbed.c @@ -0,0 +1,333 @@ +/******************************************************************************************* +* +* raylib [core] example - keyboard testbed +* +* Example complexity rating: [★★☆☆] 2/4 +* +* NOTE: raylib defined keys refer to ENG-US Keyboard layout, +* mapping to other layouts is up to the user +* +* Example originally created with raylib 5.6, last time updated with raylib 5.6 +* +* 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) 2026 Ramon Santamaria (@raysan5) +* +********************************************************************************************/ + +#include "raylib.h" + +#define KEY_REC_SPACING 4 // Space in pixels between key rectangles + +//------------------------------------------------------------------------------------ +// Module Functions Declaration +//------------------------------------------------------------------------------------ +static const char *GetKeyText(int key); +static void GuiKeyboardKey(Rectangle bounds, int key); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [core] example - keyboard testbed"); + SetExitKey(KEY_NULL); // Avoid exit on KEY_ESCAPE + + // Keyboard line 01 + int line01KeyWidths[15] = { 0 }; + for (int i = 0; i < 15; i++) line01KeyWidths[i] = 45; + line01KeyWidths[13] = 62; // PRINTSCREEN + int line01Keys[15] = { + KEY_ESCAPE, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, + KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, + KEY_F12, KEY_PRINT_SCREEN, KEY_PAUSE + }; + + // Keyboard line 02 + int line02KeyWidths[15] = { 0 }; + for (int i = 0; i < 15; i++) line02KeyWidths[i] = 45; + line02KeyWidths[0] = 25; // GRAVE + line02KeyWidths[13] = 82; // BACKSPACE + int line02Keys[15] = { + KEY_GRAVE, KEY_ONE, KEY_TWO, KEY_THREE, KEY_FOUR, + KEY_FIVE, KEY_SIX, KEY_SEVEN, KEY_EIGHT, KEY_NINE, + KEY_ZERO, KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE, KEY_DELETE }; + + // Keyboard line 03 + int line03KeyWidths[15] = { 0 }; + for (int i = 0; i < 15; i++) line03KeyWidths[i] = 45; + line03KeyWidths[0] = 50; // TAB + line03KeyWidths[13] = 57; // BACKSLASH + int line03Keys[15] = { + KEY_TAB, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, + KEY_U, KEY_I, KEY_O, KEY_P, KEY_LEFT_BRACKET, + KEY_RIGHT_BRACKET, KEY_BACKSLASH, KEY_INSERT + }; + + // Keyboard line 04 + int line04KeyWidths[14] = { 0 }; + for (int i = 0; i < 14; i++) line04KeyWidths[i] = 45; + line04KeyWidths[0] = 68; // CAPS + line04KeyWidths[12] = 88; // ENTER + int line04Keys[14] = { + KEY_CAPS_LOCK, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, + KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, + KEY_APOSTROPHE, KEY_ENTER, KEY_PAGE_UP + }; + + // Keyboard line 05 + int line05KeyWidths[14] = { 0 }; + for (int i = 0; i < 14; i++) line05KeyWidths[i] = 45; + line05KeyWidths[0] = 80; // LSHIFT + line05KeyWidths[11] = 76; // RSHIFT + int line05Keys[14] = { + KEY_LEFT_SHIFT, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, + KEY_N, KEY_M, KEY_COMMA, KEY_PERIOD, /*KEY_MINUS*/ + KEY_SLASH, KEY_RIGHT_SHIFT, KEY_UP, KEY_PAGE_DOWN + }; + + // Keyboard line 06 + int line06KeyWidths[11] = { 0 }; + for (int i = 0; i < 11; i++) line06KeyWidths[i] = 45; + line06KeyWidths[0] = 80; // LCTRL + line06KeyWidths[3] = 208; // SPACE + line06KeyWidths[7] = 60; // RCTRL + int line06Keys[11] = { + KEY_LEFT_CONTROL, KEY_LEFT_SUPER, KEY_LEFT_ALT, + KEY_SPACE, KEY_RIGHT_ALT, 162, KEY_NULL, + KEY_RIGHT_CONTROL, KEY_LEFT, KEY_DOWN, KEY_RIGHT + }; + + Vector2 keyboardOffset = { 26, 80 }; + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + int key = GetKeyPressed(); // Get pressed keycode + if (key > 0) TraceLog(LOG_INFO, "KEYBOARD TESTBED: KEY PRESSED: %d", key); + + int ch = GetCharPressed(); // Get pressed char for text input, using OS mapping + if (ch > 0) TraceLog(LOG_INFO, "KEYBOARD TESTBED: CHAR PRESSED: %c (%d)", ch, ch); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + DrawText("KEYBOARD LAYOUT: ENG-US", 26, 38, 20, LIGHTGRAY); + + // Keyboard line 01 - 15 keys + // ESC, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, IMP, CLOSE + for (int i = 0, recOffsetX = 0; i < 15; i++) + { + GuiKeyboardKey((Rectangle){ keyboardOffset.x + recOffsetX, keyboardOffset.y, line01KeyWidths[i], 30 }, line01Keys[i]); + recOffsetX += line01KeyWidths[i] + KEY_REC_SPACING; + } + + // Keyboard line 02 - 15 keys + // `, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, -, =, BACKSPACE, DEL + for (int i = 0, recOffsetX = 0; i < 15; i++) + { + GuiKeyboardKey((Rectangle){ keyboardOffset.x + recOffsetX, keyboardOffset.y + 30 + KEY_REC_SPACING, line02KeyWidths[i], 38 }, line02Keys[i]); + recOffsetX += line02KeyWidths[i] + KEY_REC_SPACING; + } + + // Keyboard line 03 - 15 keys + // TAB, Q, W, E, R, T, Y, U, I, O, P, [, ], \, INS + for (int i = 0, recOffsetX = 0; i < 15; i++) + { + GuiKeyboardKey((Rectangle){ keyboardOffset.x + recOffsetX, keyboardOffset.y + 30 + 38 + KEY_REC_SPACING*2, line03KeyWidths[i], 38 }, line03Keys[i]); + recOffsetX += line03KeyWidths[i] + KEY_REC_SPACING; + } + + // Keyboard line 04 - 14 keys + // MAYUS, A, S, D, F, G, H, J, K, L, ;, ', ENTER, REPAG + for (int i = 0, recOffsetX = 0; i < 14; i++) + { + GuiKeyboardKey((Rectangle){ keyboardOffset.x + recOffsetX, keyboardOffset.y + 30 + 38*2 + KEY_REC_SPACING*3, line04KeyWidths[i], 38 }, line04Keys[i]); + recOffsetX += line04KeyWidths[i] + KEY_REC_SPACING; + } + + // Keyboard line 05 - 14 keys + // LSHIFT, Z, X, C, V, B, N, M, ,, ., /, RSHIFT, UP, AVPAG + for (int i = 0, recOffsetX = 0; i < 14; i++) + { + GuiKeyboardKey((Rectangle){ keyboardOffset.x + recOffsetX, keyboardOffset.y + 30 + 38*3 + KEY_REC_SPACING*4, line05KeyWidths[i], 38 }, line05Keys[i]); + recOffsetX += line05KeyWidths[i] + KEY_REC_SPACING; + } + + // Keyboard line 06 - 11 keys + // LCTRL, WIN, LALT, SPACE, ALTGR, \, FN, RCTRL, LEFT, DOWN, RIGHT + for (int i = 0, recOffsetX = 0; i < 11; i++) + { + GuiKeyboardKey((Rectangle){ keyboardOffset.x + recOffsetX, keyboardOffset.y + 30 + 38*4 + KEY_REC_SPACING*5, line06KeyWidths[i], 38 }, line06Keys[i]); + recOffsetX += line06KeyWidths[i] + KEY_REC_SPACING; + } + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} + +//------------------------------------------------------------------------------------ +// Module Functions Definition +//------------------------------------------------------------------------------------ +// Get keyboard keycode as text (US keyboard) +// NOTE: Mapping for other keyboard layouts can be done here +static const char *GetKeyText(int key) +{ + switch (key) + { + case KEY_APOSTROPHE : return "'"; // Key: ' + case KEY_COMMA : return ","; // Key: , + case KEY_MINUS : return "-"; // Key: - + case KEY_PERIOD : return "."; // Key: . + case KEY_SLASH : return "/"; // Key: / + case KEY_ZERO : return "0"; // Key: 0 + case KEY_ONE : return "1"; // Key: 1 + case KEY_TWO : return "2"; // Key: 2 + case KEY_THREE : return "3"; // Key: 3 + case KEY_FOUR : return "4"; // Key: 4 + case KEY_FIVE : return "5"; // Key: 5 + case KEY_SIX : return "6"; // Key: 6 + case KEY_SEVEN : return "7"; // Key: 7 + case KEY_EIGHT : return "8"; // Key: 8 + case KEY_NINE : return "9"; // Key: 9 + case KEY_SEMICOLON : return ";"; // Key: ; + case KEY_EQUAL : return "="; // Key: = + case KEY_A : return "A"; // Key: A | a + case KEY_B : return "B"; // Key: B | b + case KEY_C : return "C"; // Key: C | c + case KEY_D : return "D"; // Key: D | d + case KEY_E : return "E"; // Key: E | e + case KEY_F : return "F"; // Key: F | f + case KEY_G : return "G"; // Key: G | g + case KEY_H : return "H"; // Key: H | h + case KEY_I : return "I"; // Key: I | i + case KEY_J : return "J"; // Key: J | j + case KEY_K : return "K"; // Key: K | k + case KEY_L : return "L"; // Key: L | l + case KEY_M : return "M"; // Key: M | m + case KEY_N : return "N"; // Key: N | n + case KEY_O : return "O"; // Key: O | o + case KEY_P : return "P"; // Key: P | p + case KEY_Q : return "Q"; // Key: Q | q + case KEY_R : return "R"; // Key: R | r + case KEY_S : return "S"; // Key: S | s + case KEY_T : return "T"; // Key: T | t + case KEY_U : return "U"; // Key: U | u + case KEY_V : return "V"; // Key: V | v + case KEY_W : return "W"; // Key: W | w + case KEY_X : return "X"; // Key: X | x + case KEY_Y : return "Y"; // Key: Y | y + case KEY_Z : return "Z"; // Key: Z | z + case KEY_LEFT_BRACKET : return "["; // Key: [ + case KEY_BACKSLASH : return "\\"; // Key: '\' + case KEY_RIGHT_BRACKET : return "]"; // Key: ] + case KEY_GRAVE : return "`"; // Key: ` + case KEY_SPACE : return "SPACE"; // Key: Space + case KEY_ESCAPE : return "ESC"; // Key: Esc + case KEY_ENTER : return "ENTER"; // Key: Enter + case KEY_TAB : return "TAB"; // Key: Tab + case KEY_BACKSPACE : return "BACK"; // Key: Backspace + case KEY_INSERT : return "INS"; // Key: Ins + case KEY_DELETE : return "DEL"; // Key: Del + case KEY_RIGHT : return "RIGHT"; // Key: Cursor right + case KEY_LEFT : return "LEFT"; // Key: Cursor left + case KEY_DOWN : return "DOWN"; // Key: Cursor down + case KEY_UP : return "UP"; // Key: Cursor up + case KEY_PAGE_UP : return "PGUP"; // Key: Page up + case KEY_PAGE_DOWN : return "PGDOWN"; // Key: Page down + case KEY_HOME : return "HOME"; // Key: Home + case KEY_END : return "END"; // Key: End + case KEY_CAPS_LOCK : return "CAPS"; // Key: Caps lock + case KEY_SCROLL_LOCK : return "LOCK"; // Key: Scroll down + case KEY_NUM_LOCK : return "NUMLOCK"; // Key: Num lock + case KEY_PRINT_SCREEN : return "PRINTSCR"; // Key: Print screen + case KEY_PAUSE : return "PAUSE"; // Key: Pause + case KEY_F1 : return "F1"; // Key: F1 + case KEY_F2 : return "F2"; // Key: F2 + case KEY_F3 : return "F3"; // Key: F3 + case KEY_F4 : return "F4"; // Key: F4 + case KEY_F5 : return "F5"; // Key: F5 + case KEY_F6 : return "F6"; // Key: F6 + case KEY_F7 : return "F7"; // Key: F7 + case KEY_F8 : return "F8"; // Key: F8 + case KEY_F9 : return "F9"; // Key: F9 + case KEY_F10 : return "F10"; // Key: F10 + case KEY_F11 : return "F11"; // Key: F11 + case KEY_F12 : return "F12"; // Key: F12 + case KEY_LEFT_SHIFT : return "LSHIFT"; // Key: Shift left + case KEY_LEFT_CONTROL : return "LCTRL"; // Key: Control left + case KEY_LEFT_ALT : return "LALT"; // Key: Alt left + case KEY_LEFT_SUPER : return "WIN"; // Key: Super left + case KEY_RIGHT_SHIFT : return "RSHIFT"; // Key: Shift right + case KEY_RIGHT_CONTROL : return "RCTRL"; // Key: Control right + case KEY_RIGHT_ALT : return "ALTGR"; // Key: Alt right + case KEY_RIGHT_SUPER : return "RSUPER"; // Key: Super right + case KEY_KB_MENU : return "KBMENU"; // Key: KB menu + case KEY_KP_0 : return "KP0"; // Key: Keypad 0 + case KEY_KP_1 : return "KP1"; // Key: Keypad 1 + case KEY_KP_2 : return "KP2"; // Key: Keypad 2 + case KEY_KP_3 : return "KP3"; // Key: Keypad 3 + case KEY_KP_4 : return "KP4"; // Key: Keypad 4 + case KEY_KP_5 : return "KP5"; // Key: Keypad 5 + case KEY_KP_6 : return "KP6"; // Key: Keypad 6 + case KEY_KP_7 : return "KP7"; // Key: Keypad 7 + case KEY_KP_8 : return "KP8"; // Key: Keypad 8 + case KEY_KP_9 : return "KP9"; // Key: Keypad 9 + case KEY_KP_DECIMAL : return "KPDEC"; // Key: Keypad . + case KEY_KP_DIVIDE : return "KPDIV"; // Key: Keypad / + case KEY_KP_MULTIPLY : return "KPMUL"; // Key: Keypad * + case KEY_KP_SUBTRACT : return "KPSUB"; // Key: Keypad - + case KEY_KP_ADD : return "KPADD"; // Key: Keypad + + case KEY_KP_ENTER : return "KPENTER"; // Key: Keypad Enter + case KEY_KP_EQUAL : return "KPEQU"; // Key: Keypad = + default: return ""; + } +} + +// Draw keyboard key +static void GuiKeyboardKey(Rectangle bounds, int key) +{ + if (key == KEY_NULL) DrawRectangleLinesEx(bounds, 2.0f, LIGHTGRAY); + else + { + if (IsKeyDown(key)) + { + DrawRectangleLinesEx(bounds, 2.0f, MAROON); + DrawText(GetKeyText(key), bounds.x + 4, bounds.y + 4, 10, MAROON); + } + else + { + DrawRectangleLinesEx(bounds, 2.0f, DARKGRAY); + DrawText(GetKeyText(key), bounds.x + 4, bounds.y + 4, 10, DARKGRAY); + } + } + + if (CheckCollisionPointRec(GetMousePosition(), bounds)) + { + DrawRectangleRec(bounds, Fade(RED, 0.2f)); + DrawRectangleLinesEx(bounds, 3.0f, RED); + } +} \ No newline at end of file diff --git a/examples/core/core_keyboard_testbed.png b/examples/core/core_keyboard_testbed.png new file mode 100644 index 0000000000000000000000000000000000000000..bac0fc29ac41ee31592622b54bf65af602427995 GIT binary patch literal 17631 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 literal 0 HcmV?d00001 diff --git a/examples/examples_list.txt b/examples/examples_list.txt index c825e137d..eda62ee13 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -56,6 +56,7 @@ core;core_screen_recording;★★☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santama core;core_clipboard_text;★★☆☆;5.6-dev;5.6-dev;2025;2025;"Ananth S";@Ananth1839 core;core_text_file_loading;★☆☆☆;5.5;5.6;0;0;"Aanjishnu Bhattacharyya";@NimComPoo-04 core;core_compute_hash;★★☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 +core;core_keyboard_testbed;★★☆☆;5.6;5.6;2026;2026;"Ramon Santamaria";@raysan5 shapes;shapes_basic_shapes;★☆☆☆;1.0;4.2;2014;2025;"Ramon Santamaria";@raysan5 shapes;shapes_bouncing_ball;★☆☆☆;2.5;2.5;2013;2025;"Ramon Santamaria";@raysan5 shapes;shapes_bullet_hell;★☆☆☆;5.6;5.6;2025;2025;"Zero";@zerohorsepower diff --git a/projects/VS2022/examples/core_keyboard_testbed.vcxproj b/projects/VS2022/examples/core_keyboard_testbed.vcxproj new file mode 100644 index 000000000..3a146b762 --- /dev/null +++ b/projects/VS2022/examples/core_keyboard_testbed.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {6B1A933E-71B8-4C1F-9E79-02D98830E671} + Win32Proj + core_keyboard_testbed + 10.0 + core_keyboard_testbed + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\core + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index df2633843..93c4efaf1 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -433,6 +433,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "textures_cellular_automata" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_hilbert_curve", "examples\shapes_hilbert_curve.vcxproj", "{DC163251-16C3-4B72-B965-ACDBA0F02BD1}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_keyboard_testbed", "examples\core_keyboard_testbed.vcxproj", "{6B1A933E-71B8-4C1F-9E79-02D98830E671}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "textures_framebuffer_rendering", "examples\textures_framebuffer_rendering.vcxproj", "{2CCCD9E4-9058-4291-BD89-39C979F0CA1E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug.DLL|ARM64 = Debug.DLL|ARM64 @@ -5391,6 +5395,54 @@ Global {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release|x64.Build.0 = Release|x64 {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release|x86.ActiveCfg = Release|Win32 {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release|x86.Build.0 = Release|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|ARM64.Build.0 = Debug|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x64.ActiveCfg = Debug|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x64.Build.0 = Debug|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x86.ActiveCfg = Debug|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x86.Build.0 = Debug|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|ARM64.ActiveCfg = Release|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|ARM64.Build.0 = Release|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x64.ActiveCfg = Release|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x64.Build.0 = Release|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x86.ActiveCfg = Release|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x86.Build.0 = Release|Win32 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug|ARM64.Build.0 = Debug|ARM64 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug|x64.ActiveCfg = Debug|x64 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug|x64.Build.0 = Debug|x64 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug|x86.ActiveCfg = Debug|Win32 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug|x86.Build.0 = Debug|Win32 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release|ARM64.ActiveCfg = Release|ARM64 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release|ARM64.Build.0 = Release|ARM64 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release|x64.ActiveCfg = Release|x64 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release|x64.Build.0 = Release|x64 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release|x86.ActiveCfg = Release|Win32 + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5609,6 +5661,8 @@ Global {1F4722E7-F78E-413F-A106-D3490211EA57} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A} {0A0FC982-6E31-401F-BA77-3C5E8AB02C68} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} {DC163251-16C3-4B72-B965-ACDBA0F02BD1} = {278D8859-20B1-428F-8448-064F46E1F021} + {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} diff --git a/tools/rexm/reports/examples_issues.md b/tools/rexm/reports/examples_issues.md index 7b39c7a0b..081170806 100644 --- a/tools/rexm/reports/examples_issues.md +++ b/tools/rexm/reports/examples_issues.md @@ -21,7 +21,6 @@ Example elements validated: | **EXAMPLE NAME** | [C] | [CAT]| [INFO]|[PNG]|[WPNG]| [RES]| [MK] |[MKWEB]| [VCX]| [SOL]|[RDME]|[JS] | [WOUT]|[WMETA]| |:---------------------------------|:---:|:----:|:-----:|:---:|:----:|:----:|:----:|:-----:|:----:|:----:|:----:|:---:|:-----:|:-----:| | core_highdpi_testbed | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| core_keyboard_testbed | ✔ | ✔ | ✔ | ❌ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | rlgl_standalone | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | rlgl_compute_shader | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | easings_testbed | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | diff --git a/tools/rexm/reports/examples_validation.md b/tools/rexm/reports/examples_validation.md index 8d3f699ca..d8f4a9521 100644 --- a/tools/rexm/reports/examples_validation.md +++ b/tools/rexm/reports/examples_validation.md @@ -67,6 +67,7 @@ Example elements validated: | core_clipboard_text | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_text_file_loading | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | core_compute_hash | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_keyboard_testbed | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_basic_shapes | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_bouncing_ball | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_bullet_hell | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | From a6dd2af9e993a3a1d22696324e01f5b4f446c890 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 9 Jan 2026 20:06:57 +0100 Subject: [PATCH 347/430] Update rexm.c --- tools/rexm/rexm.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 9914733f0..95ed13739 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -570,7 +570,7 @@ int main(int argc, char *argv[]) // ----------------------------------------------------------------------------------------- // Add example to the collection list, if not already there - // NOTE: Required format: shapes;shapes_basic_shapes;★☆☆☆;1.0;4.2;2014;2025;"Ray";@raysan5 + // NOTE: Required format: shapes;shapes_basic_shapes;★☆☆☆;1.0;4.2;2014;2026;"Ray";@raysan5 //------------------------------------------------------------------------------------------------ char *exCollectionList = LoadFileText(exCollectionFilePath); if (TextFindIndex(exCollectionList, exName) == -1) // Example not found @@ -2440,7 +2440,7 @@ static void UnloadExampleInfo(rlExampleInfo *exInfo) } // raylib example line info parser -// Parses following line format: core;core_basic_window;★☆☆☆;1.0;1.0;2013;2025;"Ray";@raysan5 +// Parses following line format: core;core_basic_window;★☆☆☆;1.0;1.0;2013;2026;"Ray";@raysan5 static int ParseExampleInfoLine(const char *line, rlExampleInfo *entry) { #define MAX_EXAMPLE_INFO_LINE_LEN 512 @@ -2452,7 +2452,10 @@ static int ParseExampleInfoLine(const char *line, rlExampleInfo *entry) int tokenCount = 0; char **tokens = TextSplit(line, ';', &tokenCount); - if (tokenCount != 9) LOG("REXM: WARNING: Example collection line contains invalid number of tokens: %i\n", tokenCount); + if (tokenCount != 9) + { + LOG("REXM: WARNING: Example collection line contains invalid number of tokens: %i\n", tokenCount); + } // Get category and name strcpy(entry->category, tokens[0]); From 1284d687213ae53313cf69f283e04a0e6c200c17 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 9 Jan 2026 20:18:45 +0100 Subject: [PATCH 348/430] Update core_highdpi_testbed.png --- examples/core/core_highdpi_testbed.png | Bin 17323 -> 18560 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/core/core_highdpi_testbed.png b/examples/core/core_highdpi_testbed.png index da99bbb0d97118eef3ca41c16b62182acced63f9..a37c821304b22cc7cbcb463de52eb95d0b5c2a90 100644 GIT binary patch literal 18560 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 literal 17323 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 aXx{Fh`%kLYY!czW3FF=h-xU_U{Qm(sHl%3) From 5b0a799769da9a2ebc662d4aab1f31cf85882c56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=AErvu=20Mihai=20Cristian?= Date: Sat, 10 Jan 2026 00:09:06 +0200 Subject: [PATCH 349/430] fix (#5482) --- src/shell.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shell.html b/src/shell.html index e6c80a39b..e5e26a3b1 100644 --- a/src/shell.html +++ b/src/shell.html @@ -179,7 +179,7 @@ jwE50AGjLCVuS8Yt4H7OgZLKK5EKOsLviEWJSL/+0uMi7gLUSBseYwqEbXvSHCec1CJvZPyHCmYQffaB - + From 8de88c71daa21b943daef1b08b2c39013a868d74 Mon Sep 17 00:00:00 2001 From: lucas150670 Date: Sat, 10 Jan 2026 17:54:54 +0800 Subject: [PATCH 350/430] fix: fall back on dirent filename when all route fails on Android (#5484) --- src/utils.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils.c b/src/utils.c index 82d7d0aa2..28939b4f8 100644 --- a/src/utils.c +++ b/src/utils.c @@ -473,7 +473,9 @@ FILE *android_fopen(const char *fileName, const char *mode) { #undef fopen // Just do a regular open if file is not found in the assets - return fopen(TextFormat("%s/%s", internalDataPath, fileName), mode); + if(fopen(TextFormat("%s/%s", internalDataPath, fileName), mode) == NULL) { + return fopen(fileName, mode); + } #define fopen(name, mode) android_fopen(name, mode) } } From 683330582679397f225212bfcf925684e351954b Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 10 Jan 2026 10:59:22 +0100 Subject: [PATCH 351/430] REVIEWED: `android_fopen()` --- src/utils.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/utils.c b/src/utils.c index 28939b4f8..80c8dec55 100644 --- a/src/utils.c +++ b/src/utils.c @@ -449,6 +449,8 @@ void InitAssetManager(AAssetManager *manager, const char *dataPath) // REF: https://developer.android.com/ndk/reference/group/asset FILE *android_fopen(const char *fileName, const char *mode) { + FILE *file = NULL; + if (mode[0] == 'w') { // NOTE: fopen() is mapped to android_fopen() that only grants read access to @@ -456,7 +458,7 @@ FILE *android_fopen(const char *fileName, const char *mode) // write data when required using the standard stdio FILE access functions // REF: https://stackoverflow.com/questions/11294487/android-writing-saving-files-from-native-code-only #undef fopen - return fopen(TextFormat("%s/%s", internalDataPath, fileName), mode); + file = fopen(TextFormat("%s/%s", internalDataPath, fileName), mode); #define fopen(name, mode) android_fopen(name, mode) } else @@ -467,18 +469,19 @@ FILE *android_fopen(const char *fileName, const char *mode) if (asset != NULL) { // Get pointer to file in the assets - return funopen(asset, android_read, android_write, android_seek, android_close); + file = funopen(asset, android_read, android_write, android_seek, android_close); } else { #undef fopen // Just do a regular open if file is not found in the assets - if(fopen(TextFormat("%s/%s", internalDataPath, fileName), mode) == NULL) { - return fopen(fileName, mode); - } + file = fopen(TextFormat("%s/%s", internalDataPath, fileName), mode); + if (file == NULL) file = fopen(fileName, mode); #define fopen(name, mode) android_fopen(name, mode) } } + + return file; } #endif // PLATFORM_ANDROID From c7de5c9d4b2f0ec0ba049db5ffa4036fa63d5fff Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 10 Jan 2026 11:53:29 +0100 Subject: [PATCH 352/430] Updated examples VCXPROJ UUID, not correctly calculated by REXM --- projects/VS2022/examples/core_keyboard_testbed.vcxproj | 2 +- projects/VS2022/examples/textures_framebuffer_rendering.vcxproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/VS2022/examples/core_keyboard_testbed.vcxproj b/projects/VS2022/examples/core_keyboard_testbed.vcxproj index 3a146b762..2278f4ec5 100644 --- a/projects/VS2022/examples/core_keyboard_testbed.vcxproj +++ b/projects/VS2022/examples/core_keyboard_testbed.vcxproj @@ -51,7 +51,7 @@ - {6B1A933E-71B8-4C1F-9E79-02D98830E671} + {D35D2FDA-B53F-4F70-81CA-24D95812B89C} Win32Proj core_keyboard_testbed 10.0 diff --git a/projects/VS2022/examples/textures_framebuffer_rendering.vcxproj b/projects/VS2022/examples/textures_framebuffer_rendering.vcxproj index 3a7eeeb35..3e845545e 100644 --- a/projects/VS2022/examples/textures_framebuffer_rendering.vcxproj +++ b/projects/VS2022/examples/textures_framebuffer_rendering.vcxproj @@ -51,7 +51,7 @@ - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E} + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C} Win32Proj textures_framebuffer_rendering 10.0 From 2f6feb74d49eab6b3384440c51aff46c40326e44 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 10 Jan 2026 11:53:46 +0100 Subject: [PATCH 353/430] Update raylib.sln --- projects/VS2022/raylib.sln | 106 ++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 54 deletions(-) diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index 93c4efaf1..9b088f52d 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -433,9 +433,9 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "textures_cellular_automata" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_hilbert_curve", "examples\shapes_hilbert_curve.vcxproj", "{DC163251-16C3-4B72-B965-ACDBA0F02BD1}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_keyboard_testbed", "examples\core_keyboard_testbed.vcxproj", "{6B1A933E-71B8-4C1F-9E79-02D98830E671}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_keyboard_testbed", "examples\core_keyboard_testbed.vcxproj", "{D35D2FDA-B53F-4F70-81CA-24D95812B89C}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "textures_framebuffer_rendering", "examples\textures_framebuffer_rendering.vcxproj", "{2CCCD9E4-9058-4291-BD89-39C979F0CA1E}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "textures_framebuffer_rendering", "examples\textures_framebuffer_rendering.vcxproj", "{F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -5395,54 +5395,54 @@ Global {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release|x64.Build.0 = Release|x64 {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release|x86.ActiveCfg = Release|Win32 {DC163251-16C3-4B72-B965-ACDBA0F02BD1}.Release|x86.Build.0 = Release|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|ARM64.Build.0 = Debug|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x64.ActiveCfg = Debug|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x64.Build.0 = Debug|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x86.ActiveCfg = Debug|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x86.Build.0 = Debug|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x64.Build.0 = Release.DLL|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x86.Build.0 = Release.DLL|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|ARM64.ActiveCfg = Release|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|ARM64.Build.0 = Release|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x64.ActiveCfg = Release|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x64.Build.0 = Release|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x86.ActiveCfg = Release|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x86.Build.0 = Release|Win32 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug|ARM64.Build.0 = Debug|ARM64 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug|x64.ActiveCfg = Debug|x64 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug|x64.Build.0 = Debug|x64 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug|x86.ActiveCfg = Debug|Win32 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Debug|x86.Build.0 = Debug|Win32 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release.DLL|x64.Build.0 = Release.DLL|x64 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release.DLL|x86.Build.0 = Release.DLL|Win32 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release|ARM64.ActiveCfg = Release|ARM64 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release|ARM64.Build.0 = Release|ARM64 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release|x64.ActiveCfg = Release|x64 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release|x64.Build.0 = Release|x64 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release|x86.ActiveCfg = Release|Win32 - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E}.Release|x86.Build.0 = Release|Win32 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Debug|ARM64.Build.0 = Debug|ARM64 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Debug|x64.ActiveCfg = Debug|x64 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Debug|x64.Build.0 = Debug|x64 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Debug|x86.ActiveCfg = Debug|Win32 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Debug|x86.Build.0 = Debug|Win32 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Release|ARM64.ActiveCfg = Release|ARM64 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Release|ARM64.Build.0 = Release|ARM64 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Release|x64.ActiveCfg = Release|x64 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Release|x64.Build.0 = Release|x64 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Release|x86.ActiveCfg = Release|Win32 + {D35D2FDA-B53F-4F70-81CA-24D95812B89C}.Release|x86.Build.0 = Release|Win32 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Debug|ARM64.Build.0 = Debug|ARM64 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Debug|x64.ActiveCfg = Debug|x64 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Debug|x64.Build.0 = Debug|x64 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Debug|x86.ActiveCfg = Debug|Win32 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Debug|x86.Build.0 = Debug|Win32 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Release|ARM64.ActiveCfg = Release|ARM64 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Release|ARM64.Build.0 = Release|ARM64 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Release|x64.ActiveCfg = Release|x64 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Release|x64.Build.0 = Release|x64 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Release|x86.ActiveCfg = Release|Win32 + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5610,7 +5610,7 @@ Global {C54703BF-D68A-480D-BE27-49B62E45D582} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {9CD8BCAD-F212-4BCC-BA98-899743CE3279} = {CC132A4D-D081-4C26-BFB9-AB11984054F8} {0981CA28-E4A5-4DF1-987F-A41D09131EFC} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} - {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {278D8859-20B1-428F-8448-064F46E1F021} + {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {6BFF72EA-7362-4A3B-B6E5-9A3655BBBDA3} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {6777EC3C-077C-42FC-B4AD-B799CE55CCE4} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A} {A61DAD9C-271C-4E95-81AA-DB4CD58564D4} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} @@ -5619,7 +5619,7 @@ Global {3B27F358-2679-4F38-B297-17B536F580BB} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {718FCBD0-591D-448C-B7D5-9F1CA8544E7B} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {19CA0070-B4B2-4394-90B7-D0C259AA35BA} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E} = {278D8859-20B1-428F-8448-064F46E1F021} + {2CCCD9E4-9058-4291-BD89-39C979F0CA1E} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} {9DB1F875-6E65-4195-B23F-ED8095C0B99C} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A} {52BA9067-A5FC-4CE8-82AD-7204ECFDEF9F} = {AF5BEC5C-1F2B-4DA8-B12D-D09FE569237C} {8E132D5A-2C00-48D0-8747-97E41356F26F} = {278D8859-20B1-428F-8448-064F46E1F021} @@ -5661,8 +5661,6 @@ Global {1F4722E7-F78E-413F-A106-D3490211EA57} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A} {0A0FC982-6E31-401F-BA77-3C5E8AB02C68} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} {DC163251-16C3-4B72-B965-ACDBA0F02BD1} = {278D8859-20B1-428F-8448-064F46E1F021} - {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} - {2CCCD9E4-9058-4291-BD89-39C979F0CA1E} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} From dd7a1948f1c5b97b6e6e4c80cc0ae17d99ded8c6 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 10 Jan 2026 12:13:07 +0100 Subject: [PATCH 354/430] WARNING: REDESIGN: REMOVED: `utils` module, functionality moved to `rcore` module: logging and file-system #4551 [utils] was created long time ago, when [rcore] contained all the platforms code, the purpose of the file was exposing basic filesystem functionality across modules and also logging mechanism but many things have changed since then and there is no need to keep using this module. - Logging system has been move to [rcore] module and macros are exposed through `config.h` to other modules - File system functionality has also been centralized in [rcore] module that along the years it was already adding more and more file-system functions, now they are all in the same module - Android specific code has been moved to `rcore_android.c`, it had no sense to have specific platform code in `utils`, [rcore] is responsible of all platform code. --- build.zig | 2 +- projects/VS2022/raylib/raylib.vcxproj | 1 - projects/VS2022/raylib/raylib.vcxproj.filters | 3 - src/Makefile | 13 +- src/config.h | 27 +- src/platforms/rcore_android.c | 82 ++- src/platforms/rcore_desktop_glfw.c | 4 - src/platforms/rcore_desktop_rgfw.c | 5 +- src/platforms/rcore_desktop_sdl.c | 3 - src/platforms/rcore_desktop_win32.c | 3 - src/platforms/rcore_drm.c | 3 - src/platforms/rcore_memory.c | 3 - src/platforms/rcore_web.c | 3 - src/platforms/rcore_web_emscripten.c | 3 - src/raudio.c | 2 +- src/raylib.h | 59 +- src/rcore.c | 407 +++++++++++++- src/rmodels.c | 1 - src/rtext.c | 1 - src/rtextures.c | 1 - src/utils.c | 514 ------------------ src/utils.h | 74 --- 22 files changed, 525 insertions(+), 689 deletions(-) delete mode 100644 src/utils.c delete mode 100644 src/utils.h diff --git a/build.zig b/build.zig index 239b10f9e..2e53bfbdc 100644 --- a/build.zig +++ b/build.zig @@ -197,7 +197,7 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. } var c_source_files: std.ArrayList([]const u8) = try .initCapacity(b.allocator, 2); - c_source_files.appendSliceAssumeCapacity(&.{ "src/rcore.c", "src/utils.c" }); + c_source_files.appendSliceAssumeCapacity(&.{ "src/rcore.c" }); if (options.rshapes) { try c_source_files.append(b.allocator, "src/rshapes.c"); diff --git a/projects/VS2022/raylib/raylib.vcxproj b/projects/VS2022/raylib/raylib.vcxproj index 3a7082d77..287410f06 100644 --- a/projects/VS2022/raylib/raylib.vcxproj +++ b/projects/VS2022/raylib/raylib.vcxproj @@ -582,7 +582,6 @@ - diff --git a/projects/VS2022/raylib/raylib.vcxproj.filters b/projects/VS2022/raylib/raylib.vcxproj.filters index 33030fc9c..75cc28a7e 100644 --- a/projects/VS2022/raylib/raylib.vcxproj.filters +++ b/projects/VS2022/raylib/raylib.vcxproj.filters @@ -22,9 +22,6 @@ Source Files - - Source Files - Source Files\Platform Files diff --git a/src/Makefile b/src/Makefile index 89dd759aa..459b79f83 100644 --- a/src/Makefile +++ b/src/Makefile @@ -658,8 +658,7 @@ endif OBJS = rcore.o \ rshapes.o \ rtextures.o \ - rtext.o \ - utils.o + rtext.o ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW) ifeq ($(USE_EXTERNAL_GLFW),FALSE) @@ -758,7 +757,7 @@ endif rcore.o : platforms/*.c # Compile core module -rcore.o : rcore.c raylib.h rlgl.h utils.h raymath.h rcamera.h rgestures.h +rcore.o : rcore.c raylib.h rlgl.h raymath.h rcamera.h rgestures.h $(CC) -c $< $(CFLAGS) $(INCLUDE_PATHS) # Compile rglfw module @@ -770,15 +769,11 @@ rshapes.o : rshapes.c raylib.h rlgl.h $(CC) -c $< $(CFLAGS) $(INCLUDE_PATHS) # Compile textures module -rtextures.o : rtextures.c raylib.h rlgl.h utils.h +rtextures.o : rtextures.c raylib.h rlgl.h $(CC) -c $< $(CFLAGS) $(INCLUDE_PATHS) # Compile text module -rtext.o : rtext.c raylib.h utils.h - $(CC) -c $< $(CFLAGS) $(INCLUDE_PATHS) - -# Compile utils module -utils.o : utils.c utils.h +rtext.o : rtext.c raylib.h $(CC) -c $< $(CFLAGS) $(INCLUDE_PATHS) # Compile models module diff --git a/src/config.h b/src/config.h index 1286e5082..68b42cc0e 100644 --- a/src/config.h +++ b/src/config.h @@ -30,7 +30,7 @@ //------------------------------------------------------------------------------------ // Module selection - Some modules could be avoided -// Mandatory modules: rcore, rlgl, utils +// Mandatory modules: rcore, rlgl //------------------------------------------------------------------------------------ #define SUPPORT_MODULE_RSHAPES 1 #define SUPPORT_MODULE_RTEXTURES 1 @@ -41,6 +41,16 @@ //------------------------------------------------------------------------------------ // Module: rcore - Configuration Flags //------------------------------------------------------------------------------------ +// Standard file io library (stdio.h) included +#define SUPPORT_STANDARD_FILEIO 1 +// Show TRACELOG() output messages +#define SUPPORT_TRACELOG 1 +#if defined(SUPPORT_TRACELOG) + #define TRACELOG(level, ...) TraceLog(level, __VA_ARGS__) +#else + #define TRACELOG(level, ...) (void)0 +#endif + // Camera module is included (rcamera.h) and multiple predefined cameras are available: free, 1st/3rd person, orbital #define SUPPORT_CAMERA_SYSTEM 1 // Gestures module is included (rgestures.h) to support gestures detection: tap, hold, swipe, drag @@ -72,7 +82,7 @@ // Support for clipboard image loading // NOTE: Only working on SDL3, GLFW (Windows) and RGFW (Windows) -#define SUPPORT_CLIPBOARD_IMAGE 1 +#define SUPPORT_CLIPBOARD_IMAGE 1 // NOTE: Clipboard image loading requires support for some image file formats // TODO: Those defines should probably be removed from here, letting the user manage them @@ -96,6 +106,7 @@ // rcore: Configuration values //------------------------------------------------------------------------------------ +#define MAX_TRACELOG_MSG_LENGTH 256 // Max length of one trace-log message #define MAX_FILEPATH_CAPACITY 8192 // Maximum file paths capacity #define MAX_FILEPATH_LENGTH 4096 // Maximum length for filepaths (Linux PATH_MAX default value) @@ -281,16 +292,4 @@ #define MAX_AUDIO_BUFFER_POOL_CHANNELS 16 // Maximum number of audio pool channels -//------------------------------------------------------------------------------------ -// Module: utils - Configuration Flags -//------------------------------------------------------------------------------------ -// Standard file io library (stdio.h) included -#define SUPPORT_STANDARD_FILEIO 1 -// Show TRACELOG() output messages -#define SUPPORT_TRACELOG 1 - -// utils: Configuration values -//------------------------------------------------------------------------------------ -#define MAX_TRACELOG_MSG_LENGTH 256 // Max length of one trace-log message - #endif // CONFIG_H diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 19b686cef..6122f9a74 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -13,9 +13,6 @@ * - Improvement 01 * - Improvement 02 * -* 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- @@ -48,7 +45,11 @@ #include // Required for: android_app struct and activity management #include // Required for: AWINDOW_FLAG_FULLSCREEN definition and others +#include // Required for: Android log system: __android_log_vprint() +#include // Required for: AAssetManager //#include // Required for: Android sensors functions (accelerometer, gyroscope, light...) + +#include // Required for: error types #include // Required for: JNIEnv and JavaVM [Used in OpenURL() and GetCurrentMonitor()] #include // Native platform windowing system interface @@ -269,6 +270,17 @@ static GamepadButton AndroidTranslateGamepadButton(int button); static void SetupFramebuffer(int width, int height); // Setup main framebuffer (required by InitPlatform()) +static int android_read(void *cookie, char *buf, int size); +static int android_write(void *cookie, const char *buf, int size); +static fpos_t android_seek(void *cookie, fpos_t offset, int whence); +static int android_close(void *cookie); + +FILE *android_fopen(const char *fileName, const char *mode); // Replacement for fopen() -> Read-only! +FILE *funopen(const void *cookie, int (*readfn)(void *, char *, int), int (*writefn)(void *, const char *, int), + fpos_t (*seekfn)(void *, fpos_t, int), int (*closefn)(void *)); + +#define fopen(name, mode) android_fopen(name, mode) + //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- @@ -819,8 +831,6 @@ int InitPlatform(void) // Initialize storage system //---------------------------------------------------------------------------- - InitAssetManager(platform.app->activity->assetManager, platform.app->activity->internalDataPath); // Initialize assets manager - CORE.Storage.basePath = platform.app->activity->internalDataPath; // Define base path for storage //---------------------------------------------------------------------------- @@ -1514,4 +1524,66 @@ static void SetupFramebuffer(int width, int height) } } +// Replacement for fopen() +// REF: https://developer.android.com/ndk/reference/group/asset +FILE *android_fopen(const char *fileName, const char *mode) +{ + FILE *file = NULL; + + if (mode[0] == 'w') + { + // NOTE: fopen() is mapped to android_fopen() that only grants read access to + // assets directory through AAssetManager but we want to also be able to + // write data when required using the standard stdio FILE access functions + // REF: https://stackoverflow.com/questions/11294487/android-writing-saving-files-from-native-code-only + #undef fopen + file = fopen(TextFormat("%s/%s", platform.app->activity->internalDataPath, fileName), mode); + #define fopen(name, mode) android_fopen(name, mode) + } + else + { + // NOTE: AAsset provides access to read-only asset + AAsset *asset = AAssetManager_open(platform.app->activity->assetManager, fileName, AASSET_MODE_UNKNOWN); + + if (asset != NULL) + { + // Get pointer to file in the assets + file = funopen(asset, android_read, android_write, android_seek, android_close); + } + else + { + #undef fopen + // Just do a regular open if file is not found in the assets + file = fopen(TextFormat("%s/%s", platform.app->activity->internalDataPath, fileName), mode); + if (file == NULL) file = fopen(fileName, mode); + #define fopen(name, mode) android_fopen(name, mode) + } + } + + return file; +} + +static int android_read(void *cookie, char *data, int dataSize) +{ + return AAsset_read((AAsset *)cookie, data, dataSize); +} + +static int android_write(void *cookie, const char *data, int dataSize) +{ + TRACELOG(LOG_WARNING, "ANDROID: Failed to provide write access to APK"); + + return EACCES; +} + +static fpos_t android_seek(void *cookie, fpos_t offset, int whence) +{ + return AAsset_seek((AAsset *)cookie, offset, whence); +} + +static int android_close(void *cookie) +{ + AAsset_close((AAsset *)cookie); + return 0; +} + // EOF diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 84b021573..f39e256aa 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -16,9 +16,6 @@ * - Improvement 01 * - Improvement 02 * -* 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- @@ -1364,7 +1361,6 @@ void PollInputEvents(void) //---------------------------------------------------------------------------------- // Function wrappers around RL_*alloc macros, used by glfwInitAllocator() inside of InitPlatform() // We need to provide these because GLFWallocator expects function pointers with specific signatures -// Similar wrappers exist in utils.c but we cannot reuse them here due to declaration mismatch // REF: https://www.glfw.org/docs/latest/intro_guide.html#init_allocator static void *AllocateWrapper(size_t size, void *user) { diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 05960c14b..d1518b909 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -13,10 +13,7 @@ * - TODO * * POSSIBLE IMPROVEMENTS: -* - TODO -* -* ADDITIONAL NOTES: -* - TRACELOG() function is located in raylib [utils] module +* - TBD * * CONFIGURATION: * #define RCORE_PLATFORM_RGFW diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 0279c0c28..eabea6bfe 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -15,9 +15,6 @@ * - Improvement 01 * - Improvement 02 * -* 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- diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 7ef01a1a0..9f33dce1b 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -13,9 +13,6 @@ * - Improvement 01 * - Improvement 02 * -* 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- diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index eedb915e2..2e224ad92 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -13,9 +13,6 @@ * - Improvement 01 * - Improvement 02 * -* ADDITIONAL NOTES: -* - TRACELOG() function is located in raylib [utils] module -* * CONFIGURATION: * #define SUPPORT_SSH_KEYBOARD_RPI (Raspberry Pi only) * Reconfigure standard input to receive key inputs, works with SSH connection diff --git a/src/platforms/rcore_memory.c b/src/platforms/rcore_memory.c index 04d343164..c9409a750 100644 --- a/src/platforms/rcore_memory.c +++ b/src/platforms/rcore_memory.c @@ -13,9 +13,6 @@ * - Improvement 01 * - Improvement 02 * -* 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- diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index 2b51804ef..f1922600f 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -12,9 +12,6 @@ * POSSIBLE IMPROVEMENTS: * - Replace glfw3 dependency by direct browser API calls (same as library_glfw3.js) * -* 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- diff --git a/src/platforms/rcore_web_emscripten.c b/src/platforms/rcore_web_emscripten.c index 5fdcdbefc..36b8e964a 100644 --- a/src/platforms/rcore_web_emscripten.c +++ b/src/platforms/rcore_web_emscripten.c @@ -11,9 +11,6 @@ * POSSIBLE IMPROVEMENTS: * - TBD * -* 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- diff --git a/src/raudio.c b/src/raudio.c index cfb86cdbd..c25ad1f02 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -78,7 +78,7 @@ #if !defined(EXTERNAL_CONFIG_FLAGS) #include "config.h" // Defines module configuration flags #endif - #include "utils.h" // Required for: fopen() Android mapping + //#include "utils.h" // Required for: fopen() Android mapping #endif #if defined(SUPPORT_MODULE_RAUDIO) || defined(RAUDIO_STANDALONE) diff --git a/src/raylib.h b/src/raylib.h index 6eae8411e..177138dc9 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1074,47 +1074,41 @@ RLAPI Matrix GetCameraMatrix(Camera camera); // Get c RLAPI Matrix GetCameraMatrix2D(Camera2D camera); // Get camera 2d transform matrix // Timing-related functions -RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) -RLAPI float GetFrameTime(void); // Get time in seconds for last frame drawn (delta time) -RLAPI double GetTime(void); // Get elapsed time in seconds since InitWindow() -RLAPI int GetFPS(void); // Get current FPS +RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) +RLAPI float GetFrameTime(void); // Get time in seconds for last frame drawn (delta time) +RLAPI double GetTime(void); // Get elapsed time in seconds since InitWindow() +RLAPI int GetFPS(void); // Get current FPS // Custom frame control functions // NOTE: Those functions are intended for advanced users that want full control over the frame processing // By default EndDrawing() does this job: draws everything + SwapScreenBuffer() + manage frame timing + PollInputEvents() // To avoid that behaviour and control frame processes manually, enable in config.h: SUPPORT_CUSTOM_FRAME_CONTROL -RLAPI void SwapScreenBuffer(void); // Swap back buffer with front buffer (screen drawing) -RLAPI void PollInputEvents(void); // Register all input events -RLAPI void WaitTime(double seconds); // Wait for some time (halt program execution) +RLAPI void SwapScreenBuffer(void); // Swap back buffer with front buffer (screen drawing) +RLAPI void PollInputEvents(void); // Register all input events +RLAPI void WaitTime(double seconds); // Wait for some time (halt program execution) // Random values generation functions -RLAPI void SetRandomSeed(unsigned int seed); // Set the seed for the random number generator -RLAPI int GetRandomValue(int min, int max); // Get a random value between min and max (both included) +RLAPI void SetRandomSeed(unsigned int seed); // Set the seed for the random number generator +RLAPI int GetRandomValue(int min, int max); // Get a random value between min and max (both included) RLAPI int *LoadRandomSequence(unsigned int count, int min, int max); // Load random values sequence, no values repeated -RLAPI void UnloadRandomSequence(int *sequence); // Unload random values sequence +RLAPI void UnloadRandomSequence(int *sequence); // Unload random values sequence // Misc. functions -RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (filename extension defines format) -RLAPI void SetConfigFlags(unsigned int flags); // Setup init configuration flags (view FLAGS) -RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) +RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (filename extension defines format) +RLAPI void SetConfigFlags(unsigned int flags); // Setup init configuration flags (view FLAGS) +RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) -// NOTE: Following functions implemented in module [utils] -//------------------------------------------------------------------ -RLAPI void TraceLog(int logLevel, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...) -RLAPI void SetTraceLogLevel(int logLevel); // Set the current threshold (minimum) log level -RLAPI void *MemAlloc(unsigned int size); // Internal memory allocator -RLAPI void *MemRealloc(void *ptr, unsigned int size); // Internal memory reallocator -RLAPI void MemFree(void *ptr); // Internal memory free +// Logging system +RLAPI void SetTraceLogLevel(int logLevel); // Set the current threshold (minimum) log level +RLAPI void TraceLog(int logLevel, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...) +RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log -// Set custom callbacks -// WARNING: Callbacks setup is intended for advanced users -RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log -RLAPI void SetLoadFileDataCallback(LoadFileDataCallback callback); // Set custom file binary data loader -RLAPI void SetSaveFileDataCallback(SaveFileDataCallback callback); // Set custom file binary data saver -RLAPI void SetLoadFileTextCallback(LoadFileTextCallback callback); // Set custom file text data loader -RLAPI void SetSaveFileTextCallback(SaveFileTextCallback callback); // Set custom file text data saver +// Memory management, using internal allocators +RLAPI void *MemAlloc(unsigned int size); // Internal memory allocator +RLAPI void *MemRealloc(void *ptr, unsigned int size); // Internal memory reallocator +RLAPI void MemFree(void *ptr); // Internal memory free -// Files management functions +// File system management functions RLAPI unsigned char *LoadFileData(const char *fileName, int *dataSize); // Load file data as byte array (read) RLAPI void UnloadFileData(unsigned char *data); // Unload file data allocated by LoadFileData() RLAPI bool SaveFileData(const char *fileName, void *data, int dataSize); // Save data to file from byte array (write), returns true on success @@ -1122,9 +1116,14 @@ RLAPI bool ExportDataAsCode(const unsigned char *data, int dataSize, const char RLAPI char *LoadFileText(const char *fileName); // Load text data from file (read), returns a '\0' terminated string RLAPI void UnloadFileText(char *text); // Unload file text data allocated by LoadFileText() RLAPI bool SaveFileText(const char *fileName, const char *text); // Save text data to file (write), string must be '\0' terminated, returns true on success -//------------------------------------------------------------------ -// File system functions +// File access custom callbacks +// WARNING: Callbacks setup is intended for advanced users +RLAPI void SetLoadFileDataCallback(LoadFileDataCallback callback); // Set custom file binary data loader +RLAPI void SetSaveFileDataCallback(SaveFileDataCallback callback); // Set custom file binary data saver +RLAPI void SetLoadFileTextCallback(LoadFileTextCallback callback); // Set custom file text data loader +RLAPI void SetSaveFileTextCallback(SaveFileTextCallback callback); // Set custom file text data saver + RLAPI int FileRename(const char *fileName, const char *fileRename); // Rename file (if exists) RLAPI int FileRemove(const char *fileName); // Remove file (if exists) RLAPI int FileCopy(const char *srcPath, const char *dstPath); // Copy file from one path to another, dstPath created if it doesn't exist diff --git a/src/rcore.c b/src/rcore.c index b7fcbf4a0..7837f8c4e 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -109,11 +109,10 @@ #include "config.h" // Defines module configuration flags #endif -#include "utils.h" // Required for: TRACELOG() macros - -#include // Required for: srand(), rand(), atexit() -#include // Required for: sprintf() [Used in OpenURL()] -#include // Required for: strlen(), strncpy(), strcmp(), strrchr(), memset() +#include // Required for: srand(), rand(), atexit(), exit() +#include // Required for: FILE, fopen(), fseek(), ftell(), fread(), fwrite(), fprintf(), vprintf(), fclose(), sprintf() [Used in OpenURL()] +#include // Required for: strlen(), strncpy(), strcmp(), strrchr(), memset(), strcat() +#include // Required for: va_list, va_start(), va_end() [Used in TraceLog()] #include // Required for: time() [Used in InitTimer()] #include // Required for: tan() [Used in BeginMode3D()], atan2f() [Used in LoadVrStereoConfig()] @@ -217,6 +216,10 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- +#ifndef MAX_TRACELOG_MSG_LENGTH + #define MAX_TRACELOG_MSG_LENGTH 256 // Max length of one trace-log message +#endif + #ifndef MAX_FILEPATH_CAPACITY #define MAX_FILEPATH_CAPACITY 8192 // Maximum capacity for filepath #endif @@ -387,10 +390,18 @@ typedef struct CoreData { //---------------------------------------------------------------------------------- RLAPI const char *raylib_version = RAYLIB_VERSION; // raylib version exported symbol, required for some bindings -CoreData CORE = { 0 }; // Global CORE state context +CoreData CORE = { 0 }; // Global CORE state context + +static int logTypeLevel = LOG_INFO; // Minimum log type level + +static TraceLogCallback traceLog = NULL; // TraceLog callback function pointer +static LoadFileDataCallback loadFileData = NULL; // LoadFileData callback function pointer +static SaveFileDataCallback saveFileData = NULL; // SaveFileText callback function pointer +static LoadFileTextCallback loadFileText = NULL; // LoadFileText callback function pointer +static SaveFileTextCallback saveFileText = NULL; // SaveFileText callback function pointer #if defined(SUPPORT_SCREEN_CAPTURE) -static int screenshotCounter = 0; // Screenshots counter +static int screenshotCounter = 0; // Screenshots counter #endif #if defined(SUPPORT_AUTOMATION_EVENTS) @@ -1857,9 +1868,389 @@ void SetConfigFlags(unsigned int flags) FLAG_SET(CORE.Window.flags, flags); } +// void OpenURL(const char *url); // Defined per platform + //---------------------------------------------------------------------------------- -// Module Functions Definition: File system +// Module Functions Definition: Logging system //---------------------------------------------------------------------------------- +// Set the current threshold (minimum) log level +void SetTraceLogLevel(int logType) { logTypeLevel = logType; } + +// Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) +void TraceLog(int logType, const char *text, ...) +{ +#if defined(SUPPORT_TRACELOG) + // Message has level below current threshold, don't emit + if ((logType < logTypeLevel) || (text == NULL)) return; + + va_list args; + va_start(args, text); + + if (traceLog) + { + traceLog(logType, text, args); + va_end(args); + return; + } + +#if defined(PLATFORM_ANDROID) + switch (logType) + { + case LOG_TRACE: __android_log_vprint(ANDROID_LOG_VERBOSE, "raylib", text, args); break; + case LOG_DEBUG: __android_log_vprint(ANDROID_LOG_DEBUG, "raylib", text, args); break; + case LOG_INFO: __android_log_vprint(ANDROID_LOG_INFO, "raylib", text, args); break; + case LOG_WARNING: __android_log_vprint(ANDROID_LOG_WARN, "raylib", text, args); break; + case LOG_ERROR: __android_log_vprint(ANDROID_LOG_ERROR, "raylib", text, args); break; + case LOG_FATAL: __android_log_vprint(ANDROID_LOG_FATAL, "raylib", text, args); break; + default: break; + } +#else + char buffer[MAX_TRACELOG_MSG_LENGTH] = { 0 }; + + switch (logType) + { + case LOG_TRACE: strncpy(buffer, "TRACE: ", 8); break; + case LOG_DEBUG: strncpy(buffer, "DEBUG: ", 8); break; + case LOG_INFO: strncpy(buffer, "INFO: ", 7); break; + case LOG_WARNING: strncpy(buffer, "WARNING: ", 10); break; + case LOG_ERROR: strncpy(buffer, "ERROR: ", 8); break; + case LOG_FATAL: strncpy(buffer, "FATAL: ", 8); break; + default: break; + } + + unsigned int textLength = (unsigned int)strlen(text); + memcpy(buffer + strlen(buffer), text, (textLength < (MAX_TRACELOG_MSG_LENGTH - 12))? textLength : (MAX_TRACELOG_MSG_LENGTH - 12)); + strcat(buffer, "\n"); + vprintf(buffer, args); + fflush(stdout); +#endif + + va_end(args); + + if (logType == LOG_FATAL) exit(EXIT_FAILURE); // If fatal logging, exit program + +#endif // SUPPORT_TRACELOG +} + +// Set custom trace log +void SetTraceLogCallback(TraceLogCallback callback) +{ + traceLog = callback; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Memory management +//---------------------------------------------------------------------------------- +// Internal memory allocator +// NOTE: Initializes to zero by default +void *MemAlloc(unsigned int size) +{ + void *ptr = RL_CALLOC(size, 1); + return ptr; +} + +// Internal memory reallocator +void *MemRealloc(void *ptr, unsigned int size) +{ + void *ret = RL_REALLOC(ptr, size); + return ret; +} + +// Internal memory free +void MemFree(void *ptr) +{ + RL_FREE(ptr); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: File System management +//---------------------------------------------------------------------------------- +// Load data from file into a buffer +unsigned char *LoadFileData(const char *fileName, int *dataSize) +{ + unsigned char *data = NULL; + *dataSize = 0; + + if (fileName != NULL) + { + if (loadFileData) + { + data = loadFileData(fileName, dataSize); + return data; + } +#if defined(SUPPORT_STANDARD_FILEIO) + FILE *file = fopen(fileName, "rb"); + + if (file != NULL) + { + // WARNING: On binary streams SEEK_END could not be found, + // using fseek() and ftell() could not work in some (rare) cases + fseek(file, 0, SEEK_END); + int size = ftell(file); // WARNING: ftell() returns 'long int', maximum size returned is INT_MAX (2147483647 bytes) + fseek(file, 0, SEEK_SET); + + if (size > 0) + { + data = (unsigned char *)RL_CALLOC(size, sizeof(unsigned char)); + + if (data != NULL) + { + // NOTE: fread() returns number of read elements instead of bytes, so we read [1 byte, size elements] + size_t count = fread(data, sizeof(unsigned char), size, file); + + // WARNING: fread() returns a size_t value, usually 'unsigned int' (32bit compilation) and 'unsigned long long' (64bit compilation) + // dataSize is unified along raylib as a 'int' type, so, for file-sizes > INT_MAX (2147483647 bytes) we have a limitation + if (count > 2147483647) + { + TRACELOG(LOG_WARNING, "FILEIO: [%s] File is bigger than 2147483647 bytes, avoid using LoadFileData()", fileName); + + RL_FREE(data); + data = NULL; + } + else + { + *dataSize = (int)count; + + if ((*dataSize) != size) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially loaded (%i bytes out of %i)", fileName, dataSize, count); + else TRACELOG(LOG_INFO, "FILEIO: [%s] File loaded successfully", fileName); + } + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to allocated memory for file reading", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read file", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); +#else + TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); +#endif + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return data; +} + +// Unload file data allocated by LoadFileData() +void UnloadFileData(unsigned char *data) +{ + RL_FREE(data); +} + +// Save data to file from buffer +bool SaveFileData(const char *fileName, void *data, int dataSize) +{ + bool success = false; + + if (fileName != NULL) + { + if (saveFileData) + { + return saveFileData(fileName, data, dataSize); + } +#if defined(SUPPORT_STANDARD_FILEIO) + FILE *file = fopen(fileName, "wb"); + + if (file != NULL) + { + // WARNING: fwrite() returns a size_t value, usually 'unsigned int' (32bit compilation) and 'unsigned long long' (64bit compilation) + // and expects a size_t input value but as dataSize is limited to INT_MAX (2147483647 bytes), there shouldn't be a problem + int count = (int)fwrite(data, sizeof(unsigned char), dataSize, file); + + if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write file", fileName); + else if (count != dataSize) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially written", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] File saved successfully", fileName); + + int result = fclose(file); + if (result == 0) success = true; + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); +#else + TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); +#endif + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return success; +} + +// Export data to code (.h), returns true on success +bool ExportDataAsCode(const unsigned char *data, int dataSize, const char *fileName) +{ + bool success = false; + +#ifndef TEXT_BYTES_PER_LINE + #define TEXT_BYTES_PER_LINE 20 +#endif + + // NOTE: Text data buffer size is estimated considering raw data size in bytes + // and requiring 6 char bytes for every byte: "0x00, " + char *txtData = (char *)RL_CALLOC(dataSize*6 + 2000, sizeof(char)); + + int byteCount = 0; + byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// DataAsCode exporter v1.0 - Raw data exported as an array of bytes //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2022-2026 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); + + // Get file name from path + char varFileName[256] = { 0 }; + strncpy(varFileName, GetFileNameWithoutExt(fileName), 256 - 1); + for (int i = 0; varFileName[i] != '\0'; i++) + { + // Convert variable name to uppercase + if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; } + // Replace non valid character for C identifier with '_' + else if (varFileName[i] == '.' || varFileName[i] == '-' || varFileName[i] == '?' || varFileName[i] == '!' || varFileName[i] == '+') { varFileName[i] = '_'; } + } + + byteCount += sprintf(txtData + byteCount, "#define %s_DATA_SIZE %i\n\n", varFileName, dataSize); + + byteCount += sprintf(txtData + byteCount, "static unsigned char %s_DATA[%s_DATA_SIZE] = { ", varFileName, varFileName); + for (int i = 0; i < (dataSize - 1); i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), data[i]); + byteCount += sprintf(txtData + byteCount, "0x%x };\n", data[dataSize - 1]); + + // NOTE: Text data size exported is determined by '\0' (NULL) character + success = SaveFileText(fileName, txtData); + + RL_FREE(txtData); + + if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Data as code exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export data as code", fileName); + + return success; +} + +// Load text data from file, returns a '\0' terminated string +// NOTE: text chars array should be freed manually +char *LoadFileText(const char *fileName) +{ + char *text = NULL; + + if (fileName != NULL) + { + if (loadFileText) + { + text = loadFileText(fileName); + return text; + } +#if defined(SUPPORT_STANDARD_FILEIO) + FILE *file = fopen(fileName, "rt"); + + if (file != NULL) + { + // WARNING: When reading a file as 'text' file, + // text mode causes carriage return-linefeed translation... + // ...but using fseek() should return correct byte-offset + fseek(file, 0, SEEK_END); + unsigned int size = (unsigned int)ftell(file); + fseek(file, 0, SEEK_SET); + + if (size > 0) + { + text = (char *)RL_CALLOC(size + 1, sizeof(char)); + + if (text != NULL) + { + unsigned int count = (unsigned int)fread(text, sizeof(char), size, file); + + // WARNING: \r\n is converted to \n on reading, so, + // read bytes count gets reduced by the number of lines + if (count < size) text = (char *)RL_REALLOC(text, count + 1); + + // Zero-terminate the string + text[count] = '\0'; + + TRACELOG(LOG_INFO, "FILEIO: [%s] Text file loaded successfully", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to allocated memory for file reading", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read text file", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); +#else + TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); +#endif + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return text; +} + +// Unload file text data allocated by LoadFileText() +void UnloadFileText(char *text) +{ + RL_FREE(text); +} + +// Save text data to file (write), string must be '\0' terminated +bool SaveFileText(const char *fileName, const char *text) +{ + bool success = false; + + if (fileName != NULL) + { + if (saveFileText) + { + return saveFileText(fileName, text); + } +#if defined(SUPPORT_STANDARD_FILEIO) + FILE *file = fopen(fileName, "wt"); + + if (file != NULL) + { + int count = fprintf(file, "%s", text); + + if (count < 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write text file", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] Text file saved successfully", fileName); + + int result = fclose(file); + if (result == 0) success = true; + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); +#else + TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); +#endif + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return success; +} + +// File access custom callbacks +// WARNING: Callbacks setup is intended for advanced users + +// Set custom file binary data loader +void SetLoadFileDataCallback(LoadFileDataCallback callback) +{ + loadFileData = callback; +} + +// Set custom file binary data saver +void SetSaveFileDataCallback(SaveFileDataCallback callback) +{ + saveFileData = callback; +} + +// Set custom file text data loader +void SetLoadFileTextCallback(LoadFileTextCallback callback) +{ + loadFileText = callback; +} + +// Set custom file text data saver +void SetSaveFileTextCallback(SaveFileTextCallback callback) +{ + saveFileText = callback; +} // Rename file (if exists) // NOTE: Only rename file name required, not full path diff --git a/src/rmodels.c b/src/rmodels.c index 20287a935..76efa3e4a 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -49,7 +49,6 @@ #if defined(SUPPORT_MODULE_RMODELS) -#include "utils.h" // Required for: TRACELOG(), LoadFileData(), LoadFileText(), SaveFileText() #include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 #include "raymath.h" // Required for: Vector3, Quaternion and Matrix functionality diff --git a/src/rtext.c b/src/rtext.c index 8413d62bf..7a1b3c027 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -62,7 +62,6 @@ #if defined(SUPPORT_MODULE_RTEXT) -#include "utils.h" // Required for: LoadFile*() #include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 -> Only DrawTextPro() #include // Required for: malloc(), free() diff --git a/src/rtextures.c b/src/rtextures.c index 8d4128d4e..fa03d193b 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -70,7 +70,6 @@ #if defined(SUPPORT_MODULE_RTEXTURES) -#include "utils.h" // Required for: TRACELOG() #include "rlgl.h" // OpenGL abstraction layer to multiple versions #include // Required for: malloc(), calloc(), free() diff --git a/src/utils.c b/src/utils.c deleted file mode 100644 index 80c8dec55..000000000 --- a/src/utils.c +++ /dev/null @@ -1,514 +0,0 @@ -/********************************************************************************************** -* -* raylib.utils - Some common utility functions -* -* CONFIGURATION: -* #define SUPPORT_TRACELOG -* Show TraceLog() output messages -* NOTE: By default LOG_DEBUG traces not shown -* -* -* LICENSE: zlib/libpng -* -* Copyright (c) 2014-2026 Ramon Santamaria (@raysan5) -* -* This software is provided "as-is", without any express or implied warranty. In no event -* will the authors be held liable for any damages arising from the use of this software. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -**********************************************************************************************/ - -#include "raylib.h" // WARNING: Required for: LogType enum - -// Check if config flags have been externally provided on compilation line -#if !defined(EXTERNAL_CONFIG_FLAGS) - #include "config.h" // Defines module configuration flags -#endif - -#include "utils.h" - -#if defined(PLATFORM_ANDROID) - #include // Required for: Android error types - #include // Required for: Android log system: __android_log_vprint() - #include // Required for: Android assets manager: AAsset, AAssetManager_open()... -#endif - -#include // Required for: exit() -#include // Required for: FILE, fopen(), fseek(), ftell(), fread(), fwrite(), fprintf(), vprintf(), fclose() -#include // Required for: va_list, va_start(), va_end() -#include // Required for: strcpy(), strcat() - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -#ifndef MAX_TRACELOG_MSG_LENGTH - #define MAX_TRACELOG_MSG_LENGTH 256 // Max length of one trace-log message -#endif - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -static int logTypeLevel = LOG_INFO; // Minimum log type level - -static TraceLogCallback traceLog = NULL; // TraceLog callback function pointer -static LoadFileDataCallback loadFileData = NULL; // LoadFileData callback function pointer -static SaveFileDataCallback saveFileData = NULL; // SaveFileText callback function pointer -static LoadFileTextCallback loadFileText = NULL; // LoadFileText callback function pointer -static SaveFileTextCallback saveFileText = NULL; // SaveFileText callback function pointer - -//---------------------------------------------------------------------------------- -// Functions to set internal callbacks -//---------------------------------------------------------------------------------- -void SetTraceLogCallback(TraceLogCallback callback) { traceLog = callback; } // Set custom trace log -void SetLoadFileDataCallback(LoadFileDataCallback callback) { loadFileData = callback; } // Set custom file data loader -void SetSaveFileDataCallback(SaveFileDataCallback callback) { saveFileData = callback; } // Set custom file data saver -void SetLoadFileTextCallback(LoadFileTextCallback callback) { loadFileText = callback; } // Set custom file text loader -void SetSaveFileTextCallback(SaveFileTextCallback callback) { saveFileText = callback; } // Set custom file text saver - -#if defined(PLATFORM_ANDROID) -static AAssetManager *assetManager = NULL; // Android assets manager pointer -static const char *internalDataPath = NULL; // Android internal data path -#endif - -//---------------------------------------------------------------------------------- -// Module Internal Functions Declaration -//---------------------------------------------------------------------------------- -#if defined(PLATFORM_ANDROID) -FILE *funopen(const void *cookie, int (*readfn)(void *, char *, int), int (*writefn)(void *, const char *, int), - fpos_t (*seekfn)(void *, fpos_t, int), int (*closefn)(void *)); - -static int android_read(void *cookie, char *buf, int size); -static int android_write(void *cookie, const char *buf, int size); -static fpos_t android_seek(void *cookie, fpos_t offset, int whence); -static int android_close(void *cookie); -#endif - -//---------------------------------------------------------------------------------- -// Module Functions Definition -//---------------------------------------------------------------------------------- -// Set the current threshold (minimum) log level -void SetTraceLogLevel(int logType) { logTypeLevel = logType; } - -// Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) -void TraceLog(int logType, const char *text, ...) -{ -#if defined(SUPPORT_TRACELOG) - // Message has level below current threshold, don't emit - if ((logType < logTypeLevel) || (text == NULL)) return; - - va_list args; - va_start(args, text); - - if (traceLog) - { - traceLog(logType, text, args); - va_end(args); - return; - } - -#if defined(PLATFORM_ANDROID) - switch (logType) - { - case LOG_TRACE: __android_log_vprint(ANDROID_LOG_VERBOSE, "raylib", text, args); break; - case LOG_DEBUG: __android_log_vprint(ANDROID_LOG_DEBUG, "raylib", text, args); break; - case LOG_INFO: __android_log_vprint(ANDROID_LOG_INFO, "raylib", text, args); break; - case LOG_WARNING: __android_log_vprint(ANDROID_LOG_WARN, "raylib", text, args); break; - case LOG_ERROR: __android_log_vprint(ANDROID_LOG_ERROR, "raylib", text, args); break; - case LOG_FATAL: __android_log_vprint(ANDROID_LOG_FATAL, "raylib", text, args); break; - default: break; - } -#else - char buffer[MAX_TRACELOG_MSG_LENGTH] = { 0 }; - - switch (logType) - { - case LOG_TRACE: strcpy(buffer, "TRACE: "); break; - case LOG_DEBUG: strcpy(buffer, "DEBUG: "); break; - case LOG_INFO: strcpy(buffer, "INFO: "); break; - case LOG_WARNING: strcpy(buffer, "WARNING: "); break; - case LOG_ERROR: strcpy(buffer, "ERROR: "); break; - case LOG_FATAL: strcpy(buffer, "FATAL: "); break; - default: break; - } - - unsigned int textLength = (unsigned int)strlen(text); - memcpy(buffer + strlen(buffer), text, (textLength < (MAX_TRACELOG_MSG_LENGTH - 12))? textLength : (MAX_TRACELOG_MSG_LENGTH - 12)); - strcat(buffer, "\n"); - vprintf(buffer, args); - fflush(stdout); -#endif - - va_end(args); - - if (logType == LOG_FATAL) exit(EXIT_FAILURE); // If fatal logging, exit program - -#endif // SUPPORT_TRACELOG -} - -// Internal memory allocator -// NOTE: Initializes to zero by default -void *MemAlloc(unsigned int size) -{ - void *ptr = RL_CALLOC(size, 1); - return ptr; -} - -// Internal memory reallocator -void *MemRealloc(void *ptr, unsigned int size) -{ - void *ret = RL_REALLOC(ptr, size); - return ret; -} - -// Internal memory free -void MemFree(void *ptr) -{ - RL_FREE(ptr); -} - -// Load data from file into a buffer -unsigned char *LoadFileData(const char *fileName, int *dataSize) -{ - unsigned char *data = NULL; - *dataSize = 0; - - if (fileName != NULL) - { - if (loadFileData) - { - data = loadFileData(fileName, dataSize); - return data; - } -#if defined(SUPPORT_STANDARD_FILEIO) - FILE *file = fopen(fileName, "rb"); - - if (file != NULL) - { - // WARNING: On binary streams SEEK_END could not be found, - // using fseek() and ftell() could not work in some (rare) cases - fseek(file, 0, SEEK_END); - int size = ftell(file); // WARNING: ftell() returns 'long int', maximum size returned is INT_MAX (2147483647 bytes) - fseek(file, 0, SEEK_SET); - - if (size > 0) - { - data = (unsigned char *)RL_CALLOC(size, sizeof(unsigned char)); - - if (data != NULL) - { - // NOTE: fread() returns number of read elements instead of bytes, so we read [1 byte, size elements] - size_t count = fread(data, sizeof(unsigned char), size, file); - - // WARNING: fread() returns a size_t value, usually 'unsigned int' (32bit compilation) and 'unsigned long long' (64bit compilation) - // dataSize is unified along raylib as a 'int' type, so, for file-sizes > INT_MAX (2147483647 bytes) we have a limitation - if (count > 2147483647) - { - TRACELOG(LOG_WARNING, "FILEIO: [%s] File is bigger than 2147483647 bytes, avoid using LoadFileData()", fileName); - - RL_FREE(data); - data = NULL; - } - else - { - *dataSize = (int)count; - - if ((*dataSize) != size) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially loaded (%i bytes out of %i)", fileName, dataSize, count); - else TRACELOG(LOG_INFO, "FILEIO: [%s] File loaded successfully", fileName); - } - } - else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to allocated memory for file reading", fileName); - } - else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read file", fileName); - - fclose(file); - } - else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); -#else - TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); -#endif - } - else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); - - return data; -} - -// Unload file data allocated by LoadFileData() -void UnloadFileData(unsigned char *data) -{ - RL_FREE(data); -} - -// Save data to file from buffer -bool SaveFileData(const char *fileName, void *data, int dataSize) -{ - bool success = false; - - if (fileName != NULL) - { - if (saveFileData) - { - return saveFileData(fileName, data, dataSize); - } -#if defined(SUPPORT_STANDARD_FILEIO) - FILE *file = fopen(fileName, "wb"); - - if (file != NULL) - { - // WARNING: fwrite() returns a size_t value, usually 'unsigned int' (32bit compilation) and 'unsigned long long' (64bit compilation) - // and expects a size_t input value but as dataSize is limited to INT_MAX (2147483647 bytes), there shouldn't be a problem - int count = (int)fwrite(data, sizeof(unsigned char), dataSize, file); - - if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write file", fileName); - else if (count != dataSize) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially written", fileName); - else TRACELOG(LOG_INFO, "FILEIO: [%s] File saved successfully", fileName); - - int result = fclose(file); - if (result == 0) success = true; - } - else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); -#else - TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); -#endif - } - else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); - - return success; -} - -// Export data to code (.h), returns true on success -bool ExportDataAsCode(const unsigned char *data, int dataSize, const char *fileName) -{ - bool success = false; - -#ifndef TEXT_BYTES_PER_LINE - #define TEXT_BYTES_PER_LINE 20 -#endif - - // NOTE: Text data buffer size is estimated considering raw data size in bytes - // and requiring 6 char bytes for every byte: "0x00, " - char *txtData = (char *)RL_CALLOC(dataSize*6 + 2000, sizeof(char)); - - int byteCount = 0; - byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n"); - byteCount += sprintf(txtData + byteCount, "// //\n"); - byteCount += sprintf(txtData + byteCount, "// DataAsCode exporter v1.0 - Raw data exported as an array of bytes //\n"); - byteCount += sprintf(txtData + byteCount, "// //\n"); - byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); - byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); - byteCount += sprintf(txtData + byteCount, "// //\n"); - byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2022-2026 Ramon Santamaria (@raysan5) //\n"); - byteCount += sprintf(txtData + byteCount, "// //\n"); - byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); - - // Get file name from path - char varFileName[256] = { 0 }; - strncpy(varFileName, GetFileNameWithoutExt(fileName), 256 - 1); - for (int i = 0; varFileName[i] != '\0'; i++) - { - // Convert variable name to uppercase - if ((varFileName[i] >= 'a') && (varFileName[i] <= 'z')) { varFileName[i] = varFileName[i] - 32; } - // Replace non valid character for C identifier with '_' - else if (varFileName[i] == '.' || varFileName[i] == '-' || varFileName[i] == '?' || varFileName[i] == '!' || varFileName[i] == '+') { varFileName[i] = '_'; } - } - - byteCount += sprintf(txtData + byteCount, "#define %s_DATA_SIZE %i\n\n", varFileName, dataSize); - - byteCount += sprintf(txtData + byteCount, "static unsigned char %s_DATA[%s_DATA_SIZE] = { ", varFileName, varFileName); - for (int i = 0; i < (dataSize - 1); i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), data[i]); - byteCount += sprintf(txtData + byteCount, "0x%x };\n", data[dataSize - 1]); - - // NOTE: Text data size exported is determined by '\0' (NULL) character - success = SaveFileText(fileName, txtData); - - RL_FREE(txtData); - - if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Data as code exported successfully", fileName); - else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export data as code", fileName); - - return success; -} - -// Load text data from file, returns a '\0' terminated string -// NOTE: text chars array should be freed manually -char *LoadFileText(const char *fileName) -{ - char *text = NULL; - - if (fileName != NULL) - { - if (loadFileText) - { - text = loadFileText(fileName); - return text; - } -#if defined(SUPPORT_STANDARD_FILEIO) - FILE *file = fopen(fileName, "rt"); - - if (file != NULL) - { - // WARNING: When reading a file as 'text' file, - // text mode causes carriage return-linefeed translation... - // ...but using fseek() should return correct byte-offset - fseek(file, 0, SEEK_END); - unsigned int size = (unsigned int)ftell(file); - fseek(file, 0, SEEK_SET); - - if (size > 0) - { - text = (char *)RL_CALLOC(size + 1, sizeof(char)); - - if (text != NULL) - { - unsigned int count = (unsigned int)fread(text, sizeof(char), size, file); - - // WARNING: \r\n is converted to \n on reading, so, - // read bytes count gets reduced by the number of lines - if (count < size) text = (char *)RL_REALLOC(text, count + 1); - - // Zero-terminate the string - text[count] = '\0'; - - TRACELOG(LOG_INFO, "FILEIO: [%s] Text file loaded successfully", fileName); - } - else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to allocated memory for file reading", fileName); - } - else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read text file", fileName); - - fclose(file); - } - else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); -#else - TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); -#endif - } - else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); - - return text; -} - -// Unload file text data allocated by LoadFileText() -void UnloadFileText(char *text) -{ - RL_FREE(text); -} - -// Save text data to file (write), string must be '\0' terminated -bool SaveFileText(const char *fileName, const char *text) -{ - bool success = false; - - if (fileName != NULL) - { - if (saveFileText) - { - return saveFileText(fileName, text); - } -#if defined(SUPPORT_STANDARD_FILEIO) - FILE *file = fopen(fileName, "wt"); - - if (file != NULL) - { - int count = fprintf(file, "%s", text); - - if (count < 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write text file", fileName); - else TRACELOG(LOG_INFO, "FILEIO: [%s] Text file saved successfully", fileName); - - int result = fclose(file); - if (result == 0) success = true; - } - else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); -#else - TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); -#endif - } - else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); - - return success; -} - -#if defined(PLATFORM_ANDROID) -// Initialize asset manager from android app -void InitAssetManager(AAssetManager *manager, const char *dataPath) -{ - assetManager = manager; - internalDataPath = dataPath; -} - -// Replacement for fopen() -// REF: https://developer.android.com/ndk/reference/group/asset -FILE *android_fopen(const char *fileName, const char *mode) -{ - FILE *file = NULL; - - if (mode[0] == 'w') - { - // NOTE: fopen() is mapped to android_fopen() that only grants read access to - // assets directory through AAssetManager but we want to also be able to - // write data when required using the standard stdio FILE access functions - // REF: https://stackoverflow.com/questions/11294487/android-writing-saving-files-from-native-code-only - #undef fopen - file = fopen(TextFormat("%s/%s", internalDataPath, fileName), mode); - #define fopen(name, mode) android_fopen(name, mode) - } - else - { - // NOTE: AAsset provides access to read-only asset - AAsset *asset = AAssetManager_open(assetManager, fileName, AASSET_MODE_UNKNOWN); - - if (asset != NULL) - { - // Get pointer to file in the assets - file = funopen(asset, android_read, android_write, android_seek, android_close); - } - else - { - #undef fopen - // Just do a regular open if file is not found in the assets - file = fopen(TextFormat("%s/%s", internalDataPath, fileName), mode); - if (file == NULL) file = fopen(fileName, mode); - #define fopen(name, mode) android_fopen(name, mode) - } - } - - return file; -} -#endif // PLATFORM_ANDROID - -//---------------------------------------------------------------------------------- -// Module Internal Functions Definition -//---------------------------------------------------------------------------------- -#if defined(PLATFORM_ANDROID) -static int android_read(void *cookie, char *data, int dataSize) -{ - return AAsset_read((AAsset *)cookie, data, dataSize); -} - -static int android_write(void *cookie, const char *data, int dataSize) -{ - TRACELOG(LOG_WARNING, "ANDROID: Failed to provide write access to APK"); - - return EACCES; -} - -static fpos_t android_seek(void *cookie, fpos_t offset, int whence) -{ - return AAsset_seek((AAsset *)cookie, offset, whence); -} - -static int android_close(void *cookie) -{ - AAsset_close((AAsset *)cookie); - return 0; -} -#endif // PLATFORM_ANDROID diff --git a/src/utils.h b/src/utils.h deleted file mode 100644 index 9c15ac285..000000000 --- a/src/utils.h +++ /dev/null @@ -1,74 +0,0 @@ -/********************************************************************************************** -* -* raylib.utils - Some common utility functions -* -* -* LICENSE: zlib/libpng -* -* Copyright (c) 2014-2026 Ramon Santamaria (@raysan5) -* -* This software is provided "as-is", without any express or implied warranty. In no event -* will the authors be held liable for any damages arising from the use of this software. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -**********************************************************************************************/ - -#ifndef UTILS_H -#define UTILS_H - -#if defined(PLATFORM_ANDROID) - #include // Required for: FILE - #include // Required for: AAssetManager -#endif - -#if defined(SUPPORT_TRACELOG) - #define TRACELOG(level, ...) TraceLog(level, __VA_ARGS__) -#else - #define TRACELOG(level, ...) (void)0 -#endif - -//---------------------------------------------------------------------------------- -// Some basic Defines -//---------------------------------------------------------------------------------- -#if defined(PLATFORM_ANDROID) - #define fopen(name, mode) android_fopen(name, mode) -#endif - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- -//... - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -// Nop... - -//---------------------------------------------------------------------------------- -// Module Functions Declaration -//---------------------------------------------------------------------------------- -#if defined(__cplusplus) -extern "C" { // Prevents name mangling of functions -#endif - -#if defined(PLATFORM_ANDROID) -void InitAssetManager(AAssetManager *manager, const char *dataPath); // Initialize asset manager from android app -FILE *android_fopen(const char *fileName, const char *mode); // Replacement for fopen() -> Read-only! -#endif - -#if defined(__cplusplus) -} -#endif - -#endif // UTILS_H From 21f026a48492a649f74a53519f82809cd5ba0119 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 10 Jan 2026 11:13:29 +0000 Subject: [PATCH 355/430] rlparser: update raylib_api.* by CI --- tools/rlparser/output/raylib_api.json | 118 +++++++++++++------------- tools/rlparser/output/raylib_api.lua | 86 +++++++++---------- tools/rlparser/output/raylib_api.txt | 80 ++++++++--------- tools/rlparser/output/raylib_api.xml | 34 ++++---- 4 files changed, 159 insertions(+), 159 deletions(-) diff --git a/tools/rlparser/output/raylib_api.json b/tools/rlparser/output/raylib_api.json index 66d8e9f30..185516563 100644 --- a/tools/rlparser/output/raylib_api.json +++ b/tools/rlparser/output/raylib_api.json @@ -4191,6 +4191,17 @@ } ] }, + { + "name": "SetTraceLogLevel", + "description": "Set the current threshold (minimum) log level", + "returnType": "void", + "params": [ + { + "type": "int", + "name": "logLevel" + } + ] + }, { "name": "TraceLog", "description": "Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...)", @@ -4211,13 +4222,13 @@ ] }, { - "name": "SetTraceLogLevel", - "description": "Set the current threshold (minimum) log level", + "name": "SetTraceLogCallback", + "description": "Set custom trace log", "returnType": "void", "params": [ { - "type": "int", - "name": "logLevel" + "type": "TraceLogCallback", + "name": "callback" } ] }, @@ -4258,61 +4269,6 @@ } ] }, - { - "name": "SetTraceLogCallback", - "description": "Set custom trace log", - "returnType": "void", - "params": [ - { - "type": "TraceLogCallback", - "name": "callback" - } - ] - }, - { - "name": "SetLoadFileDataCallback", - "description": "Set custom file binary data loader", - "returnType": "void", - "params": [ - { - "type": "LoadFileDataCallback", - "name": "callback" - } - ] - }, - { - "name": "SetSaveFileDataCallback", - "description": "Set custom file binary data saver", - "returnType": "void", - "params": [ - { - "type": "SaveFileDataCallback", - "name": "callback" - } - ] - }, - { - "name": "SetLoadFileTextCallback", - "description": "Set custom file text data loader", - "returnType": "void", - "params": [ - { - "type": "LoadFileTextCallback", - "name": "callback" - } - ] - }, - { - "name": "SetSaveFileTextCallback", - "description": "Set custom file text data saver", - "returnType": "void", - "params": [ - { - "type": "SaveFileTextCallback", - "name": "callback" - } - ] - }, { "name": "LoadFileData", "description": "Load file data as byte array (read)", @@ -4414,6 +4370,50 @@ } ] }, + { + "name": "SetLoadFileDataCallback", + "description": "Set custom file binary data loader", + "returnType": "void", + "params": [ + { + "type": "LoadFileDataCallback", + "name": "callback" + } + ] + }, + { + "name": "SetSaveFileDataCallback", + "description": "Set custom file binary data saver", + "returnType": "void", + "params": [ + { + "type": "SaveFileDataCallback", + "name": "callback" + } + ] + }, + { + "name": "SetLoadFileTextCallback", + "description": "Set custom file text data loader", + "returnType": "void", + "params": [ + { + "type": "LoadFileTextCallback", + "name": "callback" + } + ] + }, + { + "name": "SetSaveFileTextCallback", + "description": "Set custom file text data saver", + "returnType": "void", + "params": [ + { + "type": "SaveFileTextCallback", + "name": "callback" + } + ] + }, { "name": "FileRename", "description": "Rename file (if exists)", diff --git a/tools/rlparser/output/raylib_api.lua b/tools/rlparser/output/raylib_api.lua index 192ad963a..f2836e1ff 100644 --- a/tools/rlparser/output/raylib_api.lua +++ b/tools/rlparser/output/raylib_api.lua @@ -3864,6 +3864,14 @@ return { {type = "const char *", name = "url"} } }, + { + name = "SetTraceLogLevel", + description = "Set the current threshold (minimum) log level", + returnType = "void", + params = { + {type = "int", name = "logLevel"} + } + }, { name = "TraceLog", description = "Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...)", @@ -3875,11 +3883,11 @@ return { } }, { - name = "SetTraceLogLevel", - description = "Set the current threshold (minimum) log level", + name = "SetTraceLogCallback", + description = "Set custom trace log", returnType = "void", params = { - {type = "int", name = "logLevel"} + {type = "TraceLogCallback", name = "callback"} } }, { @@ -3907,46 +3915,6 @@ return { {type = "void *", name = "ptr"} } }, - { - name = "SetTraceLogCallback", - description = "Set custom trace log", - returnType = "void", - params = { - {type = "TraceLogCallback", name = "callback"} - } - }, - { - name = "SetLoadFileDataCallback", - description = "Set custom file binary data loader", - returnType = "void", - params = { - {type = "LoadFileDataCallback", name = "callback"} - } - }, - { - name = "SetSaveFileDataCallback", - description = "Set custom file binary data saver", - returnType = "void", - params = { - {type = "SaveFileDataCallback", name = "callback"} - } - }, - { - name = "SetLoadFileTextCallback", - description = "Set custom file text data loader", - returnType = "void", - params = { - {type = "LoadFileTextCallback", name = "callback"} - } - }, - { - name = "SetSaveFileTextCallback", - description = "Set custom file text data saver", - returnType = "void", - params = { - {type = "SaveFileTextCallback", name = "callback"} - } - }, { name = "LoadFileData", description = "Load file data as byte array (read)", @@ -4009,6 +3977,38 @@ return { {type = "const char *", name = "text"} } }, + { + name = "SetLoadFileDataCallback", + description = "Set custom file binary data loader", + returnType = "void", + params = { + {type = "LoadFileDataCallback", name = "callback"} + } + }, + { + name = "SetSaveFileDataCallback", + description = "Set custom file binary data saver", + returnType = "void", + params = { + {type = "SaveFileDataCallback", name = "callback"} + } + }, + { + name = "SetLoadFileTextCallback", + description = "Set custom file text data loader", + returnType = "void", + params = { + {type = "LoadFileTextCallback", name = "callback"} + } + }, + { + name = "SetSaveFileTextCallback", + description = "Set custom file text data saver", + returnType = "void", + params = { + {type = "SaveFileTextCallback", name = "callback"} + } + }, { name = "FileRename", description = "Rename file (if exists)", diff --git a/tools/rlparser/output/raylib_api.txt b/tools/rlparser/output/raylib_api.txt index f60f8fc81..0676b8138 100644 --- a/tools/rlparser/output/raylib_api.txt +++ b/tools/rlparser/output/raylib_api.txt @@ -1563,100 +1563,100 @@ Function 106: OpenURL() (1 input parameters) Return type: void Description: Open URL with default system browser (if available) Param[1]: url (type: const char *) -Function 107: TraceLog() (3 input parameters) +Function 107: SetTraceLogLevel() (1 input parameters) + Name: SetTraceLogLevel + Return type: void + Description: Set the current threshold (minimum) log level + Param[1]: logLevel (type: int) +Function 108: TraceLog() (3 input parameters) Name: TraceLog Return type: void Description: Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...) Param[1]: logLevel (type: int) Param[2]: text (type: const char *) Param[3]: args (type: ...) -Function 108: SetTraceLogLevel() (1 input parameters) - Name: SetTraceLogLevel +Function 109: SetTraceLogCallback() (1 input parameters) + Name: SetTraceLogCallback Return type: void - Description: Set the current threshold (minimum) log level - Param[1]: logLevel (type: int) -Function 109: MemAlloc() (1 input parameters) + Description: Set custom trace log + Param[1]: callback (type: TraceLogCallback) +Function 110: MemAlloc() (1 input parameters) Name: MemAlloc Return type: void * Description: Internal memory allocator Param[1]: size (type: unsigned int) -Function 110: MemRealloc() (2 input parameters) +Function 111: MemRealloc() (2 input parameters) Name: MemRealloc Return type: void * Description: Internal memory reallocator Param[1]: ptr (type: void *) Param[2]: size (type: unsigned int) -Function 111: MemFree() (1 input parameters) +Function 112: MemFree() (1 input parameters) Name: MemFree Return type: void Description: Internal memory free Param[1]: ptr (type: void *) -Function 112: SetTraceLogCallback() (1 input parameters) - Name: SetTraceLogCallback - Return type: void - Description: Set custom trace log - Param[1]: callback (type: TraceLogCallback) -Function 113: SetLoadFileDataCallback() (1 input parameters) - Name: SetLoadFileDataCallback - Return type: void - Description: Set custom file binary data loader - Param[1]: callback (type: LoadFileDataCallback) -Function 114: SetSaveFileDataCallback() (1 input parameters) - Name: SetSaveFileDataCallback - Return type: void - Description: Set custom file binary data saver - Param[1]: callback (type: SaveFileDataCallback) -Function 115: SetLoadFileTextCallback() (1 input parameters) - Name: SetLoadFileTextCallback - Return type: void - Description: Set custom file text data loader - Param[1]: callback (type: LoadFileTextCallback) -Function 116: SetSaveFileTextCallback() (1 input parameters) - Name: SetSaveFileTextCallback - Return type: void - Description: Set custom file text data saver - Param[1]: callback (type: SaveFileTextCallback) -Function 117: LoadFileData() (2 input parameters) +Function 113: LoadFileData() (2 input parameters) Name: LoadFileData Return type: unsigned char * Description: Load file data as byte array (read) Param[1]: fileName (type: const char *) Param[2]: dataSize (type: int *) -Function 118: UnloadFileData() (1 input parameters) +Function 114: UnloadFileData() (1 input parameters) Name: UnloadFileData Return type: void Description: Unload file data allocated by LoadFileData() Param[1]: data (type: unsigned char *) -Function 119: SaveFileData() (3 input parameters) +Function 115: SaveFileData() (3 input parameters) Name: SaveFileData Return type: bool Description: Save data to file from byte array (write), returns true on success Param[1]: fileName (type: const char *) Param[2]: data (type: void *) Param[3]: dataSize (type: int) -Function 120: ExportDataAsCode() (3 input parameters) +Function 116: ExportDataAsCode() (3 input parameters) Name: ExportDataAsCode Return type: bool Description: Export data to code (.h), returns true on success Param[1]: data (type: const unsigned char *) Param[2]: dataSize (type: int) Param[3]: fileName (type: const char *) -Function 121: LoadFileText() (1 input parameters) +Function 117: LoadFileText() (1 input parameters) Name: LoadFileText Return type: char * Description: Load text data from file (read), returns a '\0' terminated string Param[1]: fileName (type: const char *) -Function 122: UnloadFileText() (1 input parameters) +Function 118: UnloadFileText() (1 input parameters) Name: UnloadFileText Return type: void Description: Unload file text data allocated by LoadFileText() Param[1]: text (type: char *) -Function 123: SaveFileText() (2 input parameters) +Function 119: SaveFileText() (2 input parameters) Name: SaveFileText Return type: bool Description: Save text data to file (write), string must be '\0' terminated, returns true on success Param[1]: fileName (type: const char *) Param[2]: text (type: const char *) +Function 120: SetLoadFileDataCallback() (1 input parameters) + Name: SetLoadFileDataCallback + Return type: void + Description: Set custom file binary data loader + Param[1]: callback (type: LoadFileDataCallback) +Function 121: SetSaveFileDataCallback() (1 input parameters) + Name: SetSaveFileDataCallback + Return type: void + Description: Set custom file binary data saver + Param[1]: callback (type: SaveFileDataCallback) +Function 122: SetLoadFileTextCallback() (1 input parameters) + Name: SetLoadFileTextCallback + Return type: void + Description: Set custom file text data loader + Param[1]: callback (type: LoadFileTextCallback) +Function 123: SetSaveFileTextCallback() (1 input parameters) + Name: SetSaveFileTextCallback + Return type: void + Description: Set custom file text data saver + Param[1]: callback (type: SaveFileTextCallback) Function 124: FileRename() (2 input parameters) Name: FileRename Return type: int diff --git a/tools/rlparser/output/raylib_api.xml b/tools/rlparser/output/raylib_api.xml index 1bbeb175c..3853ac74f 100644 --- a/tools/rlparser/output/raylib_api.xml +++ b/tools/rlparser/output/raylib_api.xml @@ -988,13 +988,16 @@ + + + - - + + @@ -1006,21 +1009,6 @@ - - - - - - - - - - - - - - - @@ -1048,6 +1036,18 @@ + + + + + + + + + + + + From 1606dca0cbece4ffc7769bf664a86e05b1866fbb Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 10 Jan 2026 12:20:26 +0100 Subject: [PATCH 356/430] Update CMakeLists.txt --- src/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 087141447..76c985969 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,7 +36,6 @@ set(raylib_sources rshapes.c rtext.c rtextures.c - utils.c ) # /cmake/GlfwImport.cmake handles the details around the inclusion of glfw From d94ea00a9782ce7f9dc451a0f9afc9fa313b6954 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 11 Jan 2026 00:27:24 +0100 Subject: [PATCH 357/430] Update raylib.sln --- projects/VS2022/raylib.sln | 2 ++ 1 file changed, 2 insertions(+) diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index 9b088f52d..47e83ef4c 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -5661,6 +5661,8 @@ Global {1F4722E7-F78E-413F-A106-D3490211EA57} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A} {0A0FC982-6E31-401F-BA77-3C5E8AB02C68} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} {DC163251-16C3-4B72-B965-ACDBA0F02BD1} = {278D8859-20B1-428F-8448-064F46E1F021} + {D35D2FDA-B53F-4F70-81CA-24D95812B89C} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} + {F8DC77C0-556C-4672-B5B3-D2FA4ADC505C} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} From 972d6f0775f1d51199654dcc14e4285bec746770 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 11 Jan 2026 00:32:16 +0100 Subject: [PATCH 358/430] REXM: Fix raylib building --- tools/rexm/VS2022/raylib/raylib.vcxproj | 2 -- tools/rexm/VS2022/raylib/raylib.vcxproj.filters | 2 -- 2 files changed, 4 deletions(-) diff --git a/tools/rexm/VS2022/raylib/raylib.vcxproj b/tools/rexm/VS2022/raylib/raylib.vcxproj index df2831b33..dbc6272ed 100644 --- a/tools/rexm/VS2022/raylib/raylib.vcxproj +++ b/tools/rexm/VS2022/raylib/raylib.vcxproj @@ -312,7 +312,6 @@ - @@ -320,7 +319,6 @@ - diff --git a/tools/rexm/VS2022/raylib/raylib.vcxproj.filters b/tools/rexm/VS2022/raylib/raylib.vcxproj.filters index b5f5536dc..5914ba240 100644 --- a/tools/rexm/VS2022/raylib/raylib.vcxproj.filters +++ b/tools/rexm/VS2022/raylib/raylib.vcxproj.filters @@ -8,7 +8,6 @@ - @@ -16,7 +15,6 @@ - external From 483b26ef843cff0cda5f939033c9bc4bca73d879 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 11 Jan 2026 01:04:32 +0100 Subject: [PATCH 359/430] Update rexm.c --- tools/rexm/rexm.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 95ed13739..ad9bcbed0 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -660,7 +660,8 @@ int main(int argc, char *argv[]) // we must store provided file paths because pointers will be overwriten // TODO: It seems projects are added to solution BUT not to required solution folder, // that process still requires to be done manually - LOG("INFO: [%s] Adding project to raylib solution (.sln)\n", TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exName)); + LOG("INFO: [%s] Adding project to raylib solution (.sln)\n", + TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exName)); AddVSProjectToSolution(exVSProjectSolutionFile, TextFormat("%s/../projects/VS2022/examples/%s.vcxproj", exBasePath, exName), exCategory); //------------------------------------------------------------------------------------------------ @@ -2613,7 +2614,7 @@ static int AddVSProjectToSolution(const char *slnFile, const char *projFile, con int result = 0; // WARNING: Function uses extensively TextFormat(), - // *projFile ptr will be overwriten after a while + // *projFile ptr could be overwriten after a while -> Use copied string // Generate unique UUID const char *uuid = GenerateUUIDv4(); @@ -2697,14 +2698,22 @@ static int AddVSProjectToSolution(const char *slnFile, const char *projFile, con // Add project folder line // NOTE: Folder uuid depends on category - if (strcmp(category, "core") == 0) offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035}\n", uuid)); - else if (strcmp(category, "shapes") == 0) offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s} = {278D8859-20B1-428F-8448-064F46E1F021}\n", uuid)); - else if (strcmp(category, "textures") == 0) offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE}\n", uuid)); - else if (strcmp(category, "text") == 0) offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A}\n", uuid)); - else if (strcmp(category, "models") == 0) offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s} = {AF5BEC5C-1F2B-4DA8-B12D-D09FE569237C}\n", uuid)); - else if (strcmp(category, "shaders") == 0) offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9}\n", uuid)); - else if (strcmp(category, "audio") == 0) offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s} = {CC132A4D-D081-4C26-BFB9-AB11984054F8}\n", uuid)); - else if (strcmp(category, "other") == 0) offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s} = {E9D708A5-9C1F-4B84-A795-C5F191801762}\n", uuid)); + if (strcmp(category, "core") == 0) offsetIndex += sprintf(slnTextUpdated + offsetIndex, + TextFormat("\t\t{%s} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035}\n", uuid)); + else if (strcmp(category, "shapes") == 0) offsetIndex += sprintf(slnTextUpdated + offsetIndex, + TextFormat("\t\t{%s} = {278D8859-20B1-428F-8448-064F46E1F021}\n", uuid)); + else if (strcmp(category, "textures") == 0) offsetIndex += sprintf(slnTextUpdated + offsetIndex, + TextFormat("\t\t{%s} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE}\n", uuid)); + else if (strcmp(category, "text") == 0) offsetIndex += sprintf(slnTextUpdated + offsetIndex, + TextFormat("\t\t{%s} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A}\n", uuid)); + else if (strcmp(category, "models") == 0) offsetIndex += sprintf(slnTextUpdated + offsetIndex, + TextFormat("\t\t{%s} = {AF5BEC5C-1F2B-4DA8-B12D-D09FE569237C}\n", uuid)); + else if (strcmp(category, "shaders") == 0) offsetIndex += sprintf(slnTextUpdated + offsetIndex, + TextFormat("\t\t{%s} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9}\n", uuid)); + else if (strcmp(category, "audio") == 0) offsetIndex += sprintf(slnTextUpdated + offsetIndex, + TextFormat("\t\t{%s} = {CC132A4D-D081-4C26-BFB9-AB11984054F8}\n", uuid)); + else if (strcmp(category, "other") == 0) offsetIndex += sprintf(slnTextUpdated + offsetIndex, + TextFormat("\t\t{%s} = {E9D708A5-9C1F-4B84-A795-C5F191801762}\n", uuid)); else LOG("WARNING: Provided category is not valid: %s\n", category); //---------------------------------------------------------------------------------------- From 51bdaa34fa2f84063d643d999e44f54d52cc38d1 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 11 Jan 2026 16:56:47 +0100 Subject: [PATCH 360/430] Update raylib.vcxproj --- projects/VS2022/raylib/raylib.vcxproj | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/VS2022/raylib/raylib.vcxproj b/projects/VS2022/raylib/raylib.vcxproj index 287410f06..8cc3fae7a 100644 --- a/projects/VS2022/raylib/raylib.vcxproj +++ b/projects/VS2022/raylib/raylib.vcxproj @@ -601,7 +601,6 @@ - From 4badbe2b1751b88a813c5f2cfe7ac94d2a979078 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 11 Jan 2026 21:02:59 +0100 Subject: [PATCH 361/430] REVIEWED: Variable scope #5485 --- src/platforms/rcore_drm.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 2e224ad92..a0ae1fa37 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -1415,6 +1415,7 @@ int InitPlatform(void) }; EGLint numConfigs = 0; + const char *eglClientExtensions = NULL; // Get an EGL device connection // NOTE: eglGetPlatformDisplay() is preferred over eglGetDisplay() legacy call @@ -1424,14 +1425,12 @@ int InitPlatform(void) #else // Check if extension is available for eglGetPlatformDisplayEXT() // NOTE: Better compatibility with some drivers (e.g. Mali Midgard) - const char *eglClientExtensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); - if (eglClientExtensions != NULL) + eglClientExtensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if ((eglClientExtensions != NULL) && (strstr(eglClientExtensions, "EGL_EXT_platform_base") != NULL)) { - if (strstr(eglClientExtensions, "EGL_EXT_platform_base") != NULL) - { - PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); - if (eglGetPlatformDisplayEXT != NULL) platform.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, platform.gbmDevice, NULL); - } + PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); + + if (eglGetPlatformDisplayEXT != NULL) platform.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, platform.gbmDevice, NULL); } // In case extension not found or display could not be retrieved, try useing legacy version @@ -1520,13 +1519,9 @@ int InitPlatform(void) if ((eglClientExtensions != NULL) && (strstr(eglClientExtensions, "EGL_EXT_platform_base") != NULL)) { - PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC eglCreatePlatformWindowSurfaceEXT = - (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"); + PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC eglCreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"); - if (eglCreatePlatformWindowSurfaceEXT != NULL) - { - platform.surface = eglCreatePlatformWindowSurfaceEXT(platform.device, platform.config, platform.gbmSurface, NULL); - } + if (eglCreatePlatformWindowSurfaceEXT != NULL) platform.surface = eglCreatePlatformWindowSurfaceEXT(platform.device, platform.config, platform.gbmSurface, NULL); } if (platform.surface == EGL_NO_SURFACE) From d7c38cfbe43dc9f053eacb219cf9523e9c2372b0 Mon Sep 17 00:00:00 2001 From: James Mintram Date: Sun, 11 Jan 2026 21:01:14 +0000 Subject: [PATCH 362/430] fix rglfw.c path in build.zig for bsd platforms (#5486) --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 2e53bfbdc..ab98bbe98 100644 --- a/build.zig +++ b/build.zig @@ -348,7 +348,7 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. } }, .freebsd, .openbsd, .netbsd, .dragonfly => { - try c_source_files.append(b.allocator, "rglfw.c"); + try c_source_files.append(b.allocator, "src/rglfw.c"); raylib.root_module.linkSystemLibrary("GL", .{}); raylib.root_module.linkSystemLibrary("rt", .{}); raylib.root_module.linkSystemLibrary("dl", .{}); From 32e7732061ec425aa261e7eede3b483fa33adbef Mon Sep 17 00:00:00 2001 From: Morgan Moore Date: Sun, 11 Jan 2026 17:43:38 -0500 Subject: [PATCH 363/430] Update build.zig to work with 0.16 (#5487) --- build.zig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/build.zig b/build.zig index ab98bbe98..111a1bcb1 100644 --- a/build.zig +++ b/build.zig @@ -522,12 +522,13 @@ fn addExamples( raylib: *std.Build.Step.Compile, ) !*std.Build.Step { const all = b.step(module, "All " ++ module ++ " examples"); + const io = all.owner.graph.io; const module_subpath = b.pathJoin(&.{ "examples", module }); - var dir = try std.fs.cwd().openDir(b.pathFromRoot(module_subpath), .{ .iterate = true }); - defer dir.close(); + var dir = try std.Io.Dir.cwd().openDir(io, b.pathFromRoot(module_subpath), .{ .iterate = true }); + defer dir.close(io); var iter = dir.iterate(); - while (try iter.next()) |entry| { + while (try iter.next(io)) |entry| { if (entry.kind != .file) continue; const extension_idx = std.mem.lastIndexOf(u8, entry.name, ".c") orelse continue; const name = entry.name[0..extension_idx]; From 0c33c603f4d7244b652fd6d133fa8a1ed8ae583f Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 12 Jan 2026 13:04:38 +0100 Subject: [PATCH 364/430] REVIEWED: `EXTERNAL_CONFIG_FLAGS` usage, check moved to `config.h` Due to `utils` module removal, `EXTERNAL_CONFIG_FLAGS` was not working, so the system was redesigned. This change is independent of #4411 --- src/config.h | 40 +++++++++++++++++++++++++++------------- src/raudio.c | 6 +----- src/rcore.c | 5 +---- src/rmodels.c | 5 +---- src/rshapes.c | 5 +---- src/rtext.c | 5 +---- src/rtextures.c | 5 +---- 7 files changed, 33 insertions(+), 38 deletions(-) diff --git a/src/config.h b/src/config.h index 68b42cc0e..5e7c6f1d2 100644 --- a/src/config.h +++ b/src/config.h @@ -32,25 +32,22 @@ // Module selection - Some modules could be avoided // Mandatory modules: rcore, rlgl //------------------------------------------------------------------------------------ -#define SUPPORT_MODULE_RSHAPES 1 -#define SUPPORT_MODULE_RTEXTURES 1 -#define SUPPORT_MODULE_RTEXT 1 // WARNING: It requires SUPPORT_MODULE_RTEXTURES to load sprite font textures -#define SUPPORT_MODULE_RMODELS 1 -#define SUPPORT_MODULE_RAUDIO 1 +#if !defined(EXTERNAL_CONFIG_FLAGS) + #define SUPPORT_MODULE_RSHAPES 1 + #define SUPPORT_MODULE_RTEXTURES 1 + #define SUPPORT_MODULE_RTEXT 1 // WARNING: It requires SUPPORT_MODULE_RTEXTURES to load sprite font textures + #define SUPPORT_MODULE_RMODELS 1 + #define SUPPORT_MODULE_RAUDIO 1 +#endif //------------------------------------------------------------------------------------ // Module: rcore - Configuration Flags //------------------------------------------------------------------------------------ +#if !defined(EXTERNAL_CONFIG_FLAGS) // Standard file io library (stdio.h) included #define SUPPORT_STANDARD_FILEIO 1 // Show TRACELOG() output messages #define SUPPORT_TRACELOG 1 -#if defined(SUPPORT_TRACELOG) - #define TRACELOG(level, ...) TraceLog(level, __VA_ARGS__) -#else - #define TRACELOG(level, ...) (void)0 -#endif - // Camera module is included (rcamera.h) and multiple predefined cameras are available: free, 1st/3rd person, orbital #define SUPPORT_CAMERA_SYSTEM 1 // Gestures module is included (rgestures.h) to support gestures detection: tap, hold, swipe, drag @@ -79,10 +76,10 @@ // By default EndDrawing() does this job: draws everything + SwapScreenBuffer() + manage frame timing + PollInputEvents() // Enabling this flag allows manual control of the frame processes, use at your own risk //#define SUPPORT_CUSTOM_FRAME_CONTROL 1 - // Support for clipboard image loading // NOTE: Only working on SDL3, GLFW (Windows) and RGFW (Windows) #define SUPPORT_CLIPBOARD_IMAGE 1 +#endif // NOTE: Clipboard image loading requires support for some image file formats // TODO: Those defines should probably be removed from here, letting the user manage them @@ -104,6 +101,12 @@ #endif #endif +#if defined(SUPPORT_TRACELOG) + #define TRACELOG(level, ...) TraceLog(level, __VA_ARGS__) +#else + #define TRACELOG(level, ...) (void)0 +#endif + // rcore: Configuration values //------------------------------------------------------------------------------------ #define MAX_TRACELOG_MSG_LENGTH 256 // Max length of one trace-log message @@ -127,7 +130,7 @@ //------------------------------------------------------------------------------------ // Module: rlgl - Configuration values //------------------------------------------------------------------------------------ - +#if !defined(EXTERNAL_CONFIG_FLAGS) // Enable OpenGL Debug Context (only available on OpenGL 4.3) //#define RLGL_ENABLE_OPENGL_DEBUG_CONTEXT 1 @@ -135,6 +138,7 @@ //#define RLGL_SHOW_GL_DETAILS_INFO 1 #define RL_SUPPORT_MESH_GPU_SKINNING 1 // GPU skinning, comment if your GPU does not support more than 8 VBOs +#endif //#define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 4096 // Default internal render batch elements limits #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) @@ -184,9 +188,11 @@ //------------------------------------------------------------------------------------ // Module: rshapes - Configuration Flags //------------------------------------------------------------------------------------ +#if !defined(EXTERNAL_CONFIG_FLAGS) // Use QUADS instead of TRIANGLES for drawing when possible // Some lines-based shapes could still use lines #define SUPPORT_QUADS_DRAW_MODE 1 +#endif // rshapes: Configuration values //------------------------------------------------------------------------------------ @@ -195,6 +201,7 @@ //------------------------------------------------------------------------------------ // Module: rtextures - Configuration Flags //------------------------------------------------------------------------------------ +#if !defined(EXTERNAL_CONFIG_FLAGS) // Selected desired fileformats to be supported for image data loading #define SUPPORT_FILEFORMAT_PNG 1 //#define SUPPORT_FILEFORMAT_BMP 1 @@ -218,10 +225,12 @@ // Support multiple image editing functions to scale, adjust colors, flip, draw on images, crop... // If not defined, still some functions are supported: ImageFormat(), ImageCrop(), ImageToPOT() #define SUPPORT_IMAGE_MANIPULATION 1 +#endif //------------------------------------------------------------------------------------ // Module: rtext - Configuration Flags //------------------------------------------------------------------------------------ +#if !defined(EXTERNAL_CONFIG_FLAGS) // Default font is loaded on window initialization to be available for the user to render simple text // NOTE: If enabled, uses external module functions to load default raylib font #define SUPPORT_DEFAULT_FONT 1 @@ -241,6 +250,7 @@ // Support conservative font atlas size estimation //#define SUPPORT_FONT_ATLAS_SIZE_CONSERVATIVE 1 +#endif // rtext: Configuration values //------------------------------------------------------------------------------------ @@ -251,6 +261,7 @@ //------------------------------------------------------------------------------------ // Module: rmodels - Configuration Flags //------------------------------------------------------------------------------------ +#if !defined(EXTERNAL_CONFIG_FLAGS) // Selected desired model fileformats to be supported for loading #define SUPPORT_FILEFORMAT_OBJ 1 #define SUPPORT_FILEFORMAT_MTL 1 @@ -261,6 +272,7 @@ // Support procedural mesh generation functions, uses external par_shapes.h library // NOTE: Some generated meshes DO NOT include generated texture coordinates #define SUPPORT_MESH_GENERATION 1 +#endif // rmodels: Configuration values //------------------------------------------------------------------------------------ @@ -275,6 +287,7 @@ //------------------------------------------------------------------------------------ // Module: raudio - Configuration Flags //------------------------------------------------------------------------------------ +#if !defined(EXTERNAL_CONFIG_FLAGS) // Desired audio fileformats to be supported for loading #define SUPPORT_FILEFORMAT_WAV 1 #define SUPPORT_FILEFORMAT_OGG 1 @@ -283,6 +296,7 @@ //#define SUPPORT_FILEFORMAT_FLAC 1 #define SUPPORT_FILEFORMAT_XM 1 #define SUPPORT_FILEFORMAT_MOD 1 +#endif // raudio: Configuration values //------------------------------------------------------------------------------------ diff --git a/src/raudio.c b/src/raudio.c index c25ad1f02..18f9e0aad 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -74,11 +74,7 @@ #else #include "raylib.h" // Declares module functions - // Check if config flags have been externally provided on compilation line - #if !defined(EXTERNAL_CONFIG_FLAGS) - #include "config.h" // Defines module configuration flags - #endif - //#include "utils.h" // Required for: fopen() Android mapping + #include "config.h" // Defines module configuration flags #endif #if defined(SUPPORT_MODULE_RAUDIO) || defined(RAUDIO_STANDALONE) diff --git a/src/rcore.c b/src/rcore.c index 7837f8c4e..dd4123a3b 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -104,10 +104,7 @@ #include "raylib.h" // Declares module functions -// Check if config flags have been externally provided on compilation line -#if !defined(EXTERNAL_CONFIG_FLAGS) - #include "config.h" // Defines module configuration flags -#endif +#include "config.h" // Defines module configuration flags #include // Required for: srand(), rand(), atexit(), exit() #include // Required for: FILE, fopen(), fseek(), ftell(), fread(), fwrite(), fprintf(), vprintf(), fclose(), sprintf() [Used in OpenURL()] diff --git a/src/rmodels.c b/src/rmodels.c index 76efa3e4a..58b08350d 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -42,10 +42,7 @@ #include "raylib.h" // Declares module functions -// Check if config flags have been externally provided on compilation line -#if !defined(EXTERNAL_CONFIG_FLAGS) - #include "config.h" // Defines module configuration flags -#endif +#include "config.h" // Defines module configuration flags #if defined(SUPPORT_MODULE_RMODELS) diff --git a/src/rshapes.c b/src/rshapes.c index e828b98bc..3f686f21a 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -46,10 +46,7 @@ #include "raylib.h" // Declares module functions -// Check if config flags have been externally provided on compilation line -#if !defined(EXTERNAL_CONFIG_FLAGS) - #include "config.h" // Defines module configuration flags -#endif +#include "config.h" // Defines module configuration flags #if defined(SUPPORT_MODULE_RSHAPES) diff --git a/src/rtext.c b/src/rtext.c index 7a1b3c027..8085e81c8 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -55,10 +55,7 @@ #include "raylib.h" // Declares module functions -// Check if config flags have been externally provided on compilation line -#if !defined(EXTERNAL_CONFIG_FLAGS) - #include "config.h" // Defines module configuration flags -#endif +#include "config.h" // Defines module configuration flags #if defined(SUPPORT_MODULE_RTEXT) diff --git a/src/rtextures.c b/src/rtextures.c index fa03d193b..a20b5e516 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -63,10 +63,7 @@ #include "raylib.h" // Declares module functions -// Check if config flags have been externally provided on compilation line -#if !defined(EXTERNAL_CONFIG_FLAGS) - #include "config.h" // Defines module configuration flags -#endif +#include "config.h" // Defines module configuration flags #if defined(SUPPORT_MODULE_RTEXTURES) From 28b9411e9d8d14ba2bf991a5d2ce95f3498d0cde Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 12 Jan 2026 13:23:27 +0100 Subject: [PATCH 365/430] REMOVED: `RLGL_RENDER_TEXTURES_HINT`, enabled by default and no complaints of anyone having issues #5479 --- src/rlgl.h | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 7fa22f9cc..8b264343a 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -37,10 +37,6 @@ * If not defined, the library is in header only mode and can be included in other headers * or source files without problems. But only ONE file should hold the implementation * -* #define RLGL_RENDER_TEXTURES_HINT -* Enable framebuffer objects (fbo) support (enabled by default) -* Some GPUs could not support them despite the OpenGL version -* * #define RLGL_SHOW_GL_DETAILS_INFO * Show OpenGL extensions and capabilities detailed logs on init * @@ -196,10 +192,6 @@ #define GRAPHICS_API_OPENGL_ES2 #endif -// Support framebuffer objects by default -// NOTE: Some driver implementation do not support it, despite they should -#define RLGL_RENDER_TEXTURES_HINT - //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -1863,7 +1855,7 @@ void rlDisableShader(void) // Enable rendering to texture (fbo) void rlEnableFramebuffer(unsigned int id) { -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) glBindFramebuffer(GL_FRAMEBUFFER, id); #endif } @@ -1872,7 +1864,7 @@ void rlEnableFramebuffer(unsigned int id) unsigned int rlGetActiveFramebuffer(void) { GLint fboId = 0; -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT) +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fboId); #endif return fboId; @@ -1881,7 +1873,7 @@ unsigned int rlGetActiveFramebuffer(void) // Disable rendering to texture void rlDisableFramebuffer(void) { -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) glBindFramebuffer(GL_FRAMEBUFFER, 0); #endif } @@ -1889,7 +1881,7 @@ void rlDisableFramebuffer(void) // Blit active framebuffer to main framebuffer void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight, int bufferMask) { -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT) +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) glBlitFramebuffer(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, bufferMask, GL_NEAREST); #endif } @@ -1897,7 +1889,7 @@ void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX // Bind framebuffer object (fbo) void rlBindFramebuffer(unsigned int target, unsigned int framebuffer) { -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) glBindFramebuffer(target, framebuffer); #endif } @@ -1906,7 +1898,7 @@ void rlBindFramebuffer(unsigned int target, unsigned int framebuffer) // NOTE: One color buffer is always active by default void rlActiveDrawBuffers(int count) { -#if ((defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT)) +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) // NOTE: Maximum number of draw buffers supported is implementation dependant, // it can be queried with glGet*() but it must be at least 8 //GLint maxDrawBuffers = 0; @@ -3826,7 +3818,7 @@ unsigned int rlLoadFramebuffer(void) unsigned int fboId = 0; if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return fboId; } -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) glGenFramebuffers(1, &fboId); // Create the framebuffer object glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind any framebuffer #endif @@ -3838,7 +3830,7 @@ unsigned int rlLoadFramebuffer(void) // NOTE: Attach type: 0-Color, 1-Depth renderbuffer, 2-Depth texture void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel) { -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) glBindFramebuffer(GL_FRAMEBUFFER, fboId); switch (attachType) @@ -3878,7 +3870,7 @@ bool rlFramebufferComplete(unsigned int id) { bool result = false; -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) glBindFramebuffer(GL_FRAMEBUFFER, id); GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); @@ -3909,7 +3901,7 @@ bool rlFramebufferComplete(unsigned int id) // NOTE: All attached textures/cubemaps/renderbuffers are also deleted void rlUnloadFramebuffer(unsigned int id) { -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) // Query depth attachment to automatically delete texture/renderbuffer int depthType = 0, depthId = 0; glBindFramebuffer(GL_FRAMEBUFFER, id); // Bind framebuffer to query depth texture type From 644ff28f87055e84289684036b67bc7149e38ac3 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 12 Jan 2026 13:36:40 +0100 Subject: [PATCH 366/430] Update shaders_deferred_rendering.c --- examples/shaders/shaders_deferred_rendering.c | 110 ++++++++---------- 1 file changed, 51 insertions(+), 59 deletions(-) diff --git a/examples/shaders/shaders_deferred_rendering.c b/examples/shaders/shaders_deferred_rendering.c index 811566917..4b03b69a4 100644 --- a/examples/shaders/shaders_deferred_rendering.c +++ b/examples/shaders/shaders_deferred_rendering.c @@ -40,13 +40,13 @@ //---------------------------------------------------------------------------------- // GBuffer data typedef struct GBuffer { - unsigned int framebuffer; + unsigned int framebufferId; - unsigned int positionTexture; - unsigned int normalTexture; - unsigned int albedoSpecTexture; + unsigned int positionTextureId; + unsigned int normalTextureId; + unsigned int albedoSpecTextureId; - unsigned int depthRenderbuffer; + unsigned int depthRenderbufferId; } GBuffer; // Deferred mode passes @@ -90,15 +90,10 @@ int main(void) // Initialize the G-buffer GBuffer gBuffer = { 0 }; - gBuffer.framebuffer = rlLoadFramebuffer(); + gBuffer.framebufferId = rlLoadFramebuffer(); + if (gBuffer.framebufferId == 0) TraceLog(LOG_WARNING, "Failed to create framebufferId"); - if (!gBuffer.framebuffer) - { - TraceLog(LOG_WARNING, "Failed to create framebuffer"); - exit(1); - } - - rlEnableFramebuffer(gBuffer.framebuffer); + rlEnableFramebuffer(gBuffer.framebufferId); // NOTE: Vertex positions are stored in a texture for simplicity. A better approach would use a depth texture // (instead of a detph renderbuffer) to reconstruct world positions in the final render shader via clip-space position, @@ -107,46 +102,42 @@ int main(void) // 16-bit precision ensures OpenGL ES 3 compatibility, though it may lack precision for real scenarios // But as mentioned above, the positions could be reconstructed instead of stored. If not targeting OpenGL ES // and you wish to maintain this approach, consider using `RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32` - gBuffer.positionTexture = rlLoadTexture(NULL, screenWidth, screenHeight, RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16, 1); + gBuffer.positionTextureId = rlLoadTexture(NULL, screenWidth, screenHeight, RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16, 1); // Similarly, 16-bit precision is used for normals ensures OpenGL ES 3 compatibility // This is generally sufficient, but a 16-bit fixed-point format offer a better uniform precision in all orientations - gBuffer.normalTexture = rlLoadTexture(NULL, screenWidth, screenHeight, RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16, 1); + gBuffer.normalTextureId = rlLoadTexture(NULL, screenWidth, screenHeight, RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16, 1); // Albedo (diffuse color) and specular strength can be combined into one texture // The color in RGB, and the specular strength in the alpha channel - gBuffer.albedoSpecTexture = rlLoadTexture(NULL, screenWidth, screenHeight, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); + gBuffer.albedoSpecTextureId = rlLoadTexture(NULL, screenWidth, screenHeight, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); - // Activate the draw buffers for our framebuffer + // Activate the draw buffers for our framebufferId rlActiveDrawBuffers(3); - // Now we attach our textures to the framebuffer - rlFramebufferAttach(gBuffer.framebuffer, gBuffer.positionTexture, RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_TEXTURE2D, 0); - rlFramebufferAttach(gBuffer.framebuffer, gBuffer.normalTexture, RL_ATTACHMENT_COLOR_CHANNEL1, RL_ATTACHMENT_TEXTURE2D, 0); - rlFramebufferAttach(gBuffer.framebuffer, gBuffer.albedoSpecTexture, RL_ATTACHMENT_COLOR_CHANNEL2, RL_ATTACHMENT_TEXTURE2D, 0); + // Now we attach our textures to the framebufferId + rlFramebufferAttach(gBuffer.framebufferId, gBuffer.positionTextureId, RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_TEXTURE2D, 0); + rlFramebufferAttach(gBuffer.framebufferId, gBuffer.normalTextureId, RL_ATTACHMENT_COLOR_CHANNEL1, RL_ATTACHMENT_TEXTURE2D, 0); + rlFramebufferAttach(gBuffer.framebufferId, gBuffer.albedoSpecTextureId, RL_ATTACHMENT_COLOR_CHANNEL2, RL_ATTACHMENT_TEXTURE2D, 0); // Finally we attach the depth buffer - gBuffer.depthRenderbuffer = rlLoadTextureDepth(screenWidth, screenHeight, true); - rlFramebufferAttach(gBuffer.framebuffer, gBuffer.depthRenderbuffer, RL_ATTACHMENT_DEPTH, RL_ATTACHMENT_RENDERBUFFER, 0); + gBuffer.depthRenderbufferId = rlLoadTextureDepth(screenWidth, screenHeight, true); + rlFramebufferAttach(gBuffer.framebufferId, gBuffer.depthRenderbufferId, RL_ATTACHMENT_DEPTH, RL_ATTACHMENT_RENDERBUFFER, 0); - // Make sure our framebuffer is complete - // NOTE: rlFramebufferComplete() automatically unbinds the framebuffer, so we don't have - // to rlDisableFramebuffer() here - if (!rlFramebufferComplete(gBuffer.framebuffer)) - { - TraceLog(LOG_WARNING, "Framebuffer is not complete"); - } + // Make sure our framebufferId is complete + // NOTE: rlFramebufferComplete() automatically unbinds the framebufferId, so we don't have to rlDisableFramebuffer() here + if (!rlFramebufferComplete(gBuffer.framebufferId)) TraceLog(LOG_WARNING, "Framebuffer is not complete"); // Now we initialize the sampler2D uniform's in the deferred shader // We do this by setting the uniform's values to the texture units that // we later bind our g-buffer textures to rlEnableShader(deferredShader.id); - int texUnitPosition = 0; - int texUnitNormal = 1; - int texUnitAlbedoSpec = 2; - SetShaderValue(deferredShader, rlGetLocationUniform(deferredShader.id, "gPosition"), &texUnitPosition, RL_SHADER_UNIFORM_SAMPLER2D); - SetShaderValue(deferredShader, rlGetLocationUniform(deferredShader.id, "gNormal"), &texUnitNormal, RL_SHADER_UNIFORM_SAMPLER2D); - SetShaderValue(deferredShader, rlGetLocationUniform(deferredShader.id, "gAlbedoSpec"), &texUnitAlbedoSpec, RL_SHADER_UNIFORM_SAMPLER2D); + int texUnitPosition = 0; + int texUnitNormal = 1; + int texUnitAlbedoSpec = 2; + SetShaderValue(deferredShader, rlGetLocationUniform(deferredShader.id, "gPosition"), &texUnitPosition, RL_SHADER_UNIFORM_SAMPLER2D); + SetShaderValue(deferredShader, rlGetLocationUniform(deferredShader.id, "gNormal"), &texUnitNormal, RL_SHADER_UNIFORM_SAMPLER2D); + SetShaderValue(deferredShader, rlGetLocationUniform(deferredShader.id, "gAlbedoSpec"), &texUnitAlbedoSpec, RL_SHADER_UNIFORM_SAMPLER2D); rlDisableShader(); // Assign out lighting shader to model @@ -176,7 +167,7 @@ int main(void) cubeRotations[i] = (float)(rand()%360); } - DeferredMode mode = DEFERRED_SHADING; + int mode = DEFERRED_SHADING; rlEnableDepthTest(); @@ -215,17 +206,16 @@ int main(void) BeginDrawing(); // Draw to the geometry buffer by first activating it - rlEnableFramebuffer(gBuffer.framebuffer); + rlEnableFramebuffer(gBuffer.framebufferId); rlClearColor(0, 0, 0, 0); rlClearScreenBuffers(); // Clear color and depth buffer - rlDisableColorBlend(); + BeginMode3D(camera); // NOTE: We have to use rlEnableShader here. `BeginShaderMode` or thus `rlSetShader` // will not work, as they won't immediately load the shader program rlEnableShader(gbufferShader.id); - // When drawing a model here, make sure that the material's shaders - // are set to the gbuffer shader! + // When drawing a model here, make sure that the material's shaders are set to the gbuffer shader! DrawModel(model, Vector3Zero(), 1.0f, WHITE); DrawModel(cube, (Vector3) { 0.0, 1.0f, 0.0 }, 1.0f, WHITE); @@ -234,12 +224,12 @@ int main(void) Vector3 position = cubePositions[i]; DrawModelEx(cube, position, (Vector3) { 1, 1, 1 }, cubeRotations[i], (Vector3) { CUBE_SCALE, CUBE_SCALE, CUBE_SCALE }, WHITE); } - rlDisableShader(); EndMode3D(); + rlEnableColorBlend(); - // Go back to the default framebuffer (0) and draw our deferred shading + // Go back to the default framebufferId (0) and draw our deferred shading rlDisableFramebuffer(); rlClearScreenBuffers(); // Clear color & depth buffer @@ -254,21 +244,21 @@ int main(void) // We are binding them to locations that we earlier set in sampler2D uniforms `gPosition`, `gNormal`, // and `gAlbedoSpec` rlActiveTextureSlot(texUnitPosition); - rlEnableTexture(gBuffer.positionTexture); + rlEnableTexture(gBuffer.positionTextureId); rlActiveTextureSlot(texUnitNormal); - rlEnableTexture(gBuffer.normalTexture); + rlEnableTexture(gBuffer.normalTextureId); rlActiveTextureSlot(texUnitAlbedoSpec); - rlEnableTexture(gBuffer.albedoSpecTexture); + rlEnableTexture(gBuffer.albedoSpecTextureId); - // Finally, we draw a fullscreen quad to our default framebuffer + // Finally, we draw a fullscreen quad to our default framebufferId // This will now be shaded using our deferred shader rlLoadDrawQuad(); rlDisableShader(); rlEnableColorBlend(); EndMode3D(); - // As a last step, we now copy over the depth buffer from our g-buffer to the default framebuffer - rlBindFramebuffer(RL_READ_FRAMEBUFFER, gBuffer.framebuffer); + // As a last step, we now copy over the depth buffer from our g-buffer to the default framebufferId + rlBindFramebuffer(RL_READ_FRAMEBUFFER, gBuffer.framebufferId); rlBindFramebuffer(RL_DRAW_FRAMEBUFFER, 0); rlBlitFramebuffer(0, 0, screenWidth, screenHeight, 0, 0, screenWidth, screenHeight, 0x00000100); // GL_DEPTH_BUFFER_BIT rlDisableFramebuffer(); @@ -290,7 +280,7 @@ int main(void) case DEFERRED_POSITION: { DrawTextureRec((Texture2D){ - .id = gBuffer.positionTexture, + .id = gBuffer.positionTextureId, .width = screenWidth, .height = screenHeight, }, (Rectangle) { 0, 0, (float)screenWidth, (float)-screenHeight }, Vector2Zero(), RAYWHITE); @@ -300,7 +290,7 @@ int main(void) case DEFERRED_NORMAL: { DrawTextureRec((Texture2D){ - .id = gBuffer.normalTexture, + .id = gBuffer.normalTextureId, .width = screenWidth, .height = screenHeight, }, (Rectangle) { 0, 0, (float)screenWidth, (float)-screenHeight }, Vector2Zero(), RAYWHITE); @@ -310,7 +300,7 @@ int main(void) case DEFERRED_ALBEDO: { DrawTextureRec((Texture2D){ - .id = gBuffer.albedoSpecTexture, + .id = gBuffer.albedoSpecTextureId, .width = screenWidth, .height = screenHeight, }, (Rectangle) { 0, 0, (float)screenWidth, (float)-screenHeight }, Vector2Zero(), RAYWHITE); @@ -331,18 +321,20 @@ int main(void) // De-Initialization //-------------------------------------------------------------------------------------- - UnloadModel(model); // Unload the models + // Unload the models + UnloadModel(model); UnloadModel(cube); - UnloadShader(deferredShader); // Unload shaders + // Unload shaders + UnloadShader(deferredShader); UnloadShader(gbufferShader); // Unload geometry buffer and all attached textures - rlUnloadFramebuffer(gBuffer.framebuffer); - rlUnloadTexture(gBuffer.positionTexture); - rlUnloadTexture(gBuffer.normalTexture); - rlUnloadTexture(gBuffer.albedoSpecTexture); - rlUnloadTexture(gBuffer.depthRenderbuffer); + rlUnloadFramebuffer(gBuffer.framebufferId); + rlUnloadTexture(gBuffer.positionTextureId); + rlUnloadTexture(gBuffer.normalTextureId); + rlUnloadTexture(gBuffer.albedoSpecTextureId); + rlUnloadTexture(gBuffer.depthRenderbufferId); CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- From cfb81e4a0000b1ad1bd5726a697aaa66c82ba73d Mon Sep 17 00:00:00 2001 From: Iisakki Rotko Date: Mon, 12 Jan 2026 22:58:41 +0100 Subject: [PATCH 367/430] fix zig build accessing env vars (#5490) --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 111a1bcb1..c62e48642 100644 --- a/build.zig +++ b/build.zig @@ -448,7 +448,7 @@ pub const Options = struct { .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 &.{}, - .android_ndk = b.option([]const u8, "android_ndk", "specify path to android ndk") orelse std.process.getEnvVarOwned(b.allocator, "ANDROID_NDK_HOME") catch "", + .android_ndk = b.option([]const u8, "android_ndk", "specify path to android ndk") orelse "", .android_api_version = b.option([]const u8, "android_api_version", "specify target android API level") orelse defaults.android_api_version, }; } From 132151cf280b738c2a0e8b1fb0c5e081a1366478 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 13 Jan 2026 16:27:16 +0100 Subject: [PATCH 368/430] Revert "Update build.zig to work with 0.16 (#5487)" This reverts commit 32e7732061ec425aa261e7eede3b483fa33adbef. --- build.zig | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/build.zig b/build.zig index 111a1bcb1..ab98bbe98 100644 --- a/build.zig +++ b/build.zig @@ -522,13 +522,12 @@ fn addExamples( raylib: *std.Build.Step.Compile, ) !*std.Build.Step { const all = b.step(module, "All " ++ module ++ " examples"); - const io = all.owner.graph.io; const module_subpath = b.pathJoin(&.{ "examples", module }); - var dir = try std.Io.Dir.cwd().openDir(io, b.pathFromRoot(module_subpath), .{ .iterate = true }); - defer dir.close(io); + var dir = try std.fs.cwd().openDir(b.pathFromRoot(module_subpath), .{ .iterate = true }); + defer dir.close(); var iter = dir.iterate(); - while (try iter.next(io)) |entry| { + while (try iter.next()) |entry| { if (entry.kind != .file) continue; const extension_idx = std.mem.lastIndexOf(u8, entry.name, ".c") orelse continue; const name = entry.name[0..extension_idx]; From 4be6815b3b15e6ec9b37c12bc111fab7ab340394 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 13 Jan 2026 16:27:51 +0100 Subject: [PATCH 369/430] Revert "fix zig build accessing env vars (#5490)" This reverts commit cfb81e4a0000b1ad1bd5726a697aaa66c82ba73d. --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 50222a109..ab98bbe98 100644 --- a/build.zig +++ b/build.zig @@ -448,7 +448,7 @@ pub const Options = struct { .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 &.{}, - .android_ndk = b.option([]const u8, "android_ndk", "specify path to android ndk") orelse "", + .android_ndk = b.option([]const u8, "android_ndk", "specify path to android ndk") orelse std.process.getEnvVarOwned(b.allocator, "ANDROID_NDK_HOME") catch "", .android_api_version = b.option([]const u8, "android_api_version", "specify target android API level") orelse defaults.android_api_version, }; } From 4b74312860e16de6272803e1ae7abfde006c31c9 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 13 Jan 2026 20:03:30 +0100 Subject: [PATCH 370/430] Update ROADMAP.md --- ROADMAP.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 9a8111133..a49cdbfd7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -15,13 +15,13 @@ _Current version of raylib is complete and functional but there is always room f **raylib 5.x** - [ ] `rcore`: Support additional platforms: iOS, consoles? - - [ ] `rcore_web`: Avoid GLFW dependency, functionality can be directly implemented using emscripten SDK + - [x] `rcore_web`: Avoid GLFW dependency, functionality can be directly implemented using emscripten SDK - [ ] `rlgl`: Review GLSL shaders naming conventions for consistency - [ ] `textures`: Improve compressed textures support, loading and saving - [ ] `rmodels`: Improve 3d objects loading, specially animations (obj, gltf) - [ ] `raudio`: Implement miniaudio high-level provided features - - [ ] `examples`: Review all examples, add more and better code explanations - - [ ] Software renderer backend? Maybe using `Image` provided API + - [x] `examples`: Review all examples, add more and better code explanations + - [x] Software renderer backend? Maybe using `Image` provided API **raylib 4.x** - [x] Split core module into separate platforms? From 026b7e808a6b30c2948b1b1c4e666c9cbaf4322c Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Thu, 15 Jan 2026 01:03:54 -0800 Subject: [PATCH 371/430] fix drm resources leak (#5494) --- src/platforms/rcore_drm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index a0ae1fa37..b296e0042 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -1161,7 +1161,8 @@ int InitPlatform(void) platform.fd = open("/dev/dri/by-path/platform-gpu-card", O_RDWR); // VideoCore VI (Raspberry Pi 4) if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: platform-gpu-card opened successfully"); - if ((platform.fd == -1) || (drmModeGetResources(platform.fd) == NULL)) + drmModeRes *res = NULL; + if ((platform.fd == -1) || ((res = drmModeGetResources(platform.fd)) == NULL)) { if (platform.fd != -1) close(platform.fd); TRACELOG(LOG_WARNING, "DISPLAY: Failed to open platform-gpu-card, trying card1"); @@ -1169,7 +1170,7 @@ int InitPlatform(void) if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: card1 opened successfully"); } - if ((platform.fd == -1) || (drmModeGetResources(platform.fd) == NULL)) + if ((platform.fd == -1) || ((res = drmModeGetResources(platform.fd)) == NULL)) { if (platform.fd != -1) close(platform.fd); TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card1, trying card0"); @@ -1177,7 +1178,7 @@ int InitPlatform(void) if (platform.fd != -1) TRACELOG(LOG_INFO, "DISPLAY: card0 opened successfully"); } - if ((platform.fd == -1) || (drmModeGetResources(platform.fd) == NULL)) + if ((platform.fd == -1) || ((res = drmModeGetResources(platform.fd)) == NULL)) { if (platform.fd != -1) close(platform.fd); TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card0, trying card2"); @@ -1192,7 +1193,6 @@ int InitPlatform(void) return -1; } - drmModeRes *res = drmModeGetResources(platform.fd); if (!res) { TRACELOG(LOG_WARNING, "DISPLAY: Failed get DRM resources"); From a938a7c97a8372e0d4a84addbb1cd1bc3673c44c Mon Sep 17 00:00:00 2001 From: base Date: Thu, 15 Jan 2026 06:08:03 -0300 Subject: [PATCH 372/430] chore: add GetAppDir for wasm returning root VFS (#5495) --- src/rcore.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/rcore.c b/src/rcore.c index dd4123a3b..a393264af 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2738,6 +2738,9 @@ const char *GetApplicationDirectory(void) appDir[0] = '.'; appDir[1] = '/'; } + +#elif defined(__wasm__) + appDir[0] = '/'; #endif return appDir; From 0df2fe981b8173db9074688a1f612a3263f96052 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 15 Jan 2026 10:42:58 +0100 Subject: [PATCH 373/430] REVIEWED: `ExportFontAsCode()` #5497 --- src/rtext.c | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/rtext.c b/src/rtext.c index 8085e81c8..45aa19b13 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1044,15 +1044,26 @@ bool ExportFontAsCode(Font font, const char *fileName) #define TEXT_BYTES_PER_LINE 20 #endif - #define MAX_FONT_DATA_SIZE 1024*1024 // 1 MB - // Get file name from path char fileNamePascal[256] = { 0 }; strncpy(fileNamePascal, TextToPascal(GetFileNameWithoutExt(fileName)), 256 - 1); + + // Get font atlas image and size, required to estimate code file size + // NOTE: This mechanism is highly coupled to raylib + Image image = LoadImageFromTexture(font.texture); + if (image.format != PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) TRACELOG(LOG_WARNING, "Font export as code: Font image format is not GRAY+ALPHA!"); + int imageDataSize = GetPixelDataSize(image.width, image.height, image.format); - // NOTE: Text data buffer size is estimated considering image data size in bytes - // and requiring 6 char bytes for every byte: "0x00, " - char *txtData = (char *)RL_CALLOC(MAX_FONT_DATA_SIZE, sizeof(char)); + // Image data is usually GRAYSCALE + ALPHA and can be reduced to GRAYSCALE + //ImageFormat(&image, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); + + // Estimate text code size + // - Image data is stored as "0x%02x", so it requires at least 4 char per byte, let's use 6 + // - font.recs[] data is stored as "{ %1.0f, %1.0f, %1.0f , %1.0f }", let's reserve 64 per rec + // - font.glyphs[] data is stored as "{ %i, %i, %i, %i, { 0 }},\n", let's reserve 64 per glyph + // - Comments and additional code, let's reserve 32KB + int txtDataSize = imageDataSize*6 + font.glyphCount*64 + font.glyphCount*64 + 32768; + char *txtData = (char *)RL_CALLOC(txtDataSize, sizeof(char)); int byteCount = 0; byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n"); @@ -1074,15 +1085,6 @@ bool ExportFontAsCode(Font font, const char *fileName) byteCount += sprintf(txtData + byteCount, "// //\n"); byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n"); - // Support font export and initialization - // NOTE: This mechanism is highly coupled to raylib - Image image = LoadImageFromTexture(font.texture); - if (image.format != PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) TRACELOG(LOG_WARNING, "Font export as code: Font image format is not GRAY+ALPHA!"); - int imageDataSize = GetPixelDataSize(image.width, image.height, image.format); - - // Image data is usually GRAYSCALE + ALPHA and can be reduced to GRAYSCALE - //ImageFormat(&image, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); - #define SUPPORT_COMPRESSED_FONT_ATLAS #if defined(SUPPORT_COMPRESSED_FONT_ATLAS) // WARNING: Data is compressed using raylib CompressData() DEFLATE, @@ -1120,8 +1122,7 @@ bool ExportFontAsCode(Font font, const char *fileName) byteCount += sprintf(txtData + byteCount, "};\n\n"); // Save font glyphs data - // NOTE: Glyphs image data not saved (grayscale pixels), - // it could be generated from image and recs + // NOTE: Glyphs image data not saved (grayscale pixels), it could be generated from image and recs byteCount += sprintf(txtData + byteCount, "// Font glyphs info data\n"); byteCount += sprintf(txtData + byteCount, "// NOTE: No glyphs.image data provided\n"); byteCount += sprintf(txtData + byteCount, "static GlyphInfo fontGlyphs_%s[%i] = {\n", fileNamePascal, font.glyphCount); @@ -1152,8 +1153,8 @@ bool ExportFontAsCode(Font font, const char *fileName) #if defined(SUPPORT_COMPRESSED_FONT_ATLAS) byteCount += sprintf(txtData + byteCount, " UnloadImage(imFont); // Uncompressed data can be unloaded from memory\n\n"); #endif - // We have two possible mechanisms to assign font.recs and font.glyphs data, - // that data is already available as global arrays, we two options to assign that data: + // There are two possible mechanisms to assign font.recs and font.glyphs data, + // that data is already available as global arrays, two options to assign that data: // - 1. Data copy. This option consumes more memory and Font MUST be unloaded by user, requiring additional code // - 2. Data assignment. This option consumes less memory and Font MUST NOT be unloaded by user because data is on protected DATA segment //#define SUPPORT_FONT_DATA_COPY From 439448ad7ccfadd2adb15502e17564d873e4dc92 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 15 Jan 2026 10:43:08 +0100 Subject: [PATCH 374/430] Update rcore.c --- src/rcore.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rcore.c b/src/rcore.c index a393264af..1794637bd 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2740,6 +2740,7 @@ const char *GetApplicationDirectory(void) } #elif defined(__wasm__) + appDir[0] = '/'; #endif From 10b94b02adb469e7ce9af169c2d21c13124a82ce Mon Sep 17 00:00:00 2001 From: jscaff Date: Thu, 15 Jan 2026 17:30:13 -0500 Subject: [PATCH 375/430] Fix opengl interop single header library not having it's implementation loaded (#5498) --- examples/others/raylib_opengl_interop.c | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/others/raylib_opengl_interop.c b/examples/others/raylib_opengl_interop.c index 540a623ab..935eb87f8 100644 --- a/examples/others/raylib_opengl_interop.c +++ b/examples/others/raylib_opengl_interop.c @@ -30,6 +30,7 @@ #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_DESKTOP_SDL) #if defined(GRAPHICS_API_OPENGL_ES2) + #define GLAD_GLES2_IMPLEMENTATION #include "glad_gles2.h" // Required for: OpenGL functionality #define glGenVertexArrays glGenVertexArraysOES #define glBindVertexArray glBindVertexArrayOES From 9621c3d395859382f856389c54d81b1c2f9abeee Mon Sep 17 00:00:00 2001 From: jscaff Date: Fri, 16 Jan 2026 03:42:15 -0500 Subject: [PATCH 376/430] Add QNX EGL2.0 Library configuration (#5499) --- cmake/LibraryConfigurations.cmake | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmake/LibraryConfigurations.cmake b/cmake/LibraryConfigurations.cmake index 96abeea93..9b8fbdb25 100644 --- a/cmake/LibraryConfigurations.cmake +++ b/cmake/LibraryConfigurations.cmake @@ -27,6 +27,12 @@ if (${PLATFORM} MATCHES "Desktop") add_definitions(-D_CRT_SECURE_NO_WARNINGS) find_package(OpenGL QUIET) set(LIBS_PRIVATE ${OPENGL_LIBRARIES} winmm) + elseif("${CMAKE_SYSTEM_NAME}" MATCHES "QNX") + set(GRAPHICS "GRAPHICS_API_OPENGL_ES2") + find_library(GLESV2 GLESv2) + find_library(EGL EGL) + set(LIBS_PUBLIC m) + set(LIBS_PRIVATE ${GLESV2} ${EGL} atomic pthread dl) elseif (UNIX) find_library(pthread NAMES pthread) find_package(OpenGL QUIET) From fbed591a6ff2da8b953ac201943799ea6129b6aa Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 17 Jan 2026 20:15:45 +0100 Subject: [PATCH 377/430] Reviewed example --- examples/audio/audio_spectrum_visualizer.c | 2 +- examples/audio/audio_spectrum_visualizer.png | Bin 15580 -> 15477 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/audio/audio_spectrum_visualizer.c b/examples/audio/audio_spectrum_visualizer.c index f5334c9cc..5cde7e9bd 100644 --- a/examples/audio/audio_spectrum_visualizer.c +++ b/examples/audio/audio_spectrum_visualizer.c @@ -115,7 +115,7 @@ int main(void) .tapbackPos = 0.01f }; - size_t wavCursor = 0; + int wavCursor = 0; const short *wavPCM16 = wav.data; short chunkSamples[AUDIO_STREAM_RING_BUFFER_SIZE] = { 0 }; diff --git a/examples/audio/audio_spectrum_visualizer.png b/examples/audio/audio_spectrum_visualizer.png index c3f1bc8b0acfbc066c241358f06922dcc8133e86..f885927b1b8bbc124c7ac52f0fb41a8f5d958cc2 100644 GIT binary patch literal 15477 zcmeHOdt4Od8s1$HR$6pbv}FMSQPPqVmq8T4h}BtdtCVI*W{V9|yW(6in24<#4$4h!T@67Ct*%^1aX!Ses$Nu)l%=^8U z=Y8Ji`!+LSjNH>h>cQjjJVy*4`ZAB_62Rl}j|ko1ou{YnI?m(8#EcmF;`oJ=r#|TH zD#DeBD=Oo7Xb=lV1hET z5rAi=bD5!@9T@sG-&--0(+1U3lnqljZ6Hm^=dceoP(w(?fv!WfSEC*z*N~NCwk|Gni%1yP|K!q2qpGH$bQ5)y6A} zUBZnQxU=>z6QG_O{t#a1JwHz3?cFa;5_w9Z@aK%WEP{^ujn+pU9UOp9=fE*YmAflS z8$`#k9X(kvio&hj*M+*YIJDb0Tqcxa?#YNJTUAi+i4KV+TOhU1aV}Ez6o1EpIP|A` z5=9B;m-fdiRc^`1EYAB>g}9PS&78#rm{fJRaaM=+O`+~I;NdkcNai0{K`nJ}77GB- zW`UooIS>NAa1-#@JDV6E%LM^({}fYQ;ZrUXG>6aey-OygBEvY5C?SZ-m}-J6T#j+# z&yX>{duJoT9N}pGki-w-l^0H_L&L-M`DZwz`C?B&Z%BNf`}lP39$O=p)B>!haVG8` z0MPyn8N>g0aDYyy!wH0|{2-dWX*A66ZZ#UVM|tIL;(OQD3^7)6#Qd(dw4v)x;%pm$ zge0d@HeO1<_ikWla!MR}N2^OY#F4)D149qEzKwJ@aG{|?KsGVh(((ZZcFsh$vtFo} zLd5)g97cGy11R&2r`1(63}p=$Gl`u8*^`6p!yA-Y`8AKxjhs8%_7oHLZ7*!_0A_mEVO<>)BM_=Nggf6Vi!|mLKn^A^NPkA1~Naf z=m0$E8eaG=`hIUL?DBUwYZOL+F%CGsyf z8dARDaA^$Pu_8ISGl@Gh-IG-*$;bwFlFxexWdvi$Irfi%`?m*nwQ&q-Hxs(}3Gh3I zbt!5)hvv?qhQe_*f8;5n6u9+Rcd24Uvuw15$FoqN&?k^c+c#~$O;2Q1p#}1{a?p9V z*;ktj3&M?V~PQ2PjChwnM{uTGStZLd{P3(Muu|U(yv(S}+ zn~sz^gx_qThL~7DERVLkP#Mg?;EH~WSa6q8LY}mC8YfdWsWpUaGjrE#|63ZQ&Okt;z19^r0{?2r=mgJ{8))h6(@2T)+cttjdmaBeZht|JjZF;(P ztSZndP~hqdAS=s1Im+%%(LJ%`B@nLA2bkWE9bh%^Hnc>qW>94tG>6qr;rFApg=i48 zcC?8O@}Uk3U%60y7~8K~FSfPMTxzmNI)*T@KG1g>hpG96-}b&zk_xR$uws< zg*%Uz#XpgFw9nR+c9;+((@sBp;Cti0K9yQVSc9ISFtKoHn=nx`+yy^537vOwILPtH z3&yJ}X9#uqwM4O5(U3*adoGV3lJX#l(;En{2(Nf0vPGX?&CDW_o0{{P;S3O3sNI69 zL3^|GJj2x+%>N~KopXGtO~)qST4e#?yif6pg$(BvET_opu1E381Q0>+0=(ZG)Bg%r zmSPhJ8AsZVgz5+MHczabo#q8N+>U-C<3C9*xp%~A6SPTFd}4d4id|V{;GJ}T-_-hr zI^K65jsMyKT?2c)2`{+1y?#(y`8{5^gb}%-<&+9s>^xF$3k;2p7+`F&BClI7s-zlc zGp=oU>A1%`46EbS!a)aF%$SO=hDIN_r7G^rdS0P`mP<<5(wuzHd7u>?g}wTV1B|n+ zZZIV>&!pyMxBe#WHhViTA-m6~#JIKSyx;6v5=^0~z}O+p?*~Uibme&FG*dYHcGeId ztB~%rc+o_8^eX5159d9~G5FH^ok?4F$iI{tdc4k7FU$JT`8U^+aEzU5Oa7Jp4N!UM zO?e}BSC?|1;T?^LUNCFskUYmBv^XYMkA08JVuE&(P_%DqvfNPOSD9?*H8qUA5ZoDU z6LO2TYlvx)_-871#}9{0f#x&b!OWKs2*QX=E7$vF{hL_5pf~iAXn)1`R-0WwK_WLw zoyRD9Rtk%Cf=$+exHn)45(X1PV_>Lp4mz&VwluQoQy*UD1&xOi8B>9+wv`$9H8isO zS?y;$Gh-bNltKSMAKW()R(wFJc(vL0me2=rjCS~9&TWS~wk_n?WSjqWGFoi|2B|cS zFIKD(OJ>Dn1@GJ@7T}F4U>P>sb)=d}9vQl3+sMsEQtvsm^zolsYOlKUsgx=A*X#>T zf2$lzv1{!76SONo?-a3{#D0<+SK|+cvRmvCmS$oc?UD#c&?R zaoz+Wkk6dW%d3xL5>LYtL~6jltFtaF70xdL05ox)acp6VG5{N1!WOf63>1SYiN6u` zOq8ghV{)uzh2-KTz(3iFkD%FVZ%UkXJ$fG-7L|Cs4mf{Pm~jXrKn-0em{M^b&U3ih z7B$N%D9(qy@j`|e$9t6?CbR6FMWdqSCZ4NcpC!y+@qRHhzNSdyK@4zq`b;Ftv@OBA zMI%`V9;k+)$)`JBF|{uV&VGvRR;ZGhn(ut}9$*n5%QI&M$Rp+Q@$ymI>coPw5I@7! zlf)7mh0K>mJbeoSr6$MoNocoYmYm~-&{x^b2#)ooJsQP@!c}Bww+ho$L+>(**4xgFs%tRfjfGxfF&q6w1ufg6iQD*)F`YM5ptGIL(;9K?)%cG!Gs*^~4-0CE+w$dVA3EiU3n{t5%9n|MCUP0~BsHK6SiR*RwI3X^j$DXqs zXK~Ehoi3RBq%WCTPK-T_46){@lg-8IBsd@x`j4WB^!sP96IO&QrGEp`IMotD^;L*_ zKW~N2WLiW!cZD;~WrQ&R2F;1(MDOU$v}dt(?T_l2feZ1kcM^^AR+n~3@sBMw4t&uf7v*)mUBM@)lkJx4F6dVWf0RI_V=06HZ zMX5kvd+37?=Kui*OPYq*ZlNXv{aiK|`KlD8?hsvm2Wb5HE|;P40}!8a8i=)vVDLVX z6?{tr^E)!o=tiYWHa#cwv}Ql!nN2Z}(?L)MH60WH+gUUN%PSxjN-OWtULwT`dpzXy zU^5g6Ab+g!%ka+oO>M#E!!9tPXKg3y4RFG!`Y3iFcN)!mzGRex>qzp-Ft5VkypM!@GsR+x|vO<*7LwE{%zPHr9BPa-Q3>u4wwb2%vP8i|um8>r0zhSya z_0h6l(xrlQpLm1FgZ==NyDYY|etj4|NC!s(XHTC-Dl-3R27}92@OB?Ng)Li%JeLd9 z#VFa2gl#T+7rvNy-s#dYd6)+$xAYl?U{d1-9`M@F6f2WEJuJDw#3f%ZU-K=Qs=R$N zW$uY}#O;~b_G#(IzJ+2guHodQ_986rDCF|(L8+O-9{<7Th8lC^GBunlH}1jK>>xAy zGwW!o&J#;^Wy`yX(uD!rGp$2B$QUGqWI62zZIV!T_N9hx*MA~5x?G!9PN+g-JoeSC zeg^${KuWqV1ad%$H5(bvSd(*Ee{jiY`I`C!PyWp58+tY_Le7YGd66X*a)ogL>r2*4 zAs+m9&}s6qYo!W3bXC7|{)?t3<&~2IZSiJX$MUol)#|Lm^Crk@Lt8u&t-`D}@;Pj-0+dGWBvH)3nX{=iq@u}ezcg)r&Lpgqat40PU&QOeertVP`g*lSQ_S=5K_ w;j`u7fG(?^cP;wJK=njB(a;R=kk~xlV0VvoW1lHe^5Ad8urWh-$15}b2WK@#FaQ7m literal 15580 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 XmXUW`GFsuSWx Date: Sat, 17 Jan 2026 20:15:48 +0100 Subject: [PATCH 378/430] Update models_first_person_maze.c --- examples/models/models_first_person_maze.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/models/models_first_person_maze.c b/examples/models/models_first_person_maze.c index 4c77d6121..13b56f4cd 100644 --- a/examples/models/models_first_person_maze.c +++ b/examples/models/models_first_person_maze.c @@ -84,19 +84,19 @@ int main(void) for (int y = playerCellY - 1; y <= playerCellY + 1; y++) { // Avoid map accessing out of bounds - if ((y < 0) || (y >= cubicmap.height)) continue; - - for (int x = playerCellX - 1; x <= playerCellX + 1; x++) + if ((y >= 0) && (y < cubicmap.height)) { - // Avoid map accessing out of bounds - if ((x < 0) || (x >= cubicmap.width)) continue; - - if ((mapPixels[y*cubicmap.width + x].r == 255) && // Collision: white pixel, only check R channel - (CheckCollisionCircleRec(playerPos, playerRadius, - (Rectangle){ mapPosition.x - 0.5f + x*1.0f, mapPosition.z - 0.5f + y*1.0f, 1.0f, 1.0f }))) + for (int x = playerCellX - 1; x <= playerCellX + 1; x++) { - // Collision detected, reset camera position - camera.position = oldCamPos; + // NOTE: Collision: Only checking R channel for white pixel + if (((x >= 0) && (x < cubicmap.width)) && + (mapPixels[y*cubicmap.width + x].r == 255) && + (CheckCollisionCircleRec(playerPos, playerRadius, + (Rectangle){ mapPosition.x - 0.5f + x*1.0f, mapPosition.z - 0.5f + y*1.0f, 1.0f, 1.0f }))) + { + // Collision detected, reset camera position + camera.position = oldCamPos; + } } } } From 29896a24039fb687d6ede44c63a78dd3b5829f8b Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 19 Jan 2026 12:40:32 +0100 Subject: [PATCH 379/430] REVIEWED: Some comments (Code Gardening) --- src/external/rlsw.h | 50 ++++++++++++++-------------- src/platforms/rcore_android.c | 4 +-- src/platforms/rcore_desktop_glfw.c | 28 ++++++++-------- src/platforms/rcore_desktop_sdl.c | 45 ++++++++++++------------- src/platforms/rcore_desktop_win32.c | 32 +++++++++--------- src/platforms/rcore_memory.c | 2 +- src/platforms/rcore_web.c | 8 ++--- src/platforms/rcore_web_emscripten.c | 10 +++--- src/raudio.c | 18 +++++----- src/raylib.h | 2 +- src/raymath.h | 26 +++++++-------- src/rcore.c | 4 +-- src/rlgl.h | 8 ++--- src/rmodels.c | 4 +-- src/rshapes.c | 20 +++++------ 15 files changed, 130 insertions(+), 131 deletions(-) diff --git a/src/external/rlsw.h b/src/external/rlsw.h index 80fb02c4f..aad99f937 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -1177,7 +1177,7 @@ static inline void sw_float_to_unorm8_simd(uint8_t dst[4], const float src[4]) static inline void sw_float_from_unorm8_simd(float dst[4], const uint8_t src[4]) { #if defined(SW_HAS_NEON) - uint8x8_t bytes8 = vld1_u8(src); //< Read 8 bytes, faster, but let's hope we're not at the end of the page (unlikely)... + uint8x8_t bytes8 = vld1_u8(src); // Reading 8 bytes, faster, but let's hope not hitting the end of the page (unlikely)... uint16x8_t bytes16 = vmovl_u8(bytes8); uint32x4_t ints = vmovl_u16(vget_low_u16(bytes16)); float32x4_t floats = vcvtq_f32_u32(ints); @@ -1224,8 +1224,8 @@ static inline uint32_t sw_half_to_float_ui(uint16_t h) // denormal: flush to zero r = (em < (1 << 10))? 0 : r; - // infinity/NaN; note that we preserve NaN payload as a byproduct of unifying inf/nan cases - // 112 is an exponent bias fixup; since we already applied it once, applying it twice converts 31 to 255 + // NOTE: infinity/NaN; NaN payload is preserved as a byproduct of unifying inf/nan cases + // 112 is an exponent bias fixup; since it is already applied once, applying it twice converts 31 to 255 r += (em >= (31 << 10))? (112 << 23) : 0; return s | r; @@ -1252,7 +1252,7 @@ static inline uint16_t sw_half_from_float_ui(uint32_t ui) // Overflow: infinity; 143 encodes exponent 16 h = (em >= (143 << 23))? 0x7c00 : h; - // NaN; note that we convert all types of NaN to qNaN + // NOTE: NaN; all types of NaN aree converted to qNaN h = (em > (255 << 23))? 0x7e00 : h; return (uint16_t)(s | h); @@ -1918,8 +1918,8 @@ static inline void sw_texture_sample_nearest(float *color, const sw_texture_t *t static inline void sw_texture_sample_linear(float *color, const sw_texture_t *tex, float u, float v) { - // TODO: With a bit more cleverness we could clearly reduce the - // number of operations here, but for now it works fine + // TODO: With a bit more cleverness thee number of operations can + // be clearly reduced, but for now it works fine float xf = (u*tex->width) - 0.5f; float yf = (v*tex->height) - 0.5f; @@ -1933,7 +1933,7 @@ static inline void sw_texture_sample_linear(float *color, const sw_texture_t *te int x1 = x0 + 1; int y1 = y0 + 1; - // NOTE: If the textures are POT we could avoid the division for SW_REPEAT + // NOTE: If the textures are POT, avoid the division for SW_REPEAT if (tex->sWrap == SW_CLAMP) { @@ -1974,7 +1974,7 @@ static inline void sw_texture_sample_linear(float *color, const sw_texture_t *te static inline void sw_texture_sample(float *color, const sw_texture_t *tex, float u, float v, float dUdx, float dUdy, float dVdx, float dVdy) { // Previous method: There is no need to compute the square root - // because using the squared value, the comparison remains `L2 > 1.0f*1.0f` + // because using the squared value, the comparison remains (L2 > 1.0f*1.0f) //float du = sqrtf(dUdx*dUdx + dUdy*dUdy); //float dv = sqrtf(dVdx*dVdx + dVdy*dVdy); //float L = (du > dv)? du : dv; @@ -2204,12 +2204,12 @@ static inline bool sw_polygon_clip(sw_vertex_t polygon[SW_MAX_CLIPPED_POLYGON_VE static inline bool sw_triangle_face_culling(void) { // NOTE: Face culling is done before clipping to avoid unnecessary computations - // To handle triangles crossing the w=0 plane correctly, - // we perform the winding order test in homogeneous coordinates directly, - // before the perspective division (division by w) - // This test determines the orientation of the triangle in the (x,y,w) plane, - // which corresponds to the projected 2D winding order sign, - // even with negative w values + // To handle triangles crossing the w=0 plane correctly, + // the winding order test is performeed in homogeneous coordinates directly, + // before the perspective division (division by w) + // This test determines the orientation of the triangle in the (x,y,w) plane, + // which corresponds to the projected 2D winding order sign, + // even with negative w values // Preload homogeneous coordinates into local variables const float *h0 = RLSW.vertexBuffer[0].homogeneous; @@ -2558,13 +2558,13 @@ static inline void sw_triangle_render(void) static inline bool sw_quad_face_culling(void) { // NOTE: Face culling is done before clipping to avoid unnecessary computations - // To handle quads crossing the w=0 plane correctly, - // we perform the winding order test in homogeneous coordinates directly, - // before the perspective division (division by w) - // For a convex quad with vertices P0, P1, P2, P3 in sequential order, - // the winding order of the quad is the same as the winding order - // of the triangle P0 P1 P2. We use the homogeneous triangle - // winding test on this first triangle + // To handle quads crossing the w=0 plane correctly, + // the winding order test is performed in homogeneous coordinates directly, + // before the perspective division (division by w) + // For a convex quad with vertices P0, P1, P2, P3 in sequential order, + // the winding order of the quad is the same as the winding order + // of the triangle P0 P1 P2. The homogeneous triangle is used on + // winding test on this first triangle // Preload homogeneous coordinates into local variables const float *h0 = RLSW.vertexBuffer[0].homogeneous; @@ -2649,7 +2649,7 @@ static inline bool sw_quad_is_axis_aligned(void) { // Reject quads with perspective projection // The fast path assumes affine (non-perspective) quads, - // so we require all vertices to have homogeneous w = 1.0 + // so it's required for all vertices to have homogeneous w = 1.0 for (int i = 0; i < 4; i++) { if (RLSW.vertexBuffer[i].homogeneous[3] != 1.0f) return false; @@ -2721,7 +2721,7 @@ static inline void sw_quad_sort_cw(const sw_vertex_t* *output) // TODO: REVIEW: Could a perfectly aligned quad, where one of the four points has a different depth, // still appear perfectly aligned from a certain point of view? -// Because in that case, we would still need to perform perspective division for textures and colors... +// Because in that case, it's still needed to perform perspective division for textures and colors... #define DEFINE_QUAD_RASTER_AXIS_ALIGNED(FUNC_NAME, ENABLE_TEXTURE, ENABLE_DEPTH_TEST, ENABLE_COLOR_BLEND) \ static inline void FUNC_NAME(void) \ { \ @@ -3090,7 +3090,7 @@ static inline void FUNC_NAME(const sw_vertex_t *v0, const sw_vertex_t *v1) \ \ for (int i = 0; i < numPixels; i++) \ { \ - /* REVIEW: May require reviewing projection details */ \ + /* TODO: REVIEW: May require reviewing projection details */ \ int px = (int)(x - 0.5f); \ int py = (int)(y - 0.5f); \ \ @@ -3721,7 +3721,7 @@ void swBlitFramebuffer(int xDst, int yDst, int wDst, int hDst, int xSrc, int ySr ySrc = sw_clampi(ySrc, 0, hSrc); // Check if the sizes are identical after clamping the source to avoid unexpected issues - // REVIEW: This repeats the operations if true, so we could make a copy function without these checks + // TODO: REVIEW: This repeats the operations if true, so a copy function can be made without these checks if (xDst == xSrc && yDst == ySrc && wDst == wSrc && hDst == hSrc) { swCopyFramebuffer(xSrc, ySrc, wSrc, hSrc, format, type, pixels); diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 6122f9a74..65d1c2ddf 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -1189,7 +1189,7 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) if (FLAG_IS_SET(source, AINPUT_SOURCE_JOYSTICK) || FLAG_IS_SET(source, AINPUT_SOURCE_GAMEPAD)) { - // For now we'll assume a single gamepad which we "detect" on its input event + // Assuming a single gamepad, "detected" on its input event CORE.Input.Gamepad.ready[0] = true; CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_X] = AMotionEvent_getAxisValue( @@ -1256,7 +1256,7 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) FLAG_IS_SET(source, AINPUT_SOURCE_GAMEPAD)) && !FLAG_IS_SET(source, AINPUT_SOURCE_KEYBOARD)) { - // For now we'll assume a single gamepad which we "detect" on its input event + // Assuming a single gamepad, "detected" on its input event CORE.Input.Gamepad.ready[0] = true; GamepadButton button = AndroidTranslateGamepadButton(keycode); diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index f39e256aa..fd9632700 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -602,7 +602,7 @@ void SetWindowIcon(Image image) icon[0].height = image.height; icon[0].pixels = (unsigned char *)image.data; - // NOTE 1: We only support one image icon + // NOTE 1: Only one image icon supported // NOTE 2: The specified image data is copied before this function returns glfwSetWindowIcon(platform.handle, 1, icon); } @@ -833,7 +833,7 @@ int GetCurrentMonitor(void) } else { - // In case the window is between two monitors, we use below logic + // In case the window is between two monitors, below logic is used // to try to detect the "current monitor" for that window, note that // this is probably an overengineered solution for a very side case // trying to match SDL behaviour @@ -1186,7 +1186,7 @@ void SetMouseCursor(int cursor) if (cursor == MOUSE_CURSOR_DEFAULT) glfwSetCursor(platform.handle, NULL); else { - // NOTE: We are relating internal GLFW enum values to our MouseCursor enum values + // NOTE: Mapping internal GLFW enum values to MouseCursor enum values glfwSetCursor(platform.handle, glfwCreateStandardCursor(0x00036000 + cursor)); } } @@ -1247,7 +1247,7 @@ void PollInputEvents(void) CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; // Check if gamepads are ready - // NOTE: We do it here in case of disconnection + // NOTE: Doing it here in case of disconnection for (int i = 0; i < MAX_GAMEPADS; i++) { if (glfwJoystickPresent(i)) CORE.Input.Gamepad.ready[i] = true; @@ -1263,7 +1263,7 @@ void PollInputEvents(void) for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; // Get current gamepad state - // NOTE: There is no callback available, so we get it manually + // NOTE: There is no callback available, getting it manually GLFWgamepadstate state = { 0 }; int result = glfwGetGamepadState(i, &state); // This remaps all gamepads so they have their buttons mapped like an xbox controller if (result == GLFW_FALSE) // No joystick is connected, no gamepad mapping or an error occurred @@ -1359,8 +1359,8 @@ void PollInputEvents(void) //---------------------------------------------------------------------------------- // Module Internal Functions Definition //---------------------------------------------------------------------------------- -// Function wrappers around RL_*alloc macros, used by glfwInitAllocator() inside of InitPlatform() -// We need to provide these because GLFWallocator expects function pointers with specific signatures +// Function wrappers around RL_*ALLOC macros, used by glfwInitAllocator() inside of InitPlatform() +// GLFWallocator expects function pointers with specific signatures to be provided // REF: https://www.glfw.org/docs/latest/intro_guide.html#init_allocator static void *AllocateWrapper(size_t size, void *user) { @@ -1742,7 +1742,7 @@ int InitPlatform(void) for (int i = 0; i < MAX_GAMEPADS; i++) { // WARNING: If glfwGetJoystickName() is longer than MAX_GAMEPAD_NAME_LENGTH, - // we can get a not-NULL terminated string, so, we only copy up to (MAX_GAMEPAD_NAME_LENGTH - 1) + // only copying up to (MAX_GAMEPAD_NAME_LENGTH - 1) if (glfwJoystickPresent(i)) { CORE.Input.Gamepad.ready[i] = true; @@ -1819,8 +1819,8 @@ static void FramebufferSizeCallback(GLFWwindow *window, int width, int height) { //TRACELOG(LOG_INFO, "GLFW3: Window framebuffer size callback called [%i,%i]", width, height); - // WARNING: On window minimization, callback is called, - // but we don't want to change internal screen values, it breaks things + // WARNING: On window minimization, callback is called with 0 values, + // but internal screen values should not be changed, it breaks things if ((width == 0) || (height == 0)) return; // Reset viewport and projection matrix for new size @@ -1926,7 +1926,7 @@ static void WindowDropCallback(GLFWwindow *window, int count, const char **paths { if (count > 0) { - // In case previous dropped filepaths have not been freed, we free them + // In case previous dropped filepaths have not been freed, free them if (CORE.Window.dropFileCount > 0) { for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) RL_FREE(CORE.Window.dropFilepaths[i]); @@ -1937,7 +1937,7 @@ static void WindowDropCallback(GLFWwindow *window, int count, const char **paths CORE.Window.dropFilepaths = NULL; } - // WARNING: Paths are freed by GLFW when the callback returns, we must keep an internal copy + // WARNING: Paths are freed by GLFW when the callback returns, keeping an internal copy CORE.Window.dropFileCount = count; CORE.Window.dropFilepaths = (char **)RL_CALLOC(CORE.Window.dropFileCount, sizeof(char *)); @@ -1954,7 +1954,7 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i { if (key < 0) return; // Security check, macOS fn key generates -1 - // WARNING: GLFW could return GLFW_REPEAT, we need to consider it as 1 + // WARNING: GLFW could return GLFW_REPEAT, it needs to be considered as 1 // to work properly with our implementation (IsKeyDown/IsKeyUp checks) if (action == GLFW_RELEASE) CORE.Input.Keyboard.currentKeyState[key] = 0; else if (action == GLFW_PRESS) CORE.Input.Keyboard.currentKeyState[key] = 1; @@ -2079,7 +2079,7 @@ static void JoystickCallback(int jid, int event) if (event == GLFW_CONNECTED) { // WARNING: If glfwGetJoystickName() is longer than MAX_GAMEPAD_NAME_LENGTH, - // we can get a not-NULL terminated string, so, we clean destination and only copy up to -1 + // only copy up to (MAX_GAMEPAD_NAME_LENGTH -1) to destination string memset(CORE.Input.Gamepad.name[jid], 0, MAX_GAMEPAD_NAME_LENGTH); strncpy(CORE.Input.Gamepad.name[jid], glfwGetJoystickName(jid), MAX_GAMEPAD_NAME_LENGTH - 1); } diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index eabea6bfe..45bb30c65 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -49,7 +49,7 @@ #define USING_SDL3_PROJECT #endif #ifndef SDL_ENABLE_OLD_NAMES - #define SDL_ENABLE_OLD_NAMES // Just in case we're on SDL3, we need some in-between compatibily + #define SDL_ENABLE_OLD_NAMES // Just in case on SDL3, some in-between compatibily is needed #endif // SDL base library (window/rendered, input, timing... functionality) #ifdef USING_SDL3_PROJECT @@ -254,10 +254,10 @@ static const int CursorsLUT[] = { #if defined(USING_VERSION_SDL3) // SDL3 Migration: -// SDL_WINDOW_FULLSCREEN_DESKTOP has been removed, -// and you can call SDL_GetWindowFullscreenMode() -// to see whether an exclusive fullscreen mode will be used -// or the borderless fullscreen desktop mode will be used +// SDL_WINDOW_FULLSCREEN_DESKTOP has been removed, +// and you can call SDL_GetWindowFullscreenMode() +// to see whether an exclusive fullscreen mode will be used +// or the borderless fullscreen desktop mode will be used #define SDL_WINDOW_FULLSCREEN_DESKTOP SDL_WINDOW_FULLSCREEN #define SDL_IGNORE false @@ -340,9 +340,8 @@ SDL_Surface *SDL_CreateRGBSurface(Uint32 flags, int width, int height, int depth } // SDL3 Migration: -// SDL_GetDisplayDPI() - -// not reliable across platforms, approximately replaced by multiplying -// SDL_GetWindowDisplayScale() times 160 on iPhone and Android, and 96 on other platforms +// SDL_GetDisplayDPI() not reliable across platforms, approximately replaced by multiplying +// SDL_GetWindowDisplayScale() times 160 on iPhone and Android, and 96 on other platforms // returns 0 on success or a negative error code on failure int SDL_GetDisplayDPI(int displayIndex, float *ddpi, float *hdpi, float *vdpi) { @@ -413,7 +412,7 @@ int SDL_GetNumTouchFingers(SDL_TouchID touchID) return count; } -#else // We're on SDL2 +#else // SDL2 fallback // Since SDL2 doesn't have this function we leave a stub // SDL_GetClipboardData function is available since SDL 3.1.3. (e.g. SDL3) @@ -833,10 +832,9 @@ void SetWindowMonitor(int monitor) if ((monitor >= 0) && (monitor < monitorCount)) #endif { - // NOTE: - // 1. SDL started supporting moving exclusive fullscreen windows between displays on SDL3, - // see commit https://github.com/libsdl-org/SDL/commit/3f5ef7dd422057edbcf3e736107e34be4b75d9ba - // 2. A workaround for SDL2 is leaving fullscreen, moving the window, then entering full screen again + // NOTE 1: SDL started supporting moving exclusive fullscreen windows between displays on SDL3, + // see commit https://github.com/libsdl-org/SDL/commit/3f5ef7dd422057edbcf3e736107e34be4b75d9ba + // NOTE 2: A workaround for SDL2 is leaving fullscreen, moving the window, then entering full screen again const bool wasFullscreen = (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE))? true : false; const int screenWidth = CORE.Window.screen.width; @@ -854,14 +852,13 @@ void SetWindowMonitor(int monitor) // If the screen size is larger than the monitor usable area, anchor it on the top left corner, otherwise, center it if ((screenWidth >= usableBounds.w) || (screenHeight >= usableBounds.h)) { - // NOTE: - // 1. There's a known issue where if the window larger than the target display bounds, - // when moving the windows to that display, the window could be clipped back - // ending up positioned partly outside the target display - // 2. The workaround for that is, previously to moving the window, - // setting the window size to the target display size, so they match - // 3. It wasn't done here because we can't assume changing the window size automatically - // is acceptable behavior by the user + // NOTE 1: There's a known issue where if the window larger than the target display bounds, + // when moving the windows to that display, the window could be clipped back + // ending up positioned partly outside the target display + // NOTE 2: The workaround for that is, previously to moving the window, + // setting the window size to the target display size, so they match + // NOTE 3: It wasn't done here because we can't assume changing the window size automatically + // is acceptable behavior by the user SDL_SetWindowPosition(platform.window, usableBounds.x, usableBounds.y); CORE.Window.position.x = usableBounds.x; CORE.Window.position.y = usableBounds.y; @@ -1250,7 +1247,7 @@ void DisableCursor(void) void SwapScreenBuffer(void) { #if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) - // NOTE: We use a preprocessor condition here because `rlCopyFramebuffer` is only declared for software rendering + // NOTE: We use a preprocessor condition here because rlCopyFramebuffer() is only declared for software rendering SDL_Surface *surface = SDL_GetWindowSurface(platform.window); rlCopyFramebuffer(0, 0, CORE.Window.render.width, CORE.Window.render.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, surface->pixels); SDL_UpdateWindowSurface(platform.window); @@ -1617,7 +1614,7 @@ void PollInputEvents(void) case SDL_MOUSEBUTTONDOWN: { // NOTE: SDL2 mouse button order is LEFT, MIDDLE, RIGHT, but raylib uses LEFT, RIGHT, MIDDLE like GLFW - // The following conditions align SDL with raylib.h MouseButton enum order + // The following conditions align SDL with raylib.h MouseButton enum order int btn = event.button.button - 1; if (btn == 2) btn = 1; else if (btn == 1) btn = 2; @@ -1630,7 +1627,7 @@ void PollInputEvents(void) case SDL_MOUSEBUTTONUP: { // NOTE: SDL2 mouse button order is LEFT, MIDDLE, RIGHT, but raylib uses LEFT, RIGHT, MIDDLE like GLFW - // The following conditions align SDL with raylib.h MouseButton enum order + // The following conditions align SDL with raylib.h MouseButton enum order int btn = event.button.button - 1; if (btn == 2) btn = 1; else if (btn == 1) btn = 2; diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 9f33dce1b..28094b53f 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -433,7 +433,7 @@ static bool UpdateWindowSize(int mode, HWND hwnd, int width, int height, unsigne return true; } -// Verify if we are running in Windows 10 version 1703 (Creators Update) +// Check if running in Windows 10 version 1703 (Creators Update) static BOOL IsWindows10Version1703OrGreaterWin32(void) { HMODULE ntdll = LoadLibraryW(L"ntdll.dll"); @@ -1138,7 +1138,7 @@ void ShowCursor(void) // Hides mouse cursor void HideCursor(void) { - // NOTE: We use SetCursor() instead of ShowCursor() because + // NOTE: Using SetCursor() instead of ShowCursor() because // it makes it easy to only hide the cursor while it's inside the client area SetCursor(NULL); CORE.Input.Mouse.cursorHidden = true; @@ -1345,7 +1345,7 @@ void PollInputEvents(void) //---------------------------------------------------------------------------------- // Initialize modern OpenGL context -// NOTE: We need to create a dummy context first to query required extensions +// NOTE: Creating a dummy context first to query required extensions HGLRC InitOpenGL(HWND hwnd, HDC hdc) { // First, create a dummy context to get WGL extensions @@ -1460,7 +1460,7 @@ HGLRC InitOpenGL(HWND hwnd, HDC hdc) 0 // Terminator }; - // NOTE: We are not sharing context resources so, second parameters is NULL + // NOTE: Not sharing context resources so, second parameters is NULL realContext = wglCreateContextAttribsARB(hdc, NULL, contextAttribs); // Check for error context creation errors @@ -1476,8 +1476,8 @@ HGLRC InitOpenGL(HWND hwnd, HDC hdc) // Activate real context if (realContext) wglMakeCurrent(hdc, realContext); - // Once we got a real modern OpenGL context, - // we can load required extensions (function pointers) + // Once a real modern OpenGL context is created, + // required extensions can be loaded (function pointers) rlLoadExtensions(WglGetProcAddress); return realContext; @@ -1521,7 +1521,7 @@ int InitPlatform(void) .lpfnWndProc = WndProc, // Custom procedure assigned .cbWndExtra = sizeof(LONG_PTR), // extra space for the Tuple object ptr .hInstance = hInstance, - .hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW), // TODO: Audit if we want to set this since we're implementing WM_SETCURSOR + .hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW), // TODO: Check if this is really required, since WM_SETCURSOR event is processed .lpszClassName = CLASS_NAME // Class name: L"raylibWindow" }; @@ -1854,8 +1854,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara SIZE *inoutSize = (SIZE *)lparam; UINT newDpi = (UINT)wparam; // TODO: WARNING: Converting from WPARAM = UINT_PTR - // for any of these other cases, we might want to post a window - // resize event after the dpi changes? + // For the following flag changes, a window resize event should be posted, + // TODO: Should it be done after dpi changes? if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) return TRUE; if (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) return TRUE; if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) return TRUE; @@ -2025,8 +2025,8 @@ static void HandleRawInput(LPARAM lparam) if (input.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) TRACELOG(LOG_ERROR, "TODO: handle virtual desktop mouse inputs!"); - // Trick to keep the mouse position at 0,0 and instead move - // the previous position so we can still get a proper mouse delta + // Trick to keep the mouse position at (0,0) and instead move + // the previous position so a proper mouse delta can still be retrieved //CORE.Input.Mouse.previousPosition.x -= input.data.mouse.lLastX; //CORE.Input.Mouse.previousPosition.y -= input.data.mouse.lLastY; //if (CORE.Input.Mouse.currentPosition.x != 0) abort(); @@ -2138,12 +2138,12 @@ static unsigned SanitizeFlags(int mode, unsigned flags) // This design takes care of many odd corner cases. For example, if you want to restore // a window that was previously maximized AND minimized and you want to remove both these // flags, you actually need to call ShowWindow with SW_RESTORE twice. Another example is -// if you have a maximized window, if the undecorated flag is modified then we'd need to -// update the window style, but updating the style would mean the window size would change -// causing the window to lose its Maximized state which would mean we'd need to update the -// window size and then update the window style a second time to restore that maximized +// if you have a maximized window, if the undecorated flag is modified then the window style +// needs to be updated, but updating the style would mean the window size would change +// causing the window to lose its Maximized state which would mean the window size +// needs to be updated, followed by the update of window style, a second time, to restore that maximized // state. This implementation is able to handle any/all of these special situations with a -// retry loop that continues until we either reach the desired state or the state stops changing +// retry loop that continues until either the desired state is reached or the state stops changing static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height) { // Flags that just apply immediately without needing any operations diff --git a/src/platforms/rcore_memory.c b/src/platforms/rcore_memory.c index c9409a750..1d69f5ed3 100644 --- a/src/platforms/rcore_memory.c +++ b/src/platforms/rcore_memory.c @@ -499,7 +499,7 @@ int InitPlatform(void) } //---------------------------------------------------------------------------- - // If everything work as expected, we can continue + // If everything worked as expected, continue CORE.Window.render.width = CORE.Window.screen.width; CORE.Window.render.height = CORE.Window.screen.height; CORE.Window.currentFbo.width = CORE.Window.render.width; diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index f1922600f..b0a145f67 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -953,8 +953,8 @@ void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float d if (duration > MAX_GAMEPAD_VIBRATION_TIME) duration = MAX_GAMEPAD_VIBRATION_TIME; duration *= 1000.0f; // Convert duration to ms - // Note: At the moment (2024.10.21) Chrome, Edge, Opera, Safari, Android Chrome, Android Webview only support the vibrationActuator API, - // and Firefox only supports the hapticActuators API + // NOTE: At the moment (2024.10.21) Chrome, Edge, Opera, Safari, Android Chrome, Android Webview only support the vibrationActuator API, + // and Firefox only supports the hapticActuators API EM_ASM({ try { @@ -1798,8 +1798,8 @@ static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent // Emscripten: Called on fullscreen change events static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData) { - // NOTE: 1. Reset the fullscreen flags if the user left fullscreen manually by pressing the Escape key - // 2. Which is a necessary safeguard because that case will bypass the toggles CORE.Window.flags resets + // NOTE 1: Reset the fullscreen flags if the user left fullscreen manually by pressing the Escape key + // NOTE 2: Which is a necessary safeguard because that case will bypass the toggles CORE.Window.flags resets if (platform.ourFullscreen) platform.ourFullscreen = false; else { diff --git a/src/platforms/rcore_web_emscripten.c b/src/platforms/rcore_web_emscripten.c index 36b8e964a..ad26077f4 100644 --- a/src/platforms/rcore_web_emscripten.c +++ b/src/platforms/rcore_web_emscripten.c @@ -137,7 +137,7 @@ bool WindowShouldClose(void) // and encapsulating one frame execution on a UpdateDrawFrame() function, // allowing the browser to manage execution asynchronously - // Optionally we can manage the time we give-control-back-to-browser if required, + // NOTE: Optionally, time can be managed, giving control back-to-browser as required, // but it seems below line could generate stuttering on some browsers emscripten_sleep(12); @@ -1358,7 +1358,7 @@ static void WindowDropCallback(GLFWwindow *window, int count, const char **paths { if (count > 0) { - // In case previous dropped filepaths have not been freed, we free them + // In case previous dropped filepaths have not been freed, free them if (CORE.Window.dropFileCount > 0) { for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) RL_FREE(CORE.Window.dropFilepaths[i]); @@ -1369,7 +1369,7 @@ static void WindowDropCallback(GLFWwindow *window, int count, const char **paths CORE.Window.dropFilepaths = NULL; } - // WARNING: Paths are freed by GLFW when the callback returns, we must keep an internal copy + // WARNING: Paths are freed by GLFW when the callback returns, an internal copy should be kept CORE.Window.dropFileCount = count; CORE.Window.dropFilepaths = (char **)RL_CALLOC(CORE.Window.dropFileCount, sizeof(char *)); @@ -1610,7 +1610,7 @@ static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent double canvasWidth = 0.0; double canvasHeight = 0.0; // NOTE: emscripten_get_canvas_element_size() returns canvas.width and canvas.height but - // we are looking for actual CSS size: canvas.style.width and canvas.style.height + // looking for actual CSS size: canvas.style.width and canvas.style.height // EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight); emscripten_get_element_css_size(platform.canvasId, &canvasWidth, &canvasHeight); @@ -1630,7 +1630,7 @@ static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) CORE.Input.Touch.currentTouchState[i] = 0; } - // Update mouse position if we detect a single touch + // Update mouse position if a single touch is detected if (CORE.Input.Touch.pointCount == 1) { CORE.Input.Mouse.currentPosition.x = CORE.Input.Touch.position[0].x; diff --git a/src/raudio.c b/src/raudio.c index 18f9e0aad..2e087205e 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -2418,7 +2418,7 @@ static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, audioBuffer->frameCursorPos = (audioBuffer->frameCursorPos + framesToRead)%audioBuffer->sizeInFrames; framesRead += framesToRead; - // If we've read to the end of the buffer, mark it as processed + // If the end of the buffer is read, mark it as processed if (framesToRead == framesRemainingInOutputBuffer) { audioBuffer->isSubBufferProcessed[currentSubBufferIndex] = true; @@ -2426,7 +2426,7 @@ static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, currentSubBufferIndex = (currentSubBufferIndex + 1)%2; - // We need to break from this loop if we're not looping + // Break from this loop if looping not enabled if (!audioBuffer->looping) { StopAudioBufferInLockedState(audioBuffer); @@ -2453,10 +2453,12 @@ static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, // Reads audio data from an AudioBuffer object in device format, returned data will be in a format appropriate for mixing static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, float *framesOut, ma_uint32 frameCount) { - // What's going on here is that we're continuously converting data from the AudioBuffer's internal format to the mixing format, which - // should be defined by the output format of the data converter. We do this until frameCount frames have been output. The important - // detail to remember here is that we never, ever attempt to read more input data than is required for the specified number of output - // frames. This can be achieved with ma_data_converter_get_required_input_frame_count() + // NOTE: Continuously converting data from the AudioBuffer's internal format to the mixing format, + // which should be defined by the output format of the data converter. + // This is done until frameCount frames have been output. + // The important detail to remember is that more data than required should neeveer be read, + // for the specified number of output frames. + // This can be achieved with ma_data_converter_get_required_input_frame_count() ma_uint8 inputBuffer[4096] = { 0 }; ma_uint32 inputBufferFrameCap = sizeof(inputBuffer)/ma_get_bytes_per_frame(audioBuffer->converter.formatIn, audioBuffer->converter.channelsIn); @@ -2573,8 +2575,8 @@ static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const } } - // If for some reason we weren't able to read every frame we'll need to break from the loop - // Not doing this could theoretically put us into an infinite loop + // If for some reason is not possible to read every frame, the loop needs to be broken + // Not doing this could theoretically eend up into an infinite loop if (framesToRead > 0) break; } } diff --git a/src/raylib.h b/src/raylib.h index 177138dc9..9a68b80ce 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -336,7 +336,7 @@ typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D typedef struct Camera2D { Vector2 offset; // Camera offset (screen space offset from window origin) Vector2 target; // Camera target (world space target point that is mapped to screen space offset) - float rotation; // Camera rotation in degrees (pivots around target) + float rotation; // Camera rotation in degrees (pivots around target) float zoom; // Camera zoom (scaling around target), must not be set to 0, set to 1.0f for no scale } Camera2D; diff --git a/src/raymath.h b/src/raymath.h index 57e3dac51..7b58d410e 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -66,11 +66,11 @@ // Function specifiers definition #if defined(RAYMATH_IMPLEMENTATION) #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) - #define RMAPI __declspec(dllexport) extern inline // We are building raylib as a Win32 shared library (.dll) + #define RMAPI __declspec(dllexport) extern inline // Building raylib as a Win32 shared library (.dll) #elif defined(BUILD_LIBTYPE_SHARED) - #define RMAPI __attribute__((visibility("default"))) // We are building raylib as a Unix shared library (.so/.dylib) + #define RMAPI __attribute__((visibility("default"))) // Building raylib as a Unix shared library (.so/.dylib) #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) - #define RMAPI __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) + #define RMAPI __declspec(dllimport) // Using raylib as a Win32 shared library (.dll) #else #define RMAPI extern inline // Provide external definition #endif @@ -595,7 +595,7 @@ RMAPI int Vector2Equals(Vector2 p, Vector2 q) // v: normalized direction of the incoming ray // n: normalized normal vector of the interface of two optical media // r: ratio of the refractive index of the medium from where the ray comes -// to the refractive index of the medium on the other side of the surface +// to the refractive index of the medium on the other side of the surface RMAPI Vector2 Vector2Refract(Vector2 v, Vector2 n, float r) { Vector2 result = { 0 }; @@ -1083,7 +1083,7 @@ RMAPI Vector3 Vector3Barycenter(Vector3 p, Vector3 a, Vector3 b, Vector3 c) } // Projects a Vector3 from screen space into object space -// NOTE: We are avoiding calling other raymath functions despite available +// NOTE: Self-contained function, no other raymath functions are called RMAPI Vector3 Vector3Unproject(Vector3 source, Matrix projection, Matrix view) { Vector3 result = { 0 }; @@ -1245,7 +1245,7 @@ RMAPI int Vector3Equals(Vector3 p, Vector3 q) // v: normalized direction of the incoming ray // n: normalized normal vector of the interface of two optical media // r: ratio of the refractive index of the medium from where the ray comes -// to the refractive index of the medium on the other side of the surface +// to the refractive index of the medium on the other side of the surface RMAPI Vector3 Vector3Refract(Vector3 v, Vector3 n, float r) { Vector3 result = { 0 }; @@ -2663,14 +2663,14 @@ RMAPI Matrix MatrixCompose(Vector3 translation, Quaternion rotation, Vector3 sca forward = Vector3RotateByQuaternion(forward, rotation); // Set result matrix output - Matrix result = { - right.x, up.x, forward.x, translation.x, - right.y, up.y, forward.y, translation.y, - right.z, up.z, forward.z, translation.z, - 0.0f, 0.0f, 0.0f, 1.0f - }; + Matrix result = { + right.x, up.x, forward.x, translation.x, + right.y, up.y, forward.y, translation.y, + right.z, up.z, forward.z, translation.z, + 0.0f, 0.0f, 0.0f, 1.0f + }; - return result; + return result; } // Decompose a transformation matrix into its rotational, translational and scaling components and remove shear diff --git a/src/rcore.c b/src/rcore.c index 1794637bd..d07334b28 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -525,7 +525,7 @@ const char *TextFormat(const char *text, ...); // Formatting of text with variab #define PLATFORM_DESKTOP_GLFW #endif -// We're using '#pragma message' because '#warning' is not adopted by MSVC +// Using '#pragma message' because '#warning' is not adopted by MSVC #if defined(SUPPORT_CLIPBOARD_IMAGE) #if !defined(SUPPORT_MODULE_RTEXTURES) #pragma message ("WARNING: Enabling SUPPORT_CLIPBOARD_IMAGE requires SUPPORT_MODULE_RTEXTURES to work properly") @@ -1499,7 +1499,7 @@ Matrix GetCameraMatrix2D(Camera2D camera) // When setting higher scale, it's more intuitive for the world to become bigger (= camera become smaller), // not for the camera getting bigger, hence the invert. Same deal with rotation // 3. Move it by (-offset); - // Offset defines target transform relative to screen, but since we're effectively "moving" screen (camera) + // Offset defines target transform relative to screen, but since effectively "moving" screen (camera) // we need to do it into opposite direction (inverse transform) // Having camera transform in world-space, inverse of it gives the modelview transform diff --git a/src/rlgl.h b/src/rlgl.h index 8b264343a..c604553ee 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -113,11 +113,11 @@ // NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll // NOTE: visibility(default) attribute makes symbols "visible" when compiled with -fvisibility=hidden #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) + #define RLAPI __declspec(dllexport) // Building the library as a Win32 shared library (.dll) #elif defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __attribute__((visibility("default"))) // We are building the library as a Unix shared library (.so/.dylib) + #define RLAPI __attribute__((visibility("default"))) // Building the library as a Unix shared library (.so/.dylib) #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) - #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) + #define RLAPI __declspec(dllimport) // Using the library as a Win32 shared library (.dll) #endif // Function specifiers definition @@ -3731,7 +3731,7 @@ void *rlReadTexturePixels(unsigned int id, int width, int height, int format) // Two possible Options: // 1 - Bind texture to color fbo attachment and glReadPixels() // 2 - Create an fbo, activate it, render quad with texture, glReadPixels() - // We are using Option 1, just need to care for texture format on retrieval + // Using Option 1, just need to care for texture format on retrieval // NOTE: This behaviour could be conditioned by graphic driver... unsigned int fboId = rlLoadFramebuffer(); diff --git a/src/rmodels.c b/src/rmodels.c index 58b08350d..26fc7bcf8 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -1754,7 +1754,7 @@ void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, i // This could alternatively use a static VBO and either glMapBuffer() or glBufferSubData() // It isn't clear which would be reliably faster in all cases and on all platforms, // anecdotally glMapBuffer() seems very slow (syncs) while glBufferSubData() seems - // no faster, since we're transferring all the transform matrices anyway + // no faster, since all the transform matrices are transferred anyway instancesVboId = rlLoadVertexBuffer(instanceTransforms, instances*sizeof(float16), false); // Instances transformation matrices are sent to shader attribute location: SHADER_LOC_VERTEX_INSTANCE_TX @@ -4084,7 +4084,7 @@ RayCollision GetRayCollisionBox(Ray ray, BoundingBox box) { RayCollision collision = { 0 }; - // Note: If ray.position is inside the box, the distance is negative (as if the ray was reversed) + // NOTE: If ray.position is inside the box, the distance is negative (as if the ray was reversed) // Reversing ray.direction will give use the correct result bool insideBox = (ray.position.x > box.min.x) && (ray.position.x < box.max.x) && (ray.position.y > box.min.y) && (ray.position.y < box.max.y) && diff --git a/src/rshapes.c b/src/rshapes.c index 3f686f21a..7487e6296 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -59,9 +59,9 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -// Error rate to calculate how many segments we need to draw a smooth circle, -// taken from https://stackoverflow.com/a/2244088 #ifndef SMOOTH_CIRCLE_ERROR_RATE + // Define error rate to calculate how many segments are needed to draw a smooth circle + // REF: https://stackoverflow.com/a/2244088 #define SMOOTH_CIRCLE_ERROR_RATE 0.5f // Circle error rate #endif #ifndef SPLINE_SEGMENT_DIVISIONS @@ -318,7 +318,7 @@ void DrawCircle(int centerX, int centerY, float radius, Color color) } // Draw a color-filled circle (Vector version) -// NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues +// NOTE: On OpenGL 3.3 and ES2 using QUADS to avoid drawing order issues void DrawCircleV(Vector2 center, float radius, Color color) { DrawCircleSector(center, radius, 0, 360, 36, color); @@ -379,7 +379,7 @@ void DrawCircleSector(Vector2 center, float radius, float startAngle, float endA angle += (stepLength*2.0f); } - // NOTE: In case number of segments is odd, we add one last piece to the cake + // NOTE: In case number of segments is odd, adding one last piece to the cake if ((((unsigned int)segments)%2) == 1) { rlColor4ub(color.r, color.g, color.b, color.a); @@ -722,7 +722,7 @@ void DrawRectangle(int posX, int posY, int width, int height, Color color) } // Draw a color-filled rectangle (Vector version) -// NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues +// NOTE: On OpenGL 3.3 and ES2 using QUADS to avoid drawing order issues void DrawRectangleV(Vector2 position, Vector2 size, Color color) { DrawRectanglePro((Rectangle){ position.x, position.y, size.x, size.y }, (Vector2){ 0.0f, 0.0f }, 0.0f, color); @@ -968,7 +968,7 @@ void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color co /* Quick sketch to make sense of all of this, - there are 9 parts to draw, also mark the 12 points we'll use + there are 9 parts to draw, also mark the 12 points used P0____________________P1 /| |\ @@ -1024,7 +1024,7 @@ void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color co angle += (stepLength*2); } - // NOTE: In case number of segments is odd, we add one last piece to the cake + // NOTE: In case number of segments is odd, adding one last piece to the cake if (segments%2) { rlColor4ub(color.r, color.g, color.b, color.a); @@ -1168,7 +1168,7 @@ void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color co // Draw rectangle with rounded edges void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, Color color) { - // NOTE: For line thicknes <=1.0f we use RL_LINES, otherwise wee use RL_QUADS/RL_TRIANGLES + // NOTE: For line thicknes <=1.0f using RL_LINES, otherwise using RL_QUADS/RL_TRIANGLES DrawRectangleRoundedLinesEx(rec, roundness, segments, 1.0f, color); } @@ -1204,7 +1204,7 @@ void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness, int segments, f /* Quick sketch to make sense of all of this, - marks the 16 + 4(corner centers P16-19) points we'll use + marks the 16 + 4(corner centers P16-19) points used P0 ================== P1 // P8 P9 \\ @@ -1946,7 +1946,7 @@ void DrawSplineBezierCubic(const Vector2 *points, int pointCount, float thick, C // Draw spline segment: Linear, 2 points void DrawSplineSegmentLinear(Vector2 p1, Vector2 p2, float thick, Color color) { - // NOTE: For the linear spline we don't use subdivisions, just a single quad + // NOTE: For the linear spline no subdivisions are used, just a single quad Vector2 delta = { p2.x - p1.x, p2.y - p1.y }; float length = sqrtf(delta.x*delta.x + delta.y*delta.y); From c610d228a244f930ad53492604640f39584c66da Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 20 Jan 2026 21:01:41 +0100 Subject: [PATCH 380/430] Update text_inline_styling.c --- examples/text/text_inline_styling.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/text/text_inline_styling.c b/examples/text/text_inline_styling.c index 81f8156b6..8634ccc19 100644 --- a/examples/text/text_inline_styling.c +++ b/examples/text/text_inline_styling.c @@ -180,12 +180,12 @@ static void DrawTextStyled(Font font, const char *text, Vector2 position, float if (text[i - 1] == 'c') { colFront = GetColor(colHexValue); - colFront.a = (unsigned char)(colFront.a * (float)color.a/255.0f); + //colFront.a *= (unsigned char)(colFront.a*(float)color.a/255.0f); // TODO: Review } else if (text[i - 1] == 'b') { colBack = GetColor(colHexValue); - colBack.a *= (unsigned char)(colFront.a * (float)color.a / 255.0f); + //colBack.a *= (unsigned char)(colFront.a*(float)color.a/255.0f); } i += (colHexCount + 1); // Skip color value retrieved and ']' From 594f5429b2a5b5551f810340927321d0ee6c8a7c Mon Sep 17 00:00:00 2001 From: Catania <79325830+katanya04@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:48:26 +0100 Subject: [PATCH 381/430] [rcore] `LoadDirectoryFilesEx()`, count files if not recursive (#5496) * LoadDirectoryFilesEx on not recursive loading count files * Removed FilePathList.capacity * Added security check in case of memory leak * Fix stop loading paths early on recursive loading * Fix count directories only if filter contains DIRECTORY_FILTER_TAG * rlparser: update raylib_api.* by CI * GetDirectoryFileCount() and GetDirectoryFileCountEx() made visible * rlparser: update raylib_api.* by CI * Added new file and directories filter tags * Renamed `fileCount` in `ScanDirectoryFiles()` to `expectedFileCount` --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/raylib.h | 3 +- src/rcore.c | 223 +++---- tools/rlparser/output/raylib_api.json | 35 +- tools/rlparser/output/raylib_api.lua | 23 +- tools/rlparser/output/raylib_api.txt | 913 +++++++++++++------------- tools/rlparser/output/raylib_api.xml | 13 +- 6 files changed, 615 insertions(+), 595 deletions(-) diff --git a/src/raylib.h b/src/raylib.h index 9a68b80ce..8a0a14dad 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -512,7 +512,6 @@ typedef struct VrStereoConfig { // File path list typedef struct FilePathList { - unsigned int capacity; // Filepaths max entries unsigned int count; // Filepaths entries count char **paths; // Filepaths entries } FilePathList; @@ -1152,6 +1151,8 @@ RLAPI void UnloadDirectoryFiles(FilePathList files); // Unload fi RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window RLAPI FilePathList LoadDroppedFiles(void); // Load dropped filepaths RLAPI void UnloadDroppedFiles(FilePathList files); // Unload dropped filepaths +RLAPI unsigned int GetDirectoryFileCount(const char *dirPath); // Get the file count in a directory +RLAPI unsigned int GetDirectoryFileCountEx(const char *basePath, const char *filter, bool scanSubdirs);// Get the file count in a directory with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result // Compression/Encoding functionality RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize); // Compress data (DEFLATE algorithm), memory must be MemFree() diff --git a/src/rcore.c b/src/rcore.c index d07334b28..aabf85022 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -267,9 +267,15 @@ #define MAX_AUTOMATION_EVENTS 16384 // Maximum number of automation events to record #endif -#ifndef DIRECTORY_FILTER_TAG - #define DIRECTORY_FILTER_TAG "DIR" // Name tag used to request directory inclusion on directory scan -#endif // NOTE: Used in ScanDirectoryFiles(), ScanDirectoryFilesRecursively() and LoadDirectoryFilesEx() +#ifndef FILE_FILTER_TAG_ALL + #define FILE_FILTER_TAG_ALL "*.*" // Filter to include all file types and directories on directory scan +#endif // NOTE: Used in ScanDirectoryFiles(), LoadDirectoryFilesEx() and GetDirectoryFileCountEx() +#ifndef FILE_FILTER_TAG_FILE_ONLY + #define FILE_FILTER_TAG_FILE_ONLY "FILES*" // Filter to include all file types on directory scan +#endif // NOTE: Used in ScanDirectoryFiles(), LoadDirectoryFilesEx() and GetDirectoryFileCountEx() +#ifndef FILE_FILTER_TAG_DIR_ONLY + #define FILE_FILTER_TAG_DIR_ONLY "DIR*" // Filter to include directories on directory scan +#endif // NOTE: Used in ScanDirectoryFiles(), LoadDirectoryFilesEx() and GetDirectoryFileCountEx() // Flags operation macros #define FLAG_SET(n, f) ((n) |= (f)) @@ -505,8 +511,7 @@ extern void ClosePlatform(void); // Close platform static void InitTimer(void); // Initialize timer, hi-resolution if available (required by InitPlatform()) static void SetupViewport(int width, int height); // Set viewport for a provided width and height -static void ScanDirectoryFiles(const char *basePath, FilePathList *list, const char *filter); // Scan all files and directories in a base path -static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *list, const char *filter); // Scan all files and directories recursively from a base path +static void ScanDirectoryFiles(const char *basePath, FilePathList *list, const char *filter, unsigned int expectedFileCount, bool scanSubdirs); // Scan all files and directories in a base path #if defined(SUPPORT_AUTOMATION_EVENTS) static void RecordAutomationEvent(void); // Record frame events (to internal events array) @@ -2753,53 +2758,36 @@ const char *GetApplicationDirectory(void) // No recursive scanning is done! FilePathList LoadDirectoryFiles(const char *dirPath) { - FilePathList files = { 0 }; - unsigned int fileCounter = 0; - - struct dirent *entity; - DIR *dir = opendir(dirPath); - - if (dir != NULL) // It's a directory - { - // SCAN 1: Count files - while ((entity = readdir(dir)) != NULL) - { - // NOTE: We skip '.' (current dir) and '..' (parent dir) filepaths - if ((strcmp(entity->d_name, ".") != 0) && (strcmp(entity->d_name, "..") != 0)) fileCounter++; - } - - // Memory allocation for dirFileCount - files.capacity = fileCounter; - files.paths = (char **)RL_CALLOC(files.capacity, sizeof(char *)); - for (unsigned int i = 0; i < files.capacity; i++) files.paths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); - - closedir(dir); - - // SCAN 2: Read filepaths - // NOTE: Directory paths are also registered - ScanDirectoryFiles(dirPath, &files, NULL); - - // Security check: read files.count should match fileCounter - if (files.count != files.capacity) TRACELOG(LOG_WARNING, "FILEIO: Read files count do not match capacity allocated"); - } - else TRACELOG(LOG_WARNING, "FILEIO: Failed to open requested directory"); // Maybe it's a file... - - return files; + return LoadDirectoryFilesEx(dirPath, FILE_FILTER_TAG_ALL, false); } // Load directory filepaths with extension filtering and recursive directory scan -// NOTE: On recursive loading we do not pre-scan for file count, we use MAX_FILEPATH_CAPACITY +// WARNING: Directory is scanned twice, first time to get files count FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs) { FilePathList files = { 0 }; - files.capacity = MAX_FILEPATH_CAPACITY; - files.paths = (char **)RL_CALLOC(files.capacity, sizeof(char *)); - for (unsigned int i = 0; i < files.capacity; i++) files.paths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); + if (DirectoryExists(basePath)) // It's a directory + { + // SCAN 1: Count files + unsigned int fileCounter = GetDirectoryFileCountEx(basePath, filter, scanSubdirs); + + // Memory allocation for dirFileCount + files.paths = (char **)RL_CALLOC(fileCounter, sizeof(char *)); + for (unsigned int i = 0; i < fileCounter; i++) files.paths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); - // WARNING: basePath is always prepended to scanned paths - if (scanSubdirs) ScanDirectoryFilesRecursively(basePath, &files, filter); - else ScanDirectoryFiles(basePath, &files, filter); + // SCAN 2: Read filepaths + // WARNING: basePath is always prepended to scanned paths + ScanDirectoryFiles(basePath, &files, filter, fileCounter, scanSubdirs); + + // Security check: read files.count should match fileCounter + if (files.count != fileCounter) + { + TRACELOG(LOG_WARNING, "FILEIO: Read files count (%u) does not match capacity allocated (%u)", files.count, fileCounter); + files.count = fileCounter; // Avoid memory leak when unloading this FilePathList + } + } + else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); // Maybe it's a file... return files; } @@ -2810,7 +2798,7 @@ void UnloadDirectoryFiles(FilePathList files) { if (files.paths != NULL) { - for (unsigned int i = 0; i < files.capacity; i++) RL_FREE(files.paths[i]); + for (unsigned int i = 0; i < files.count; i++) RL_FREE(files.paths[i]); RL_FREE(files.paths); } @@ -2966,6 +2954,59 @@ void UnloadDroppedFiles(FilePathList files) } } +// Get the file count in a directory +unsigned int GetDirectoryFileCount(const char *dirPath) +{ + return GetDirectoryFileCountEx(dirPath, FILE_FILTER_TAG_ALL, false); +} + +// Get the file count in a directory with extension filtering and recursive directory scan. Use 'FILE_FILTER_TAG_DIR_ONLY' in the filter string to include directories in the result +unsigned int GetDirectoryFileCountEx(const char *basePath, const char *filter, bool scanSubdirs) +{ + unsigned int fileCounter = 0; + + // WARNING: Path can not be static or it will be reused between recursive function calls! + char path[MAX_FILEPATH_LENGTH] = { 0 }; + memset(path, 0, MAX_FILEPATH_LENGTH); + + struct dirent *entity; + DIR *dir = opendir(basePath); + + if (dir != NULL) // It's a directory + { + while ((entity = readdir(dir)) != NULL) + { + // NOTE: We skip '.' (current dir) and '..' (parent dir) filepaths + if ((strcmp(entity->d_name, ".") != 0) && (strcmp(entity->d_name, "..") != 0)) + { + // Construct new path from our base path + #if defined(_WIN32) + int pathLength = snprintf(path, MAX_FILEPATH_LENGTH - 1, "%s\\%s", basePath, entity->d_name); + #else + int pathLength = snprintf(path, MAX_FILEPATH_LENGTH - 1, "%s/%s", basePath, entity->d_name); + #endif + // Don't add to count if path too long + if ((pathLength < 0) || (pathLength >= MAX_FILEPATH_LENGTH)) + { + TRACELOG(LOG_WARNING, "FILEIO: Path longer than %d characters (%s...)", MAX_FILEPATH_LENGTH, basePath); + } + else if (IsPathFile(path)) + { + if ((filter == NULL) || (strstr(filter, FILE_FILTER_TAG_ALL) != NULL) || + (strstr(filter, FILE_FILTER_TAG_FILE_ONLY) != NULL) || IsFileExtension(path, filter)) fileCounter++; + } + else + { + if ((filter != NULL) && ((strstr(filter, FILE_FILTER_TAG_ALL) != NULL) || (strstr(filter, FILE_FILTER_TAG_DIR_ONLY) != NULL))) fileCounter++; + if (scanSubdirs) fileCounter += GetDirectoryFileCountEx(path, filter, scanSubdirs); + } + } + } + } + else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); // Maybe it's a file... + return fileCounter; +} + //---------------------------------------------------------------------------------- // Module Functions Definition: Compression and Encoding //---------------------------------------------------------------------------------- @@ -4229,66 +4270,7 @@ void SetupViewport(int width, int height) // Scan all files and directories in a base path // WARNING: files.paths[] must be previously allocated and // contain enough space to store all required paths -static void ScanDirectoryFiles(const char *basePath, FilePathList *files, const char *filter) -{ - static char path[MAX_FILEPATH_LENGTH] = { 0 }; - memset(path, 0, MAX_FILEPATH_LENGTH); - - struct dirent *dp = NULL; - DIR *dir = opendir(basePath); - - if (dir != NULL) - { - while ((dp = readdir(dir)) != NULL) - { - if ((strcmp(dp->d_name, ".") != 0) && - (strcmp(dp->d_name, "..") != 0)) - { - // Construct new path from our base path - #if defined(_WIN32) - int pathLength = snprintf(path, MAX_FILEPATH_LENGTH - 1, "%s\\%s", basePath, dp->d_name); - #else - int pathLength = snprintf(path, MAX_FILEPATH_LENGTH - 1, "%s/%s", basePath, dp->d_name); - #endif - - if ((pathLength < 0) || (pathLength >= MAX_FILEPATH_LENGTH)) - { - TRACELOG(LOG_WARNING, "FILEIO: Path longer than %d characters (%s...)", MAX_FILEPATH_LENGTH, basePath); - } - else if (filter != NULL) - { - if (IsPathFile(path)) - { - if (IsFileExtension(path, filter)) - { - strncpy(files->paths[files->count], path, MAX_FILEPATH_LENGTH - 1); - files->count++; - } - } - else - { - if (strstr(filter, DIRECTORY_FILTER_TAG) != NULL) - { - strncpy(files->paths[files->count], path, MAX_FILEPATH_LENGTH - 1); - files->count++; - } - } - } - else - { - strncpy(files->paths[files->count], path, MAX_FILEPATH_LENGTH - 1); - files->count++; - } - } - } - - closedir(dir); - } - else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); -} - -// Scan all files and directories recursively from a base path -static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *files, const char *filter) +static void ScanDirectoryFiles(const char *basePath, FilePathList *files, const char *filter, unsigned int expectedFileCount, bool scanSubdirs) { // WARNING: Path can not be static or it will be reused between recursive function calls! char path[MAX_FILEPATH_LENGTH] = { 0 }; @@ -4299,7 +4281,7 @@ static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *fi if (dir != NULL) { - while (((dp = readdir(dir)) != NULL) && (files->count < files->capacity)) + while (((dp = readdir(dir)) != NULL) && (files->count < expectedFileCount)) { if ((strcmp(dp->d_name, ".") != 0) && (strcmp(dp->d_name, "..") != 0)) { @@ -4316,48 +4298,29 @@ static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *fi } else if (IsPathFile(path)) { - if (filter != NULL) - { - if (IsFileExtension(path, filter)) - { - strncpy(files->paths[files->count], path, MAX_FILEPATH_LENGTH - 1); - files->count++; - } - } - else + if ((filter == NULL) || (strstr(filter, FILE_FILTER_TAG_ALL) != NULL) || + (strstr(filter, FILE_FILTER_TAG_FILE_ONLY) != NULL) || IsFileExtension(path, filter)) { strncpy(files->paths[files->count], path, MAX_FILEPATH_LENGTH - 1); files->count++; } - - if (files->count >= files->capacity) - { - TRACELOG(LOG_WARNING, "FILEIO: Maximum filepath scan capacity reached (%i files)", files->capacity); - break; - } } else { - if ((filter != NULL) && (strstr(filter, DIRECTORY_FILTER_TAG) != NULL)) + if ((filter != NULL) && ((strstr(filter, FILE_FILTER_TAG_DIR_ONLY) != NULL) || (strstr(filter, FILE_FILTER_TAG_ALL) != NULL))) { strncpy(files->paths[files->count], path, MAX_FILEPATH_LENGTH - 1); files->count++; } - if (files->count >= files->capacity) - { - TRACELOG(LOG_WARNING, "FILEIO: Maximum filepath scan capacity reached (%i files)", files->capacity); - break; - } - - ScanDirectoryFilesRecursively(path, files, filter); + if (scanSubdirs) ScanDirectoryFiles(path, files, filter, expectedFileCount, scanSubdirs); } } } closedir(dir); } - else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); + else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); // Maybe it's a file... } #if defined(SUPPORT_AUTOMATION_EVENTS) diff --git a/tools/rlparser/output/raylib_api.json b/tools/rlparser/output/raylib_api.json index 185516563..d4c059c50 100644 --- a/tools/rlparser/output/raylib_api.json +++ b/tools/rlparser/output/raylib_api.json @@ -1324,11 +1324,6 @@ "name": "FilePathList", "description": "File path list", "fields": [ - { - "type": "unsigned int", - "name": "capacity", - "description": "Filepaths max entries" - }, { "type": "unsigned int", "name": "count", @@ -4734,6 +4729,36 @@ } ] }, + { + "name": "GetDirectoryFileCount", + "description": "Get the file count in a directory", + "returnType": "unsigned int", + "params": [ + { + "type": "const char *", + "name": "dirPath" + } + ] + }, + { + "name": "GetDirectoryFileCountEx", + "description": "Get the file count in a directory with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result", + "returnType": "unsigned int", + "params": [ + { + "type": "const char *", + "name": "basePath" + }, + { + "type": "const char *", + "name": "filter" + }, + { + "type": "bool", + "name": "scanSubdirs" + } + ] + }, { "name": "CompressData", "description": "Compress data (DEFLATE algorithm), memory must be MemFree()", diff --git a/tools/rlparser/output/raylib_api.lua b/tools/rlparser/output/raylib_api.lua index f2836e1ff..2de69f69c 100644 --- a/tools/rlparser/output/raylib_api.lua +++ b/tools/rlparser/output/raylib_api.lua @@ -1324,11 +1324,6 @@ return { name = "FilePathList", description = "File path list", fields = { - { - type = "unsigned int", - name = "capacity", - description = "Filepaths max entries" - }, { type = "unsigned int", name = "count", @@ -4230,6 +4225,24 @@ return { {type = "FilePathList", name = "files"} } }, + { + name = "GetDirectoryFileCount", + description = "Get the file count in a directory", + returnType = "unsigned int", + params = { + {type = "const char *", name = "dirPath"} + } + }, + { + name = "GetDirectoryFileCountEx", + description = "Get the file count in a directory with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result", + returnType = "unsigned int", + params = { + {type = "const char *", name = "basePath"}, + {type = "const char *", name = "filter"}, + {type = "bool", name = "scanSubdirs"} + } + }, { name = "CompressData", description = "Compress data (DEFLATE algorithm), memory must be MemFree()", diff --git a/tools/rlparser/output/raylib_api.txt b/tools/rlparser/output/raylib_api.txt index 0676b8138..53dbf8813 100644 --- a/tools/rlparser/output/raylib_api.txt +++ b/tools/rlparser/output/raylib_api.txt @@ -540,12 +540,11 @@ Struct 31: VrStereoConfig (8 fields) Field[6]: float[2] rightScreenCenter // VR right screen center Field[7]: float[2] scale // VR distortion scale Field[8]: float[2] scaleIn // VR distortion scale in -Struct 32: FilePathList (3 fields) +Struct 32: FilePathList (2 fields) Name: FilePathList Description: File path list - Field[1]: unsigned int capacity // Filepaths max entries - Field[2]: unsigned int count // Filepaths entries count - Field[3]: char ** paths // Filepaths entries + Field[1]: unsigned int count // Filepaths entries count + Field[2]: char ** paths // Filepaths entries Struct 33: AutomationEvent (3 fields) Name: AutomationEvent Description: Automation event @@ -993,7 +992,7 @@ Callback 006: AudioCallback() (2 input parameters) Param[1]: bufferData (type: void *) Param[2]: frames (type: unsigned int) -Functions found: 597 +Functions found: 599 Function 001: InitWindow() (3 input parameters) Name: InitWindow @@ -1806,199 +1805,211 @@ Function 151: UnloadDroppedFiles() (1 input parameters) Return type: void Description: Unload dropped filepaths Param[1]: files (type: FilePathList) -Function 152: CompressData() (3 input parameters) +Function 152: GetDirectoryFileCount() (1 input parameters) + Name: GetDirectoryFileCount + Return type: unsigned int + Description: Get the file count in a directory + Param[1]: dirPath (type: const char *) +Function 153: GetDirectoryFileCountEx() (3 input parameters) + Name: GetDirectoryFileCountEx + Return type: unsigned int + Description: Get the file count in a directory with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result + Param[1]: basePath (type: const char *) + Param[2]: filter (type: const char *) + Param[3]: scanSubdirs (type: bool) +Function 154: CompressData() (3 input parameters) Name: CompressData Return type: unsigned char * Description: Compress data (DEFLATE algorithm), memory must be MemFree() Param[1]: data (type: const unsigned char *) Param[2]: dataSize (type: int) Param[3]: compDataSize (type: int *) -Function 153: DecompressData() (3 input parameters) +Function 155: DecompressData() (3 input parameters) Name: DecompressData Return type: unsigned char * Description: Decompress data (DEFLATE algorithm), memory must be MemFree() Param[1]: compData (type: const unsigned char *) Param[2]: compDataSize (type: int) Param[3]: dataSize (type: int *) -Function 154: EncodeDataBase64() (3 input parameters) +Function 156: EncodeDataBase64() (3 input parameters) Name: EncodeDataBase64 Return type: char * Description: Encode data to Base64 string (includes NULL terminator), memory must be MemFree() Param[1]: data (type: const unsigned char *) Param[2]: dataSize (type: int) Param[3]: outputSize (type: int *) -Function 155: DecodeDataBase64() (2 input parameters) +Function 157: DecodeDataBase64() (2 input parameters) Name: DecodeDataBase64 Return type: unsigned char * Description: Decode Base64 string (expected NULL terminated), memory must be MemFree() Param[1]: text (type: const char *) Param[2]: outputSize (type: int *) -Function 156: ComputeCRC32() (2 input parameters) +Function 158: ComputeCRC32() (2 input parameters) Name: ComputeCRC32 Return type: unsigned int Description: Compute CRC32 hash code Param[1]: data (type: unsigned char *) Param[2]: dataSize (type: int) -Function 157: ComputeMD5() (2 input parameters) +Function 159: ComputeMD5() (2 input parameters) Name: ComputeMD5 Return type: unsigned int * Description: Compute MD5 hash code, returns static int[4] (16 bytes) Param[1]: data (type: unsigned char *) Param[2]: dataSize (type: int) -Function 158: ComputeSHA1() (2 input parameters) +Function 160: ComputeSHA1() (2 input parameters) Name: ComputeSHA1 Return type: unsigned int * Description: Compute SHA1 hash code, returns static int[5] (20 bytes) Param[1]: data (type: unsigned char *) Param[2]: dataSize (type: int) -Function 159: ComputeSHA256() (2 input parameters) +Function 161: ComputeSHA256() (2 input parameters) Name: ComputeSHA256 Return type: unsigned int * Description: Compute SHA256 hash code, returns static int[8] (32 bytes) Param[1]: data (type: unsigned char *) Param[2]: dataSize (type: int) -Function 160: LoadAutomationEventList() (1 input parameters) +Function 162: LoadAutomationEventList() (1 input parameters) Name: LoadAutomationEventList Return type: AutomationEventList Description: Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS Param[1]: fileName (type: const char *) -Function 161: UnloadAutomationEventList() (1 input parameters) +Function 163: UnloadAutomationEventList() (1 input parameters) Name: UnloadAutomationEventList Return type: void Description: Unload automation events list from file Param[1]: list (type: AutomationEventList) -Function 162: ExportAutomationEventList() (2 input parameters) +Function 164: ExportAutomationEventList() (2 input parameters) Name: ExportAutomationEventList Return type: bool Description: Export automation events list as text file Param[1]: list (type: AutomationEventList) Param[2]: fileName (type: const char *) -Function 163: SetAutomationEventList() (1 input parameters) +Function 165: SetAutomationEventList() (1 input parameters) Name: SetAutomationEventList Return type: void Description: Set automation event list to record to Param[1]: list (type: AutomationEventList *) -Function 164: SetAutomationEventBaseFrame() (1 input parameters) +Function 166: SetAutomationEventBaseFrame() (1 input parameters) Name: SetAutomationEventBaseFrame Return type: void Description: Set automation event internal base frame to start recording Param[1]: frame (type: int) -Function 165: StartAutomationEventRecording() (0 input parameters) +Function 167: StartAutomationEventRecording() (0 input parameters) Name: StartAutomationEventRecording Return type: void Description: Start recording automation events (AutomationEventList must be set) No input parameters -Function 166: StopAutomationEventRecording() (0 input parameters) +Function 168: StopAutomationEventRecording() (0 input parameters) Name: StopAutomationEventRecording Return type: void Description: Stop recording automation events No input parameters -Function 167: PlayAutomationEvent() (1 input parameters) +Function 169: PlayAutomationEvent() (1 input parameters) Name: PlayAutomationEvent Return type: void Description: Play a recorded automation event Param[1]: event (type: AutomationEvent) -Function 168: IsKeyPressed() (1 input parameters) +Function 170: IsKeyPressed() (1 input parameters) Name: IsKeyPressed Return type: bool Description: Check if a key has been pressed once Param[1]: key (type: int) -Function 169: IsKeyPressedRepeat() (1 input parameters) +Function 171: IsKeyPressedRepeat() (1 input parameters) Name: IsKeyPressedRepeat Return type: bool Description: Check if a key has been pressed again Param[1]: key (type: int) -Function 170: IsKeyDown() (1 input parameters) +Function 172: IsKeyDown() (1 input parameters) Name: IsKeyDown Return type: bool Description: Check if a key is being pressed Param[1]: key (type: int) -Function 171: IsKeyReleased() (1 input parameters) +Function 173: IsKeyReleased() (1 input parameters) Name: IsKeyReleased Return type: bool Description: Check if a key has been released once Param[1]: key (type: int) -Function 172: IsKeyUp() (1 input parameters) +Function 174: IsKeyUp() (1 input parameters) Name: IsKeyUp Return type: bool Description: Check if a key is NOT being pressed Param[1]: key (type: int) -Function 173: GetKeyPressed() (0 input parameters) +Function 175: GetKeyPressed() (0 input parameters) Name: GetKeyPressed Return type: int Description: Get key pressed (keycode), call it multiple times for keys queued, returns 0 when the queue is empty No input parameters -Function 174: GetCharPressed() (0 input parameters) +Function 176: GetCharPressed() (0 input parameters) Name: GetCharPressed Return type: int Description: Get char pressed (unicode), call it multiple times for chars queued, returns 0 when the queue is empty No input parameters -Function 175: GetKeyName() (1 input parameters) +Function 177: GetKeyName() (1 input parameters) Name: GetKeyName Return type: const char * Description: Get name of a QWERTY key on the current keyboard layout (eg returns string 'q' for KEY_A on an AZERTY keyboard) Param[1]: key (type: int) -Function 176: SetExitKey() (1 input parameters) +Function 178: SetExitKey() (1 input parameters) Name: SetExitKey Return type: void Description: Set a custom key to exit program (default is ESC) Param[1]: key (type: int) -Function 177: IsGamepadAvailable() (1 input parameters) +Function 179: IsGamepadAvailable() (1 input parameters) Name: IsGamepadAvailable Return type: bool Description: Check if a gamepad is available Param[1]: gamepad (type: int) -Function 178: GetGamepadName() (1 input parameters) +Function 180: GetGamepadName() (1 input parameters) Name: GetGamepadName Return type: const char * Description: Get gamepad internal name id Param[1]: gamepad (type: int) -Function 179: IsGamepadButtonPressed() (2 input parameters) +Function 181: IsGamepadButtonPressed() (2 input parameters) Name: IsGamepadButtonPressed Return type: bool Description: Check if a gamepad button has been pressed once Param[1]: gamepad (type: int) Param[2]: button (type: int) -Function 180: IsGamepadButtonDown() (2 input parameters) +Function 182: IsGamepadButtonDown() (2 input parameters) Name: IsGamepadButtonDown Return type: bool Description: Check if a gamepad button is being pressed Param[1]: gamepad (type: int) Param[2]: button (type: int) -Function 181: IsGamepadButtonReleased() (2 input parameters) +Function 183: IsGamepadButtonReleased() (2 input parameters) Name: IsGamepadButtonReleased Return type: bool Description: Check if a gamepad button has been released once Param[1]: gamepad (type: int) Param[2]: button (type: int) -Function 182: IsGamepadButtonUp() (2 input parameters) +Function 184: IsGamepadButtonUp() (2 input parameters) Name: IsGamepadButtonUp Return type: bool Description: Check if a gamepad button is NOT being pressed Param[1]: gamepad (type: int) Param[2]: button (type: int) -Function 183: GetGamepadButtonPressed() (0 input parameters) +Function 185: GetGamepadButtonPressed() (0 input parameters) Name: GetGamepadButtonPressed Return type: int Description: Get the last gamepad button pressed No input parameters -Function 184: GetGamepadAxisCount() (1 input parameters) +Function 186: GetGamepadAxisCount() (1 input parameters) Name: GetGamepadAxisCount Return type: int Description: Get axis count for a gamepad Param[1]: gamepad (type: int) -Function 185: GetGamepadAxisMovement() (2 input parameters) +Function 187: GetGamepadAxisMovement() (2 input parameters) Name: GetGamepadAxisMovement Return type: float Description: Get movement value for a gamepad axis Param[1]: gamepad (type: int) Param[2]: axis (type: int) -Function 186: SetGamepadMappings() (1 input parameters) +Function 188: SetGamepadMappings() (1 input parameters) Name: SetGamepadMappings Return type: int Description: Set internal gamepad mappings (SDL_GameControllerDB) Param[1]: mappings (type: const char *) -Function 187: SetGamepadVibration() (4 input parameters) +Function 189: SetGamepadVibration() (4 input parameters) Name: SetGamepadVibration Return type: void Description: Set gamepad vibration for both motors (duration in seconds) @@ -2006,151 +2017,151 @@ Function 187: SetGamepadVibration() (4 input parameters) Param[2]: leftMotor (type: float) Param[3]: rightMotor (type: float) Param[4]: duration (type: float) -Function 188: IsMouseButtonPressed() (1 input parameters) +Function 190: IsMouseButtonPressed() (1 input parameters) Name: IsMouseButtonPressed Return type: bool Description: Check if a mouse button has been pressed once Param[1]: button (type: int) -Function 189: IsMouseButtonDown() (1 input parameters) +Function 191: IsMouseButtonDown() (1 input parameters) Name: IsMouseButtonDown Return type: bool Description: Check if a mouse button is being pressed Param[1]: button (type: int) -Function 190: IsMouseButtonReleased() (1 input parameters) +Function 192: IsMouseButtonReleased() (1 input parameters) Name: IsMouseButtonReleased Return type: bool Description: Check if a mouse button has been released once Param[1]: button (type: int) -Function 191: IsMouseButtonUp() (1 input parameters) +Function 193: IsMouseButtonUp() (1 input parameters) Name: IsMouseButtonUp Return type: bool Description: Check if a mouse button is NOT being pressed Param[1]: button (type: int) -Function 192: GetMouseX() (0 input parameters) +Function 194: GetMouseX() (0 input parameters) Name: GetMouseX Return type: int Description: Get mouse position X No input parameters -Function 193: GetMouseY() (0 input parameters) +Function 195: GetMouseY() (0 input parameters) Name: GetMouseY Return type: int Description: Get mouse position Y No input parameters -Function 194: GetMousePosition() (0 input parameters) +Function 196: GetMousePosition() (0 input parameters) Name: GetMousePosition Return type: Vector2 Description: Get mouse position XY No input parameters -Function 195: GetMouseDelta() (0 input parameters) +Function 197: GetMouseDelta() (0 input parameters) Name: GetMouseDelta Return type: Vector2 Description: Get mouse delta between frames No input parameters -Function 196: SetMousePosition() (2 input parameters) +Function 198: SetMousePosition() (2 input parameters) Name: SetMousePosition Return type: void Description: Set mouse position XY Param[1]: x (type: int) Param[2]: y (type: int) -Function 197: SetMouseOffset() (2 input parameters) +Function 199: SetMouseOffset() (2 input parameters) Name: SetMouseOffset Return type: void Description: Set mouse offset Param[1]: offsetX (type: int) Param[2]: offsetY (type: int) -Function 198: SetMouseScale() (2 input parameters) +Function 200: SetMouseScale() (2 input parameters) Name: SetMouseScale Return type: void Description: Set mouse scaling Param[1]: scaleX (type: float) Param[2]: scaleY (type: float) -Function 199: GetMouseWheelMove() (0 input parameters) +Function 201: GetMouseWheelMove() (0 input parameters) Name: GetMouseWheelMove Return type: float Description: Get mouse wheel movement for X or Y, whichever is larger No input parameters -Function 200: GetMouseWheelMoveV() (0 input parameters) +Function 202: GetMouseWheelMoveV() (0 input parameters) Name: GetMouseWheelMoveV Return type: Vector2 Description: Get mouse wheel movement for both X and Y No input parameters -Function 201: SetMouseCursor() (1 input parameters) +Function 203: SetMouseCursor() (1 input parameters) Name: SetMouseCursor Return type: void Description: Set mouse cursor Param[1]: cursor (type: int) -Function 202: GetTouchX() (0 input parameters) +Function 204: GetTouchX() (0 input parameters) Name: GetTouchX Return type: int Description: Get touch position X for touch point 0 (relative to screen size) No input parameters -Function 203: GetTouchY() (0 input parameters) +Function 205: GetTouchY() (0 input parameters) Name: GetTouchY Return type: int Description: Get touch position Y for touch point 0 (relative to screen size) No input parameters -Function 204: GetTouchPosition() (1 input parameters) +Function 206: GetTouchPosition() (1 input parameters) Name: GetTouchPosition Return type: Vector2 Description: Get touch position XY for a touch point index (relative to screen size) Param[1]: index (type: int) -Function 205: GetTouchPointId() (1 input parameters) +Function 207: GetTouchPointId() (1 input parameters) Name: GetTouchPointId Return type: int Description: Get touch point identifier for given index Param[1]: index (type: int) -Function 206: GetTouchPointCount() (0 input parameters) +Function 208: GetTouchPointCount() (0 input parameters) Name: GetTouchPointCount Return type: int Description: Get number of touch points No input parameters -Function 207: SetGesturesEnabled() (1 input parameters) +Function 209: SetGesturesEnabled() (1 input parameters) Name: SetGesturesEnabled Return type: void Description: Enable a set of gestures using flags Param[1]: flags (type: unsigned int) -Function 208: IsGestureDetected() (1 input parameters) +Function 210: IsGestureDetected() (1 input parameters) Name: IsGestureDetected Return type: bool Description: Check if a gesture have been detected Param[1]: gesture (type: unsigned int) -Function 209: GetGestureDetected() (0 input parameters) +Function 211: GetGestureDetected() (0 input parameters) Name: GetGestureDetected Return type: int Description: Get latest detected gesture No input parameters -Function 210: GetGestureHoldDuration() (0 input parameters) +Function 212: GetGestureHoldDuration() (0 input parameters) Name: GetGestureHoldDuration Return type: float Description: Get gesture hold time in seconds No input parameters -Function 211: GetGestureDragVector() (0 input parameters) +Function 213: GetGestureDragVector() (0 input parameters) Name: GetGestureDragVector Return type: Vector2 Description: Get gesture drag vector No input parameters -Function 212: GetGestureDragAngle() (0 input parameters) +Function 214: GetGestureDragAngle() (0 input parameters) Name: GetGestureDragAngle Return type: float Description: Get gesture drag angle No input parameters -Function 213: GetGesturePinchVector() (0 input parameters) +Function 215: GetGesturePinchVector() (0 input parameters) Name: GetGesturePinchVector Return type: Vector2 Description: Get gesture pinch delta No input parameters -Function 214: GetGesturePinchAngle() (0 input parameters) +Function 216: GetGesturePinchAngle() (0 input parameters) Name: GetGesturePinchAngle Return type: float Description: Get gesture pinch angle No input parameters -Function 215: UpdateCamera() (2 input parameters) +Function 217: UpdateCamera() (2 input parameters) Name: UpdateCamera Return type: void Description: Update camera position for selected mode Param[1]: camera (type: Camera *) Param[2]: mode (type: int) -Function 216: UpdateCameraPro() (4 input parameters) +Function 218: UpdateCameraPro() (4 input parameters) Name: UpdateCameraPro Return type: void Description: Update camera movement/rotation @@ -2158,36 +2169,36 @@ Function 216: UpdateCameraPro() (4 input parameters) Param[2]: movement (type: Vector3) Param[3]: rotation (type: Vector3) Param[4]: zoom (type: float) -Function 217: SetShapesTexture() (2 input parameters) +Function 219: SetShapesTexture() (2 input parameters) Name: SetShapesTexture Return type: void Description: Set texture and rectangle to be used on shapes drawing Param[1]: texture (type: Texture2D) Param[2]: source (type: Rectangle) -Function 218: GetShapesTexture() (0 input parameters) +Function 220: GetShapesTexture() (0 input parameters) Name: GetShapesTexture Return type: Texture2D Description: Get texture that is used for shapes drawing No input parameters -Function 219: GetShapesTextureRectangle() (0 input parameters) +Function 221: GetShapesTextureRectangle() (0 input parameters) Name: GetShapesTextureRectangle Return type: Rectangle Description: Get texture source rectangle that is used for shapes drawing No input parameters -Function 220: DrawPixel() (3 input parameters) +Function 222: DrawPixel() (3 input parameters) Name: DrawPixel Return type: void Description: Draw a pixel using geometry [Can be slow, use with care] Param[1]: posX (type: int) Param[2]: posY (type: int) Param[3]: color (type: Color) -Function 221: DrawPixelV() (2 input parameters) +Function 223: DrawPixelV() (2 input parameters) Name: DrawPixelV Return type: void Description: Draw a pixel using geometry (Vector version) [Can be slow, use with care] Param[1]: position (type: Vector2) Param[2]: color (type: Color) -Function 222: DrawLine() (5 input parameters) +Function 224: DrawLine() (5 input parameters) Name: DrawLine Return type: void Description: Draw a line @@ -2196,14 +2207,14 @@ Function 222: DrawLine() (5 input parameters) Param[3]: endPosX (type: int) Param[4]: endPosY (type: int) Param[5]: color (type: Color) -Function 223: DrawLineV() (3 input parameters) +Function 225: DrawLineV() (3 input parameters) Name: DrawLineV Return type: void Description: Draw a line (using gl lines) Param[1]: startPos (type: Vector2) Param[2]: endPos (type: Vector2) Param[3]: color (type: Color) -Function 224: DrawLineEx() (4 input parameters) +Function 226: DrawLineEx() (4 input parameters) Name: DrawLineEx Return type: void Description: Draw a line (using triangles/quads) @@ -2211,14 +2222,14 @@ Function 224: DrawLineEx() (4 input parameters) Param[2]: endPos (type: Vector2) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 225: DrawLineStrip() (3 input parameters) +Function 227: DrawLineStrip() (3 input parameters) Name: DrawLineStrip Return type: void Description: Draw lines sequence (using gl lines) Param[1]: points (type: const Vector2 *) Param[2]: pointCount (type: int) Param[3]: color (type: Color) -Function 226: DrawLineBezier() (4 input parameters) +Function 228: DrawLineBezier() (4 input parameters) Name: DrawLineBezier Return type: void Description: Draw line segment cubic-bezier in-out interpolation @@ -2226,7 +2237,7 @@ Function 226: DrawLineBezier() (4 input parameters) Param[2]: endPos (type: Vector2) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 227: DrawLineDashed() (5 input parameters) +Function 229: DrawLineDashed() (5 input parameters) Name: DrawLineDashed Return type: void Description: Draw a dashed line @@ -2235,7 +2246,7 @@ Function 227: DrawLineDashed() (5 input parameters) Param[3]: dashSize (type: int) Param[4]: spaceSize (type: int) Param[5]: color (type: Color) -Function 228: DrawCircle() (4 input parameters) +Function 230: DrawCircle() (4 input parameters) Name: DrawCircle Return type: void Description: Draw a color-filled circle @@ -2243,7 +2254,7 @@ Function 228: DrawCircle() (4 input parameters) Param[2]: centerY (type: int) Param[3]: radius (type: float) Param[4]: color (type: Color) -Function 229: DrawCircleSector() (6 input parameters) +Function 231: DrawCircleSector() (6 input parameters) Name: DrawCircleSector Return type: void Description: Draw a piece of a circle @@ -2253,7 +2264,7 @@ Function 229: DrawCircleSector() (6 input parameters) Param[4]: endAngle (type: float) Param[5]: segments (type: int) Param[6]: color (type: Color) -Function 230: DrawCircleSectorLines() (6 input parameters) +Function 232: DrawCircleSectorLines() (6 input parameters) Name: DrawCircleSectorLines Return type: void Description: Draw circle sector outline @@ -2263,7 +2274,7 @@ Function 230: DrawCircleSectorLines() (6 input parameters) Param[4]: endAngle (type: float) Param[5]: segments (type: int) Param[6]: color (type: Color) -Function 231: DrawCircleGradient() (5 input parameters) +Function 233: DrawCircleGradient() (5 input parameters) Name: DrawCircleGradient Return type: void Description: Draw a gradient-filled circle @@ -2272,14 +2283,14 @@ Function 231: DrawCircleGradient() (5 input parameters) Param[3]: radius (type: float) Param[4]: inner (type: Color) Param[5]: outer (type: Color) -Function 232: DrawCircleV() (3 input parameters) +Function 234: DrawCircleV() (3 input parameters) Name: DrawCircleV Return type: void Description: Draw a color-filled circle (Vector version) Param[1]: center (type: Vector2) Param[2]: radius (type: float) Param[3]: color (type: Color) -Function 233: DrawCircleLines() (4 input parameters) +Function 235: DrawCircleLines() (4 input parameters) Name: DrawCircleLines Return type: void Description: Draw circle outline @@ -2287,14 +2298,14 @@ Function 233: DrawCircleLines() (4 input parameters) Param[2]: centerY (type: int) Param[3]: radius (type: float) Param[4]: color (type: Color) -Function 234: DrawCircleLinesV() (3 input parameters) +Function 236: DrawCircleLinesV() (3 input parameters) Name: DrawCircleLinesV Return type: void Description: Draw circle outline (Vector version) Param[1]: center (type: Vector2) Param[2]: radius (type: float) Param[3]: color (type: Color) -Function 235: DrawEllipse() (5 input parameters) +Function 237: DrawEllipse() (5 input parameters) Name: DrawEllipse Return type: void Description: Draw ellipse @@ -2303,7 +2314,7 @@ Function 235: DrawEllipse() (5 input parameters) Param[3]: radiusH (type: float) Param[4]: radiusV (type: float) Param[5]: color (type: Color) -Function 236: DrawEllipseV() (4 input parameters) +Function 238: DrawEllipseV() (4 input parameters) Name: DrawEllipseV Return type: void Description: Draw ellipse (Vector version) @@ -2311,7 +2322,7 @@ Function 236: DrawEllipseV() (4 input parameters) Param[2]: radiusH (type: float) Param[3]: radiusV (type: float) Param[4]: color (type: Color) -Function 237: DrawEllipseLines() (5 input parameters) +Function 239: DrawEllipseLines() (5 input parameters) Name: DrawEllipseLines Return type: void Description: Draw ellipse outline @@ -2320,7 +2331,7 @@ Function 237: DrawEllipseLines() (5 input parameters) Param[3]: radiusH (type: float) Param[4]: radiusV (type: float) Param[5]: color (type: Color) -Function 238: DrawEllipseLinesV() (4 input parameters) +Function 240: DrawEllipseLinesV() (4 input parameters) Name: DrawEllipseLinesV Return type: void Description: Draw ellipse outline (Vector version) @@ -2328,7 +2339,7 @@ Function 238: DrawEllipseLinesV() (4 input parameters) Param[2]: radiusH (type: float) Param[3]: radiusV (type: float) Param[4]: color (type: Color) -Function 239: DrawRing() (7 input parameters) +Function 241: DrawRing() (7 input parameters) Name: DrawRing Return type: void Description: Draw ring @@ -2339,7 +2350,7 @@ Function 239: DrawRing() (7 input parameters) Param[5]: endAngle (type: float) Param[6]: segments (type: int) Param[7]: color (type: Color) -Function 240: DrawRingLines() (7 input parameters) +Function 242: DrawRingLines() (7 input parameters) Name: DrawRingLines Return type: void Description: Draw ring outline @@ -2350,7 +2361,7 @@ Function 240: DrawRingLines() (7 input parameters) Param[5]: endAngle (type: float) Param[6]: segments (type: int) Param[7]: color (type: Color) -Function 241: DrawRectangle() (5 input parameters) +Function 243: DrawRectangle() (5 input parameters) Name: DrawRectangle Return type: void Description: Draw a color-filled rectangle @@ -2359,20 +2370,20 @@ Function 241: DrawRectangle() (5 input parameters) Param[3]: width (type: int) Param[4]: height (type: int) Param[5]: color (type: Color) -Function 242: DrawRectangleV() (3 input parameters) +Function 244: DrawRectangleV() (3 input parameters) Name: DrawRectangleV Return type: void Description: Draw a color-filled rectangle (Vector version) Param[1]: position (type: Vector2) Param[2]: size (type: Vector2) Param[3]: color (type: Color) -Function 243: DrawRectangleRec() (2 input parameters) +Function 245: DrawRectangleRec() (2 input parameters) Name: DrawRectangleRec Return type: void Description: Draw a color-filled rectangle Param[1]: rec (type: Rectangle) Param[2]: color (type: Color) -Function 244: DrawRectanglePro() (4 input parameters) +Function 246: DrawRectanglePro() (4 input parameters) Name: DrawRectanglePro Return type: void Description: Draw a color-filled rectangle with pro parameters @@ -2380,7 +2391,7 @@ Function 244: DrawRectanglePro() (4 input parameters) Param[2]: origin (type: Vector2) Param[3]: rotation (type: float) Param[4]: color (type: Color) -Function 245: DrawRectangleGradientV() (6 input parameters) +Function 247: DrawRectangleGradientV() (6 input parameters) Name: DrawRectangleGradientV Return type: void Description: Draw a vertical-gradient-filled rectangle @@ -2390,7 +2401,7 @@ Function 245: DrawRectangleGradientV() (6 input parameters) Param[4]: height (type: int) Param[5]: top (type: Color) Param[6]: bottom (type: Color) -Function 246: DrawRectangleGradientH() (6 input parameters) +Function 248: DrawRectangleGradientH() (6 input parameters) Name: DrawRectangleGradientH Return type: void Description: Draw a horizontal-gradient-filled rectangle @@ -2400,7 +2411,7 @@ Function 246: DrawRectangleGradientH() (6 input parameters) Param[4]: height (type: int) Param[5]: left (type: Color) Param[6]: right (type: Color) -Function 247: DrawRectangleGradientEx() (5 input parameters) +Function 249: DrawRectangleGradientEx() (5 input parameters) Name: DrawRectangleGradientEx Return type: void Description: Draw a gradient-filled rectangle with custom vertex colors @@ -2409,7 +2420,7 @@ Function 247: DrawRectangleGradientEx() (5 input parameters) Param[3]: bottomLeft (type: Color) Param[4]: bottomRight (type: Color) Param[5]: topRight (type: Color) -Function 248: DrawRectangleLines() (5 input parameters) +Function 250: DrawRectangleLines() (5 input parameters) Name: DrawRectangleLines Return type: void Description: Draw rectangle outline @@ -2418,14 +2429,14 @@ Function 248: DrawRectangleLines() (5 input parameters) Param[3]: width (type: int) Param[4]: height (type: int) Param[5]: color (type: Color) -Function 249: DrawRectangleLinesEx() (3 input parameters) +Function 251: DrawRectangleLinesEx() (3 input parameters) Name: DrawRectangleLinesEx Return type: void Description: Draw rectangle outline with extended parameters Param[1]: rec (type: Rectangle) Param[2]: lineThick (type: float) Param[3]: color (type: Color) -Function 250: DrawRectangleRounded() (4 input parameters) +Function 252: DrawRectangleRounded() (4 input parameters) Name: DrawRectangleRounded Return type: void Description: Draw rectangle with rounded edges @@ -2433,7 +2444,7 @@ Function 250: DrawRectangleRounded() (4 input parameters) Param[2]: roundness (type: float) Param[3]: segments (type: int) Param[4]: color (type: Color) -Function 251: DrawRectangleRoundedLines() (4 input parameters) +Function 253: DrawRectangleRoundedLines() (4 input parameters) Name: DrawRectangleRoundedLines Return type: void Description: Draw rectangle lines with rounded edges @@ -2441,7 +2452,7 @@ Function 251: DrawRectangleRoundedLines() (4 input parameters) Param[2]: roundness (type: float) Param[3]: segments (type: int) Param[4]: color (type: Color) -Function 252: DrawRectangleRoundedLinesEx() (5 input parameters) +Function 254: DrawRectangleRoundedLinesEx() (5 input parameters) Name: DrawRectangleRoundedLinesEx Return type: void Description: Draw rectangle with rounded edges outline @@ -2450,7 +2461,7 @@ Function 252: DrawRectangleRoundedLinesEx() (5 input parameters) Param[3]: segments (type: int) Param[4]: lineThick (type: float) Param[5]: color (type: Color) -Function 253: DrawTriangle() (4 input parameters) +Function 255: DrawTriangle() (4 input parameters) Name: DrawTriangle Return type: void Description: Draw a color-filled triangle (vertex in counter-clockwise order!) @@ -2458,7 +2469,7 @@ Function 253: DrawTriangle() (4 input parameters) Param[2]: v2 (type: Vector2) Param[3]: v3 (type: Vector2) Param[4]: color (type: Color) -Function 254: DrawTriangleLines() (4 input parameters) +Function 256: DrawTriangleLines() (4 input parameters) Name: DrawTriangleLines Return type: void Description: Draw triangle outline (vertex in counter-clockwise order!) @@ -2466,21 +2477,21 @@ Function 254: DrawTriangleLines() (4 input parameters) Param[2]: v2 (type: Vector2) Param[3]: v3 (type: Vector2) Param[4]: color (type: Color) -Function 255: DrawTriangleFan() (3 input parameters) +Function 257: DrawTriangleFan() (3 input parameters) Name: DrawTriangleFan Return type: void Description: Draw a triangle fan defined by points (first vertex is the center) Param[1]: points (type: const Vector2 *) Param[2]: pointCount (type: int) Param[3]: color (type: Color) -Function 256: DrawTriangleStrip() (3 input parameters) +Function 258: DrawTriangleStrip() (3 input parameters) Name: DrawTriangleStrip Return type: void Description: Draw a triangle strip defined by points Param[1]: points (type: const Vector2 *) Param[2]: pointCount (type: int) Param[3]: color (type: Color) -Function 257: DrawPoly() (5 input parameters) +Function 259: DrawPoly() (5 input parameters) Name: DrawPoly Return type: void Description: Draw a regular polygon (Vector version) @@ -2489,7 +2500,7 @@ Function 257: DrawPoly() (5 input parameters) Param[3]: radius (type: float) Param[4]: rotation (type: float) Param[5]: color (type: Color) -Function 258: DrawPolyLines() (5 input parameters) +Function 260: DrawPolyLines() (5 input parameters) Name: DrawPolyLines Return type: void Description: Draw a polygon outline of n sides @@ -2498,7 +2509,7 @@ Function 258: DrawPolyLines() (5 input parameters) Param[3]: radius (type: float) Param[4]: rotation (type: float) Param[5]: color (type: Color) -Function 259: DrawPolyLinesEx() (6 input parameters) +Function 261: DrawPolyLinesEx() (6 input parameters) Name: DrawPolyLinesEx Return type: void Description: Draw a polygon outline of n sides with extended parameters @@ -2508,7 +2519,7 @@ Function 259: DrawPolyLinesEx() (6 input parameters) Param[4]: rotation (type: float) Param[5]: lineThick (type: float) Param[6]: color (type: Color) -Function 260: DrawSplineLinear() (4 input parameters) +Function 262: DrawSplineLinear() (4 input parameters) Name: DrawSplineLinear Return type: void Description: Draw spline: Linear, minimum 2 points @@ -2516,7 +2527,7 @@ Function 260: DrawSplineLinear() (4 input parameters) Param[2]: pointCount (type: int) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 261: DrawSplineBasis() (4 input parameters) +Function 263: DrawSplineBasis() (4 input parameters) Name: DrawSplineBasis Return type: void Description: Draw spline: B-Spline, minimum 4 points @@ -2524,7 +2535,7 @@ Function 261: DrawSplineBasis() (4 input parameters) Param[2]: pointCount (type: int) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 262: DrawSplineCatmullRom() (4 input parameters) +Function 264: DrawSplineCatmullRom() (4 input parameters) Name: DrawSplineCatmullRom Return type: void Description: Draw spline: Catmull-Rom, minimum 4 points @@ -2532,7 +2543,7 @@ Function 262: DrawSplineCatmullRom() (4 input parameters) Param[2]: pointCount (type: int) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 263: DrawSplineBezierQuadratic() (4 input parameters) +Function 265: DrawSplineBezierQuadratic() (4 input parameters) Name: DrawSplineBezierQuadratic Return type: void Description: Draw spline: Quadratic Bezier, minimum 3 points (1 control point): [p1, c2, p3, c4...] @@ -2540,7 +2551,7 @@ Function 263: DrawSplineBezierQuadratic() (4 input parameters) Param[2]: pointCount (type: int) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 264: DrawSplineBezierCubic() (4 input parameters) +Function 266: DrawSplineBezierCubic() (4 input parameters) Name: DrawSplineBezierCubic Return type: void Description: Draw spline: Cubic Bezier, minimum 4 points (2 control points): [p1, c2, c3, p4, c5, c6...] @@ -2548,7 +2559,7 @@ Function 264: DrawSplineBezierCubic() (4 input parameters) Param[2]: pointCount (type: int) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 265: DrawSplineSegmentLinear() (4 input parameters) +Function 267: DrawSplineSegmentLinear() (4 input parameters) Name: DrawSplineSegmentLinear Return type: void Description: Draw spline segment: Linear, 2 points @@ -2556,7 +2567,7 @@ Function 265: DrawSplineSegmentLinear() (4 input parameters) Param[2]: p2 (type: Vector2) Param[3]: thick (type: float) Param[4]: color (type: Color) -Function 266: DrawSplineSegmentBasis() (6 input parameters) +Function 268: DrawSplineSegmentBasis() (6 input parameters) Name: DrawSplineSegmentBasis Return type: void Description: Draw spline segment: B-Spline, 4 points @@ -2566,7 +2577,7 @@ Function 266: DrawSplineSegmentBasis() (6 input parameters) Param[4]: p4 (type: Vector2) Param[5]: thick (type: float) Param[6]: color (type: Color) -Function 267: DrawSplineSegmentCatmullRom() (6 input parameters) +Function 269: DrawSplineSegmentCatmullRom() (6 input parameters) Name: DrawSplineSegmentCatmullRom Return type: void Description: Draw spline segment: Catmull-Rom, 4 points @@ -2576,7 +2587,7 @@ Function 267: DrawSplineSegmentCatmullRom() (6 input parameters) Param[4]: p4 (type: Vector2) Param[5]: thick (type: float) Param[6]: color (type: Color) -Function 268: DrawSplineSegmentBezierQuadratic() (5 input parameters) +Function 270: DrawSplineSegmentBezierQuadratic() (5 input parameters) Name: DrawSplineSegmentBezierQuadratic Return type: void Description: Draw spline segment: Quadratic Bezier, 2 points, 1 control point @@ -2585,7 +2596,7 @@ Function 268: DrawSplineSegmentBezierQuadratic() (5 input parameters) Param[3]: p3 (type: Vector2) Param[4]: thick (type: float) Param[5]: color (type: Color) -Function 269: DrawSplineSegmentBezierCubic() (6 input parameters) +Function 271: DrawSplineSegmentBezierCubic() (6 input parameters) Name: DrawSplineSegmentBezierCubic Return type: void Description: Draw spline segment: Cubic Bezier, 2 points, 2 control points @@ -2595,14 +2606,14 @@ Function 269: DrawSplineSegmentBezierCubic() (6 input parameters) Param[4]: p4 (type: Vector2) Param[5]: thick (type: float) Param[6]: color (type: Color) -Function 270: GetSplinePointLinear() (3 input parameters) +Function 272: GetSplinePointLinear() (3 input parameters) Name: GetSplinePointLinear Return type: Vector2 Description: Get (evaluate) spline point: Linear Param[1]: startPos (type: Vector2) Param[2]: endPos (type: Vector2) Param[3]: t (type: float) -Function 271: GetSplinePointBasis() (5 input parameters) +Function 273: GetSplinePointBasis() (5 input parameters) Name: GetSplinePointBasis Return type: Vector2 Description: Get (evaluate) spline point: B-Spline @@ -2611,7 +2622,7 @@ Function 271: GetSplinePointBasis() (5 input parameters) Param[3]: p3 (type: Vector2) Param[4]: p4 (type: Vector2) Param[5]: t (type: float) -Function 272: GetSplinePointCatmullRom() (5 input parameters) +Function 274: GetSplinePointCatmullRom() (5 input parameters) Name: GetSplinePointCatmullRom Return type: Vector2 Description: Get (evaluate) spline point: Catmull-Rom @@ -2620,7 +2631,7 @@ Function 272: GetSplinePointCatmullRom() (5 input parameters) Param[3]: p3 (type: Vector2) Param[4]: p4 (type: Vector2) Param[5]: t (type: float) -Function 273: GetSplinePointBezierQuad() (4 input parameters) +Function 275: GetSplinePointBezierQuad() (4 input parameters) Name: GetSplinePointBezierQuad Return type: Vector2 Description: Get (evaluate) spline point: Quadratic Bezier @@ -2628,7 +2639,7 @@ Function 273: GetSplinePointBezierQuad() (4 input parameters) Param[2]: c2 (type: Vector2) Param[3]: p3 (type: Vector2) Param[4]: t (type: float) -Function 274: GetSplinePointBezierCubic() (5 input parameters) +Function 276: GetSplinePointBezierCubic() (5 input parameters) Name: GetSplinePointBezierCubic Return type: Vector2 Description: Get (evaluate) spline point: Cubic Bezier @@ -2637,13 +2648,13 @@ Function 274: GetSplinePointBezierCubic() (5 input parameters) Param[3]: c3 (type: Vector2) Param[4]: p4 (type: Vector2) Param[5]: t (type: float) -Function 275: CheckCollisionRecs() (2 input parameters) +Function 277: CheckCollisionRecs() (2 input parameters) Name: CheckCollisionRecs Return type: bool Description: Check collision between two rectangles Param[1]: rec1 (type: Rectangle) Param[2]: rec2 (type: Rectangle) -Function 276: CheckCollisionCircles() (4 input parameters) +Function 278: CheckCollisionCircles() (4 input parameters) Name: CheckCollisionCircles Return type: bool Description: Check collision between two circles @@ -2651,14 +2662,14 @@ Function 276: CheckCollisionCircles() (4 input parameters) Param[2]: radius1 (type: float) Param[3]: center2 (type: Vector2) Param[4]: radius2 (type: float) -Function 277: CheckCollisionCircleRec() (3 input parameters) +Function 279: CheckCollisionCircleRec() (3 input parameters) Name: CheckCollisionCircleRec Return type: bool Description: Check collision between circle and rectangle Param[1]: center (type: Vector2) Param[2]: radius (type: float) Param[3]: rec (type: Rectangle) -Function 278: CheckCollisionCircleLine() (4 input parameters) +Function 280: CheckCollisionCircleLine() (4 input parameters) Name: CheckCollisionCircleLine Return type: bool Description: Check if circle collides with a line created betweeen two points [p1] and [p2] @@ -2666,20 +2677,20 @@ Function 278: CheckCollisionCircleLine() (4 input parameters) Param[2]: radius (type: float) Param[3]: p1 (type: Vector2) Param[4]: p2 (type: Vector2) -Function 279: CheckCollisionPointRec() (2 input parameters) +Function 281: CheckCollisionPointRec() (2 input parameters) Name: CheckCollisionPointRec Return type: bool Description: Check if point is inside rectangle Param[1]: point (type: Vector2) Param[2]: rec (type: Rectangle) -Function 280: CheckCollisionPointCircle() (3 input parameters) +Function 282: CheckCollisionPointCircle() (3 input parameters) Name: CheckCollisionPointCircle Return type: bool Description: Check if point is inside circle Param[1]: point (type: Vector2) Param[2]: center (type: Vector2) Param[3]: radius (type: float) -Function 281: CheckCollisionPointTriangle() (4 input parameters) +Function 283: CheckCollisionPointTriangle() (4 input parameters) Name: CheckCollisionPointTriangle Return type: bool Description: Check if point is inside a triangle @@ -2687,7 +2698,7 @@ Function 281: CheckCollisionPointTriangle() (4 input parameters) Param[2]: p1 (type: Vector2) Param[3]: p2 (type: Vector2) Param[4]: p3 (type: Vector2) -Function 282: CheckCollisionPointLine() (4 input parameters) +Function 284: CheckCollisionPointLine() (4 input parameters) Name: CheckCollisionPointLine Return type: bool Description: Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] @@ -2695,14 +2706,14 @@ Function 282: CheckCollisionPointLine() (4 input parameters) Param[2]: p1 (type: Vector2) Param[3]: p2 (type: Vector2) Param[4]: threshold (type: int) -Function 283: CheckCollisionPointPoly() (3 input parameters) +Function 285: CheckCollisionPointPoly() (3 input parameters) Name: CheckCollisionPointPoly Return type: bool Description: Check if point is within a polygon described by array of vertices Param[1]: point (type: Vector2) Param[2]: points (type: const Vector2 *) Param[3]: pointCount (type: int) -Function 284: CheckCollisionLines() (5 input parameters) +Function 286: CheckCollisionLines() (5 input parameters) Name: CheckCollisionLines Return type: bool Description: Check the collision between two lines defined by two points each, returns collision point by reference @@ -2711,18 +2722,18 @@ Function 284: CheckCollisionLines() (5 input parameters) Param[3]: startPos2 (type: Vector2) Param[4]: endPos2 (type: Vector2) Param[5]: collisionPoint (type: Vector2 *) -Function 285: GetCollisionRec() (2 input parameters) +Function 287: GetCollisionRec() (2 input parameters) Name: GetCollisionRec Return type: Rectangle Description: Get collision rectangle for two rectangles collision Param[1]: rec1 (type: Rectangle) Param[2]: rec2 (type: Rectangle) -Function 286: LoadImage() (1 input parameters) +Function 288: LoadImage() (1 input parameters) Name: LoadImage Return type: Image Description: Load image from file into CPU memory (RAM) Param[1]: fileName (type: const char *) -Function 287: LoadImageRaw() (5 input parameters) +Function 289: LoadImageRaw() (5 input parameters) Name: LoadImageRaw Return type: Image Description: Load image from RAW file data @@ -2731,13 +2742,13 @@ Function 287: LoadImageRaw() (5 input parameters) Param[3]: height (type: int) Param[4]: format (type: int) Param[5]: headerSize (type: int) -Function 288: LoadImageAnim() (2 input parameters) +Function 290: LoadImageAnim() (2 input parameters) Name: LoadImageAnim Return type: Image Description: Load image sequence from file (frames appended to image.data) Param[1]: fileName (type: const char *) Param[2]: frames (type: int *) -Function 289: LoadImageAnimFromMemory() (4 input parameters) +Function 291: LoadImageAnimFromMemory() (4 input parameters) Name: LoadImageAnimFromMemory Return type: Image Description: Load image sequence from memory buffer @@ -2745,60 +2756,60 @@ Function 289: LoadImageAnimFromMemory() (4 input parameters) Param[2]: fileData (type: const unsigned char *) Param[3]: dataSize (type: int) Param[4]: frames (type: int *) -Function 290: LoadImageFromMemory() (3 input parameters) +Function 292: LoadImageFromMemory() (3 input parameters) Name: LoadImageFromMemory Return type: Image Description: Load image from memory buffer, fileType refers to extension: i.e. '.png' Param[1]: fileType (type: const char *) Param[2]: fileData (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 291: LoadImageFromTexture() (1 input parameters) +Function 293: LoadImageFromTexture() (1 input parameters) Name: LoadImageFromTexture Return type: Image Description: Load image from GPU texture data Param[1]: texture (type: Texture2D) -Function 292: LoadImageFromScreen() (0 input parameters) +Function 294: LoadImageFromScreen() (0 input parameters) Name: LoadImageFromScreen Return type: Image Description: Load image from screen buffer and (screenshot) No input parameters -Function 293: IsImageValid() (1 input parameters) +Function 295: IsImageValid() (1 input parameters) Name: IsImageValid Return type: bool Description: Check if an image is valid (data and parameters) Param[1]: image (type: Image) -Function 294: UnloadImage() (1 input parameters) +Function 296: UnloadImage() (1 input parameters) Name: UnloadImage Return type: void Description: Unload image from CPU memory (RAM) Param[1]: image (type: Image) -Function 295: ExportImage() (2 input parameters) +Function 297: ExportImage() (2 input parameters) Name: ExportImage Return type: bool Description: Export image data to file, returns true on success Param[1]: image (type: Image) Param[2]: fileName (type: const char *) -Function 296: ExportImageToMemory() (3 input parameters) +Function 298: ExportImageToMemory() (3 input parameters) Name: ExportImageToMemory Return type: unsigned char * Description: Export image to memory buffer Param[1]: image (type: Image) Param[2]: fileType (type: const char *) Param[3]: fileSize (type: int *) -Function 297: ExportImageAsCode() (2 input parameters) +Function 299: ExportImageAsCode() (2 input parameters) Name: ExportImageAsCode Return type: bool Description: Export image as code file defining an array of bytes, returns true on success Param[1]: image (type: Image) Param[2]: fileName (type: const char *) -Function 298: GenImageColor() (3 input parameters) +Function 300: GenImageColor() (3 input parameters) Name: GenImageColor Return type: Image Description: Generate image: plain color Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: color (type: Color) -Function 299: GenImageGradientLinear() (5 input parameters) +Function 301: GenImageGradientLinear() (5 input parameters) Name: GenImageGradientLinear Return type: Image Description: Generate image: linear gradient, direction in degrees [0..360], 0=Vertical gradient @@ -2807,7 +2818,7 @@ Function 299: GenImageGradientLinear() (5 input parameters) Param[3]: direction (type: int) Param[4]: start (type: Color) Param[5]: end (type: Color) -Function 300: GenImageGradientRadial() (5 input parameters) +Function 302: GenImageGradientRadial() (5 input parameters) Name: GenImageGradientRadial Return type: Image Description: Generate image: radial gradient @@ -2816,7 +2827,7 @@ Function 300: GenImageGradientRadial() (5 input parameters) Param[3]: density (type: float) Param[4]: inner (type: Color) Param[5]: outer (type: Color) -Function 301: GenImageGradientSquare() (5 input parameters) +Function 303: GenImageGradientSquare() (5 input parameters) Name: GenImageGradientSquare Return type: Image Description: Generate image: square gradient @@ -2825,7 +2836,7 @@ Function 301: GenImageGradientSquare() (5 input parameters) Param[3]: density (type: float) Param[4]: inner (type: Color) Param[5]: outer (type: Color) -Function 302: GenImageChecked() (6 input parameters) +Function 304: GenImageChecked() (6 input parameters) Name: GenImageChecked Return type: Image Description: Generate image: checked @@ -2835,14 +2846,14 @@ Function 302: GenImageChecked() (6 input parameters) Param[4]: checksY (type: int) Param[5]: col1 (type: Color) Param[6]: col2 (type: Color) -Function 303: GenImageWhiteNoise() (3 input parameters) +Function 305: GenImageWhiteNoise() (3 input parameters) Name: GenImageWhiteNoise Return type: Image Description: Generate image: white noise Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: factor (type: float) -Function 304: GenImagePerlinNoise() (5 input parameters) +Function 306: GenImagePerlinNoise() (5 input parameters) Name: GenImagePerlinNoise Return type: Image Description: Generate image: perlin noise @@ -2851,45 +2862,45 @@ Function 304: GenImagePerlinNoise() (5 input parameters) Param[3]: offsetX (type: int) Param[4]: offsetY (type: int) Param[5]: scale (type: float) -Function 305: GenImageCellular() (3 input parameters) +Function 307: GenImageCellular() (3 input parameters) Name: GenImageCellular Return type: Image Description: Generate image: cellular algorithm, bigger tileSize means bigger cells Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: tileSize (type: int) -Function 306: GenImageText() (3 input parameters) +Function 308: GenImageText() (3 input parameters) Name: GenImageText Return type: Image Description: Generate image: grayscale image from text data Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: text (type: const char *) -Function 307: ImageCopy() (1 input parameters) +Function 309: ImageCopy() (1 input parameters) Name: ImageCopy Return type: Image Description: Create an image duplicate (useful for transformations) Param[1]: image (type: Image) -Function 308: ImageFromImage() (2 input parameters) +Function 310: ImageFromImage() (2 input parameters) Name: ImageFromImage Return type: Image Description: Create an image from another image piece Param[1]: image (type: Image) Param[2]: rec (type: Rectangle) -Function 309: ImageFromChannel() (2 input parameters) +Function 311: ImageFromChannel() (2 input parameters) Name: ImageFromChannel Return type: Image Description: Create an image from a selected channel of another image (GRAYSCALE) Param[1]: image (type: Image) Param[2]: selectedChannel (type: int) -Function 310: ImageText() (3 input parameters) +Function 312: ImageText() (3 input parameters) Name: ImageText Return type: Image Description: Create an image from text (default font) Param[1]: text (type: const char *) Param[2]: fontSize (type: int) Param[3]: color (type: Color) -Function 311: ImageTextEx() (5 input parameters) +Function 313: ImageTextEx() (5 input parameters) Name: ImageTextEx Return type: Image Description: Create an image from text (custom sprite font) @@ -2898,76 +2909,76 @@ Function 311: ImageTextEx() (5 input parameters) Param[3]: fontSize (type: float) Param[4]: spacing (type: float) Param[5]: tint (type: Color) -Function 312: ImageFormat() (2 input parameters) +Function 314: ImageFormat() (2 input parameters) Name: ImageFormat Return type: void Description: Convert image data to desired format Param[1]: image (type: Image *) Param[2]: newFormat (type: int) -Function 313: ImageToPOT() (2 input parameters) +Function 315: ImageToPOT() (2 input parameters) Name: ImageToPOT Return type: void Description: Convert image to POT (power-of-two) Param[1]: image (type: Image *) Param[2]: fill (type: Color) -Function 314: ImageCrop() (2 input parameters) +Function 316: ImageCrop() (2 input parameters) Name: ImageCrop Return type: void Description: Crop an image to a defined rectangle Param[1]: image (type: Image *) Param[2]: crop (type: Rectangle) -Function 315: ImageAlphaCrop() (2 input parameters) +Function 317: ImageAlphaCrop() (2 input parameters) Name: ImageAlphaCrop Return type: void Description: Crop image depending on alpha value Param[1]: image (type: Image *) Param[2]: threshold (type: float) -Function 316: ImageAlphaClear() (3 input parameters) +Function 318: ImageAlphaClear() (3 input parameters) Name: ImageAlphaClear Return type: void Description: Clear alpha channel to desired color Param[1]: image (type: Image *) Param[2]: color (type: Color) Param[3]: threshold (type: float) -Function 317: ImageAlphaMask() (2 input parameters) +Function 319: ImageAlphaMask() (2 input parameters) Name: ImageAlphaMask Return type: void Description: Apply alpha mask to image Param[1]: image (type: Image *) Param[2]: alphaMask (type: Image) -Function 318: ImageAlphaPremultiply() (1 input parameters) +Function 320: ImageAlphaPremultiply() (1 input parameters) Name: ImageAlphaPremultiply Return type: void Description: Premultiply alpha channel Param[1]: image (type: Image *) -Function 319: ImageBlurGaussian() (2 input parameters) +Function 321: ImageBlurGaussian() (2 input parameters) Name: ImageBlurGaussian Return type: void Description: Apply Gaussian blur using a box blur approximation Param[1]: image (type: Image *) Param[2]: blurSize (type: int) -Function 320: ImageKernelConvolution() (3 input parameters) +Function 322: ImageKernelConvolution() (3 input parameters) Name: ImageKernelConvolution Return type: void Description: Apply custom square convolution kernel to image Param[1]: image (type: Image *) Param[2]: kernel (type: const float *) Param[3]: kernelSize (type: int) -Function 321: ImageResize() (3 input parameters) +Function 323: ImageResize() (3 input parameters) Name: ImageResize Return type: void Description: Resize image (Bicubic scaling algorithm) Param[1]: image (type: Image *) Param[2]: newWidth (type: int) Param[3]: newHeight (type: int) -Function 322: ImageResizeNN() (3 input parameters) +Function 324: ImageResizeNN() (3 input parameters) Name: ImageResizeNN Return type: void Description: Resize image (Nearest-Neighbor scaling algorithm) Param[1]: image (type: Image *) Param[2]: newWidth (type: int) Param[3]: newHeight (type: int) -Function 323: ImageResizeCanvas() (6 input parameters) +Function 325: ImageResizeCanvas() (6 input parameters) Name: ImageResizeCanvas Return type: void Description: Resize canvas and fill with color @@ -2977,12 +2988,12 @@ Function 323: ImageResizeCanvas() (6 input parameters) Param[4]: offsetX (type: int) Param[5]: offsetY (type: int) Param[6]: fill (type: Color) -Function 324: ImageMipmaps() (1 input parameters) +Function 326: ImageMipmaps() (1 input parameters) Name: ImageMipmaps Return type: void Description: Compute all mipmap levels for a provided image Param[1]: image (type: Image *) -Function 325: ImageDither() (5 input parameters) +Function 327: ImageDither() (5 input parameters) Name: ImageDither Return type: void Description: Dither image data to 16bpp or lower (Floyd-Steinberg dithering) @@ -2991,109 +3002,109 @@ Function 325: ImageDither() (5 input parameters) Param[3]: gBpp (type: int) Param[4]: bBpp (type: int) Param[5]: aBpp (type: int) -Function 326: ImageFlipVertical() (1 input parameters) +Function 328: ImageFlipVertical() (1 input parameters) Name: ImageFlipVertical Return type: void Description: Flip image vertically Param[1]: image (type: Image *) -Function 327: ImageFlipHorizontal() (1 input parameters) +Function 329: ImageFlipHorizontal() (1 input parameters) Name: ImageFlipHorizontal Return type: void Description: Flip image horizontally Param[1]: image (type: Image *) -Function 328: ImageRotate() (2 input parameters) +Function 330: ImageRotate() (2 input parameters) Name: ImageRotate Return type: void Description: Rotate image by input angle in degrees (-359 to 359) Param[1]: image (type: Image *) Param[2]: degrees (type: int) -Function 329: ImageRotateCW() (1 input parameters) +Function 331: ImageRotateCW() (1 input parameters) Name: ImageRotateCW Return type: void Description: Rotate image clockwise 90deg Param[1]: image (type: Image *) -Function 330: ImageRotateCCW() (1 input parameters) +Function 332: ImageRotateCCW() (1 input parameters) Name: ImageRotateCCW Return type: void Description: Rotate image counter-clockwise 90deg Param[1]: image (type: Image *) -Function 331: ImageColorTint() (2 input parameters) +Function 333: ImageColorTint() (2 input parameters) Name: ImageColorTint Return type: void Description: Modify image color: tint Param[1]: image (type: Image *) Param[2]: color (type: Color) -Function 332: ImageColorInvert() (1 input parameters) +Function 334: ImageColorInvert() (1 input parameters) Name: ImageColorInvert Return type: void Description: Modify image color: invert Param[1]: image (type: Image *) -Function 333: ImageColorGrayscale() (1 input parameters) +Function 335: ImageColorGrayscale() (1 input parameters) Name: ImageColorGrayscale Return type: void Description: Modify image color: grayscale Param[1]: image (type: Image *) -Function 334: ImageColorContrast() (2 input parameters) +Function 336: ImageColorContrast() (2 input parameters) Name: ImageColorContrast Return type: void Description: Modify image color: contrast (-100 to 100) Param[1]: image (type: Image *) Param[2]: contrast (type: float) -Function 335: ImageColorBrightness() (2 input parameters) +Function 337: ImageColorBrightness() (2 input parameters) Name: ImageColorBrightness Return type: void Description: Modify image color: brightness (-255 to 255) Param[1]: image (type: Image *) Param[2]: brightness (type: int) -Function 336: ImageColorReplace() (3 input parameters) +Function 338: ImageColorReplace() (3 input parameters) Name: ImageColorReplace Return type: void Description: Modify image color: replace color Param[1]: image (type: Image *) Param[2]: color (type: Color) Param[3]: replace (type: Color) -Function 337: LoadImageColors() (1 input parameters) +Function 339: LoadImageColors() (1 input parameters) Name: LoadImageColors Return type: Color * Description: Load color data from image as a Color array (RGBA - 32bit) Param[1]: image (type: Image) -Function 338: LoadImagePalette() (3 input parameters) +Function 340: LoadImagePalette() (3 input parameters) Name: LoadImagePalette Return type: Color * Description: Load colors palette from image as a Color array (RGBA - 32bit) Param[1]: image (type: Image) Param[2]: maxPaletteSize (type: int) Param[3]: colorCount (type: int *) -Function 339: UnloadImageColors() (1 input parameters) +Function 341: UnloadImageColors() (1 input parameters) Name: UnloadImageColors Return type: void Description: Unload color data loaded with LoadImageColors() Param[1]: colors (type: Color *) -Function 340: UnloadImagePalette() (1 input parameters) +Function 342: UnloadImagePalette() (1 input parameters) Name: UnloadImagePalette Return type: void Description: Unload colors palette loaded with LoadImagePalette() Param[1]: colors (type: Color *) -Function 341: GetImageAlphaBorder() (2 input parameters) +Function 343: GetImageAlphaBorder() (2 input parameters) Name: GetImageAlphaBorder Return type: Rectangle Description: Get image alpha border rectangle Param[1]: image (type: Image) Param[2]: threshold (type: float) -Function 342: GetImageColor() (3 input parameters) +Function 344: GetImageColor() (3 input parameters) Name: GetImageColor Return type: Color Description: Get image pixel color at (x, y) position Param[1]: image (type: Image) Param[2]: x (type: int) Param[3]: y (type: int) -Function 343: ImageClearBackground() (2 input parameters) +Function 345: ImageClearBackground() (2 input parameters) Name: ImageClearBackground Return type: void Description: Clear image background with given color Param[1]: dst (type: Image *) Param[2]: color (type: Color) -Function 344: ImageDrawPixel() (4 input parameters) +Function 346: ImageDrawPixel() (4 input parameters) Name: ImageDrawPixel Return type: void Description: Draw pixel within an image @@ -3101,14 +3112,14 @@ Function 344: ImageDrawPixel() (4 input parameters) Param[2]: posX (type: int) Param[3]: posY (type: int) Param[4]: color (type: Color) -Function 345: ImageDrawPixelV() (3 input parameters) +Function 347: ImageDrawPixelV() (3 input parameters) Name: ImageDrawPixelV Return type: void Description: Draw pixel within an image (Vector version) Param[1]: dst (type: Image *) Param[2]: position (type: Vector2) Param[3]: color (type: Color) -Function 346: ImageDrawLine() (6 input parameters) +Function 348: ImageDrawLine() (6 input parameters) Name: ImageDrawLine Return type: void Description: Draw line within an image @@ -3118,7 +3129,7 @@ Function 346: ImageDrawLine() (6 input parameters) Param[4]: endPosX (type: int) Param[5]: endPosY (type: int) Param[6]: color (type: Color) -Function 347: ImageDrawLineV() (4 input parameters) +Function 349: ImageDrawLineV() (4 input parameters) Name: ImageDrawLineV Return type: void Description: Draw line within an image (Vector version) @@ -3126,7 +3137,7 @@ Function 347: ImageDrawLineV() (4 input parameters) Param[2]: start (type: Vector2) Param[3]: end (type: Vector2) Param[4]: color (type: Color) -Function 348: ImageDrawLineEx() (5 input parameters) +Function 350: ImageDrawLineEx() (5 input parameters) Name: ImageDrawLineEx Return type: void Description: Draw a line defining thickness within an image @@ -3135,7 +3146,7 @@ Function 348: ImageDrawLineEx() (5 input parameters) Param[3]: end (type: Vector2) Param[4]: thick (type: int) Param[5]: color (type: Color) -Function 349: ImageDrawCircle() (5 input parameters) +Function 351: ImageDrawCircle() (5 input parameters) Name: ImageDrawCircle Return type: void Description: Draw a filled circle within an image @@ -3144,7 +3155,7 @@ Function 349: ImageDrawCircle() (5 input parameters) Param[3]: centerY (type: int) Param[4]: radius (type: int) Param[5]: color (type: Color) -Function 350: ImageDrawCircleV() (4 input parameters) +Function 352: ImageDrawCircleV() (4 input parameters) Name: ImageDrawCircleV Return type: void Description: Draw a filled circle within an image (Vector version) @@ -3152,7 +3163,7 @@ Function 350: ImageDrawCircleV() (4 input parameters) Param[2]: center (type: Vector2) Param[3]: radius (type: int) Param[4]: color (type: Color) -Function 351: ImageDrawCircleLines() (5 input parameters) +Function 353: ImageDrawCircleLines() (5 input parameters) Name: ImageDrawCircleLines Return type: void Description: Draw circle outline within an image @@ -3161,7 +3172,7 @@ Function 351: ImageDrawCircleLines() (5 input parameters) Param[3]: centerY (type: int) Param[4]: radius (type: int) Param[5]: color (type: Color) -Function 352: ImageDrawCircleLinesV() (4 input parameters) +Function 354: ImageDrawCircleLinesV() (4 input parameters) Name: ImageDrawCircleLinesV Return type: void Description: Draw circle outline within an image (Vector version) @@ -3169,7 +3180,7 @@ Function 352: ImageDrawCircleLinesV() (4 input parameters) Param[2]: center (type: Vector2) Param[3]: radius (type: int) Param[4]: color (type: Color) -Function 353: ImageDrawRectangle() (6 input parameters) +Function 355: ImageDrawRectangle() (6 input parameters) Name: ImageDrawRectangle Return type: void Description: Draw rectangle within an image @@ -3179,7 +3190,7 @@ Function 353: ImageDrawRectangle() (6 input parameters) Param[4]: width (type: int) Param[5]: height (type: int) Param[6]: color (type: Color) -Function 354: ImageDrawRectangleV() (4 input parameters) +Function 356: ImageDrawRectangleV() (4 input parameters) Name: ImageDrawRectangleV Return type: void Description: Draw rectangle within an image (Vector version) @@ -3187,14 +3198,14 @@ Function 354: ImageDrawRectangleV() (4 input parameters) Param[2]: position (type: Vector2) Param[3]: size (type: Vector2) Param[4]: color (type: Color) -Function 355: ImageDrawRectangleRec() (3 input parameters) +Function 357: ImageDrawRectangleRec() (3 input parameters) Name: ImageDrawRectangleRec Return type: void Description: Draw rectangle within an image Param[1]: dst (type: Image *) Param[2]: rec (type: Rectangle) Param[3]: color (type: Color) -Function 356: ImageDrawRectangleLines() (4 input parameters) +Function 358: ImageDrawRectangleLines() (4 input parameters) Name: ImageDrawRectangleLines Return type: void Description: Draw rectangle lines within an image @@ -3202,7 +3213,7 @@ Function 356: ImageDrawRectangleLines() (4 input parameters) Param[2]: rec (type: Rectangle) Param[3]: thick (type: int) Param[4]: color (type: Color) -Function 357: ImageDrawTriangle() (5 input parameters) +Function 359: ImageDrawTriangle() (5 input parameters) Name: ImageDrawTriangle Return type: void Description: Draw triangle within an image @@ -3211,7 +3222,7 @@ Function 357: ImageDrawTriangle() (5 input parameters) Param[3]: v2 (type: Vector2) Param[4]: v3 (type: Vector2) Param[5]: color (type: Color) -Function 358: ImageDrawTriangleEx() (7 input parameters) +Function 360: ImageDrawTriangleEx() (7 input parameters) Name: ImageDrawTriangleEx Return type: void Description: Draw triangle with interpolated colors within an image @@ -3222,7 +3233,7 @@ Function 358: ImageDrawTriangleEx() (7 input parameters) Param[5]: c1 (type: Color) Param[6]: c2 (type: Color) Param[7]: c3 (type: Color) -Function 359: ImageDrawTriangleLines() (5 input parameters) +Function 361: ImageDrawTriangleLines() (5 input parameters) Name: ImageDrawTriangleLines Return type: void Description: Draw triangle outline within an image @@ -3231,7 +3242,7 @@ Function 359: ImageDrawTriangleLines() (5 input parameters) Param[3]: v2 (type: Vector2) Param[4]: v3 (type: Vector2) Param[5]: color (type: Color) -Function 360: ImageDrawTriangleFan() (4 input parameters) +Function 362: ImageDrawTriangleFan() (4 input parameters) Name: ImageDrawTriangleFan Return type: void Description: Draw a triangle fan defined by points within an image (first vertex is the center) @@ -3239,7 +3250,7 @@ Function 360: ImageDrawTriangleFan() (4 input parameters) Param[2]: points (type: const Vector2 *) Param[3]: pointCount (type: int) Param[4]: color (type: Color) -Function 361: ImageDrawTriangleStrip() (4 input parameters) +Function 363: ImageDrawTriangleStrip() (4 input parameters) Name: ImageDrawTriangleStrip Return type: void Description: Draw a triangle strip defined by points within an image @@ -3247,7 +3258,7 @@ Function 361: ImageDrawTriangleStrip() (4 input parameters) Param[2]: points (type: const Vector2 *) Param[3]: pointCount (type: int) Param[4]: color (type: Color) -Function 362: ImageDraw() (5 input parameters) +Function 364: ImageDraw() (5 input parameters) Name: ImageDraw Return type: void Description: Draw a source image within a destination image (tint applied to source) @@ -3256,7 +3267,7 @@ Function 362: ImageDraw() (5 input parameters) Param[3]: srcRec (type: Rectangle) Param[4]: dstRec (type: Rectangle) Param[5]: tint (type: Color) -Function 363: ImageDrawText() (6 input parameters) +Function 365: ImageDrawText() (6 input parameters) Name: ImageDrawText Return type: void Description: Draw text (using default font) within an image (destination) @@ -3266,7 +3277,7 @@ Function 363: ImageDrawText() (6 input parameters) Param[4]: posY (type: int) Param[5]: fontSize (type: int) Param[6]: color (type: Color) -Function 364: ImageDrawTextEx() (7 input parameters) +Function 366: ImageDrawTextEx() (7 input parameters) Name: ImageDrawTextEx Return type: void Description: Draw text (custom sprite font) within an image (destination) @@ -3277,79 +3288,79 @@ Function 364: ImageDrawTextEx() (7 input parameters) Param[5]: fontSize (type: float) Param[6]: spacing (type: float) Param[7]: tint (type: Color) -Function 365: LoadTexture() (1 input parameters) +Function 367: LoadTexture() (1 input parameters) Name: LoadTexture Return type: Texture2D Description: Load texture from file into GPU memory (VRAM) Param[1]: fileName (type: const char *) -Function 366: LoadTextureFromImage() (1 input parameters) +Function 368: LoadTextureFromImage() (1 input parameters) Name: LoadTextureFromImage Return type: Texture2D Description: Load texture from image data Param[1]: image (type: Image) -Function 367: LoadTextureCubemap() (2 input parameters) +Function 369: LoadTextureCubemap() (2 input parameters) Name: LoadTextureCubemap Return type: TextureCubemap Description: Load cubemap from image, multiple image cubemap layouts supported Param[1]: image (type: Image) Param[2]: layout (type: int) -Function 368: LoadRenderTexture() (2 input parameters) +Function 370: LoadRenderTexture() (2 input parameters) Name: LoadRenderTexture Return type: RenderTexture2D Description: Load texture for rendering (framebuffer) Param[1]: width (type: int) Param[2]: height (type: int) -Function 369: IsTextureValid() (1 input parameters) +Function 371: IsTextureValid() (1 input parameters) Name: IsTextureValid Return type: bool Description: Check if a texture is valid (loaded in GPU) Param[1]: texture (type: Texture2D) -Function 370: UnloadTexture() (1 input parameters) +Function 372: UnloadTexture() (1 input parameters) Name: UnloadTexture Return type: void Description: Unload texture from GPU memory (VRAM) Param[1]: texture (type: Texture2D) -Function 371: IsRenderTextureValid() (1 input parameters) +Function 373: IsRenderTextureValid() (1 input parameters) Name: IsRenderTextureValid Return type: bool Description: Check if a render texture is valid (loaded in GPU) Param[1]: target (type: RenderTexture2D) -Function 372: UnloadRenderTexture() (1 input parameters) +Function 374: UnloadRenderTexture() (1 input parameters) Name: UnloadRenderTexture Return type: void Description: Unload render texture from GPU memory (VRAM) Param[1]: target (type: RenderTexture2D) -Function 373: UpdateTexture() (2 input parameters) +Function 375: UpdateTexture() (2 input parameters) Name: UpdateTexture Return type: void Description: Update GPU texture with new data (pixels should be able to fill texture) Param[1]: texture (type: Texture2D) Param[2]: pixels (type: const void *) -Function 374: UpdateTextureRec() (3 input parameters) +Function 376: UpdateTextureRec() (3 input parameters) Name: UpdateTextureRec Return type: void Description: Update GPU texture rectangle with new data (pixels and rec should fit in texture) Param[1]: texture (type: Texture2D) Param[2]: rec (type: Rectangle) Param[3]: pixels (type: const void *) -Function 375: GenTextureMipmaps() (1 input parameters) +Function 377: GenTextureMipmaps() (1 input parameters) Name: GenTextureMipmaps Return type: void Description: Generate GPU mipmaps for a texture Param[1]: texture (type: Texture2D *) -Function 376: SetTextureFilter() (2 input parameters) +Function 378: SetTextureFilter() (2 input parameters) Name: SetTextureFilter Return type: void Description: Set texture scaling filter mode Param[1]: texture (type: Texture2D) Param[2]: filter (type: int) -Function 377: SetTextureWrap() (2 input parameters) +Function 379: SetTextureWrap() (2 input parameters) Name: SetTextureWrap Return type: void Description: Set texture wrapping mode Param[1]: texture (type: Texture2D) Param[2]: wrap (type: int) -Function 378: DrawTexture() (4 input parameters) +Function 380: DrawTexture() (4 input parameters) Name: DrawTexture Return type: void Description: Draw a Texture2D @@ -3357,14 +3368,14 @@ Function 378: DrawTexture() (4 input parameters) Param[2]: posX (type: int) Param[3]: posY (type: int) Param[4]: tint (type: Color) -Function 379: DrawTextureV() (3 input parameters) +Function 381: DrawTextureV() (3 input parameters) Name: DrawTextureV Return type: void Description: Draw a Texture2D with position defined as Vector2 Param[1]: texture (type: Texture2D) Param[2]: position (type: Vector2) Param[3]: tint (type: Color) -Function 380: DrawTextureEx() (5 input parameters) +Function 382: DrawTextureEx() (5 input parameters) Name: DrawTextureEx Return type: void Description: Draw a Texture2D with extended parameters @@ -3373,7 +3384,7 @@ Function 380: DrawTextureEx() (5 input parameters) Param[3]: rotation (type: float) Param[4]: scale (type: float) Param[5]: tint (type: Color) -Function 381: DrawTextureRec() (4 input parameters) +Function 383: DrawTextureRec() (4 input parameters) Name: DrawTextureRec Return type: void Description: Draw a part of a texture defined by a rectangle @@ -3381,7 +3392,7 @@ Function 381: DrawTextureRec() (4 input parameters) Param[2]: source (type: Rectangle) Param[3]: position (type: Vector2) Param[4]: tint (type: Color) -Function 382: DrawTexturePro() (6 input parameters) +Function 384: DrawTexturePro() (6 input parameters) Name: DrawTexturePro Return type: void Description: Draw a part of a texture defined by a rectangle with 'pro' parameters @@ -3391,7 +3402,7 @@ Function 382: DrawTexturePro() (6 input parameters) Param[4]: origin (type: Vector2) Param[5]: rotation (type: float) Param[6]: tint (type: Color) -Function 383: DrawTextureNPatch() (6 input parameters) +Function 385: DrawTextureNPatch() (6 input parameters) Name: DrawTextureNPatch Return type: void Description: Draws a texture (or part of it) that stretches or shrinks nicely @@ -3401,119 +3412,119 @@ Function 383: DrawTextureNPatch() (6 input parameters) Param[4]: origin (type: Vector2) Param[5]: rotation (type: float) Param[6]: tint (type: Color) -Function 384: ColorIsEqual() (2 input parameters) +Function 386: ColorIsEqual() (2 input parameters) Name: ColorIsEqual Return type: bool Description: Check if two colors are equal Param[1]: col1 (type: Color) Param[2]: col2 (type: Color) -Function 385: Fade() (2 input parameters) +Function 387: Fade() (2 input parameters) Name: Fade Return type: Color Description: Get color with alpha applied, alpha goes from 0.0f to 1.0f Param[1]: color (type: Color) Param[2]: alpha (type: float) -Function 386: ColorToInt() (1 input parameters) +Function 388: ColorToInt() (1 input parameters) Name: ColorToInt Return type: int Description: Get hexadecimal value for a Color (0xRRGGBBAA) Param[1]: color (type: Color) -Function 387: ColorNormalize() (1 input parameters) +Function 389: ColorNormalize() (1 input parameters) Name: ColorNormalize Return type: Vector4 Description: Get Color normalized as float [0..1] Param[1]: color (type: Color) -Function 388: ColorFromNormalized() (1 input parameters) +Function 390: ColorFromNormalized() (1 input parameters) Name: ColorFromNormalized Return type: Color Description: Get Color from normalized values [0..1] Param[1]: normalized (type: Vector4) -Function 389: ColorToHSV() (1 input parameters) +Function 391: ColorToHSV() (1 input parameters) Name: ColorToHSV Return type: Vector3 Description: Get HSV values for a Color, hue [0..360], saturation/value [0..1] Param[1]: color (type: Color) -Function 390: ColorFromHSV() (3 input parameters) +Function 392: ColorFromHSV() (3 input parameters) Name: ColorFromHSV Return type: Color Description: Get a Color from HSV values, hue [0..360], saturation/value [0..1] Param[1]: hue (type: float) Param[2]: saturation (type: float) Param[3]: value (type: float) -Function 391: ColorTint() (2 input parameters) +Function 393: ColorTint() (2 input parameters) Name: ColorTint Return type: Color Description: Get color multiplied with another color Param[1]: color (type: Color) Param[2]: tint (type: Color) -Function 392: ColorBrightness() (2 input parameters) +Function 394: ColorBrightness() (2 input parameters) Name: ColorBrightness Return type: Color Description: Get color with brightness correction, brightness factor goes from -1.0f to 1.0f Param[1]: color (type: Color) Param[2]: factor (type: float) -Function 393: ColorContrast() (2 input parameters) +Function 395: ColorContrast() (2 input parameters) Name: ColorContrast Return type: Color Description: Get color with contrast correction, contrast values between -1.0f and 1.0f Param[1]: color (type: Color) Param[2]: contrast (type: float) -Function 394: ColorAlpha() (2 input parameters) +Function 396: ColorAlpha() (2 input parameters) Name: ColorAlpha Return type: Color Description: Get color with alpha applied, alpha goes from 0.0f to 1.0f Param[1]: color (type: Color) Param[2]: alpha (type: float) -Function 395: ColorAlphaBlend() (3 input parameters) +Function 397: ColorAlphaBlend() (3 input parameters) Name: ColorAlphaBlend Return type: Color Description: Get src alpha-blended into dst color with tint Param[1]: dst (type: Color) Param[2]: src (type: Color) Param[3]: tint (type: Color) -Function 396: ColorLerp() (3 input parameters) +Function 398: ColorLerp() (3 input parameters) Name: ColorLerp Return type: Color Description: Get color lerp interpolation between two colors, factor [0.0f..1.0f] Param[1]: color1 (type: Color) Param[2]: color2 (type: Color) Param[3]: factor (type: float) -Function 397: GetColor() (1 input parameters) +Function 399: GetColor() (1 input parameters) Name: GetColor Return type: Color Description: Get Color structure from hexadecimal value Param[1]: hexValue (type: unsigned int) -Function 398: GetPixelColor() (2 input parameters) +Function 400: GetPixelColor() (2 input parameters) Name: GetPixelColor Return type: Color Description: Get Color from a source pixel pointer of certain format Param[1]: srcPtr (type: void *) Param[2]: format (type: int) -Function 399: SetPixelColor() (3 input parameters) +Function 401: SetPixelColor() (3 input parameters) Name: SetPixelColor Return type: void Description: Set color formatted into destination pixel pointer Param[1]: dstPtr (type: void *) Param[2]: color (type: Color) Param[3]: format (type: int) -Function 400: GetPixelDataSize() (3 input parameters) +Function 402: GetPixelDataSize() (3 input parameters) Name: GetPixelDataSize Return type: int Description: Get pixel data size in bytes for certain format Param[1]: width (type: int) Param[2]: height (type: int) Param[3]: format (type: int) -Function 401: GetFontDefault() (0 input parameters) +Function 403: GetFontDefault() (0 input parameters) Name: GetFontDefault Return type: Font Description: Get the default Font No input parameters -Function 402: LoadFont() (1 input parameters) +Function 404: LoadFont() (1 input parameters) Name: LoadFont Return type: Font Description: Load font from file into GPU memory (VRAM) Param[1]: fileName (type: const char *) -Function 403: LoadFontEx() (4 input parameters) +Function 405: LoadFontEx() (4 input parameters) Name: LoadFontEx Return type: Font Description: Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height @@ -3521,14 +3532,14 @@ Function 403: LoadFontEx() (4 input parameters) Param[2]: fontSize (type: int) Param[3]: codepoints (type: const int *) Param[4]: codepointCount (type: int) -Function 404: LoadFontFromImage() (3 input parameters) +Function 406: LoadFontFromImage() (3 input parameters) Name: LoadFontFromImage Return type: Font Description: Load font from Image (XNA style) Param[1]: image (type: Image) Param[2]: key (type: Color) Param[3]: firstChar (type: int) -Function 405: LoadFontFromMemory() (6 input parameters) +Function 407: LoadFontFromMemory() (6 input parameters) Name: LoadFontFromMemory Return type: Font Description: Load font from memory buffer, fileType refers to extension: i.e. '.ttf' @@ -3538,12 +3549,12 @@ Function 405: LoadFontFromMemory() (6 input parameters) Param[4]: fontSize (type: int) Param[5]: codepoints (type: const int *) Param[6]: codepointCount (type: int) -Function 406: IsFontValid() (1 input parameters) +Function 408: IsFontValid() (1 input parameters) Name: IsFontValid Return type: bool Description: Check if a font is valid (font data loaded, WARNING: GPU texture not checked) Param[1]: font (type: Font) -Function 407: LoadFontData() (7 input parameters) +Function 409: LoadFontData() (7 input parameters) Name: LoadFontData Return type: GlyphInfo * Description: Load font data for further use @@ -3554,7 +3565,7 @@ Function 407: LoadFontData() (7 input parameters) Param[5]: codepointCount (type: int) Param[6]: type (type: int) Param[7]: glyphCount (type: int *) -Function 408: GenImageFontAtlas() (6 input parameters) +Function 410: GenImageFontAtlas() (6 input parameters) Name: GenImageFontAtlas Return type: Image Description: Generate image font atlas using chars info @@ -3564,30 +3575,30 @@ Function 408: GenImageFontAtlas() (6 input parameters) Param[4]: fontSize (type: int) Param[5]: padding (type: int) Param[6]: packMethod (type: int) -Function 409: UnloadFontData() (2 input parameters) +Function 411: UnloadFontData() (2 input parameters) Name: UnloadFontData Return type: void Description: Unload font chars info data (RAM) Param[1]: glyphs (type: GlyphInfo *) Param[2]: glyphCount (type: int) -Function 410: UnloadFont() (1 input parameters) +Function 412: UnloadFont() (1 input parameters) Name: UnloadFont Return type: void Description: Unload font from GPU memory (VRAM) Param[1]: font (type: Font) -Function 411: ExportFontAsCode() (2 input parameters) +Function 413: ExportFontAsCode() (2 input parameters) Name: ExportFontAsCode Return type: bool Description: Export font as code file, returns true on success Param[1]: font (type: Font) Param[2]: fileName (type: const char *) -Function 412: DrawFPS() (2 input parameters) +Function 414: DrawFPS() (2 input parameters) Name: DrawFPS Return type: void Description: Draw current FPS Param[1]: posX (type: int) Param[2]: posY (type: int) -Function 413: DrawText() (5 input parameters) +Function 415: DrawText() (5 input parameters) Name: DrawText Return type: void Description: Draw text (using default font) @@ -3596,7 +3607,7 @@ Function 413: DrawText() (5 input parameters) Param[3]: posY (type: int) Param[4]: fontSize (type: int) Param[5]: color (type: Color) -Function 414: DrawTextEx() (6 input parameters) +Function 416: DrawTextEx() (6 input parameters) Name: DrawTextEx Return type: void Description: Draw text using font and additional parameters @@ -3606,7 +3617,7 @@ Function 414: DrawTextEx() (6 input parameters) Param[4]: fontSize (type: float) Param[5]: spacing (type: float) Param[6]: tint (type: Color) -Function 415: DrawTextPro() (8 input parameters) +Function 417: DrawTextPro() (8 input parameters) Name: DrawTextPro Return type: void Description: Draw text using Font and pro parameters (rotation) @@ -3618,7 +3629,7 @@ Function 415: DrawTextPro() (8 input parameters) Param[6]: fontSize (type: float) Param[7]: spacing (type: float) Param[8]: tint (type: Color) -Function 416: DrawTextCodepoint() (5 input parameters) +Function 418: DrawTextCodepoint() (5 input parameters) Name: DrawTextCodepoint Return type: void Description: Draw one character (codepoint) @@ -3627,7 +3638,7 @@ Function 416: DrawTextCodepoint() (5 input parameters) Param[3]: position (type: Vector2) Param[4]: fontSize (type: float) Param[5]: tint (type: Color) -Function 417: DrawTextCodepoints() (7 input parameters) +Function 419: DrawTextCodepoints() (7 input parameters) Name: DrawTextCodepoints Return type: void Description: Draw multiple character (codepoint) @@ -3638,18 +3649,18 @@ Function 417: DrawTextCodepoints() (7 input parameters) Param[5]: fontSize (type: float) Param[6]: spacing (type: float) Param[7]: tint (type: Color) -Function 418: SetTextLineSpacing() (1 input parameters) +Function 420: SetTextLineSpacing() (1 input parameters) Name: SetTextLineSpacing Return type: void Description: Set vertical line spacing when drawing with line-breaks Param[1]: spacing (type: int) -Function 419: MeasureText() (2 input parameters) +Function 421: MeasureText() (2 input parameters) Name: MeasureText Return type: int Description: Measure string width for default font Param[1]: text (type: const char *) Param[2]: fontSize (type: int) -Function 420: MeasureTextEx() (4 input parameters) +Function 422: MeasureTextEx() (4 input parameters) Name: MeasureTextEx Return type: Vector2 Description: Measure string size for Font @@ -3657,137 +3668,137 @@ Function 420: MeasureTextEx() (4 input parameters) Param[2]: text (type: const char *) Param[3]: fontSize (type: float) Param[4]: spacing (type: float) -Function 421: GetGlyphIndex() (2 input parameters) +Function 423: GetGlyphIndex() (2 input parameters) Name: GetGlyphIndex Return type: int Description: Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found Param[1]: font (type: Font) Param[2]: codepoint (type: int) -Function 422: GetGlyphInfo() (2 input parameters) +Function 424: GetGlyphInfo() (2 input parameters) Name: GetGlyphInfo Return type: GlyphInfo Description: Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found Param[1]: font (type: Font) Param[2]: codepoint (type: int) -Function 423: GetGlyphAtlasRec() (2 input parameters) +Function 425: GetGlyphAtlasRec() (2 input parameters) Name: GetGlyphAtlasRec Return type: Rectangle Description: Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found Param[1]: font (type: Font) Param[2]: codepoint (type: int) -Function 424: LoadUTF8() (2 input parameters) +Function 426: LoadUTF8() (2 input parameters) Name: LoadUTF8 Return type: char * Description: Load UTF-8 text encoded from codepoints array Param[1]: codepoints (type: const int *) Param[2]: length (type: int) -Function 425: UnloadUTF8() (1 input parameters) +Function 427: UnloadUTF8() (1 input parameters) Name: UnloadUTF8 Return type: void Description: Unload UTF-8 text encoded from codepoints array Param[1]: text (type: char *) -Function 426: LoadCodepoints() (2 input parameters) +Function 428: LoadCodepoints() (2 input parameters) Name: LoadCodepoints Return type: int * Description: Load all codepoints from a UTF-8 text string, codepoints count returned by parameter Param[1]: text (type: const char *) Param[2]: count (type: int *) -Function 427: UnloadCodepoints() (1 input parameters) +Function 429: UnloadCodepoints() (1 input parameters) Name: UnloadCodepoints Return type: void Description: Unload codepoints data from memory Param[1]: codepoints (type: int *) -Function 428: GetCodepointCount() (1 input parameters) +Function 430: GetCodepointCount() (1 input parameters) Name: GetCodepointCount Return type: int Description: Get total number of codepoints in a UTF-8 encoded string Param[1]: text (type: const char *) -Function 429: GetCodepoint() (2 input parameters) +Function 431: GetCodepoint() (2 input parameters) Name: GetCodepoint Return type: int Description: Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure Param[1]: text (type: const char *) Param[2]: codepointSize (type: int *) -Function 430: GetCodepointNext() (2 input parameters) +Function 432: GetCodepointNext() (2 input parameters) Name: GetCodepointNext Return type: int Description: Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure Param[1]: text (type: const char *) Param[2]: codepointSize (type: int *) -Function 431: GetCodepointPrevious() (2 input parameters) +Function 433: GetCodepointPrevious() (2 input parameters) Name: GetCodepointPrevious Return type: int Description: Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure Param[1]: text (type: const char *) Param[2]: codepointSize (type: int *) -Function 432: CodepointToUTF8() (2 input parameters) +Function 434: CodepointToUTF8() (2 input parameters) Name: CodepointToUTF8 Return type: const char * Description: Encode one codepoint into UTF-8 byte array (array length returned as parameter) Param[1]: codepoint (type: int) Param[2]: utf8Size (type: int *) -Function 433: LoadTextLines() (2 input parameters) +Function 435: LoadTextLines() (2 input parameters) Name: LoadTextLines Return type: char ** Description: Load text as separate lines ('\n') Param[1]: text (type: const char *) Param[2]: count (type: int *) -Function 434: UnloadTextLines() (2 input parameters) +Function 436: UnloadTextLines() (2 input parameters) Name: UnloadTextLines Return type: void Description: Unload text lines Param[1]: text (type: char **) Param[2]: lineCount (type: int) -Function 435: TextCopy() (2 input parameters) +Function 437: TextCopy() (2 input parameters) Name: TextCopy Return type: int Description: Copy one string to another, returns bytes copied Param[1]: dst (type: char *) Param[2]: src (type: const char *) -Function 436: TextIsEqual() (2 input parameters) +Function 438: TextIsEqual() (2 input parameters) Name: TextIsEqual Return type: bool Description: Check if two text string are equal Param[1]: text1 (type: const char *) Param[2]: text2 (type: const char *) -Function 437: TextLength() (1 input parameters) +Function 439: TextLength() (1 input parameters) Name: TextLength Return type: unsigned int Description: Get text length, checks for '\0' ending Param[1]: text (type: const char *) -Function 438: TextFormat() (2 input parameters) +Function 440: TextFormat() (2 input parameters) Name: TextFormat Return type: const char * Description: Text formatting with variables (sprintf() style) Param[1]: text (type: const char *) Param[2]: args (type: ...) -Function 439: TextSubtext() (3 input parameters) +Function 441: TextSubtext() (3 input parameters) Name: TextSubtext Return type: const char * Description: Get a piece of a text string Param[1]: text (type: const char *) Param[2]: position (type: int) Param[3]: length (type: int) -Function 440: TextRemoveSpaces() (1 input parameters) +Function 442: TextRemoveSpaces() (1 input parameters) Name: TextRemoveSpaces Return type: const char * Description: Remove text spaces, concat words Param[1]: text (type: const char *) -Function 441: GetTextBetween() (3 input parameters) +Function 443: GetTextBetween() (3 input parameters) Name: GetTextBetween Return type: char * Description: Get text between two strings Param[1]: text (type: const char *) Param[2]: begin (type: const char *) Param[3]: end (type: const char *) -Function 442: TextReplace() (3 input parameters) +Function 444: TextReplace() (3 input parameters) Name: TextReplace Return type: char * Description: Replace text string (WARNING: memory must be freed!) Param[1]: text (type: const char *) Param[2]: search (type: const char *) Param[3]: replacement (type: const char *) -Function 443: TextReplaceBetween() (4 input parameters) +Function 445: TextReplaceBetween() (4 input parameters) Name: TextReplaceBetween Return type: char * Description: Replace text between two specific strings (WARNING: memory must be freed!) @@ -3795,89 +3806,89 @@ Function 443: TextReplaceBetween() (4 input parameters) Param[2]: begin (type: const char *) Param[3]: end (type: const char *) Param[4]: replacement (type: const char *) -Function 444: TextInsert() (3 input parameters) +Function 446: TextInsert() (3 input parameters) Name: TextInsert Return type: char * Description: Insert text in a position (WARNING: memory must be freed!) Param[1]: text (type: const char *) Param[2]: insert (type: const char *) Param[3]: position (type: int) -Function 445: TextJoin() (3 input parameters) +Function 447: TextJoin() (3 input parameters) Name: TextJoin Return type: char * Description: Join text strings with delimiter Param[1]: textList (type: char **) Param[2]: count (type: int) Param[3]: delimiter (type: const char *) -Function 446: TextSplit() (3 input parameters) +Function 448: TextSplit() (3 input parameters) Name: TextSplit Return type: char ** Description: Split text into multiple strings, using MAX_TEXTSPLIT_COUNT static strings Param[1]: text (type: const char *) Param[2]: delimiter (type: char) Param[3]: count (type: int *) -Function 447: TextAppend() (3 input parameters) +Function 449: TextAppend() (3 input parameters) Name: TextAppend Return type: void Description: Append text at specific position and move cursor Param[1]: text (type: char *) Param[2]: append (type: const char *) Param[3]: position (type: int *) -Function 448: TextFindIndex() (2 input parameters) +Function 450: TextFindIndex() (2 input parameters) Name: TextFindIndex Return type: int Description: Find first text occurrence within a string, -1 if not found Param[1]: text (type: const char *) Param[2]: search (type: const char *) -Function 449: TextToUpper() (1 input parameters) +Function 451: TextToUpper() (1 input parameters) Name: TextToUpper Return type: char * Description: Get upper case version of provided string Param[1]: text (type: const char *) -Function 450: TextToLower() (1 input parameters) +Function 452: TextToLower() (1 input parameters) Name: TextToLower Return type: char * Description: Get lower case version of provided string Param[1]: text (type: const char *) -Function 451: TextToPascal() (1 input parameters) +Function 453: TextToPascal() (1 input parameters) Name: TextToPascal Return type: char * Description: Get Pascal case notation version of provided string Param[1]: text (type: const char *) -Function 452: TextToSnake() (1 input parameters) +Function 454: TextToSnake() (1 input parameters) Name: TextToSnake Return type: char * Description: Get Snake case notation version of provided string Param[1]: text (type: const char *) -Function 453: TextToCamel() (1 input parameters) +Function 455: TextToCamel() (1 input parameters) Name: TextToCamel Return type: char * Description: Get Camel case notation version of provided string Param[1]: text (type: const char *) -Function 454: TextToInteger() (1 input parameters) +Function 456: TextToInteger() (1 input parameters) Name: TextToInteger Return type: int Description: Get integer value from text Param[1]: text (type: const char *) -Function 455: TextToFloat() (1 input parameters) +Function 457: TextToFloat() (1 input parameters) Name: TextToFloat Return type: float Description: Get float value from text Param[1]: text (type: const char *) -Function 456: DrawLine3D() (3 input parameters) +Function 458: DrawLine3D() (3 input parameters) Name: DrawLine3D Return type: void Description: Draw a line in 3D world space Param[1]: startPos (type: Vector3) Param[2]: endPos (type: Vector3) Param[3]: color (type: Color) -Function 457: DrawPoint3D() (2 input parameters) +Function 459: DrawPoint3D() (2 input parameters) Name: DrawPoint3D Return type: void Description: Draw a point in 3D space, actually a small line Param[1]: position (type: Vector3) Param[2]: color (type: Color) -Function 458: DrawCircle3D() (5 input parameters) +Function 460: DrawCircle3D() (5 input parameters) Name: DrawCircle3D Return type: void Description: Draw a circle in 3D world space @@ -3886,7 +3897,7 @@ Function 458: DrawCircle3D() (5 input parameters) Param[3]: rotationAxis (type: Vector3) Param[4]: rotationAngle (type: float) Param[5]: color (type: Color) -Function 459: DrawTriangle3D() (4 input parameters) +Function 461: DrawTriangle3D() (4 input parameters) Name: DrawTriangle3D Return type: void Description: Draw a color-filled triangle (vertex in counter-clockwise order!) @@ -3894,14 +3905,14 @@ Function 459: DrawTriangle3D() (4 input parameters) Param[2]: v2 (type: Vector3) Param[3]: v3 (type: Vector3) Param[4]: color (type: Color) -Function 460: DrawTriangleStrip3D() (3 input parameters) +Function 462: DrawTriangleStrip3D() (3 input parameters) Name: DrawTriangleStrip3D Return type: void Description: Draw a triangle strip defined by points Param[1]: points (type: const Vector3 *) Param[2]: pointCount (type: int) Param[3]: color (type: Color) -Function 461: DrawCube() (5 input parameters) +Function 463: DrawCube() (5 input parameters) Name: DrawCube Return type: void Description: Draw cube @@ -3910,14 +3921,14 @@ Function 461: DrawCube() (5 input parameters) Param[3]: height (type: float) Param[4]: length (type: float) Param[5]: color (type: Color) -Function 462: DrawCubeV() (3 input parameters) +Function 464: DrawCubeV() (3 input parameters) Name: DrawCubeV Return type: void Description: Draw cube (Vector version) Param[1]: position (type: Vector3) Param[2]: size (type: Vector3) Param[3]: color (type: Color) -Function 463: DrawCubeWires() (5 input parameters) +Function 465: DrawCubeWires() (5 input parameters) Name: DrawCubeWires Return type: void Description: Draw cube wires @@ -3926,21 +3937,21 @@ Function 463: DrawCubeWires() (5 input parameters) Param[3]: height (type: float) Param[4]: length (type: float) Param[5]: color (type: Color) -Function 464: DrawCubeWiresV() (3 input parameters) +Function 466: DrawCubeWiresV() (3 input parameters) Name: DrawCubeWiresV Return type: void Description: Draw cube wires (Vector version) Param[1]: position (type: Vector3) Param[2]: size (type: Vector3) Param[3]: color (type: Color) -Function 465: DrawSphere() (3 input parameters) +Function 467: DrawSphere() (3 input parameters) Name: DrawSphere Return type: void Description: Draw sphere Param[1]: centerPos (type: Vector3) Param[2]: radius (type: float) Param[3]: color (type: Color) -Function 466: DrawSphereEx() (5 input parameters) +Function 468: DrawSphereEx() (5 input parameters) Name: DrawSphereEx Return type: void Description: Draw sphere with extended parameters @@ -3949,7 +3960,7 @@ Function 466: DrawSphereEx() (5 input parameters) Param[3]: rings (type: int) Param[4]: slices (type: int) Param[5]: color (type: Color) -Function 467: DrawSphereWires() (5 input parameters) +Function 469: DrawSphereWires() (5 input parameters) Name: DrawSphereWires Return type: void Description: Draw sphere wires @@ -3958,7 +3969,7 @@ Function 467: DrawSphereWires() (5 input parameters) Param[3]: rings (type: int) Param[4]: slices (type: int) Param[5]: color (type: Color) -Function 468: DrawCylinder() (6 input parameters) +Function 470: DrawCylinder() (6 input parameters) Name: DrawCylinder Return type: void Description: Draw a cylinder/cone @@ -3968,7 +3979,7 @@ Function 468: DrawCylinder() (6 input parameters) Param[4]: height (type: float) Param[5]: slices (type: int) Param[6]: color (type: Color) -Function 469: DrawCylinderEx() (6 input parameters) +Function 471: DrawCylinderEx() (6 input parameters) Name: DrawCylinderEx Return type: void Description: Draw a cylinder with base at startPos and top at endPos @@ -3978,7 +3989,7 @@ Function 469: DrawCylinderEx() (6 input parameters) Param[4]: endRadius (type: float) Param[5]: sides (type: int) Param[6]: color (type: Color) -Function 470: DrawCylinderWires() (6 input parameters) +Function 472: DrawCylinderWires() (6 input parameters) Name: DrawCylinderWires Return type: void Description: Draw a cylinder/cone wires @@ -3988,7 +3999,7 @@ Function 470: DrawCylinderWires() (6 input parameters) Param[4]: height (type: float) Param[5]: slices (type: int) Param[6]: color (type: Color) -Function 471: DrawCylinderWiresEx() (6 input parameters) +Function 473: DrawCylinderWiresEx() (6 input parameters) Name: DrawCylinderWiresEx Return type: void Description: Draw a cylinder wires with base at startPos and top at endPos @@ -3998,7 +4009,7 @@ Function 471: DrawCylinderWiresEx() (6 input parameters) Param[4]: endRadius (type: float) Param[5]: sides (type: int) Param[6]: color (type: Color) -Function 472: DrawCapsule() (6 input parameters) +Function 474: DrawCapsule() (6 input parameters) Name: DrawCapsule Return type: void Description: Draw a capsule with the center of its sphere caps at startPos and endPos @@ -4008,7 +4019,7 @@ Function 472: DrawCapsule() (6 input parameters) Param[4]: slices (type: int) Param[5]: rings (type: int) Param[6]: color (type: Color) -Function 473: DrawCapsuleWires() (6 input parameters) +Function 475: DrawCapsuleWires() (6 input parameters) Name: DrawCapsuleWires Return type: void Description: Draw capsule wireframe with the center of its sphere caps at startPos and endPos @@ -4018,51 +4029,51 @@ Function 473: DrawCapsuleWires() (6 input parameters) Param[4]: slices (type: int) Param[5]: rings (type: int) Param[6]: color (type: Color) -Function 474: DrawPlane() (3 input parameters) +Function 476: DrawPlane() (3 input parameters) Name: DrawPlane Return type: void Description: Draw a plane XZ Param[1]: centerPos (type: Vector3) Param[2]: size (type: Vector2) Param[3]: color (type: Color) -Function 475: DrawRay() (2 input parameters) +Function 477: DrawRay() (2 input parameters) Name: DrawRay Return type: void Description: Draw a ray line Param[1]: ray (type: Ray) Param[2]: color (type: Color) -Function 476: DrawGrid() (2 input parameters) +Function 478: DrawGrid() (2 input parameters) Name: DrawGrid Return type: void Description: Draw a grid (centered at (0, 0, 0)) Param[1]: slices (type: int) Param[2]: spacing (type: float) -Function 477: LoadModel() (1 input parameters) +Function 479: LoadModel() (1 input parameters) Name: LoadModel Return type: Model Description: Load model from files (meshes and materials) Param[1]: fileName (type: const char *) -Function 478: LoadModelFromMesh() (1 input parameters) +Function 480: LoadModelFromMesh() (1 input parameters) Name: LoadModelFromMesh Return type: Model Description: Load model from generated mesh (default material) Param[1]: mesh (type: Mesh) -Function 479: IsModelValid() (1 input parameters) +Function 481: IsModelValid() (1 input parameters) Name: IsModelValid Return type: bool Description: Check if a model is valid (loaded in GPU, VAO/VBOs) Param[1]: model (type: Model) -Function 480: UnloadModel() (1 input parameters) +Function 482: UnloadModel() (1 input parameters) Name: UnloadModel Return type: void Description: Unload model (including meshes) from memory (RAM and/or VRAM) Param[1]: model (type: Model) -Function 481: GetModelBoundingBox() (1 input parameters) +Function 483: GetModelBoundingBox() (1 input parameters) Name: GetModelBoundingBox Return type: BoundingBox Description: Compute model bounding box limits (considers all meshes) Param[1]: model (type: Model) -Function 482: DrawModel() (4 input parameters) +Function 484: DrawModel() (4 input parameters) Name: DrawModel Return type: void Description: Draw a model (with texture if set) @@ -4070,7 +4081,7 @@ Function 482: DrawModel() (4 input parameters) Param[2]: position (type: Vector3) Param[3]: scale (type: float) Param[4]: tint (type: Color) -Function 483: DrawModelEx() (6 input parameters) +Function 485: DrawModelEx() (6 input parameters) Name: DrawModelEx Return type: void Description: Draw a model with extended parameters @@ -4080,7 +4091,7 @@ Function 483: DrawModelEx() (6 input parameters) Param[4]: rotationAngle (type: float) Param[5]: scale (type: Vector3) Param[6]: tint (type: Color) -Function 484: DrawModelWires() (4 input parameters) +Function 486: DrawModelWires() (4 input parameters) Name: DrawModelWires Return type: void Description: Draw a model wires (with texture if set) @@ -4088,7 +4099,7 @@ Function 484: DrawModelWires() (4 input parameters) Param[2]: position (type: Vector3) Param[3]: scale (type: float) Param[4]: tint (type: Color) -Function 485: DrawModelWiresEx() (6 input parameters) +Function 487: DrawModelWiresEx() (6 input parameters) Name: DrawModelWiresEx Return type: void Description: Draw a model wires (with texture if set) with extended parameters @@ -4098,7 +4109,7 @@ Function 485: DrawModelWiresEx() (6 input parameters) Param[4]: rotationAngle (type: float) Param[5]: scale (type: Vector3) Param[6]: tint (type: Color) -Function 486: DrawModelPoints() (4 input parameters) +Function 488: DrawModelPoints() (4 input parameters) Name: DrawModelPoints Return type: void Description: Draw a model as points @@ -4106,7 +4117,7 @@ Function 486: DrawModelPoints() (4 input parameters) Param[2]: position (type: Vector3) Param[3]: scale (type: float) Param[4]: tint (type: Color) -Function 487: DrawModelPointsEx() (6 input parameters) +Function 489: DrawModelPointsEx() (6 input parameters) Name: DrawModelPointsEx Return type: void Description: Draw a model as points with extended parameters @@ -4116,13 +4127,13 @@ Function 487: DrawModelPointsEx() (6 input parameters) Param[4]: rotationAngle (type: float) Param[5]: scale (type: Vector3) Param[6]: tint (type: Color) -Function 488: DrawBoundingBox() (2 input parameters) +Function 490: DrawBoundingBox() (2 input parameters) Name: DrawBoundingBox Return type: void Description: Draw bounding box (wires) Param[1]: box (type: BoundingBox) Param[2]: color (type: Color) -Function 489: DrawBillboard() (5 input parameters) +Function 491: DrawBillboard() (5 input parameters) Name: DrawBillboard Return type: void Description: Draw a billboard texture @@ -4131,7 +4142,7 @@ Function 489: DrawBillboard() (5 input parameters) Param[3]: position (type: Vector3) Param[4]: scale (type: float) Param[5]: tint (type: Color) -Function 490: DrawBillboardRec() (6 input parameters) +Function 492: DrawBillboardRec() (6 input parameters) Name: DrawBillboardRec Return type: void Description: Draw a billboard texture defined by source @@ -4141,7 +4152,7 @@ Function 490: DrawBillboardRec() (6 input parameters) Param[4]: position (type: Vector3) Param[5]: size (type: Vector2) Param[6]: tint (type: Color) -Function 491: DrawBillboardPro() (9 input parameters) +Function 493: DrawBillboardPro() (9 input parameters) Name: DrawBillboardPro Return type: void Description: Draw a billboard texture defined by source and rotation @@ -4154,13 +4165,13 @@ Function 491: DrawBillboardPro() (9 input parameters) Param[7]: origin (type: Vector2) Param[8]: rotation (type: float) Param[9]: tint (type: Color) -Function 492: UploadMesh() (2 input parameters) +Function 494: UploadMesh() (2 input parameters) Name: UploadMesh Return type: void Description: Upload mesh vertex data in GPU and provide VAO/VBO ids Param[1]: mesh (type: Mesh *) Param[2]: dynamic (type: bool) -Function 493: UpdateMeshBuffer() (5 input parameters) +Function 495: UpdateMeshBuffer() (5 input parameters) Name: UpdateMeshBuffer Return type: void Description: Update mesh vertex data in GPU for a specific buffer index @@ -4169,19 +4180,19 @@ Function 493: UpdateMeshBuffer() (5 input parameters) Param[3]: data (type: const void *) Param[4]: dataSize (type: int) Param[5]: offset (type: int) -Function 494: UnloadMesh() (1 input parameters) +Function 496: UnloadMesh() (1 input parameters) Name: UnloadMesh Return type: void Description: Unload mesh data from CPU and GPU Param[1]: mesh (type: Mesh) -Function 495: DrawMesh() (3 input parameters) +Function 497: DrawMesh() (3 input parameters) Name: DrawMesh Return type: void Description: Draw a 3d mesh with material and transform Param[1]: mesh (type: Mesh) Param[2]: material (type: Material) Param[3]: transform (type: Matrix) -Function 496: DrawMeshInstanced() (4 input parameters) +Function 498: DrawMeshInstanced() (4 input parameters) Name: DrawMeshInstanced Return type: void Description: Draw multiple mesh instances with material and different transforms @@ -4189,35 +4200,35 @@ Function 496: DrawMeshInstanced() (4 input parameters) Param[2]: material (type: Material) Param[3]: transforms (type: const Matrix *) Param[4]: instances (type: int) -Function 497: GetMeshBoundingBox() (1 input parameters) +Function 499: GetMeshBoundingBox() (1 input parameters) Name: GetMeshBoundingBox Return type: BoundingBox Description: Compute mesh bounding box limits Param[1]: mesh (type: Mesh) -Function 498: GenMeshTangents() (1 input parameters) +Function 500: GenMeshTangents() (1 input parameters) Name: GenMeshTangents Return type: void Description: Compute mesh tangents Param[1]: mesh (type: Mesh *) -Function 499: ExportMesh() (2 input parameters) +Function 501: ExportMesh() (2 input parameters) Name: ExportMesh Return type: bool Description: Export mesh data to file, returns true on success Param[1]: mesh (type: Mesh) Param[2]: fileName (type: const char *) -Function 500: ExportMeshAsCode() (2 input parameters) +Function 502: ExportMeshAsCode() (2 input parameters) Name: ExportMeshAsCode Return type: bool Description: Export mesh as code file (.h) defining multiple arrays of vertex attributes Param[1]: mesh (type: Mesh) Param[2]: fileName (type: const char *) -Function 501: GenMeshPoly() (2 input parameters) +Function 503: GenMeshPoly() (2 input parameters) Name: GenMeshPoly Return type: Mesh Description: Generate polygonal mesh Param[1]: sides (type: int) Param[2]: radius (type: float) -Function 502: GenMeshPlane() (4 input parameters) +Function 504: GenMeshPlane() (4 input parameters) Name: GenMeshPlane Return type: Mesh Description: Generate plane mesh (with subdivisions) @@ -4225,42 +4236,42 @@ Function 502: GenMeshPlane() (4 input parameters) Param[2]: length (type: float) Param[3]: resX (type: int) Param[4]: resZ (type: int) -Function 503: GenMeshCube() (3 input parameters) +Function 505: GenMeshCube() (3 input parameters) Name: GenMeshCube Return type: Mesh Description: Generate cuboid mesh Param[1]: width (type: float) Param[2]: height (type: float) Param[3]: length (type: float) -Function 504: GenMeshSphere() (3 input parameters) +Function 506: GenMeshSphere() (3 input parameters) Name: GenMeshSphere Return type: Mesh Description: Generate sphere mesh (standard sphere) Param[1]: radius (type: float) Param[2]: rings (type: int) Param[3]: slices (type: int) -Function 505: GenMeshHemiSphere() (3 input parameters) +Function 507: GenMeshHemiSphere() (3 input parameters) Name: GenMeshHemiSphere Return type: Mesh Description: Generate half-sphere mesh (no bottom cap) Param[1]: radius (type: float) Param[2]: rings (type: int) Param[3]: slices (type: int) -Function 506: GenMeshCylinder() (3 input parameters) +Function 508: GenMeshCylinder() (3 input parameters) Name: GenMeshCylinder Return type: Mesh Description: Generate cylinder mesh Param[1]: radius (type: float) Param[2]: height (type: float) Param[3]: slices (type: int) -Function 507: GenMeshCone() (3 input parameters) +Function 509: GenMeshCone() (3 input parameters) Name: GenMeshCone Return type: Mesh Description: Generate cone/pyramid mesh Param[1]: radius (type: float) Param[2]: height (type: float) Param[3]: slices (type: int) -Function 508: GenMeshTorus() (4 input parameters) +Function 510: GenMeshTorus() (4 input parameters) Name: GenMeshTorus Return type: Mesh Description: Generate torus mesh @@ -4268,7 +4279,7 @@ Function 508: GenMeshTorus() (4 input parameters) Param[2]: size (type: float) Param[3]: radSeg (type: int) Param[4]: sides (type: int) -Function 509: GenMeshKnot() (4 input parameters) +Function 511: GenMeshKnot() (4 input parameters) Name: GenMeshKnot Return type: Mesh Description: Generate trefoil knot mesh @@ -4276,91 +4287,91 @@ Function 509: GenMeshKnot() (4 input parameters) Param[2]: size (type: float) Param[3]: radSeg (type: int) Param[4]: sides (type: int) -Function 510: GenMeshHeightmap() (2 input parameters) +Function 512: GenMeshHeightmap() (2 input parameters) Name: GenMeshHeightmap Return type: Mesh Description: Generate heightmap mesh from image data Param[1]: heightmap (type: Image) Param[2]: size (type: Vector3) -Function 511: GenMeshCubicmap() (2 input parameters) +Function 513: GenMeshCubicmap() (2 input parameters) Name: GenMeshCubicmap Return type: Mesh Description: Generate cubes-based map mesh from image data Param[1]: cubicmap (type: Image) Param[2]: cubeSize (type: Vector3) -Function 512: LoadMaterials() (2 input parameters) +Function 514: LoadMaterials() (2 input parameters) Name: LoadMaterials Return type: Material * Description: Load materials from model file Param[1]: fileName (type: const char *) Param[2]: materialCount (type: int *) -Function 513: LoadMaterialDefault() (0 input parameters) +Function 515: LoadMaterialDefault() (0 input parameters) Name: LoadMaterialDefault Return type: Material Description: Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) No input parameters -Function 514: IsMaterialValid() (1 input parameters) +Function 516: IsMaterialValid() (1 input parameters) Name: IsMaterialValid Return type: bool Description: Check if a material is valid (shader assigned, map textures loaded in GPU) Param[1]: material (type: Material) -Function 515: UnloadMaterial() (1 input parameters) +Function 517: UnloadMaterial() (1 input parameters) Name: UnloadMaterial Return type: void Description: Unload material from GPU memory (VRAM) Param[1]: material (type: Material) -Function 516: SetMaterialTexture() (3 input parameters) +Function 518: SetMaterialTexture() (3 input parameters) Name: SetMaterialTexture Return type: void Description: Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) Param[1]: material (type: Material *) Param[2]: mapType (type: int) Param[3]: texture (type: Texture2D) -Function 517: SetModelMeshMaterial() (3 input parameters) +Function 519: SetModelMeshMaterial() (3 input parameters) Name: SetModelMeshMaterial Return type: void Description: Set material for a mesh Param[1]: model (type: Model *) Param[2]: meshId (type: int) Param[3]: materialId (type: int) -Function 518: LoadModelAnimations() (2 input parameters) +Function 520: LoadModelAnimations() (2 input parameters) Name: LoadModelAnimations Return type: ModelAnimation * Description: Load model animations from file Param[1]: fileName (type: const char *) Param[2]: animCount (type: int *) -Function 519: UpdateModelAnimation() (3 input parameters) +Function 521: UpdateModelAnimation() (3 input parameters) Name: UpdateModelAnimation Return type: void Description: Update model animation pose (CPU) Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) Param[3]: frame (type: int) -Function 520: UpdateModelAnimationBones() (3 input parameters) +Function 522: UpdateModelAnimationBones() (3 input parameters) Name: UpdateModelAnimationBones Return type: void Description: Update model animation mesh bone matrices (GPU skinning) Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) Param[3]: frame (type: int) -Function 521: UnloadModelAnimation() (1 input parameters) +Function 523: UnloadModelAnimation() (1 input parameters) Name: UnloadModelAnimation Return type: void Description: Unload animation data Param[1]: anim (type: ModelAnimation) -Function 522: UnloadModelAnimations() (2 input parameters) +Function 524: UnloadModelAnimations() (2 input parameters) Name: UnloadModelAnimations Return type: void Description: Unload animation array data Param[1]: animations (type: ModelAnimation *) Param[2]: animCount (type: int) -Function 523: IsModelAnimationValid() (2 input parameters) +Function 525: IsModelAnimationValid() (2 input parameters) Name: IsModelAnimationValid Return type: bool Description: Check model animation skeleton match Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) -Function 524: CheckCollisionSpheres() (4 input parameters) +Function 526: CheckCollisionSpheres() (4 input parameters) Name: CheckCollisionSpheres Return type: bool Description: Check collision between two spheres @@ -4368,40 +4379,40 @@ Function 524: CheckCollisionSpheres() (4 input parameters) Param[2]: radius1 (type: float) Param[3]: center2 (type: Vector3) Param[4]: radius2 (type: float) -Function 525: CheckCollisionBoxes() (2 input parameters) +Function 527: CheckCollisionBoxes() (2 input parameters) Name: CheckCollisionBoxes Return type: bool Description: Check collision between two bounding boxes Param[1]: box1 (type: BoundingBox) Param[2]: box2 (type: BoundingBox) -Function 526: CheckCollisionBoxSphere() (3 input parameters) +Function 528: CheckCollisionBoxSphere() (3 input parameters) Name: CheckCollisionBoxSphere Return type: bool Description: Check collision between box and sphere Param[1]: box (type: BoundingBox) Param[2]: center (type: Vector3) Param[3]: radius (type: float) -Function 527: GetRayCollisionSphere() (3 input parameters) +Function 529: GetRayCollisionSphere() (3 input parameters) Name: GetRayCollisionSphere Return type: RayCollision Description: Get collision info between ray and sphere Param[1]: ray (type: Ray) Param[2]: center (type: Vector3) Param[3]: radius (type: float) -Function 528: GetRayCollisionBox() (2 input parameters) +Function 530: GetRayCollisionBox() (2 input parameters) Name: GetRayCollisionBox Return type: RayCollision Description: Get collision info between ray and box Param[1]: ray (type: Ray) Param[2]: box (type: BoundingBox) -Function 529: GetRayCollisionMesh() (3 input parameters) +Function 531: GetRayCollisionMesh() (3 input parameters) Name: GetRayCollisionMesh Return type: RayCollision Description: Get collision info between ray and mesh Param[1]: ray (type: Ray) Param[2]: mesh (type: Mesh) Param[3]: transform (type: Matrix) -Function 530: GetRayCollisionTriangle() (4 input parameters) +Function 532: GetRayCollisionTriangle() (4 input parameters) Name: GetRayCollisionTriangle Return type: RayCollision Description: Get collision info between ray and triangle @@ -4409,7 +4420,7 @@ Function 530: GetRayCollisionTriangle() (4 input parameters) Param[2]: p1 (type: Vector3) Param[3]: p2 (type: Vector3) Param[4]: p3 (type: Vector3) -Function 531: GetRayCollisionQuad() (5 input parameters) +Function 533: GetRayCollisionQuad() (5 input parameters) Name: GetRayCollisionQuad Return type: RayCollision Description: Get collision info between ray and quad @@ -4418,158 +4429,158 @@ Function 531: GetRayCollisionQuad() (5 input parameters) Param[3]: p2 (type: Vector3) Param[4]: p3 (type: Vector3) Param[5]: p4 (type: Vector3) -Function 532: InitAudioDevice() (0 input parameters) +Function 534: InitAudioDevice() (0 input parameters) Name: InitAudioDevice Return type: void Description: Initialize audio device and context No input parameters -Function 533: CloseAudioDevice() (0 input parameters) +Function 535: CloseAudioDevice() (0 input parameters) Name: CloseAudioDevice Return type: void Description: Close the audio device and context No input parameters -Function 534: IsAudioDeviceReady() (0 input parameters) +Function 536: IsAudioDeviceReady() (0 input parameters) Name: IsAudioDeviceReady Return type: bool Description: Check if audio device has been initialized successfully No input parameters -Function 535: SetMasterVolume() (1 input parameters) +Function 537: SetMasterVolume() (1 input parameters) Name: SetMasterVolume Return type: void Description: Set master volume (listener) Param[1]: volume (type: float) -Function 536: GetMasterVolume() (0 input parameters) +Function 538: GetMasterVolume() (0 input parameters) Name: GetMasterVolume Return type: float Description: Get master volume (listener) No input parameters -Function 537: LoadWave() (1 input parameters) +Function 539: LoadWave() (1 input parameters) Name: LoadWave Return type: Wave Description: Load wave data from file Param[1]: fileName (type: const char *) -Function 538: LoadWaveFromMemory() (3 input parameters) +Function 540: LoadWaveFromMemory() (3 input parameters) Name: LoadWaveFromMemory Return type: Wave Description: Load wave from memory buffer, fileType refers to extension: i.e. '.wav' Param[1]: fileType (type: const char *) Param[2]: fileData (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 539: IsWaveValid() (1 input parameters) +Function 541: IsWaveValid() (1 input parameters) Name: IsWaveValid Return type: bool Description: Checks if wave data is valid (data loaded and parameters) Param[1]: wave (type: Wave) -Function 540: LoadSound() (1 input parameters) +Function 542: LoadSound() (1 input parameters) Name: LoadSound Return type: Sound Description: Load sound from file Param[1]: fileName (type: const char *) -Function 541: LoadSoundFromWave() (1 input parameters) +Function 543: LoadSoundFromWave() (1 input parameters) Name: LoadSoundFromWave Return type: Sound Description: Load sound from wave data Param[1]: wave (type: Wave) -Function 542: LoadSoundAlias() (1 input parameters) +Function 544: LoadSoundAlias() (1 input parameters) Name: LoadSoundAlias Return type: Sound Description: Create a new sound that shares the same sample data as the source sound, does not own the sound data Param[1]: source (type: Sound) -Function 543: IsSoundValid() (1 input parameters) +Function 545: IsSoundValid() (1 input parameters) Name: IsSoundValid Return type: bool Description: Checks if a sound is valid (data loaded and buffers initialized) Param[1]: sound (type: Sound) -Function 544: UpdateSound() (3 input parameters) +Function 546: UpdateSound() (3 input parameters) Name: UpdateSound Return type: void Description: Update sound buffer with new data (default data format: 32 bit float, stereo) Param[1]: sound (type: Sound) Param[2]: data (type: const void *) Param[3]: sampleCount (type: int) -Function 545: UnloadWave() (1 input parameters) +Function 547: UnloadWave() (1 input parameters) Name: UnloadWave Return type: void Description: Unload wave data Param[1]: wave (type: Wave) -Function 546: UnloadSound() (1 input parameters) +Function 548: UnloadSound() (1 input parameters) Name: UnloadSound Return type: void Description: Unload sound Param[1]: sound (type: Sound) -Function 547: UnloadSoundAlias() (1 input parameters) +Function 549: UnloadSoundAlias() (1 input parameters) Name: UnloadSoundAlias Return type: void Description: Unload a sound alias (does not deallocate sample data) Param[1]: alias (type: Sound) -Function 548: ExportWave() (2 input parameters) +Function 550: ExportWave() (2 input parameters) Name: ExportWave Return type: bool Description: Export wave data to file, returns true on success Param[1]: wave (type: Wave) Param[2]: fileName (type: const char *) -Function 549: ExportWaveAsCode() (2 input parameters) +Function 551: ExportWaveAsCode() (2 input parameters) Name: ExportWaveAsCode Return type: bool Description: Export wave sample data to code (.h), returns true on success Param[1]: wave (type: Wave) Param[2]: fileName (type: const char *) -Function 550: PlaySound() (1 input parameters) +Function 552: PlaySound() (1 input parameters) Name: PlaySound Return type: void Description: Play a sound Param[1]: sound (type: Sound) -Function 551: StopSound() (1 input parameters) +Function 553: StopSound() (1 input parameters) Name: StopSound Return type: void Description: Stop playing a sound Param[1]: sound (type: Sound) -Function 552: PauseSound() (1 input parameters) +Function 554: PauseSound() (1 input parameters) Name: PauseSound Return type: void Description: Pause a sound Param[1]: sound (type: Sound) -Function 553: ResumeSound() (1 input parameters) +Function 555: ResumeSound() (1 input parameters) Name: ResumeSound Return type: void Description: Resume a paused sound Param[1]: sound (type: Sound) -Function 554: IsSoundPlaying() (1 input parameters) +Function 556: IsSoundPlaying() (1 input parameters) Name: IsSoundPlaying Return type: bool Description: Check if a sound is currently playing Param[1]: sound (type: Sound) -Function 555: SetSoundVolume() (2 input parameters) +Function 557: SetSoundVolume() (2 input parameters) Name: SetSoundVolume Return type: void Description: Set volume for a sound (1.0 is max level) Param[1]: sound (type: Sound) Param[2]: volume (type: float) -Function 556: SetSoundPitch() (2 input parameters) +Function 558: SetSoundPitch() (2 input parameters) Name: SetSoundPitch Return type: void Description: Set pitch for a sound (1.0 is base level) Param[1]: sound (type: Sound) Param[2]: pitch (type: float) -Function 557: SetSoundPan() (2 input parameters) +Function 559: SetSoundPan() (2 input parameters) Name: SetSoundPan Return type: void Description: Set pan for a sound (-1.0 left, 0.0 center, 1.0 right) Param[1]: sound (type: Sound) Param[2]: pan (type: float) -Function 558: WaveCopy() (1 input parameters) +Function 560: WaveCopy() (1 input parameters) Name: WaveCopy Return type: Wave Description: Copy a wave to a new wave Param[1]: wave (type: Wave) -Function 559: WaveCrop() (3 input parameters) +Function 561: WaveCrop() (3 input parameters) Name: WaveCrop Return type: void Description: Crop a wave to defined frames range Param[1]: wave (type: Wave *) Param[2]: initFrame (type: int) Param[3]: finalFrame (type: int) -Function 560: WaveFormat() (4 input parameters) +Function 562: WaveFormat() (4 input parameters) Name: WaveFormat Return type: void Description: Convert wave data to desired format @@ -4577,203 +4588,203 @@ Function 560: WaveFormat() (4 input parameters) Param[2]: sampleRate (type: int) Param[3]: sampleSize (type: int) Param[4]: channels (type: int) -Function 561: LoadWaveSamples() (1 input parameters) +Function 563: LoadWaveSamples() (1 input parameters) Name: LoadWaveSamples Return type: float * Description: Load samples data from wave as a 32bit float data array Param[1]: wave (type: Wave) -Function 562: UnloadWaveSamples() (1 input parameters) +Function 564: UnloadWaveSamples() (1 input parameters) Name: UnloadWaveSamples Return type: void Description: Unload samples data loaded with LoadWaveSamples() Param[1]: samples (type: float *) -Function 563: LoadMusicStream() (1 input parameters) +Function 565: LoadMusicStream() (1 input parameters) Name: LoadMusicStream Return type: Music Description: Load music stream from file Param[1]: fileName (type: const char *) -Function 564: LoadMusicStreamFromMemory() (3 input parameters) +Function 566: LoadMusicStreamFromMemory() (3 input parameters) Name: LoadMusicStreamFromMemory Return type: Music Description: Load music stream from data Param[1]: fileType (type: const char *) Param[2]: data (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 565: IsMusicValid() (1 input parameters) +Function 567: IsMusicValid() (1 input parameters) Name: IsMusicValid Return type: bool Description: Checks if a music stream is valid (context and buffers initialized) Param[1]: music (type: Music) -Function 566: UnloadMusicStream() (1 input parameters) +Function 568: UnloadMusicStream() (1 input parameters) Name: UnloadMusicStream Return type: void Description: Unload music stream Param[1]: music (type: Music) -Function 567: PlayMusicStream() (1 input parameters) +Function 569: PlayMusicStream() (1 input parameters) Name: PlayMusicStream Return type: void Description: Start music playing Param[1]: music (type: Music) -Function 568: IsMusicStreamPlaying() (1 input parameters) +Function 570: IsMusicStreamPlaying() (1 input parameters) Name: IsMusicStreamPlaying Return type: bool Description: Check if music is playing Param[1]: music (type: Music) -Function 569: UpdateMusicStream() (1 input parameters) +Function 571: UpdateMusicStream() (1 input parameters) Name: UpdateMusicStream Return type: void Description: Updates buffers for music streaming Param[1]: music (type: Music) -Function 570: StopMusicStream() (1 input parameters) +Function 572: StopMusicStream() (1 input parameters) Name: StopMusicStream Return type: void Description: Stop music playing Param[1]: music (type: Music) -Function 571: PauseMusicStream() (1 input parameters) +Function 573: PauseMusicStream() (1 input parameters) Name: PauseMusicStream Return type: void Description: Pause music playing Param[1]: music (type: Music) -Function 572: ResumeMusicStream() (1 input parameters) +Function 574: ResumeMusicStream() (1 input parameters) Name: ResumeMusicStream Return type: void Description: Resume playing paused music Param[1]: music (type: Music) -Function 573: SeekMusicStream() (2 input parameters) +Function 575: SeekMusicStream() (2 input parameters) Name: SeekMusicStream Return type: void Description: Seek music to a position (in seconds) Param[1]: music (type: Music) Param[2]: position (type: float) -Function 574: SetMusicVolume() (2 input parameters) +Function 576: SetMusicVolume() (2 input parameters) Name: SetMusicVolume Return type: void Description: Set volume for music (1.0 is max level) Param[1]: music (type: Music) Param[2]: volume (type: float) -Function 575: SetMusicPitch() (2 input parameters) +Function 577: SetMusicPitch() (2 input parameters) Name: SetMusicPitch Return type: void Description: Set pitch for a music (1.0 is base level) Param[1]: music (type: Music) Param[2]: pitch (type: float) -Function 576: SetMusicPan() (2 input parameters) +Function 578: SetMusicPan() (2 input parameters) Name: SetMusicPan Return type: void Description: Set pan for a music (-1.0 left, 0.0 center, 1.0 right) Param[1]: music (type: Music) Param[2]: pan (type: float) -Function 577: GetMusicTimeLength() (1 input parameters) +Function 579: GetMusicTimeLength() (1 input parameters) Name: GetMusicTimeLength Return type: float Description: Get music time length (in seconds) Param[1]: music (type: Music) -Function 578: GetMusicTimePlayed() (1 input parameters) +Function 580: GetMusicTimePlayed() (1 input parameters) Name: GetMusicTimePlayed Return type: float Description: Get current music time played (in seconds) Param[1]: music (type: Music) -Function 579: LoadAudioStream() (3 input parameters) +Function 581: LoadAudioStream() (3 input parameters) Name: LoadAudioStream Return type: AudioStream Description: Load audio stream (to stream raw audio pcm data) Param[1]: sampleRate (type: unsigned int) Param[2]: sampleSize (type: unsigned int) Param[3]: channels (type: unsigned int) -Function 580: IsAudioStreamValid() (1 input parameters) +Function 582: IsAudioStreamValid() (1 input parameters) Name: IsAudioStreamValid Return type: bool Description: Checks if an audio stream is valid (buffers initialized) Param[1]: stream (type: AudioStream) -Function 581: UnloadAudioStream() (1 input parameters) +Function 583: UnloadAudioStream() (1 input parameters) Name: UnloadAudioStream Return type: void Description: Unload audio stream and free memory Param[1]: stream (type: AudioStream) -Function 582: UpdateAudioStream() (3 input parameters) +Function 584: UpdateAudioStream() (3 input parameters) Name: UpdateAudioStream Return type: void Description: Update audio stream buffers with data Param[1]: stream (type: AudioStream) Param[2]: data (type: const void *) Param[3]: frameCount (type: int) -Function 583: IsAudioStreamProcessed() (1 input parameters) +Function 585: IsAudioStreamProcessed() (1 input parameters) Name: IsAudioStreamProcessed Return type: bool Description: Check if any audio stream buffers requires refill Param[1]: stream (type: AudioStream) -Function 584: PlayAudioStream() (1 input parameters) +Function 586: PlayAudioStream() (1 input parameters) Name: PlayAudioStream Return type: void Description: Play audio stream Param[1]: stream (type: AudioStream) -Function 585: PauseAudioStream() (1 input parameters) +Function 587: PauseAudioStream() (1 input parameters) Name: PauseAudioStream Return type: void Description: Pause audio stream Param[1]: stream (type: AudioStream) -Function 586: ResumeAudioStream() (1 input parameters) +Function 588: ResumeAudioStream() (1 input parameters) Name: ResumeAudioStream Return type: void Description: Resume audio stream Param[1]: stream (type: AudioStream) -Function 587: IsAudioStreamPlaying() (1 input parameters) +Function 589: IsAudioStreamPlaying() (1 input parameters) Name: IsAudioStreamPlaying Return type: bool Description: Check if audio stream is playing Param[1]: stream (type: AudioStream) -Function 588: StopAudioStream() (1 input parameters) +Function 590: StopAudioStream() (1 input parameters) Name: StopAudioStream Return type: void Description: Stop audio stream Param[1]: stream (type: AudioStream) -Function 589: SetAudioStreamVolume() (2 input parameters) +Function 591: SetAudioStreamVolume() (2 input parameters) Name: SetAudioStreamVolume Return type: void Description: Set volume for audio stream (1.0 is max level) Param[1]: stream (type: AudioStream) Param[2]: volume (type: float) -Function 590: SetAudioStreamPitch() (2 input parameters) +Function 592: SetAudioStreamPitch() (2 input parameters) Name: SetAudioStreamPitch Return type: void Description: Set pitch for audio stream (1.0 is base level) Param[1]: stream (type: AudioStream) Param[2]: pitch (type: float) -Function 591: SetAudioStreamPan() (2 input parameters) +Function 593: SetAudioStreamPan() (2 input parameters) Name: SetAudioStreamPan Return type: void Description: Set pan for audio stream (0.5 is centered) Param[1]: stream (type: AudioStream) Param[2]: pan (type: float) -Function 592: SetAudioStreamBufferSizeDefault() (1 input parameters) +Function 594: SetAudioStreamBufferSizeDefault() (1 input parameters) Name: SetAudioStreamBufferSizeDefault Return type: void Description: Default size for new audio streams Param[1]: size (type: int) -Function 593: SetAudioStreamCallback() (2 input parameters) +Function 595: SetAudioStreamCallback() (2 input parameters) Name: SetAudioStreamCallback Return type: void Description: Audio thread callback to request new data Param[1]: stream (type: AudioStream) Param[2]: callback (type: AudioCallback) -Function 594: AttachAudioStreamProcessor() (2 input parameters) +Function 596: AttachAudioStreamProcessor() (2 input parameters) Name: AttachAudioStreamProcessor Return type: void Description: Attach audio stream processor to stream, receives frames x 2 samples as 'float' (stereo) Param[1]: stream (type: AudioStream) Param[2]: processor (type: AudioCallback) -Function 595: DetachAudioStreamProcessor() (2 input parameters) +Function 597: DetachAudioStreamProcessor() (2 input parameters) Name: DetachAudioStreamProcessor Return type: void Description: Detach audio stream processor from stream Param[1]: stream (type: AudioStream) Param[2]: processor (type: AudioCallback) -Function 596: AttachAudioMixedProcessor() (1 input parameters) +Function 598: AttachAudioMixedProcessor() (1 input parameters) Name: AttachAudioMixedProcessor Return type: void Description: Attach audio stream processor to the entire audio pipeline, receives frames x 2 samples as 'float' (stereo) Param[1]: processor (type: AudioCallback) -Function 597: DetachAudioMixedProcessor() (1 input parameters) +Function 599: DetachAudioMixedProcessor() (1 input parameters) Name: DetachAudioMixedProcessor Return type: void Description: Detach audio stream processor from the entire audio pipeline diff --git a/tools/rlparser/output/raylib_api.xml b/tools/rlparser/output/raylib_api.xml index 3853ac74f..c1f9bc818 100644 --- a/tools/rlparser/output/raylib_api.xml +++ b/tools/rlparser/output/raylib_api.xml @@ -280,8 +280,7 @@ - - + @@ -679,7 +678,7 @@ - + @@ -1137,6 +1136,14 @@ + + + + + + + + From e16467e8b69cfad51ba4d380eef2017cebd9292f Mon Sep 17 00:00:00 2001 From: nate Date: Fri, 23 Jan 2026 22:53:57 +1000 Subject: [PATCH 382/430] Clean up Matrix handling and rl* functions to follow convention (#5505) - Use named fields in rlTranslatef and rlScalef instead of array literals - Label implicit row-major to column-major transpose in MatrixToFloatV - Update rlSetUniformMatrix to use rlMatrixToFloat convention --- src/rlgl.h | 47 +++++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index c604553ee..d3e86cf1f 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -1270,12 +1270,12 @@ void rlLoadIdentity(void) // Multiply the current matrix by a translation matrix void rlTranslatef(float x, float y, float z) { - Matrix matTranslation = { - 1.0f, 0.0f, 0.0f, x, - 0.0f, 1.0f, 0.0f, y, - 0.0f, 0.0f, 1.0f, z, - 0.0f, 0.0f, 0.0f, 1.0f - }; + Matrix matTranslation = rlMatrixIdentity(); + + // Set translation component of matrix + matTranslation.m12 = x; + matTranslation.m13 = y; + matTranslation.m14 = z; // NOTE: We transpose matrix with multiplication order *RLGL.State.currentMatrix = rlMatrixMultiply(matTranslation, *RLGL.State.currentMatrix); @@ -1329,12 +1329,12 @@ void rlRotatef(float angle, float x, float y, float z) // Multiply the current matrix by a scaling matrix void rlScalef(float x, float y, float z) { - Matrix matScale = { - x, 0.0f, 0.0f, 0.0f, - 0.0f, y, 0.0f, 0.0f, - 0.0f, 0.0f, z, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; + Matrix matScale = rlMatrixIdentity(); + + // Set scale component of matrix + matScale.m0 = x; + matScale.m5 = y; + matScale.m10 = z; // NOTE: We transpose matrix with multiplication order *RLGL.State.currentMatrix = rlMatrixMultiply(matScale, *RLGL.State.currentMatrix); @@ -1344,6 +1344,7 @@ void rlScalef(float x, float y, float z) void rlMultMatrixf(const float *matf) { // Matrix creation from array + // Conversion from column-major to row-major memory order Matrix mat = { matf[0], matf[4], matf[8], matf[12], matf[1], matf[5], matf[9], matf[13], matf[2], matf[6], matf[10], matf[14], @@ -4463,13 +4464,7 @@ void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType void rlSetUniformMatrix(int locIndex, Matrix mat) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - float matfloat[16] = { - mat.m0, mat.m1, mat.m2, mat.m3, - mat.m4, mat.m5, mat.m6, mat.m7, - mat.m8, mat.m9, mat.m10, mat.m11, - mat.m12, mat.m13, mat.m14, mat.m15 - }; - glUniformMatrix4fv(locIndex, 1, false, matfloat); + glUniformMatrix4fv(locIndex, 1, false, rlMatrixToFloat(mat)); #endif } @@ -5255,17 +5250,17 @@ static int rlGetPixelDataSize(int width, int height, int format) // Get identity matrix static Matrix rlMatrixIdentity(void) { - Matrix result = { - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; + Matrix matIdentity = { 0 }; + matIdentity.m0 = 1.0f; + matIdentity.m5 = 1.0f; + matIdentity.m10 = 1.0f; + matIdentity.m15 = 1.0f; - return result; + return matIdentity; } #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // Get float array of matrix data +// Explicit conversion to column-major memory layout static rl_float16 rlMatrixToFloatV(Matrix mat) { rl_float16 result = { 0 }; From eda915232d91a48770735fa69bca9e9866dab9f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Dem=C4=8D=C3=A1k?= <71095952+vdemcak@users.noreply.github.com> Date: Fri, 23 Jan 2026 14:13:17 +0100 Subject: [PATCH 383/430] Update miniaudio to v0.11.24 (#5506) --- src/external/miniaudio.h | 879 ++++++++++++++++++++++++--------------- 1 file changed, 537 insertions(+), 342 deletions(-) diff --git a/src/external/miniaudio.h b/src/external/miniaudio.h index b7e7a54dd..24e676bb2 100644 --- a/src/external/miniaudio.h +++ b/src/external/miniaudio.h @@ -1,6 +1,6 @@ /* Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. -miniaudio - v0.11.23 - 2025-09-11 +miniaudio - v0.11.24 - 2026-01-17 David Reid - mackron@gmail.com @@ -3747,7 +3747,7 @@ extern "C" { #define MA_VERSION_MAJOR 0 #define MA_VERSION_MINOR 11 -#define MA_VERSION_REVISION 23 +#define MA_VERSION_REVISION 24 #define MA_VERSION_STRING MA_XSTRINGIFY(MA_VERSION_MAJOR) "." MA_XSTRINGIFY(MA_VERSION_MINOR) "." MA_XSTRINGIFY(MA_VERSION_REVISION) #if defined(_MSC_VER) && !defined(__clang__) @@ -3858,7 +3858,7 @@ typedef ma_uint16 wchar_t; /* Platform/backend detection. */ -#if defined(_WIN32) || defined(__COSMOPOLITAN__) +#if defined(_WIN32) #define MA_WIN32 #if defined(MA_FORCE_UWP) || (defined(WINAPI_FAMILY) && ((defined(WINAPI_FAMILY_PC_APP) && WINAPI_FAMILY == WINAPI_FAMILY_PC_APP) || (defined(WINAPI_FAMILY_PHONE_APP) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP))) #define MA_WIN32_UWP @@ -4182,9 +4182,13 @@ typedef enum MA_CHANNEL_AUX_29 = 49, MA_CHANNEL_AUX_30 = 50, MA_CHANNEL_AUX_31 = 51, + + /* Count. */ + MA_CHANNEL_POSITION_COUNT, + + /* Aliases. */ MA_CHANNEL_LEFT = MA_CHANNEL_FRONT_LEFT, MA_CHANNEL_RIGHT = MA_CHANNEL_FRONT_RIGHT, - MA_CHANNEL_POSITION_COUNT = (MA_CHANNEL_AUX_31 + 1) } _ma_channel_position; /* Do not use `_ma_channel_position` directly. Use `ma_channel` instead. */ typedef enum @@ -6604,16 +6608,12 @@ This section contains the APIs for device playback and capture. Here is where yo #if defined(MA_WIN32_DESKTOP) /* DirectSound and WinMM backends are only supported on desktops. */ #define MA_SUPPORT_DSOUND #define MA_SUPPORT_WINMM - - /* Don't enable JACK here if compiling with Cosmopolitan. It'll be enabled in the Linux section below. */ - #if !defined(__COSMOPOLITAN__) - #define MA_SUPPORT_JACK /* JACK is technically supported on Windows, but I don't know how many people use it in practice... */ - #endif + #define MA_SUPPORT_JACK /* JACK is technically supported on Windows, but I don't know how many people use it in practice... */ #endif #endif #if defined(MA_UNIX) && !defined(MA_ORBIS) && !defined(MA_PROSPERO) #if defined(MA_LINUX) - #if !defined(MA_ANDROID) && !defined(__COSMOPOLITAN__) /* ALSA is not supported on Android. */ + #if !defined(MA_ANDROID) && !defined(MA_EMSCRIPTEN) /* ALSA is not supported on Android. */ #define MA_SUPPORT_ALSA #endif #endif @@ -9675,7 +9675,7 @@ Parameters ---------- pBackends (out, optional) A pointer to the buffer that will receive the enabled backends. Set to NULL to retrieve the backend count. Setting - the capacity of the buffer to `MA_BUFFER_COUNT` will guarantee it's large enough for all backends. + the capacity of the buffer to `MA_BACKEND_COUNT` will guarantee it's large enough for all backends. backendCap (in) The capacity of the `pBackends` buffer. @@ -10520,6 +10520,7 @@ typedef struct ma_decoding_backend_vtable** ppCustomDecodingBackendVTables; ma_uint32 customDecodingBackendCount; void* pCustomDecodingBackendUserData; + ma_resampler_config resampling; } ma_resource_manager_config; MA_API ma_resource_manager_config ma_resource_manager_config_init(void); @@ -10847,6 +10848,7 @@ MA_API ma_result ma_node_graph_read_pcm_frames(ma_node_graph* pNodeGraph, void* MA_API ma_uint32 ma_node_graph_get_channels(const ma_node_graph* pNodeGraph); MA_API ma_uint64 ma_node_graph_get_time(const ma_node_graph* pNodeGraph); MA_API ma_result ma_node_graph_set_time(ma_node_graph* pNodeGraph, ma_uint64 globalTime); +MA_API ma_uint32 ma_node_graph_get_processing_size_in_frames(const ma_node_graph* pNodeGraph); @@ -11154,6 +11156,7 @@ typedef struct ma_bool8 isPitchDisabled; /* Pitching can be explicitly disabled with MA_SOUND_FLAG_NO_PITCH to optimize processing. */ ma_bool8 isSpatializationDisabled; /* Spatialization can be explicitly disabled with MA_SOUND_FLAG_NO_SPATIALIZATION. */ ma_uint8 pinnedListenerIndex; /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */ + ma_resampler_config resampling; } ma_engine_node_config; MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_engine_node_type type, ma_uint32 flags); @@ -11168,7 +11171,7 @@ typedef struct ma_uint32 volumeSmoothTimeInPCMFrames; ma_mono_expansion_mode monoExpansionMode; ma_fader fader; - ma_linear_resampler resampler; /* For pitch shift. */ + ma_resampler resampler; /* For pitch shift. */ ma_spatializer spatializer; ma_panner panner; ma_gainer volumeGainer; /* This will only be used if volumeSmoothTimeInPCMFrames is > 0. */ @@ -11224,6 +11227,7 @@ typedef struct ma_uint64 loopPointEndInPCMFrames; ma_sound_end_proc endCallback; /* Fired when the sound reaches the end. Will be fired from the audio thread. Do not restart, uninitialize or otherwise change the state of the sound from here. Instead fire an event or set a variable to indicate to a different thread to change the start of the sound. Will not be fired in response to a scheduled stop with ma_sound_set_stop_time_*(). */ void* pEndCallbackUserData; + ma_resampler_config pitchResampling; #ifndef MA_NO_RESOURCE_MANAGER ma_resource_manager_pipeline_notifications initNotifications; #endif @@ -11242,7 +11246,10 @@ struct ma_sound MA_ATOMIC(4, ma_bool32) atEnd; ma_sound_end_proc endCallback; void* pEndCallbackUserData; - ma_bool8 ownsDataSource; + float* pProcessingCache; /* Will be null if pDataSource is null. */ + ma_uint32 processingCacheFramesRemaining; + ma_uint32 processingCacheCap; + ma_bool8 ownsDataSource; /* We're declaring a resource manager data source object here to save us a malloc when loading a @@ -11300,6 +11307,8 @@ typedef struct ma_vfs* pResourceManagerVFS; /* A pointer to a pre-allocated VFS object to use with the resource manager. This is ignored if pResourceManager is not NULL. */ ma_engine_process_proc onProcess; /* Fired at the end of each call to ma_engine_read_pcm_frames(). For engine's that manage their own internal device (the default configuration), this will be fired from the audio thread, and you do not need to call ma_engine_read_pcm_frames() manually in order to trigger this. */ void* pProcessUserData; /* User data that's passed into onProcess. */ + ma_resampler_config resourceManagerResampling; /* The resampling config to use with the resource manager. */ + ma_resampler_config pitchResampling; /* The resampling config for the pitch and Doppler effects. You will typically want this to be a fast resampler. For high quality stuff, it's recommended that you pre-resample. */ } ma_engine_config; MA_API ma_engine_config ma_engine_config_init(void); @@ -11329,6 +11338,7 @@ struct ma_engine ma_mono_expansion_mode monoExpansionMode; ma_engine_process_proc onProcess; void* pProcessUserData; + ma_resampler_config pitchResamplingConfig; }; MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine); @@ -11389,8 +11399,12 @@ MA_API ma_engine* ma_sound_get_engine(const ma_sound* pSound); MA_API ma_data_source* ma_sound_get_data_source(const ma_sound* pSound); MA_API ma_result ma_sound_start(ma_sound* pSound); MA_API ma_result ma_sound_stop(ma_sound* pSound); -MA_API ma_result ma_sound_stop_with_fade_in_pcm_frames(ma_sound* pSound, ma_uint64 fadeLengthInFrames); /* Will overwrite any scheduled stop and fade. */ -MA_API ma_result ma_sound_stop_with_fade_in_milliseconds(ma_sound* pSound, ma_uint64 fadeLengthInFrames); /* Will overwrite any scheduled stop and fade. */ +MA_API ma_result ma_sound_stop_with_fade_in_pcm_frames(ma_sound* pSound, ma_uint64 fadeLengthInFrames); /* Will overwrite any scheduled stop and fade. If you want to restart the sound, first reset it with `ma_sound_reset_stop_time_and_fade()`. There are plans to make this less awkward in the future. */ +MA_API ma_result ma_sound_stop_with_fade_in_milliseconds(ma_sound* pSound, ma_uint64 fadeLengthInFrames); /* Will overwrite any scheduled stop and fade. If you want to restart the sound, first reset it with `ma_sound_reset_stop_time_and_fade()`. There are plans to make this less awkward in the future. */ +MA_API void ma_sound_reset_start_time(ma_sound* pSound); +MA_API void ma_sound_reset_stop_time(ma_sound* pSound); +MA_API void ma_sound_reset_fade(ma_sound* pSound); +MA_API void ma_sound_reset_stop_time_and_fade(ma_sound* pSound); /* Resets fades and scheduled stop time. Does not seek back to the start. */ MA_API void ma_sound_set_volume(ma_sound* pSound, float volume); MA_API float ma_sound_get_volume(const ma_sound* pSound); MA_API void ma_sound_set_pan(ma_sound* pSound, float pan); @@ -11643,7 +11657,7 @@ IMPLEMENTATION #endif /* Intrinsics Support */ -#if (defined(MA_X64) || defined(MA_X86)) && !defined(__COSMOPOLITAN__) +#if defined(MA_X64) || defined(MA_X86) #if defined(_MSC_VER) && !defined(__clang__) /* MSVC. */ #if _MSC_VER >= 1400 && !defined(MA_NO_SSE2) /* 2005 */ @@ -12080,7 +12094,7 @@ static MA_INLINE unsigned int ma_disable_denormals(void) } #elif defined(MA_X86) || defined(MA_X64) { - #if defined(MA_SUPPORT_SSE2) && defined(__SSE2__) && !(defined(__TINYC__) || defined(__WATCOMC__) || defined(__COSMOPOLITAN__)) /* <-- Add compilers that lack support for _mm_getcsr() and _mm_setcsr() to this list. */ + #if defined(MA_SUPPORT_SSE2) && defined(__SSE2__) && !(defined(__TINYC__) || defined(__WATCOMC__)) /* <-- Add compilers that lack support for _mm_getcsr() and _mm_setcsr() to this list. */ { prevState = _mm_getcsr(); _mm_setcsr(prevState | MA_MM_DENORMALS_ZERO_MASK | MA_MM_FLUSH_ZERO_MASK); @@ -12120,7 +12134,7 @@ static MA_INLINE void ma_restore_denormals(unsigned int prevState) } #elif defined(MA_X86) || defined(MA_X64) { - #if defined(MA_SUPPORT_SSE2) && defined(__SSE2__) && !(defined(__TINYC__) || defined(__WATCOMC__) || defined(__COSMOPOLITAN__)) /* <-- Add compilers that lack support for _mm_getcsr() and _mm_setcsr() to this list. */ + #if defined(MA_SUPPORT_SSE2) && defined(__SSE2__) && !(defined(__TINYC__) || defined(__WATCOMC__)) /* <-- Add compilers that lack support for _mm_getcsr() and _mm_setcsr() to this list. */ { _mm_setcsr(prevState); } @@ -14241,6 +14255,29 @@ typedef int ma_atomic_memory_order; #define ma_atomic_memory_order_release 4 #define ma_atomic_memory_order_acq_rel 5 #define ma_atomic_memory_order_seq_cst 6 + #define MA_ATOMIC_MSVC_ARM_INTRINSIC_NORETURN(dst, src, order, intrin, ma_atomicType, msvcType) \ + switch (order) \ + { \ + case ma_atomic_memory_order_relaxed: \ + { \ + intrin##_nf((volatile msvcType*)dst, (msvcType)src); \ + } break; \ + case ma_atomic_memory_order_consume: \ + case ma_atomic_memory_order_acquire: \ + { \ + intrin##_acq((volatile msvcType*)dst, (msvcType)src); \ + } break; \ + case ma_atomic_memory_order_release: \ + { \ + intrin##_rel((volatile msvcType*)dst, (msvcType)src); \ + } break; \ + case ma_atomic_memory_order_acq_rel: \ + case ma_atomic_memory_order_seq_cst: \ + default: \ + { \ + intrin((volatile msvcType*)dst, (msvcType)src); \ + } break; \ + } #define MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, src, order, intrin, ma_atomicType, msvcType) \ ma_atomicType result; \ switch (order) \ @@ -14284,7 +14321,7 @@ typedef int ma_atomic_memory_order; { #if defined(MA_ARM) { - MA_ATOMIC_MSVC_ARM_INTRINSIC(dst, 0, order, _InterlockedExchange, ma_atomic_flag, long); + MA_ATOMIC_MSVC_ARM_INTRINSIC_NORETURN(dst, 0, order, _InterlockedExchange, ma_atomic_flag, long); } #else { @@ -17593,7 +17630,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority int priorityStep = (priorityMax - priorityMin) / 7; /* 7 = number of priorities supported by miniaudio. */ struct sched_param sched; - if (pthread_attr_getschedparam(&attr, &sched) == 0) { + if (priorityMin != -1 && priorityMax != -1 && pthread_attr_getschedparam(&attr, &sched) == 0) { if (priority == ma_thread_priority_idle) { sched.sched_priority = priorityMin; } else if (priority == ma_thread_priority_realtime) { @@ -20050,7 +20087,7 @@ Timing struct timespec newTime; clock_gettime(MA_CLOCK_ID, &newTime); - pTimer->counter = (newTime.tv_sec * 1000000000) + newTime.tv_nsec; + pTimer->counter = ((ma_int64)newTime.tv_sec * 1000000000) + newTime.tv_nsec; } static MA_INLINE double ma_timer_get_time_in_seconds(ma_timer* pTimer) @@ -20061,7 +20098,7 @@ Timing struct timespec newTime; clock_gettime(MA_CLOCK_ID, &newTime); - newTimeCounter = (newTime.tv_sec * 1000000000) + newTime.tv_nsec; + newTimeCounter = ((ma_uint64)newTime.tv_sec * 1000000000) + newTime.tv_nsec; oldTimeCounter = pTimer->counter; return (newTimeCounter - oldTimeCounter) / 1000000000.0; @@ -20072,7 +20109,7 @@ Timing struct timeval newTime; gettimeofday(&newTime, NULL); - pTimer->counter = (newTime.tv_sec * 1000000) + newTime.tv_usec; + pTimer->counter = ((ma_int64)newTime.tv_sec * 1000000) + newTime.tv_usec; } static MA_INLINE double ma_timer_get_time_in_seconds(ma_timer* pTimer) @@ -20083,7 +20120,7 @@ Timing struct timeval newTime; gettimeofday(&newTime, NULL); - newTimeCounter = (newTime.tv_sec * 1000000) + newTime.tv_usec; + newTimeCounter = ((ma_uint64)newTime.tv_sec * 1000000) + newTime.tv_usec; oldTimeCounter = pTimer->counter; return (newTimeCounter - oldTimeCounter) / 1000000.0; @@ -31205,6 +31242,7 @@ static ma_result ma_init_pa_mainloop_and_pa_context__pulse(ma_context* pContext, result = ma_result_from_pulse(((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)((ma_pa_context*)pPulseContext, pServerName, (tryAutoSpawn) ? MA_PA_CONTEXT_NOFLAGS : MA_PA_CONTEXT_NOAUTOSPAWN, NULL)); if (result != MA_SUCCESS) { ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context."); + ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)(pPulseContext)); ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pMainLoop)); return result; } @@ -31213,6 +31251,7 @@ static ma_result ma_init_pa_mainloop_and_pa_context__pulse(ma_context* pContext, result = ma_wait_for_pa_context_to_connect__pulse(pContext, pMainLoop, pPulseContext); if (result != MA_SUCCESS) { ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Waiting for connection failed."); + ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)(pPulseContext)); ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pMainLoop)); return result; } @@ -41724,8 +41763,11 @@ static EM_BOOL ma_audio_worklet_process_callback__webaudio(int inputCount, const frameCount = pDevice->capture.internalPeriodSizeInFrames; } + /* + If this is called by the device has not yet been started we need to return early, making sure we output silence to + the output buffer. + */ if (ma_device_get_state(pDevice) != ma_device_state_started) { - /* Fill the output buffer with zero to avoid a noise sound */ for (int i = 0; i < outputCount; i += 1) { MA_ZERO_MEMORY(pOutputs[i].data, pOutputs[i].numberOfChannels * frameCount * sizeof(float)); } @@ -41747,7 +41789,9 @@ static EM_BOOL ma_audio_worklet_process_callback__webaudio(int inputCount, const if (outputCount > 0) { /* If it's a capture-only device, we'll need to output silence. */ if (pDevice->type == ma_device_type_capture) { - MA_ZERO_MEMORY(pOutputs[0].data, frameCount * pDevice->playback.internalChannels * sizeof(float)); + for (int i = 0; i < outputCount; i += 1) { + MA_ZERO_MEMORY(pOutputs[i].data, pOutputs[i].numberOfChannels * frameCount * sizeof(float)); + } } else { ma_device_process_pcm_frames_playback__webaudio(pDevice, frameCount, pDevice->webaudio.pIntermediaryBuffer); @@ -41757,6 +41801,14 @@ static EM_BOOL ma_audio_worklet_process_callback__webaudio(int inputCount, const pOutputs[0].data[frameCount*iChannel + iFrame] = pDevice->webaudio.pIntermediaryBuffer[iFrame*pDevice->playback.internalChannels + iChannel]; } } + + /* + Just above we output data to the first output buffer. Here we just make sure we're putting silence into any + remaining output buffers. + */ + for (int i = 1; i < outputCount; i += 1) { /* <-- Note that the counter starts at 1 instead of 0. */ + MA_ZERO_MEMORY(pOutputs[i].data, pOutputs[i].numberOfChannels * frameCount * sizeof(float)); + } } } @@ -42237,8 +42289,8 @@ static ma_result ma_context_uninit__webaudio(ma_context* pContext) /* Remove the global miniaudio object from window if there are no more references to it. */ EM_ASM({ if (typeof(window.miniaudio) !== 'undefined') { - miniaudio.unlock_event_types.map(function(event_type) { - document.removeEventListener(event_type, miniaudio.unlock, true); + window.miniaudio.unlock_event_types.map(function(event_type) { + document.removeEventListener(event_type, window.miniaudio.unlock, true); }); window.miniaudio.referenceCount -= 1; @@ -50827,15 +50879,15 @@ static /*__attribute__((noinline))*/ ma_result ma_gainer_process_pcm_frames_inte a += d; } } + + pFramesOut = ma_offset_ptr(pFramesOut, interpolatedFrameCount * sizeof(float)); + pFramesIn = ma_offset_ptr(pFramesIn, interpolatedFrameCount * sizeof(float)); } + frameCount -= interpolatedFrameCount; + /* Make sure the timer is updated. */ pGainer->t = (ma_uint32)ma_min(pGainer->t + interpolatedFrameCount, pGainer->config.smoothTimeInFrames); - - /* Adjust our arguments so the next part can work normally. */ - frameCount -= interpolatedFrameCount; - pFramesOut = ma_offset_ptr(pFramesOut, interpolatedFrameCount * sizeof(float)); - pFramesIn = ma_offset_ptr(pFramesIn, interpolatedFrameCount * sizeof(float)); } /* All we need to do here is apply the new gains using an optimized path. */ @@ -52263,13 +52315,16 @@ static float ma_calculate_angular_gain(ma_vec3f dirA, ma_vec3f dirB, float coneI MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, ma_spatializer_listener* pListener, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) { - ma_channel* pChannelMapIn = pSpatializer->pChannelMapIn; - ma_channel* pChannelMapOut = pListener->config.pChannelMapOut; + ma_channel* pChannelMapIn; + ma_channel* pChannelMapOut; - if (pSpatializer == NULL) { + if (pSpatializer == NULL || pListener == NULL) { return MA_INVALID_ARGS; } + pChannelMapIn = pSpatializer->pChannelMapIn; + pChannelMapOut = pListener->config.pChannelMapOut; + /* If we're not spatializing we need to run an optimized path. */ if (ma_atomic_load_i32(&pSpatializer->attenuationModel) == ma_attenuation_model_none) { if (ma_spatializer_listener_is_enabled(pListener)) { @@ -52314,23 +52369,17 @@ MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, We'll need the listener velocity for doppler pitch calculations. The speed of sound is defined by the listener, so we'll grab that here too. */ - if (pListener != NULL) { - listenerVel = ma_spatializer_listener_get_velocity(pListener); - speedOfSound = pListener->config.speedOfSound; - } else { - listenerVel = ma_vec3f_init_3f(0, 0, 0); - speedOfSound = MA_DEFAULT_SPEED_OF_SOUND; - } + listenerVel = ma_spatializer_listener_get_velocity(pListener); + speedOfSound = pListener->config.speedOfSound; - if (pListener == NULL || ma_spatializer_get_positioning(pSpatializer) == ma_positioning_relative) { - /* There's no listener or we're using relative positioning. */ + if (ma_spatializer_get_positioning(pSpatializer) == ma_positioning_relative) { relativePos = ma_spatializer_get_position(pSpatializer); relativeDir = ma_spatializer_get_direction(pSpatializer); } else { /* - We've found a listener and we're using absolute positioning. We need to transform the - sound's position and direction so that it's relative to listener. Later on we'll use - this for determining the factors to apply to each channel to apply the panning effect. + We're using absolute positioning. We need to transform the sound's position and + direction so that it's relative to listener. Later on we'll use this for determining + the factors to apply to each channel to apply the panning effect. */ ma_spatializer_get_relative_position_and_direction(pSpatializer, pListener, &relativePos, &relativeDir); } @@ -54365,7 +54414,7 @@ static ma_bool32 ma_is_spatial_channel_position(ma_channel channelPosition) return MA_FALSE; } - if (channelPosition >= MA_CHANNEL_AUX_0 && channelPosition <= MA_CHANNEL_AUX_31) { + if (channelPosition >= MA_CHANNEL_AUX_0) { return MA_FALSE; } @@ -61653,7 +61702,6 @@ static ma_result ma_default_vfs_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_inf if (result == MA_NOT_IMPLEMENTED) { /* Not implemented. Fall back to seek/tell/seek. */ - ma_result result; ma_int64 cursor; ma_int64 sizeInBytes; @@ -61861,6 +61909,8 @@ Decoding and Encoding Headers. These are auto-generated from a tool. **************************************************************************************************************************************************************/ #if !defined(MA_NO_WAV) && (!defined(MA_NO_DECODING) || !defined(MA_NO_ENCODING)) +#define MA_HAS_WAV + /* dr_wav_h begin */ #ifndef ma_dr_wav_h #define ma_dr_wav_h @@ -61871,7 +61921,7 @@ extern "C" { #define MA_DR_WAV_XSTRINGIFY(x) MA_DR_WAV_STRINGIFY(x) #define MA_DR_WAV_VERSION_MAJOR 0 #define MA_DR_WAV_VERSION_MINOR 14 -#define MA_DR_WAV_VERSION_REVISION 1 +#define MA_DR_WAV_VERSION_REVISION 4 #define MA_DR_WAV_VERSION_STRING MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MAJOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MINOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_REVISION) #include #define MA_DR_WAVE_FORMAT_PCM 0x1 @@ -62294,6 +62344,8 @@ MA_API ma_bool32 ma_dr_wav_fourcc_equal(const ma_uint8* a, const char* b); #endif /* MA_NO_WAV */ #if !defined(MA_NO_FLAC) && !defined(MA_NO_DECODING) +#define MA_HAS_FLAC + /* dr_flac_h begin */ #ifndef ma_dr_flac_h #define ma_dr_flac_h @@ -62304,7 +62356,7 @@ extern "C" { #define MA_DR_FLAC_XSTRINGIFY(x) MA_DR_FLAC_STRINGIFY(x) #define MA_DR_FLAC_VERSION_MAJOR 0 #define MA_DR_FLAC_VERSION_MINOR 13 -#define MA_DR_FLAC_VERSION_REVISION 1 +#define MA_DR_FLAC_VERSION_REVISION 3 #define MA_DR_FLAC_VERSION_STRING MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_MAJOR) "." MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_MINOR) "." MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_REVISION) #include #if defined(_MSC_VER) && _MSC_VER >= 1700 @@ -62392,8 +62444,9 @@ typedef struct typedef struct { ma_uint32 type; - const void* pRawData; ma_uint32 rawDataSize; + ma_uint64 rawDataOffset; + const void* pRawData; union { ma_dr_flac_streaminfo streaminfo; @@ -62439,6 +62492,7 @@ typedef struct ma_uint32 colorDepth; ma_uint32 indexColorCount; ma_uint32 pictureDataSize; + ma_uint64 pictureDataOffset; const ma_uint8* pPictureData; } picture; } data; @@ -62584,6 +62638,8 @@ MA_API ma_bool32 ma_dr_flac_next_cuesheet_track(ma_dr_flac_cuesheet_track_iterat #endif /* MA_NO_FLAC */ #if !defined(MA_NO_MP3) && !defined(MA_NO_DECODING) +#define MA_HAS_MP3 + #ifndef MA_DR_MP3_NO_SIMD #if (defined(MA_NO_NEON) && defined(MA_ARM)) || (defined(MA_NO_SSE2) && (defined(MA_X86) || defined(MA_X64))) #define MA_DR_MP3_NO_SIMD @@ -62600,22 +62656,47 @@ extern "C" { #define MA_DR_MP3_XSTRINGIFY(x) MA_DR_MP3_STRINGIFY(x) #define MA_DR_MP3_VERSION_MAJOR 0 #define MA_DR_MP3_VERSION_MINOR 7 -#define MA_DR_MP3_VERSION_REVISION 1 +#define MA_DR_MP3_VERSION_REVISION 3 #define MA_DR_MP3_VERSION_STRING MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_MAJOR) "." MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_MINOR) "." MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_REVISION) #include #define MA_DR_MP3_MAX_PCM_FRAMES_PER_MP3_FRAME 1152 #define MA_DR_MP3_MAX_SAMPLES_PER_FRAME (MA_DR_MP3_MAX_PCM_FRAMES_PER_MP3_FRAME*2) MA_API void ma_dr_mp3_version(ma_uint32* pMajor, ma_uint32* pMinor, ma_uint32* pRevision); MA_API const char* ma_dr_mp3_version_string(void); +#define MA_DR_MP3_MAX_BITRESERVOIR_BYTES 511 +#define MA_DR_MP3_MAX_FREE_FORMAT_FRAME_SIZE 2304 +#define MA_DR_MP3_MAX_L3_FRAME_PAYLOAD_BYTES MA_DR_MP3_MAX_FREE_FORMAT_FRAME_SIZE typedef struct { int frame_bytes, channels, sample_rate, layer, bitrate_kbps; } ma_dr_mp3dec_frame_info; typedef struct +{ + const ma_uint8 *buf; + int pos, limit; +} ma_dr_mp3_bs; +typedef struct +{ + const ma_uint8 *sfbtab; + ma_uint16 part_23_length, big_values, scalefac_compress; + ma_uint8 global_gain, block_type, mixed_block_flag, n_long_sfb, n_short_sfb; + ma_uint8 table_select[3], region_count[3], subblock_gain[3]; + ma_uint8 preflag, scalefac_scale, count1_table, scfsi; +} ma_dr_mp3_L3_gr_info; +typedef struct +{ + ma_dr_mp3_bs bs; + ma_uint8 maindata[MA_DR_MP3_MAX_BITRESERVOIR_BYTES + MA_DR_MP3_MAX_L3_FRAME_PAYLOAD_BYTES]; + ma_dr_mp3_L3_gr_info gr_info[4]; + float grbuf[2][576], scf[40], syn[18 + 15][2*32]; + ma_uint8 ist_pos[2][39]; +} ma_dr_mp3dec_scratch; +typedef struct { float mdct_overlap[2][9*32], qmf_state[15*2*32]; int reserv, free_format_bytes; ma_uint8 header[4], reserv_buf[511]; + ma_dr_mp3dec_scratch scratch; } ma_dr_mp3dec; MA_API void ma_dr_mp3dec_init(ma_dr_mp3dec *dec); MA_API int ma_dr_mp3dec_decode_frame(ma_dr_mp3dec *dec, const ma_uint8 *mp3, int mp3_bytes, void *pcm, ma_dr_mp3dec_frame_info *info); @@ -63179,7 +63260,6 @@ static ma_result ma_decoder_init_custom_from_memory__internal(const void* pData, /* WAV */ #ifdef ma_dr_wav_h -#define MA_HAS_WAV typedef struct { @@ -63885,7 +63965,6 @@ static ma_result ma_decoder_init_wav_from_memory__internal(const void* pData, si /* FLAC */ #ifdef ma_dr_flac_h -#define MA_HAS_FLAC typedef struct { @@ -64529,7 +64608,6 @@ static ma_result ma_decoder_init_flac_from_memory__internal(const void* pData, s /* MP3 */ #ifdef ma_dr_mp3_h -#define MA_HAS_MP3 typedef struct { @@ -66207,11 +66285,9 @@ static ma_result ma_decoder_init__internal(ma_decoder_read_proc onRead, ma_decod We use trial and error to open a decoder. We prioritize custom decoders so that if they implement the same encoding format they take priority over the built-in decoders. */ + result = ma_decoder_init_custom__internal(pConfig, pDecoder); if (result != MA_SUCCESS) { - result = ma_decoder_init_custom__internal(pConfig, pDecoder); - if (result != MA_SUCCESS) { - onSeek(pDecoder, 0, ma_seek_origin_start); - } + onSeek(pDecoder, 0, ma_seek_origin_start); } /* @@ -66475,14 +66551,6 @@ MA_API ma_result ma_decoder_init_memory(const void* pData, size_t dataSize, cons /* Initialization was successful. Finish up. */ result = ma_decoder__postinit(&config, pDecoder); if (result != MA_SUCCESS) { - /* - The backend was initialized successfully, but for some reason post-initialization failed. This is most likely - due to an out of memory error. We're going to abort with an error here and not try to recover. - */ - if (pDecoder->pBackendVTable != NULL && pDecoder->pBackendVTable->onUninit != NULL) { - pDecoder->pBackendVTable->onUninit(pDecoder->pBackendUserData, &pDecoder->pBackend, &pDecoder->allocationCallbacks); - } - return result; } } else { @@ -66783,11 +66851,9 @@ MA_API ma_result ma_decoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const We use trial and error to open a decoder. We prioritize custom decoders so that if they implement the same encoding format they take priority over the built-in decoders. */ + result = ma_decoder_init_custom__internal(&config, pDecoder); if (result != MA_SUCCESS) { - result = ma_decoder_init_custom__internal(&config, pDecoder); - if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); - } + ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); } /* @@ -66916,11 +66982,9 @@ MA_API ma_result ma_decoder_init_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, c We use trial and error to open a decoder. We prioritize custom decoders so that if they implement the same encoding format they take priority over the built-in decoders. */ + result = ma_decoder_init_custom__internal(&config, pDecoder); if (result != MA_SUCCESS) { - result = ma_decoder_init_custom__internal(&config, pDecoder); - if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); - } + ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); } /* @@ -67102,14 +67166,6 @@ MA_API ma_result ma_decoder_init_file(const char* pFilePath, const ma_decoder_co /* Initialization was successful. Finish up. */ result = ma_decoder__postinit(&config, pDecoder); if (result != MA_SUCCESS) { - /* - The backend was initialized successfully, but for some reason post-initialization failed. This is most likely - due to an out of memory error. We're going to abort with an error here and not try to recover. - */ - if (pDecoder->pBackendVTable != NULL && pDecoder->pBackendVTable->onUninit != NULL) { - pDecoder->pBackendVTable->onUninit(pDecoder->pBackendUserData, &pDecoder->pBackend, &pDecoder->allocationCallbacks); - } - return result; } } else { @@ -67252,14 +67308,6 @@ MA_API ma_result ma_decoder_init_file_w(const wchar_t* pFilePath, const ma_decod /* Initialization was successful. Finish up. */ result = ma_decoder__postinit(&config, pDecoder); if (result != MA_SUCCESS) { - /* - The backend was initialized successfully, but for some reason post-initialization failed. This is most likely - due to an out of memory error. We're going to abort with an error here and not try to recover. - */ - if (pDecoder->pBackendVTable != NULL && pDecoder->pBackendVTable->onUninit != NULL) { - pDecoder->pBackendVTable->onUninit(pDecoder->pBackendUserData, &pDecoder->pBackend, &pDecoder->allocationCallbacks); - } - return result; } } else { @@ -69905,6 +69953,7 @@ MA_API ma_resource_manager_config ma_resource_manager_config_init(void) config.decodedSampleRate = 0; config.jobThreadCount = 1; /* A single miniaudio-managed job thread by default. */ config.jobQueueCapacity = MA_JOB_TYPE_RESOURCE_MANAGER_QUEUE_CAPACITY; + config.resampling = ma_resampler_config_init(ma_format_unknown, 0, 0, 0, ma_resample_algorithm_linear); /* Format/channels/rate doesn't matter here. */ /* Flags. */ config.flags = 0; @@ -70158,6 +70207,7 @@ static ma_decoder_config ma_resource_manager__init_decoder_config(ma_resource_ma config.ppCustomBackendVTables = pResourceManager->config.ppCustomDecodingBackendVTables; config.customBackendCount = pResourceManager->config.customDecodingBackendCount; config.pCustomBackendUserData = pResourceManager->config.pCustomDecodingBackendUserData; + config.resampling = pResourceManager->config.resampling; return config; } @@ -71483,13 +71533,13 @@ MA_API ma_result ma_resource_manager_data_buffer_get_data_format(ma_resource_man MA_API ma_result ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pCursor) { - /* We cannot be using the data source after it's been uninitialized. */ - MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); - if (pDataBuffer == NULL || pCursor == NULL) { return MA_INVALID_ARGS; } + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + *pCursor = 0; switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode)) @@ -71523,13 +71573,13 @@ MA_API ma_result ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(ma_res MA_API ma_result ma_resource_manager_data_buffer_get_length_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pLength) { - /* We cannot be using the data source after it's been uninitialized. */ - MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); - if (pDataBuffer == NULL || pLength == NULL) { return MA_INVALID_ARGS; } + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) { return MA_BUSY; /* Still loading. */ } @@ -72884,8 +72934,6 @@ static ma_result ma_job_process__resource_manager__free_data_buffer_node(ma_job* return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ } - ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode); - /* The event needs to be signalled last. */ if (pJob->data.resourceManager.freeDataBufferNode.pDoneNotification != NULL) { ma_async_notification_signal(pJob->data.resourceManager.freeDataBufferNode.pDoneNotification); @@ -72896,6 +72944,9 @@ static ma_result ma_job_process__resource_manager__free_data_buffer_node(ma_job* } ma_atomic_fetch_add_32(&pDataBufferNode->executionPointer, 1); + + ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode); + return MA_SUCCESS; } @@ -73768,6 +73819,15 @@ MA_API ma_result ma_node_graph_set_time(ma_node_graph* pNodeGraph, ma_uint64 glo return ma_node_set_time(&pNodeGraph->endpoint, globalTime); /* Global time is just the local time of the endpoint. */ } +MA_API ma_uint32 ma_node_graph_get_processing_size_in_frames(const ma_node_graph* pNodeGraph) +{ + if (pNodeGraph == NULL) { + return 0; + } + + return pNodeGraph->processingSizeInFrames; +} + #define MA_NODE_OUTPUT_BUS_FLAG_HAS_READ 0x01 /* Whether or not this bus ready to read more data. Only used on nodes with multiple output buses. */ @@ -74927,12 +74987,12 @@ MA_API ma_node_state ma_node_get_state_by_time_range(const ma_node* pNode, ma_ui its start time not having been reached yet. Also, the stop time may have also been reached in which case it'll be considered stopped. */ - if (ma_node_get_state_time(pNode, ma_node_state_started) > globalTimeBeg) { - return ma_node_state_stopped; /* Start time has not yet been reached. */ + if (ma_node_get_state_time(pNode, ma_node_state_stopped) < globalTimeBeg) { + return ma_node_state_stopped; /* End time is before the start of the range. */ } - if (ma_node_get_state_time(pNode, ma_node_state_stopped) <= globalTimeEnd) { - return ma_node_state_stopped; /* Stop time has been reached. */ + if (ma_node_get_state_time(pNode, ma_node_state_started) > globalTimeEnd) { + return ma_node_state_stopped; /* Start time is after the end of the range. */ } /* Getting here means the node is marked as started and is within its start/stop times. */ @@ -75012,14 +75072,14 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde return MA_INVALID_ARGS; /* Invalid output bus index. */ } + globalTimeBeg = globalTime; + globalTimeEnd = globalTime + frameCount; + /* Don't do anything if we're in a stopped state. */ - if (ma_node_get_state_by_time_range(pNode, globalTime, globalTime + frameCount) != ma_node_state_started) { + if (ma_node_get_state_by_time_range(pNode, globalTimeBeg, globalTimeEnd) != ma_node_state_started) { return MA_SUCCESS; /* We're in a stopped state. This is not an error - we just need to not read anything. */ } - - globalTimeBeg = globalTime; - globalTimeEnd = globalTime + frameCount; startTime = ma_node_get_state_time(pNode, ma_node_state_started); stopTime = ma_node_get_state_time(pNode, ma_node_state_stopped); @@ -75032,11 +75092,16 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde therefore need to offset it by a number of frames to accommodate. The same thing applies for the stop time. */ - timeOffsetBeg = (globalTimeBeg < startTime) ? (ma_uint32)(globalTimeEnd - startTime) : 0; + timeOffsetBeg = (globalTimeBeg < startTime) ? (ma_uint32)(startTime - globalTimeBeg) : 0; timeOffsetEnd = (globalTimeEnd > stopTime) ? (ma_uint32)(globalTimeEnd - stopTime) : 0; /* Trim based on the start offset. We need to silence the start of the buffer. */ if (timeOffsetBeg > 0) { + MA_ASSERT(timeOffsetBeg <= frameCount); + if (timeOffsetBeg > frameCount) { + timeOffsetBeg = frameCount; + } + ma_silence_pcm_frames(pFramesOut, timeOffsetBeg, ma_format_f32, ma_node_get_output_channels(pNode, outputBusIndex)); pFramesOut += timeOffsetBeg * ma_node_get_output_channels(pNode, outputBusIndex); frameCount -= timeOffsetBeg; @@ -75044,6 +75109,11 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde /* Trim based on the end offset. We don't need to silence the tail section because we'll just have a reduced value written to pFramesRead. */ if (timeOffsetEnd > 0) { + MA_ASSERT(timeOffsetEnd <= frameCount); + if (timeOffsetEnd > frameCount) { + timeOffsetEnd = frameCount; + } + frameCount -= timeOffsetEnd; } @@ -76458,12 +76528,20 @@ static void ma_sound_set_at_end(ma_sound* pSound, ma_bool32 atEnd) MA_ASSERT(pSound != NULL); ma_atomic_exchange_32(&pSound->atEnd, atEnd); + /* + When this function is called the state of the sound will not yet be in a stopped state. This makes it confusing + because an end callback will intuitively expect ma_sound_is_playing() to return false from inside the callback. + I'm therefore no longer firing the callback here and will instead fire it manually in the *next* processing step + when the state should be set to stopped as expected. + */ + #if 0 /* Fire any callbacks or events. */ if (atEnd) { if (pSound->endCallback != NULL) { pSound->endCallback(pSound->pEndCallbackUserData, pSound); } } + #endif } static ma_bool32 ma_sound_get_at_end(const ma_sound* pSound) @@ -76483,6 +76561,7 @@ MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_e config.isPitchDisabled = (flags & MA_SOUND_FLAG_NO_PITCH) != 0; config.isSpatializationDisabled = (flags & MA_SOUND_FLAG_NO_SPATIALIZATION) != 0; config.monoExpansionMode = pEngine->monoExpansionMode; + config.resampling = pEngine->pitchResamplingConfig; return config; } @@ -76509,7 +76588,7 @@ static void ma_engine_node_update_pitch_if_required(ma_engine_node* pEngineNode) if (isUpdateRequired) { float basePitch = (float)pEngineNode->sampleRate / ma_engine_get_sample_rate(pEngineNode->pEngine); - ma_linear_resampler_set_rate_ratio(&pEngineNode->resampler, basePitch * pEngineNode->oldPitch * pEngineNode->oldDopplerPitch); + ma_resampler_set_rate_ratio(&pEngineNode->resampler, basePitch * pEngineNode->oldPitch * pEngineNode->oldDopplerPitch); } } @@ -76528,22 +76607,6 @@ static ma_bool32 ma_engine_node_is_spatialization_enabled(const ma_engine_node* return !ma_atomic_load_explicit_32(&pEngineNode->isSpatializationDisabled, ma_atomic_memory_order_acquire); } -static ma_uint64 ma_engine_node_get_required_input_frame_count(const ma_engine_node* pEngineNode, ma_uint64 outputFrameCount) -{ - ma_uint64 inputFrameCount = 0; - - if (ma_engine_node_is_pitching_enabled(pEngineNode)) { - ma_result result = ma_linear_resampler_get_required_input_frame_count(&pEngineNode->resampler, outputFrameCount, &inputFrameCount); - if (result != MA_SUCCESS) { - inputFrameCount = 0; - } - } else { - inputFrameCount = outputFrameCount; /* No resampling, so 1:1. */ - } - - return inputFrameCount; -} - static ma_result ma_engine_node_set_volume(ma_engine_node* pEngineNode, float volume) { if (pEngineNode == NULL) { @@ -76685,7 +76748,7 @@ static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNo ma_uint64 resampleFrameCountIn = framesAvailableIn; ma_uint64 resampleFrameCountOut = framesAvailableOut; - ma_linear_resampler_process_pcm_frames(&pEngineNode->resampler, pRunningFramesIn, &resampleFrameCountIn, pWorkingBuffer, &resampleFrameCountOut); + ma_resampler_process_pcm_frames(&pEngineNode->resampler, pRunningFramesIn, &resampleFrameCountIn, pWorkingBuffer, &resampleFrameCountOut); isWorkingBufferValid = MA_TRUE; framesJustProcessedIn = (ma_uint32)resampleFrameCountIn; @@ -76809,6 +76872,11 @@ static void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, const float /* If we're marked at the end we need to stop the sound and do nothing. */ if (ma_sound_at_end(pSound)) { ma_sound_stop(pSound); + + if (pSound->endCallback != NULL) { + pSound->endCallback(pSound->pEndCallbackUserData, pSound); + } + *pFrameCountOut = 0; return; } @@ -76846,55 +76914,74 @@ static void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, const float /* Keep reading until we've read as much as was requested or we reach the end of the data source. */ while (totalFramesRead < frameCount) { ma_uint32 framesRemaining = frameCount - totalFramesRead; - ma_uint32 framesToRead; ma_uint64 framesJustRead; ma_uint32 frameCountIn; ma_uint32 frameCountOut; const float* pRunningFramesIn; float* pRunningFramesOut; - /* - The first thing we need to do is read into the temporary buffer. We can calculate exactly - how many input frames we'll need after resampling. - */ - framesToRead = (ma_uint32)ma_engine_node_get_required_input_frame_count(&pSound->engineNode, framesRemaining); - if (framesToRead > tempCapInFrames) { - framesToRead = tempCapInFrames; - } + /* If there's any input frames sitting in the cache get those processed first. */ + if (pSound->processingCacheFramesRemaining > 0) { + pRunningFramesIn = pSound->pProcessingCache; + frameCountIn = pSound->processingCacheFramesRemaining; - result = ma_data_source_read_pcm_frames(pSound->pDataSource, temp, framesToRead, &framesJustRead); + pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesRead, ma_node_get_output_channels(pNode, 0)); + frameCountOut = framesRemaining; - /* If we reached the end of the sound we'll want to mark it as at the end and stop it. This should never be returned for looping sounds. */ - if (result == MA_AT_END) { - ma_sound_set_at_end(pSound, MA_TRUE); /* This will be set to false in ma_sound_start(). */ - } - - pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesRead, ma_node_get_output_channels(pNode, 0)); - - frameCountIn = (ma_uint32)framesJustRead; - frameCountOut = framesRemaining; - - /* Convert if necessary. */ - if (dataSourceFormat == ma_format_f32) { - /* Fast path. No data conversion necessary. */ - pRunningFramesIn = (float*)temp; ma_engine_node_process_pcm_frames__general(&pSound->engineNode, &pRunningFramesIn, &frameCountIn, &pRunningFramesOut, &frameCountOut); + + MA_ASSERT(frameCountIn <= pSound->processingCacheFramesRemaining); + pSound->processingCacheFramesRemaining -= frameCountIn; + + /* Move any remaining data in the cache down. */ + if (pSound->processingCacheFramesRemaining > 0) { + MA_MOVE_MEMORY(pSound->pProcessingCache, ma_offset_pcm_frames_ptr_f32(pSound->pProcessingCache, frameCountIn, dataSourceChannels), pSound->processingCacheFramesRemaining * ma_get_bytes_per_frame(ma_format_f32, dataSourceChannels)); + } + + totalFramesRead += (ma_uint32)frameCountOut; /* Safe cast. */ + + if (result != MA_SUCCESS || ma_sound_at_end(pSound)) { + break; /* Might have reached the end. */ + } } else { - /* Slow path. Need to do sample format conversion to f32. If we give the f32 buffer the same count as the first temp buffer, we're guaranteed it'll be large enough. */ - float tempf32[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* Do not do `MA_DATA_CONVERTER_STACK_BUFFER_SIZE/sizeof(float)` here like we've done in other places. */ - ma_convert_pcm_frames_format(tempf32, ma_format_f32, temp, dataSourceFormat, framesJustRead, dataSourceChannels, ma_dither_mode_none); + /* Getting here means there's nothing in the cache. Read more data from the data source. */ + if (dataSourceFormat == ma_format_f32) { + /* Fast path. No conversion to f32 necessary. */ + result = ma_data_source_read_pcm_frames(pSound->pDataSource, pSound->pProcessingCache, pSound->processingCacheCap, &framesJustRead); + } else { + /* Slow path. Need to convert to f32. */ + ma_uint64 totalFramesConverted = 0; - /* Now that we have our samples in f32 format we can process like normal. */ - pRunningFramesIn = tempf32; - ma_engine_node_process_pcm_frames__general(&pSound->engineNode, &pRunningFramesIn, &frameCountIn, &pRunningFramesOut, &frameCountOut); - } + while (totalFramesConverted < pSound->processingCacheCap) { + ma_uint64 framesConverted; + ma_uint32 framesToConvertThisIteration = pSound->processingCacheCap - (ma_uint32)totalFramesConverted; + if (framesToConvertThisIteration > tempCapInFrames) { + framesToConvertThisIteration = tempCapInFrames; + } - /* We should have processed all of our input frames since we calculated the required number of input frames at the top. */ - MA_ASSERT(frameCountIn == framesJustRead); - totalFramesRead += (ma_uint32)frameCountOut; /* Safe cast. */ + result = ma_data_source_read_pcm_frames(pSound->pDataSource, temp, framesToConvertThisIteration, &framesConverted); + if (result != MA_SUCCESS) { + break; + } - if (result != MA_SUCCESS || ma_sound_at_end(pSound)) { - break; /* Might have reached the end. */ + ma_convert_pcm_frames_format(ma_offset_pcm_frames_ptr_f32(pSound->pProcessingCache, totalFramesConverted, dataSourceChannels), ma_format_f32, temp, dataSourceFormat, framesConverted, dataSourceChannels, ma_dither_mode_none); + totalFramesConverted += framesConverted; + } + + framesJustRead = totalFramesConverted; + } + + MA_ASSERT(framesJustRead <= pSound->processingCacheCap); + pSound->processingCacheFramesRemaining = (ma_uint32)framesJustRead; + + /* If we reached the end of the sound we'll want to mark it as at the end and stop it. This should never be returned for looping sounds. */ + if (result == MA_AT_END) { + ma_sound_set_at_end(pSound, MA_TRUE); /* This will be set to false in ma_sound_start(). */ + } + + if (result != MA_SUCCESS || ma_sound_at_end(pSound)) { + break; + } } } } @@ -76917,25 +77004,6 @@ static void ma_engine_node_process_pcm_frames__group(ma_node* pNode, const float ma_engine_node_process_pcm_frames__general((ma_engine_node*)pNode, ppFramesIn, pFrameCountIn, ppFramesOut, pFrameCountOut); } -static ma_result ma_engine_node_get_required_input_frame_count__group(ma_node* pNode, ma_uint32 outputFrameCount, ma_uint32* pInputFrameCount) -{ - ma_uint64 inputFrameCount; - - MA_ASSERT(pInputFrameCount != NULL); - - /* Our pitch will affect this calculation. We need to update it. */ - ma_engine_node_update_pitch_if_required((ma_engine_node*)pNode); - - inputFrameCount = ma_engine_node_get_required_input_frame_count((ma_engine_node*)pNode, outputFrameCount); - if (inputFrameCount > 0xFFFFFFFF) { - inputFrameCount = 0xFFFFFFFF; /* Will never happen because miniaudio will only ever process in relatively small chunks. */ - } - - *pInputFrameCount = (ma_uint32)inputFrameCount; - - return MA_SUCCESS; -} - static ma_node_vtable g_ma_engine_node_vtable__sound = { @@ -76949,7 +77017,7 @@ static ma_node_vtable g_ma_engine_node_vtable__sound = static ma_node_vtable g_ma_engine_node_vtable__group = { ma_engine_node_process_pcm_frames__group, - ma_engine_node_get_required_input_frame_count__group, + NULL, /* onGetRequiredInputFrameCount */ 1, /* Groups have one input bus. */ 1, /* Groups have one output bus. */ MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES /* The engine node does resampling so should let miniaudio know about it. */ @@ -76995,9 +77063,10 @@ static ma_result ma_engine_node_get_heap_layout(const ma_engine_node_config* pCo ma_result result; size_t tempHeapSize; ma_node_config baseNodeConfig; - ma_linear_resampler_config resamplerConfig; + ma_resampler_config resamplerConfig; ma_spatializer_config spatializerConfig; ma_gainer_config gainerConfig; + ma_uint32 sampleRate; ma_uint32 channelsIn; ma_uint32 channelsOut; ma_channel defaultStereoChannelMap[2] = {MA_CHANNEL_SIDE_LEFT, MA_CHANNEL_SIDE_RIGHT}; /* <-- Consistent with the default channel map of a stereo listener. Means channel conversion can run on a fast path. */ @@ -77016,6 +77085,7 @@ static ma_result ma_engine_node_get_heap_layout(const ma_engine_node_config* pCo pHeapLayout->sizeInBytes = 0; + sampleRate = (pConfig->sampleRate > 0) ? pConfig->sampleRate : ma_engine_get_sample_rate(pConfig->pEngine); channelsIn = (pConfig->channelsIn != 0) ? pConfig->channelsIn : ma_engine_get_channels(pConfig->pEngine); channelsOut = (pConfig->channelsOut != 0) ? pConfig->channelsOut : ma_engine_get_channels(pConfig->pEngine); @@ -77035,10 +77105,13 @@ static ma_result ma_engine_node_get_heap_layout(const ma_engine_node_config* pCo /* Resmapler. */ - resamplerConfig = ma_linear_resampler_config_init(ma_format_f32, channelsIn, 1, 1); /* Input and output sample rates don't affect the calculation of the heap size. */ - resamplerConfig.lpfOrder = 0; + resamplerConfig = pConfig->resampling; + resamplerConfig.format = ma_format_f32; + resamplerConfig.channels = channelsIn; + resamplerConfig.sampleRateIn = sampleRate; + resamplerConfig.sampleRateOut = ma_engine_get_sample_rate(pConfig->pEngine); - result = ma_linear_resampler_get_heap_size(&resamplerConfig, &tempHeapSize); + result = ma_resampler_get_heap_size(&resamplerConfig, &tempHeapSize); if (result != MA_SUCCESS) { return result; /* Failed to retrieve the size of the heap for the resampler. */ } @@ -77106,7 +77179,7 @@ MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* p ma_result result; ma_engine_node_heap_layout heapLayout; ma_node_config baseNodeConfig; - ma_linear_resampler_config resamplerConfig; + ma_resampler_config resamplerConfig; ma_fader_config faderConfig; ma_spatializer_config spatializerConfig; ma_panner_config pannerConfig; @@ -77181,10 +77254,13 @@ MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* p */ /* We'll always do resampling first. */ - resamplerConfig = ma_linear_resampler_config_init(ma_format_f32, baseNodeConfig.pInputChannels[0], pEngineNode->sampleRate, ma_engine_get_sample_rate(pEngineNode->pEngine)); - resamplerConfig.lpfOrder = 0; /* <-- Need to disable low-pass filtering for pitch shifting for now because there's cases where the biquads are becoming unstable. Need to figure out a better fix for this. */ + resamplerConfig = pConfig->resampling; + resamplerConfig.format = ma_format_f32; + resamplerConfig.channels = baseNodeConfig.pInputChannels[0]; + resamplerConfig.sampleRateIn = pEngineNode->sampleRate; + resamplerConfig.sampleRateOut = ma_engine_get_sample_rate(pEngineNode->pEngine); - result = ma_linear_resampler_init_preallocated(&resamplerConfig, ma_offset_ptr(pHeap, heapLayout.resamplerOffset), &pEngineNode->resampler); + result = ma_resampler_init_preallocated(&resamplerConfig, ma_offset_ptr(pHeap, heapLayout.resamplerOffset), &pEngineNode->resampler); if (result != MA_SUCCESS) { goto error1; } @@ -77243,7 +77319,7 @@ MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* p /* No need for allocation callbacks here because we use a preallocated heap. */ error3: ma_spatializer_uninit(&pEngineNode->spatializer, NULL); -error2: ma_linear_resampler_uninit(&pEngineNode->resampler, NULL); +error2: ma_resampler_uninit(&pEngineNode->resampler, NULL); error1: ma_node_uninit(&pEngineNode->baseNode, NULL); error0: return result; } @@ -77292,7 +77368,7 @@ MA_API void ma_engine_node_uninit(ma_engine_node* pEngineNode, const ma_allocati } ma_spatializer_uninit(&pEngineNode->spatializer, pAllocationCallbacks); - ma_linear_resampler_uninit(&pEngineNode->resampler, pAllocationCallbacks); + ma_resampler_uninit(&pEngineNode->resampler, pAllocationCallbacks); /* Free the heap last. */ if (pEngineNode->_ownsHeap) { @@ -77314,8 +77390,12 @@ MA_API ma_sound_config ma_sound_config_init_2(ma_engine* pEngine) if (pEngine != NULL) { config.monoExpansionMode = pEngine->monoExpansionMode; + config.pitchResampling = pEngine->pitchResamplingConfig; } else { config.monoExpansionMode = ma_mono_expansion_mode_default; + + config.pitchResampling = ma_resampler_config_init(ma_format_f32, 0, 0, 0, ma_resample_algorithm_linear); + config.pitchResampling.linear.lpfOrder = 0; /* <-- Need to disable low-pass filtering for pitch shifting for now because there's cases where the biquads are becoming unstable. Need to figure out a better fix for this. */ } config.rangeEndInPCMFrames = ~((ma_uint64)0); @@ -77337,8 +77417,12 @@ MA_API ma_sound_group_config ma_sound_group_config_init_2(ma_engine* pEngine) if (pEngine != NULL) { config.monoExpansionMode = pEngine->monoExpansionMode; + config.pitchResampling = pEngine->pitchResamplingConfig; } else { config.monoExpansionMode = ma_mono_expansion_mode_default; + + config.pitchResampling = ma_resampler_config_init(ma_format_f32, 0, 0, 0, ma_resample_algorithm_linear); + config.pitchResampling.linear.lpfOrder = 0; /* <-- Need to disable low-pass filtering for pitch shifting for now because there's cases where the biquads are becoming unstable. Need to figure out a better fix for this. */ } return config; @@ -77350,8 +77434,12 @@ MA_API ma_engine_config ma_engine_config_init(void) ma_engine_config config; MA_ZERO_OBJECT(&config); - config.listenerCount = 1; /* Always want at least one listener. */ - config.monoExpansionMode = ma_mono_expansion_mode_default; + config.listenerCount = 1; /* Always want at least one listener. */ + config.monoExpansionMode = ma_mono_expansion_mode_default; + config.resourceManagerResampling = ma_resampler_config_init(ma_format_unknown, 0, 0, 0, ma_resample_algorithm_linear); + + config.pitchResampling = ma_resampler_config_init(ma_format_f32, 0, 0, 0, ma_resample_algorithm_linear); + config.pitchResampling.linear.lpfOrder = 0; /* <-- Need to disable low-pass filtering for pitch shifting for now because there's cases where the biquads are becoming unstable. Need to figure out a better fix for this. */ return config; } @@ -77432,6 +77520,7 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng pEngine->defaultVolumeSmoothTimeInPCMFrames = engineConfig.defaultVolumeSmoothTimeInPCMFrames; pEngine->onProcess = engineConfig.onProcess; pEngine->pProcessUserData = engineConfig.pProcessUserData; + pEngine->pitchResamplingConfig = engineConfig.pitchResampling; ma_allocation_callbacks_init_copy(&pEngine->allocationCallbacks, &engineConfig.allocationCallbacks); #if !defined(MA_NO_RESOURCE_MANAGER) @@ -77614,6 +77703,7 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng resourceManagerConfig.decodedSampleRate = ma_engine_get_sample_rate(pEngine); ma_allocation_callbacks_init_copy(&resourceManagerConfig.allocationCallbacks, &pEngine->allocationCallbacks); resourceManagerConfig.pVFS = engineConfig.pResourceManagerVFS; + resourceManagerConfig.resampling = engineConfig.resourceManagerResampling; /* The Emscripten build cannot use threads unless it's targeting pthreads. */ #if defined(MA_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__) @@ -78339,6 +78429,25 @@ static ma_result ma_sound_init_from_data_source_internal(ma_engine* pEngine, con } + /* + When pulling data from a data source we need a processing cache to hold onto unprocessed input data from the data source + after doing resampling. + */ + if (pSound->pDataSource != NULL) { + pSound->processingCacheFramesRemaining = 0; + pSound->processingCacheCap = ma_node_graph_get_processing_size_in_frames(&pEngine->nodeGraph); + if (pSound->processingCacheCap == 0) { + pSound->processingCacheCap = 512; + } + + pSound->pProcessingCache = (float*)ma_calloc(pSound->processingCacheCap * ma_get_bytes_per_frame(ma_format_f32, engineNodeConfig.channelsIn), &pEngine->allocationCallbacks); + if (pSound->pProcessingCache == NULL) { + ma_engine_node_uninit(&pSound->engineNode, &pEngine->allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + } + + /* Apply initial range and looping state to the data source if applicable. */ if (pConfig->rangeBegInPCMFrames != 0 || pConfig->rangeEndInPCMFrames != ~((ma_uint64)0)) { ma_data_source_set_range_in_pcm_frames(ma_sound_get_data_source(pSound), pConfig->rangeBegInPCMFrames, pConfig->rangeEndInPCMFrames); @@ -78576,6 +78685,11 @@ MA_API void ma_sound_uninit(ma_sound* pSound) */ ma_engine_node_uninit(&pSound->engineNode, &pSound->engineNode.pEngine->allocationCallbacks); + if (pSound->pProcessingCache != NULL) { + ma_free(pSound->pProcessingCache, &pSound->engineNode.pEngine->allocationCallbacks); + pSound->pProcessingCache = NULL; + } + /* Once the sound is detached from the group we can guarantee that it won't be referenced by the mixer thread which means it's safe for us to destroy the data source. */ #ifndef MA_NO_RESOURCE_MANAGER if (pSound->ownsDataSource) { @@ -78671,6 +78785,27 @@ MA_API ma_result ma_sound_stop_with_fade_in_milliseconds(ma_sound* pSound, ma_ui return ma_sound_stop_with_fade_in_pcm_frames(pSound, (fadeLengthInMilliseconds * sampleRate) / 1000); } +MA_API void ma_sound_reset_start_time(ma_sound* pSound) +{ + ma_sound_set_start_time_in_pcm_frames(pSound, 0); +} + +MA_API void ma_sound_reset_stop_time(ma_sound* pSound) +{ + ma_sound_set_stop_time_in_pcm_frames(pSound, ~(ma_uint64)0); +} + +MA_API void ma_sound_reset_fade(ma_sound* pSound) +{ + ma_sound_set_fade_in_pcm_frames(pSound, 0, 1, 0); +} + +MA_API void ma_sound_reset_stop_time_and_fade(ma_sound* pSound) +{ + ma_sound_reset_stop_time(pSound); + ma_sound_reset_fade(pSound); +} + MA_API void ma_sound_set_volume(ma_sound* pSound, float volume) { if (pSound == NULL) { @@ -79322,7 +79457,7 @@ MA_API ma_result ma_sound_get_data_format(const ma_sound* pSound, ma_format* pFo } if (pSampleRate != NULL) { - *pSampleRate = pSound->engineNode.resampler.config.sampleRateIn; + *pSampleRate = pSound->engineNode.resampler.sampleRateIn; } if (pChannelMap != NULL) { @@ -82386,7 +82521,6 @@ MA_PRIVATE ma_bool32 ma_dr_wav__on_seek_memory(void* pUserData, int offset, ma_d ma_dr_wav* pWav = (ma_dr_wav*)pUserData; ma_int64 newCursor; MA_DR_WAV_ASSERT(pWav != NULL); - newCursor = pWav->memoryStream.currentReadPos; if (origin == MA_DR_WAV_SEEK_SET) { newCursor = 0; } else if (origin == MA_DR_WAV_SEEK_CUR) { @@ -82440,7 +82574,6 @@ MA_PRIVATE ma_bool32 ma_dr_wav__on_seek_memory_write(void* pUserData, int offset ma_dr_wav* pWav = (ma_dr_wav*)pUserData; ma_int64 newCursor; MA_DR_WAV_ASSERT(pWav != NULL); - newCursor = pWav->memoryStreamWrite.currentWritePos; if (origin == MA_DR_WAV_SEEK_SET) { newCursor = 0; } else if (origin == MA_DR_WAV_SEEK_CUR) { @@ -82449,7 +82582,7 @@ MA_PRIVATE ma_bool32 ma_dr_wav__on_seek_memory_write(void* pUserData, int offset newCursor = (ma_int64)pWav->memoryStreamWrite.dataSize; } else { MA_DR_WAV_ASSERT(!"Invalid seek origin"); - return MA_INVALID_ARGS; + return MA_FALSE; } newCursor += offset; if (newCursor < 0) { @@ -82950,7 +83083,7 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__msadpcm(ma_dr_wav* pWav, ma_ pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][0]; pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.cachedFrameCount = 2; - if (pWav->msadpcm.predictor[0] >= ma_dr_wav_countof(coeff1Table)) { + if (pWav->msadpcm.predictor[0] >= ma_dr_wav_countof(coeff1Table) || pWav->msadpcm.predictor[0] >= ma_dr_wav_countof(coeff2Table)) { return totalFramesRead; } } else { @@ -82972,7 +83105,8 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__msadpcm(ma_dr_wav* pWav, ma_ pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[1][1]; pWav->msadpcm.cachedFrameCount = 2; - if (pWav->msadpcm.predictor[0] >= ma_dr_wav_countof(coeff1Table) || pWav->msadpcm.predictor[1] >= ma_dr_wav_countof(coeff2Table)) { + if (pWav->msadpcm.predictor[0] >= ma_dr_wav_countof(coeff1Table) || pWav->msadpcm.predictor[0] >= ma_dr_wav_countof(coeff2Table) || + pWav->msadpcm.predictor[1] >= ma_dr_wav_countof(coeff1Table) || pWav->msadpcm.predictor[1] >= ma_dr_wav_countof(coeff2Table)) { return totalFramesRead; } } @@ -83009,6 +83143,9 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__msadpcm(ma_dr_wav* pWav, ma_ if (pWav->channels == 1) { ma_int32 newSample0; ma_int32 newSample1; + if (pWav->msadpcm.predictor[0] >= ma_dr_wav_countof(coeff1Table) || pWav->msadpcm.predictor[0] >= ma_dr_wav_countof(coeff2Table)) { + return totalFramesRead; + } newSample0 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; newSample0 += nibble0 * pWav->msadpcm.delta[0]; newSample0 = ma_dr_wav_clamp(newSample0, -32768, 32767); @@ -83033,6 +83170,9 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__msadpcm(ma_dr_wav* pWav, ma_ } else { ma_int32 newSample0; ma_int32 newSample1; + if (pWav->msadpcm.predictor[0] >= ma_dr_wav_countof(coeff1Table) || pWav->msadpcm.predictor[0] >= ma_dr_wav_countof(coeff2Table)) { + return totalFramesRead; + } newSample0 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; newSample0 += nibble0 * pWav->msadpcm.delta[0]; newSample0 = ma_dr_wav_clamp(newSample0, -32768, 32767); @@ -83042,6 +83182,9 @@ MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__msadpcm(ma_dr_wav* pWav, ma_ } pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.prevFrames[0][1] = newSample0; + if (pWav->msadpcm.predictor[1] >= ma_dr_wav_countof(coeff1Table) || pWav->msadpcm.predictor[1] >= ma_dr_wav_countof(coeff2Table)) { + return totalFramesRead; + } newSample1 = ((pWav->msadpcm.prevFrames[1][1] * coeff1Table[pWav->msadpcm.predictor[1]]) + (pWav->msadpcm.prevFrames[1][0] * coeff2Table[pWav->msadpcm.predictor[1]])) >> 8; newSample1 += nibble1 * pWav->msadpcm.delta[1]; newSample1 = ma_dr_wav_clamp(newSample1, -32768, 32767); @@ -84286,6 +84429,10 @@ MA_PRIVATE ma_int16* ma_dr_wav__read_pcm_frames_and_close_s16(ma_dr_wav* pWav, u ma_int16* pSampleData; ma_uint64 framesRead; MA_DR_WAV_ASSERT(pWav != NULL); + if (pWav->channels == 0 || pWav->totalPCMFrameCount > MA_SIZE_MAX / pWav->channels / sizeof(ma_int16)) { + ma_dr_wav_uninit(pWav); + return NULL; + } sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(ma_int16); if (sampleDataSize > MA_SIZE_MAX) { ma_dr_wav_uninit(pWav); @@ -84320,6 +84467,10 @@ MA_PRIVATE float* ma_dr_wav__read_pcm_frames_and_close_f32(ma_dr_wav* pWav, unsi float* pSampleData; ma_uint64 framesRead; MA_DR_WAV_ASSERT(pWav != NULL); + if (pWav->channels == 0 || pWav->totalPCMFrameCount > MA_SIZE_MAX / pWav->channels / sizeof(float)) { + ma_dr_wav_uninit(pWav); + return NULL; + } sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(float); if (sampleDataSize > MA_SIZE_MAX) { ma_dr_wav_uninit(pWav); @@ -84354,6 +84505,10 @@ MA_PRIVATE ma_int32* ma_dr_wav__read_pcm_frames_and_close_s32(ma_dr_wav* pWav, u ma_int32* pSampleData; ma_uint64 framesRead; MA_DR_WAV_ASSERT(pWav != NULL); + if (pWav->channels == 0 || pWav->totalPCMFrameCount > MA_SIZE_MAX / pWav->channels / sizeof(ma_int32)) { + ma_dr_wav_uninit(pWav); + return NULL; + } sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(ma_int32); if (sampleDataSize > MA_SIZE_MAX) { ma_dr_wav_uninit(pWav); @@ -85736,7 +85891,7 @@ static MA_INLINE ma_uint32 ma_dr_flac__clz_lzcnt(ma_dr_flac_cache_t x) { ma_uint64 r; __asm__ __volatile__ ( - "lzcnt{ %1, %0| %0, %1}" : "=r"(r) : "r"(x) : "cc" + "rep; bsr{q %1, %0| %0, %1}" : "=r"(r) : "r"(x) : "cc" ); return (ma_uint32)r; } @@ -85744,11 +85899,11 @@ static MA_INLINE ma_uint32 ma_dr_flac__clz_lzcnt(ma_dr_flac_cache_t x) { ma_uint32 r; __asm__ __volatile__ ( - "lzcnt{l %1, %0| %0, %1}" : "=r"(r) : "r"(x) : "cc" + "rep; bsr{l %1, %0| %0, %1}" : "=r"(r) : "r"(x) : "cc" ); return r; } - #elif defined(MA_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) && !defined(__ARM_ARCH_6M__) && !defined(MA_64BIT) + #elif defined(MA_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) && !defined(__ARM_ARCH_6M__) && !(defined(__thumb__) && !defined(__thumb2__)) && !defined(MA_64BIT) { unsigned int r; __asm__ __volatile__ ( @@ -88502,8 +88657,9 @@ static ma_bool32 ma_dr_flac__read_and_decode_metadata(ma_dr_flac_read_proc onRea } runningFilePos += 4; metadata.type = blockType; - metadata.pRawData = NULL; metadata.rawDataSize = 0; + metadata.rawDataOffset = runningFilePos; + metadata.pRawData = NULL; switch (blockType) { case MA_DR_FLAC_METADATA_BLOCK_TYPE_APPLICATION: @@ -88703,46 +88859,117 @@ static ma_bool32 ma_dr_flac__read_and_decode_metadata(ma_dr_flac_read_proc onRea return MA_FALSE; } if (onMeta) { - void* pRawData; - const char* pRunningData; - const char* pRunningDataEnd; - pRawData = ma_dr_flac__malloc_from_callbacks(blockSize, pAllocationCallbacks); - if (pRawData == NULL) { + ma_bool32 result = MA_TRUE; + ma_uint32 blockSizeRemaining = blockSize; + char* pMime = NULL; + char* pDescription = NULL; + void* pPictureData = NULL; + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.type, 4) != 4) { + result = MA_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.type = ma_dr_flac__be2host_32(metadata.data.picture.type); + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.mimeLength, 4) != 4) { + result = MA_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.mimeLength = ma_dr_flac__be2host_32(metadata.data.picture.mimeLength); + pMime = (char*)ma_dr_flac__malloc_from_callbacks(metadata.data.picture.mimeLength + 1, pAllocationCallbacks); + if (pMime == NULL) { + result = MA_FALSE; + goto done_flac; + } + if (blockSizeRemaining < metadata.data.picture.mimeLength || onRead(pUserData, pMime, metadata.data.picture.mimeLength) != metadata.data.picture.mimeLength) { + result = MA_FALSE; + goto done_flac; + } + blockSizeRemaining -= metadata.data.picture.mimeLength; + pMime[metadata.data.picture.mimeLength] = '\0'; + metadata.data.picture.mime = (const char*)pMime; + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.descriptionLength, 4) != 4) { + result = MA_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.descriptionLength = ma_dr_flac__be2host_32(metadata.data.picture.descriptionLength); + pDescription = (char*)ma_dr_flac__malloc_from_callbacks(metadata.data.picture.descriptionLength + 1, pAllocationCallbacks); + if (pDescription == NULL) { + result = MA_FALSE; + goto done_flac; + } + if (blockSizeRemaining < metadata.data.picture.descriptionLength || onRead(pUserData, pDescription, metadata.data.picture.descriptionLength) != metadata.data.picture.descriptionLength) { + result = MA_FALSE; + goto done_flac; + } + blockSizeRemaining -= metadata.data.picture.descriptionLength; + pDescription[metadata.data.picture.descriptionLength] = '\0'; + metadata.data.picture.description = (const char*)pDescription; + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.width, 4) != 4) { + result = MA_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.width = ma_dr_flac__be2host_32(metadata.data.picture.width); + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.height, 4) != 4) { + result = MA_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.height = ma_dr_flac__be2host_32(metadata.data.picture.height); + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.colorDepth, 4) != 4) { + result = MA_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.colorDepth = ma_dr_flac__be2host_32(metadata.data.picture.colorDepth); + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.indexColorCount, 4) != 4) { + result = MA_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.indexColorCount = ma_dr_flac__be2host_32(metadata.data.picture.indexColorCount); + if (blockSizeRemaining < 4 || onRead(pUserData, &metadata.data.picture.pictureDataSize, 4) != 4) { + result = MA_FALSE; + goto done_flac; + } + blockSizeRemaining -= 4; + metadata.data.picture.pictureDataSize = ma_dr_flac__be2host_32(metadata.data.picture.pictureDataSize); + if (blockSizeRemaining < metadata.data.picture.pictureDataSize) { + result = MA_FALSE; + goto done_flac; + } + metadata.data.picture.pictureDataOffset = runningFilePos + (blockSize - blockSizeRemaining); + #ifndef MA_DR_FLAC_NO_PICTURE_METADATA_MALLOC + pPictureData = ma_dr_flac__malloc_from_callbacks(metadata.data.picture.pictureDataSize, pAllocationCallbacks); + if (pPictureData != NULL) { + if (onRead(pUserData, pPictureData, metadata.data.picture.pictureDataSize) != metadata.data.picture.pictureDataSize) { + result = MA_FALSE; + goto done_flac; + } + } else + #endif + { + if (!onSeek(pUserData, metadata.data.picture.pictureDataSize, MA_DR_FLAC_SEEK_CUR)) { + result = MA_FALSE; + goto done_flac; + } + } + blockSizeRemaining -= metadata.data.picture.pictureDataSize; + (void)blockSizeRemaining; + metadata.data.picture.pPictureData = (const ma_uint8*)pPictureData; + if (metadata.data.picture.pictureDataOffset != 0 || metadata.data.picture.pPictureData != NULL) { + onMeta(pUserDataMD, &metadata); + } else { + } + done_flac: + ma_dr_flac__free_from_callbacks(pMime, pAllocationCallbacks); + ma_dr_flac__free_from_callbacks(pDescription, pAllocationCallbacks); + ma_dr_flac__free_from_callbacks(pPictureData, pAllocationCallbacks); + if (result != MA_TRUE) { return MA_FALSE; } - if (onRead(pUserData, pRawData, blockSize) != blockSize) { - ma_dr_flac__free_from_callbacks(pRawData, pAllocationCallbacks); - return MA_FALSE; - } - metadata.pRawData = pRawData; - metadata.rawDataSize = blockSize; - pRunningData = (const char*)pRawData; - pRunningDataEnd = (const char*)pRawData + blockSize; - metadata.data.picture.type = ma_dr_flac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; - metadata.data.picture.mimeLength = ma_dr_flac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; - if ((pRunningDataEnd - pRunningData) - 24 < (ma_int64)metadata.data.picture.mimeLength) { - ma_dr_flac__free_from_callbacks(pRawData, pAllocationCallbacks); - return MA_FALSE; - } - metadata.data.picture.mime = pRunningData; pRunningData += metadata.data.picture.mimeLength; - metadata.data.picture.descriptionLength = ma_dr_flac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; - if ((pRunningDataEnd - pRunningData) - 20 < (ma_int64)metadata.data.picture.descriptionLength) { - ma_dr_flac__free_from_callbacks(pRawData, pAllocationCallbacks); - return MA_FALSE; - } - metadata.data.picture.description = pRunningData; pRunningData += metadata.data.picture.descriptionLength; - metadata.data.picture.width = ma_dr_flac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; - metadata.data.picture.height = ma_dr_flac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; - metadata.data.picture.colorDepth = ma_dr_flac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; - metadata.data.picture.indexColorCount = ma_dr_flac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; - metadata.data.picture.pictureDataSize = ma_dr_flac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; - metadata.data.picture.pPictureData = (const ma_uint8*)pRunningData; - if (pRunningDataEnd - pRunningData < (ma_int64)metadata.data.picture.pictureDataSize) { - ma_dr_flac__free_from_callbacks(pRawData, pAllocationCallbacks); - return MA_FALSE; - } - onMeta(pUserDataMD, &metadata); - ma_dr_flac__free_from_callbacks(pRawData, pAllocationCallbacks); } } break; case MA_DR_FLAC_METADATA_BLOCK_TYPE_PADDING: @@ -88768,12 +88995,15 @@ static ma_bool32 ma_dr_flac__read_and_decode_metadata(ma_dr_flac_read_proc onRea { if (onMeta) { void* pRawData = ma_dr_flac__malloc_from_callbacks(blockSize, pAllocationCallbacks); - if (pRawData == NULL) { - return MA_FALSE; - } - if (onRead(pUserData, pRawData, blockSize) != blockSize) { - ma_dr_flac__free_from_callbacks(pRawData, pAllocationCallbacks); - return MA_FALSE; + if (pRawData != NULL) { + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + ma_dr_flac__free_from_callbacks(pRawData, pAllocationCallbacks); + return MA_FALSE; + } + } else { + if (!onSeek(pUserData, blockSize, MA_DR_FLAC_SEEK_CUR)) { + return MA_FALSE; + } } metadata.pRawData = pRawData; metadata.rawDataSize = blockSize; @@ -89832,7 +90062,6 @@ static ma_bool32 ma_dr_flac__on_seek_memory(void* pUserData, int offset, ma_dr_f ma_dr_flac__memory_stream* memoryStream = (ma_dr_flac__memory_stream*)pUserData; ma_int64 newCursor; MA_DR_FLAC_ASSERT(memoryStream != NULL); - newCursor = memoryStream->currentReadPos; if (origin == MA_DR_FLAC_SEEK_SET) { newCursor = 0; } else if (origin == MA_DR_FLAC_SEEK_CUR) { @@ -92153,56 +92382,41 @@ static type* ma_dr_flac__full_read_and_close_ ## extension (ma_dr_flac* pFlac, u { \ type* pSampleData = NULL; \ ma_uint64 totalPCMFrameCount; \ + type buffer[4096]; \ + ma_uint64 pcmFramesRead; \ + size_t sampleDataBufferSize = sizeof(buffer); \ \ MA_DR_FLAC_ASSERT(pFlac != NULL); \ \ - totalPCMFrameCount = pFlac->totalPCMFrameCount; \ + totalPCMFrameCount = 0; \ \ - if (totalPCMFrameCount == 0) { \ - type buffer[4096]; \ - ma_uint64 pcmFramesRead; \ - size_t sampleDataBufferSize = sizeof(buffer); \ + pSampleData = (type*)ma_dr_flac__malloc_from_callbacks(sampleDataBufferSize, &pFlac->allocationCallbacks); \ + if (pSampleData == NULL) { \ + goto on_error; \ + } \ \ - pSampleData = (type*)ma_dr_flac__malloc_from_callbacks(sampleDataBufferSize, &pFlac->allocationCallbacks); \ - if (pSampleData == NULL) { \ - goto on_error; \ - } \ + while ((pcmFramesRead = (ma_uint64)ma_dr_flac_read_pcm_frames_##extension(pFlac, sizeof(buffer)/sizeof(buffer[0])/pFlac->channels, buffer)) > 0) { \ + if (((totalPCMFrameCount + pcmFramesRead) * pFlac->channels * sizeof(type)) > sampleDataBufferSize) { \ + type* pNewSampleData; \ + size_t newSampleDataBufferSize; \ \ - while ((pcmFramesRead = (ma_uint64)ma_dr_flac_read_pcm_frames_##extension(pFlac, sizeof(buffer)/sizeof(buffer[0])/pFlac->channels, buffer)) > 0) { \ - if (((totalPCMFrameCount + pcmFramesRead) * pFlac->channels * sizeof(type)) > sampleDataBufferSize) { \ - type* pNewSampleData; \ - size_t newSampleDataBufferSize; \ - \ - newSampleDataBufferSize = sampleDataBufferSize * 2; \ - pNewSampleData = (type*)ma_dr_flac__realloc_from_callbacks(pSampleData, newSampleDataBufferSize, sampleDataBufferSize, &pFlac->allocationCallbacks); \ - if (pNewSampleData == NULL) { \ - ma_dr_flac__free_from_callbacks(pSampleData, &pFlac->allocationCallbacks); \ - goto on_error; \ - } \ - \ - sampleDataBufferSize = newSampleDataBufferSize; \ - pSampleData = pNewSampleData; \ + newSampleDataBufferSize = sampleDataBufferSize * 2; \ + pNewSampleData = (type*)ma_dr_flac__realloc_from_callbacks(pSampleData, newSampleDataBufferSize, sampleDataBufferSize, &pFlac->allocationCallbacks); \ + if (pNewSampleData == NULL) { \ + ma_dr_flac__free_from_callbacks(pSampleData, &pFlac->allocationCallbacks); \ + goto on_error; \ } \ \ - MA_DR_FLAC_COPY_MEMORY(pSampleData + (totalPCMFrameCount*pFlac->channels), buffer, (size_t)(pcmFramesRead*pFlac->channels*sizeof(type))); \ - totalPCMFrameCount += pcmFramesRead; \ + sampleDataBufferSize = newSampleDataBufferSize; \ + pSampleData = pNewSampleData; \ } \ \ + MA_DR_FLAC_COPY_MEMORY(pSampleData + (totalPCMFrameCount*pFlac->channels), buffer, (size_t)(pcmFramesRead*pFlac->channels*sizeof(type))); \ + totalPCMFrameCount += pcmFramesRead; \ + } \ + \ \ - MA_DR_FLAC_ZERO_MEMORY(pSampleData + (totalPCMFrameCount*pFlac->channels), (size_t)(sampleDataBufferSize - totalPCMFrameCount*pFlac->channels*sizeof(type))); \ - } else { \ - ma_uint64 dataSize = totalPCMFrameCount*pFlac->channels*sizeof(type); \ - if (dataSize > (ma_uint64)MA_SIZE_MAX) { \ - goto on_error; \ - } \ - \ - pSampleData = (type*)ma_dr_flac__malloc_from_callbacks((size_t)dataSize, &pFlac->allocationCallbacks); \ - if (pSampleData == NULL) { \ - goto on_error; \ - } \ - \ - totalPCMFrameCount = ma_dr_flac_read_pcm_frames_##extension(pFlac, pFlac->totalPCMFrameCount, pSampleData); \ - } \ + MA_DR_FLAC_ZERO_MEMORY(pSampleData + (totalPCMFrameCount*pFlac->channels), (size_t)(sampleDataBufferSize - totalPCMFrameCount*pFlac->channels*sizeof(type))); \ \ if (sampleRateOut) *sampleRateOut = pFlac->sampleRate; \ if (channelsOut) *channelsOut = pFlac->channels; \ @@ -92488,12 +92702,9 @@ MA_API const char* ma_dr_mp3_version_string(void) #define MA_DR_MP3_NO_SIMD #endif #define MA_DR_MP3_OFFSET_PTR(p, offset) ((void*)((ma_uint8*)(p) + (offset))) -#define MA_DR_MP3_MAX_FREE_FORMAT_FRAME_SIZE 2304 #ifndef MA_DR_MP3_MAX_FRAME_SYNC_MATCHES #define MA_DR_MP3_MAX_FRAME_SYNC_MATCHES 10 #endif -#define MA_DR_MP3_MAX_L3_FRAME_PAYLOAD_BYTES MA_DR_MP3_MAX_FREE_FORMAT_FRAME_SIZE -#define MA_DR_MP3_MAX_BITRESERVOIR_BYTES 511 #define MA_DR_MP3_SHORT_BLOCK_TYPE 2 #define MA_DR_MP3_STOP_BLOCK_TYPE 3 #define MA_DR_MP3_MODE_MONO 3 @@ -92543,7 +92754,7 @@ MA_API const char* ma_dr_mp3_version_string(void) #define MA_DR_MP3_VMUL_S(x, s) _mm_mul_ps(x, _mm_set1_ps(s)) #define MA_DR_MP3_VREV(x) _mm_shuffle_ps(x, x, _MM_SHUFFLE(0, 1, 2, 3)) typedef __m128 ma_dr_mp3_f4; -#if defined(_MSC_VER) || defined(MA_DR_MP3_ONLY_SIMD) +#if (defined(_MSC_VER) || defined(MA_DR_MP3_ONLY_SIMD)) && !defined(__clang__) #define ma_dr_mp3_cpuid __cpuid #else static __inline__ __attribute__((always_inline)) void ma_dr_mp3_cpuid(int CPUInfo[], const int InfoType) @@ -92659,11 +92870,6 @@ static __inline__ __attribute__((always_inline)) ma_int32 ma_dr_mp3_clip_int16_a #define MA_DR_MP3_FREE(p) free((p)) #endif typedef struct -{ - const ma_uint8 *buf; - int pos, limit; -} ma_dr_mp3_bs; -typedef struct { float scf[3*64]; ma_uint8 total_bands, stereo_bands, bitalloc[64], scfcod[64]; @@ -92672,22 +92878,6 @@ typedef struct { ma_uint8 tab_offset, code_tab_width, band_count; } ma_dr_mp3_L12_subband_alloc; -typedef struct -{ - const ma_uint8 *sfbtab; - ma_uint16 part_23_length, big_values, scalefac_compress; - ma_uint8 global_gain, block_type, mixed_block_flag, n_long_sfb, n_short_sfb; - ma_uint8 table_select[3], region_count[3], subblock_gain[3]; - ma_uint8 preflag, scalefac_scale, count1_table, scfsi; -} ma_dr_mp3_L3_gr_info; -typedef struct -{ - ma_dr_mp3_bs bs; - ma_uint8 maindata[MA_DR_MP3_MAX_BITRESERVOIR_BYTES + MA_DR_MP3_MAX_L3_FRAME_PAYLOAD_BYTES]; - ma_dr_mp3_L3_gr_info gr_info[4]; - float grbuf[2][576], scf[40], syn[18 + 15][2*32]; - ma_uint8 ist_pos[2][39]; -} ma_dr_mp3dec_scratch; static void ma_dr_mp3_bs_init(ma_dr_mp3_bs *bs, const ma_uint8 *data, int bytes) { bs->buf = data; @@ -93070,7 +93260,7 @@ static float ma_dr_mp3_L3_ldexp_q2(float y, int exp_q2) } while ((exp_q2 -= e) > 0); return y; } -#if (defined(__GNUC__) && (__GNUC__ >= 14)) && !defined(__clang__) +#if (defined(__GNUC__) && (__GNUC__ >= 13)) && !defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstringop-overflow" #endif @@ -93132,7 +93322,7 @@ static void ma_dr_mp3_L3_decode_scalefactors(const ma_uint8 *hdr, ma_uint8 *ist_ scf[i] = ma_dr_mp3_L3_ldexp_q2(gain, iscf[i] << scf_shift); } } -#if (defined(__GNUC__) && (__GNUC__ >= 14)) && !defined(__clang__) +#if (defined(__GNUC__) && (__GNUC__ >= 13)) && !defined(__clang__) #pragma GCC diagnostic pop #endif static const float ma_dr_mp3_g_pow43[129 + 16] = { @@ -94060,7 +94250,6 @@ MA_API int ma_dr_mp3dec_decode_frame(ma_dr_mp3dec *dec, const ma_uint8 *mp3, int int i = 0, igr, frame_size = 0, success = 1; const ma_uint8 *hdr; ma_dr_mp3_bs bs_frame[1]; - ma_dr_mp3dec_scratch scratch; if (mp3_bytes > 4 && dec->header[0] == 0xff && ma_dr_mp3_hdr_compare(dec->header, mp3)) { frame_size = ma_dr_mp3_hdr_frame_bytes(mp3, dec->free_format_bytes) + ma_dr_mp3_hdr_padding(mp3); @@ -94093,23 +94282,23 @@ MA_API int ma_dr_mp3dec_decode_frame(ma_dr_mp3dec *dec, const ma_uint8 *mp3, int } if (info->layer == 3) { - int main_data_begin = ma_dr_mp3_L3_read_side_info(bs_frame, scratch.gr_info, hdr); + int main_data_begin = ma_dr_mp3_L3_read_side_info(bs_frame, dec->scratch.gr_info, hdr); if (main_data_begin < 0 || bs_frame->pos > bs_frame->limit) { ma_dr_mp3dec_init(dec); return 0; } - success = ma_dr_mp3_L3_restore_reservoir(dec, bs_frame, &scratch, main_data_begin); + success = ma_dr_mp3_L3_restore_reservoir(dec, bs_frame, &dec->scratch, main_data_begin); if (success && pcm != NULL) { for (igr = 0; igr < (MA_DR_MP3_HDR_TEST_MPEG1(hdr) ? 2 : 1); igr++, pcm = MA_DR_MP3_OFFSET_PTR(pcm, sizeof(ma_dr_mp3d_sample_t)*576*info->channels)) { - MA_DR_MP3_ZERO_MEMORY(scratch.grbuf[0], 576*2*sizeof(float)); - ma_dr_mp3_L3_decode(dec, &scratch, scratch.gr_info + igr*info->channels, info->channels); - ma_dr_mp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 18, info->channels, (ma_dr_mp3d_sample_t*)pcm, scratch.syn[0]); + MA_DR_MP3_ZERO_MEMORY(dec->scratch.grbuf[0], 576*2*sizeof(float)); + ma_dr_mp3_L3_decode(dec, &dec->scratch, dec->scratch.gr_info + igr*info->channels, info->channels); + ma_dr_mp3d_synth_granule(dec->qmf_state, dec->scratch.grbuf[0], 18, info->channels, (ma_dr_mp3d_sample_t*)pcm, dec->scratch.syn[0]); } } - ma_dr_mp3_L3_save_reservoir(dec, &scratch); + ma_dr_mp3_L3_save_reservoir(dec, &dec->scratch); } else { #ifdef MA_DR_MP3_ONLY_MP3 @@ -94120,15 +94309,15 @@ MA_API int ma_dr_mp3dec_decode_frame(ma_dr_mp3dec *dec, const ma_uint8 *mp3, int return ma_dr_mp3_hdr_frame_samples(hdr); } ma_dr_mp3_L12_read_scale_info(hdr, bs_frame, sci); - MA_DR_MP3_ZERO_MEMORY(scratch.grbuf[0], 576*2*sizeof(float)); + MA_DR_MP3_ZERO_MEMORY(dec->scratch.grbuf[0], 576*2*sizeof(float)); for (i = 0, igr = 0; igr < 3; igr++) { - if (12 == (i += ma_dr_mp3_L12_dequantize_granule(scratch.grbuf[0] + i, bs_frame, sci, info->layer | 1))) + if (12 == (i += ma_dr_mp3_L12_dequantize_granule(dec->scratch.grbuf[0] + i, bs_frame, sci, info->layer | 1))) { i = 0; - ma_dr_mp3_L12_apply_scf_384(sci, sci->scf + igr, scratch.grbuf[0]); - ma_dr_mp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 12, info->channels, (ma_dr_mp3d_sample_t*)pcm, scratch.syn[0]); - MA_DR_MP3_ZERO_MEMORY(scratch.grbuf[0], 576*2*sizeof(float)); + ma_dr_mp3_L12_apply_scf_384(sci, sci->scf + igr, dec->scratch.grbuf[0]); + ma_dr_mp3d_synth_granule(dec->qmf_state, dec->scratch.grbuf[0], 12, info->channels, (ma_dr_mp3d_sample_t*)pcm, dec->scratch.syn[0]); + MA_DR_MP3_ZERO_MEMORY(dec->scratch.grbuf[0], 576*2*sizeof(float)); pcm = MA_DR_MP3_OFFSET_PTR(pcm, sizeof(ma_dr_mp3d_sample_t)*384*info->channels); } if (bs_frame->pos > bs_frame->limit) @@ -94587,19 +94776,22 @@ static ma_bool32 ma_dr_mp3_init_internal(ma_dr_mp3* pMP3, ma_dr_mp3_read_proc on ((ma_uint32)ape[25] << 8) | ((ma_uint32)ape[26] << 16) | ((ma_uint32)ape[27] << 24); - streamEndOffset -= 32 + tagSize; - streamLen -= 32 + tagSize; - if (onMeta != NULL) { - if (onSeek(pUserData, streamEndOffset, MA_DR_MP3_SEEK_END)) { - size_t apeTagSize = (size_t)tagSize + 32; - ma_uint8* pTagData = (ma_uint8*)ma_dr_mp3_malloc(apeTagSize, pAllocationCallbacks); - if (pTagData != NULL) { - if (onRead(pUserData, pTagData, apeTagSize) == apeTagSize) { - ma_dr_mp3__on_meta(pMP3, MA_DR_MP3_METADATA_TYPE_APE, pTagData, apeTagSize); + if (32 + tagSize < streamLen) { + streamEndOffset -= 32 + tagSize; + streamLen -= 32 + tagSize; + if (onMeta != NULL) { + if (onSeek(pUserData, streamEndOffset, MA_DR_MP3_SEEK_END)) { + size_t apeTagSize = (size_t)tagSize + 32; + ma_uint8* pTagData = (ma_uint8*)ma_dr_mp3_malloc(apeTagSize, pAllocationCallbacks); + if (pTagData != NULL) { + if (onRead(pUserData, pTagData, apeTagSize) == apeTagSize) { + ma_dr_mp3__on_meta(pMP3, MA_DR_MP3_METADATA_TYPE_APE, pTagData, apeTagSize); + } + ma_dr_mp3_free(pTagData, pAllocationCallbacks); } - ma_dr_mp3_free(pTagData, pAllocationCallbacks); } } + } else { } } } @@ -94687,7 +94879,6 @@ static ma_bool32 ma_dr_mp3_init_internal(ma_dr_mp3* pMP3, ma_dr_mp3_read_proc on { ma_dr_mp3_bs bs; ma_dr_mp3_L3_gr_info grInfo[4]; - const ma_uint8* pTagData = pFirstFrameData; ma_dr_mp3_bs_init(&bs, pFirstFrameData + MA_DR_MP3_HDR_SIZE, firstFrameInfo.frame_bytes - MA_DR_MP3_HDR_SIZE); if (MA_DR_MP3_HDR_IS_CRC(pFirstFrameData)) { ma_dr_mp3_bs_get_bits(&bs, 16); @@ -94695,6 +94886,7 @@ static ma_bool32 ma_dr_mp3_init_internal(ma_dr_mp3* pMP3, ma_dr_mp3_read_proc on if (ma_dr_mp3_L3_read_side_info(&bs, grInfo, pFirstFrameData) >= 0) { ma_bool32 isXing = MA_FALSE; ma_bool32 isInfo = MA_FALSE; + const ma_uint8* pTagData; const ma_uint8* pTagDataBeg; pTagDataBeg = pFirstFrameData + MA_DR_MP3_HDR_SIZE + (bs.pos/8); pTagData = pTagDataBeg; @@ -94794,7 +94986,6 @@ static ma_bool32 ma_dr_mp3__on_seek_memory(void* pUserData, int byteOffset, ma_d ma_dr_mp3* pMP3 = (ma_dr_mp3*)pUserData; ma_int64 newCursor; MA_DR_MP3_ASSERT(pMP3 != NULL); - newCursor = pMP3->memory.currentReadPos; if (origin == MA_DR_MP3_SEEK_SET) { newCursor = 0; } else if (origin == MA_DR_MP3_SEEK_CUR) { @@ -95445,6 +95636,8 @@ static float* ma_dr_mp3__full_read_and_close_f32(ma_dr_mp3* pMP3, ma_dr_mp3_conf pNewFrames = (float*)ma_dr_mp3__realloc_from_callbacks(pFrames, (size_t)newFramesBufferSize, (size_t)oldFramesBufferSize, &pMP3->allocationCallbacks); if (pNewFrames == NULL) { ma_dr_mp3__free_from_callbacks(pFrames, &pMP3->allocationCallbacks); + pFrames = NULL; + totalFramesRead = 0; break; } pFrames = pNewFrames; @@ -95496,6 +95689,8 @@ static ma_int16* ma_dr_mp3__full_read_and_close_s16(ma_dr_mp3* pMP3, ma_dr_mp3_c pNewFrames = (ma_int16*)ma_dr_mp3__realloc_from_callbacks(pFrames, (size_t)newFramesBufferSize, (size_t)oldFramesBufferSize, &pMP3->allocationCallbacks); if (pNewFrames == NULL) { ma_dr_mp3__free_from_callbacks(pFrames, &pMP3->allocationCallbacks); + pFrames = NULL; + totalFramesRead = 0; break; } pFrames = pNewFrames; @@ -95646,4 +95841,4 @@ 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. -*/ \ No newline at end of file +*/ From b21d7f234b8ca1e8887830f7b4d65edeba22e3ac Mon Sep 17 00:00:00 2001 From: The4codeblocks <72419529+The4codeblocks@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:08:10 -0500 Subject: [PATCH 384/430] [raymath] `QuaternionFromVector3ToVector3()`, math is wrong (#5508) * the math in QuaternionFromVector3ToVector3 is wrong * fix styling --- src/raymath.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raymath.h b/src/raymath.h index 7b58d410e..91c858e2a 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -2369,7 +2369,7 @@ RMAPI Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) result.x = cross.x; result.y = cross.y; result.z = cross.z; - result.w = 1.0f + cos2Theta; + result.w = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z + cos2Theta*cos2Theta) + cos2Theta; // sqrtf(Vector3DotProduct(cross, cross) + cos2Theta * cos2Theta) + cos2Theta // QuaternionNormalize(q); // NOTE: Normalize to essentially nlerp the original and identity to 0.5 From a33ae4a8ef8809cc7f3c078651bd91e6e56591f4 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 23 Jan 2026 17:11:37 +0100 Subject: [PATCH 385/430] Update raymath.h --- src/raymath.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/raymath.h b/src/raymath.h index 91c858e2a..0f9cbc38b 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -2363,13 +2363,13 @@ RMAPI Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) { Quaternion result = { 0 }; - float cos2Theta = (from.x*to.x + from.y*to.y + from.z*to.z); // Vector3DotProduct(from, to) + float cos2Theta = (from.x*to.x + from.y*to.y + from.z*to.z); // Vector3DotProduct(from, to) Vector3 cross = { from.y*to.z - from.z*to.y, from.z*to.x - from.x*to.z, from.x*to.y - from.y*to.x }; // Vector3CrossProduct(from, to) result.x = cross.x; result.y = cross.y; result.z = cross.z; - result.w = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z + cos2Theta*cos2Theta) + cos2Theta; // sqrtf(Vector3DotProduct(cross, cross) + cos2Theta * cos2Theta) + cos2Theta + result.w = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z + cos2Theta*cos2Theta) + cos2Theta; // QuaternionNormalize(q); // NOTE: Normalize to essentially nlerp the original and identity to 0.5 From 70a63f7c626bf1982d7ee9d09e7a2ca943fcf046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Dem=C4=8D=C3=A1k?= <71095952+vdemcak@users.noreply.github.com> Date: Sat, 24 Jan 2026 21:21:43 +0100 Subject: [PATCH 386/430] [web] Fix Emscripten's Closure compiler error: undeclared canvas variable (#5507) * Fix Emscripten Closure compiler error: undeclared canvas variable * Fix hardcoded canvas IDs in web targets --- src/platforms/rcore_web.c | 9 +++++---- src/platforms/rcore_web_emscripten.c | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index b0a145f67..986197b9d 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -316,15 +316,16 @@ void ToggleBorderlessWindowed(void) // 2. The style unset handles the possibility of a width="value%" like on the default shell.html file EM_ASM ( + const canvasId = UTF8ToString($0); setTimeout(function() { Module.requestFullscreen(false, true); setTimeout(function() { - canvas.style.width="unset"; + document.querySelector(canvasId).style.width="unset"; }, 100); }, 100); - ); + , platform.canvasId); FLAG_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); } } @@ -1238,9 +1239,9 @@ int InitPlatform(void) // Avoid creating a WebGL canvas, avoid calling glfwCreateWindow() emscripten_set_canvas_element_size(platform.canvasId, CORE.Window.screen.width, CORE.Window.screen.height); EM_ASM({ - const canvas = document.getElementById("canvas"); + const canvas = document.querySelector(UTF8ToString($0)); Module.canvas = canvas; - }); + }, platform.canvasId); // Load memory framebuffer with desired screen size // NOTE: Despite using a software framebuffer for blitting, GLFW still creates a WebGL canvas, diff --git a/src/platforms/rcore_web_emscripten.c b/src/platforms/rcore_web_emscripten.c index ad26077f4..ba2489a31 100644 --- a/src/platforms/rcore_web_emscripten.c +++ b/src/platforms/rcore_web_emscripten.c @@ -280,15 +280,16 @@ void ToggleBorderlessWindowed(void) // 2. The style unset handles the possibility of a width="value%" like on the default shell.html file EM_ASM ( + const canvasId = UTF8ToString($0); setTimeout(function() { Module.requestFullscreen(false, true); setTimeout(function() { - canvas.style.width="unset"; + document.querySelector(canvasId).style.width="unset"; }, 100); }, 100); - ); + , platform.canvasId); FLAG_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE); } } From afe74c1c707a7c0d9499fb63f0120466c719c6fe Mon Sep 17 00:00:00 2001 From: Maicon Santana Date: Sat, 24 Jan 2026 20:53:22 +0000 Subject: [PATCH 387/430] Refactor int to float missing parse (#5503) * refactor int to float parse * Reverting as requested --------- Co-authored-by: Maicon --- examples/core/core_2d_camera_platformer.c | 8 ++--- examples/core/core_automation_events.c | 8 ++--- examples/core/core_viewport_scaling.c | 4 +-- examples/models/models_rlgl_solar_system.c | 36 +++++++++---------- examples/models/models_waving_cubes.c | 6 ++-- examples/shaders/shaders_game_of_life.c | 4 +-- examples/shapes/shapes_colors_palette.c | 2 +- examples/shapes/shapes_digital_clock.c | 2 +- examples/shapes/shapes_double_pendulum.c | 8 ++--- examples/text/text_font_sdf.c | 4 +-- examples/text/text_rectangle_bounds.c | 6 ++-- examples/text/text_unicode_emojis.c | 8 ++--- examples/text/text_words_alignment.c | 2 +- examples/textures/textures_bunnymark.c | 8 ++--- .../textures/textures_cellular_automata.c | 2 +- examples/textures/textures_fog_of_war.c | 4 +-- examples/textures/textures_image_processing.c | 2 +- examples/textures/textures_image_text.c | 2 +- examples/textures/textures_sprite_button.c | 2 +- examples/textures/textures_sprite_explosion.c | 4 +-- src/rtextures.c | 2 +- 21 files changed, 62 insertions(+), 62 deletions(-) diff --git a/examples/core/core_2d_camera_platformer.c b/examples/core/core_2d_camera_platformer.c index 45bad4015..49d0a940c 100644 --- a/examples/core/core_2d_camera_platformer.c +++ b/examples/core/core_2d_camera_platformer.c @@ -226,10 +226,10 @@ void UpdateCameraCenterInsideMap(Camera2D *camera, Player *player, EnvItem *envI Vector2 max = GetWorldToScreen2D((Vector2){ maxX, maxY }, *camera); Vector2 min = GetWorldToScreen2D((Vector2){ minX, minY }, *camera); - if (max.x < width) camera->offset.x = width - (max.x - width/2); - if (max.y < height) camera->offset.y = height - (max.y - height/2); - if (min.x > 0) camera->offset.x = width/2 - min.x; - if (min.y > 0) camera->offset.y = height/2 - min.y; + if (max.x < width) camera->offset.x = width - (max.x - (float)width/2); + if (max.y < height) camera->offset.y = height - (max.y - (float)height/2); + if (min.x > 0) camera->offset.x = (float)width/2 - min.x; + if (min.y > 0) camera->offset.y = (float)height/2 - min.y; } void UpdateCameraCenterSmoothFollow(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height) diff --git a/examples/core/core_automation_events.c b/examples/core/core_automation_events.c index b98b37c69..50204187f 100644 --- a/examples/core/core_automation_events.c +++ b/examples/core/core_automation_events.c @@ -225,10 +225,10 @@ int main(void) Vector2 max = GetWorldToScreen2D((Vector2){ maxX, maxY }, camera); Vector2 min = GetWorldToScreen2D((Vector2){ minX, minY }, camera); - if (max.x < screenWidth) camera.offset.x = screenWidth - (max.x - screenWidth/2); - if (max.y < screenHeight) camera.offset.y = screenHeight - (max.y - screenHeight/2); - if (min.x > 0) camera.offset.x = screenWidth/2 - min.x; - if (min.y > 0) camera.offset.y = screenHeight/2 - min.y; + if (max.x < screenWidth) camera.offset.x = screenWidth - (max.x - (float)screenWidth/2); + if (max.y < screenHeight) camera.offset.y = screenHeight - (max.y - (float)screenHeight/2); + if (min.x > 0) camera.offset.x = (float)screenWidth/2 - min.x; + if (min.y > 0) camera.offset.y = (float)screenHeight/2 - min.y; //---------------------------------------------------------------------------------- // Events management diff --git a/examples/core/core_viewport_scaling.c b/examples/core/core_viewport_scaling.c index 6ff5ac9c4..4a608e96d 100644 --- a/examples/core/core_viewport_scaling.c +++ b/examples/core/core_viewport_scaling.c @@ -216,7 +216,7 @@ static void KeepAspectCenteredInteger(int screenWidth, int screenHeight, int gam static void KeepHeightCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect) { - const float resizeRatio = (float)(screenHeight/gameHeight); + const float resizeRatio = (float)screenHeight/gameHeight; sourceRect->x = 0.0f; sourceRect->y = 0.0f; sourceRect->width = (float)(int)(screenWidth/resizeRatio); @@ -230,7 +230,7 @@ static void KeepHeightCenteredInteger(int screenWidth, int screenHeight, int gam static void KeepWidthCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect) { - const float resizeRatio = (float)(screenWidth/gameWidth); + const float resizeRatio = (float)screenWidth/gameWidth; sourceRect->x = 0.0f; sourceRect->y = 0.0f; sourceRect->width = (float)gameWidth; diff --git a/examples/models/models_rlgl_solar_system.c b/examples/models/models_rlgl_solar_system.c index d8d86d69a..0988ede8d 100644 --- a/examples/models/models_rlgl_solar_system.c +++ b/examples/models/models_rlgl_solar_system.c @@ -148,25 +148,25 @@ void DrawSphereBasic(Color color) { for (int j = 0; j < slices; j++) { - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*i))*sinf(DEG2RAD*(j*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*i)), - cosf(DEG2RAD*(270+(180/(rings + 1))*i))*cosf(DEG2RAD*(j*360/slices))); - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*((j+1)*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), - cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*((j+1)*360/slices))); - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*(j*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), - cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*(j*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180.0f/(rings + 1))*i))*sinf(DEG2RAD*(j*360.0f/slices)), + sinf(DEG2RAD*(270+(180.0f/(rings + 1))*i)), + cosf(DEG2RAD*(270+(180.0f/(rings + 1))*i))*cosf(DEG2RAD*(j*360.0f/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180.0f/(rings + 1))*(i+1)))*sinf(DEG2RAD*((j+1)*360.0f/slices)), + sinf(DEG2RAD*(270+(180.0f/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180.0f/(rings + 1))*(i+1)))*cosf(DEG2RAD*((j+1)*360.0f/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180.0f/(rings + 1))*(i+1)))*sinf(DEG2RAD*(j*360.0f/slices)), + sinf(DEG2RAD*(270+(180.0f/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180.0f/(rings + 1))*(i+1)))*cosf(DEG2RAD*(j*360.0f/slices))); - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*i))*sinf(DEG2RAD*(j*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*i)), - cosf(DEG2RAD*(270+(180/(rings + 1))*i))*cosf(DEG2RAD*(j*360/slices))); - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i)))*sinf(DEG2RAD*((j+1)*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*(i))), - cosf(DEG2RAD*(270+(180/(rings + 1))*(i)))*cosf(DEG2RAD*((j+1)*360/slices))); - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*((j+1)*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), - cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*((j+1)*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180.0f/(rings + 1))*i))*sinf(DEG2RAD*(j*360.0f/slices)), + sinf(DEG2RAD*(270+(180.0f/(rings + 1))*i)), + cosf(DEG2RAD*(270+(180.0f/(rings + 1))*i))*cosf(DEG2RAD*(j*360.0f/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180.0f/(rings + 1))*(i)))*sinf(DEG2RAD*((j+1)*360.0f/slices)), + sinf(DEG2RAD*(270+(180.0f/(rings + 1))*(i))), + cosf(DEG2RAD*(270+(180.0f/(rings + 1))*(i)))*cosf(DEG2RAD*((j+1)*360.0f/slices))); + rlVertex3f(cosf(DEG2RAD*(270+(180.0f/(rings + 1))*(i+1)))*sinf(DEG2RAD*((j+1)*360.0f/slices)), + sinf(DEG2RAD*(270+(180.0f/(rings + 1))*(i+1))), + cosf(DEG2RAD*(270+(180.0f/(rings + 1))*(i+1)))*cosf(DEG2RAD*((j+1)*360.0f/slices))); } } rlEnd(); diff --git a/examples/models/models_waving_cubes.c b/examples/models/models_waving_cubes.c index 7996c1c8a..36e8bccf7 100644 --- a/examples/models/models_waving_cubes.c +++ b/examples/models/models_waving_cubes.c @@ -85,9 +85,9 @@ int main(void) // Calculate the cube position Vector3 cubePos = { - (float)(x - numBlocks/2)*(scale*3.0f) + scatter, - (float)(y - numBlocks/2)*(scale*2.0f) + scatter, - (float)(z - numBlocks/2)*(scale*3.0f) + scatter + (float)(x - (float)numBlocks/2)*(scale*3.0f) + scatter, + (float)(y - (float)numBlocks/2)*(scale*2.0f) + scatter, + (float)(z - (float)numBlocks/2)*(scale*3.0f) + scatter }; // Pick a color with a hue depending on cube position for the rainbow color effect diff --git a/examples/shaders/shaders_game_of_life.c b/examples/shaders/shaders_game_of_life.c index 9b9242a0d..0ae91b756 100644 --- a/examples/shaders/shaders_game_of_life.c +++ b/examples/shaders/shaders_game_of_life.c @@ -258,8 +258,8 @@ int main(void) UnloadImage(pattern); mode = MODE_PAUSE; - offsetX = worldWidth*presetPatterns[preset].position.x - windowWidth/zoom/2.0f; - offsetY = worldHeight*presetPatterns[preset].position.y - windowHeight/zoom/2.0f; + offsetX = worldWidth*presetPatterns[preset].position.x - (float)windowWidth/zoom/2.0f; + offsetY = worldHeight*presetPatterns[preset].position.y - (float)windowHeight/zoom/2.0f; } // Check window draw inside world limits diff --git a/examples/shapes/shapes_colors_palette.c b/examples/shapes/shapes_colors_palette.c index 9fbcf3063..44da323eb 100644 --- a/examples/shapes/shapes_colors_palette.c +++ b/examples/shapes/shapes_colors_palette.c @@ -45,7 +45,7 @@ int main(void) for (int i = 0; i < MAX_COLORS_COUNT; i++) { colorsRecs[i].x = 20.0f + 100.0f *(i%7) + 10.0f *(i%7); - colorsRecs[i].y = 80.0f + 100.0f *(i/7) + 10.0f *(i/7); + colorsRecs[i].y = 80.0f + 100.0f *((float)i/7) + 10.0f *((float)i/7); colorsRecs[i].width = 100.0f; colorsRecs[i].height = 100.0f; } diff --git a/examples/shapes/shapes_digital_clock.c b/examples/shapes/shapes_digital_clock.c index cca3f3c44..fb9ae80e6 100644 --- a/examples/shapes/shapes_digital_clock.c +++ b/examples/shapes/shapes_digital_clock.c @@ -311,7 +311,7 @@ static void DrawDisplaySegment(Vector2 center, int length, int thick, bool verti (Vector2){ center.x + thick/2.0f, center.y - length/2.0f }, // Point 3 (Vector2){ center.x - thick/2.0f, center.y + length/2.0f }, // Point 4 (Vector2){ center.x + thick/2.0f, center.y + length/2.0f }, // Point 5 - (Vector2){ center.x, center.y + length/2 + thick/2.0f }, // Point 6 + (Vector2){ center.x, center.y + (float)length/2 + thick/2.0f }, // Point 6 }; DrawTriangleStrip(segmentPointsV, 6, color); diff --git a/examples/shapes/shapes_double_pendulum.c b/examples/shapes/shapes_double_pendulum.c index 760d66203..5b357c9f3 100644 --- a/examples/shapes/shapes_double_pendulum.c +++ b/examples/shapes/shapes_double_pendulum.c @@ -49,8 +49,8 @@ int main(void) float totalM = m1 + m2; Vector2 previousPosition = CalculateDoublePendulumEndPoint(l1, theta1, l2, theta2); - previousPosition.x += (screenWidth/2); - previousPosition.y += (screenHeight/2 - 100); + previousPosition.x += ((float)screenWidth/2); + previousPosition.y += ((float)screenHeight/2 - 100); // Scale length float L1 = l1*lengthScaler; @@ -105,8 +105,8 @@ int main(void) // Calculate position Vector2 currentPosition = CalculateDoublePendulumEndPoint(l1, theta1, l2, theta2); - currentPosition.x += screenWidth/2; - currentPosition.y += screenHeight/2 - 100; + currentPosition.x += (float)screenWidth/2; + currentPosition.y += (float)screenHeight/2 - 100; // Draw to render texture BeginTextureMode(target); diff --git a/examples/text/text_font_sdf.c b/examples/text/text_font_sdf.c index 744450fa6..42b7495e2 100644 --- a/examples/text/text_font_sdf.c +++ b/examples/text/text_font_sdf.c @@ -97,8 +97,8 @@ int main(void) if (currentFont == 0) textSize = MeasureTextEx(fontDefault, msg, fontSize, 0); else textSize = MeasureTextEx(fontSDF, msg, fontSize, 0); - fontPosition.x = GetScreenWidth()/2 - textSize.x/2; - fontPosition.y = GetScreenHeight()/2 - textSize.y/2 + 80; + fontPosition.x = (float)GetScreenWidth()/2 - textSize.x/2; + fontPosition.y = (float)GetScreenHeight()/2 - textSize.y/2 + 80; //---------------------------------------------------------------------------------- // Draw diff --git a/examples/text/text_rectangle_bounds.c b/examples/text/text_rectangle_bounds.c index e180ae475..b87a3a956 100644 --- a/examples/text/text_rectangle_bounds.c +++ b/examples/text/text_rectangle_bounds.c @@ -226,7 +226,7 @@ static void DrawTextBoxedSelectable(Font font, const char *text, Rectangle rec, { if (!wordWrap) { - textOffsetY += (font.baseSize + font.baseSize/2)*scaleFactor; + textOffsetY += (font.baseSize + (float)font.baseSize/2)*scaleFactor; textOffsetX = 0; } } @@ -234,7 +234,7 @@ static void DrawTextBoxedSelectable(Font font, const char *text, Rectangle rec, { if (!wordWrap && ((textOffsetX + glyphWidth) > rec.width)) { - textOffsetY += (font.baseSize + font.baseSize/2)*scaleFactor; + textOffsetY += (font.baseSize + (float)font.baseSize/2)*scaleFactor; textOffsetX = 0; } @@ -258,7 +258,7 @@ static void DrawTextBoxedSelectable(Font font, const char *text, Rectangle rec, if (wordWrap && (i == endLine)) { - textOffsetY += (font.baseSize + font.baseSize/2)*scaleFactor; + textOffsetY += (font.baseSize + (float)font.baseSize/2)*scaleFactor; textOffsetX = 0; startLine = endLine; endLine = -1; diff --git a/examples/text/text_unicode_emojis.c b/examples/text/text_unicode_emojis.c index 00712745d..240b50052 100644 --- a/examples/text/text_unicode_emojis.c +++ b/examples/text/text_unicode_emojis.c @@ -277,7 +277,7 @@ int main(void) DrawTriangle(a, b, c, emoji[selected].color); // Draw the main text message - Rectangle textRect = { msgRect.x + horizontalPadding/2, msgRect.y + verticalPadding/2, msgRect.width - horizontalPadding, msgRect.height }; + Rectangle textRect = { msgRect.x + (float)horizontalPadding/2, msgRect.y + (float)verticalPadding/2, msgRect.width - horizontalPadding, msgRect.height }; DrawTextBoxed(*font, messages[message].text, textRect, (float)font->baseSize, 1.0f, true, WHITE); // Draw the info text below the main message @@ -421,7 +421,7 @@ static void DrawTextBoxedSelectable(Font font, const char *text, Rectangle rec, { if (!wordWrap) { - textOffsetY += (font.baseSize + font.baseSize/2)*scaleFactor; + textOffsetY += (font.baseSize + (float)font.baseSize/2)*scaleFactor; textOffsetX = 0; } } @@ -429,7 +429,7 @@ static void DrawTextBoxedSelectable(Font font, const char *text, Rectangle rec, { if (!wordWrap && ((textOffsetX + glyphWidth) > rec.width)) { - textOffsetY += (font.baseSize + font.baseSize/2)*scaleFactor; + textOffsetY += (font.baseSize + (float)font.baseSize/2)*scaleFactor; textOffsetX = 0; } @@ -453,7 +453,7 @@ static void DrawTextBoxedSelectable(Font font, const char *text, Rectangle rec, if (wordWrap && (i == endLine)) { - textOffsetY += (font.baseSize + font.baseSize/2)*scaleFactor; + textOffsetY += (font.baseSize + (float)font.baseSize/2)*scaleFactor; textOffsetX = 0; startLine = endLine; endLine = -1; diff --git a/examples/text/text_words_alignment.c b/examples/text/text_words_alignment.c index dbd9cd03e..f7ffa74af 100644 --- a/examples/text/text_words_alignment.c +++ b/examples/text/text_words_alignment.c @@ -41,7 +41,7 @@ int main(void) InitWindow(screenWidth, screenHeight, "raylib [text] example - words alignment"); // Define the rectangle we will draw the text in - Rectangle textContainerRect = (Rectangle){ screenWidth/2-screenWidth/4, screenHeight/2-screenHeight/3, screenWidth/2, screenHeight*2/3 }; + Rectangle textContainerRect = (Rectangle){ (float)screenWidth/2-(float)screenWidth/4, (float)screenHeight/2-(float)screenHeight/3, (float)screenWidth/2, (float)screenHeight*2/3 }; // Some text to display the current alignment const char *textAlignNameH[] = { "Left", "Centre", "Right" }; diff --git a/examples/textures/textures_bunnymark.c b/examples/textures/textures_bunnymark.c index a95181ed6..3279abb8f 100644 --- a/examples/textures/textures_bunnymark.c +++ b/examples/textures/textures_bunnymark.c @@ -83,10 +83,10 @@ int main(void) bunnies[i].position.x += bunnies[i].speed.x; bunnies[i].position.y += bunnies[i].speed.y; - if (((bunnies[i].position.x + texBunny.width/2) > GetScreenWidth()) || - ((bunnies[i].position.x + texBunny.width/2) < 0)) bunnies[i].speed.x *= -1; - if (((bunnies[i].position.y + texBunny.height/2) > GetScreenHeight()) || - ((bunnies[i].position.y + texBunny.height/2 - 40) < 0)) bunnies[i].speed.y *= -1; + if (((bunnies[i].position.x + (float)texBunny.width/2) > GetScreenWidth()) || + ((bunnies[i].position.x + (float)texBunny.width/2) < 0)) bunnies[i].speed.x *= -1; + if (((bunnies[i].position.y + (float)texBunny.height/2) > GetScreenHeight()) || + ((bunnies[i].position.y + (float)texBunny.height/2 - 40) < 0)) bunnies[i].speed.y *= -1; } //---------------------------------------------------------------------------------- diff --git a/examples/textures/textures_cellular_automata.c b/examples/textures/textures_cellular_automata.c index affeeda93..802de3611 100644 --- a/examples/textures/textures_cellular_automata.c +++ b/examples/textures/textures_cellular_automata.c @@ -165,7 +165,7 @@ int main(void) // If the mouse is on this preset, highlight it if (mouseInCell == i + 8) - DrawRectangleLinesEx((Rectangle) { 2 + (presetsSizeX + 2.0f)*(i/2), + DrawRectangleLinesEx((Rectangle) { 2 + (presetsSizeX + 2.0f)*((float)i/2), (presetsSizeY + 2.0f)*(i%2), presetsSizeX + 4.0f, presetsSizeY + 4.0f }, 3, RED); } diff --git a/examples/textures/textures_fog_of_war.c b/examples/textures/textures_fog_of_war.c index 4733caeb4..ea98b03ce 100644 --- a/examples/textures/textures_fog_of_war.c +++ b/examples/textures/textures_fog_of_war.c @@ -93,8 +93,8 @@ int main(void) for (unsigned int i = 0; i < map.tilesX*map.tilesY; i++) if (map.tileFog[i] == 1) map.tileFog[i] = 2; // Get current tile position from player pixel position - playerTileX = (int)((playerPosition.x + MAP_TILE_SIZE/2)/MAP_TILE_SIZE); - playerTileY = (int)((playerPosition.y + MAP_TILE_SIZE/2)/MAP_TILE_SIZE); + playerTileX = (int)((playerPosition.x + (float)MAP_TILE_SIZE/2)/MAP_TILE_SIZE); + playerTileY = (int)((playerPosition.y + (float)MAP_TILE_SIZE/2)/MAP_TILE_SIZE); // Check visibility and update fog // NOTE: We check tilemap limits to avoid processing tiles out-of-array-bounds (it could crash program) diff --git a/examples/textures/textures_image_processing.c b/examples/textures/textures_image_processing.c index 474974f97..8d2472c0a 100644 --- a/examples/textures/textures_image_processing.c +++ b/examples/textures/textures_image_processing.c @@ -156,7 +156,7 @@ int main(void) { DrawRectangleRec(toggleRecs[i], ((i == currentProcess) || (i == mouseHoverRec)) ? SKYBLUE : LIGHTGRAY); DrawRectangleLines((int)toggleRecs[i].x, (int) toggleRecs[i].y, (int) toggleRecs[i].width, (int) toggleRecs[i].height, ((i == currentProcess) || (i == mouseHoverRec)) ? BLUE : GRAY); - DrawText( processText[i], (int)( toggleRecs[i].x + toggleRecs[i].width/2 - MeasureText(processText[i], 10)/2), (int) toggleRecs[i].y + 11, 10, ((i == currentProcess) || (i == mouseHoverRec)) ? DARKBLUE : DARKGRAY); + DrawText( processText[i], (int)( toggleRecs[i].x + toggleRecs[i].width/2 - (float)MeasureText(processText[i], 10)/2), (int) toggleRecs[i].y + 11, 10, ((i == currentProcess) || (i == mouseHoverRec)) ? DARKBLUE : DARKGRAY); } DrawTexture(texture, screenWidth - texture.width - 60, screenHeight/2 - texture.height/2, WHITE); diff --git a/examples/textures/textures_image_text.c b/examples/textures/textures_image_text.c index e6777261a..b71ca4792 100644 --- a/examples/textures/textures_image_text.c +++ b/examples/textures/textures_image_text.c @@ -38,7 +38,7 @@ int main(void) Texture2D texture = LoadTextureFromImage(parrots); // Image converted to texture, uploaded to GPU memory (VRAM) UnloadImage(parrots); // Once image has been converted to texture and uploaded to VRAM, it can be unloaded from RAM - Vector2 position = { (float)(screenWidth/2 - texture.width/2), (float)(screenHeight/2 - texture.height/2 - 20) }; + Vector2 position = { (float)screenWidth/2 - (float)texture.width/2, (float)screenHeight/2 - (float)texture.height/2 - 20 }; bool showFont = false; diff --git a/examples/textures/textures_sprite_button.c b/examples/textures/textures_sprite_button.c index a7db93c4f..7fe2adfd6 100644 --- a/examples/textures/textures_sprite_button.c +++ b/examples/textures/textures_sprite_button.c @@ -39,7 +39,7 @@ int main(void) Rectangle sourceRec = { 0, 0, (float)button.width, frameHeight }; // Define button bounds on screen - Rectangle btnBounds = { screenWidth/2.0f - button.width/2.0f, screenHeight/2.0f - button.height/NUM_FRAMES/2.0f, (float)button.width, frameHeight }; + Rectangle btnBounds = { screenWidth/2.0f - button.width/2.0f, screenHeight/2.0f - (float)button.height/NUM_FRAMES/2.0f, (float)button.width, frameHeight }; int btnState = 0; // Button state: 0-NORMAL, 1-MOUSE_HOVER, 2-PRESSED bool btnAction = false; // Button action should be activated diff --git a/examples/textures/textures_sprite_explosion.c b/examples/textures/textures_sprite_explosion.c index fe9669224..7efe8cf17 100644 --- a/examples/textures/textures_sprite_explosion.c +++ b/examples/textures/textures_sprite_explosion.c @@ -39,8 +39,8 @@ int main(void) Texture2D explosion = LoadTexture("resources/explosion.png"); // Init variables for animation - float frameWidth = (float)(explosion.width/NUM_FRAMES_PER_LINE); // Sprite one frame rectangle width - float frameHeight = (float)(explosion.height/NUM_LINES); // Sprite one frame rectangle height + float frameWidth = (float)explosion.width/NUM_FRAMES_PER_LINE; // Sprite one frame rectangle width + float frameHeight = (float)explosion.height/NUM_LINES; // Sprite one frame rectangle height int currentFrame = 0; int currentLine = 0; diff --git a/src/rtextures.c b/src/rtextures.c index a20b5e516..a57f9037d 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -5602,4 +5602,4 @@ static Vector4 *LoadImageDataNormalized(Image image) return pixels; } -#endif // SUPPORT_MODULE_RTEXTURES +#endif // SUPPORT_MODULE_RTEXTURES \ No newline at end of file From 65cddc852eb9cfde70ca6409c30ca1b87f64dff2 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 25 Jan 2026 19:06:08 +0100 Subject: [PATCH 388/430] Reviewed comments --- src/platforms/rcore_desktop_glfw.c | 2 +- src/rcore.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index fd9632700..ffbbc6668 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1211,7 +1211,7 @@ void PollInputEvents(void) CORE.Input.Keyboard.charPressedQueueCount = 0; // Reset last gamepad button/axis registered state - CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN + CORE.Input.Gamepad.lastButtonPressed = GAMEPAD_BUTTON_UNKNOWN; //CORE.Input.Gamepad.axisCount = 0; // Keyboard/Mouse input polling (automatically managed by GLFW3 through callback) diff --git a/src/rcore.c b/src/rcore.c index aabf85022..8d2a9ca0a 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -4013,6 +4013,7 @@ bool IsGamepadButtonUp(int gamepad, int button) } // Get the last gamepad button pressed +// NOTE: Returns last gamepad button down, down->up change not considered int GetGamepadButtonPressed(void) { return CORE.Input.Gamepad.lastButtonPressed; From 3568b6e2932623e53da3194bb5f915fbdf999e1f Mon Sep 17 00:00:00 2001 From: ssszcmawo Date: Mon, 26 Jan 2026 12:04:22 +0100 Subject: [PATCH 389/430] [rtext] Fix and enhance `TextReplace()` function (#5511) * add check for replacement,replace strcpy,strncpy with memcpy * add 4 spaces in if statement * add spaces --- src/rtext.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/rtext.c b/src/rtext.c index 45aa19b13..2c871cc49 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1727,14 +1727,15 @@ char *GetTextBetween(const char *text, const char *begin, const char *end) // Replace text string // REQUIRES: strstr(), strncpy() -// TODO: If (replacement == "") remove "search" text // WARNING: Allocated memory must be manually freed char *TextReplace(const char *text, const char *search, const char *replacement) { char *result = NULL; if ((text != NULL) && (search != NULL)) - { + { + if (replacement == NULL) replacement = ""; + char *insertPoint = NULL; // Next insert point char *temp = NULL; // Temp pointer int textLen = 0; // Text string length @@ -1767,16 +1768,15 @@ char *TextReplace(const char *text, const char *search, const char *replacement) { insertPoint = (char *)strstr(text, search); lastReplacePos = (int)(insertPoint - text); - - // TODO: Review logic to avoid strcpy() - // OK - Those lines work - temp = strncpy(temp, text, lastReplacePos) + lastReplacePos; - temp = strcpy(temp, replacement) + replaceLen; - // WRONG - But not those ones - //temp = strncpy(temp, text, tempLen - 1) + lastReplacePos; - //tempLen -= lastReplacePos; - //temp = strncpy(temp, replacement, tempLen - 1) + replaceLen; - //tempLen -= replaceLen; + + memcpy(temp, text, lastReplacePos); + temp += lastReplacePos; + + if (replaceLen > 0) + { + memcpy(temp, replacement, replaceLen); + temp += replaceLen; + } text += lastReplacePos + searchLen; // Move to next "end of replace" } From 4c71625730fe4520266fad4036f8ba11f7740429 Mon Sep 17 00:00:00 2001 From: Maicon Santana Date: Mon, 26 Jan 2026 11:04:45 +0000 Subject: [PATCH 390/430] [CI] Removing double zip and misleading zip type (#5512) * Removing double zip and misleading zip type * Removing extra spaces --------- Co-authored-by: maiconpintoabreu --- .github/workflows/build_android.yml | 6 ++++-- .github/workflows/build_linux.yml | 6 ++++-- .github/workflows/build_macos.yml | 6 ++++-- .github/workflows/build_webassembly.yml | 6 ++++-- .github/workflows/build_windows.yml | 6 ++++-- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build_android.yml b/.github/workflows/build_android.yml index 80520d0b2..6993eb292 100644 --- a/.github/workflows/build_android.yml +++ b/.github/workflows/build_android.yml @@ -84,8 +84,10 @@ jobs: - name: Upload Artifacts uses: actions/upload-artifact@v4 with: - name: ${{ env.RELEASE_NAME }}.tar.gz - path: ./build/${{ env.RELEASE_NAME }}.tar.gz + name: ${{ env.RELEASE_NAME }} + path: | + ./build/${{ env.RELEASE_NAME }} + !./build/${{ env.RELEASE_NAME }}.tar.gz - name: Upload Artifact to Release uses: softprops/action-gh-release@v1 diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index a76345b90..b101750bc 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -114,8 +114,10 @@ jobs: - name: Upload Artifacts uses: actions/upload-artifact@v4 with: - name: ${{ env.RELEASE_NAME }}.tar.gz - path: ./build/${{ env.RELEASE_NAME }}.tar.gz + name: ${{ env.RELEASE_NAME }} + path: | + ./build/${{ env.RELEASE_NAME }} + !./build/${{ env.RELEASE_NAME }}.tar.gz - name: Upload Artifact to Release uses: softprops/action-gh-release@v1 diff --git a/.github/workflows/build_macos.yml b/.github/workflows/build_macos.yml index 965efe249..f27140107 100644 --- a/.github/workflows/build_macos.yml +++ b/.github/workflows/build_macos.yml @@ -101,8 +101,10 @@ jobs: - name: Upload Artifacts uses: actions/upload-artifact@v4 with: - name: ${{ env.RELEASE_NAME }}.tar.gz - path: ./build/${{ env.RELEASE_NAME }}.tar.gz + name: ${{ env.RELEASE_NAME }} + path: | + ./build/${{ env.RELEASE_NAME }} + !./build/${{ env.RELEASE_NAME }}.tar.gz - name: Upload Artifact to Release uses: softprops/action-gh-release@v1 diff --git a/.github/workflows/build_webassembly.yml b/.github/workflows/build_webassembly.yml index 32ae94215..d79b12b1c 100644 --- a/.github/workflows/build_webassembly.yml +++ b/.github/workflows/build_webassembly.yml @@ -71,8 +71,10 @@ jobs: - name: Upload Artifacts uses: actions/upload-artifact@v4 with: - name: ${{ env.RELEASE_NAME }}.zip - path: ./build/${{ env.RELEASE_NAME }}.zip + name: ${{ env.RELEASE_NAME }} + path: | + ./build/${{ env.RELEASE_NAME }} + !./build/${{ env.RELEASE_NAME }}.zip - name: Upload Artifact to Release uses: softprops/action-gh-release@v1 diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml index 7a92c208a..988428403 100644 --- a/.github/workflows/build_windows.yml +++ b/.github/workflows/build_windows.yml @@ -142,8 +142,10 @@ jobs: - name: Upload Artifacts uses: actions/upload-artifact@v4 with: - name: ${{ env.RELEASE_NAME }}.zip - path: ./build/${{ env.RELEASE_NAME }}.zip + name: ${{ env.RELEASE_NAME }} + path: | + ./build/${{ env.RELEASE_NAME }} + !./build/${{ env.RELEASE_NAME }}.zip - name: Upload Artifact to Release uses: softprops/action-gh-release@v1 From 63e4fd838d5e201799eec70fa826022f08b9b3bf Mon Sep 17 00:00:00 2001 From: mikeemm <42421968+mikeemm@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:38:51 +0100 Subject: [PATCH 391/430] fixed typos preventing launch of native win32 backend (#5515) --- src/platforms/rcore_desktop_win32.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 28094b53f..70ebd8b92 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -1227,7 +1227,7 @@ void SwapScreenBuffer(void) // Get elapsed time measure in seconds double GetTime(void) { - LARGE_INTEGER now = 0; + LARGE_INTEGER now = { 0 }; QueryPerformanceCounter(&now); return (double)(now.QuadPart - CORE.Time.base)/(double)platform.timerFrequency.QuadPart; } @@ -1875,8 +1875,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara case WM_DPICHANGED: { // Get current dpi scale factor - float scalex = HIWORD(wParam)/96.0f; - float scaley = LOWORD(wParam)/96.0f; + float scalex = HIWORD(wparam)/96.0f; + float scaley = LOWORD(wparam)/96.0f; RECT *suggestedRect = (RECT *)lparam; From d0a6892989752ec508afa58facf8736011df20ba Mon Sep 17 00:00:00 2001 From: Jason Mao <64656764+jasoncnm@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:26:07 -0500 Subject: [PATCH 392/430] [rcore] `IsMouseButton*()`, random key codes return unexpected results (#5516) * update * update * stuff * update * move headerfile to root * delete .h * update ignore * fix IsMouseButtonDown\Pressed\Released\Up will get randomly returned to true when the button code is outside the range of mouse button * remove unessary macro * refactor IsMouseButton*() early returns --- src/rcore.c | 60 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 8d2a9ca0a..42d92547e 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -4052,12 +4052,17 @@ float GetGamepadAxisMovement(int gamepad, int axis) bool IsMouseButtonPressed(int button) { bool pressed = false; + + if ((button >= 0) && (button <= MOUSE_BUTTON_BACK)) + { + + if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) pressed = true; - if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) pressed = true; - - // Map touches to mouse buttons checking - if ((CORE.Input.Touch.currentTouchState[button] == 1) && (CORE.Input.Touch.previousTouchState[button] == 0)) pressed = true; - + // Map touches to mouse buttons checking + if ((CORE.Input.Touch.currentTouchState[button] == 1) && (CORE.Input.Touch.previousTouchState[button] == 0)) pressed = true; + + } + return pressed; } @@ -4065,12 +4070,17 @@ bool IsMouseButtonPressed(int button) bool IsMouseButtonDown(int button) { bool down = false; + + if ((button >= 0) && (button <= MOUSE_BUTTON_BACK)) + { + + if (CORE.Input.Mouse.currentButtonState[button] == 1) down = true; - if (CORE.Input.Mouse.currentButtonState[button] == 1) down = true; - - // NOTE: Touches are considered like mouse buttons - if (CORE.Input.Touch.currentTouchState[button] == 1) down = true; - + // NOTE: Touches are considered like mouse buttons + if (CORE.Input.Touch.currentTouchState[button] == 1) down = true; + + } + return down; } @@ -4078,12 +4088,17 @@ bool IsMouseButtonDown(int button) bool IsMouseButtonReleased(int button) { bool released = false; + + if ((button >= 0) && (button <= MOUSE_BUTTON_BACK)) + { + + if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) released = true; - if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) released = true; - - // Map touches to mouse buttons checking - if ((CORE.Input.Touch.currentTouchState[button] == 0) && (CORE.Input.Touch.previousTouchState[button] == 1)) released = true; - + // Map touches to mouse buttons checking + if ((CORE.Input.Touch.currentTouchState[button] == 0) && (CORE.Input.Touch.previousTouchState[button] == 1)) released = true; + + } + return released; } @@ -4091,12 +4106,17 @@ bool IsMouseButtonReleased(int button) bool IsMouseButtonUp(int button) { bool up = false; + + if ((button >= 0) && (button <= MOUSE_BUTTON_BACK)) + { + + if (CORE.Input.Mouse.currentButtonState[button] == 0) up = true; - if (CORE.Input.Mouse.currentButtonState[button] == 0) up = true; - - // NOTE: Touches are considered like mouse buttons - if (CORE.Input.Touch.currentTouchState[button] == 0) up = true; - + // NOTE: Touches are considered like mouse buttons + if (CORE.Input.Touch.currentTouchState[button] == 0) up = true; + + } + return up; } From af37fa2a96c091799cb7877de4d122e154ebd1bb Mon Sep 17 00:00:00 2001 From: Maicon Santana Date: Wed, 28 Jan 2026 18:27:03 +0000 Subject: [PATCH 393/430] Refactoring based on Coding Style Conventions (#5517) Co-authored-by: maiconpintoabreu --- src/platforms/rcore_desktop_glfw.c | 10 +++++----- src/platforms/rcore_desktop_sdl.c | 2 +- src/rmodels.c | 2 +- src/rtextures.c | 4 ++-- tools/rlparser/rlparser.c | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index ffbbc6668..e3078dacf 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -100,7 +100,7 @@ #include // Required for: usleep() //#define GLFW_EXPOSE_NATIVE_COCOA // WARNING: Fails due to type redefinition - void *glfwGetCocoaWindow(GLFWwindow* handle); + void *glfwGetCocoaWindow(GLFWwindow *handle); #include "GLFW/glfw3native.h" // Required for: glfwGetCocoaWindow() #endif @@ -224,8 +224,8 @@ void ToggleFullscreen(void) if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { Vector2 scaleDpi = GetWindowScaleDPI(); - CORE.Window.screen.width = (unsigned int)(CORE.Window.screen.width * scaleDpi.x); - CORE.Window.screen.height = (unsigned int)(CORE.Window.screen.height * scaleDpi.y); + CORE.Window.screen.width = (unsigned int)(CORE.Window.screen.width*scaleDpi.x); + CORE.Window.screen.height = (unsigned int)(CORE.Window.screen.height*scaleDpi.y); } #endif @@ -303,8 +303,8 @@ void ToggleBorderlessWindowed(void) if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) { Vector2 scaleDpi = GetWindowScaleDPI(); - CORE.Window.screen.width = (unsigned int)(CORE.Window.screen.width * scaleDpi.x); - CORE.Window.screen.height = (unsigned int)(CORE.Window.screen.height * scaleDpi.y); + CORE.Window.screen.width = (unsigned int)(CORE.Window.screen.width*scaleDpi.x); + CORE.Window.screen.height = (unsigned int)(CORE.Window.screen.height*scaleDpi.y); } #endif diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 45bb30c65..66a917da2 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -323,7 +323,7 @@ Uint8 SDL_EventState(Uint32 type, int state) return stateBefore; } -void SDL_GetCurrentDisplayMode_Adapter(SDL_DisplayID displayID, SDL_DisplayMode* mode) +void SDL_GetCurrentDisplayMode_Adapter(SDL_DisplayID displayID, SDL_DisplayMode *mode) { const SDL_DisplayMode *currentMode = SDL_GetCurrentDisplayMode(displayID); diff --git a/src/rmodels.c b/src/rmodels.c index 26fc7bcf8..2988dfaba 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -6517,7 +6517,7 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCo }; } - Transform* root = &animations[i].framePoses[j][0]; + Transform *root = &animations[i].framePoses[j][0]; root->rotation = QuaternionMultiply(worldTransform.rotation, root->rotation); root->scale = Vector3Multiply(root->scale, worldTransform.scale); root->translation = Vector3Multiply(root->translation, worldTransform.scale); diff --git a/src/rtextures.c b/src/rtextures.c index a57f9037d..1aced0533 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -3625,7 +3625,7 @@ void ImageDrawLineEx(Image *dst, Vector2 start, Vector2 end, int thick, Color co } // Draw circle within an image -void ImageDrawCircle(Image* dst, int centerX, int centerY, int radius, Color color) +void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color) { int x = 0; int y = radius; @@ -3649,7 +3649,7 @@ void ImageDrawCircle(Image* dst, int centerX, int centerY, int radius, Color col } // Draw circle within an image (Vector version) -void ImageDrawCircleV(Image* dst, Vector2 center, int radius, Color color) +void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color) { ImageDrawCircle(dst, (int)center.x, (int)center.y, radius, color); } diff --git a/tools/rlparser/rlparser.c b/tools/rlparser/rlparser.c index c291c3038..899ba7db6 100644 --- a/tools/rlparser/rlparser.c +++ b/tools/rlparser/rlparser.c @@ -198,7 +198,7 @@ static void ExportParsedData(const char *fileName, int format); // Export parsed //---------------------------------------------------------------------------------- // Program main entry point //---------------------------------------------------------------------------------- -int main(int argc, char* argv[]) +int main(int argc, char *argv[]) { if (argc > 1) ProcessCommandLine(argc, argv); From 8a2da96eed62171ebfeaceed61907b0ec1069549 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 28 Jan 2026 19:34:55 +0100 Subject: [PATCH 394/430] Reviewed formating and spacing --- src/rcore.c | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 42d92547e..503eea086 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -1936,7 +1936,7 @@ void TraceLog(int logType, const char *text, ...) // Set custom trace log void SetTraceLogCallback(TraceLogCallback callback) -{ +{ traceLog = callback; } @@ -2771,7 +2771,7 @@ FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool { // SCAN 1: Count files unsigned int fileCounter = GetDirectoryFileCountEx(basePath, filter, scanSubdirs); - + // Memory allocation for dirFileCount files.paths = (char **)RL_CALLOC(fileCounter, sizeof(char *)); for (unsigned int i = 0; i < fileCounter; i++) files.paths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); @@ -3670,13 +3670,13 @@ bool ExportAutomationEventList(AutomationEventList list, const char *fileName) int binarySize = 4 + sizeof(int) + sizeof(AutomationEvent)*list.count; unsigned char *binBuffer = (unsigned char *)RL_CALLOC(binarySize, 1); int offset = 0; - memcpy(binBuffer + offset, "rAE ", 4); + memcpy(binBuffer + offset, "rAE ", 4); offset += 4; - memcpy(binBuffer + offset, &list.count, sizeof(int)); + memcpy(binBuffer + offset, &list.count, sizeof(int)); offset += sizeof(int); memcpy(binBuffer + offset, list.events, sizeof(AutomationEvent)*list.count); offset += sizeof(AutomationEvent)*list.count; - + success = SaveFileData(TextFormat("%s.rae",fileName), binBuffer, binarySize); RL_FREE(binBuffer); } @@ -3831,7 +3831,6 @@ void PlayAutomationEvent(AutomationEvent event) // Check if a key has been pressed once bool IsKeyPressed(int key) { - bool pressed = false; if ((key > 0) && (key < MAX_KEYBOARD_KEYS)) @@ -4052,17 +4051,15 @@ float GetGamepadAxisMovement(int gamepad, int axis) bool IsMouseButtonPressed(int button) { bool pressed = false; - + if ((button >= 0) && (button <= MOUSE_BUTTON_BACK)) { - if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) pressed = true; // Map touches to mouse buttons checking if ((CORE.Input.Touch.currentTouchState[button] == 1) && (CORE.Input.Touch.previousTouchState[button] == 0)) pressed = true; - } - + return pressed; } @@ -4070,17 +4067,15 @@ bool IsMouseButtonPressed(int button) bool IsMouseButtonDown(int button) { bool down = false; - + if ((button >= 0) && (button <= MOUSE_BUTTON_BACK)) { - if (CORE.Input.Mouse.currentButtonState[button] == 1) down = true; // NOTE: Touches are considered like mouse buttons if (CORE.Input.Touch.currentTouchState[button] == 1) down = true; - } - + return down; } @@ -4088,17 +4083,15 @@ bool IsMouseButtonDown(int button) bool IsMouseButtonReleased(int button) { bool released = false; - + if ((button >= 0) && (button <= MOUSE_BUTTON_BACK)) { - if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) released = true; // Map touches to mouse buttons checking if ((CORE.Input.Touch.currentTouchState[button] == 0) && (CORE.Input.Touch.previousTouchState[button] == 1)) released = true; - } - + return released; } @@ -4106,17 +4099,15 @@ bool IsMouseButtonReleased(int button) bool IsMouseButtonUp(int button) { bool up = false; - + if ((button >= 0) && (button <= MOUSE_BUTTON_BACK)) { - if (CORE.Input.Mouse.currentButtonState[button] == 0) up = true; // NOTE: Touches are considered like mouse buttons if (CORE.Input.Touch.currentTouchState[button] == 0) up = true; - } - + return up; } @@ -4124,6 +4115,7 @@ bool IsMouseButtonUp(int button) int GetMouseX(void) { int mouseX = (int)((CORE.Input.Mouse.currentPosition.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x); + return mouseX; } @@ -4131,6 +4123,7 @@ int GetMouseX(void) int GetMouseY(void) { int mouseY = (int)((CORE.Input.Mouse.currentPosition.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y); + return mouseY; } @@ -4490,7 +4483,7 @@ static void RecordAutomationEvent(void) if (currentEventList->count == currentEventList->capacity) return; // Security check - // Event type: INPUT_TOUCH_POSITION + // Event type: INPUT_TOUCH_POSITION if (((int)CORE.Input.Touch.position[id].x != (int)CORE.Input.Touch.previousPosition[id].x) || ((int)CORE.Input.Touch.position[id].y != (int)CORE.Input.Touch.previousPosition[id].y)) { @@ -4503,7 +4496,7 @@ static void RecordAutomationEvent(void) TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_TOUCH_POSITION | 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]); currentEventList->count++; } - + if (currentEventList->count == currentEventList->capacity) return; // Security check } From de7fc12be03f8c6b960f41b7ee9f86abf3890771 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 28 Jan 2026 19:35:54 +0100 Subject: [PATCH 395/430] REVIEWED: `IsGamepadButton*()` for consistency with key and mouse equivalents --- src/rcore.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 503eea086..810915040 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -3972,8 +3972,10 @@ bool IsGamepadButtonPressed(int gamepad, int button) { bool pressed = false; - if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && - (CORE.Input.Gamepad.previousButtonState[gamepad][button] == 0) && (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 1)) pressed = true; + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS)) + { + if ((CORE.Input.Gamepad.previousButtonState[gamepad][button] == 0) && (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 1)) pressed = true; + } return pressed; } @@ -3983,8 +3985,10 @@ bool IsGamepadButtonDown(int gamepad, int button) { bool down = false; - if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && - (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 1)) down = true; + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS)) + { + if (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 1) down = true; + } return down; } @@ -3994,8 +3998,10 @@ bool IsGamepadButtonReleased(int gamepad, int button) { bool released = false; - if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && - (CORE.Input.Gamepad.previousButtonState[gamepad][button] == 1) && (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 0)) released = true; + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS)) + { + if ((CORE.Input.Gamepad.previousButtonState[gamepad][button] == 1) && (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 0)) released = true; + } return released; } @@ -4005,8 +4011,10 @@ bool IsGamepadButtonUp(int gamepad, int button) { bool up = false; - if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && - (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 0)) up = true; + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS)) + { + if (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 0) up = true; + } return up; } From 08e79a16b01816a9ea730cef9ec0a437081f438d Mon Sep 17 00:00:00 2001 From: Maicon Santana Date: Thu, 29 Jan 2026 16:30:03 +0000 Subject: [PATCH 396/430] Refactoring {0} to { 0 } to follow conventions (#5519) Co-authored-by: maiconpintoabreu --- examples/core/msf_gif.h | 6 +++--- examples/models/models_decals.c | 4 ++-- examples/shaders/shaders_hybrid_rendering.c | 2 +- examples/shaders/shaders_vertex_displacement.c | 2 +- examples/shapes/shapes_ball_physics.c | 2 +- examples/shapes/shapes_pie_chart.c | 4 ++-- examples/text/text_3d_drawing.c | 4 ++-- examples/text/text_strings_management.c | 2 +- src/platforms/rcore_android.c | 4 ++-- src/platforms/rcore_drm.c | 8 ++++---- tools/rexm/rexm.c | 2 +- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/examples/core/msf_gif.h b/examples/core/msf_gif.h index bc2c6edef..6aa11fdbd 100644 --- a/examples/core/msf_gif.h +++ b/examples/core/msf_gif.h @@ -413,7 +413,7 @@ static MsfGifBuffer * msf_compress_frame(void * allocContext, int width, int hei //generate palette typedef struct { uint8_t r, g, b; } Color3; - Color3 table[256] = { {0} }; + Color3 table[256] = { { 0 } }; int tableIdx = 1; //we start counting at 1 because 0 is the transparent color //transparent is always last in the table tlb[tlbSize-1] = 0; @@ -550,7 +550,7 @@ static void msf_free_gif_state(MsfGifState * handle) { int msf_gif_begin(MsfGifState * handle, int width, int height) { MsfTimeFunc //NOTE: we cannot stomp the entire struct to zero because we must preserve `customAllocatorContext`. - MsfCookedFrame empty = {0}; //god I hate MSVC... + MsfCookedFrame empty = { 0 }; //god I hate MSVC... handle->previousFrame = empty; handle->currentFrame = empty; handle->width = width; @@ -614,7 +614,7 @@ int msf_gif_frame(MsfGifState * handle, uint8_t * pixelData, int centiSecondsPer } MsfGifResult msf_gif_end(MsfGifState * handle) { MsfTimeFunc - if (!handle->listHead) { MsfGifResult empty = {0}; return empty; } + if (!handle->listHead) { MsfGifResult empty = { 0 }; return empty; } //first pass: determine total size size_t total = 1; //1 byte for trailing marker diff --git a/examples/models/models_decals.c b/examples/models/models_decals.c index 71122dd68..9eba33de6 100644 --- a/examples/models/models_decals.c +++ b/examples/models/models_decals.c @@ -183,7 +183,7 @@ int main(void) if (showModel) DrawModel(model, (Vector3){0.0f, 0.0f, 0.0f}, 1.0f, WHITE); // Draw the decal models - for (int i = 0; i < decalCount; i++) DrawModel(decalModels[i], (Vector3){0}, 1.0f, WHITE); + for (int i = 0; i < decalCount; i++) DrawModel(decalModels[i], (Vector3){ 0 }, 1.0f, WHITE); // If we hit the mesh, draw the box for the decal if (collision.hit) @@ -191,7 +191,7 @@ int main(void) Vector3 origin = Vector3Add(collision.point, Vector3Scale(collision.normal, 1.0f)); Matrix splat = MatrixLookAt(collision.point, origin, (Vector3){0,1,0}); placementCube.transform = MatrixInvert(splat); - DrawModel(placementCube, (Vector3){0}, 1.0f, Fade(WHITE, 0.5f)); + DrawModel(placementCube, (Vector3){ 0 }, 1.0f, Fade(WHITE, 0.5f)); } DrawGrid(10, 10.0f); diff --git a/examples/shaders/shaders_hybrid_rendering.c b/examples/shaders/shaders_hybrid_rendering.c index 439965fd6..ca6e93d9c 100644 --- a/examples/shaders/shaders_hybrid_rendering.c +++ b/examples/shaders/shaders_hybrid_rendering.c @@ -65,7 +65,7 @@ int main(void) Shader shdrRaster = LoadShader(0, TextFormat("resources/shaders/glsl%i/hybrid_raster.fs", GLSL_VERSION)); // Declare Struct used to store camera locs - RayLocs marchLocs = {0}; + RayLocs marchLocs = { 0 }; // Fill the struct with shader locs marchLocs.camPos = GetShaderLocation(shdrRaymarch, "camPos"); diff --git a/examples/shaders/shaders_vertex_displacement.c b/examples/shaders/shaders_vertex_displacement.c index 3eff34e00..8978b0fce 100644 --- a/examples/shaders/shaders_vertex_displacement.c +++ b/examples/shaders/shaders_vertex_displacement.c @@ -41,7 +41,7 @@ int main(void) InitWindow(screenWidth, screenHeight, "raylib [shaders] example - vertex displacement"); // set up camera - Camera camera = {0}; + 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}; diff --git a/examples/shapes/shapes_ball_physics.c b/examples/shapes/shapes_ball_physics.c index 0c98ccf9d..293be1cd5 100644 --- a/examples/shapes/shapes_ball_physics.c +++ b/examples/shapes/shapes_ball_physics.c @@ -58,7 +58,7 @@ int main(void) int ballCount = 1; Ball *grabbedBall = NULL; // A pointer to the current ball that is grabbed - Vector2 pressOffset = {0}; // Mouse press offset relative to the ball that grabbedd + Vector2 pressOffset = { 0 }; // Mouse press offset relative to the ball that grabbedd float gravity = 100; // World gravity diff --git a/examples/shapes/shapes_pie_chart.c b/examples/shapes/shapes_pie_chart.c index 6db7f96da..baf0a3652 100644 --- a/examples/shapes/shapes_pie_chart.c +++ b/examples/shapes/shapes_pie_chart.c @@ -49,8 +49,8 @@ int main(void) bool showPercentages = false; bool showDonut = false; int hoveredSlice = -1; - Rectangle scrollPanelBounds = {0}; - Vector2 scrollContentOffset = {0}; + Rectangle scrollPanelBounds = { 0 }; + Vector2 scrollContentOffset = { 0 }; Rectangle view = { 0 }; // UI layout parameters diff --git a/examples/text/text_3d_drawing.c b/examples/text/text_3d_drawing.c index 80b617b2e..aebf33837 100644 --- a/examples/text/text_3d_drawing.c +++ b/examples/text/text_3d_drawing.c @@ -113,7 +113,7 @@ int main(void) // Set the text (using markdown!) char text[64] = "Hello ~~World~~ in 3D!"; - Vector3 tbox = {0}; + Vector3 tbox = { 0 }; int layers = 1; int quads = 0; float layerDistance = 0.01f; @@ -133,7 +133,7 @@ int main(void) Shader alphaDiscard = LoadShader(NULL, TextFormat("resources/shaders/glsl%i/alpha_discard.fs", GLSL_VERSION)); // Array filled with multiple random colors (when multicolor mode is set) - Color multi[TEXT_MAX_LAYERS] = {0}; + Color multi[TEXT_MAX_LAYERS] = { 0 }; DisableCursor(); // Limit cursor to relative movement inside the window diff --git a/examples/text/text_strings_management.c b/examples/text/text_strings_management.c index e4a7ab2af..db4540081 100644 --- a/examples/text/text_strings_management.c +++ b/examples/text/text_strings_management.c @@ -65,7 +65,7 @@ int main(void) TextParticle textParticles[MAX_TEXT_PARTICLES] = { 0 }; int particleCount = 0; TextParticle *grabbedTextParticle = NULL; - Vector2 pressOffset = {0}; + Vector2 pressOffset = { 0 }; PrepareFirstTextParticle("raylib => fun videogames programming!", textParticles, &particleCount); diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 65d1c2ddf..65236be0f 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -886,8 +886,8 @@ void ClosePlatform(void) // NOTE: Reset global state in case the activity is being relaunched if (platform.app->destroyRequested != 0) { - CORE = (CoreData){0}; - platform = (PlatformData){0}; + CORE = (CoreData){ 0 }; + platform = (PlatformData){ 0 }; } } diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index b296e0042..68431030b 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -915,7 +915,7 @@ void SwapScreenBuffer(void) { TRACELOG(LOG_ERROR, "DISPLAY: Failed to get DRM resources"); drmModeRmFB(platform.fd, fb); - struct drm_mode_destroy_dumb dreq = {0}; + struct drm_mode_destroy_dumb dreq = { 0 }; dreq.handle = creq.handle; drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); return; @@ -955,7 +955,7 @@ void SwapScreenBuffer(void) { TRACELOG(LOG_ERROR, "DISPLAY: No compatible CRTC found"); drmModeRmFB(platform.fd, fb); - struct drm_mode_destroy_dumb dreq = {0}; + struct drm_mode_destroy_dumb dreq = { 0 }; dreq.handle = creq.handle; drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); return; @@ -971,7 +971,7 @@ void SwapScreenBuffer(void) TRACELOG(LOG_ERROR, "DISPLAY: Mode: %dx%d@%d", mode->hdisplay, mode->vdisplay, mode->vrefresh); drmModeRmFB(platform.fd, fb); - struct drm_mode_destroy_dumb dreq = {0}; + struct drm_mode_destroy_dumb dreq = { 0 }; dreq.handle = creq.handle; drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); return; @@ -989,7 +989,7 @@ void SwapScreenBuffer(void) // Clean up previous dumb buffer if (platform.prevDumbHandle) { - struct drm_mode_destroy_dumb dreq = {0}; + struct drm_mode_destroy_dumb dreq = { 0 }; dreq.handle = platform.prevDumbHandle; drmIoctl(platform.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); } diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index ad9bcbed0..70001a5f3 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1556,7 +1556,7 @@ int main(int argc, char *argv[]) "#include \n" "#include \n" "#include \n\n" - "static char logText[4096] = {0};\n" + "static char logText[4096] = { 0 };\n" "static int logTextOffset = 0;\n\n" "void CustomTraceLog(int msgType, const char *text, va_list args)\n{\n" " if (logTextOffset < 3800)\n {\n" From d5ae12f3eb1f625a2cd65447ca4fa48db1e0fd74 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 29 Jan 2026 19:50:59 +0100 Subject: [PATCH 397/430] Update raylib_1024x1024.png --- logo/raylib_1024x1024.png | Bin 4591 -> 4591 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/logo/raylib_1024x1024.png b/logo/raylib_1024x1024.png index 3930aeb79e595f2571e1035391bc4c1e32bf059c..9b5a808ffffd2310416ed81c4b322d0a773c26e1 100644 GIT binary patch delta 24 gcmaE_{9bv2^5k`b<{OoJ1UVTzUHx3vIVCg!0C+(N%>V!Z delta 24 fcmaE_{9bv2^2Vqh!HFtnTnr4Ju6{1-oD!M Date: Thu, 29 Jan 2026 19:51:04 +0100 Subject: [PATCH 398/430] Update raylib_144x144.png --- logo/raylib_144x144.png | Bin 475 -> 490 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/logo/raylib_144x144.png b/logo/raylib_144x144.png index f89d90b23ce52f7c11155258738a45d48bb47ee5..011214b61f7ac37d5e3da765371f83dcb00e10b1 100644 GIT binary patch delta 302 zcmV+}0nz^31L^~iSx9_IL_t(|+U(gej>0eyKv5=2>HDA5bwrU8VoZ>2oV;J51*BCT zGZ=`7cxav$mjIZ62^cT|6EI-v+FH0T=j;LvDWzn>tVEH&8wO0kfC-p@k>DYbygGjx z-RcrlmQvbp`zqLBIZQJ`PSr!v!CCW-1ey}uQ0;bEa#HOb*6DD9*;;gT}?^oZ66XyPu zn$PtuRg8%>k07*qoM6N<$f_#^Y A(EtDd delta 307 zcmaFGe4BYfq<^lbi(^Q|tv9zE^9~sZI0WWA{pT)sut~PdaATXr$KT#uLT69&EX&ed zYEv?Il{bUW0T!Ma4V)4Nf48pvxVfXC@tox1DBl@VH4>QEKyniwicEZLHSv{jy;{Nn z7M}wRoHH62g$)>))j$f;x9?=xyS=RT{vE-Kbv5s2s-5wgGPUBj(YJY=#)&`Yzx})Y z+X?S)&ldlGaG~Pab@Q?WE8iK;?bmEdzO3*5x7PIJdV^1OK0wRvAAWYHLe1yEnj Date: Sat, 31 Jan 2026 23:17:55 +0100 Subject: [PATCH 399/430] fixed win32 vsync flag not being applied (#5521) --- src/platforms/rcore_desktop_win32.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 70ebd8b92..4e39d554d 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -2149,10 +2149,10 @@ static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height) // Flags that just apply immediately without needing any operations CORE.Window.flags |= (desiredFlags & FLAG_MASK_NO_UPDATE); - int vsync = (CORE.Window.flags & FLAG_VSYNC_HINT)? 1 : 0; + int vsync = (desiredFlags & FLAG_VSYNC_HINT)? 1 : 0; if (wglSwapIntervalEXT) { - (*wglSwapIntervalEXT)(vsync); + wglSwapIntervalEXT(vsync); if (vsync) CORE.Window.flags |= FLAG_VSYNC_HINT; else CORE.Window.flags &= ~FLAG_VSYNC_HINT; } From 403c2cbccff44cac0c0bbae034c00191eeee7b4c Mon Sep 17 00:00:00 2001 From: Eddy Jansson Date: Sat, 31 Jan 2026 23:18:52 +0100 Subject: [PATCH 400/430] trivial: Correct typo in log message. (#5523) * trivial: Correct typo in log message. * trivial: Correct typo in rlparser. --- src/platforms/rcore_desktop_win32.c | 2 +- tools/rlparser/rlparser.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 4e39d554d..5913dfac6 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -1777,7 +1777,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara { // looks like windows will automatically "unminimize" a window // if a style changes modifies it's size - TRACELOG(LOG_INFO, "WIN32: WINDOW: Style change modifed window size, removing maximized flag"); + TRACELOG(LOG_INFO, "WIN32: WINDOW: Style change modified window size, removing maximized flag"); deferredFlags->clear |= FLAG_WINDOW_MAXIMIZED; } } diff --git a/tools/rlparser/rlparser.c b/tools/rlparser/rlparser.c index 899ba7db6..f96dcc0bd 100644 --- a/tools/rlparser/rlparser.c +++ b/tools/rlparser/rlparser.c @@ -260,7 +260,7 @@ int main(int argc, char *argv[]) for (int i = 0; i < lineCount; i++) { int j = 0; - while ((lines[i][j] == ' ') || (lines[i][j] == '\t')) j++; // skip spaces and tabs in the begining + while ((lines[i][j] == ' ') || (lines[i][j] == '\t')) j++; // skip spaces and tabs in the beginning // Read define line if (IsTextEqual(lines[i]+j, "#define ", 8)) { @@ -385,7 +385,7 @@ int main(int argc, char *argv[]) char *linePtr = lines[defineLines[i]]; int j = 0; - while ((linePtr[j] == ' ') || (linePtr[j] == '\t')) j++; // Skip spaces and tabs in the begining + while ((linePtr[j] == ' ') || (linePtr[j] == '\t')) j++; // Skip spaces and tabs in the beginning j += 8; // Skip "#define " while ((linePtr[j] == ' ') || (linePtr[j] == '\t')) j++; // Skip spaces and tabs after "#define " From 33dcd6266386f583b81a83ff991ae29367978cf0 Mon Sep 17 00:00:00 2001 From: Aly Date: Mon, 2 Feb 2026 03:21:10 -0700 Subject: [PATCH 401/430] Added documentation comments for (#5525) --- src/rcore.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rcore.c b/src/rcore.c index 810915040..4cae00f01 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2762,6 +2762,8 @@ FilePathList LoadDirectoryFiles(const char *dirPath) } // Load directory filepaths with extension filtering and recursive directory scan +// Use 'DIR*' to include directories on directory scan +// Use '*.*' to include all file types and directories on directory scan // WARNING: Directory is scanned twice, first time to get files count FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs) { From 54b12ed56db460fda18d72e8c228a0c2028215f9 Mon Sep 17 00:00:00 2001 From: Thomas Anderson <5776225+CrackedPixel@users.noreply.github.com> Date: Tue, 3 Feb 2026 16:11:20 -0600 Subject: [PATCH 402/430] update cmake for rgfw (#5527) --- cmake/LibraryConfigurations.cmake | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cmake/LibraryConfigurations.cmake b/cmake/LibraryConfigurations.cmake index 9b8fbdb25..ffc12edda 100644 --- a/cmake/LibraryConfigurations.cmake +++ b/cmake/LibraryConfigurations.cmake @@ -149,6 +149,24 @@ elseif ("${PLATFORM}" MATCHES "SDL") endif() elseif ("${PLATFORM}" MATCHES "RGFW") set(PLATFORM_CPP "PLATFORM_DESKTOP_RGFW") + + if (APPLE) + find_library(COCOA Cocoa) + find_library(OPENGL OpenGL) + + set(LIBS_PRIVATE ${COCOA} ${OPENGL}) + elseif (WIN32) + find_package(OpenGL REQUIRED) + + set(LIBS_PRIVATE ${OPENGL_LIBRARIES} gdi32) + elseif("${CMAKE_SYSTEM_NAME}" MATCHES "QNX") + message(FATAL_ERROR "RGFW platform does not support QNX. Use PLATFORM=Desktop or PLATFORM=SDL instead.") + elseif (UNIX) + find_package(X11 REQUIRED) + find_package(OpenGL REQUIRED) + + set(LIBS_PRIVATE ${X11_LIBRARIES} ${OPENGL_LIBRARIES}) + endif () endif () if (NOT ${OPENGL_VERSION} MATCHES "OFF") From ccfa3f762a4548e893229bfd9e973357c5eae8ff Mon Sep 17 00:00:00 2001 From: Thomas Anderson <5776225+CrackedPixel@users.noreply.github.com> Date: Tue, 3 Feb 2026 16:12:13 -0600 Subject: [PATCH 403/430] fixed an issue when using an empty window title (#5526) --- src/platforms/rcore_desktop_rgfw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index d1518b909..dbc5e0132 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -1290,7 +1290,7 @@ int InitPlatform(void) if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) FLAG_SET(flags, RGFW_windowFocusOnShow | RGFW_windowFocus); - platform.window = RGFW_createWindow(CORE.Window.title, RGFW_RECT(0, 0, CORE.Window.screen.width, CORE.Window.screen.height), flags); + platform.window = RGFW_createWindow((CORE.Window.title != 0)? CORE.Window.title : " ", RGFW_RECT(0, 0, CORE.Window.screen.width, CORE.Window.screen.height), flags); platform.mon.mode.area.w = 0; if (platform.window != NULL) From 4c1efc2bd3a9b8cb6d399547022813d73c732ba2 Mon Sep 17 00:00:00 2001 From: mikeemm <42421968+mikeemm@users.noreply.github.com> Date: Wed, 4 Feb 2026 19:37:12 +0100 Subject: [PATCH 404/430] [rcore] Fix native win32 window minimizing/maximizing (#5524) * fixed typos preventing window from min/maxing * fixed window style generation ignoring minimize precedence, causing errors in edge cases * added maximize button on resizable windows * fixed infinite loop when resizing the window manually * activate window upon creation to set focus and show taskbar icon * extended SanitizeFlags() to account for problematic resizing/mizing flag mixups --- src/platforms/rcore_desktop_win32.c | 57 +++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 5913dfac6..fd10fca04 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -141,7 +141,7 @@ static PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = NULL; #define STYLE_MASK_READONLY (WS_MINIMIZE | WS_MAXIMIZE) #define STYLE_MASK_WRITABLE (~STYLE_MASK_READONLY) -#define STYLE_FLAGS_RESIZABLE WS_THICKFRAME +#define STYLE_FLAGS_RESIZABLE (WS_THICKFRAME | WS_MAXIMIZEBOX) #define STYLE_FLAGS_UNDECORATED_OFF (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX) #define STYLE_FLAGS_UNDECORATED_ON WS_POPUP @@ -270,8 +270,8 @@ static DWORD MakeWindowStyle(unsigned flags) // Minimized takes precedence over maximized int mized = MIZED_NONE; - if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) mized = MIZED_MIN; - if (flags & FLAG_WINDOW_MAXIMIZED) mized = MIZED_MAX; + if (flags & FLAG_WINDOW_MINIMIZED) mized = MIZED_MIN; + else if (flags & FLAG_WINDOW_MAXIMIZED) mized = MIZED_MAX; switch (mized) { @@ -1590,8 +1590,6 @@ int InitPlatform(void) if (rlGetVersion() == RL_OPENGL_11_SOFTWARE) // Using software renderer { - //ShowWindow(platform.hwnd, SW_SHOWDEFAULT); //SW_SHOWNORMAL - // Initialize software framebuffer BITMAPINFO bmi = { 0 }; ZeroMemory(&bmi, sizeof(bmi)); @@ -1620,6 +1618,9 @@ int InitPlatform(void) CORE.Window.ready = true; + // Activate window to set focus and show taskbar icon + ShowWindow(platform.hwnd, SW_SHOWDEFAULT); + // Update flags (in case of deferred state change required) UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight); @@ -1916,6 +1917,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara EndPaint(hwnd, &ps); } + else DefWindowProc(hwnd, msg, wparam, lparam); } case WM_INPUT: { @@ -2090,10 +2092,10 @@ static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags) // Minimized takes precedence over maximized Mized currentMized = MIZED_NONE; Mized desiredMized = MIZED_NONE; - if (CORE.Window.flags & WS_MINIMIZE) currentMized = MIZED_MIN; - else if (CORE.Window.flags & WS_MAXIMIZE) currentMized = MIZED_MAX; - if (desiredFlags & WS_MINIMIZE) currentMized = MIZED_MIN; - else if (desiredFlags & WS_MAXIMIZE) currentMized = MIZED_MAX; + if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) currentMized = MIZED_MIN; + else if (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) currentMized = MIZED_MAX; + if (desiredFlags & FLAG_WINDOW_MINIMIZED) desiredMized = MIZED_MIN; + else if (desiredFlags & FLAG_WINDOW_MAXIMIZED) desiredMized = MIZED_MAX; if (currentMized != desiredMized) { @@ -2109,10 +2111,41 @@ static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags) // Sanitize flags static unsigned SanitizeFlags(int mode, unsigned flags) { - if ((flags & FLAG_WINDOW_MAXIMIZED) && (flags & FLAG_BORDERLESS_WINDOWED_MODE)) + if (flags & FLAG_WINDOW_MAXIMIZED) { - TRACELOG(LOG_WARNING, "WIN32: WINDOW: Borderless windows mode overriding maximized window flag"); - flags &= ~FLAG_WINDOW_MAXIMIZED; + if (flags & FLAG_BORDERLESS_WINDOWED_MODE) + { + TRACELOG(LOG_WARNING, "WIN32: WINDOW: Borderless windows mode overriding maximized window flag"); + flags &= ~FLAG_WINDOW_MAXIMIZED; + } + + if (~flags & FLAG_WINDOW_RESIZABLE) + { + if (!(CORE.Window.flags & FLAG_WINDOW_MAXIMIZED)) + { + TRACELOG(LOG_WARNING, "WIN32: WINDOW: Cannot maximize a non-resizable window"); + flags &= ~FLAG_WINDOW_MAXIMIZED; + } + else if (CORE.Window.flags & FLAG_WINDOW_RESIZABLE) + { + TRACELOG(LOG_WARNING, "WIN32: WINDOW: Cannot set window as non-resizable when maximized"); + flags |= FLAG_WINDOW_RESIZABLE; + } + } + else if (!(CORE.Window.flags & FLAG_WINDOW_MAXIMIZED)) + { + if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) + { + // Window needs to be unminimized before it can be maximized since minimizing takes precedence + flags &= ~FLAG_WINDOW_MINIMIZED; + } + else if ((flags & FLAG_WINDOW_MINIMIZED) && !(CORE.Window.flags & FLAG_WINDOW_MINIMIZED)) + { + TRACELOG(LOG_WARNING, "WIN32: WINDOW: Cannot minimize and maximize a window in the same frame"); + flags &= ~FLAG_WINDOW_MINIMIZED; + flags &= ~FLAG_WINDOW_MAXIMIZED; + } + } } if (mode == 1) From a96cbe0183d36a1ffc4d17ff5acfb35e74231388 Mon Sep 17 00:00:00 2001 From: Alexander Fasching Date: Wed, 4 Feb 2026 19:42:44 +0100 Subject: [PATCH 405/430] Close opened directory (#5529) --- src/rcore.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rcore.c b/src/rcore.c index 4cae00f01..26a394131 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -3004,6 +3004,7 @@ unsigned int GetDirectoryFileCountEx(const char *basePath, const char *filter, b } } } + closedir(dir); } else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); // Maybe it's a file... return fileCounter; From d4f636151b2d1e27249d4fe7859f11d6b7bc4d73 Mon Sep 17 00:00:00 2001 From: Maicon Santana Date: Wed, 4 Feb 2026 18:43:55 +0000 Subject: [PATCH 406/430] refactor to follow the CONVENTIONS.md (#5530) Co-authored-by: maiconpintoabreu --- .../shaders/resources/shaders/glsl100/deferred_shading.fs | 2 +- .../shaders/resources/shaders/glsl100/mandelbrot_set.fs | 2 +- .../shaders/resources/shaders/glsl120/deferred_shading.fs | 2 +- .../shaders/resources/shaders/glsl120/mandelbrot_set.fs | 2 +- .../shaders/resources/shaders/glsl330/deferred_shading.fs | 2 +- .../shaders/resources/shaders/glsl330/mandelbrot_set.fs | 2 +- src/rcore.c | 8 ++++---- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/shaders/resources/shaders/glsl100/deferred_shading.fs b/examples/shaders/resources/shaders/glsl100/deferred_shading.fs index 63e9c5bea..f8004ef5e 100644 --- a/examples/shaders/resources/shaders/glsl100/deferred_shading.fs +++ b/examples/shaders/resources/shaders/glsl100/deferred_shading.fs @@ -36,7 +36,7 @@ void main() vec3 ambient = albedo*vec3(0.1); vec3 viewDirection = normalize(viewPosition - fragPosition); - for (int i = 0; i < NR_LIGHTS; ++i) + for (int i = 0; i < NR_LIGHTS; i++) { if (lights[i].enabled == 0) continue; vec3 lightDirection = lights[i].position - fragPosition; diff --git a/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs b/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs index fb6dee8b3..7a89e86b0 100644 --- a/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs +++ b/examples/shaders/resources/shaders/glsl100/mandelbrot_set.fs @@ -34,7 +34,7 @@ void main() // Fc(z) = z^2 + c on the complex numbers c from the plane does not diverge to infinity starting at z = 0 // Here: z = a + bi. Iterations: z -> z^2 + c = (a + bi)^2 + (c.x + c.yi) = (a^2 - b^2 + c.x) + (2ab + c.y)i - for (int iter = 0; iter < maxIterationsLimit; ++iter) + for (int iter = 0; iter < maxIterationsLimit; iter++) { float aa = a*a; float bb = b*b; diff --git a/examples/shaders/resources/shaders/glsl120/deferred_shading.fs b/examples/shaders/resources/shaders/glsl120/deferred_shading.fs index f52454d8c..b3c5f1ea0 100644 --- a/examples/shaders/resources/shaders/glsl120/deferred_shading.fs +++ b/examples/shaders/resources/shaders/glsl120/deferred_shading.fs @@ -34,7 +34,7 @@ void main() vec3 ambient = albedo*vec3(0.1); vec3 viewDirection = normalize(viewPosition - fragPosition); - for (int i = 0; i < NR_LIGHTS; ++i) + for (int i = 0; i < NR_LIGHTS; i++) { if (lights[i].enabled == 0) continue; vec3 lightDirection = lights[i].position - fragPosition; diff --git a/examples/shaders/resources/shaders/glsl120/mandelbrot_set.fs b/examples/shaders/resources/shaders/glsl120/mandelbrot_set.fs index 5da3ef437..1943813a3 100644 --- a/examples/shaders/resources/shaders/glsl120/mandelbrot_set.fs +++ b/examples/shaders/resources/shaders/glsl120/mandelbrot_set.fs @@ -41,7 +41,7 @@ void main() a = aa - bb + c.x; b = twoab + c.y; - ++iter; + iter++; } if (iter >= maxIterations) diff --git a/examples/shaders/resources/shaders/glsl330/deferred_shading.fs b/examples/shaders/resources/shaders/glsl330/deferred_shading.fs index 660db3244..18102e934 100644 --- a/examples/shaders/resources/shaders/glsl330/deferred_shading.fs +++ b/examples/shaders/resources/shaders/glsl330/deferred_shading.fs @@ -32,7 +32,7 @@ void main() { vec3 ambient = albedo*vec3(0.1f); vec3 viewDirection = normalize(viewPosition - fragPosition); - for (int i = 0; i < NR_LIGHTS; ++i) + for (int i = 0; i < NR_LIGHTS; i++) { if (lights[i].enabled == 0) continue; vec3 lightDirection = lights[i].position - fragPosition; diff --git a/examples/shaders/resources/shaders/glsl330/mandelbrot_set.fs b/examples/shaders/resources/shaders/glsl330/mandelbrot_set.fs index bde74565f..06fb1e6f8 100644 --- a/examples/shaders/resources/shaders/glsl330/mandelbrot_set.fs +++ b/examples/shaders/resources/shaders/glsl330/mandelbrot_set.fs @@ -31,7 +31,7 @@ void main() // Here: z = a + bi. Iterations: z -> z^2 + c = (a + bi)^2 + (c.x + c.yi) = (a^2 - b^2 + c.x) + (2ab + c.y)i int iter = 0; - for (iter = 0; iter < maxIterations; ++iter) + for (iter = 0; iter < maxIterations; iter++) { float aa = a*a; float bb = b*b; diff --git a/src/rcore.c b/src/rcore.c index 26a394131..2d7b90b9a 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2662,7 +2662,7 @@ const char *GetApplicationDirectory(void) if (len > 0) { - for (int i = len; i >= 0; --i) + for (int i = len; i >= 0; i--) { if (appDir[i] == '\\') { @@ -2684,7 +2684,7 @@ const char *GetApplicationDirectory(void) if (len > 0) { - for (int i = len; i >= 0; --i) + for (int i = len; i >= 0; i--) { if (appDir[i] == '/') { @@ -2706,7 +2706,7 @@ const char *GetApplicationDirectory(void) if (_NSGetExecutablePath(appDir, &size) == 0) { int appDirLength = (int)strlen(appDir); - for (int i = appDirLength; i >= 0; --i) + for (int i = appDirLength; i >= 0; i--) { if (appDir[i] == '/') { @@ -2729,7 +2729,7 @@ const char *GetApplicationDirectory(void) if (sysctl(mib, 4, appDir, &size, NULL, 0) == 0) { int appDirLength = (int)strlen(appDir); - for (int i = appDirLength; i >= 0; --i) + for (int i = appDirLength; i >= 0; i--) { if (appDir[i] == '/') { From f43e049444d65c4492d60fa7dfb3a7932fc81ab9 Mon Sep 17 00:00:00 2001 From: Maicon Santana Date: Thu, 5 Feb 2026 14:10:55 +0000 Subject: [PATCH 407/430] Refactor removing extra space and add break line for { (#5533) Co-authored-by: maiconpintoabreu --- CONTRIBUTING.md | 2 +- examples/shaders/resources/shaders/glsl100/ascii.fs | 2 +- .../shaders/resources/shaders/glsl100/hybrid_raymarch.fs | 2 +- examples/shaders/resources/shaders/glsl100/wave.fs | 3 ++- examples/shaders/resources/shaders/glsl120/ascii.fs | 2 +- .../shaders/resources/shaders/glsl120/hybrid_raymarch.fs | 2 +- examples/shaders/resources/shaders/glsl120/wave.fs | 3 ++- examples/shaders/resources/shaders/glsl330/ascii.fs | 2 +- .../shaders/resources/shaders/glsl330/deferred_shading.fs | 3 ++- .../shaders/resources/shaders/glsl330/deferred_shading.vs | 3 ++- examples/shaders/resources/shaders/glsl330/gbuffer.fs | 3 ++- .../shaders/resources/shaders/glsl330/hybrid_raymarch.fs | 2 +- examples/shaders/resources/shaders/glsl330/wave.fs | 3 ++- examples/shapes/shapes_ball_physics.c | 8 ++++---- projects/4coder/main.c | 6 ++++-- src/platforms/rcore_web.c | 3 ++- src/platforms/rcore_web_emscripten.c | 3 ++- tools/rlparser/rlparser.c | 6 ++++-- 18 files changed, 35 insertions(+), 23 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d6a2d1d9..3ce54d39e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Hello contributors! Welcome to raylib! Do you enjoy raylib and want to contribute? Nice! You can help with the following points: -- `C programming` - Can you write/review/test/improve the code? +- `C programming` - Can you write/review/test/improve the code? - `Documentation/Tutorials/Example` - Can you write some tutorials/examples? - `Porting to other platforms` - Can you port/adapt/compile raylib on other systems? - `Web Development` - Can you help [with the website](https://github.com/raysan5/raylib.com)? diff --git a/examples/shaders/resources/shaders/glsl100/ascii.fs b/examples/shaders/resources/shaders/glsl100/ascii.fs index 54e20bf66..11b46e471 100644 --- a/examples/shaders/resources/shaders/glsl100/ascii.fs +++ b/examples/shaders/resources/shaders/glsl100/ascii.fs @@ -44,7 +44,7 @@ float GetCharacter(float n, vec2 p) // Main shader logic // ----------------------------------------------------------------------------- -void main() +void main() { vec2 charPixelSize = vec2(fontSize, fontSize); vec2 uvCellSize = charPixelSize/resolution; diff --git a/examples/shaders/resources/shaders/glsl100/hybrid_raymarch.fs b/examples/shaders/resources/shaders/glsl100/hybrid_raymarch.fs index 8f9fa0907..e3287fc04 100644 --- a/examples/shaders/resources/shaders/glsl100/hybrid_raymarch.fs +++ b/examples/shaders/resources/shaders/glsl100/hybrid_raymarch.fs @@ -63,7 +63,7 @@ float sdSixWayCutHollowSphere(vec3 p, float r, float h, float t) } // SRC: https://iquilezles.org/articles/boxfunctions -vec2 iBox(in vec3 ro, in vec3 rd, in vec3 rad) +vec2 iBox(in vec3 ro, in vec3 rd, in vec3 rad) { vec3 m = 1.0/rd; vec3 n = m*ro; diff --git a/examples/shaders/resources/shaders/glsl100/wave.fs b/examples/shaders/resources/shaders/glsl100/wave.fs index df12df9ba..00097f2c4 100644 --- a/examples/shaders/resources/shaders/glsl100/wave.fs +++ b/examples/shaders/resources/shaders/glsl100/wave.fs @@ -19,7 +19,8 @@ uniform float ampY; uniform float speedX; uniform float speedY; -void main() { +void main() +{ float pixelWidth = 1.0/size.x; float pixelHeight = 1.0/size.y; float aspect = pixelHeight/pixelWidth; diff --git a/examples/shaders/resources/shaders/glsl120/ascii.fs b/examples/shaders/resources/shaders/glsl120/ascii.fs index 09c572ae5..e4e9927b9 100644 --- a/examples/shaders/resources/shaders/glsl120/ascii.fs +++ b/examples/shaders/resources/shaders/glsl120/ascii.fs @@ -42,7 +42,7 @@ float GetCharacter(float n, vec2 p) // Main shader logic // ----------------------------------------------------------------------------- -void main() +void main() { vec2 charPixelSize = vec2(fontSize, fontSize); vec2 uvCellSize = charPixelSize / resolution; diff --git a/examples/shaders/resources/shaders/glsl120/hybrid_raymarch.fs b/examples/shaders/resources/shaders/glsl120/hybrid_raymarch.fs index 3118e1861..6090df6b1 100644 --- a/examples/shaders/resources/shaders/glsl120/hybrid_raymarch.fs +++ b/examples/shaders/resources/shaders/glsl120/hybrid_raymarch.fs @@ -61,7 +61,7 @@ float sdSixWayCutHollowSphere(vec3 p, float r, float h, float t) } // SRC: https://iquilezles.org/articles/boxfunctions -vec2 iBox(in vec3 ro, in vec3 rd, in vec3 rad) +vec2 iBox(in vec3 ro, in vec3 rd, in vec3 rad) { vec3 m = 1.0/rd; vec3 n = m*ro; diff --git a/examples/shaders/resources/shaders/glsl120/wave.fs b/examples/shaders/resources/shaders/glsl120/wave.fs index dd6bb2e22..9f0f300e1 100644 --- a/examples/shaders/resources/shaders/glsl120/wave.fs +++ b/examples/shaders/resources/shaders/glsl120/wave.fs @@ -17,7 +17,8 @@ uniform float ampY; uniform float speedX; uniform float speedY; -void main() { +void main() +{ float pixelWidth = 1.0/size.x; float pixelHeight = 1.0/size.y; float aspect = pixelHeight/pixelWidth; diff --git a/examples/shaders/resources/shaders/glsl330/ascii.fs b/examples/shaders/resources/shaders/glsl330/ascii.fs index 3f73bf288..3934c5dc1 100644 --- a/examples/shaders/resources/shaders/glsl330/ascii.fs +++ b/examples/shaders/resources/shaders/glsl330/ascii.fs @@ -38,7 +38,7 @@ float GetCharacter(int n, vec2 p) // Main shader logic // ----------------------------------------------------------------------------- -void main() +void main() { vec2 charPixelSize = vec2(fontSize, fontSize); vec2 uvCellSize = charPixelSize/resolution; diff --git a/examples/shaders/resources/shaders/glsl330/deferred_shading.fs b/examples/shaders/resources/shaders/glsl330/deferred_shading.fs index 18102e934..93a13319c 100644 --- a/examples/shaders/resources/shaders/glsl330/deferred_shading.fs +++ b/examples/shaders/resources/shaders/glsl330/deferred_shading.fs @@ -23,7 +23,8 @@ uniform vec3 viewPosition; const float QUADRATIC = 0.032; const float LINEAR = 0.09; -void main() { +void main() +{ vec3 fragPosition = texture(gPosition, texCoord).rgb; vec3 normal = texture(gNormal, texCoord).rgb; vec3 albedo = texture(gAlbedoSpec, texCoord).rgb; diff --git a/examples/shaders/resources/shaders/glsl330/deferred_shading.vs b/examples/shaders/resources/shaders/glsl330/deferred_shading.vs index f2b1bd7c4..3a6c1612c 100644 --- a/examples/shaders/resources/shaders/glsl330/deferred_shading.vs +++ b/examples/shaders/resources/shaders/glsl330/deferred_shading.vs @@ -5,7 +5,8 @@ layout (location = 1) in vec2 vertexTexCoord; out vec2 texCoord; -void main() { +void main() +{ gl_Position = vec4(vertexPosition, 1.0); texCoord = vertexTexCoord; } diff --git a/examples/shaders/resources/shaders/glsl330/gbuffer.fs b/examples/shaders/resources/shaders/glsl330/gbuffer.fs index c86e20a9e..cbb6d38dc 100644 --- a/examples/shaders/resources/shaders/glsl330/gbuffer.fs +++ b/examples/shaders/resources/shaders/glsl330/gbuffer.fs @@ -10,7 +10,8 @@ in vec3 fragNormal; uniform sampler2D diffuseTexture; uniform sampler2D specularTexture; -void main() { +void main() +{ // store the fragment position vector in the first gbuffer texture gPosition = fragPosition; // also store the per-fragment normals into the gbuffer diff --git a/examples/shaders/resources/shaders/glsl330/hybrid_raymarch.fs b/examples/shaders/resources/shaders/glsl330/hybrid_raymarch.fs index f1fafc640..073fef4f1 100644 --- a/examples/shaders/resources/shaders/glsl330/hybrid_raymarch.fs +++ b/examples/shaders/resources/shaders/glsl330/hybrid_raymarch.fs @@ -59,7 +59,7 @@ float sdSixWayCutHollowSphere(vec3 p, float r, float h, float t) } // https://iquilezles.org/articles/boxfunctions -vec2 iBox(in vec3 ro, in vec3 rd, in vec3 rad) +vec2 iBox(in vec3 ro, in vec3 rd, in vec3 rad) { vec3 m = 1.0/rd; vec3 n = m*ro; diff --git a/examples/shaders/resources/shaders/glsl330/wave.fs b/examples/shaders/resources/shaders/glsl330/wave.fs index 393f1bde2..be07ccd05 100644 --- a/examples/shaders/resources/shaders/glsl330/wave.fs +++ b/examples/shaders/resources/shaders/glsl330/wave.fs @@ -22,7 +22,8 @@ uniform float ampY; uniform float speedX; uniform float speedY; -void main() { +void main() +{ float pixelWidth = 1.0/size.x; float pixelHeight = 1.0/size.y; float aspect = pixelHeight/pixelWidth; diff --git a/examples/shapes/shapes_ball_physics.c b/examples/shapes/shapes_ball_physics.c index 293be1cd5..b790c7e90 100644 --- a/examples/shapes/shapes_ball_physics.c +++ b/examples/shapes/shapes_ball_physics.c @@ -139,14 +139,14 @@ int main(void) Ball *ball = &balls[i]; // The ball is not grabbed - if (!ball->grabbed) + if (!ball->grabbed) { // Ball repositioning using the velocity ball->pos.x += ball->vel.x * delta; ball->pos.y += ball->vel.y * delta; // Does the ball hit the screen right boundary? - if ((ball->pos.x + ball->radius) >= screenWidth) + if ((ball->pos.x + ball->radius) >= screenWidth) { ball->pos.x = screenWidth - ball->radius; // Ball repositioning ball->vel.x = -ball->vel.x*ball->elasticity; // Elasticity makes the ball lose 10% of its velocity on hit @@ -159,12 +159,12 @@ int main(void) } // The same for y axis - if ((ball->pos.y + ball->radius) >= screenHeight) + if ((ball->pos.y + ball->radius) >= screenHeight) { ball->pos.y = screenHeight - ball->radius; ball->vel.y = -ball->vel.y*ball->elasticity; } - else if ((ball->pos.y - ball->radius) <= 0) + else if ((ball->pos.y - ball->radius) <= 0) { ball->pos.y = ball->radius; ball->vel.y = -ball->vel.y*ball->elasticity; diff --git a/projects/4coder/main.c b/projects/4coder/main.c index 062d1d7db..e33f9a1db 100644 --- a/projects/4coder/main.c +++ b/projects/4coder/main.c @@ -1,7 +1,8 @@ #include #include "raylib.h" -int main() { +int main() +{ int screenWidth = 800; int screenHeight = 450; @@ -17,7 +18,8 @@ int main() { SetTargetFPS(60); - while (!WindowShouldClose()) { + while (!WindowShouldClose()) + { cam.position.x = sin(GetTime())*10.0f; cam.position.z = cos(GetTime())*10.0f; diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index 986197b9d..3dd3eb9df 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -894,7 +894,8 @@ void SwapScreenBuffer(void) const canvas = Module.canvas; const ctx = canvas.getContext('2d'); - if (!Module.__img || (Module.__img.width !== width) || (Module.__img.height !== height)) { + if (!Module.__img || (Module.__img.width !== width) || (Module.__img.height !== height)) + { Module.__img = ctx.createImageData(width, height); } diff --git a/src/platforms/rcore_web_emscripten.c b/src/platforms/rcore_web_emscripten.c index ba2489a31..92caae99f 100644 --- a/src/platforms/rcore_web_emscripten.c +++ b/src/platforms/rcore_web_emscripten.c @@ -875,7 +875,8 @@ void SwapScreenBuffer(void) //const canvas = Module['canvas']; const ctx = canvas.getContext('2d'); - if (!Module.__img || (Module.__img.width !== width) || (Module.__img.height !== height)) { + if (!Module.__img || (Module.__img.width !== width) || (Module.__img.height !== height)) + { Module.__img = ctx.createImageData(width, height); } diff --git a/tools/rlparser/rlparser.c b/tools/rlparser/rlparser.c index f96dcc0bd..e69f7ad56 100644 --- a/tools/rlparser/rlparser.c +++ b/tools/rlparser/rlparser.c @@ -721,7 +721,8 @@ int main(int argc, char *argv[]) char v = structs[i].fieldType[originalIndex][k]; if ((v == '*') || (v == ' ') || (v == ',')) { - if (nameEnd != -1) { + if (nameEnd != -1) + { // Don't copy to last additional field if (fieldsRemaining != additionalFields) { @@ -1011,7 +1012,8 @@ int main(int argc, char *argv[]) ((linePtr[c - 4] == 'v') && (linePtr[c - 3] == 'o') && (linePtr[c - 2] == 'i') && - (linePtr[c - 1] == 'd'))) { + (linePtr[c - 1] == 'd'))) + { break; } From 3881d2aac25a11dca6890aee9983ebaf0c44241d Mon Sep 17 00:00:00 2001 From: bielern <917465+bielern@users.noreply.github.com> Date: Thu, 5 Feb 2026 19:21:45 +0100 Subject: [PATCH 408/430] Fix: Detect collision if one line is almost vertical (#5510) (#5531) --- src/rshapes.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/rshapes.c b/src/rshapes.c index 7487e6296..4f6e86c18 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -2365,30 +2365,30 @@ bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec) // Check the collision between two lines defined by two points each, returns collision point by reference bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint) { - bool collision = false; + // According to https://en.wikipedia.org/wiki/Line–line_intersection#Given_two_points_on_each_line_segment + float rx = endPos1.x - startPos1.x; + float ry = endPos1.y - startPos1.y; + float sx = endPos2.x - startPos2.x; + float sy = endPos2.y - startPos2.y; - float div = (endPos2.y - startPos2.y)*(endPos1.x - startPos1.x) - (endPos2.x - startPos2.x)*(endPos1.y - startPos1.y); + float div = rx * sy - ry * sx; - if (fabsf(div) >= FLT_EPSILON) - { - collision = true; - - float xi = ((startPos2.x - endPos2.x)*(startPos1.x*endPos1.y - startPos1.y*endPos1.x) - (startPos1.x - endPos1.x)*(startPos2.x*endPos2.y - startPos2.y*endPos2.x))/div; - float yi = ((startPos2.y - endPos2.y)*(startPos1.x*endPos1.y - startPos1.y*endPos1.x) - (startPos1.y - endPos1.y)*(startPos2.x*endPos2.y - startPos2.y*endPos2.x))/div; - - if (((fabsf(startPos1.x - endPos1.x) > FLT_EPSILON) && (xi < fminf(startPos1.x, endPos1.x) || (xi > fmaxf(startPos1.x, endPos1.x)))) || - ((fabsf(startPos2.x - endPos2.x) > FLT_EPSILON) && (xi < fminf(startPos2.x, endPos2.x) || (xi > fmaxf(startPos2.x, endPos2.x)))) || - ((fabsf(startPos1.y - endPos1.y) > FLT_EPSILON) && (yi < fminf(startPos1.y, endPos1.y) || (yi > fmaxf(startPos1.y, endPos1.y)))) || - ((fabsf(startPos2.y - endPos2.y) > FLT_EPSILON) && (yi < fminf(startPos2.y, endPos2.y) || (yi > fmaxf(startPos2.y, endPos2.y))))) collision = false; - - if (collision && (collisionPoint != 0)) - { - collisionPoint->x = xi; - collisionPoint->y = yi; - } + if (fabsf(div) < FLT_EPSILON) { + return false; } - return collision; + float s12x = startPos2.x - startPos1.x; + float s12y = startPos2.y - startPos1.y; + + float t = (s12x * sy - s12y * sx) / div; + float u = (s12x * ry - s12y * rx) / div; + + if (0.0f <= t && t <= 1.0f && 0.0f <= u && u <= 1.0f) { + collisionPoint->x = startPos1.x + t * rx; + collisionPoint->y = startPos1.y + t * ry; + return true; + } + return false; } // Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] From 4f76b896d52a4e11c088b5bc5ce006f3a2f889b8 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 6 Feb 2026 10:55:42 +0100 Subject: [PATCH 409/430] REVIEWED: `CheckCollisionLines()`, formating and follow raylib conventions --- src/rshapes.c | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/rshapes.c b/src/rshapes.c index 4f6e86c18..35614bd6c 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -2363,32 +2363,36 @@ bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec) } // Check the collision between two lines defined by two points each, returns collision point by reference +// REF: https://en.wikipedia.org/wiki/Line–line_intersection#Given_two_points_on_each_line_segment bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint) { - // According to https://en.wikipedia.org/wiki/Line–line_intersection#Given_two_points_on_each_line_segment + bool collision = false; + float rx = endPos1.x - startPos1.x; float ry = endPos1.y - startPos1.y; float sx = endPos2.x - startPos2.x; float sy = endPos2.y - startPos2.y; - float div = rx * sy - ry * sx; + float div = rx*sy - ry*sx; - if (fabsf(div) < FLT_EPSILON) { - return false; + if (fabsf(div) >= FLT_EPSILON) + { + float s12x = startPos2.x - startPos1.x; + float s12y = startPos2.y - startPos1.y; + + float t = (s12x*sy - s12y*sx)/div; + float u = (s12x*ry - s12y*rx)/div; + + if ((0.0f <= t) && (t <= 1.0f) && (0.0f <= u) && (u <= 1.0f)) + { + collisionPoint->x = startPos1.x + t*rx; + collisionPoint->y = startPos1.y + t*ry; + + collision = true; + } } - - float s12x = startPos2.x - startPos1.x; - float s12y = startPos2.y - startPos1.y; - - float t = (s12x * sy - s12y * sx) / div; - float u = (s12x * ry - s12y * rx) / div; - - if (0.0f <= t && t <= 1.0f && 0.0f <= u && u <= 1.0f) { - collisionPoint->x = startPos1.x + t * rx; - collisionPoint->y = startPos1.y + t * ry; - return true; - } - return false; + + return collision; } // Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] From a6fa8b9ff44a83aad9962141804cfcaa2a86928d Mon Sep 17 00:00:00 2001 From: Ross Martin Date: Fri, 6 Feb 2026 13:58:27 +0100 Subject: [PATCH 410/430] Fix out of bound Memory read in Material.maps (#5534) * Fix out of bounds Memory read in Material.Maps by using the MATERIAL_MAP_SPECULAR define instead of the SHADER_LOG_SPECULAR enum * Fix out of bounds Memory read in Material.Maps by using the MATERIAL_MAP_SPECULAR define instead of the SHADER_LOG_SPECULAR enum --- src/rmodels.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index 2988dfaba..6f3ae995f 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -1719,10 +1719,10 @@ void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, i if (material.shader.locs[SHADER_LOC_COLOR_SPECULAR] != -1) { float values[4] = { - (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.r/255.0f, - (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.g/255.0f, - (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.b/255.0f, - (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.a/255.0f + (float)material.maps[MATERIAL_MAP_SPECULAR].color.r/255.0f, + (float)material.maps[MATERIAL_MAP_SPECULAR].color.g/255.0f, + (float)material.maps[MATERIAL_MAP_SPECULAR].color.b/255.0f, + (float)material.maps[MATERIAL_MAP_SPECULAR].color.a/255.0f }; rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_SPECULAR], values, SHADER_UNIFORM_VEC4, 1); From b29d6ee4627d7dd707372249030df370d210292f Mon Sep 17 00:00:00 2001 From: SabeDoesThings <122580233+SabeDoesThings@users.noreply.github.com> Date: Mon, 9 Feb 2026 05:51:43 -0600 Subject: [PATCH 411/430] Update BINDINGS.md (#5538) --- BINDINGS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BINDINGS.md b/BINDINGS.md index 6ef57035f..e39602e4b 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -78,7 +78,6 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [raylib-rs](https://github.com/raylib-rs/raylib-rs) | **5.5** | [Rust](https://www.rust-lang.org) | Zlib | | [raylib-ruby](https://github.com/wilsonsilva/raylib-ruby) | 4.5 | [Ruby](https://www.ruby-lang.org) | Zlib | | [Relib](https://github.com/RedCubeDev-ByteSpace/Relib) | 3.5 | [ReCT](https://github.com/RedCubeDev-ByteSpace/ReCT) | **???** | -| [ringraylib5](https://github.com/ring-lang/ring/tree/master/extensions/ringraylib5) | **5.0** | [Ring](https://ring-lang.github.io/) | **???** | | [racket-raylib](https://github.com/eutro/racket-raylib) | **5.5** | [Racket](https://racket-lang.org) | MIT/Apache-2.0 | | [raylib-swift](https://github.com/STREGAsGate/Raylib) | 4.0 | [Swift](https://swift.org) | MIT | | [raylib-scopes](https://github.com/salotz/raylib-scopes) | auto | [Scopes](http://scopes.rocks) | MIT | @@ -95,6 +94,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [raylib-sunder](https://github.com/ashn-dot-dev/raylib-sunder) | **auto** | [Sunder](https://github.com/ashn-dot-dev/sunder) | 0BSD | | [raylib-bqn](https://github.com/Brian-ED/raylib-bqn) | **5.0** | [BQN](https://mlochbaum.github.io/BQN) | MIT | | [rayjs](https://github.com/mode777/rayjs) | 4.6-dev | [QuickJS](https://bellard.org/quickjs) | MIT | +| [rayjule](https://github.com/SabeDoesThings/rayjule) | **5.5** | [Jule](https://jule.dev/) | MIT | | [raylib-raku](https://github.com/vushu/raylib-raku) | **auto** | [Raku](https://www.raku.org) | Artistic License 2.0 | | [Raylib.lean](https://github.com/KislyjKisel/Raylib.lean) | **5.5-dev** | [Lean4](https://lean-lang.org) | BSD-3-Clause | | [raylib-cobol](https://codeberg.org/glowiak/raylib-cobol) | **auto** | [COBOL](https://gnucobol.sourceforge.io) | Public domain | From 5a36ce5e7c2e7b278901c022caeb427b8635f9a4 Mon Sep 17 00:00:00 2001 From: mikeemm <42421968+mikeemm@users.noreply.github.com> Date: Mon, 9 Feb 2026 13:00:18 +0100 Subject: [PATCH 412/430] [rcore] Implemented SetWindowMaxSize, SetWindowMinSize, and SetWindowSize (#5536) * implemented SetWindowMaxSize, SetWindowMinSize and SetWindowSize * removed outdated warning * prevented incompatible size limits --- src/platforms/rcore_desktop_win32.c | 61 +++++++++++++++++++---------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index fd10fca04..17d4fc530 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -156,8 +156,6 @@ static PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = NULL; // Flags that have no operations to perform during an update #define FLAG_MASK_NO_UPDATE (FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT) -#define WM_APP_UPDATE_WINDOW_SIZE (WM_APP + 1) - #define WGL_DRAW_TO_WINDOW_ARB 0x2001 #define WGL_ACCELERATION_ARB 0x2003 #define WGL_SUPPORT_OPENGL_ARB 0x2010 @@ -426,9 +424,7 @@ static bool UpdateWindowSize(int mode, HWND hwnd, int width, int height, unsigne else swpFlags |= SWP_NOMOVE; // WARNING: This code must be called after swInit() has been called, after InitPlatform() in [rcore] - //RECT rc = {0, 0, desired.cx, desired.cy}; - //AdjustWindowRectEx(&rc, WS_OVERLAPPEDWINDOW, FALSE, 0); - //SetWindowPos(hwnd, NULL, windowPos.x, windowPos.y, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER); + SetWindowPos(hwnd, NULL, windowPos.x, windowPos.y, windowSize.cx, windowSize.cy, SWP_NOMOVE | SWP_NOZORDER); return true; } @@ -976,25 +972,40 @@ void SetWindowMonitor(int monitor) // Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) void SetWindowMinSize(int width, int height) { - TRACELOG(LOG_WARNING, "SetWindowMinSize not implemented"); + if ((width > CORE.Window.screenMax.width) || (height > CORE.Window.screenMax.height)) + { + TRACELOG(LOG_WARNING, "WIN32: WINDOW: Cannot set minimum screen size higher than the maximum"); + return; + } CORE.Window.screenMin.width = width; CORE.Window.screenMin.height = height; + + SetWindowSize(platform.appScreenWidth, platform.appScreenHeight); } // Set window maximum dimensions (FLAG_WINDOW_RESIZABLE) void SetWindowMaxSize(int width, int height) { - TRACELOG(LOG_WARNING, "SetWindowMaxSize not implemented"); + if ((width < CORE.Window.screenMin.width) || (height < CORE.Window.screenMin.height)) + { + TRACELOG(LOG_WARNING, "WIN32: WINDOW: Cannot set maximum screen size lower than the minimum"); + return; + } CORE.Window.screenMax.width = width; CORE.Window.screenMax.height = height; + + SetWindowSize(platform.appScreenWidth, platform.appScreenHeight); } // Set window dimensions void SetWindowSize(int width, int height) { - TRACELOG(LOG_WARNING, "SetWindowSize not implemented"); + int screenWidth = fmaxf(CORE.Window.screenMin.width, fminf(CORE.Window.screenMax.width, width)); + int screenHeight = fmaxf(CORE.Window.screenMin.height, fminf(CORE.Window.screenMax.height, height)); + + UpdateWindowSize(1, platform.hwnd, screenWidth, screenHeight, platform.desiredFlags); } // Set window opacity, value opacity is between 0.0 and 1.0 @@ -1494,7 +1505,8 @@ int InitPlatform(void) // NOTE: From this point CORE.Window.flags should always reflect the actual state of the window CORE.Window.flags = FLAG_WINDOW_HIDDEN | (platform.desiredFlags & FLAG_MASK_NO_UPDATE); - + CORE.Window.screenMax.width = 9999; + CORE.Window.screenMax.height = 9999; /* // TODO: Review SetProcessDpiAwarenessContext() // NOTE: SetProcessDpiAwarenessContext() requires Windows 10, version 1703 and shcore.lib linkage @@ -1747,14 +1759,27 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara } break; case WM_SIZING: { - if (CORE.Window.flags & FLAG_WINDOW_RESIZABLE) - { - // TODO: Enforce min/max size - } - else TRACELOG(LOG_WARNING, "WIN32: WINDOW: Trying to resize a non-resizable window"); + if (!(CORE.Window.flags & FLAG_WINDOW_RESIZABLE)) + TRACELOG(LOG_WARNING, "WIN32: WINDOW: Trying to resize a non-resizable window"); result = TRUE; } break; + case WM_GETMINMAXINFO: + { + DWORD style = MakeWindowStyle(platform.desiredFlags); + SIZE maxClientSize = { CORE.Window.screenMax.width, CORE.Window.screenMax.height }; + SIZE maxWindowSize = CalcWindowSize(96, maxClientSize, style); + SIZE minClientSize = { CORE.Window.screenMin.width, CORE.Window.screenMin.height }; + SIZE minWindowSize = CalcWindowSize(96, minClientSize, style); + + LPMINMAXINFO lpmmi = (LPMINMAXINFO) lparam; + lpmmi->ptMaxSize.x = maxWindowSize.cx; + lpmmi->ptMaxSize.y = maxWindowSize.cy; + lpmmi->ptMaxTrackSize.x = maxWindowSize.cx; + lpmmi->ptMaxTrackSize.y = maxWindowSize.cy; + lpmmi->ptMinTrackSize.x = minWindowSize.cx; + lpmmi->ptMinTrackSize.y = minWindowSize.cy; + } break; case WM_STYLECHANGING: { if (wparam == GWL_STYLE) @@ -1960,10 +1985,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara } break; case WM_MOUSEWHEEL: CORE.Input.Mouse.currentWheelMove.y = ((float)GET_WHEEL_DELTA_WPARAM(wparam))/WHEEL_DELTA; break; case WM_MOUSEHWHEEL: CORE.Input.Mouse.currentWheelMove.x = ((float)GET_WHEEL_DELTA_WPARAM(wparam))/WHEEL_DELTA; break; - case WM_APP_UPDATE_WINDOW_SIZE: - { - //UpdateWindowSize(UPDATE_WINDOW_NORMAL, hwnd, platform.appScreenWidth, platform.appScreenHeight, CORE.Window.flags); - } break; default: result = DefWindowProcW(hwnd, msg, wparam, lparam); // Message passed directly for execution (default behaviour) } @@ -2045,12 +2066,10 @@ static void HandleWindowResize(HWND hwnd, int *width, int *height) GetClientRect(hwnd, &rect); SIZE clientSize = { rect.right, rect.bottom }; - // TODO: Update framebuffer on resize CORE.Window.currentFbo.width = (int)clientSize.cx; CORE.Window.currentFbo.height = (int)clientSize.cy; - //SetupViewport(0, 0, clientSize.cx, clientSize.cy); - SetupViewport(clientSize.cx, clientSize.cy); + CORE.Window.resizedLastFrame = true; float dpiScale = ((float)GetDpiForWindow(hwnd))/96.0f; bool highdpi = !!(CORE.Window.flags & FLAG_WINDOW_HIGHDPI); From c0829bc69e59a7fa92336b77e2fa2dd57df05840 Mon Sep 17 00:00:00 2001 From: LunaStev Date: Mon, 9 Feb 2026 21:27:51 +0900 Subject: [PATCH 413/430] Add raylib bindings for Wave language (#5539) * Bindings Wave * fix format --- BINDINGS.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/BINDINGS.md b/BINDINGS.md index e39602e4b..5ef67c98f 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -86,6 +86,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [raylib-v](https://github.com/vlang/raylib) | 5.5 | [V](https://vlang.io) | MIT/Unlicense | | [raylib.v](https://github.com/irishgreencitrus/raylib.v) | 4.2 | [V](https://vlang.io) | Zlib | | [raylib-vapi](https://github.com/lxmcf/raylib-vapi) | **5.0** | [Vala](https://vala.dev) | Zlib | +| [raylib-wave](https://github.com/wavefnd/raylib-wave) | **auto** |[Wave](http://wave-lang.dev) | Zlib | | [raylib-wren](https://github.com/TSnake41/raylib-wren) | 4.5 | [Wren](http://wren.io) | ISC | | [raylib-zig](https://github.com/raylib-zig/raylib-zig) | **5.6-dev** | [Zig](https://ziglang.org) | MIT | | [raylib.zig](https://github.com/ryupold/raylib.zig) | **5.1-dev** | [Zig](https://ziglang.org) | MIT | @@ -103,6 +104,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [fnl-raylib](https://github.com/0riginaln0/fnl-raylib) | **5.5** | [Fennel](https://fennel-lang.org/) | MIT | | [Rayua](https://github.com/uiua-lang/rayua) | **5.5** | [Uiua](https://www.uiua.org/) | **???** | + ### Utility Wrapers These are utility wrappers for specific languages, they are not required to use raylib in the language but may adapt the raylib API to be more inline with the language's paradigm. @@ -183,4 +185,4 @@ Missing some language or wrapper? Feel free to create a new one! :) Usually, raylib bindings follow the convention: `raylib-{language}` -Let me know if you're writing a new binding for raylib, I will list it here! +Let me know if you're writing a new binding for raylib, I will list it here! \ No newline at end of file From c4baa5b81d19051bfa3408cdd2cc1e59c863ad65 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 9 Feb 2026 22:23:23 +0100 Subject: [PATCH 414/430] REVIEWED: Comments --- src/rlgl.h | 87 +++++++++++++++++++++++++++--------------------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index d3e86cf1f..d05bc42cb 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -204,9 +204,9 @@ #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 #endif #if defined(GRAPHICS_API_OPENGL_ES2) - // We reduce memory sizes for embedded systems (RPI and HTML5) + // Reducing memory sizes for embedded systems (RPI and HTML5) // NOTE: On HTML5 (emscripten) this is allocated on heap, - // by default it's only 16MB!...just take care... + // by default heap is only 16MB!...just take care... #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 2048 #endif #endif @@ -1277,7 +1277,7 @@ void rlTranslatef(float x, float y, float z) matTranslation.m13 = y; matTranslation.m14 = z; - // NOTE: We transpose matrix with multiplication order + // NOTE: Transposing matrix by multiplication order *RLGL.State.currentMatrix = rlMatrixMultiply(matTranslation, *RLGL.State.currentMatrix); } @@ -1322,7 +1322,7 @@ void rlRotatef(float angle, float x, float y, float z) matRotation.m14 = 0.0f; matRotation.m15 = 1.0f; - // NOTE: We transpose matrix with multiplication order + // NOTE: Transposing matrix by multiplication order *RLGL.State.currentMatrix = rlMatrixMultiply(matRotation, *RLGL.State.currentMatrix); } @@ -1336,7 +1336,7 @@ void rlScalef(float x, float y, float z) matScale.m5 = y; matScale.m10 = z; - // NOTE: We transpose matrix with multiplication order + // NOTE: Transposing matrix by multiplication order *RLGL.State.currentMatrix = rlMatrixMultiply(matScale, *RLGL.State.currentMatrix); } @@ -1418,7 +1418,6 @@ void rlOrtho(double left, double right, double bottom, double top, double znear, #endif // Set the viewport area (transformation from normalized device coordinates to window coordinates) -// NOTE: We store current viewport dimensions void rlViewport(int x, int y, int width, int height) { glViewport(x, y, width, height); @@ -1529,9 +1528,9 @@ void rlVertex3f(float x, float y, float z) tz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z + RLGL.State.transform.m14; } - // WARNING: We can't break primitives when launching a new batch + // WARNING: Be careful with primitives breaking when launching a new batch! // RL_LINES comes in pairs, RL_TRIANGLES come in groups of 3 vertices and RL_QUADS come in groups of 4 vertices - // We must check current draw.mode when a new vertex is required and finish the batch only if the draw.mode draw.vertexCount is %2, %3 or %4 + // Checking current draw.mode when a new vertex is required and finish the batch only if the draw.mode draw.vertexCount is %2, %3 or %4 if (RLGL.State.vertexCounter > (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4 - 4)) { if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) && @@ -1539,7 +1538,7 @@ void rlVertex3f(float x, float y, float z) { // Reached the maximum number of vertices for RL_LINES drawing // Launch a draw call but keep current state for next vertices comming - // NOTE: We add +1 vertex to the check for security + // NOTE: Adding +1 vertex to the check for some safety rlCheckRenderBatchLimit(2 + 1); } else if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) && @@ -1659,7 +1658,7 @@ void rlSetTexture(unsigned int id) #if defined(GRAPHICS_API_OPENGL_11) rlDisableTexture(); #else - // NOTE: If quads batch limit is reached, we force a draw call and next batch starts + // NOTE: If quads batch limit is reached, force a draw call and next batch starts if (RLGL.State.vertexCounter >= RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4) { @@ -2485,7 +2484,7 @@ void rlLoadExtensions(void *loader) const char **extList = (const char **)RL_CALLOC(512, sizeof(const char *)); // Allocate 512 strings pointers (2 KB) const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string - // NOTE: We have to duplicate string because glGetString() returns a const string + // NOTE: String duplication rquired because glGetString() returns a const string int extensionsLength = (int)strlen(extensions); // Get extensions string size in bytes char *extensionsDup = (char *)RL_CALLOC(extensionsLength + 1, sizeof(char)); // Allocate space for copy with additional EOL byte strncpy(extensionsDup, extensions, extensionsLength); @@ -2970,19 +2969,20 @@ void rlUnloadRenderBatch(rlRenderBatch batch) } // Draw render batch -// NOTE: We require a pointer to reset batch and increase current buffer (multi-buffer) +// NOTE: Batch is reseted and current buffer is updated (for multi-buffer config) void rlDrawRenderBatch(rlRenderBatch *batch) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // Update batch vertex buffers //------------------------------------------------------------------------------------------------------------ // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) - // TODO: If no data changed on the CPU arrays there is no need to re-upload data to GPU, - // a flag can be used to detect changes but it would imply keeping a copy buffer and memcmp() both, does it worth it? if (RLGL.State.vertexCounter > 0) { // Activate elements VAO if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + + // TODO: If no data changed on the CPU arrays there is no need to re-upload data to GPU, + // a flag can be used to detect changes but it would imply keeping a copy buffer and memcmp() both, does it worth it? // Vertex positions buffer glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); @@ -3006,18 +3006,17 @@ void rlDrawRenderBatch(rlRenderBatch *batch) // NOTE: glMapBuffer() causes sync issue // If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job - // To avoid waiting (idle), you can call first glBufferData() with NULL pointer before glMapBuffer() - // If you do that, the previous data in PBO will be discarded and glMapBuffer() returns a new + // To avoid waiting (idle), glBufferData() can bee called first with NULL pointer before glMapBuffer() + // Doing that, the previous data in PBO will be discarded and glMapBuffer() returns a new // allocated pointer immediately even if GPU is still working with the previous data // Another option: map the buffer object into client's memory - // Probably this code could be moved somewhere else... - // batch->vertexBuffer[batch->currentBuffer].vertices = (float *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); - // if (batch->vertexBuffer[batch->currentBuffer].vertices) - // { - // Update vertex data - // } - // glUnmapBuffer(GL_ARRAY_BUFFER); + //batch->vertexBuffer[batch->currentBuffer].vertices = (float *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); + //if (batch->vertexBuffer[batch->currentBuffer].vertices) + //{ + // Update vertex data + //} + //glUnmapBuffer(GL_ARRAY_BUFFER); // Unbind the current VAO if (RLGL.ExtSupported.vao) glBindVertexArray(0); @@ -3132,7 +3131,7 @@ void rlDrawRenderBatch(rlRenderBatch *batch) else { #if defined(GRAPHICS_API_OPENGL_33) - // We need to define the number of indices to be processed: elementCount*6 + // The number of indices to be processed needs to be defined: elementCount*6 // NOTE: The final parameter tells the GPU the offset in bytes from the // start of the index buffer to the location of the first index to process glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_INT, (GLvoid *)(vertexOffset/4*6*sizeof(GLuint))); @@ -3233,7 +3232,7 @@ bool rlCheckRenderBatchLimit(int vCount) rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside - // Restore state of last batch so we can continue adding vertices + // Restore state of last batch so new vertices can be added RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = currentMode; RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = currentTexture; } @@ -3395,7 +3394,7 @@ unsigned int rlLoadTexture(const void *data, int width, int height, int format, } #endif - // At this point we have the texture loaded in GPU and texture parameters configured + // At this point texture is loaded in GPU and texture parameters configured // NOTE: If mipmaps were not in data, they are not generated automatically @@ -3416,10 +3415,10 @@ unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) if (!isGpuReady) { TRACELOG(RL_LOG_WARNING, "GL: GPU is not ready to load data, trying to load before InitWindow()?"); return id; } #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // In case depth textures not supported, we force renderbuffer usage + // In case depth textures were not supported, force renderbuffer usage if (!RLGL.ExtSupported.texDepth) useRenderBuffer = true; - // NOTE: We let the implementation to choose the best bit-depth + // NOTE: Letting the implementation to choose the best bit-depth // Possible formats: GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32 and GL_DEPTH_COMPONENT32F unsigned int glInternalFormat = GL_DEPTH_COMPONENT; @@ -3565,7 +3564,7 @@ unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mi } // Update already loaded texture in GPU with new data -// NOTE: We don't know safely if internal texture format is the expected one... +// WARNING: Not possible to know safely if internal texture format is the expected one... void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data) { glBindTexture(GL_TEXTURE_2D, id); @@ -3699,7 +3698,7 @@ void *rlReadTexturePixels(unsigned int id, int width, int height, int format) #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) glBindTexture(GL_TEXTURE_2D, id); - // NOTE: Using texture id, we can retrieve some texture info (but not on OpenGL ES 2.0) + // NOTE: Using texture id, some texture info can be retrieved (but not on OpenGL ES 2.0) // Possible texture info: GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE //int width, height, format; //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); @@ -3742,7 +3741,7 @@ void *rlReadTexturePixels(unsigned int id, int width, int height, int format) // Attach our texture to FBO glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, id, 0); - // We read data as RGBA because FBO texture is configured as RGBA, despite binding another texture format + // Reading data as RGBA because FBO texture is configured as RGBA, despite binding another texture format pixels = RL_CALLOC(rlGetPixelDataSize(width, height, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8), 1); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); @@ -3778,12 +3777,12 @@ unsigned char *rlReadScreenPixels(int width, int height) { unsigned char *imgData = (unsigned char *)RL_CALLOC(width*height*4, sizeof(unsigned char)); - // NOTE 1: glReadPixels returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer - // NOTE 2: We are getting alpha channel! Be careful, it can be transparent if not cleared properly! + // NOTE: glReadPixels() returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer + // WARNING: Getting alpha channel! Be careful, it can be transparent if not cleared properly! glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, imgData); - // Flip image vertically! - // NOTE: Alpha value has already been applied to RGB in framebuffer, we don't need it! + // Flip image vertically + // NOTE: Alpha value has already been applied to RGB in framebuffer, not needed anymore for (int y = height - 1; y >= height/2; y--) { for (int x = 0; x < (width*4); x += 4) @@ -3904,13 +3903,13 @@ void rlUnloadFramebuffer(unsigned int id) { #if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) // Query depth attachment to automatically delete texture/renderbuffer - int depthType = 0, depthId = 0; + int depthType = 0; glBindFramebuffer(GL_FRAMEBUFFER, id); // Bind framebuffer to query depth texture type glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &depthType); - // TODO: Review warning retrieving object name in WebGL // WARNING: WebGL: INVALID_ENUM: getFramebufferAttachmentParameter: invalid parameter name // REF: https://registry.khronos.org/webgl/specs/latest/1.0/ + int depthId = 0; glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthId); unsigned int depthIdU = (unsigned int)depthId; @@ -4190,15 +4189,15 @@ unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) if (fsCode != NULL) fragmentShaderId = rlCompileShader(fsCode, GL_FRAGMENT_SHADER); else fragmentShaderId = RLGL.State.defaultFShaderId; - // In case vertex and fragment shader are the default ones, no need to recompile, we can just assign the default shader program id + // In case vertex and fragment shader are the default ones, no need to recompile, just assign the default shader program id if ((vertexShaderId == RLGL.State.defaultVShaderId) && (fragmentShaderId == RLGL.State.defaultFShaderId)) id = RLGL.State.defaultShaderId; else if ((vertexShaderId > 0) && (fragmentShaderId > 0)) { - // One of or both shader are new, we need to compile a new shader program + // One of or both shader are new, a new shader program needs to be compiled id = rlLoadShaderProgram(vertexShaderId, fragmentShaderId); - // We can detach and delete vertex/fragment shaders (if not default ones) - // NOTE: We detach shader before deletion to make sure memory is freed + // Detaching and deleting vertex/fragment shaders (if not default ones) + // WARNING: Detach shader before deletion to make sure memory is freed if (vertexShaderId != RLGL.State.defaultVShaderId) { // WARNING: Shader program linkage could fail and returned id is 0 @@ -4212,10 +4211,10 @@ unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) glDeleteShader(fragmentShaderId); } - // In case shader program loading failed, we assign default shader + // In case shader program loading failed, assign default shader if (id == 0) { - // In case shader loading fails, we return the default shader + // In case shader loading fails, reassigning default shader TRACELOG(RL_LOG_WARNING, "SHADER: Failed to load custom shader code, using default shader"); id = RLGL.State.defaultShaderId; } @@ -4737,9 +4736,9 @@ Matrix rlGetMatrixTransform(void) Matrix mat = rlMatrixIdentity(); #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // TODO: Consider possible transform matrices in the RLGL.State.stack - // Is this the right order? or should we start with the first stored matrix instead of the last one? //Matrix matStackTransform = rlMatrixIdentity(); //for (int i = RLGL.State.stackCounter; i > 0; i--) matStackTransform = rlMatrixMultiply(RLGL.State.stack[i], matStackTransform); + mat = RLGL.State.transform; #endif return mat; From 49cd2ddaa15efd1617efd142c74386a99a2f4d30 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 9 Feb 2026 22:24:07 +0100 Subject: [PATCH 415/430] Update rcore.c --- src/rcore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rcore.c b/src/rcore.c index 2d7b90b9a..6974314cc 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -277,7 +277,7 @@ #define FILE_FILTER_TAG_DIR_ONLY "DIR*" // Filter to include directories on directory scan #endif // NOTE: Used in ScanDirectoryFiles(), LoadDirectoryFilesEx() and GetDirectoryFileCountEx() -// Flags operation macros +// Flags bitwise operation macros #define FLAG_SET(n, f) ((n) |= (f)) #define FLAG_CLEAR(n, f) ((n) &= ~(f)) #define FLAG_TOGGLE(n, f) ((n) ^= (f)) From eba1fca93378bbccf2e7f6374bd6a5c474908665 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 9 Feb 2026 22:24:44 +0100 Subject: [PATCH 416/430] Update rcore.c --- src/rcore.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 6974314cc..e6338367c 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -18,18 +18,19 @@ * - macOS/OSX (x64, arm64) * - Others (not tested) * > PLATFORM_WEB_RGFW: +* > PLATFORM_WEB (GLFW + Emscripten): * - HTML5 (WebAssembly) -* > PLATFORM_WEB: +* > PLATFORM_WEB_EMSCRIPTEN (Emscripten): * - HTML5 (WebAssembly) -* > PLATFORM_DRM: +* > PLATFORM_DRM (native DRM): * - Raspberry Pi 0-5 (DRM/KMS) * - Linux DRM subsystem (KMS mode) -* > PLATFORM_ANDROID: +* - Embedded devices (with GPU) +* > PLATFORM_ANDROID (native NDK): * - Android (ARM, ARM64) -* > PLATFORM_DESKTOP_WIN32 (Native Win32): -* - Windows (Win32, Win64) * > PLATFORM_MEMORY * - Memory framebuffer output, using software renderer, no OS required +* * CONFIGURATION: * #define SUPPORT_DEFAULT_FONT (default) * Default font is loaded on window initialization to be available for the user to render simple text From a654beb5654bbb41a2e53e6a4005f3de560025e6 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 9 Feb 2026 22:25:20 +0100 Subject: [PATCH 417/430] REVIEWED: Comments --- src/raylib.h | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/raylib.h b/src/raylib.h index 8a0a14dad..66cfe4387 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -4,13 +4,12 @@ * * FEATURES: * - NO external dependencies, all required libraries included with raylib -* - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, -* MacOS, Haiku, Android, Raspberry Pi, DRM native, HTML5 +* - Multiplatform: Windows, Linux, macOS, FreeBSD, Web, Android, Raspberry Pi, DRM native... * - Written in plain C code (C99) in PascalCase/camelCase notation * - Hardware accelerated with OpenGL (1.1, 2.1, 3.3, 4.3, ES2, ES3 - choose at compile) -* - Unique OpenGL abstraction layer (usable as standalone module): [rlgl] +* - Custom OpenGL abstraction layer (usable as standalone module): [rlgl] * - Multiple Fonts formats supported (TTF, OTF, FNT, BDF, Sprite fonts) -* - Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC) +* - Many texture formats supportted, including compressed formats (DXT, ETC, ASTC) * - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! * - Flexible Materials system, supporting classic maps and PBR maps * - Animated 3D models supported (skeletal bones animation) (IQM, M3D, GLTF) @@ -26,10 +25,9 @@ * - One default Shader is loaded on rlglInit()->rlLoadShaderDefault() [rlgl] (OpenGL 3.3 or ES2) * - One default RenderBatch is loaded on rlglInit()->rlLoadRenderBatch() [rlgl] (OpenGL 3.3 or ES2) * -* DEPENDENCIES (included): -* [rcore][GLFW] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input -* [rcore][RGFW] rgfw (ColleagueRiley - github.com/ColleagueRiley/RGFW) for window/context management and input -* [rlgl] glad/glad_gles2 (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading +* DEPENDENCIES: +* [rcore] Depends on the selected platform backend, check rcore.c header for details +* [rlgl] glad/glad_gles2 (David Herberth - github.com/Dav1dde/glad) for OpenGL extensions loading * [raudio] miniaudio (David Reid - github.com/mackron/miniaudio) for audio device/context management * * OPTIONAL DEPENDENCIES (included): @@ -41,6 +39,7 @@ * [rtextures] stb_image_write (Sean Barret) for image writing (BMP, TGA, PNG, JPG) * [rtextures] stb_image_resize2 (Sean Barret) for image resizing algorithms * [rtextures] stb_perlin (Sean Barret) for Perlin Noise image generation +* [rtextures] rl_gputex (Ramon Santamaria) for GPU-compressed texture formats * [rtext] stb_truetype (Sean Barret) for ttf fonts loading * [rtext] stb_rect_pack (Sean Barret) for rectangles packing * [rmodels] par_shapes (Philip Rideout) for parametric 3d shapes generation @@ -1102,7 +1101,7 @@ RLAPI void SetTraceLogLevel(int logLevel); // Set the curre RLAPI void TraceLog(int logLevel, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...) RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log -// Memory management, using internal allocators +// Memory management, using internal allocators RLAPI void *MemAlloc(unsigned int size); // Internal memory allocator RLAPI void *MemRealloc(void *ptr, unsigned int size); // Internal memory reallocator RLAPI void MemFree(void *ptr); // Internal memory free From f6910bc1e0c413441d3176c8568d132eea160621 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 9 Feb 2026 22:25:52 +0100 Subject: [PATCH 418/430] Update rcore_drm.c --- src/platforms/rcore_drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 68431030b..6d3394f69 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -1469,7 +1469,7 @@ int InitPlatform(void) if (!eglChooseConfig(platform.device, framebufferAttribs, configs, numConfigs, &matchingNumConfigs)) { TRACELOG(LOG_WARNING, "DISPLAY: Failed to choose EGL config: 0x%x", eglGetError()); - free(configs); + RL_FREE(configs); return -1; } From e67dc15a52f404f811ab9064225f5dc77258d521 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 9 Feb 2026 22:25:55 +0100 Subject: [PATCH 419/430] Update rcore_desktop_glfw.c --- src/platforms/rcore_desktop_glfw.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index e3078dacf..b13018576 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1049,14 +1049,14 @@ Image GetClipboardImage(void) #if defined(SUPPORT_CLIPBOARD_IMAGE) #if defined(_WIN32) unsigned long long int dataSize = 0; - void *fileData = NULL; + void *bmpData = NULL; int width = 0; int height = 0; - fileData = (void *)Win32GetClipboardImageData(&width, &height, &dataSize); + bmpData = (void *)Win32GetClipboardImageData(&width, &height, &dataSize); - if (fileData == NULL) TRACELOG(LOG_WARNING, "Clipboard image: Couldn't get clipboard data."); - else image = LoadImageFromMemory(".bmp", (const unsigned char *)fileData, (int)dataSize); + if (bmpData == NULL) TRACELOG(LOG_WARNING, "Clipboard image: Couldn't get clipboard data."); + else image = LoadImageFromMemory(".bmp", (const unsigned char *)bmpData, (int)dataSize); #else TRACELOG(LOG_WARNING, "GetClipboardImage() not implemented on target platform"); #endif From 9861baf4b7aa0b6061738c5a795890a2f9ae1ce5 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 9 Feb 2026 22:26:07 +0100 Subject: [PATCH 420/430] Update textures_framebuffer_rendering.c --- examples/textures/textures_framebuffer_rendering.c | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/textures/textures_framebuffer_rendering.c b/examples/textures/textures_framebuffer_rendering.c index 484192739..097ae1f2f 100644 --- a/examples/textures/textures_framebuffer_rendering.c +++ b/examples/textures/textures_framebuffer_rendering.c @@ -148,6 +148,7 @@ int main(void) //-------------------------------------------------------------------------------------- UnloadRenderTexture(observerTarget); UnloadRenderTexture(subjectTarget); + CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- From f190c6a4d479f6a8ee7ccf03cebe16848512b056 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 9 Feb 2026 22:27:16 +0100 Subject: [PATCH 421/430] Update rcore.c --- src/rcore.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rcore.c b/src/rcore.c index e6338367c..4568477d2 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -13,6 +13,7 @@ * - Linux (X11/Wayland desktop mode) * - Others (not tested) * > PLATFORM_DESKTOP_RGFW (RGFW backend): +* > PLATFORM_DESKTOP_WIN32 (native Win32): * - Windows (Win32, Win64) * - Linux (X11/Wayland desktop mode) * - macOS/OSX (x64, arm64) From 84f75785eeebc43a2c96cd4fcd3c21150027ebc3 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 9 Feb 2026 22:29:13 +0100 Subject: [PATCH 422/430] Update rcore.c --- src/rcore.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 4568477d2..49a928372 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -13,16 +13,18 @@ * - Linux (X11/Wayland desktop mode) * - Others (not tested) * > PLATFORM_DESKTOP_RGFW (RGFW backend): -* > PLATFORM_DESKTOP_WIN32 (native Win32): * - Windows (Win32, Win64) * - Linux (X11/Wayland desktop mode) * - macOS/OSX (x64, arm64) * - Others (not tested) -* > PLATFORM_WEB_RGFW: +* > PLATFORM_DESKTOP_WIN32 (native Win32): +* - Windows (Win32, Win64) * > PLATFORM_WEB (GLFW + Emscripten): * - HTML5 (WebAssembly) * > PLATFORM_WEB_EMSCRIPTEN (Emscripten): * - HTML5 (WebAssembly) +* > PLATFORM_WEB_RGFW (Emscripten): +* - HTML5 (WebAssembly) * > PLATFORM_DRM (native DRM): * - Raspberry Pi 0-5 (DRM/KMS) * - Linux DRM subsystem (KMS mode) From 7e59e1d93d602c9b71c0382d2c989167d5e432bb Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 9 Feb 2026 22:29:47 +0100 Subject: [PATCH 423/430] REVIEWED: Formating --- src/external/win32_clipboard.h | 103 +++++++++++++++------------------ 1 file changed, 47 insertions(+), 56 deletions(-) diff --git a/src/external/win32_clipboard.h b/src/external/win32_clipboard.h index 1f9a27521..75cd720a9 100644 --- a/src/external/win32_clipboard.h +++ b/src/external/win32_clipboard.h @@ -4,7 +4,7 @@ #ifndef WIN32_CLIPBOARD_ #define WIN32_CLIPBOARD_ -unsigned char* Win32GetClipboardImageData(int* width, int* height, unsigned long long int *dataSize); +unsigned char *Win32GetClipboardImageData(int *width, int *height, unsigned long long int *dataSize); #endif // WIN32_CLIPBOARD_ #ifdef WIN32_CLIPBOARD_IMPLEMENTATION @@ -92,7 +92,6 @@ unsigned char* Win32GetClipboardImageData(int* width, int* height, unsigned long typedef int WINBOOL; - #if !defined(_WINUSER_) || !defined(WINUSER_ALREADY_INCLUDED) WINUSERAPI WINBOOL WINAPI OpenClipboard(HWND hWndNewOwner); WINUSERAPI WINBOOL WINAPI CloseClipboard(VOID); @@ -170,8 +169,7 @@ typedef struct tagRGBQUAD { } RGBQUAD, *LPRGBQUAD; #endif - -// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wmf/4e588f70-bd92-4a6f-b77f-35d0feaf7a57 +// REF: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wmf/4e588f70-bd92-4a6f-b77f-35d0feaf7a57 #define BI_RGB 0x0000 #define BI_RLE8 0x0001 #define BI_RLE4 0x0002 @@ -184,10 +182,10 @@ typedef struct tagRGBQUAD { #endif -// https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats +// REF: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats #define CF_DIB 8 -// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setsystemcursor +// REF: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setsystemcursor // #define OCR_NORMAL 32512 // Normal select // #define OCR_IBEAM 32513 // Text select // #define OCR_WAIT 32514 // Busy @@ -202,36 +200,37 @@ typedef struct tagRGBQUAD { // #define OCR_HAND 32649 // Link select // #define OCR_APPSTARTING 32650 // +static BOOL OpenClipboardRetrying(HWND handle); // Open clipboard with a number of retries +static int GetPixelDataOffset(BITMAPINFOHEADER bih); //---------------------------------------------------------------------------------- // Module Internal Functions Declaration //---------------------------------------------------------------------------------- - - -static BOOL OpenClipboardRetrying(HWND handle); // Open clipboard with a number of retries -static int GetPixelDataOffset(BITMAPINFOHEADER bih); - -unsigned char* Win32GetClipboardImageData(int* width, int* height, unsigned long long int *dataSize) +unsigned char *Win32GetClipboardImageData(int *width, int *height, unsigned long long int *dataSize) { HWND win = NULL; // Get from somewhere but is doesnt seem to matter - const char* msgString = ""; + const char *msgString = ""; int severity = LOG_INFO; - BYTE* bmpData = NULL; - if (!OpenClipboardRetrying(win)) { + BYTE *bmpData = NULL; + + if (!OpenClipboardRetrying(win)) + { severity = LOG_ERROR; msgString = "Couldn't open clipboard"; goto end; } HGLOBAL clipHandle = (HGLOBAL)GetClipboardData(CF_DIB); - if (!clipHandle) { + if (!clipHandle) + { severity = LOG_ERROR; msgString = "Clipboard data is not an Image"; goto close; } BITMAPINFOHEADER *bmpInfoHeader = (BITMAPINFOHEADER *)GlobalLock(clipHandle); - if (!bmpInfoHeader) { + if (!bmpInfoHeader) + { // Mapping from HGLOBAL to our local *address space* failed severity = LOG_ERROR; msgString = "Clipboard data failed to be locked"; @@ -242,7 +241,8 @@ unsigned char* Win32GetClipboardImageData(int* width, int* height, unsigned long *height = bmpInfoHeader->biHeight; SIZE_T clipDataSize = GlobalSize(clipHandle); - if (clipDataSize < sizeof(BITMAPINFOHEADER)) { + if (clipDataSize < sizeof(BITMAPINFOHEADER)) + { // Format CF_DIB needs space for BITMAPINFOHEADER struct. msgString = "Clipboard has Malformed data"; severity = LOG_ERROR; @@ -259,31 +259,29 @@ unsigned char* Win32GetClipboardImageData(int* width, int* height, unsigned long // //--------------------------------------------------------------------------------// - BITMAPFILEHEADER bmpFileHeader = {0}; + BITMAPFILEHEADER bmpFileHeader = { 0 }; SIZE_T bmpFileSize = sizeof(bmpFileHeader) + clipDataSize; *dataSize = bmpFileSize; - bmpFileHeader.bfType = 0x4D42; //https://stackoverflow.com/questions/601430/multibyte-character-constants-and-bitmap-file-header-type-constants#601536 + bmpFileHeader.bfType = 0x4D42; // REF: https://stackoverflow.com/questions/601430/multibyte-character-constants-and-bitmap-file-header-type-constants#601536 bmpFileHeader.bfSize = (DWORD)bmpFileSize; // Up to 4GB works fine bmpFileHeader.bfOffBits = sizeof(bmpFileHeader) + pixelOffset; - // - // Each process has a default heap provided by the system + // WARNING: Each process has a default heap provided by the system // Memory objects allocated by GlobalAlloc and LocalAlloc are in private, - // committed pages with read/write access that cannot be accessed by other processes. + // committed pages with read/write access that cannot be accessed by other processes // // This may be wrong since we might be allocating in a DLL and freeing from another module, the main application // that may cause heap corruption. We could create a FreeImage function - // - bmpData = (BYTE *)malloc(sizeof(bmpFileHeader) + clipDataSize); + bmpData = (BYTE *)RL_MALLOC(sizeof(bmpFileHeader) + clipDataSize); // First we add the header for a bmp file - memcpy(bmpData, &bmpFileHeader, sizeof(bmpFileHeader)); + memcpy(bmpData, &bmpFileHeader, sizeof(bmpFileHeader)); // Add BMP file header data // Then we add the header for the bmp itself + the pixel data - memcpy(bmpData + sizeof(bmpFileHeader), bmpInfoHeader, clipDataSize); + memcpy(bmpData + sizeof(bmpFileHeader), bmpInfoHeader, clipDataSize); // Add BMP info header data + msgString = "Clipboad image acquired successfully"; - unlock: GlobalUnlock(clipHandle); close: @@ -291,6 +289,7 @@ close: end: TRACELOG(severity, msgString); + return bmpData; } @@ -298,65 +297,57 @@ static BOOL OpenClipboardRetrying(HWND hWnd) { static const int maxTries = 20; static const int sleepTimeMS = 60; - for (int _ = 0; _ < maxTries; ++_) + + for (int i = 0; i < maxTries; i++) { // Might be being hold by another process // Or yourself forgot to CloseClipboard - if (OpenClipboard(hWnd)) { - return true; - } + if (OpenClipboard(hWnd)) return true; + Sleep(sleepTimeMS); } + return false; } -// Based off of researching microsoft docs and reponses from this question https://stackoverflow.com/questions/30552255/how-to-read-a-bitmap-from-the-windows-clipboard#30552856 -// https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader // Get the byte offset where does the pixels data start (from a packed DIB) +// REF: https://stackoverflow.com/questions/30552255/how-to-read-a-bitmap-from-the-windows-clipboard#30552856 +// REF: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader static int GetPixelDataOffset(BITMAPINFOHEADER bih) { int offset = 0; const unsigned int rgbaSize = sizeof(RGBQUAD); - // biSize Specifies the number of bytes required by the structure + // NOTE: biSize specifies the number of bytes required by the structure // We expect to always be 40 because it should be packed - if (40 == bih.biSize && 40 == sizeof(BITMAPINFOHEADER)) + if ((40 == bih.biSize) && (40 == sizeof(BITMAPINFOHEADER))) { - // - // biBitCount Specifies the number of bits per pixel. + // NOTE: biBitCount specifies the number of bits per pixel. // Might exist some bit masks *after* the header and *before* the pixel offset // we're looking, but only if we have more than // 8 bits per pixel, so we need to ajust for that - // if (bih.biBitCount > 8) { // if bih.biCompression is RBG we should NOT offset more - if (bih.biCompression == BI_BITFIELDS) + if (bih.biCompression == BI_BITFIELDS) offset += 3*rgbaSize; + else if (bih.biCompression == 6) // BI_ALPHABITFIELDS { - offset += 3 * rgbaSize; - } else if (bih.biCompression == 6 /* BI_ALPHABITFIELDS */) - { - // Not widely supported, but valid. - offset += 4 * rgbaSize; + // Not widely supported, but valid + offset += 4*rgbaSize; } } } - // - // biClrUsed Specifies the number of color indices in the color table that are actually used by the bitmap. + // NOTE: biClrUsed specifies the number of color indices in the color table that are actually used by the bitmap // If this value is zero, the bitmap uses the maximum number of colors - // corresponding to the value of the biBitCount member for the compression mode specified by biCompression. + // corresponding to the value of the biBitCount member for the compression mode specified by biCompression // If biClrUsed is nonzero and the biBitCount member is less than 16 // the biClrUsed member specifies the actual number of colors - // - if (bih.biClrUsed > 0) { - offset += bih.biClrUsed * rgbaSize; - } else { - if (bih.biBitCount < 16) - { - offset = offset + (rgbaSize << bih.biBitCount); - } + if (bih.biClrUsed > 0) offset += bih.biClrUsed*rgbaSize; + else + { + if (bih.biBitCount < 16) offset = offset + (rgbaSize << bih.biBitCount); } return bih.biSize + offset; From b39cc6bce70b78f0ba771c6b41a37db19e9169e8 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 9 Feb 2026 23:28:02 +0100 Subject: [PATCH 424/430] Update win32_clipboard.h --- src/external/win32_clipboard.h | 152 ++++++++++++++------------------- 1 file changed, 65 insertions(+), 87 deletions(-) diff --git a/src/external/win32_clipboard.h b/src/external/win32_clipboard.h index 75cd720a9..31caaf890 100644 --- a/src/external/win32_clipboard.h +++ b/src/external/win32_clipboard.h @@ -180,6 +180,10 @@ typedef struct tagRGBQUAD { #define BI_CMYKRLE8 0x000C #define BI_CMYKRLE4 0x000D +// Bitmap not compressed and that the color table consists of four DWORD color masks, +// that specify the red, green, blue, and alpha components of each pixel +#define BI_ALPHABITFIELDS 0x0006 + #endif // REF: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats @@ -208,91 +212,70 @@ static int GetPixelDataOffset(BITMAPINFOHEADER bih); //---------------------------------------------------------------------------------- unsigned char *Win32GetClipboardImageData(int *width, int *height, unsigned long long int *dataSize) { - HWND win = NULL; // Get from somewhere but is doesnt seem to matter - const char *msgString = ""; - int severity = LOG_INFO; - BYTE *bmpData = NULL; + unsigned char *bmpData = NULL; - if (!OpenClipboardRetrying(win)) + if (OpenClipboardRetrying(NULL)) { - severity = LOG_ERROR; - msgString = "Couldn't open clipboard"; - goto end; + HGLOBAL clipHandle = (HGLOBAL)GetClipboardData(CF_DIB); + if (clipHandle != NULL) + { + BITMAPINFOHEADER *bmpInfoHeader = (BITMAPINFOHEADER *)GlobalLock(clipHandle); + if (bmpInfoHeader) + { + *width = bmpInfoHeader->biWidth; + *height = bmpInfoHeader->biHeight; + SIZE_T clipDataSize = GlobalSize(clipHandle); + if (clipDataSize >= sizeof(BITMAPINFOHEADER)) + { + int pixelOffset = GetPixelDataOffset(*bmpInfoHeader); + + // Create the bytes for a correct BMP file and copy the data to a pointer + //------------------------------------------------------------------------ + BITMAPFILEHEADER bmpFileHeader = { 0 }; + SIZE_T bmpFileSize = sizeof(bmpFileHeader) + clipDataSize; + *dataSize = bmpFileSize; + + bmpFileHeader.bfType = 0x4D42; // BMP fil type constant + bmpFileHeader.bfSize = (DWORD)bmpFileSize; // Up to 4GB works fine + bmpFileHeader.bfOffBits = sizeof(bmpFileHeader) + pixelOffset; + + bmpData = (unsigned char *)RL_MALLOC(sizeof(bmpFileHeader) + clipDataSize); + memcpy(bmpData, &bmpFileHeader, sizeof(bmpFileHeader)); // Add BMP file header data + memcpy(bmpData + sizeof(bmpFileHeader), bmpInfoHeader, clipDataSize); // Add BMP info header data + + GlobalUnlock(clipHandle); + CloseClipboard(); + + TRACELOG(LOG_INFO, "Clipboad image acquired successfully"); + //------------------------------------------------------------------------ + } + else + { + TRACELOG(LOG_WARNING, "Clipboard data is malformed"); + GlobalUnlock(clipHandle); + CloseClipboard(); + } + } + else + { + TRACELOG(LOG_WARNING, "Clipboard data failed to be locked"); + GlobalUnlock(clipHandle); + CloseClipboard(); + } + } + else + { + TRACELOG(LOG_WARNING, "Clipboard data is not an image"); + CloseClipboard(); + } } + else TRACELOG(LOG_WARNING, "Clipboard can not be opened"); - HGLOBAL clipHandle = (HGLOBAL)GetClipboardData(CF_DIB); - if (!clipHandle) - { - severity = LOG_ERROR; - msgString = "Clipboard data is not an Image"; - goto close; - } - - BITMAPINFOHEADER *bmpInfoHeader = (BITMAPINFOHEADER *)GlobalLock(clipHandle); - if (!bmpInfoHeader) - { - // Mapping from HGLOBAL to our local *address space* failed - severity = LOG_ERROR; - msgString = "Clipboard data failed to be locked"; - goto unlock; - } - - *width = bmpInfoHeader->biWidth; - *height = bmpInfoHeader->biHeight; - - SIZE_T clipDataSize = GlobalSize(clipHandle); - if (clipDataSize < sizeof(BITMAPINFOHEADER)) - { - // Format CF_DIB needs space for BITMAPINFOHEADER struct. - msgString = "Clipboard has Malformed data"; - severity = LOG_ERROR; - goto unlock; - } - - // Denotes where the pixel data starts from the bmpInfoHeader pointer - int pixelOffset = GetPixelDataOffset(*bmpInfoHeader); - - //--------------------------------------------------------------------------------// - // - // The rest of the section is about create the bytes for a correct BMP file - // Then we copy the data and to a pointer - // - //--------------------------------------------------------------------------------// - - BITMAPFILEHEADER bmpFileHeader = { 0 }; - SIZE_T bmpFileSize = sizeof(bmpFileHeader) + clipDataSize; - *dataSize = bmpFileSize; - - bmpFileHeader.bfType = 0x4D42; // REF: https://stackoverflow.com/questions/601430/multibyte-character-constants-and-bitmap-file-header-type-constants#601536 - - bmpFileHeader.bfSize = (DWORD)bmpFileSize; // Up to 4GB works fine - bmpFileHeader.bfOffBits = sizeof(bmpFileHeader) + pixelOffset; - - // WARNING: Each process has a default heap provided by the system - // Memory objects allocated by GlobalAlloc and LocalAlloc are in private, - // committed pages with read/write access that cannot be accessed by other processes - // - // This may be wrong since we might be allocating in a DLL and freeing from another module, the main application - // that may cause heap corruption. We could create a FreeImage function - bmpData = (BYTE *)RL_MALLOC(sizeof(bmpFileHeader) + clipDataSize); - // First we add the header for a bmp file - memcpy(bmpData, &bmpFileHeader, sizeof(bmpFileHeader)); // Add BMP file header data - // Then we add the header for the bmp itself + the pixel data - memcpy(bmpData + sizeof(bmpFileHeader), bmpInfoHeader, clipDataSize); // Add BMP info header data - - msgString = "Clipboad image acquired successfully"; - -unlock: - GlobalUnlock(clipHandle); -close: - CloseClipboard(); -end: - - TRACELOG(severity, msgString); - return bmpData; } +// Open clipboard with several tries +// NOTE: If parameter is NULL, the open clipboard is associated with the current task static BOOL OpenClipboardRetrying(HWND hWnd) { static const int maxTries = 20; @@ -320,22 +303,18 @@ static int GetPixelDataOffset(BITMAPINFOHEADER bih) // NOTE: biSize specifies the number of bytes required by the structure // We expect to always be 40 because it should be packed - if ((40 == bih.biSize) && (40 == sizeof(BITMAPINFOHEADER))) + if ((bih.biSize == 40) && (sizeof(BITMAPINFOHEADER) == 40)) { - // NOTE: biBitCount specifies the number of bits per pixel. + // NOTE: biBitCount specifies the number of bits per pixel // Might exist some bit masks *after* the header and *before* the pixel offset // we're looking, but only if we have more than // 8 bits per pixel, so we need to ajust for that if (bih.biBitCount > 8) { - // if bih.biCompression is RBG we should NOT offset more + // If (bih.biCompression == BI_RGB) we should NOT offset more if (bih.biCompression == BI_BITFIELDS) offset += 3*rgbaSize; - else if (bih.biCompression == 6) // BI_ALPHABITFIELDS - { - // Not widely supported, but valid - offset += 4*rgbaSize; - } + else if (bih.biCompression == BI_ALPHABITFIELDS) offset += 4*rgbaSize; // Not widely supported, but valid } } @@ -353,4 +332,3 @@ static int GetPixelDataOffset(BITMAPINFOHEADER bih) return bih.biSize + offset; } #endif // WIN32_CLIPBOARD_IMPLEMENTATION -// EOF From 3b647c85e1749c8131e46387aa044cb009e60b10 Mon Sep 17 00:00:00 2001 From: "Nikolai S. Kiselev" Date: Tue, 10 Feb 2026 00:05:35 +0100 Subject: [PATCH 425/430] raymath: wrap float3 and float16 for consistency with other types (#5540) --- src/raymath.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/raymath.h b/src/raymath.h index 0f9cbc38b..214495a2c 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -164,13 +164,19 @@ typedef struct Matrix { #endif // NOTE: Helper types to be used instead of array return types for *ToFloat functions +#if !defined(RL_FLOAT3_TYPE) typedef struct float3 { float v[3]; } float3; +#define RL_FLOAT3_TYPE +#endif +#if !defined(RL_FLOAT16_TYPE) typedef struct float16 { float v[16]; } float16; +#define RL_FLOAT16_TYPE +#endif #include // Required for: sinf(), cosf(), tan(), atan2f(), sqrtf(), floor(), fminf(), fmaxf(), fabsf() From efda35b309e012dda56288072d4de57448f85fd0 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 10 Feb 2026 00:37:45 +0100 Subject: [PATCH 426/430] Update win32_clipboard.h --- src/external/win32_clipboard.h | 78 ++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/src/external/win32_clipboard.h b/src/external/win32_clipboard.h index 31caaf890..6845b0c2e 100644 --- a/src/external/win32_clipboard.h +++ b/src/external/win32_clipboard.h @@ -16,38 +16,38 @@ unsigned char *Win32GetClipboardImageData(int *width, int *height, unsigned long // NOTE: These search for architecture is taken from "Windows.h", and it's necessary if we really don't wanna import windows.h // and still make it compile on msvc, because import indirectly importing "winnt.h" (e.g. ) can cause problems is these are not defined. #if !defined(_X86_) && !defined(_68K_) && !defined(_MPPC_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_IX86) -#define _X86_ -#if !defined(_CHPE_X86_ARM64_) && defined(_M_HYBRID) -#define _CHPE_X86_ARM64_ -#endif + #define _X86_ + #if !defined(_CHPE_X86_ARM64_) && defined(_M_HYBRID) + #define _CHPE_X86_ARM64_ + #endif #endif #if !defined(_AMD64_) && !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64_) && (defined(_M_AMD64) || defined(_M_ARM64EC)) -#define _AMD64_ + #define _AMD64_ #endif #if !defined(_ARM_) && !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_ARM) -#define _ARM_ + #define _ARM_ #endif #if !defined(_ARM64_) && !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64EC_) && defined(_M_ARM64) -#define _ARM64_ + #define _ARM64_ #endif #if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_ARM_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_ARM64EC) -#define _ARM64EC_ + #define _ARM64EC_ #endif #if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_M68K) -#define _68K_ + #define _68K_ #endif #if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_MPPC) -#define _MPPC_ + #define _MPPC_ #endif #if !defined(_IA64_) && !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_M_IX86) && !defined(_AMD64_) && !defined(_ARM_) && !defined(_ARM64_) && !defined(_ARM64EC_) && defined(_M_IA64) -#define _IA64_ + #define _IA64_ #endif @@ -59,35 +59,35 @@ unsigned char *Win32GetClipboardImageData(int *width, int *height, unsigned long // #include #ifndef WINAPI -#if defined(_ARM_) -#define WINAPI -#else -#define WINAPI __stdcall -#endif + #if defined(_ARM_) + #define WINAPI + #else + #define WINAPI __stdcall + #endif #endif #ifndef WINAPI -#if defined(_ARM_) -#define WINAPI -#else -#define WINAPI __stdcall -#endif + #if defined(_ARM_) + #define WINAPI + #else + #define WINAPI __stdcall + #endif #endif #ifndef WINBASEAPI -#ifndef _KERNEL32_ -#define WINBASEAPI DECLSPEC_IMPORT -#else -#define WINBASEAPI -#endif + #ifndef _KERNEL32_ + #define WINBASEAPI DECLSPEC_IMPORT + #else + #define WINBASEAPI + #endif #endif #ifndef WINUSERAPI -#ifndef _USER32_ -#define WINUSERAPI __declspec (dllimport) -#else -#define WINUSERAPI -#endif + #ifndef _USER32_ + #define WINUSERAPI __declspec (dllimport) + #else + #define WINUSERAPI + #endif #endif typedef int WINBOOL; @@ -115,7 +115,7 @@ WINUSERAPI HWND WINAPI GetOpenClipboardWindow(VOID); #endif #ifndef HGLOBAL -#define HGLOBAL void* + #define HGLOBAL void* #endif #if !defined(_WINBASE_) || !defined(WINBASE_ALREADY_INCLUDED) @@ -124,7 +124,6 @@ WINBASEAPI LPVOID WINAPI GlobalLock (HGLOBAL hMem); WINBASEAPI WINBOOL WINAPI GlobalUnlock (HGLOBAL hMem); #endif - #if !defined(_WINGDI_) || !defined(WINGDI_ALREADY_INCLUDED) #ifndef BITMAPINFOHEADER_ALREADY_DEFINED #define BITMAPINFOHEADER_ALREADY_DEFINED @@ -183,7 +182,6 @@ typedef struct tagRGBQUAD { // Bitmap not compressed and that the color table consists of four DWORD color masks, // that specify the red, green, blue, and alpha components of each pixel #define BI_ALPHABITFIELDS 0x0006 - #endif // REF: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats @@ -204,12 +202,15 @@ typedef struct tagRGBQUAD { // #define OCR_HAND 32649 // Link select // #define OCR_APPSTARTING 32650 // -static BOOL OpenClipboardRetrying(HWND handle); // Open clipboard with a number of retries -static int GetPixelDataOffset(BITMAPINFOHEADER bih); - //---------------------------------------------------------------------------------- // Module Internal Functions Declaration //---------------------------------------------------------------------------------- +static BOOL OpenClipboardRetrying(HWND handle); // Open clipboard with a number of retries +static int GetPixelDataOffset(BITMAPINFOHEADER bih); // Get pixel data offset from DIB image + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- unsigned char *Win32GetClipboardImageData(int *width, int *height, unsigned long long int *dataSize) { unsigned char *bmpData = NULL; @@ -274,6 +275,9 @@ unsigned char *Win32GetClipboardImageData(int *width, int *height, unsigned long return bmpData; } +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- // Open clipboard with several tries // NOTE: If parameter is NULL, the open clipboard is associated with the current task static BOOL OpenClipboardRetrying(HWND hWnd) From 4b01c23ba678d4c1243fcc6ecc4672df9fe71872 Mon Sep 17 00:00:00 2001 From: dtasada <83500532+dtasada@users.noreply.github.com> Date: Tue, 10 Feb 2026 08:33:31 +0100 Subject: [PATCH 427/430] [build] Zig master branch compatibility for `build.zig`. (#5520) * fixed build errors with zig. now compatible with zig master 0.16.0-dev.1593+c13857e50. still backwards compatible with 0.15.1 * [build] building with zig-master 0.16.0-dev.2349+204fa8959. * [build] building with zig-master 0.16.0-dev.2349+204fa8959, now compatible with zig 0.15 * build: removed compatibility with zig 0.15.2. * inlined processExample function to minimize diffs --- build.zig | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/build.zig b/build.zig index ab98bbe98..7eb46699e 100644 --- a/build.zig +++ b/build.zig @@ -2,7 +2,7 @@ const std = @import("std"); const builtin = @import("builtin"); /// Minimum supported version of Zig -const min_ver = "0.15.1"; +const min_ver = "0.16.0-dev.2349+204fa8959"; const emccOutputDir = "zig-out" ++ std.fs.path.sep_str ++ "htmlout" ++ std.fs.path.sep_str; const emccOutputFile = "index.html"; @@ -197,7 +197,7 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. } var c_source_files: std.ArrayList([]const u8) = try .initCapacity(b.allocator, 2); - c_source_files.appendSliceAssumeCapacity(&.{ "src/rcore.c" }); + c_source_files.appendSliceAssumeCapacity(&.{"src/rcore.c"}); if (options.rshapes) { try c_source_files.append(b.allocator, "src/rshapes.c"); @@ -448,7 +448,7 @@ pub const Options = struct { .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 &.{}, - .android_ndk = b.option([]const u8, "android_ndk", "specify path to android ndk") orelse std.process.getEnvVarOwned(b.allocator, "ANDROID_NDK_HOME") catch "", + .android_ndk = b.option([]const u8, "android_ndk", "specify path to android ndk") orelse b.graph.environ_map.get("ANDROID_NDK_HOME") orelse "", .android_api_version = b.option([]const u8, "android_api_version", "specify target android API level") orelse defaults.android_api_version, }; } @@ -523,15 +523,17 @@ fn addExamples( ) !*std.Build.Step { const all = b.step(module, "All " ++ module ++ " examples"); const module_subpath = b.pathJoin(&.{ "examples", module }); - var dir = try std.fs.cwd().openDir(b.pathFromRoot(module_subpath), .{ .iterate = true }); - defer dir.close(); + + var dir = try std.Io.Dir.cwd().openDir(b.graph.io, b.pathFromRoot(module_subpath), .{ .iterate = true }); + defer dir.close(b.graph.io); var iter = dir.iterate(); - while (try iter.next()) |entry| { + while (try iter.next(b.graph.io)) |entry| { if (entry.kind != .file) continue; const extension_idx = std.mem.lastIndexOf(u8, entry.name, ".c") orelse continue; const name = entry.name[0..extension_idx]; - const path = b.pathJoin(&.{ module_subpath, entry.name }); + const filename = try std.fmt.allocPrint(b.allocator, "{s}.c", .{name}); + const path = b.pathJoin(&.{ module_subpath, filename }); // zig's mingw headers do not include pthread.h if (std.mem.eql(u8, "core_loading_thread", name) and target.result.os.tag == .windows) continue; @@ -553,12 +555,11 @@ fn addExamples( }); if (std.mem.eql(u8, name, "rlgl_standalone")) { - //TODO: Make rlgl_standalone example work - continue; + exe_mod.addIncludePath(b.path("src")); + exe_mod.addIncludePath(b.path("src/external/glfw/include")); } if (std.mem.eql(u8, name, "raylib_opengl_interop")) { - //TODO: Make raylib_opengl_interop example work - continue; + exe_mod.addIncludePath(b.path("src/external")); } const emcc_flags = emsdk.emccDefaultFlags(b.allocator, .{ .optimize = optimize }); @@ -650,6 +651,7 @@ fn addExamples( all.dependOn(&install_cmd.step); } } + return all; } From 48ec41f0ec2d2ed26c917c5d88d8e25e57c37db2 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 10 Feb 2026 18:02:36 +0100 Subject: [PATCH 428/430] Update raylib.h --- src/raylib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raylib.h b/src/raylib.h index 66cfe4387..b4b175919 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -417,11 +417,11 @@ typedef struct Model { // ModelAnimation typedef struct ModelAnimation { + char name[32]; // Animation name int boneCount; // Number of bones int frameCount; // Number of animation frames BoneInfo *bones; // Bones information (skeleton) Transform **framePoses; // Poses array by frame - char name[32]; // Animation name } ModelAnimation; // Ray, ray for raycasting From 3aced1fd7c44c5089c1d8d79d2b37adfa094039d Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 10 Feb 2026 18:02:42 +0100 Subject: [PATCH 429/430] Update rmodels.c --- src/rmodels.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index 6f3ae995f..755d0437d 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -2358,11 +2358,12 @@ void UpdateModelAnimation(Model model, ModelAnimation anim, int frame) Mesh mesh = model.meshes[m]; Vector3 animVertex = { 0 }; Vector3 animNormal = { 0 }; + const int vValues = mesh.vertexCount*3; + int boneId = 0; int boneCounter = 0; - float boneWeight = 0.0; + float boneWeight = 0.0f; bool updated = false; // Flag to check when anim vertex information is updated - const int vValues = mesh.vertexCount*3; // Skip if missing bone data, causes segfault without on some models if ((mesh.boneWeights == NULL) || (mesh.boneIds == NULL)) continue; @@ -2388,7 +2389,7 @@ void UpdateModelAnimation(Model model, ModelAnimation anim, int frame) // Early stop when no transformation will be applied if (boneWeight == 0.0f) continue; animVertex = (Vector3){ mesh.vertices[vCounter], mesh.vertices[vCounter + 1], mesh.vertices[vCounter + 2] }; - animVertex = Vector3Transform(animVertex,model.meshes[m].boneMatrices[boneId]); + animVertex = Vector3Transform(animVertex, model.meshes[m].boneMatrices[boneId]); mesh.animVertices[vCounter] += animVertex.x*boneWeight; mesh.animVertices[vCounter+1] += animVertex.y*boneWeight; mesh.animVertices[vCounter+2] += animVertex.z*boneWeight; From 919ad68ca71fc2f4c173694e163f3df21edefca0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 17:02:58 +0000 Subject: [PATCH 430/430] rlparser: update raylib_api.* by CI --- tools/rlparser/output/raylib_api.json | 10 +++++----- tools/rlparser/output/raylib_api.lua | 10 +++++----- tools/rlparser/output/raylib_api.txt | 10 +++++----- tools/rlparser/output/raylib_api.xml | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tools/rlparser/output/raylib_api.json b/tools/rlparser/output/raylib_api.json index d4c059c50..96875fe87 100644 --- a/tools/rlparser/output/raylib_api.json +++ b/tools/rlparser/output/raylib_api.json @@ -1029,6 +1029,11 @@ "name": "ModelAnimation", "description": "ModelAnimation", "fields": [ + { + "type": "char[32]", + "name": "name", + "description": "Animation name" + }, { "type": "int", "name": "boneCount", @@ -1048,11 +1053,6 @@ "type": "Transform **", "name": "framePoses", "description": "Poses array by frame" - }, - { - "type": "char[32]", - "name": "name", - "description": "Animation name" } ] }, diff --git a/tools/rlparser/output/raylib_api.lua b/tools/rlparser/output/raylib_api.lua index 2de69f69c..a1f79cbe6 100644 --- a/tools/rlparser/output/raylib_api.lua +++ b/tools/rlparser/output/raylib_api.lua @@ -1029,6 +1029,11 @@ return { name = "ModelAnimation", description = "ModelAnimation", fields = { + { + type = "char[32]", + name = "name", + description = "Animation name" + }, { type = "int", name = "boneCount", @@ -1048,11 +1053,6 @@ return { type = "Transform **", name = "framePoses", description = "Poses array by frame" - }, - { - type = "char[32]", - name = "name", - description = "Animation name" } } }, diff --git a/tools/rlparser/output/raylib_api.txt b/tools/rlparser/output/raylib_api.txt index 53dbf8813..e7ff4c98b 100644 --- a/tools/rlparser/output/raylib_api.txt +++ b/tools/rlparser/output/raylib_api.txt @@ -466,11 +466,11 @@ Struct 21: Model (9 fields) Struct 22: ModelAnimation (5 fields) Name: ModelAnimation Description: ModelAnimation - Field[1]: int boneCount // Number of bones - Field[2]: int frameCount // Number of animation frames - Field[3]: BoneInfo * bones // Bones information (skeleton) - Field[4]: Transform ** framePoses // Poses array by frame - Field[5]: char[32] name // Animation name + Field[1]: char[32] name // Animation name + Field[2]: int boneCount // Number of bones + Field[3]: int frameCount // Number of animation frames + Field[4]: BoneInfo * bones // Bones information (skeleton) + Field[5]: Transform ** framePoses // Poses array by frame Struct 23: Ray (2 fields) Name: Ray Description: Ray, ray for raycasting diff --git a/tools/rlparser/output/raylib_api.xml b/tools/rlparser/output/raylib_api.xml index c1f9bc818..8734f405d 100644 --- a/tools/rlparser/output/raylib_api.xml +++ b/tools/rlparser/output/raylib_api.xml @@ -214,11 +214,11 @@ + -

>qgSXIoRn@gz)$$d5xnbXc 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 V$RK5K+Z_V_J95~{wA9!&;eUw2Nk{+y From 75ae4d347a58673977000104ccd1b9124deaa6dd Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 14 Nov 2025 16:34:56 +0100 Subject: [PATCH 078/430] REXM: RENAMED: Reports moved to reports directory --- .../{examples_report_issues.md => reports/examples_issues.md} | 0 tools/rexm/{examples_report.md => reports/examples_validation.md} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tools/rexm/{examples_report_issues.md => reports/examples_issues.md} (100%) rename tools/rexm/{examples_report.md => reports/examples_validation.md} (100%) diff --git a/tools/rexm/examples_report_issues.md b/tools/rexm/reports/examples_issues.md similarity index 100% rename from tools/rexm/examples_report_issues.md rename to tools/rexm/reports/examples_issues.md diff --git a/tools/rexm/examples_report.md b/tools/rexm/reports/examples_validation.md similarity index 100% rename from tools/rexm/examples_report.md rename to tools/rexm/reports/examples_validation.md From d29112fb1f5f00e91150d9a56c1861f491b6574d Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 14 Nov 2025 16:36:00 +0100 Subject: [PATCH 079/430] Create examples_testing_windows.md --- tools/rexm/reports/examples_testing_windows.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tools/rexm/reports/examples_testing_windows.md diff --git a/tools/rexm/reports/examples_testing_windows.md b/tools/rexm/reports/examples_testing_windows.md new file mode 100644 index 000000000..dca13478f --- /dev/null +++ b/tools/rexm/reports/examples_testing_windows.md @@ -0,0 +1,18 @@ +# EXAMPLES COLLECTION - TESTING REPORT + +## Tested Platform: Windows + +``` +Example automated testing elements validated: + + - [WARN] : WARNING messages count + - [INIT] : Initialization + - [CLOSE] : Closing + - [ASSETS] : Assets loading + - [OTHER] : Other types of warnings + - [RESULT] : Ending program result (0) + +``` +| **EXAMPLE NAME** | [WARN] | [INIT] | [CLOSE] | [ASSETS] | [OTHER] | [RESULT] | +|:---------------------------------|:------:|:------:|:-------:|:--------:|:-------:|:--------:| +| core_highdpi_testbed | 2 | ✔ | ✔ | ✔ | ✔ | ✔ | From 74f7112614e74cd243af8a2941839fb33cf275d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20=E2=9D=A4=EF=B8=8F?= Date: Fri, 14 Nov 2025 11:09:30 -0500 Subject: [PATCH 080/430] [examples] Added: `shapes_rlgl_triangle` example (#5353) * [examples] Added: `shapes_rlgl_triangle` example * correct name * formatting --- examples/shapes/shapes_rlgl_triangle.c | 189 +++++++++++++++++++++++ examples/shapes/shapes_rlgl_triangle.png | Bin 0 -> 22402 bytes 2 files changed, 189 insertions(+) create mode 100644 examples/shapes/shapes_rlgl_triangle.c create mode 100644 examples/shapes/shapes_rlgl_triangle.png diff --git a/examples/shapes/shapes_rlgl_triangle.c b/examples/shapes/shapes_rlgl_triangle.c new file mode 100644 index 000000000..9174110e4 --- /dev/null +++ b/examples/shapes/shapes_rlgl_triangle.c @@ -0,0 +1,189 @@ +/******************************************************************************************* +* +* raylib [shapes] example - rlgl triangle +* +* Example complexity rating: [★★☆☆] 2/4 +* +* Example originally created with raylib 5.6-dev, last time updated with raylib 5.6-dev +* +* Example contributed by Robin (@RobinsAviary) 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) 2025-2025 Robin (@RobinsAviary) +* +********************************************************************************************/ + +#include "raylib.h" +#include "rlgl.h" +#include "raymath.h" + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + SetConfigFlags(FLAG_MSAA_4X_HINT); + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - rlgl triangle"); + + // Starting postions and rendered triangle positions + Vector2 startingPositions[] = {{ 400.0f, 150.0f }, { 300.0f, 300.0f }, { 500.0f, 300.0f }}; + Vector2 trianglePositions[] = { startingPositions[0], startingPositions[1], startingPositions[2] }; + + // Currently selected vertex, -1 means none + int triangleIndex = -1; + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + // Reset index on release + if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) + { + triangleIndex = -1; + } + + // If the user has selected a vertex, offset it by the mouse's delta this frame + if (triangleIndex != -1) + { + Vector2 *position = &trianglePositions[triangleIndex]; + + Vector2 mouseDelta = GetMouseDelta(); + position->x += mouseDelta.x; + position->y += mouseDelta.y; + } + + // Enable/disable backface culling (2-sided triangles, slower to render) + if (IsKeyPressed(KEY_LEFT)) + { + rlEnableBackfaceCulling(); + } + + if (IsKeyPressed(KEY_RIGHT)) + { + rlDisableBackfaceCulling(); + } + + // Reset triangle vertices to starting positions and reset backface culling + if (IsKeyPressed(KEY_R)) + { + trianglePositions[0] = startingPositions[0]; + trianglePositions[1] = startingPositions[1]; + trianglePositions[2] = startingPositions[2]; + + rlEnableBackfaceCulling(); + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + if (IsKeyDown(KEY_SPACE)) + { + // Draw triangle with lines + rlBegin(RL_LINES); + // Three lines, six points + // Define color for next vertex + rlColor4ub(255, 0, 0, 255); + // Define vertex + rlVertex2f(trianglePositions[0].x, trianglePositions[0].y); + rlColor4ub(0, 255, 0, 255); + rlVertex2f(trianglePositions[1].x, trianglePositions[1].y); + + rlColor4ub(0, 255, 0, 255); + rlVertex2f(trianglePositions[1].x, trianglePositions[1].y); + rlColor4ub(0, 0, 255, 255); + rlVertex2f(trianglePositions[2].x, trianglePositions[2].y); + + rlColor4ub(0, 0, 255, 255); + rlVertex2f(trianglePositions[2].x, trianglePositions[2].y); + rlColor4ub(255, 0, 0, 255); + rlVertex2f(trianglePositions[0].x, trianglePositions[0].y); + rlEnd(); + } + else + { + // Draw triangle as a triangle + rlBegin(RL_TRIANGLES); + // One triangle, three points + // Define color for next vertex + rlColor4ub(255, 0, 0, 255); + // Define vertex + rlVertex2f(trianglePositions[0].x, trianglePositions[0].y); + rlColor4ub(0, 255, 0, 255); + rlVertex2f(trianglePositions[1].x, trianglePositions[1].y); + rlColor4ub(0, 0, 255, 255); + rlVertex2f(trianglePositions[2].x, trianglePositions[2].y); + rlEnd(); + } + + // Render the vertex handles, reacting to mouse movement/input + for (unsigned int i = 0; i < 3; i++) + { + Vector2 position = trianglePositions[i]; + + float size = 4.0f; + + Vector2 mousePosition = GetMousePosition(); + + // If the cursor is within the handle circle + if (Vector2Distance(mousePosition, position) < size) + { + float fillAlpha = 0.0f; + if (triangleIndex == -1) + { + fillAlpha = 0.5f; + } + + // If handle selected/clicked + if (i == triangleIndex) + { + fillAlpha = 1.0f; + } + + // If clicked, set selected index to handle index + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) + { + triangleIndex = i; + } + + // If visible, draw DARKGRAY circle with varying alpha. + if (fillAlpha > 0.0f) + { + Color fillColor = ColorAlpha(DARKGRAY, fillAlpha); + + DrawCircleV(position, size, fillColor); + } + } + + // Draw handle outline + DrawCircleLinesV(position, size, BLACK); + } + + // Draw controls + DrawText("space for lines\nleft for backface culling\nright for no backface culling\nclick and drag points\nr to reset", 10, 10, 20, DARKGRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/shapes/shapes_rlgl_triangle.png b/examples/shapes/shapes_rlgl_triangle.png new file mode 100644 index 0000000000000000000000000000000000000000..9fec558ff1e337157e488886e18238018cf4907c GIT binary patch literal 22402 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 Date: Sat, 15 Nov 2025 16:47:11 +0100 Subject: [PATCH 081/430] Update shapes_rlgl_triangle.c --- examples/shapes/shapes_rlgl_triangle.c | 172 +++++++++++-------------- 1 file changed, 77 insertions(+), 95 deletions(-) diff --git a/examples/shapes/shapes_rlgl_triangle.c b/examples/shapes/shapes_rlgl_triangle.c index 9174110e4..695e6a00b 100644 --- a/examples/shapes/shapes_rlgl_triangle.c +++ b/examples/shapes/shapes_rlgl_triangle.c @@ -16,6 +16,7 @@ ********************************************************************************************/ #include "raylib.h" + #include "rlgl.h" #include "raymath.h" @@ -33,11 +34,13 @@ int main(void) InitWindow(screenWidth, screenHeight, "raylib [shapes] example - rlgl triangle"); // Starting postions and rendered triangle positions - Vector2 startingPositions[] = {{ 400.0f, 150.0f }, { 300.0f, 300.0f }, { 500.0f, 300.0f }}; - Vector2 trianglePositions[] = { startingPositions[0], startingPositions[1], startingPositions[2] }; + Vector2 startingPositions[3] = {{ 400.0f, 150.0f }, { 300.0f, 300.0f }, { 500.0f, 300.0f }}; + Vector2 trianglePositions[3] = { startingPositions[0], startingPositions[1], startingPositions[2] }; // Currently selected vertex, -1 means none int triangleIndex = -1; + bool linesMode = false; + float handleRadius = 8.0f; SetTargetFPS(60); //-------------------------------------------------------------------------------------- @@ -47,11 +50,7 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - // Reset index on release - if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) - { - triangleIndex = -1; - } + if (IsKeyPressed(KEY_SPACE)) linesMode = !linesMode; // If the user has selected a vertex, offset it by the mouse's delta this frame if (triangleIndex != -1) @@ -62,17 +61,13 @@ int main(void) position->x += mouseDelta.x; position->y += mouseDelta.y; } + + // Reset index on release + if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) triangleIndex = -1; // Enable/disable backface culling (2-sided triangles, slower to render) - if (IsKeyPressed(KEY_LEFT)) - { - rlEnableBackfaceCulling(); - } - - if (IsKeyPressed(KEY_RIGHT)) - { - rlDisableBackfaceCulling(); - } + if (IsKeyPressed(KEY_LEFT)) rlEnableBackfaceCulling(); + if (IsKeyPressed(KEY_RIGHT)) rlDisableBackfaceCulling(); // Reset triangle vertices to starting positions and reset backface culling if (IsKeyPressed(KEY_R)) @@ -89,92 +84,79 @@ int main(void) //---------------------------------------------------------------------------------- BeginDrawing(); - ClearBackground(RAYWHITE); + ClearBackground(RAYWHITE); - if (IsKeyDown(KEY_SPACE)) - { - // Draw triangle with lines - rlBegin(RL_LINES); - // Three lines, six points - // Define color for next vertex - rlColor4ub(255, 0, 0, 255); - // Define vertex - rlVertex2f(trianglePositions[0].x, trianglePositions[0].y); - rlColor4ub(0, 255, 0, 255); - rlVertex2f(trianglePositions[1].x, trianglePositions[1].y); - - rlColor4ub(0, 255, 0, 255); - rlVertex2f(trianglePositions[1].x, trianglePositions[1].y); - rlColor4ub(0, 0, 255, 255); - rlVertex2f(trianglePositions[2].x, trianglePositions[2].y); - - rlColor4ub(0, 0, 255, 255); - rlVertex2f(trianglePositions[2].x, trianglePositions[2].y); - rlColor4ub(255, 0, 0, 255); - rlVertex2f(trianglePositions[0].x, trianglePositions[0].y); - rlEnd(); - } - else - { - // Draw triangle as a triangle - rlBegin(RL_TRIANGLES); - // One triangle, three points - // Define color for next vertex - rlColor4ub(255, 0, 0, 255); - // Define vertex - rlVertex2f(trianglePositions[0].x, trianglePositions[0].y); - rlColor4ub(0, 255, 0, 255); - rlVertex2f(trianglePositions[1].x, trianglePositions[1].y); - rlColor4ub(0, 0, 255, 255); - rlVertex2f(trianglePositions[2].x, trianglePositions[2].y); - rlEnd(); - } - - // Render the vertex handles, reacting to mouse movement/input - for (unsigned int i = 0; i < 3; i++) - { - Vector2 position = trianglePositions[i]; - - float size = 4.0f; - - Vector2 mousePosition = GetMousePosition(); - - // If the cursor is within the handle circle - if (Vector2Distance(mousePosition, position) < size) + if (linesMode) { - float fillAlpha = 0.0f; - if (triangleIndex == -1) - { - fillAlpha = 0.5f; - } + // Draw triangle with lines + rlBegin(RL_LINES); + // Three lines, six points + // Define color for next vertex + rlColor4ub(255, 0, 0, 255); + // Define vertex + rlVertex2f(trianglePositions[0].x, trianglePositions[0].y); + rlColor4ub(0, 255, 0, 255); + rlVertex2f(trianglePositions[1].x, trianglePositions[1].y); - // If handle selected/clicked - if (i == triangleIndex) - { - fillAlpha = 1.0f; - } + rlColor4ub(0, 255, 0, 255); + rlVertex2f(trianglePositions[1].x, trianglePositions[1].y); + rlColor4ub(0, 0, 255, 255); + rlVertex2f(trianglePositions[2].x, trianglePositions[2].y); - // If clicked, set selected index to handle index - if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) - { - triangleIndex = i; - } - - // If visible, draw DARKGRAY circle with varying alpha. - if (fillAlpha > 0.0f) - { - Color fillColor = ColorAlpha(DARKGRAY, fillAlpha); - - DrawCircleV(position, size, fillColor); - } + rlColor4ub(0, 0, 255, 255); + rlVertex2f(trianglePositions[2].x, trianglePositions[2].y); + rlColor4ub(255, 0, 0, 255); + rlVertex2f(trianglePositions[0].x, trianglePositions[0].y); + rlEnd(); + } + else + { + // Draw triangle as a triangle + rlBegin(RL_TRIANGLES); + // One triangle, three points + // Define color for next vertex + rlColor4ub(255, 0, 0, 255); + // Define vertex + rlVertex2f(trianglePositions[0].x, trianglePositions[0].y); + rlColor4ub(0, 255, 0, 255); + rlVertex2f(trianglePositions[1].x, trianglePositions[1].y); + rlColor4ub(0, 0, 255, 255); + rlVertex2f(trianglePositions[2].x, trianglePositions[2].y); + rlEnd(); } - // Draw handle outline - DrawCircleLinesV(position, size, BLACK); - } + // Render the vertex handles, reacting to mouse movement/input + // TODO: Vertex selection can be moved to update logic + for (unsigned int i = 0; i < 3; i++) + { + Vector2 position = trianglePositions[i]; + Vector2 mousePosition = GetMousePosition(); - // Draw controls - DrawText("space for lines\nleft for backface culling\nright for no backface culling\nclick and drag points\nr to reset", 10, 10, 20, DARKGRAY); + // If the cursor is within the handle circle + if (Vector2Distance(mousePosition, position) < handleRadius) + { + float fillAlpha = 0.0f; + if (triangleIndex == -1) fillAlpha = 0.5f; + + // If handle selected/clicked + if (i == triangleIndex) fillAlpha = 1.0f; + + // If clicked, set selected index to handle index + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) triangleIndex = i; + + // If visible, draw DARKGRAY circle with varying alpha. + if (fillAlpha > 0.0f) DrawCircleV(position, handleRadius, ColorAlpha(DARKGRAY, fillAlpha)); + } + + // Draw handle outline + DrawCircleLinesV(position, handleRadius, BLACK); + } + + // Draw controls + DrawText("SPACE: Toggle lines mode", 10, 10, 20, DARKGRAY); + DrawText("LEFT-RIGHT: Toggle backface culling", 10, 40, 20, DARKGRAY); + DrawText("MOUSE: Click and drag vertex points", 10, 70, 20, DARKGRAY); + DrawText("R: Reset triangle to start positions", 10, 100, 20, DARKGRAY); EndDrawing(); //---------------------------------------------------------------------------------- From 5c2747e3a81ee269015f7a0512b7ec53aee05469 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 16 Nov 2025 19:13:01 +0100 Subject: [PATCH 082/430] Update shapes_rlgl_triangle.c --- examples/shapes/shapes_rlgl_triangle.c | 42 ++++++++++++-------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/examples/shapes/shapes_rlgl_triangle.c b/examples/shapes/shapes_rlgl_triangle.c index 695e6a00b..1ce8e7949 100644 --- a/examples/shapes/shapes_rlgl_triangle.c +++ b/examples/shapes/shapes_rlgl_triangle.c @@ -18,7 +18,6 @@ #include "raylib.h" #include "rlgl.h" -#include "raymath.h" //------------------------------------------------------------------------------------ // Program main entry point @@ -51,6 +50,18 @@ int main(void) // Update //---------------------------------------------------------------------------------- if (IsKeyPressed(KEY_SPACE)) linesMode = !linesMode; + + // Check selected vertex + for (unsigned int i = 0; i < 3; i++) + { + // If the mouse is within the handle circle + if (CheckCollisionPointCircle(GetMousePosition(), trianglePositions[i], handleRadius) && + IsMouseButtonDown(MOUSE_BUTTON_LEFT)) + { + triangleIndex = i; + break; + } + } // If the user has selected a vertex, offset it by the mouse's delta this frame if (triangleIndex != -1) @@ -126,30 +137,17 @@ int main(void) } // Render the vertex handles, reacting to mouse movement/input - // TODO: Vertex selection can be moved to update logic for (unsigned int i = 0; i < 3; i++) { - Vector2 position = trianglePositions[i]; - Vector2 mousePosition = GetMousePosition(); - - // If the cursor is within the handle circle - if (Vector2Distance(mousePosition, position) < handleRadius) - { - float fillAlpha = 0.0f; - if (triangleIndex == -1) fillAlpha = 0.5f; - - // If handle selected/clicked - if (i == triangleIndex) fillAlpha = 1.0f; - - // If clicked, set selected index to handle index - if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) triangleIndex = i; - - // If visible, draw DARKGRAY circle with varying alpha. - if (fillAlpha > 0.0f) DrawCircleV(position, handleRadius, ColorAlpha(DARKGRAY, fillAlpha)); - } - + // Draw handle fill focused by mouse + if (CheckCollisionPointCircle(GetMousePosition(), trianglePositions[i], handleRadius)) + DrawCircleV(trianglePositions[i], handleRadius, ColorAlpha(DARKGRAY, 0.5f)); + + // Draw handle fill selected + if (i == triangleIndex) DrawCircleV(trianglePositions[i], handleRadius, DARKGRAY); + // Draw handle outline - DrawCircleLinesV(position, handleRadius, BLACK); + DrawCircleLinesV(trianglePositions[i], handleRadius, BLACK); } // Draw controls From 596d3bcb7e658a82218c84fdb437310a960bc0dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agnis=20Aldi=C5=86=C5=A1=20=22NeZv=C4=93rs?= Date: Sun, 16 Nov 2025 20:40:49 +0200 Subject: [PATCH 083/430] [examples] Added: `textures_screen_buffer` (#5357) * Example textures_screen_buffer * remove resource preload for web makefile * update description * code formatting --- examples/Makefile | 1 + examples/Makefile.Web | 4 + examples/README.md | 5 +- examples/examples_list.txt | 1 + examples/textures/textures_screen_buffer.c | 161 +++++ examples/textures/textures_screen_buffer.png | Bin 0 -> 31532 bytes .../examples/textures_screen_buffer.vcxproj | 569 ++++++++++++++++++ projects/VS2022/raylib.sln | 27 + 8 files changed, 766 insertions(+), 2 deletions(-) create mode 100644 examples/textures/textures_screen_buffer.c create mode 100644 examples/textures/textures_screen_buffer.png create mode 100644 projects/VS2022/examples/textures_screen_buffer.vcxproj diff --git a/examples/Makefile b/examples/Makefile index f36b89bc2..ccd24bf28 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -619,6 +619,7 @@ TEXTURES = \ textures/textures_sprite_button \ textures/textures_sprite_explosion \ textures/textures_srcrec_dstrec \ + textures/textures_screen_buffer \ textures/textures_textured_curve \ textures/textures_tiled_drawing \ textures/textures_to_image diff --git a/examples/Makefile.Web b/examples/Makefile.Web index d2336e7df..01426d7f5 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -607,6 +607,7 @@ TEXTURES = \ textures/textures_sprite_button \ textures/textures_sprite_explosion \ textures/textures_srcrec_dstrec \ + textures/textures_screen_buffer \ textures/textures_textured_curve \ textures/textures_tiled_drawing \ textures/textures_to_image @@ -1058,6 +1059,9 @@ textures/textures_srcrec_dstrec: textures/textures_srcrec_dstrec.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file textures/resources/scarfy.png@resources/scarfy.png +textures/textures_screen_buffer: textures/textures_screen_buffer.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + textures/textures_textured_curve: textures/textures_textured_curve.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file textures/resources/road.png@resources/road.png diff --git a/examples/README.md b/examples/README.md index 82a91e60b..bdf50cb82 100644 --- a/examples/README.md +++ b/examples/README.md @@ -17,7 +17,7 @@ You may find it easier to use than other toolchains, especially when it comes to - `zig build [module]` to compile all examples for a module (e.g. `zig build core`) - `zig build [example]` to compile _and run_ a particular example (e.g. `zig build core_basic_window`) -## EXAMPLES COLLECTION [TOTAL: 194] +## EXAMPLES COLLECTION [TOTAL: 195] ### category: core [47] @@ -114,7 +114,7 @@ Examples using raylib shapes drawing functionality, provided by raylib [shapes]( | [shapes_lines_drawing](shapes/shapes_lines_drawing.c) | shapes_lines_drawing | ⭐☆☆☆ | 5.6-dev | 5.6 | [Robin](https://github.com/RobinsAviary) | | [shapes_math_angle_rotation](shapes/shapes_math_angle_rotation.c) | shapes_math_angle_rotation | ⭐☆☆☆ | 5.6-dev | 5.6 | [Kris](https://github.com/krispy-snacc) | -### category: textures [26] +### category: textures [27] Examples using raylib textures functionality, including image/textures loading/generation and drawing, provided by raylib [textures](../src/rtextures.c) module. @@ -145,6 +145,7 @@ Examples using raylib textures functionality, including image/textures loading/g | [textures_image_kernel](textures/textures_image_kernel.c) | textures_image_kernel | ⭐⭐⭐⭐️ | 1.3 | 1.3 | [Karim Salem](https://github.com/kimo-s) | | [textures_image_channel](textures/textures_image_channel.c) | textures_image_channel | ⭐⭐☆☆ | 5.5 | 5.5 | [Bruno Cabral](https://github.com/brccabral) | | [textures_image_rotate](textures/textures_image_rotate.c) | textures_image_rotate | ⭐⭐☆☆ | 1.0 | 1.0 | [Ramon Santamaria](https://github.com/raysan5) | +| [textures_screen_buffer](textures/textures_screen_buffer.c) | textures_screen_buffer | ⭐⭐☆☆ | 5.5 | 5.5 | [Agnis Aldins](https://github.com/nezvers) | | [textures_textured_curve](textures/textures_textured_curve.c) | textures_textured_curve | ⭐⭐⭐☆ | 4.5 | 4.5 | [Jeffery Myers](https://github.com/JeffM2501) | ### category: text [15] diff --git a/examples/examples_list.txt b/examples/examples_list.txt index 7ce5cf2dd..4e7130d7d 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -113,6 +113,7 @@ textures;textures_gif_player;★★★☆;4.2;4.2;2021;2025;"Ramon Santamaria";@ textures;textures_image_kernel;★★★★;1.3;1.3;2015;2025;"Karim Salem";@kimo-s textures;textures_image_channel;★★☆☆;5.5;5.5;2024;2025;"Bruno Cabral";@brccabral textures;textures_image_rotate;★★☆☆;1.0;1.0;2014;2025;"Ramon Santamaria";@raysan5 +textures;textures_screen_buffer;★★☆☆;5.5;5.5;2014;2025;"Agnis Aldins";@nezvers textures;textures_textured_curve;★★★☆;4.5;4.5;2022;2025;"Jeffery Myers";@JeffM2501 text;text_sprite_fonts;★☆☆☆;1.7;3.7;2017;2025;"Ramon Santamaria";@raysan5 text;text_font_spritefont;★☆☆☆;1.0;1.0;2014;2025;"Ramon Santamaria";@raysan5 diff --git a/examples/textures/textures_screen_buffer.c b/examples/textures/textures_screen_buffer.c new file mode 100644 index 000000000..f5e67f20c --- /dev/null +++ b/examples/textures/textures_screen_buffer.c @@ -0,0 +1,161 @@ +/******************************************************************************************* +* +* raylib [textures] example - screen buffer / update Image as screen buffer and display with texture +* +* Example complexity rating: [★★☆☆] 2/4 +* +* Example originally created with raylib 5.5, last time updated with raylib 5.6 +* +* Example contributed by Agnis Aldiņš (@nezvers) 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) 2025 Agnis Aldiņš (@nezvers) +* +********************************************************************************************/ + +#include "raylib.h" + +#define MAX_COLORS 256 +#define SCREEN_WIDTH 800 +#define SCREEN_HEIGHT 450 +#define SCALE_FACTOR 2 +// buffer size at least for screenImage pixel count +#define INDEX_BUFFER_SIZE ((SCREEN_WIDTH * SCREEN_HEIGHT) / SCALE_FACTOR) +#define FLAME_WIDTH (SCREEN_WIDTH / SCALE_FACTOR) + +static void GeneretePalette(Color *palette); +static void ClearIndexBuffer(unsigned char *buffer, int count); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = SCREEN_WIDTH; + const int screenHeight = SCREEN_HEIGHT; + const int pixelScale = SCALE_FACTOR; + const int imageWidth = screenWidth / pixelScale; + const int imageHeight = screenHeight / pixelScale; + InitWindow(screenWidth, screenHeight, "raylib [] example - "); + + Color palette[MAX_COLORS] = {0}; + unsigned char indexBuffer[INDEX_BUFFER_SIZE] = {0}; + unsigned char flameRootBuffer[FLAME_WIDTH] = {0}; + + Image screenImage = GenImageColor(imageWidth, imageHeight, BLACK); + Texture screenTexture = LoadTextureFromImage(screenImage); + GeneretePalette(palette); + ClearIndexBuffer(indexBuffer, INDEX_BUFFER_SIZE); + ClearIndexBuffer(flameRootBuffer, FLAME_WIDTH); + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Grow flameRoot + for (int x = 2; x < FLAME_WIDTH; ++x) + { + unsigned short flame = flameRootBuffer[x]; + if (flame == 255) continue; + flame += GetRandomValue(0, 2); + if (flame > 255) flame = 255; + flameRootBuffer[x] = flame; + } + + // transfer flameRoot to indexBuffer + for (int x = 0; x < FLAME_WIDTH; ++x) + { + int i = x + (imageHeight - 1) * imageWidth; + indexBuffer[i] = flameRootBuffer[x]; + } + + // Clear top row, because it can't move any higher + for (int x = 0; x < imageWidth; ++x) + { + if (indexBuffer[x] == 0) continue; + indexBuffer[x] = 0; + } + + // Skip top row, it is already cleared + for (int y = 1; y < imageHeight; ++y) + { + for (int x = 0; x < imageWidth; ++x) + { + unsigned i = x + y * imageWidth; + unsigned char colorIndex = indexBuffer[i]; + if (colorIndex == 0) continue; + + // Move pixel a row above + indexBuffer[i] = 0; + int moveX = GetRandomValue(0, 2) - 1; + int newX = x + moveX; + if (newX < 0 || newX >= imageWidth) continue; + + unsigned i_above = i - imageWidth + moveX; + int decay = GetRandomValue(0, 3); + colorIndex -= (decay < colorIndex) ? decay : colorIndex; + indexBuffer[i_above] = colorIndex; + } + } + + // Update screenImage with palette colors + for (int y = 1; y < imageHeight; ++y) + { + for (int x = 0; x < imageWidth; ++x) + { + unsigned i = x + y * imageWidth; + unsigned char colorIndex = indexBuffer[i]; + Color col = palette[colorIndex]; + ImageDrawPixel(&screenImage, x, y, col); + } + } + + UpdateTexture(screenTexture, screenImage.data); + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + const Vector2 origin = (Vector2){0, 0}; + const float rotation = 0.f; + DrawTextureEx(screenTexture, origin, rotation, pixelScale, WHITE); + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + + UnloadTexture(screenTexture); + UnloadImage(screenImage); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} + +static void GeneretePalette(Color *palette) +{ + for (int i = 0; i < MAX_COLORS; ++i) + { + float t = (float)i/(float)(MAX_COLORS - 1); + float hue = t * t; + float saturation = t; + float value = t; + palette[i] = ColorFromHSV(250.f + 150.f * hue, saturation, value); + } +} + +static void ClearIndexBuffer(unsigned char *buffer, int count) +{ + // Use memset to set to ZERO, but for demonstration a plain for loop is used + for (int i = 0; i < count; ++i) + { + buffer[i] = 0; + } +} \ No newline at end of file diff --git a/examples/textures/textures_screen_buffer.png b/examples/textures/textures_screen_buffer.png new file mode 100644 index 0000000000000000000000000000000000000000..b93bba9896a0075208067de806b479e01f1b43d2 GIT binary patch literal 31532 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 + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {6B1A933E-71B8-4C1F-9E79-02D98830E671} + Win32Proj + textures_screen_buffer + 10.0 + textures_screen_buffer + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index 34c696ffe..c3fbb6d2b 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -409,6 +409,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_viewport_scaling", "ex EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_compute_hash", "examples\core_compute_hash.vcxproj", "{6C897101-BE52-4387-8AA2-062123A76BA1}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "textures_screen_buffer", "examples\textures_screen_buffer.vcxproj", "{6B1A933E-71B8-4C1F-9E79-02D98830E671}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug.DLL|ARM64 = Debug.DLL|ARM64 @@ -5079,6 +5081,30 @@ Global {6C897101-BE52-4387-8AA2-062123A76BA1}.Release|x64.Build.0 = Release|x64 {6C897101-BE52-4387-8AA2-062123A76BA1}.Release|x86.ActiveCfg = Release|Win32 {6C897101-BE52-4387-8AA2-062123A76BA1}.Release|x86.Build.0 = Release|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|ARM64.Build.0 = Debug|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x64.ActiveCfg = Debug|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x64.Build.0 = Debug|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x86.ActiveCfg = Debug|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x86.Build.0 = Debug|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|ARM64.ActiveCfg = Release|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|ARM64.Build.0 = Release|ARM64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x64.ActiveCfg = Release|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x64.Build.0 = Release|x64 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x86.ActiveCfg = Release|Win32 + {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5285,6 +5311,7 @@ Global {666346D7-C84B-498D-AE17-53B20C62DB1A} = {278D8859-20B1-428F-8448-064F46E1F021} {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {6C897101-BE52-4387-8AA2-062123A76BA1} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} + {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} From 06589d33505b86bb4b515d4ea6fc3887bfb4347b Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 17 Nov 2025 00:14:08 +0100 Subject: [PATCH 084/430] Update core_2d_camera_mouse_zoom.c --- examples/core/core_2d_camera_mouse_zoom.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/core/core_2d_camera_mouse_zoom.c b/examples/core/core_2d_camera_mouse_zoom.c index 6cb6dcfa2..9006afbe1 100644 --- a/examples/core/core_2d_camera_mouse_zoom.c +++ b/examples/core/core_2d_camera_mouse_zoom.c @@ -23,7 +23,7 @@ //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ -int main () +int main(void) { // Initialization //-------------------------------------------------------------------------------------- @@ -35,9 +35,9 @@ int main () Camera2D camera = { 0 }; camera.zoom = 1.0f; - int zoomMode = 0; // 0-Mouse Wheel, 1-Mouse Move + int zoomMode = 0; // 0-Mouse Wheel, 1-Mouse Move - SetTargetFPS(60); // Set our game to run at 60 frames-per-second + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- // Main game loop @@ -93,6 +93,7 @@ int main () // under the cursor to the screen space point under the cursor at any zoom camera.target = mouseWorldPos; } + if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) { // Zoom increment @@ -110,7 +111,6 @@ int main () ClearBackground(RAYWHITE); BeginMode2D(camera); - // Draw the 3d grid, rotated 90 degrees and centered around 0,0 // just so we have something in the XY plane rlPushMatrix(); @@ -121,7 +121,6 @@ int main () // Draw a reference circle DrawCircle(GetScreenWidth()/2, GetScreenHeight()/2, 50, MAROON); - EndMode2D(); // Draw mouse reference @@ -142,5 +141,6 @@ int main () //-------------------------------------------------------------------------------------- CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- + return 0; } From 6756e9d3d72c962ee6f70ac0832abe681ce8e6e8 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 17 Nov 2025 00:16:47 +0100 Subject: [PATCH 085/430] Update core_input_gestures_testbed.c --- examples/core/core_input_gestures_testbed.c | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/core/core_input_gestures_testbed.c b/examples/core/core_input_gestures_testbed.c index dc47136c2..f1cdfdbc5 100644 --- a/examples/core/core_input_gestures_testbed.c +++ b/examples/core/core_input_gestures_testbed.c @@ -69,7 +69,6 @@ int main(void) float angleLength = 90.0f; float currentAngleDegrees = 0.0f; Vector2 finalVector = { 0.0f, 0.0f }; - char currentAngleStr[7] = ""; Vector2 protractorPosition = { 266.0f, 315.0f }; SetTargetFPS(60); // Set our game to run at 60 frames-per-second From d26b17f320faa8e3bbf94a94adf4c46d3dd2dfee Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 17 Nov 2025 00:27:33 +0100 Subject: [PATCH 086/430] Some comment tweaks --- tools/rexm/rexm.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 1e7865db1..1133fbfdd 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1,6 +1,6 @@ /******************************************************************************************* * -* rexm [raylib examples manager] - A simple command-line tool to manage raylib examples +* rexm [raylib examples manager] - A simple and easy-to-use raylib examples collection manager * * Supported processes: * - create @@ -8,8 +8,9 @@ * - rename * - remove * - build -* - validate -* - update +* - test +* - validate // All examples +* - update // All examples * * Files involved in the processes: * - raylib/examples//_example_name.c @@ -86,7 +87,7 @@ typedef struct { char author[64]; // Example author char authorGitHub[64]; // Example author, GitHub user name - int status; // Example validation status info + int status; // Example validation status flags int resCount; // Example resources counter char **resPaths; // Example resources paths (MAX: 256) } rlExampleInfo; @@ -119,9 +120,9 @@ typedef enum { OP_RENAME = 3, // Rename existing example OP_REMOVE = 4, // Remove existing example OP_VALIDATE = 5, // Validate examples, using [examples_list.txt] as main source by default - OP_UPDATE = 6, // Validate and update required examples (as far as possible) - OP_BUILD = 7, // Build example for desktop and web, copy web output - OP_TEST = 8, // Test example: check output LOG WARNINGS + OP_UPDATE = 6, // Validate and update required examples (as far as possible): ALL + OP_BUILD = 7, // Build example(s) for desktop and web, copy web output - Multiple examples supported + OP_TEST = 8, // Test example(s), checking output log "WARNING" - Multiplee examples supported } rlExampleOperation; static const char *exCategories[REXM_MAX_EXAMPLE_CATEGORIES] = { "core", "shapes", "textures", "text", "models", "shaders", "audio", "others" }; From 9fe3f7ca1491938a744174ab311f61261d16a72a Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 17 Nov 2025 00:29:54 +0100 Subject: [PATCH 087/430] REXM: ADDED: Automated-testing system Elements tested: ``` TESTING_FAIL_INIT = 1 << 0, // Initialization (InitWindow()) -> "INFO: DISPLAY: Device initialized successfully" TESTING_FAIL_CLOSE = 1 << 1, // Closing (CloseWindow()) -> "INFO: Window closed successfully" TESTING_FAIL_ASSETS = 1 << 2, // Assets loading (WARNING: FILE:) -> "WARNING: FILEIO:" TESTING_FAIL_RLGL = 1 << 3, // OpenGL-wrapped initialization -> "INFO: RLGL: Default OpenGL state initialized successfully" TESTING_FAIL_PLATFORM = 1 << 4, // Platform initialization -> "INFO: PLATFORM: DESKTOP (GLFW - Win32): Initialized successfully" TESTING_FAIL_FONT = 1 << 5, // Font default initialization -> "INFO: FONT: Default font loaded successfully (224 glyphs)" TESTING_FAIL_TIMER = 1 << 6, // Timer initialization -> "INFO: TIMER: Target time per frame: 16.667 milliseconds" ``` --- .../rexm/reports/examples_testing_windows.md | 16 +- tools/rexm/rexm.c | 527 +++++++++--------- 2 files changed, 287 insertions(+), 256 deletions(-) diff --git a/tools/rexm/reports/examples_testing_windows.md b/tools/rexm/reports/examples_testing_windows.md index dca13478f..c9ac48a9f 100644 --- a/tools/rexm/reports/examples_testing_windows.md +++ b/tools/rexm/reports/examples_testing_windows.md @@ -4,15 +4,17 @@ ``` Example automated testing elements validated: - - [WARN] : WARNING messages count - [INIT] : Initialization - [CLOSE] : Closing - [ASSETS] : Assets loading - - [OTHER] : Other types of warnings - - [RESULT] : Ending program result (0) - + - [RLGL] : OpenGL-wrapped initialization + - [PLAT] : Platform initialization + - [FONT] : Font default initialization + - [TIMER] : Timer initialization ``` -| **EXAMPLE NAME** | [WARN] | [INIT] | [CLOSE] | [ASSETS] | [OTHER] | [RESULT] | -|:---------------------------------|:------:|:------:|:-------:|:--------:|:-------:|:--------:| -| core_highdpi_testbed | 2 | ✔ | ✔ | ✔ | ✔ | ✔ | +| **EXAMPLE NAME** | [WARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | +|:---------------------------------|:------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| +| core_custom_logging | 0 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | +| core_custom_frame_control | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔ | + diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 1133fbfdd..8b0053c20 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -92,6 +92,12 @@ typedef struct { char **resPaths; // Example resources paths (MAX: 256) } rlExampleInfo; +// Automated testing data +typedef struct { + int warnings; // Warnings counter + int status; // Testing status result flags +} rlExampleTesting; + // Validation status for a single example typedef enum { VALID_OK = 0, // All required files and entries are present @@ -112,6 +118,18 @@ typedef enum { VALID_UNKNOWN_ERROR = 1 << 14 // Unknown failure case (fallback) } rlExampleValidationStatus; +typedef enum { + TESTING_OK = 0, // All automated testing ok + TESTING_FAIL_INIT = 1 << 0, // Initialization (InitWindow()) -> "INFO: DISPLAY: Device initialized successfully" + TESTING_FAIL_CLOSE = 1 << 1, // Closing (CloseWindow()) -> "INFO: Window closed successfully" + TESTING_FAIL_ASSETS = 1 << 2, // Assets loading (WARNING: FILE:) -> "WARNING: FILEIO:" + TESTING_FAIL_RLGL = 1 << 3, // OpenGL-wrapped initialization -> "INFO: RLGL: Default OpenGL state initialized successfully" + TESTING_FAIL_PLATFORM = 1 << 4, // Platform initialization -> "INFO: PLATFORM: DESKTOP (GLFW - Win32): Initialized successfully" + TESTING_FAIL_FONT = 1 << 5, // Font deefault initialization -> "INFO: FONT: Default font loaded successfully (224 glyphs)" + TESTING_FAIL_TIMER = 1 << 6, // Timer initialization -> "INFO: TIMER: Target time per frame: 16.667 milliseconds" + TESTING_FAIL_OTHER = 1 << 7, // Other types of warnings (WARNING:) +} rlExampleTestingStatus; + // Example management operations typedef enum { OP_NONE = 0, // No process to do @@ -146,8 +164,8 @@ static int UpdateRequiredFiles(void); // Load examples collection information // NOTE 1: Load by category: "ALL", "core", "shapes", "textures", "text", "models", "shaders", others" // NOTE 2: Sort examples list on request flag -static rlExampleInfo *LoadExamplesData(const char *fileName, const char *category, bool sort, int *exCount); -static void UnloadExamplesData(rlExampleInfo *exInfo); +static rlExampleInfo *LoadExampleData(const char *filter, bool sort, int *exCount); +static void UnloadExampleData(rlExampleInfo *exInfo); // Load example info from file header static rlExampleInfo *LoadExampleInfo(const char *exFileName); @@ -162,10 +180,10 @@ static int ParseExampleInfoLine(const char *line, rlExampleInfo *entry); static void SortExampleByName(rlExampleInfo *items, int count); // Scan resource paths in example file -static char **ScanExampleResources(const char *filePath, int *resPathCount); +static char **LoadExampleResourcePaths(const char *filePath, int *resPathCount); // Clear resource paths scanned -static void ClearExampleResources(char **resPaths); +static void UnloadExampleResourcePaths(char **resPaths); // Add/remove VS project (.vcxproj) tofrom existing VS solution (.sln) static int AddVSProjectToSolution(const char *slnFile, const char *projFile, const char *category); @@ -223,7 +241,8 @@ int main(int argc, char *argv[]) char exRecategory[32] = { 0 }; // Example re-name category: shapes char exRename[64] = { 0 }; // Example re-name, without extension - char exRebuildRequested[16] = { 0 }; // Example category/full rebuild request + char *exBuildList[256] = { 0 }; // Example build list for: ALL, , single-example + int exBuildListCount = 0; // Example build list file count int opCode = OP_NONE; // Operation code: 0-None(Help), 1-Create, 2-Add, 3-Rename, 4-Remove bool showUsage = false; // Flag to show usage help @@ -383,80 +402,38 @@ int main(int argc, char *argv[]) opCode = OP_UPDATE; } - else if (strcmp(argv[1], "build") == 0) + else if ((strcmp(argv[1], "build") == 0) || (strcmp(argv[1], "test") == 0)) { - // Build example for PLATFORM_DESKTOP and PLATFORM_WEB + // Build/Test example(s) for PLATFORM_DESKTOP and PLATFORM_WEB // NOTE: Build outputs to default directory, usually where the .c file is located, // to avoid issues with copying resources (at least on Desktop) // Web build files (.html, .wasm, .js, .data) are copied to raylib.com/examples repo // Check for valid upcoming argument - if (argc == 2) LOG("WARNING: No example name provided to build\n"); + if (argc == 2) LOG("WARNING: No example name/category provided\n"); else if (argc > 3) LOG("WARNING: Too many arguments provided\n"); else { - // Support building not only individual examples but categories and "ALL" - if ((strcmp(argv[2], "ALL") == 0) || TextInList(argv[2], exCategories, REXM_MAX_EXAMPLE_CATEGORIES)) + // Support building/testing not only individual examples but multiple: ALL/ + rlExampleInfo *exBuildListInfo = LoadExampleData(argv[2], false, &exBuildListCount); + + for (int i = 0; i < exBuildListCount; i++) { - // Category/ALL rebuilt requested - strcpy(exRebuildRequested, argv[2]); - } - else - { - // Verify example exists in collection to be removed - char *exColInfo = LoadFileText(exCollectionFilePath); - if (TextFindIndex(exColInfo, argv[2]) != -1) // Example in the collection - { - strcpy(exName, argv[2]); // Register example name - strncpy(exCategory, exName, TextFindIndex(exName, "_")); - opCode = OP_BUILD; - } - else LOG("WARNING: BUILD: Example requested not available in the collection\n"); - UnloadFileText(exColInfo); - } - } - } - else if (strcmp(argv[1], "test") == 0) - { - // Build and test example for PLATFORM_DESKTOP - // NOTE: Build outputs to default directory, usually where the .c file is located, - // to avoid issues with copying resources (at least on Desktop) - if (argc == 2) LOG("WARNING: No example name provided to test\n"); - else if (argc > 3) LOG("WARNING: Too many arguments provided\n"); - else - { - // Support building not only individual examples but categories and "ALL" - if ((strcmp(argv[2], "ALL") == 0) || TextInList(argv[2], exCategories, REXM_MAX_EXAMPLE_CATEGORIES)) - { - // Category/ALL rebuilt requested - strcpy(exRebuildRequested, argv[2]); - } - else - { - // Verify example exists in collection to be removed - char *exColInfo = LoadFileText(exCollectionFilePath); - if (TextFindIndex(exColInfo, argv[2]) != -1) // Example in the collection - { - strcpy(exName, argv[2]); // Register example name - strncpy(exCategory, exName, TextFindIndex(exName, "_")); - opCode = OP_TEST; - } - else LOG("WARNING: TEST: Example requested not available in the collection\n"); - UnloadFileText(exColInfo); + exBuildList[i] = (char *)RL_CALLOC(256, sizeof(char)); + strcpy(exBuildList[i], exBuildListInfo[i].name); } + + UnloadExampleData(exBuildListInfo); + + if (exBuildListCount == 0) LOG("WARNING: BUILD: Example requested not available in the collection\n"); + else opCode = OP_TEST; } } // Process command line options arguments for (int i = 1; i < argc; i++) { - if ((strcmp(argv[i], "-h") == 0) || (strcmp(argv[i], "--help") == 0)) - { - showUsage = true; - } - else if ((strcmp(argv[i], "-v") == 0) || (strcmp(argv[i], "--verbose") == 0)) - { - verbose = true; - } + if ((strcmp(argv[i], "-h") == 0) || (strcmp(argv[i], "--help") == 0)) showUsage = true; + else if ((strcmp(argv[i], "-v") == 0) || (strcmp(argv[i], "--verbose") == 0)) verbose = true; } } @@ -513,7 +490,7 @@ int main(int argc, char *argv[]) // NOTE: resources path will be relative to example source file directory int resPathCount = 0; LOG("INFO: [%s] Scanning file for resources...\n", GetFileName(inFileName)); - char **resPaths = ScanExampleResources(TextFormat("%s/%s.c", GetDirectoryPath(inFileName), exName), &resPathCount); + char **resPaths = LoadExampleResourcePaths(TextFormat("%s/%s.c", GetDirectoryPath(inFileName), exName), &resPathCount); if (resPathCount > 0) { @@ -571,7 +548,7 @@ int main(int argc, char *argv[]) } } - ClearExampleResources(resPaths); + UnloadExampleResourcePaths(resPaths); // ----------------------------------------------------------------------------------------- // Add example to the collection list, if not already there @@ -727,13 +704,13 @@ int main(int argc, char *argv[]) // Edit: Update example source code metadata int exListCount = 0; - rlExampleInfo *exList = LoadExamplesData(exCollectionFilePath, exCategory, false, &exListCount); + rlExampleInfo *exList = LoadExampleData(exCategory, false, &exListCount); for (int i = 0; i < exListCount; i++) { if (strcmp(exList[i].name, exRename) == 0) UpdateSourceMetadata(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exRename), &exList[i]); } - UnloadExamplesData(exList); + UnloadExampleData(exList); // NOTE: Example resource files do not need to be changed... // unless the example is moved from one caegory to another @@ -919,79 +896,24 @@ int main(int argc, char *argv[]) case OP_BUILD: { LOG("INFO: Command requested: BUILD\n"); - LOG("INFO: Example to be built: %s\n", exName); + LOG("INFO: Example(s) to be built: %i [%s]\n", exBuildListCount, (exBuildListCount == 1)? exBuildList[0] : argv[2]); - if ((exRebuildRequested[0] != '\0') && - (strcmp(exRebuildRequested, "others") != 0) && - (strcmp(exCategory, "others") != 0)) // Skipping "others" category for rebuild: Special needs +#if defined(_WIN32) + // Set required environment variables + //putenv(TextFormat("RAYLIB_DIR=%s\\..", exBasePath)); + _putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); + //putenv("MAKE=mingw32-make"); + //ChangeDirectory(exBasePath); +#endif + for (int i = 0; i < exBuildListCount; i++) { - // TODO: Support building full categories: exRebuildRequested + // Get example name and category + memset(exName, 0, 64); + strcpy(exName, exBuildList[i]); + memset(exCategory, 0, 32); + strncpy(exCategory, exName, TextFindIndex(exName, "_")); - int exRebuildCount = 0; - rlExampleInfo *exRebuildList = LoadExamplesData(exCollectionFilePath, exRebuildRequested, false, &exRebuildCount); - - // Build: raylib.com/examples//_example_name.html - // Build: raylib.com/examples//_example_name.data - // Build: raylib.com/examples//_example_name.wasm - // Build: raylib.com/examples//_example_name.js -#if defined(_WIN32) - // Set required environment variables - //putenv(TextFormat("RAYLIB_DIR=%s\\..", exBasePath)); - _putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); - //putenv("MAKE=mingw32-make"); - //ChangeDirectory(exBasePath); -#endif - for (int i = 0; i < exRebuildCount; i++) - { - // Build example for PLATFORM_DESKTOP -#if defined(_WIN32) - LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: Win32)\n", exName); - system(TextFormat("mingw32-make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exRebuildList[i].category, exRebuildList[i].name)); -#else - LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: POSIX)\n", exName); - system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exRebuildList[i].category, exRebuildList[i].name)); -#endif - - // Build example for PLATFORM_WEB -#if defined(_WIN32) - LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: Win32)\n", exName); - system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exRebuildList[i].category, exRebuildList[i].name)); -#else - LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: POSIX)\n", exName); - system(TextFormat("make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exRebuildList[i].category, exRebuildList[i].name)); -#endif - // Update generated .html metadata - LOG("INFO: [%s] Updating HTML Metadata...\n", TextFormat("%s.html", exRebuildList[i].name)); - UpdateWebMetadata(TextFormat("%s/%s/%s.html", exBasePath, exRebuildList[i].category, exRebuildList[i].name), - TextFormat("%s/%s/%s.c", exBasePath, exRebuildList[i].category, exRebuildList[i].name)); - - // Copy results to web side - LOG("INFO: [%s] Copy example build to raylib.com\n", exRebuildList[i].name); - FileCopy(TextFormat("%s/%s/%s.html", exBasePath, exRebuildList[i].category, exRebuildList[i].name), - TextFormat("%s/%s/%s.html", exWebPath, exRebuildList[i].category, exRebuildList[i].name)); - FileCopy(TextFormat("%s/%s/%s.data", exBasePath, exRebuildList[i].category, exRebuildList[i].name), - TextFormat("%s/%s/%s.data", exWebPath, exRebuildList[i].category, exRebuildList[i].name)); - FileCopy(TextFormat("%s/%s/%s.wasm", exBasePath, exRebuildList[i].category, exRebuildList[i].name), - TextFormat("%s/%s/%s.wasm", exWebPath, exRebuildList[i].category, exRebuildList[i].name)); - FileCopy(TextFormat("%s/%s/%s.js", exBasePath, exRebuildList[i].category, exRebuildList[i].name), - TextFormat("%s/%s/%s.js", exWebPath, exRebuildList[i].category, exRebuildList[i].name)); - } - - UnloadExamplesData(exRebuildList); - } - else // Build a single example - { - // Build: raylib.com/examples//_example_name.html - // Build: raylib.com/examples//_example_name.data - // Build: raylib.com/examples//_example_name.wasm - // Build: raylib.com/examples//_example_name.js -#if defined(_WIN32) - // Set required environment variables - //putenv(TextFormat("RAYLIB_DIR=%s\\..", exBasePath)); - _putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); - //putenv("MAKE=mingw32-make"); - //ChangeDirectory(exBasePath); -#endif + LOG("INFO: [%i/%i] Building example: [%s]\n", i + 1, exBuildListCount, exName); // Build example for PLATFORM_DESKTOP #if defined(_WIN32) @@ -1003,6 +925,10 @@ int main(int argc, char *argv[]) #endif // Build example for PLATFORM_WEB + // Build: raylib.com/examples//_example_name.html + // Build: raylib.com/examples//_example_name.data + // Build: raylib.com/examples//_example_name.wasm + // Build: raylib.com/examples//_example_name.js #if defined(_WIN32) LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: Win32)\n", exName); system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exCategory, exName)); @@ -1025,8 +951,10 @@ int main(int argc, char *argv[]) TextFormat("%s/%s/%s.wasm", exWebPath, exCategory, exName)); FileCopy(TextFormat("%s/%s/%s.js", exBasePath, exCategory, exName), TextFormat("%s/%s/%s.js", exWebPath, exCategory, exName)); + + // Once example processed, free memory from list + RL_FREE(exBuildList[i]); } - //LOG("WARNING: [others] category examples should be build manually, they could have specific build requirements\n"); } break; case OP_VALIDATE: // Validate: report and actions @@ -1129,7 +1057,7 @@ int main(int argc, char *argv[]) // Check all examples in collection [examples_list.txt] -> Source of truth! LOG("INFO: Validating examples in collection...\n"); int exCollectionCount = 0; - rlExampleInfo *exCollection = LoadExamplesData(exCollectionFilePath, "ALL", false, &exCollectionCount); + rlExampleInfo *exCollection = LoadExampleData("ALL", false, &exCollectionCount); // Set status information for all examples, using "status" field in the struct for (int i = 0; i < exCollectionCount; i++) @@ -1174,7 +1102,7 @@ int main(int argc, char *argv[]) // Validate: raylib/examples//resources/.. -> Example resources available? // Scan resources used in example to check for missing resource files // WARNING: Some paths could be for files to save, not files to load, verify it - char **resPaths = ScanExampleResources(TextFormat("%s/%s/%s.c", exBasePath, exInfo->category, exInfo->name), &exInfo->resCount); + char **resPaths = LoadExampleResourcePaths(TextFormat("%s/%s/%s.c", exBasePath, exInfo->category, exInfo->name), &exInfo->resCount); if (exInfo->resCount > 0) { for (int r = 0; r < exInfo->resCount; r++) @@ -1207,7 +1135,7 @@ int main(int argc, char *argv[]) } } } - ClearExampleResources(resPaths); + UnloadExampleResourcePaths(resPaths); // Validate: raylib.com/examples//_example_name.html -> File exists? // Validate: raylib.com/examples//_example_name.data -> File exists? @@ -1337,8 +1265,8 @@ int main(int argc, char *argv[]) _putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exInfo->category, exInfo->name)); #else - LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: POSIX)\n", exInfo->name); - system(TextFormat("make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exInfo->category, exInfo->name)); + LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: POSIX)\n", exInfo->filter); + system(TextFormat("make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exInfo->category, exInfo->filter)); #endif // Update generated .html metadata @@ -1411,7 +1339,7 @@ int main(int argc, char *argv[]) | shapes_colors_palette | ✘ | ✔ | ✘ | ✔ | ✘ | ✔ | ✔ | ✘ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_format_text | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | ✔ | ✘ | ✔ | ✔ | ✔ | ✔ | */ - LOG("INFO: [examples_report.md] Generating examples validation report...\n"); + LOG("INFO: [examples_validation.md] Generating examples validation report...\n"); char *report = (char *)RL_CALLOC(REXM_MAX_BUFFER_SIZE, 1); @@ -1457,13 +1385,13 @@ int main(int argc, char *argv[]) (exCollection[i].status & VALID_MISSING_WEB_METADATA)? "❌" : "✔"); } - SaveFileText(TextFormat("%s/../tools/rexm/%s", exBasePath, "examples_report.md"), report); + SaveFileText(TextFormat("%s/../tools/rexm/reports/%s", exBasePath, "examples_validation.md"), report); RL_FREE(report); //----------------------------------------------------------------------------------------------------- // Generate a report with only the examples missing some elements //----------------------------------------------------------------------------------------------------- - LOG("INFO: [examples_report_issues.md] Generating examples issues report...\n"); + LOG("INFO: [examples_issues.md] Generating examples issues report...\n"); char *reportIssues = (char *)RL_CALLOC(REXM_MAX_BUFFER_SIZE, 1); @@ -1512,100 +1440,193 @@ int main(int argc, char *argv[]) } } - SaveFileText(TextFormat("%s/../tools/rexm/%s", exBasePath, "examples_report_issues.md"), reportIssues); + SaveFileText(TextFormat("%s/../tools/rexm/reports/%s", exBasePath, "examples_issues.md"), reportIssues); RL_FREE(reportIssues); //----------------------------------------------------------------------------------------------------- - UnloadExamplesData(exCollection); + UnloadExampleData(exCollection); //------------------------------------------------------------------------------------------------ } break; case OP_TEST: { LOG("INFO: Command requested: TEST\n"); - LOG("INFO: Example to be built and tested: %s\n", exName); + LOG("INFO: Example(s) to be build and tested: %i [%s]\n", exBuildListCount, (exBuildListCount == 1)? exBuildList[0] : argv[2]); - // Steps to follow - // STEP 1: Load example.c and replace required code to inject basic testing code: frames to run - // OPTION 1: Code injection required multiple changes for testing but it does not require raylib changes! - // OPTION 2: Support testing on raylib side: Args processing and events injection: SUPPORT_AUTOMATD_TESTING_SYSTEM, EVENTS_TESTING_MODE - // STEP 2: Build example (PLATFORM_DESKTOP) - // STEP 3: Run example with arguments: --frames 2 > .out.log - // STEP 4: Load .out.log and check "WARNING:" messages -> Some could maybe be ignored - // STEP 5: Generate report with results + rlExampleTesting *testing = (rlExampleTesting *)RL_CALLOC(exBuildListCount, sizeof(rlExampleTesting)); - // STEP 1: Load example and inject required code - // PROBLEM: As we need to modify the example source code for building, we need to keep a copy or something - // WARNING: If we make a copy and something fails, it could not be restored at the end - // PROBLEM: Trying to build a copy won't work because Makefile is setup to look for specific example on specific path -> No output dir config - // IDEA: Create directory for testing data -> It implies moving files and set working dir... - // SOLUTION: Make a copy of original file -> Modify original -> Build -> Rename to .test.exe - FileCopy(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName), - TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName)); - char *srcText = LoadFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); - - static const char *mainReplaceText = - "#include \n" - "#include \n" - "int main(int argc, char *argv[])\n{\n" - " int requestedTestFrames = 0;\n" - " int testFramesCount = 0;\n" - " if ((argc > 1) && (argc == 3) && (strcmp(argv[1], \"--frames\") != 0)) requestedTestFrames = atoi(argv[2]);\n"; - - char *srcTextUpdated[3] = { 0 }; - srcTextUpdated[0] = TextReplace(srcText, "int main(void)\n{", mainReplaceText); - srcTextUpdated[1] = TextReplace(srcTextUpdated[0], "WindowShouldClose()", "WindowShouldClose() && (testFramesCount < requestedTestFrames)"); - srcTextUpdated[2] = TextReplace(srcTextUpdated[1], "EndDrawing();", "EndDrawing(); testFramesCount++;"); - UnloadFileText(srcText); - - SaveFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName), srcTextUpdated[2]); - for (int i = 0; i < 3; i++) { MemFree(srcTextUpdated[i]); srcTextUpdated[i] = NULL; } - - // STEP 2: Build example for DESKTOP platform -#if defined(_WIN32) - // Set required environment variables - //putenv(TextFormat("RAYLIB_DIR=%s\\..", exBasePath)); - _putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); - //putenv("MAKE=mingw32-make"); - //ChangeDirectory(exBasePath); -#endif - // Build example for PLATFORM_DESKTOP -#if defined(_WIN32) - LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: Win32)\n", exName); - system(TextFormat("mingw32-make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName)); -#else - LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: POSIX)\n", exName); - system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName)); -#endif - // Restore original source code before continue - FileCopy(TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName), - TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); - FileRemove(TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName)); - - // STEP 3: Run example with required arguments - ChangeDirectory(TextFormat("%s/%s", exBasePath, exCategory)); - system(TextFormat("%s --frames 2 > %s.log", exName, exName)); - - // STEP 4: Load and validate log -> WARNINGS - char *exTestLog = LoadFileText(TextFormat("%s/%s/%s.log", exBasePath, exCategory, exName)); - int exTestLogLinesCount = 0; - char **exTestLogLines = LoadTextLines(exTestLog, &exTestLogLinesCount); - UnloadFileText(exTestLog); - - int issueCounter = false; - for (int i = 0; i < exTestLogLinesCount; i++) + for (int i = 0; i < exBuildListCount; i++) { - if (TextFindIndex(exTestLogLines[i], "WARNING") >= 0) + // Get example name and category + memset(exName, 0, 64); + strcpy(exName, exBuildList[i]); + memset(exCategory, 0, 32); + strncpy(exCategory, exName, TextFindIndex(exName, "_")); + + LOG("INFO: [%i/%i] Testing example: [%s]\n", i + 1, exBuildListCount, exName); + + // Steps to follow + // STEP 1: Load example.c and replace required code to inject basic testing code: frames to run + // OPTION 1: Code injection required multiple changes for testing but it does not require raylib changes! + // OPTION 2: Support testing on raylib side: Args processing and events injection: SUPPORT_AUTOMATD_TESTING_SYSTEM, EVENTS_TESTING_MODE + // STEP 2: Build example (PLATFORM_DESKTOP) + // STEP 3: Run example with arguments: --frames 2 > .out.log + // STEP 4: Load .out.log and check "WARNING:" messages -> Some could maybe be ignored + // STEP 5: Generate report with results + + // STEP 1: Load example and inject required code + // PROBLEM: As we need to modify the example source code for building, we need to keep a copy or something + // WARNING: If we make a copy and something fails, it could not be restored at the end + // PROBLEM: Trying to build a copy won't work because Makefile is setup to look for specific example on specific path -> No output dir config + // IDEA: Create directory for testing data -> It implies moving files and set working dir... + // SOLUTION: Make a copy of original file -> Modify original -> Build -> Rename to .test.exe + FileCopy(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName), + TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName)); + char *srcText = LoadFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); + + static const char *mainReplaceText = + "#include \n" + "#include \n" + "int main(int argc, char *argv[])\n{\n" + " int requestedTestFrames = 0;\n" + " int testFramesCount = 0;\n" + " if ((argc > 1) && (argc == 3) && (strcmp(argv[1], \"--frames\") != 0)) requestedTestFrames = atoi(argv[2]);\n"; + + char *srcTextUpdated[3] = { 0 }; + srcTextUpdated[0] = TextReplace(srcText, "int main(void)\n{", mainReplaceText); + srcTextUpdated[1] = TextReplace(srcTextUpdated[0], "WindowShouldClose()", "WindowShouldClose() && (testFramesCount < requestedTestFrames)"); + srcTextUpdated[2] = TextReplace(srcTextUpdated[1], "EndDrawing();", "EndDrawing(); testFramesCount++;"); + UnloadFileText(srcText); + + SaveFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName), srcTextUpdated[2]); + for (int i = 0; i < 3; i++) { MemFree(srcTextUpdated[i]); srcTextUpdated[i] = NULL; } + + // STEP 2: Build example for DESKTOP platform +#if defined(_WIN32) + // Set required environment variables + //putenv(TextFormat("RAYLIB_DIR=%s\\..", exBasePath)); + _putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); + //putenv("MAKE=mingw32-make"); + //ChangeDirectory(exBasePath); +#endif + // Build example for PLATFORM_DESKTOP +#if defined(_WIN32) + LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: Win32)\n", exName); + system(TextFormat("mingw32-make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName)); +#else + LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: POSIX)\n", exName); + system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName)); +#endif + // Restore original source code before continue + FileCopy(TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName), + TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); + FileRemove(TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName)); + + // STEP 3: Run example with required arguments + // NOTE: Not easy to retrieve process return value from system(), it's platform dependant + ChangeDirectory(TextFormat("%s/%s", exBasePath, exCategory)); + system(TextFormat("%s --frames 2 > %s.log", exName, exName)); + + // STEP 4: Load and validate log info + char *exTestLog = LoadFileText(TextFormat("%s/%s/%s.log", exBasePath, exCategory, exName)); + int exTestLogLinesCount = 0; + char **exTestLogLines = LoadTextLines(exTestLog, &exTestLogLinesCount); + + /* + TESTING_FAIL_INIT = 1 << 0, // Initialization (InitWindow()) -> "INFO: DISPLAY: Device initialized successfully" + TESTING_FAIL_CLOSE = 1 << 1, // Closing (CloseWindow()) -> "INFO: Window closed successfully" + TESTING_FAIL_ASSETS = 1 << 2, // Assets loading (WARNING: FILE:) -> "WARNING: FILEIO:" + TESTING_FAIL_RLGL = 1 << 3, // OpenGL-wrapped initialization -> "INFO: RLGL: Default OpenGL state initialized successfully" + TESTING_FAIL_PLATFORM = 1 << 4, // Platform initialization -> "INFO: PLATFORM: DESKTOP (GLFW - Win32): Initialized successfully" + TESTING_FAIL_FONT = 1 << 5, // Font default initialization -> "INFO: FONT: Default font loaded successfully (224 glyphs)" + TESTING_FAIL_TIMER = 1 << 6, // Timer initialization -> "INFO: TIMER: Target time per frame: 16.667 milliseconds" + */ + + if (TextFindIndex(exTestLog, "INFO: DISPLAY: Device initialized successfully") == -1) testing[i].status |= TESTING_FAIL_INIT; + if (TextFindIndex(exTestLog, "INFO: Window closed successfully") == -1) testing[i].status |= TESTING_FAIL_CLOSE; + if (TextFindIndex(exTestLog, "WARNING: FILEIO:") >= 0) testing[i].status |= TESTING_FAIL_ASSETS; + if (TextFindIndex(exTestLog, "INFO: RLGL: Default OpenGL state initialized successfully") == -1) testing[i].status |= TESTING_FAIL_RLGL; + if (TextFindIndex(exTestLog, "INFO: PLATFORM:") == -1) testing[i].status |= TESTING_FAIL_PLATFORM; + if (TextFindIndex(exTestLog, "INFO: FONT: Default font loaded successfully") == -1) testing[i].status |= TESTING_FAIL_FONT; + if (TextFindIndex(exTestLog, "INFO: TIMER: Target time per frame:") == -1) testing[i].status |= TESTING_FAIL_TIMER; + + for (int k = 0, index = 0; k < exTestLogLinesCount; k++) { - LOG("TEST: [%s] %s\n", exName, exTestLogLines[i]); - issueCounter++; + if (TextFindIndex(exTestLogLines[k], "WARNING") >= 0) testing[i].warnings++; + } + + UnloadTextLines(exTestLogLines, exTestLogLinesCount); + UnloadFileText(exTestLog); + } + + // STEP 5: Generate testing report/table with results (.md) + //----------------------------------------------------------------------------------------------------- + /* + Columns: + - [WARN] : WARNING messages count + - [INIT] : Initialization + - [CLOSE] : Closing + - [ASSETS] : Assets loading + - [RLGL] : OpenGL-wrapped initialization + - [PLAT] : Platform initialization + - [FONT] : Font default initialization + - [TIMER] : Timer initialization + + | **EXAMPLE NAME** | [WARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | + |:---------------------------------|:------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| + | core_basic window | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | + */ + LOG("INFO: [examples_testing.md] Generating examples testing report...\n"); + + char *report = (char *)RL_CALLOC(REXM_MAX_BUFFER_SIZE, 1); + + int repIndex = 0; + repIndex += sprintf(report + repIndex, "# EXAMPLES COLLECTION - TESTING REPORT\n\n"); + repIndex += sprintf(report + repIndex, "## Tested Platform: Windows\n\n"); + + repIndex += sprintf(report + repIndex, "```\nExample automated testing elements validated:\n"); + repIndex += sprintf(report + repIndex, " - [WARN] : WARNING messages count\n"); + repIndex += sprintf(report + repIndex, " - [INIT] : Initialization\n"); + repIndex += sprintf(report + repIndex, " - [CLOSE] : Closing\n"); + repIndex += sprintf(report + repIndex, " - [ASSETS] : Assets loading\n"); + repIndex += sprintf(report + repIndex, " - [RLGL] : OpenGL-wrapped initialization\n"); + repIndex += sprintf(report + repIndex, " - [PLAT] : Platform initialization\n"); + repIndex += sprintf(report + repIndex, " - [FONT] : Font default initialization\n"); + repIndex += sprintf(report + repIndex, " - [TIMER] : Timer initialization\n```\n"); + + repIndex += sprintf(report + repIndex, "| **EXAMPLE NAME** | [WARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] |\n"); + repIndex += sprintf(report + repIndex, "|:---------------------------------|:------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:|\n"); + + /* + TESTING_FAIL_INIT = 1 << 0, // Initialization (InitWindow()) -> "INFO: DISPLAY: Device initialized successfully" + TESTING_FAIL_CLOSE = 1 << 1, // Closing (CloseWindow()) -> "INFO: Window closed successfully" + TESTING_FAIL_ASSETS = 1 << 2, // Assets loading (WARNING: FILE:) -> "WARNING: FILEIO:" + TESTING_FAIL_RLGL = 1 << 3, // OpenGL-wrapped initialization -> "INFO: RLGL: Default OpenGL state initialized successfully" + TESTING_FAIL_PLATFORM = 1 << 4, // Platform initialization -> "INFO: PLATFORM: DESKTOP (GLFW - Win32): Initialized successfully" + TESTING_FAIL_FONT = 1 << 5, // Font default initialization -> "INFO: FONT: Default font loaded successfully (224 glyphs)" + TESTING_FAIL_TIMER = 1 << 6, // Timer initialization -> "INFO: TIMER: Target time per frame: 16.667 milliseconds" + */ + for (int i = 0; i < exBuildListCount; i++) + { + if (testing[i].status > 0) + { + repIndex += sprintf(report + repIndex, "| %-32s | %i | %s | %s | %s | %s | %s | %s | %s |\n", + exBuildList[i], testing[i].warnings, + (testing[i].status & TESTING_FAIL_INIT)? "✔" : "❌", + (testing[i].status & TESTING_FAIL_CLOSE)? "✔" : "❌", + (testing[i].status & TESTING_FAIL_ASSETS)? "✔" : "❌", + (testing[i].status & TESTING_FAIL_RLGL)? "✔" : "❌", + (testing[i].status & TESTING_FAIL_PLATFORM)? "✔" : "❌", + (testing[i].status & TESTING_FAIL_FONT)? "✔" : "❌", + (testing[i].status & TESTING_FAIL_TIMER)? "✔" : "❌"); } } - UnloadTextLines(exTestLogLines, exTestLogLinesCount); + repIndex += sprintf(report + repIndex, "\n"); - // STEP 5: Generate auto-test report - //if (issueCounter > 0) + SaveFileText(TextFormat("%s/../tools/rexm/reports/%s", exBasePath, "examples_testing_windows.md"), report); + RL_FREE(report); + //----------------------------------------------------------------------------------------------------- } break; default: // Help @@ -1668,13 +1689,13 @@ static int UpdateRequiredFiles(void) //------------------------------------------------------------------------------------------------ LOG("INFO: Updating all examples metadata...\n"); int exListCount = 0; - rlExampleInfo *exList = LoadExamplesData(exCollectionFilePath, "ALL", true, &exListCount); + rlExampleInfo *exList = LoadExampleData("ALL", true, &exListCount); for (int i = 0; i < exListCount; i++) { rlExampleInfo *info = &exList[i]; UpdateSourceMetadata(TextFormat("%s/%s/%s.c", exBasePath, info->category, info->name), info); } - UnloadExamplesData(exList); + UnloadExampleData(exList); //------------------------------------------------------------------------------------------------ // Edit: raylib/examples/Makefile --> Update from collection @@ -1695,12 +1716,12 @@ static int UpdateRequiredFiles(void) mkIndex += sprintf(mkTextUpdated + mkListStartIndex + mkIndex, TextFormat("%s = \\\n", TextToUpper(exCategories[i]))); int exCollectionCount = 0; - rlExampleInfo *exCollection = LoadExamplesData(exCollectionFilePath, exCategories[i], true, &exCollectionCount); + rlExampleInfo *exCollection = LoadExampleData(exCategories[i], true, &exCollectionCount); for (int x = 0; x < exCollectionCount - 1; x++) mkIndex += sprintf(mkTextUpdated + mkListStartIndex + mkIndex, TextFormat(" %s/%s \\\n", exCollection[x].category, exCollection[x].name)); mkIndex += sprintf(mkTextUpdated + mkListStartIndex + mkIndex, TextFormat(" %s/%s\n\n", exCollection[exCollectionCount - 1].category, exCollection[exCollectionCount - 1].name)); - UnloadExamplesData(exCollection); + UnloadExampleData(exCollection); } // Add the remaining part of the original file @@ -1732,12 +1753,12 @@ static int UpdateRequiredFiles(void) mkwIndex += sprintf(mkwTextUpdated + mkwListStartIndex + mkwIndex, TextFormat("%s = \\\n", TextToUpper(exCategories[i]))); int exCollectionCount = 0; - rlExampleInfo *exCollection = LoadExamplesData(exCollectionFilePath, exCategories[i], true, &exCollectionCount); + rlExampleInfo *exCollection = LoadExampleData(exCategories[i], true, &exCollectionCount); for (int x = 0; x < exCollectionCount - 1; x++) mkwIndex += sprintf(mkwTextUpdated + mkwListStartIndex + mkwIndex, TextFormat(" %s/%s \\\n", exCollection[x].category, exCollection[x].name)); mkwIndex += sprintf(mkwTextUpdated + mkwListStartIndex + mkwIndex, TextFormat(" %s/%s\n\n", exCollection[exCollectionCount - 1].category, exCollection[exCollectionCount - 1].name)); - UnloadExamplesData(exCollection); + UnloadExampleData(exCollection); } // Add examples individual targets, considering every example resources @@ -1758,13 +1779,13 @@ static int UpdateRequiredFiles(void) mkwIndex += sprintf(mkwTextUpdated + mkwListStartIndex + mkwIndex, TextFormat("# Compile %s examples\n", TextToUpper(exCategories[i]))); int exCollectionCount = 0; - rlExampleInfo *exCollection = LoadExamplesData(exCollectionFilePath, exCategories[i], true, &exCollectionCount); + rlExampleInfo *exCollection = LoadExampleData(exCategories[i], true, &exCollectionCount); for (int x = 0; x < exCollectionCount; x++) { // Scan resources used in example to list int resPathCount = 0; - char **resPaths = ScanExampleResources(TextFormat("%s/%s/%s.c", exBasePath, exCollection[x].category, exCollection[x].name), &resPathCount); + char **resPaths = LoadExampleResourcePaths(TextFormat("%s/%s/%s.c", exBasePath, exCollection[x].category, exCollection[x].name), &resPathCount); if (resPathCount > 0) { @@ -1817,10 +1838,10 @@ static int UpdateRequiredFiles(void) mkwIndex += sprintf(mkwTextUpdated + mkwListStartIndex + mkwIndex, " $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM)\n\n"); } - ClearExampleResources(resPaths); + UnloadExampleResourcePaths(resPaths); } - UnloadExamplesData(exCollection); + UnloadExampleData(exCollection); } // Add the remaining part of the original file @@ -1846,8 +1867,8 @@ static int UpdateRequiredFiles(void) memcpy(mdTextUpdated, mdText, mdListStartIndex); int exCollectionFullCount = 0; - rlExampleInfo *exCollectionFull = LoadExamplesData(exCollectionFilePath, "ALL", false, &exCollectionFullCount); - UnloadExamplesData(exCollectionFull); + rlExampleInfo *exCollectionFull = LoadExampleData("ALL", false, &exCollectionFullCount); + UnloadExampleData(exCollectionFull); mdIndex += sprintf(mdTextUpdated + mdListStartIndex + mdIndex, TextFormat("## EXAMPLES COLLECTION [TOTAL: %i]\n", exCollectionFullCount)); @@ -1855,7 +1876,7 @@ static int UpdateRequiredFiles(void) for (int i = 0; i < REXM_MAX_EXAMPLE_CATEGORIES; i++) { int exCollectionCount = 0; - rlExampleInfo *exCollection = LoadExamplesData(exCollectionFilePath, exCategories[i], false, &exCollectionCount); + rlExampleInfo *exCollection = LoadExampleData(exCategories[i], false, &exCollectionCount); // Every category includes some introductory text, as it is quite short, just copying it here if (i == 0) // "core" @@ -1926,7 +1947,7 @@ static int UpdateRequiredFiles(void) starsText, exCollection[x].verCreated, exCollection[x].verUpdated, exCollection[x].author, exCollection[x].authorGitHub)); } - UnloadExamplesData(exCollection); + UnloadExampleData(exCollection); } mdIndex += sprintf(mdTextUpdated + mdListStartIndex + mdIndex, @@ -1972,7 +1993,7 @@ static int UpdateRequiredFiles(void) for (int i = 0; i < REXM_MAX_EXAMPLE_CATEGORIES - 1; i++) { int exCollectionCount = 0; - rlExampleInfo *exCollection = LoadExamplesData(exCollectionFilePath, exCategories[i], false, &exCollectionCount); + rlExampleInfo *exCollection = LoadExampleData(exCategories[i], false, &exCollectionCount); for (int x = 0; x < exCollectionCount; x++) { for (int s = 0; s < 4; s++) @@ -1994,7 +2015,7 @@ static int UpdateRequiredFiles(void) } } - UnloadExamplesData(exCollection); + UnloadExampleData(exCollection); } // Add the remaining part of the original file @@ -2011,8 +2032,8 @@ static int UpdateRequiredFiles(void) return result; } -// Load examples collection information -static rlExampleInfo *LoadExamplesData(const char *fileName, const char *category, bool sort, int *exCount) +// Load examples information from collection data +static rlExampleInfo *LoadExampleData(const char *filter, bool sort, int *exCount) { #define MAX_EXAMPLES_INFO 256 @@ -2020,7 +2041,8 @@ static rlExampleInfo *LoadExamplesData(const char *fileName, const char *categor int exCounter = 0; *exCount = 0; - char *text = LoadFileText(fileName); + // Load main collection list file: "raylib/examples/examples_list.txt" + char *text = LoadFileText(exCollectionFilePath); if (text != NULL) { @@ -2042,18 +2064,25 @@ static rlExampleInfo *LoadExamplesData(const char *fileName, const char *categor int result = ParseExampleInfoLine(lines[i], &info); if (result == 1) // Success on parsing { - if (strcmp(category, "ALL") == 0) + if (strcmp(filter, "ALL") == 0) { // Add all examples to the list memcpy(&exInfo[exCounter], &info, sizeof(rlExampleInfo)); exCounter++; } - else if (strcmp(info.category, category) == 0) + else if (strcmp(info.category, filter) == 0) { // Get only specific category examples memcpy(&exInfo[exCounter], &info, sizeof(rlExampleInfo)); exCounter++; } + else if (strcmp(info.name, filter) == 0) + { + // Get only requested example + memcpy(&exInfo[exCounter], &info, sizeof(rlExampleInfo)); + exCounter++; + break; + } } } } @@ -2070,7 +2099,7 @@ static rlExampleInfo *LoadExamplesData(const char *fileName, const char *categor } // Unload examples collection data -static void UnloadExamplesData(rlExampleInfo *exInfo) +static void UnloadExampleData(rlExampleInfo *exInfo) { RL_FREE(exInfo); } @@ -2160,7 +2189,7 @@ static rlExampleInfo *LoadExampleInfo(const char *exFileName) UnloadFileText(exText); - exInfo->resPaths = ScanExampleResources(exFileName, &exInfo->resCount); + exInfo->resPaths = LoadExampleResourcePaths(exFileName, &exInfo->resCount); } return exInfo; @@ -2169,7 +2198,7 @@ static rlExampleInfo *LoadExampleInfo(const char *exFileName) // Unload example information static void UnloadExampleInfo(rlExampleInfo *exInfo) { - ClearExampleResources(exInfo->resPaths); + UnloadExampleResourcePaths(exInfo->resPaths); RL_FREE(exInfo); } @@ -2245,7 +2274,7 @@ static void SortExampleByName(rlExampleInfo *items, int count) // but new examples could require other file extensions to be added, // maybe it should look for '.xxx")' patterns instead // TODO: WARNING: Some resources could require linked resources: .fnt --> .png, .mtl --> .png, .gltf --> .png, ... -static char **ScanExampleResources(const char *filePath, int *resPathCount) +static char **LoadExampleResourcePaths(const char *filePath, int *resPathCount) { #define REXM_MAX_RESOURCE_PATH_LEN 256 @@ -2327,7 +2356,7 @@ static char **ScanExampleResources(const char *filePath, int *resPathCount) } // Clear resource paths scanned -static void ClearExampleResources(char **resPaths) +static void UnloadExampleResourcePaths(char **resPaths) { for (int i = 0; i < REXM_MAX_RESOURCE_PATHS; i++) RL_FREE(resPaths[i]); From ab463ac89b441deb56ce0a8fcfebd264a1bd69f0 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 17 Nov 2025 09:39:59 +0100 Subject: [PATCH 088/430] Create raylib.vcxproj.filters --- .../rexm/VS2022/raylib/raylib.vcxproj.filters | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 tools/rexm/VS2022/raylib/raylib.vcxproj.filters diff --git a/tools/rexm/VS2022/raylib/raylib.vcxproj.filters b/tools/rexm/VS2022/raylib/raylib.vcxproj.filters new file mode 100644 index 000000000..b5f5536dc --- /dev/null +++ b/tools/rexm/VS2022/raylib/raylib.vcxproj.filters @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + external + + + external + + + external + + + external + + + external + + + external + + + external + + + external + + + external + + + external + + + external + + + + + {7c380c65-acd0-428f-83d9-70ef06f69b6d} + + + \ No newline at end of file From a235cd6a18b05e9bdabe79461541e12a7aa98f06 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 17 Nov 2025 19:20:12 +0100 Subject: [PATCH 089/430] Update raygui.h --- examples/core/raygui.h | 139 ++++++++++++++++++++--------------------- 1 file changed, 67 insertions(+), 72 deletions(-) diff --git a/examples/core/raygui.h b/examples/core/raygui.h index 17ced6ef5..2bd65e478 100644 --- a/examples/core/raygui.h +++ b/examples/core/raygui.h @@ -77,7 +77,7 @@ * * static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)]; * -* guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB +* guiStyle size is by default: 16*(16 + 8) = 384 int = 384*4 bytes = 1536 bytes = 1.5 KB * * Note that the first set of BASE properties (by default guiStyle[0..15]) belong to the generic style * used for all controls, when any of those base values is set, it is automatically populated to all @@ -141,7 +141,7 @@ * Draw text bounds rectangles for debug * * VERSIONS HISTORY: -* 5.0-dev (2025) Current dev version... +* 5.0 (xx-Nov-2025) ADDED: Support up to 32 controls (v500) * ADDED: guiControlExclusiveMode and guiControlExclusiveRec for exclusive modes * ADDED: GuiValueBoxFloat() * ADDED: GuiDropdonwBox() properties: DROPDOWN_ARROW_HIDDEN, DROPDOWN_ROLL_UP @@ -271,7 +271,7 @@ * 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria * * DEPENDENCIES: -* raylib 5.0 - Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing +* raylib 5.6-dev - Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing * * STANDALONE MODE: * By default raygui depends on raylib mostly for the inputs and the drawing functionality but that dependency can be disabled @@ -1010,28 +1010,28 @@ typedef enum { ICON_SLICING = 231, ICON_MANUAL_CONTROL = 232, ICON_COLLISION = 233, - ICON_234 = 234, - ICON_235 = 235, - ICON_236 = 236, - ICON_237 = 237, - ICON_238 = 238, - ICON_239 = 239, - ICON_240 = 240, - ICON_241 = 241, - ICON_242 = 242, - ICON_243 = 243, - ICON_244 = 244, - ICON_245 = 245, - ICON_246 = 246, - ICON_247 = 247, - ICON_248 = 248, - ICON_249 = 249, + ICON_CIRCLE_ADD = 234, + ICON_CIRCLE_ADD_FILL = 235, + ICON_CIRCLE_WARNING = 236, + ICON_CIRCLE_WARNING_FILL = 237, + ICON_BOX_MORE = 238, + ICON_BOX_MORE_FILL = 239, + ICON_BOX_MINUS = 240, + ICON_BOX_MINUS_FILL = 241, + ICON_UNION = 242, + ICON_INTERSECTION = 243, + ICON_DIFFERENCE = 244, + ICON_SPHERE = 245, + ICON_CYLINDER = 246, + ICON_CONE = 247, + ICON_ELLIPSOID = 248, + ICON_CAPSULE = 249, ICON_250 = 250, ICON_251 = 251, ICON_252 = 252, ICON_253 = 253, ICON_254 = 254, - ICON_255 = 255, + ICON_255 = 255 } GuiIconName; #endif @@ -1078,7 +1078,7 @@ typedef enum { // Check if two rectangles are equal, used to validate a slider bounds as an id #ifndef CHECK_BOUNDS_ID - #define CHECK_BOUNDS_ID(src, dst) ((src.x == dst.x) && (src.y == dst.y) && (src.width == dst.width) && (src.height == dst.height)) + #define CHECK_BOUNDS_ID(src, dst) (((int)src.x == (int)dst.x) && ((int)src.y == (int)dst.y) && ((int)src.width == (int)dst.width) && ((int)src.height == (int)dst.height)) #endif #if !defined(RAYGUI_NO_ICONS) && !defined(RAYGUI_CUSTOM_ICONS) @@ -1341,22 +1341,22 @@ static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = 0x7fe00000, 0x402e4020, 0x43ce5e0a, 0x40504078, 0x438e4078, 0x402e5e0a, 0x7fe04020, 0x00000000, // ICON_SLICING 0x00000000, 0x40027ffe, 0x47c24002, 0x55425d42, 0x55725542, 0x50125552, 0x10105016, 0x00001ff0, // ICON_MANUAL_CONTROL 0x7ffe0000, 0x43c24002, 0x48124422, 0x500a500a, 0x500a500a, 0x44224812, 0x400243c2, 0x00007ffe, // ICON_COLLISION - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_234 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_235 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_236 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_237 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_238 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_239 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_240 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_241 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_242 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_243 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_244 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_245 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_246 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_247 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_248 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_249 + 0x03c00000, 0x10080c30, 0x21842184, 0x4ff24182, 0x41824ff2, 0x21842184, 0x0c301008, 0x000003c0, // ICON_CIRCLE_ADD + 0x03c00000, 0x1ff80ff0, 0x3e7c3e7c, 0x700e7e7e, 0x7e7e700e, 0x3e7c3e7c, 0x0ff01ff8, 0x000003c0, // ICON_CIRCLE_ADD_FILL + 0x03c00000, 0x10080c30, 0x21842184, 0x41824182, 0x40024182, 0x21842184, 0x0c301008, 0x000003c0, // ICON_CIRCLE_WARNING + 0x03c00000, 0x1ff80ff0, 0x3e7c3e7c, 0x7e7e7e7e, 0x7ffe7e7e, 0x3e7c3e7c, 0x0ff01ff8, 0x000003c0, // ICON_CIRCLE_WARNING_FILL + 0x00000000, 0x10041ffc, 0x10841004, 0x13e41084, 0x10841084, 0x10041004, 0x00001ffc, 0x00000000, // ICON_BOX_MORE + 0x00000000, 0x1ffc1ffc, 0x1f7c1ffc, 0x1c1c1f7c, 0x1f7c1f7c, 0x1ffc1ffc, 0x00001ffc, 0x00000000, // ICON_BOX_MORE_FILL + 0x00000000, 0x1ffc1ffc, 0x1ffc1ffc, 0x1c1c1ffc, 0x1ffc1ffc, 0x1ffc1ffc, 0x00001ffc, 0x00000000, // ICON_BOX_MINUS + 0x00000000, 0x10041ffc, 0x10041004, 0x13e41004, 0x10041004, 0x10041004, 0x00001ffc, 0x00000000, // ICON_BOX_MINUS_FILL + 0x07fe0000, 0x055606aa, 0x7ff606aa, 0x55766eba, 0x55766eaa, 0x55606ffe, 0x55606aa0, 0x00007fe0, // ICON_UNION + 0x07fe0000, 0x04020402, 0x7fe20402, 0x456246a2, 0x456246a2, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_INTERSECTION + 0x07fe0000, 0x055606aa, 0x7ff606aa, 0x4436442a, 0x4436442a, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_DIFFERENCE + 0x03c00000, 0x10080c30, 0x20042004, 0x60064002, 0x47e2581a, 0x20042004, 0x0c301008, 0x000003c0, // ICON_SPHERE + 0x03e00000, 0x08080410, 0x0c180808, 0x08080be8, 0x08080808, 0x08080808, 0x04100808, 0x000003e0, // ICON_CYLINDER + 0x00800000, 0x01400140, 0x02200220, 0x04100410, 0x08080808, 0x1c1c13e4, 0x08081004, 0x000007f0, // ICON_CONE + 0x00000000, 0x07e00000, 0x20841918, 0x40824082, 0x40824082, 0x19182084, 0x000007e0, 0x00000000, // ICON_ELLIPSOID + 0x00000000, 0x00000000, 0x20041ff8, 0x40024002, 0x40024002, 0x1ff82004, 0x00000000, 0x00000000, // ICON_CAPSULE 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_250 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_251 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_252 @@ -1743,7 +1743,7 @@ int GuiPanel(Rectangle bounds, const char *text) // NOTE: Using GuiToggle() for the TABS int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) { - #define RAYGUI_TABBAR_ITEM_WIDTH 160 + #define RAYGUI_TABBAR_ITEM_WIDTH 148 int result = -1; //GuiState state = guiState; @@ -1776,12 +1776,12 @@ int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) if (i == (*active)) { toggle = true; - GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); + GuiToggle(tabBounds, text[i], &toggle); } else { toggle = false; - GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); + GuiToggle(tabBounds, text[i], &toggle); if (toggle) *active = i; } @@ -2590,7 +2590,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) int pasteLength = 0; int pasteCodepoint; int pasteCodepointSize; - + // Count how many codepoints to copy, stopping at the first unwanted control character while (true) { @@ -2599,7 +2599,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) if (!(multiline && (pasteCodepoint == (int)'\n')) && !(pasteCodepoint >= 32)) break; pasteLength += pasteCodepointSize; } - + if (pasteLength > 0) { // Move forward data from cursor position @@ -2662,7 +2662,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) while (offset < textLength) { if (!isspace(nextCodepoint & 0xff)) break; - + offset += nextCodepointSize; accCodepointSize += nextCodepointSize; nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); @@ -2673,11 +2673,11 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) textLength -= accCodepointSize; } - + else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && autoCursorShouldTrigger))) { // Delete single codepoint from text, after current cursor position - + int nextCodepointSize = 0; GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); @@ -2704,7 +2704,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) offset -= prevCodepointSize; accCodepointSize += prevCodepointSize; } - + // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace) // Not using isalnum() since it only works on ASCII characters bool puctuation = ispunct(prevCodepoint & 0xff); @@ -2723,11 +2723,11 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) textLength -= accCodepointSize; textBoxCursorIndex -= accCodepointSize; } - + else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && autoCursorShouldTrigger))) { // Delete single codepoint from text, before current cursor position - + int prevCodepointSize = 0; GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); @@ -3026,14 +3026,14 @@ int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int // NOTE: Requires static variables: frameCounter int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) { - #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) + //#if !defined(RAYGUI_VALUEBOX_MAX_CHARS) #define RAYGUI_VALUEBOX_MAX_CHARS 32 - #endif + //#endif int result = 0; GuiState state = guiState; - char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; + char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = { 0 }; snprintf(textValue, RAYGUI_VALUEBOX_MAX_CHARS + 1, "%i", *value); Rectangle textBounds = { 0 }; @@ -3051,7 +3051,6 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); - bool valueHasChanged = false; if (editMode) @@ -3070,7 +3069,7 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in keyCount--; valueHasChanged = true; } - else if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS -1) + else if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) { if (keyCount == 0) { @@ -3087,30 +3086,26 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in } } - // Only allow keys in range [48..57] - if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + // Add new digit to text value + if ((keyCount < RAYGUI_VALUEBOX_MAX_CHARS) && (GuiGetTextWidth(textValue) < bounds.width)) { - if (GuiGetTextWidth(textValue) < bounds.width) + int key = GetCharPressed(); + + // Only allow keys in range [48..57] + if ((key >= 48) && (key <= 57)) { - int key = GetCharPressed(); - if ((key >= 48) && (key <= 57)) - { - textValue[keyCount] = (char)key; - keyCount++; - valueHasChanged = true; - } + textValue[keyCount] = (char)key; + keyCount++; + valueHasChanged = true; } } // Delete text - if (keyCount > 0) + if ((keyCount > 0) && IsKeyPressed(KEY_BACKSPACE)) { - if (IsKeyPressed(KEY_BACKSPACE)) - { - keyCount--; - textValue[keyCount] = '\0'; - valueHasChanged = true; - } + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; } if (valueHasChanged) *value = TextToInteger(textValue); @@ -3224,9 +3219,9 @@ int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float textValue[1] = '\0'; keyCount++; } - + for (int i = keyCount; i > -1; i--) textValue[i + 1] = textValue[i]; - + textValue[0] = '-'; keyCount++; valueHasChanged = true; From 063986fdae7b9e6fe62da18304a0cec884c425ce Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 17 Nov 2025 19:20:23 +0100 Subject: [PATCH 090/430] Updated solution --- .../examples/textures_screen_buffer.vcxproj | 2 +- projects/VS2022/raylib.sln | 54 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/projects/VS2022/examples/textures_screen_buffer.vcxproj b/projects/VS2022/examples/textures_screen_buffer.vcxproj index 0453d2182..1697481ce 100644 --- a/projects/VS2022/examples/textures_screen_buffer.vcxproj +++ b/projects/VS2022/examples/textures_screen_buffer.vcxproj @@ -51,7 +51,7 @@ - {6B1A933E-71B8-4C1F-9E79-02D98830E671} + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD} Win32Proj textures_screen_buffer 10.0 diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index c3fbb6d2b..f7bbf1641 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -409,7 +409,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_viewport_scaling", "ex EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_compute_hash", "examples\core_compute_hash.vcxproj", "{6C897101-BE52-4387-8AA2-062123A76BA1}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "textures_screen_buffer", "examples\textures_screen_buffer.vcxproj", "{6B1A933E-71B8-4C1F-9E79-02D98830E671}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "textures_screen_buffer", "examples\textures_screen_buffer.vcxproj", "{4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -5081,30 +5081,30 @@ Global {6C897101-BE52-4387-8AA2-062123A76BA1}.Release|x64.Build.0 = Release|x64 {6C897101-BE52-4387-8AA2-062123A76BA1}.Release|x86.ActiveCfg = Release|Win32 {6C897101-BE52-4387-8AA2-062123A76BA1}.Release|x86.Build.0 = Release|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|ARM64.Build.0 = Debug|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x64.ActiveCfg = Debug|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x64.Build.0 = Debug|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x86.ActiveCfg = Debug|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Debug|x86.Build.0 = Debug|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x64.Build.0 = Release.DLL|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release.DLL|x86.Build.0 = Release.DLL|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|ARM64.ActiveCfg = Release|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|ARM64.Build.0 = Release|ARM64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x64.ActiveCfg = Release|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x64.Build.0 = Release|x64 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x86.ActiveCfg = Release|Win32 - {6B1A933E-71B8-4C1F-9E79-02D98830E671}.Release|x86.Build.0 = Release|Win32 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Debug|ARM64.Build.0 = Debug|ARM64 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Debug|x64.ActiveCfg = Debug|x64 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Debug|x64.Build.0 = Debug|x64 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Debug|x86.ActiveCfg = Debug|Win32 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Debug|x86.Build.0 = Debug|Win32 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Release|ARM64.ActiveCfg = Release|ARM64 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Release|ARM64.Build.0 = Release|ARM64 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Release|x64.ActiveCfg = Release|x64 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Release|x64.Build.0 = Release|x64 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Release|x86.ActiveCfg = Release|Win32 + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5272,7 +5272,7 @@ Global {C54703BF-D68A-480D-BE27-49B62E45D582} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {9CD8BCAD-F212-4BCC-BA98-899743CE3279} = {CC132A4D-D081-4C26-BFB9-AB11984054F8} {0981CA28-E4A5-4DF1-987F-A41D09131EFC} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} - {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} + {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} {6BFF72EA-7362-4A3B-B6E5-9A3655BBBDA3} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {6777EC3C-077C-42FC-B4AD-B799CE55CCE4} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A} {A61DAD9C-271C-4E95-81AA-DB4CD58564D4} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} @@ -5311,7 +5311,7 @@ Global {666346D7-C84B-498D-AE17-53B20C62DB1A} = {278D8859-20B1-428F-8448-064F46E1F021} {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {6C897101-BE52-4387-8AA2-062123A76BA1} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} - {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} + {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} From a59012635172150e616524260b068e5e4ff8ffb4 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 17 Nov 2025 19:20:45 +0100 Subject: [PATCH 091/430] Updated some examples --- examples/core/core_custom_logging.c | 8 ++++---- examples/core/core_directory_files.c | 12 ++++-------- examples/core/core_input_gestures.c | 2 ++ examples/core/core_input_gestures_testbed.c | 4 ++-- examples/core/core_input_mouse.c | 10 ++-------- 5 files changed, 14 insertions(+), 22 deletions(-) diff --git a/examples/core/core_custom_logging.c b/examples/core/core_custom_logging.c index 5ea2a4762..b1e7ee989 100644 --- a/examples/core/core_custom_logging.c +++ b/examples/core/core_custom_logging.c @@ -17,11 +17,11 @@ #include "raylib.h" -#include // Required for: fopen(), fclose(), fputc(), fwrite(), printf(), fprintf(), funopen() -#include // Required for: time_t, tm, time(), localtime(), strftime() +#include // Required for: printf(), vprintf(), fprintf() +#include // Required for: time_t, tm, time(), localtime(), strftime() // Custom logging function -void CustomLog(int msgType, const char *text, va_list args) +void CustomTraceLog(int msgType, const char *text, va_list args) { char timeStr[64] = { 0 }; time_t now = time(NULL); @@ -54,7 +54,7 @@ int main(void) const int screenHeight = 450; // Set custom logger - SetTraceLogCallback(CustomLog); + SetTraceLogCallback(CustomTraceLog); InitWindow(screenWidth, screenHeight, "raylib [core] example - custom logging"); diff --git a/examples/core/core_directory_files.c b/examples/core/core_directory_files.c index 83a4239d0..a98c950f6 100644 --- a/examples/core/core_directory_files.c +++ b/examples/core/core_directory_files.c @@ -20,9 +20,7 @@ #define RAYGUI_IMPLEMENTATION #include "raygui.h" // Required for GUI controls -#include // Required for: strcpy() - -#define MAX_FILEPATH_SIZE 2048 +#define MAX_FILEPATH_SIZE 1024 //------------------------------------------------------------------------------------ // Program main entry point @@ -53,12 +51,10 @@ int main(void) //---------------------------------------------------------------------------------- if (btnBackPressed) { - strcpy(directory, GetPrevDirectoryPath(directory)); + TextCopy(directory, GetPrevDirectoryPath(directory)); UnloadDirectoryFiles(files); files = LoadDirectoryFiles(directory); } - - //---------------------------------------------------------------------------------- // Draw @@ -68,7 +64,7 @@ int main(void) DrawText(directory, 100, 40, 20, DARKGRAY); - btnBackPressed = GuiButton((Rectangle){ 40.0f, 40.0f, 20, 20 }, "<"); + btnBackPressed = GuiButton((Rectangle){ 40.0f, 38.0f, 48, 24 }, "<"); for (int i = 0; i < (int)files.count; i++) { @@ -78,7 +74,7 @@ int main(void) { if (GuiButton((Rectangle){0.0f, 85.0f + 40.0f*(float)i, screenWidth, 40}, "")) { - strcpy(directory, files.paths[i]); + TextCopy(directory, files.paths[i]); UnloadDirectoryFiles(files); files = LoadDirectoryFiles(directory); continue; diff --git a/examples/core/core_input_gestures.c b/examples/core/core_input_gestures.c index fd7250658..168e2a0c3 100644 --- a/examples/core/core_input_gestures.c +++ b/examples/core/core_input_gestures.c @@ -118,4 +118,6 @@ int main(void) //-------------------------------------------------------------------------------------- CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- + + return 0; } \ No newline at end of file diff --git a/examples/core/core_input_gestures_testbed.c b/examples/core/core_input_gestures_testbed.c index f1cdfdbc5..f318ab4a4 100644 --- a/examples/core/core_input_gestures_testbed.c +++ b/examples/core/core_input_gestures_testbed.c @@ -22,9 +22,9 @@ #define GESTURE_LOG_SIZE 20 #define MAX_TOUCH_COUNT 32 -//---------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------ // Module Functions Declaration -//---------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------ static char const *GetGestureName(int gesture); // Get text string for gesture value static Color GetGestureColor(int gesture); // Get color for gesture value diff --git a/examples/core/core_input_mouse.c b/examples/core/core_input_mouse.c index f429018b0..d46106996 100644 --- a/examples/core/core_input_mouse.c +++ b/examples/core/core_input_mouse.c @@ -40,14 +40,8 @@ int main(void) //---------------------------------------------------------------------------------- if (IsKeyPressed(KEY_H)) { - if (IsCursorHidden()) - { - ShowCursor(); - } - else - { - HideCursor(); - } + if (IsCursorHidden()) ShowCursor(); + else HideCursor(); } ballPosition = GetMousePosition(); From cde917c63c7e90fdb3b203aca12e2910d15330d2 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 17 Nov 2025 19:40:23 +0100 Subject: [PATCH 092/430] REXM: ADDED: Build check warnings logs --- tools/rexm/rexm.c | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 8b0053c20..c294d65e0 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -94,8 +94,9 @@ typedef struct { // Automated testing data typedef struct { - int warnings; // Warnings counter - int status; // Testing status result flags + int buildwarns; // Example building warnings count (by GCC compiler) + int warnings; // Example run output log warnings count + int status; // Example run testing status flags (>0 = FAILS) } rlExampleTesting; // Validation status for a single example @@ -1501,6 +1502,8 @@ int main(int argc, char *argv[]) SaveFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName), srcTextUpdated[2]); for (int i = 0; i < 3; i++) { MemFree(srcTextUpdated[i]); srcTextUpdated[i] = NULL; } + MakeDirectory(TextFormat("%s/%s/logs", exBasePath, exCategory)); + // STEP 2: Build example for DESKTOP platform #if defined(_WIN32) // Set required environment variables @@ -1512,7 +1515,8 @@ int main(int argc, char *argv[]) // Build example for PLATFORM_DESKTOP #if defined(_WIN32) LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: Win32)\n", exName); - system(TextFormat("mingw32-make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName)); + system(TextFormat("mingw32-make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B > %s/%s/logs/%s.build.log 2>&1", + exBasePath, exCategory, exName, exBasePath, exCategory, exName)); #else LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: POSIX)\n", exName); system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName)); @@ -1525,10 +1529,24 @@ int main(int argc, char *argv[]) // STEP 3: Run example with required arguments // NOTE: Not easy to retrieve process return value from system(), it's platform dependant ChangeDirectory(TextFormat("%s/%s", exBasePath, exCategory)); - system(TextFormat("%s --frames 2 > %s.log", exName, exName)); + system(TextFormat("%s --frames 2 > logs/%s.log", exName, exName)); // STEP 4: Load and validate log info - char *exTestLog = LoadFileText(TextFormat("%s/%s/%s.log", exBasePath, exCategory, exName)); + //--------------------------------------------------------------------------------------------- + // Load .build.log to check for compilation warnings + char *exTestBuildLog = LoadFileText(TextFormat("%s/%s/logs/%s.build.log", exBasePath, exCategory, exName)); + int exTestBuildLogLinesCount = 0; + char **exTestBuildLogLines = LoadTextLines(exTestBuildLog, &exTestBuildLogLinesCount); + + for (int k = 0, index = 0; k < exTestBuildLogLinesCount; k++) + { + if (TextFindIndex(exTestBuildLogLines[k], "warning:") >= 0) testing[i].buildwarns++; + } + + UnloadTextLines(exTestBuildLogLines, exTestBuildLogLinesCount); + UnloadFileText(exTestBuildLog); + + char *exTestLog = LoadFileText(TextFormat("%s/%s/logs/%s.log", exBasePath, exCategory, exName)); int exTestLogLinesCount = 0; char **exTestLogLines = LoadTextLines(exTestLog, &exTestLogLinesCount); @@ -1557,6 +1575,8 @@ int main(int argc, char *argv[]) UnloadTextLines(exTestLogLines, exTestLogLinesCount); UnloadFileText(exTestLog); + //--------------------------------------------------------------------------------------------- +#endif } // STEP 5: Generate testing report/table with results (.md) From bbba3d080249d4ef86a486a8cd083b6f77329b60 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 17 Nov 2025 19:40:55 +0100 Subject: [PATCH 093/430] REXM: ADDED: Web platform logs automated reports -WIP- --- tools/rexm/rexm.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index c294d65e0..615dbe1c1 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1456,6 +1456,21 @@ int main(int argc, char *argv[]) rlExampleTesting *testing = (rlExampleTesting *)RL_CALLOC(exBuildListCount, sizeof(rlExampleTesting)); +#if defined(_WIN32) + // Set required environment variables + //putenv(TextFormat("RAYLIB_DIR=%s\\..", exBasePath)); + //_putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); + //putenv("MAKE=mingw32-make"); + //ChangeDirectory(exBasePath); + //_putenv("MAKE_PATH=C:\\raylib\\w64devkit\\bin"); + //_putenv("EMSDK_PATH = C:\\raylib\\emsdk"); + //_putenv("PYTHON_PATH=$(EMSDK_PATH)\\python\\3.9.2-nuget_64bit"); + //_putenv("NODE_PATH=$(EMSDK_PATH)\\node\\20.18.0_64bit\\bin"); + //_putenv("PATH=%PATH%;$(MAKE_PATH);$(EMSDK_PATH);$(NODE_PATH);$(PYTHON_PATH)"); + + _putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin;C:\\raylib\\emsdk\\python\\3.9.2-nuget_64bit;C:\\raylib\\emsdk\\node\\20.18.0_64bit\\bin"); +#endif + for (int i = 0; i < exBuildListCount; i++) { // Get example name and category @@ -1474,7 +1489,7 @@ int main(int argc, char *argv[]) // STEP 3: Run example with arguments: --frames 2 > .out.log // STEP 4: Load .out.log and check "WARNING:" messages -> Some could maybe be ignored // STEP 5: Generate report with results - + // STEP 1: Load example and inject required code // PROBLEM: As we need to modify the example source code for building, we need to keep a copy or something // WARNING: If we make a copy and something fails, it could not be restored at the end @@ -1485,6 +1500,71 @@ int main(int argc, char *argv[]) TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName)); char *srcText = LoadFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); +#define BUILD_TESTING_WEB +#if defined(BUILD_TESTING_WEB) + static const char *mainReplaceText = + "#include \n" + "#include \n" + "#include \n" + "#include \n\n" + "static char logText[1024] = {0};\n" + "static int logTextOffset = 0;\n\n" + "void CustomTraceLog(int msgType, const char *text, va_list args)\n{\n" + " switch (msgType)\n {\n" + " case LOG_INFO: logTextOffset += sprintf(logText + logTextOffset, \"INFO: \"); break;\n" + " case LOG_ERROR: logTextOffset += sprintf(logText + logTextOffset, \"ERROR: \"); break;\n" + " case LOG_WARNING: logTextOffset += sprintf(logText + logTextOffset, \"WARNING: \"); break;\n" + " case LOG_DEBUG: logTextOffset += sprintf(logText + logTextOffset, \"DEBUG: \"); break;\n" + " default: break;\n }\n" + " logTextOffset += vsprintf(logText + logTextOffset, text, args);\n" + " logTextOffset += sprintf(logText + logTextOffset, \"\\n\");\n}\n\n" + "int main(int argc, char *argv[])\n{\n" + " SetTraceLogCallback(CustomTraceLog);\n" + " int requestedTestFrames = 0;\n" + " int testFramesCount = 0;\n" + " if ((argc > 1) && (argc == 3) && (strcmp(argv[1], \"--frames\") != 0)) requestedTestFrames = atoi(argv[2]);\n"; + + static const char *returnReplaceText = + " char outputLogFile[256] = { 0 };\n" + " TextCopy(outputLogFile, GetFileNameWithoutExt(argv[0]));\n" + " SaveFileText(outputLogFile, logText);\n" + " emscripten_run_script(TextFormat(\"saveFileFromMEMFSToDisk('%s','%s')\", outputLogFile, GetFileName(outputLogFile)));\n\n" + " return 0"; + + char *srcTextUpdated[4] = { 0 }; + srcTextUpdated[0] = TextReplace(srcText, "int main(void)\n{", mainReplaceText); + srcTextUpdated[1] = TextReplace(srcTextUpdated[0], "WindowShouldClose()", "WindowShouldClose() && (testFramesCount < requestedTestFrames)"); + srcTextUpdated[2] = TextReplace(srcTextUpdated[1], "EndDrawing();", "EndDrawing(); testFramesCount++;"); + srcTextUpdated[3] = TextReplace(srcTextUpdated[2], " return 0", returnReplaceText); + UnloadFileText(srcText); + + //SaveFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName), srcTextUpdated[3]); + for (int i = 0; i < 4; i++) { MemFree(srcTextUpdated[i]); srcTextUpdated[i] = NULL; } + + // Build example for PLATFORM_WEB + // Build: raylib.com/examples//_example_name.html + // Build: raylib.com/examples//_example_name.data + // Build: raylib.com/examples//_example_name.wasm + // Build: raylib.com/examples//_example_name.js +#if defined(_WIN32) + LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: Win32)\n", exName); + system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exCategory, exName)); +#else + LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: POSIX)\n", exName); + system(TextFormat("make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exCategory, exName)); +#endif + // Restore original source code before continue + FileCopy(TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName), + TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); + FileRemove(TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName)); + + // STEP 3: Run example on browser + ChangeDirectory(TextFormat("%s/%s", exBasePath, exCategory)); + system("start python -m http.server 8080"); + system(TextFormat("start explorer \"http:\\localhost:8080/%s.html", exName)); + +#else // BUILD_TESTING_DESKTOP + static const char *mainReplaceText = "#include \n" "#include \n" From be9a24e68cf32a4cad01148b4dbc734158b4145a Mon Sep 17 00:00:00 2001 From: Serhii Zasenko Date: Tue, 18 Nov 2025 17:17:58 +0200 Subject: [PATCH 094/430] Fix controller not available right after win init (#5358) - Fix IsGamepadAvailable() returns false for an available controller immediately after window initialization --- src/platforms/rcore_desktop_glfw.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index bf383031b..746dc8a3c 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1749,7 +1749,12 @@ int InitPlatform(void) { // WARNING: If glfwGetJoystickName() is longer than MAX_GAMEPAD_NAME_LENGTH, // we can get a not-NULL terminated string, so, we only copy up to (MAX_GAMEPAD_NAME_LENGTH - 1) - if (glfwJoystickPresent(i)) strncpy(CORE.Input.Gamepad.name[i], glfwGetJoystickName(i), MAX_GAMEPAD_NAME_LENGTH - 1); + if (glfwJoystickPresent(i)) + { + CORE.Input.Gamepad.ready[i] = true; + CORE.Input.Gamepad.axisCount[i] = GLFW_GAMEPAD_AXIS_LAST + 1; + strncpy(CORE.Input.Gamepad.name[i], glfwGetJoystickName(i), MAX_GAMEPAD_NAME_LENGTH - 1); + } } //---------------------------------------------------------------------------- From b18f547d8fd3da129bea7f8e1a0fe10d35f0784d Mon Sep 17 00:00:00 2001 From: MikiZX1 <161243635+MikiZX1@users.noreply.github.com> Date: Tue, 18 Nov 2025 16:19:07 +0100 Subject: [PATCH 095/430] Update rcore_desktop_sdl.c, fix crash when strncpy tries to copy using NULL pointer (#5359) When SDL_GameControllerNameForIndex returns null, the app crashes. This was addressed earlier in PR#4859 though the fix submitted on PR #4859 was only fixing the crashing and not addressing the root cause. --- src/platforms/rcore_desktop_sdl.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index cf11037cb..e9686912b 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -1723,7 +1723,10 @@ void PollInputEvents(void) CORE.Input.Gamepad.axisState[nextAvailableSlot][GAMEPAD_AXIS_LEFT_TRIGGER] = -1.0f; CORE.Input.Gamepad.axisState[nextAvailableSlot][GAMEPAD_AXIS_RIGHT_TRIGGER] = -1.0f; memset(CORE.Input.Gamepad.name[nextAvailableSlot], 0, MAX_GAMEPAD_NAME_LENGTH); - strncpy(CORE.Input.Gamepad.name[nextAvailableSlot], SDL_GameControllerNameForIndex(nextAvailableSlot), MAX_GAMEPAD_NAME_LENGTH - 1); + if (SDL_GameControllerNameForIndex(nextAvailableSlot)) + strncpy(CORE.Input.Gamepad.name[nextAvailableSlot], SDL_GameControllerNameForIndex(nextAvailableSlot), MAX_GAMEPAD_NAME_LENGTH - 1); + else + strncpy(CORE.Input.Gamepad.name[nextAvailableSlot], "Noname", 6); } else { From f531ee2d8fe5a53dd57ff236dee6d5299b5ffd95 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 16:19:43 +0100 Subject: [PATCH 096/430] Update rexm.c --- tools/rexm/rexm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 615dbe1c1..5569d511b 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1266,8 +1266,8 @@ int main(int argc, char *argv[]) _putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exInfo->category, exInfo->name)); #else - LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: POSIX)\n", exInfo->filter); - system(TextFormat("make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exInfo->category, exInfo->filter)); + LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: POSIX)\n", exInfo->name); + system(TextFormat("make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exInfo->category, exInfo->name)); #endif // Update generated .html metadata @@ -1538,7 +1538,7 @@ int main(int argc, char *argv[]) srcTextUpdated[3] = TextReplace(srcTextUpdated[2], " return 0", returnReplaceText); UnloadFileText(srcText); - //SaveFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName), srcTextUpdated[3]); + SaveFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName), srcTextUpdated[3]); for (int i = 0; i < 4; i++) { MemFree(srcTextUpdated[i]); srcTextUpdated[i] = NULL; } // Build example for PLATFORM_WEB From 95a8977e335809bc87f965db3b76b5476e73fcc1 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 16:28:10 +0100 Subject: [PATCH 097/430] REXM: FIX: Web log redirect and download --- tools/rexm/rexm.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 5569d511b..5b33bfbb3 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1507,9 +1507,10 @@ int main(int argc, char *argv[]) "#include \n" "#include \n" "#include \n\n" - "static char logText[1024] = {0};\n" + "static char logText[4096] = {0};\n" "static int logTextOffset = 0;\n\n" "void CustomTraceLog(int msgType, const char *text, va_list args)\n{\n" + " if (logTextOffset < 3800)\n {\n" " switch (msgType)\n {\n" " case LOG_INFO: logTextOffset += sprintf(logText + logTextOffset, \"INFO: \"); break;\n" " case LOG_ERROR: logTextOffset += sprintf(logText + logTextOffset, \"ERROR: \"); break;\n" @@ -1517,7 +1518,7 @@ int main(int argc, char *argv[]) " case LOG_DEBUG: logTextOffset += sprintf(logText + logTextOffset, \"DEBUG: \"); break;\n" " default: break;\n }\n" " logTextOffset += vsprintf(logText + logTextOffset, text, args);\n" - " logTextOffset += sprintf(logText + logTextOffset, \"\\n\");\n}\n\n" + " logTextOffset += sprintf(logText + logTextOffset, \"\\n\");\n}\n}\n\n" "int main(int argc, char *argv[])\n{\n" " SetTraceLogCallback(CustomTraceLog);\n" " int requestedTestFrames = 0;\n" @@ -1525,17 +1526,17 @@ int main(int argc, char *argv[]) " if ((argc > 1) && (argc == 3) && (strcmp(argv[1], \"--frames\") != 0)) requestedTestFrames = atoi(argv[2]);\n"; static const char *returnReplaceText = - " char outputLogFile[256] = { 0 };\n" - " TextCopy(outputLogFile, GetFileNameWithoutExt(argv[0]));\n" - " SaveFileText(outputLogFile, logText);\n" - " emscripten_run_script(TextFormat(\"saveFileFromMEMFSToDisk('%s','%s')\", outputLogFile, GetFileName(outputLogFile)));\n\n" + " SaveFileText(\"outputLogFileName\", logText);\n" + " emscripten_run_script(\"saveFileFromMEMFSToDisk('outputLogFileName','outputLogFileName')\");\n\n" " return 0"; + char *returnReplaceTextUpdated = TextReplace(returnReplaceText, "outputLogFileName", TextFormat("%s.log", exName)); char *srcTextUpdated[4] = { 0 }; srcTextUpdated[0] = TextReplace(srcText, "int main(void)\n{", mainReplaceText); srcTextUpdated[1] = TextReplace(srcTextUpdated[0], "WindowShouldClose()", "WindowShouldClose() && (testFramesCount < requestedTestFrames)"); srcTextUpdated[2] = TextReplace(srcTextUpdated[1], "EndDrawing();", "EndDrawing(); testFramesCount++;"); - srcTextUpdated[3] = TextReplace(srcTextUpdated[2], " return 0", returnReplaceText); + srcTextUpdated[3] = TextReplace(srcTextUpdated[2], " return 0", returnReplaceTextUpdated); + MemFree(returnReplaceTextUpdated); UnloadFileText(srcText); SaveFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName), srcTextUpdated[3]); From 86e00bde655177f7848eb1ae3101f1e9b505ed72 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 16:30:48 +0100 Subject: [PATCH 098/430] Update rcore_desktop_sdl.c --- src/platforms/rcore_desktop_sdl.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index e9686912b..d6c7fd476 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -1723,15 +1723,11 @@ void PollInputEvents(void) CORE.Input.Gamepad.axisState[nextAvailableSlot][GAMEPAD_AXIS_LEFT_TRIGGER] = -1.0f; CORE.Input.Gamepad.axisState[nextAvailableSlot][GAMEPAD_AXIS_RIGHT_TRIGGER] = -1.0f; memset(CORE.Input.Gamepad.name[nextAvailableSlot], 0, MAX_GAMEPAD_NAME_LENGTH); - if (SDL_GameControllerNameForIndex(nextAvailableSlot)) - strncpy(CORE.Input.Gamepad.name[nextAvailableSlot], SDL_GameControllerNameForIndex(nextAvailableSlot), MAX_GAMEPAD_NAME_LENGTH - 1); - else - strncpy(CORE.Input.Gamepad.name[nextAvailableSlot], "Noname", 6); - } - else - { - TRACELOG(LOG_WARNING, "PLATFORM: Unable to open game controller [ERROR: %s]", SDL_GetError()); + const char *controllerName = SDL_GameControllerNameForIndex(nextAvailableSlot); + if (controllerName != NULL) strncpy(CORE.Input.Gamepad.name[nextAvailableSlot], controllerName, MAX_GAMEPAD_NAME_LENGTH - 1); + else strncpy(CORE.Input.Gamepad.name[nextAvailableSlot], "noname", 6); } + else TRACELOG(LOG_WARNING, "PLATFORM: Unable to open game controller [ERROR: %s]", SDL_GetError()); } } break; case SDL_JOYDEVICEREMOVED: From 4caba49658bf6e662c9fbdc5d8fd056595db7f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20=E2=9D=A4=EF=B8=8F?= Date: Tue, 18 Nov 2025 10:31:43 -0500 Subject: [PATCH 099/430] [examples] Added: `shapes_rlgl_color_wheel` example (#5355) * [examples] Added: `shapes_rlgl_triangle` example * correct name * formatting * Revert "formatting" This reverts commit f1d246a6482afb438d6371c469558729cbabf466. * Revert "correct name" This reverts commit 974985ed495d41323bd879e5ebcc21d80876db37. * Revert "[examples] Added: `shapes_rlgl_triangle` example" This reverts commit d053b9afa0a6f1d2c991f336db22397bb581742d. * [examples] Added: `shapes_rlgl_color_wheel` example * clarify color variable * formatting * formatting * formatting * formatting * reduce redundancy * moved color updating code to update --- examples/shapes/shapes_rlgl_color_wheel.c | 280 ++++++++++++++++++++ examples/shapes/shapes_rlgl_color_wheel.png | Bin 0 -> 68500 bytes 2 files changed, 280 insertions(+) create mode 100644 examples/shapes/shapes_rlgl_color_wheel.c create mode 100644 examples/shapes/shapes_rlgl_color_wheel.png diff --git a/examples/shapes/shapes_rlgl_color_wheel.c b/examples/shapes/shapes_rlgl_color_wheel.c new file mode 100644 index 000000000..323a08956 --- /dev/null +++ b/examples/shapes/shapes_rlgl_color_wheel.c @@ -0,0 +1,280 @@ +/******************************************************************************************* +* +* raylib [shapes] example - rlgl color wheel +* +* Example complexity rating: [★★★☆] 3/4 +* +* Example originally created with raylib 5.6-dev, last time updated with raylib 5.6-dev +* +* Example contributed by Robin (@RobinsAviary) 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) 2025-2025 Robin (@RobinsAviary) +* +********************************************************************************************/ + +#include "raylib.h" +#include "rlgl.h" +#include "raymath.h" +#include +#include + +#define RAYGUI_IMPLEMENTATION +#include "raygui.h" + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + // The minimum/maximum points the circle can have + const unsigned int pointsMin = 3; + const unsigned int pointsMax = 256; + + // The current number of points and the radius of the circle + unsigned int triangleCount = 64; + float pointScale = 150.0f; + + // Slider value, literally maps to value in HSV + float value = 1.0f; + + // The center of the screen + Vector2 center = { (float)screenWidth/2.0f, (float)screenHeight/2.0f }; + // The location of the color wheel + Vector2 circlePosition = center; + + // The currently selected color + Color color = { 255, 255, 255, 255 }; + + // Indicates if the slider is being clicked + bool sliderClicked = false; + + // Indicates if the current color going to be updated, as well as the handle position + bool settingColor = false; + + // How the color wheel will be rendered + unsigned int renderType = RL_TRIANGLES; + + // Enable anti-aliasing + SetConfigFlags(FLAG_MSAA_4X_HINT); + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - rlgl color wheel"); + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + triangleCount += (unsigned int)GetMouseWheelMove(); + triangleCount = (unsigned int)Clamp((float)triangleCount, (float)pointsMin, (float)pointsMax); + + Rectangle sliderRectangle = { 42.0f, 16.0f + 64.0f + 45.0f, 64.0f, 16.0f }; + Vector2 mousePosition = GetMousePosition(); + + // Checks if the user is hovering over the value slider + bool sliderHover = (mousePosition.x >= sliderRectangle.x && mousePosition.y >= sliderRectangle.y && mousePosition.x < sliderRectangle.x + sliderRectangle.width && mousePosition.y < sliderRectangle.y + sliderRectangle.height); + + // Copy color as hex + if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyDown(KEY_C)) + { + if (IsKeyPressed(KEY_C)) + { + SetClipboardText(TextFormat("#%02X%02X%02X", color.r, color.g, color.b)); + } + } + + // Scale up the color wheel, adjusting the handle visually + if (IsKeyDown(KEY_UP)) + { + pointScale *= 1.025f; + + if (pointScale > (float)screenHeight/2.0f) + { + pointScale = (float)screenHeight/2.0f; + } + else + { + circlePosition = Vector2Add(Vector2Multiply(Vector2Subtract(circlePosition, center), (Vector2){ 1.025f, 1.025f }), center); + } + } + + // Scale down the wheel, adjusting the handle visually + if (IsKeyDown(KEY_DOWN)) + { + pointScale *= 0.975f; + + if (pointScale < 32.0f) + { + pointScale = 32.0f; + } + else + { + circlePosition = Vector2Add(Vector2Multiply(Vector2Subtract(circlePosition, center), (Vector2){ 0.975f, 0.975f }), center); + } + + float distance = Vector2Distance(center, circlePosition)/pointScale; + float angle = ((Vector2Angle((Vector2){ 0.0f, -pointScale }, Vector2Subtract(center, circlePosition))/PI + 1.0f) / 2.0f); + + if (distance > 1.0f) + { + circlePosition = Vector2Add((Vector2){ sinf(angle*(PI * 2.0f)) * pointScale, -cosf(angle*(PI*2.0f))*pointScale }, center); + } + } + + // Checks if the user clicked on the color wheel + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && Vector2Distance(GetMousePosition(), center) <= pointScale + 10.0f) + { + settingColor = true; + } + + // Update flag when mouse button is released + if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) settingColor = false; + + // Check if the user clicked/released the slider for the color's value + if (sliderHover && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) sliderClicked = true; + + if (sliderClicked && IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) sliderClicked = false; + + // Update render mode accordingly + if (IsKeyPressed(KEY_SPACE)) renderType = RL_LINES; + + if (IsKeyReleased(KEY_SPACE)) renderType = RL_TRIANGLES; + + // If the slider or the wheel was clicked, update the current color + if (settingColor || sliderClicked) + { + if (settingColor) { + circlePosition = GetMousePosition(); + } + + float distance = Vector2Distance(center, circlePosition) / pointScale; + + float angle = ((Vector2Angle((Vector2){ 0.0f, -pointScale }, Vector2Subtract(center, circlePosition))/PI + 1.0f)/2.0f); + if (settingColor && distance > 1.0f) { + circlePosition = Vector2Add((Vector2){ sinf(angle*(PI*2.0f))*pointScale, -cosf(angle*(PI* 2.0f))*pointScale }, center); + } + + float angle360 = angle*360.0f; + + float valueActual = Clamp(distance, 0.0f, 1.0f); + + color = ColorLerp((Color){ (int)(value*255.0f), (int)(value*255.0f), (int)(value*255.0f), 255 }, ColorFromHSV(angle360, Clamp(distance, 0.0f, 1.0f), 1.0f), valueActual); + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + // Begin rendering color wheel + rlBegin(renderType); + for (unsigned int i = 0; i < triangleCount; i++) + { + float angleOffset = ((PI*2.0f)/(float)triangleCount); + float angle = angleOffset*(float)i; + float angleOffsetCalculated = ((float)i + 1)*angleOffset; + Vector2 scale = (Vector2){ pointScale, pointScale }; + + Vector2 offset = Vector2Multiply((Vector2){ sinf(angle), -cosf(angle) }, scale); + Vector2 offset2 = Vector2Multiply((Vector2){ sinf(angleOffsetCalculated), -cosf(angleOffsetCalculated) }, scale); + + Vector2 position = Vector2Add(center, offset); + Vector2 position2 = Vector2Add(center, offset2); + + float angleNonRadian = (angle/(2.0f*PI))*360.0f; + float angleNonRadianOffset = (angleOffset/(2.0f*PI))*360.0f; + + Color currentColor = ColorFromHSV(angleNonRadian, 1.0f, 1.0f); + Color offsetColor = ColorFromHSV(angleNonRadian + angleNonRadianOffset, 1.0f, 1.0f); + + // Input vertices differently depending on mode + if (renderType == RL_TRIANGLES) + { + // RL_TRIANGLES expects three vertices per triangle + rlColor4ub(currentColor.r, currentColor.g, currentColor.b, currentColor.a); + rlVertex2f(position.x, position.y); + rlColor4f(value, value, value, 1.0f); + rlVertex2f(center.x, center.y); + rlColor4ub(offsetColor.r, offsetColor.g, offsetColor.b, offsetColor.a); + rlVertex2f(position2.x, position2.y); + } + else if (renderType == RL_LINES) + { + // RL_LINES expects two vertices per line + rlColor4ub(currentColor.r, currentColor.g, currentColor.b, currentColor.a); + rlVertex2f(position.x, position.y); + rlColor4ub(WHITE.r, WHITE.g, WHITE.b, WHITE.a); + rlVertex2f(center.x, center.y); + + rlVertex2f(center.x, center.y); + rlColor4ub(offsetColor.r, offsetColor.g, offsetColor.b, offsetColor.a); + rlVertex2f(position2.x, position2.y); + + rlVertex2f(position2.x, position2.y); + rlColor4ub(currentColor.r, currentColor.g, currentColor.b, currentColor.a); + rlVertex2f(position.x, position.y); + } + } + rlEnd(); + + // Make the handle slightly more visible overtop darker colors + Color handleColor = BLACK; + + if (Vector2Distance(center, circlePosition)/pointScale <= 0.5f && value <= 0.5f) + { + handleColor = DARKGRAY; + } + + // Draw the color handle + DrawCircleLinesV(circlePosition, 4.0f, handleColor); + + // Draw the color in a preview, with a darkened outline. + DrawRectangleV((Vector2){ 8.0f, 8.0f }, (Vector2){ 64.0f, 64.0f }, color); + DrawRectangleLinesEx((Rectangle){ 8.0f, 8.0f, 64.0f, 64.0f }, 2.0f, ColorLerp(color, BLACK, 0.5f)); + + // Draw current color as hex and decimal + DrawText(TextFormat("#%02X%02X%02X\n(%d, %d, %d)", color.r, color.g, color.b, color.r, color.g, color.b), 8, 8 + 64 + 8, 20, DARKGRAY); + + // Update the visuals for the copying text + Color copyColor = DARKGRAY; + unsigned int offset = 0; + if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyDown(KEY_C)) + { + copyColor = DARKGREEN; + offset = 4; + } + + // Draw the copying text + DrawText("press ctrl+c to copy!", 8, 425 - offset, 20, copyColor); + + // Display the number of rendered triangles + DrawText(TextFormat("triangle count: %d", triangleCount), 8, 395, 20, DARKGRAY); + + // Slider to change color's value + GuiSliderBar(sliderRectangle, "value: ", "", &value, 0.0f, 1.0f); + + // Draw FPS next to outlined color preview + DrawFPS(64 + 16, 8); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/shapes/shapes_rlgl_color_wheel.png b/examples/shapes/shapes_rlgl_color_wheel.png new file mode 100644 index 0000000000000000000000000000000000000000..43344da5961248739620a6833fc134f27cd73e72 GIT binary patch literal 68500 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 z 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%0Aht9rt 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 VDhKq2XC=VDjT^Su71=N&{|`$k`Op9W literal 0 HcmV?d00001 From 9efe127f5d10a5da66b9ba6ab5cfaad243d98e5a Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 19:50:07 +0100 Subject: [PATCH 100/430] Update shapes_lines_drawing.c --- examples/shapes/shapes_lines_drawing.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/examples/shapes/shapes_lines_drawing.c b/examples/shapes/shapes_lines_drawing.c index aeaae3d45..16f59884d 100644 --- a/examples/shapes/shapes_lines_drawing.c +++ b/examples/shapes/shapes_lines_drawing.c @@ -51,6 +51,8 @@ int main(void) BeginTextureMode(canvas); ClearBackground(backgroundColor); EndTextureMode(); + + SetTargetFPS(60); //-------------------------------------------------------------------------------------- // Main game loop @@ -59,10 +61,7 @@ int main(void) // Update //---------------------------------------------------------------------------------- // Disable the hint text once the user clicks - if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && startText) - { - startText = false; - } + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && startText) startText = false; // Clear the canvas when the user middle-clicks if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) @@ -79,7 +78,7 @@ int main(void) if (leftButtonDown || rightButtonDown) { // The color for the line - Color drawColor; + Color drawColor = WHITE; if (leftButtonDown) { @@ -88,10 +87,7 @@ int main(void) // While the hue is >=360, subtract it to bring it down into the range 0-360 // This is more visually accurate than resetting to zero - while (lineHue >= 360.0f) - { - lineHue -= 360.0f; - } + while (lineHue >= 360.0f) lineHue -= 360.0f; // Create the final color drawColor = ColorFromHSV(lineHue, 1.0f, 1.0f); @@ -104,10 +100,12 @@ int main(void) // Draw the line onto the canvas BeginTextureMode(canvas); + // Circles act as "caps", smoothing corners DrawCircleV(mousePositionPrevious, lineThickness/2.0f, drawColor); DrawCircleV(GetMousePosition(), lineThickness/2.0f, drawColor); DrawLineEx(mousePositionPrevious, GetMousePosition(), lineThickness, drawColor); + EndTextureMode(); } From a4a6812d68702866d852f11cbbce0810a1e0e194 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 19:50:45 +0100 Subject: [PATCH 101/430] REXM: REVIEWED: Testing report generation --- tools/rexm/rexm.c | 67 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 5b33bfbb3..1e67dffe9 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1479,6 +1479,11 @@ int main(int argc, char *argv[]) memset(exCategory, 0, 32); strncpy(exCategory, exName, TextFindIndex(exName, "_")); + // Skip some examples from building + if ((strcmp(exName, "others") == 0) || + (strcmp(exName, "core_custom_logging") == 0) || + (strcmp(exName, "core_window_should_close") == 0)) continue; + LOG("INFO: [%i/%i] Testing example: [%s]\n", i + 1, exBuildListCount, exName); // Steps to follow @@ -1500,7 +1505,7 @@ int main(int argc, char *argv[]) TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName)); char *srcText = LoadFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); -#define BUILD_TESTING_WEB +//#define BUILD_TESTING_WEB #if defined(BUILD_TESTING_WEB) static const char *mainReplaceText = "#include \n" @@ -1549,7 +1554,7 @@ int main(int argc, char *argv[]) // Build: raylib.com/examples//_example_name.js #if defined(_WIN32) LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: Win32)\n", exName); - system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exCategory, exName)); + system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B > %s/%s/logs/%s.build.log 2>&1", exBasePath, exCategory, exName)); #else LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: POSIX)\n", exName); system(TextFormat("make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exCategory, exName)); @@ -1561,9 +1566,11 @@ int main(int argc, char *argv[]) // STEP 3: Run example on browser ChangeDirectory(TextFormat("%s/%s", exBasePath, exCategory)); - system("start python -m http.server 8080"); + system("start python -m http.server 8080"); // TODO: Init localhost just once! system(TextFormat("start explorer \"http:\\localhost:8080/%s.html", exName)); + // NOTE: Example .log is automatically downloaded into system Downloads directory on browser-example exectution + #else // BUILD_TESTING_DESKTOP static const char *mainReplaceText = @@ -1621,6 +1628,7 @@ int main(int argc, char *argv[]) for (int k = 0, index = 0; k < exTestBuildLogLinesCount; k++) { + // Checking compilation warnings generated if (TextFindIndex(exTestBuildLogLines[k], "warning:") >= 0) testing[i].buildwarns++; } @@ -1664,7 +1672,8 @@ int main(int argc, char *argv[]) //----------------------------------------------------------------------------------------------------- /* Columns: - - [WARN] : WARNING messages count + - [CWARN] : Compilation WARNING messages + - [LWARN] : Log WARNING messages count - [INIT] : Initialization - [CLOSE] : Closing - [ASSETS] : Assets loading @@ -1673,9 +1682,9 @@ int main(int argc, char *argv[]) - [FONT] : Font default initialization - [TIMER] : Timer initialization - | **EXAMPLE NAME** | [WARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | - |:---------------------------------|:------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| - | core_basic window | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | + | **EXAMPLE NAME** | [CWARN] | [LWARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | + |:---------------------------------|:-------:|:-------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| + | core_basic window | 0 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | */ LOG("INFO: [examples_testing.md] Generating examples testing report...\n"); @@ -1695,8 +1704,8 @@ int main(int argc, char *argv[]) repIndex += sprintf(report + repIndex, " - [FONT] : Font default initialization\n"); repIndex += sprintf(report + repIndex, " - [TIMER] : Timer initialization\n```\n"); - repIndex += sprintf(report + repIndex, "| **EXAMPLE NAME** | [WARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] |\n"); - repIndex += sprintf(report + repIndex, "|:---------------------------------|:------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:|\n"); + repIndex += sprintf(report + repIndex, "| **EXAMPLE NAME** | [CWARN] | [LWARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] |\n"); + repIndex += sprintf(report + repIndex, "|:---------------------------------|:-------:|:-------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:|\n"); /* TESTING_FAIL_INIT = 1 << 0, // Initialization (InitWindow()) -> "INFO: DISPLAY: Device initialized successfully" @@ -1709,23 +1718,41 @@ int main(int argc, char *argv[]) */ for (int i = 0; i < exBuildListCount; i++) { - if (testing[i].status > 0) + if ((testing[i].buildwarns > 0) || (testing[i].warnings > 0) || (testing[i].status > 0)) { - repIndex += sprintf(report + repIndex, "| %-32s | %i | %s | %s | %s | %s | %s | %s | %s |\n", - exBuildList[i], testing[i].warnings, - (testing[i].status & TESTING_FAIL_INIT)? "✔" : "❌", - (testing[i].status & TESTING_FAIL_CLOSE)? "✔" : "❌", - (testing[i].status & TESTING_FAIL_ASSETS)? "✔" : "❌", - (testing[i].status & TESTING_FAIL_RLGL)? "✔" : "❌", - (testing[i].status & TESTING_FAIL_PLATFORM)? "✔" : "❌", - (testing[i].status & TESTING_FAIL_FONT)? "✔" : "❌", - (testing[i].status & TESTING_FAIL_TIMER)? "✔" : "❌"); + repIndex += sprintf(report + repIndex, "| %-32s | %i | %i | %s | %s | %s | %s | %s | %s | %s |\n", + exBuildList[i], + testing[i].buildwarns, + testing[i].warnings, + (testing[i].status & TESTING_FAIL_INIT)? "❌" : "✔", + (testing[i].status & TESTING_FAIL_CLOSE)? "❌" : "✔", + (testing[i].status & TESTING_FAIL_ASSETS)? "❌" : "✔", + (testing[i].status & TESTING_FAIL_RLGL)? "❌" : "✔", + (testing[i].status & TESTING_FAIL_PLATFORM)? "❌" : "✔", + (testing[i].status & TESTING_FAIL_FONT)? "❌" : "✔", + (testing[i].status & TESTING_FAIL_TIMER)? "❌" : "✔"); } } repIndex += sprintf(report + repIndex, "\n"); - SaveFileText(TextFormat("%s/../tools/rexm/reports/%s", exBasePath, "examples_testing_windows.md"), report); +#if defined(PLATFORM_DRM) + const char *osName = "drm"; +#elif defined(PLATFORM_WEB) + const char *osName = "web"; +#elif defined(PLATFORM_DESKTOP) + #if defined(_WIN32) + const char *osName = "windows"; + #elif defined(__linux__) + const char *osName = "linux"; + #elif defined(__FreeBSD__) + const char *osName = "freebsd"; + #elif defined(__APPLE__) + const char *osName = "macos"; + #endif // Desktop OSs +#endif + SaveFileText(TextFormat("%s/../tools/rexm/reports/examples_testing_%s.md", exBasePath, osName), report); + RL_FREE(report); //----------------------------------------------------------------------------------------------------- From 6993bc7337fd690c8bda29a0a69203be3290ee3c Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 19:51:34 +0100 Subject: [PATCH 102/430] Update examples_testing_windows.md --- tools/rexm/reports/examples_testing_windows.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tools/rexm/reports/examples_testing_windows.md b/tools/rexm/reports/examples_testing_windows.md index c9ac48a9f..07085d39e 100644 --- a/tools/rexm/reports/examples_testing_windows.md +++ b/tools/rexm/reports/examples_testing_windows.md @@ -13,8 +13,16 @@ Example automated testing elements validated: - [FONT] : Font default initialization - [TIMER] : Timer initialization ``` -| **EXAMPLE NAME** | [WARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | -|:---------------------------------|:------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| -| core_custom_logging | 0 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | -| core_custom_frame_control | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✔ | +| **EXAMPLE NAME** | [CWARN] | [LWARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | +|:---------------------------------|:-------:|:-------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| +| shapes_recursive_tree | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_ring_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_circle_sector_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_rounded_rectangle_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_splines_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_digital_clock | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_triangle_strip | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_pie_chart | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_math_sine_cosine | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_lines_drawing | 0 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | From dcc9e961481b5254d7ded360e7702699e07a53e8 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 20:18:17 +0100 Subject: [PATCH 103/430] Update rexm.c --- tools/rexm/rexm.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 1e67dffe9..e25081303 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -415,12 +415,17 @@ int main(int argc, char *argv[]) else { // Support building/testing not only individual examples but multiple: ALL/ - rlExampleInfo *exBuildListInfo = LoadExampleData(argv[2], false, &exBuildListCount); + int exBuildListInfoCount = 0; + rlExampleInfo *exBuildListInfo = LoadExampleData(argv[2], false, &exBuildListInfoCount); - for (int i = 0; i < exBuildListCount; i++) + for (int i = 0; i < exBuildListInfoCount; i++) { - exBuildList[i] = (char *)RL_CALLOC(256, sizeof(char)); - strcpy(exBuildList[i], exBuildListInfo[i].name); + if (!TextIsEqual(exBuildListInfo[i].category, "others")) + { + exBuildList[exBuildListCount] = (char *)RL_CALLOC(256, sizeof(char)); + strcpy(exBuildList[exBuildListCount], exBuildListInfo[i].name); + exBuildListCount++; + } } UnloadExampleData(exBuildListInfo); From e062e3835e0ccdaf8a4a3956f7d36248f026de04 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 20:43:58 +0100 Subject: [PATCH 104/430] REVIEWED: examples: Several minor issues --- examples/models/models_loading_vox.c | 72 ++++++++----------- examples/models/models_point_rendering.c | 22 +++--- examples/shaders/shaders_lightmap_rendering.c | 36 +++++----- examples/textures/textures_tiled_drawing.c | 12 ++-- 4 files changed, 60 insertions(+), 82 deletions(-) diff --git a/examples/models/models_loading_vox.c b/examples/models/models_loading_vox.c index 956c181a0..6675f3fd7 100644 --- a/examples/models/models_loading_vox.c +++ b/examples/models/models_loading_vox.c @@ -67,7 +67,7 @@ int main(void) models[i] = LoadModel(voxFileNames[i]); double t1 = GetTime()*1000.0; - TraceLog(LOG_WARNING, TextFormat("[%s] File loaded in %.3f ms", voxFileNames[i], t1 - t0)); + TraceLog(LOG_INFO, TextFormat("[%s] Model file loaded in %.3f ms", voxFileNames[i], t1 - t0)); // Compute model translation matrix to center model on draw position (0, 0 , 0) BoundingBox bb = GetModelBoundingBox(models[i]); @@ -80,6 +80,8 @@ int main(void) } int currentModel = 0; + Vector3 modelpos = { 0 }; + Vector3 camerarot = { 0 }; // Load voxel shader Shader shader = LoadShader(TextFormat("resources/shaders/glsl%i/voxel_lighting.vs", GLSL_VERSION), @@ -98,11 +100,7 @@ int main(void) // Assign out lighting shader to model for (int i = 0; i < MAX_VOX_FILES; i++) { - Model m = models[i]; - for (int j = 0; j < m.materialCount; j++) - { - m.materials[j].shader = shader; - } + for (int j = 0; j < models[i].materialCount; j++) models[i].materials[j].shader = shader; } // Create lights @@ -112,12 +110,8 @@ int main(void) lights[2] = CreateLight(LIGHT_POINT, (Vector3) { -20, 20, 20 }, Vector3Zero(), GRAY, shader); lights[3] = CreateLight(LIGHT_POINT, (Vector3) { 20, -20, -20 }, Vector3Zero(), GRAY, shader); - SetTargetFPS(60); // Set our game to run at 60 frames-per-second - //-------------------------------------------------------------------------------------- - Vector3 modelpos = { 0 }; - Vector3 camerarot = { 0 }; // Main game loop while (!WindowShouldClose()) // Detect window close button or ESC key @@ -137,15 +131,11 @@ int main(void) } UpdateCameraPro(&camera, - (Vector3) { - (IsKeyDown(KEY_W) || IsKeyDown(KEY_UP))*0.1f - // Move forward-backward - (IsKeyDown(KEY_S) || IsKeyDown(KEY_DOWN))*0.1f, - (IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT))*0.1f - // Move right-left - (IsKeyDown(KEY_A) || IsKeyDown(KEY_LEFT))*0.1f, - 0.0f // Move up-down - }, - camerarot, - GetMouseWheelMove()*-2.0f); // Move to target (zoom) + (Vector3){ (IsKeyDown(KEY_W) || IsKeyDown(KEY_UP))*0.1f - (IsKeyDown(KEY_S) || IsKeyDown(KEY_DOWN))*0.1f, // Move forward-backward + (IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT))*0.1f - (IsKeyDown(KEY_A) || IsKeyDown(KEY_LEFT))*0.1f, // Move right-left + 0.0f }, // Move up-down + camerarot, // Camera rotation + GetMouseWheelMove()*-2.0f); // Move to target (zoom) // Cycle between models on mouse click if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) currentModel = (currentModel + 1) % MAX_VOX_FILES; @@ -156,36 +146,34 @@ int main(void) // Update light values (actually, only enable/disable them) for (int i = 0; i < MAX_LIGHTS; i++) UpdateLightValues(shader, lights[i]); - //---------------------------------------------------------------------------------- + // Draw //---------------------------------------------------------------------------------- BeginDrawing(); - ClearBackground(RAYWHITE); + ClearBackground(RAYWHITE); - // Draw 3D model - BeginMode3D(camera); + // Draw 3D model + BeginMode3D(camera); + DrawModel(models[currentModel], modelpos, 1.0f, WHITE); + DrawGrid(10, 1.0); - DrawModel(models[currentModel], modelpos, 1.0f, WHITE); - DrawGrid(10, 1.0); + // Draw spheres to show where the lights are + for (int i = 0; i < MAX_LIGHTS; i++) + { + if (lights[i].enabled) DrawSphereEx(lights[i].position, 0.2f, 8, 8, lights[i].color); + else DrawSphereWires(lights[i].position, 0.2f, 8, 8, ColorAlpha(lights[i].color, 0.3f)); + } + EndMode3D(); - // Draw spheres to show where the lights are - for (int i = 0; i < MAX_LIGHTS; i++) - { - if (lights[i].enabled) DrawSphereEx(lights[i].position, 0.2f, 8, 8, lights[i].color); - else DrawSphereWires(lights[i].position, 0.2f, 8, 8, ColorAlpha(lights[i].color, 0.3f)); - } - - EndMode3D(); - - // Display info - DrawRectangle(10, 400, 340, 60, Fade(SKYBLUE, 0.5f)); - DrawRectangleLines(10, 400, 340, 60, Fade(DARKBLUE, 0.5f)); - DrawText("MOUSE LEFT BUTTON to CYCLE VOX MODELS", 40, 410, 10, BLUE); - DrawText("MOUSE MIDDLE BUTTON to ZOOM OR ROTATE CAMERA", 40, 420, 10, BLUE); - DrawText("UP-DOWN-LEFT-RIGHT KEYS to MOVE CAMERA", 40, 430, 10, BLUE); - DrawText(TextFormat("File: %s", GetFileName(voxFileNames[currentModel])), 10, 10, 20, GRAY); + // Display info + DrawRectangle(10, 40, 340, 70, Fade(SKYBLUE, 0.5f)); + DrawRectangleLines(10, 40, 340, 70, Fade(DARKBLUE, 0.5f)); + DrawText("- MOUSE LEFT BUTTON: CYCLE VOX MODELS", 20, 50, 10, BLUE); + DrawText("- MOUSE MIDDLE BUTTON: ZOOM OR ROTATE CAMERA", 20, 70, 10, BLUE); + DrawText("- UP-DOWN-LEFT-RIGHT KEYS: MOVE CAMERA", 20, 90, 10, BLUE); + DrawText(TextFormat("Model file: %s", GetFileName(voxFileNames[currentModel])), 10, 10, 20, GRAY); EndDrawing(); //---------------------------------------------------------------------------------- @@ -201,5 +189,3 @@ int main(void) return 0; } - - diff --git a/examples/models/models_point_rendering.c b/examples/models/models_point_rendering.c index ebfad5ac1..71b907225 100644 --- a/examples/models/models_point_rendering.c +++ b/examples/models/models_point_rendering.c @@ -57,7 +57,7 @@ int main(void) Mesh mesh = GenMeshPoints(numPoints); Model model = LoadModelFromMesh(mesh); - //SetTargetFPS(60); + SetTargetFPS(60); //-------------------------------------------------------------------------------------- // Main game loop @@ -92,15 +92,12 @@ int main(void) // Draw //---------------------------------------------------------------------------------- BeginDrawing(); + ClearBackground(BLACK); BeginMode3D(camera); - // The new method only uploads the points once to the GPU - if (useDrawModelPoints) - { - DrawModelPoints(model, position, 1.0f, WHITE); - } + if (useDrawModelPoints) DrawModelPoints(model, position, 1.0f, WHITE); else { // The old method must continually draw the "points" (lines) @@ -124,17 +121,16 @@ int main(void) // Draw a unit sphere for reference DrawSphereWires(position, 1.0f, 10, 10, YELLOW); - EndMode3D(); // Draw UI text - DrawText(TextFormat("Point Count: %d", numPoints), 20, screenHeight - 50, 40, WHITE); - DrawText("Up - increase points", 20, 70, 20, WHITE); - DrawText("Down - decrease points", 20, 100, 20, WHITE); - DrawText("Space - drawing function", 20, 130, 20, WHITE); + DrawText(TextFormat("Point Count: %d", numPoints), 10, screenHeight - 50, 40, WHITE); + DrawText("UP - Increase points", 10, 40, 20, WHITE); + DrawText("DOWN - Decrease points", 10, 70, 20, WHITE); + DrawText("SPACE - Drawing function", 10, 100, 20, WHITE); - if (useDrawModelPoints) DrawText("Using: DrawModelPoints()", 20, 160, 20, GREEN); - else DrawText("Using: DrawPoint3D()", 20, 160, 20, RED); + if (useDrawModelPoints) DrawText("Using: DrawModelPoints()", 10, 130, 20, GREEN); + else DrawText("Using: DrawPoint3D()", 10, 130, 20, RED); DrawFPS(10, 10); diff --git a/examples/shaders/shaders_lightmap_rendering.c b/examples/shaders/shaders_lightmap_rendering.c index e269aada2..51651cfd3 100644 --- a/examples/shaders/shaders_lightmap_rendering.c +++ b/examples/shaders/shaders_lightmap_rendering.c @@ -33,7 +33,7 @@ #define GLSL_VERSION 100 #endif -#define MAP_SIZE 10 +#define MAP_SIZE 16 //------------------------------------------------------------------------------------ // Program main entry point @@ -88,8 +88,6 @@ int main(void) RenderTexture lightmap = LoadRenderTexture(MAP_SIZE, MAP_SIZE); - SetTextureFilter(lightmap.texture, TEXTURE_FILTER_TRILINEAR); - Material material = LoadMaterialDefault(); material.shader = shader; material.maps[MATERIAL_MAP_ALBEDO].texture = texture; @@ -103,29 +101,33 @@ int main(void) DrawTexturePro( light, (Rectangle){ 0, 0, (float)light.width, (float)light.height }, - (Rectangle){ 0, 0, 20, 20 }, - (Vector2){ 10.0, 10.0 }, + (Rectangle){ 0, 0, 2.0f*MAP_SIZE, 2.0f*MAP_SIZE }, + (Vector2){ (float)MAP_SIZE, (float)MAP_SIZE }, 0.0, RED ); DrawTexturePro( light, (Rectangle){ 0, 0, (float)light.width, (float)light.height }, - (Rectangle){ 8, 4, 20, 20 }, - (Vector2){ 10.0, 10.0 }, + (Rectangle){ (float)MAP_SIZE*0.8f, (float)MAP_SIZE/2.0f, 2.0f*MAP_SIZE, 2.0f*MAP_SIZE }, + (Vector2){ (float)MAP_SIZE, (float)MAP_SIZE }, 0.0, BLUE ); DrawTexturePro( light, (Rectangle){ 0, 0, (float)light.width, (float)light.height }, - (Rectangle){ 8, 8, 10, 10 }, - (Vector2){ 5.0, 5.0 }, + (Rectangle){ (float)MAP_SIZE*0.8f, (float)MAP_SIZE*0.8f, (float)MAP_SIZE, (float)MAP_SIZE }, + (Vector2){ (float)MAP_SIZE/2.0f, (float)MAP_SIZE/2.0f }, 0.0, GREEN ); BeginBlendMode(BLEND_ALPHA); EndTextureMode(); + + // NOTE: To enable trilinear filtering we need mipmaps available for texture + GenTextureMipmaps(&lightmap.texture); + SetTextureFilter(lightmap.texture, TEXTURE_FILTER_TRILINEAR); SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- @@ -141,24 +143,20 @@ int main(void) // Draw //---------------------------------------------------------------------------------- BeginDrawing(); + ClearBackground(RAYWHITE); BeginMode3D(camera); DrawMesh(mesh, material, MatrixIdentity()); EndMode3D(); - DrawFPS(10, 10); - - DrawTexturePro( - lightmap.texture, - (Rectangle){ 0, 0, -MAP_SIZE, -MAP_SIZE }, + DrawTexturePro(lightmap.texture, (Rectangle){ 0, 0, -MAP_SIZE, -MAP_SIZE }, (Rectangle){ (float)GetRenderWidth() - MAP_SIZE*8 - 10, 10, (float)MAP_SIZE*8, (float)MAP_SIZE*8 }, - (Vector2){ 0.0, 0.0 }, - 0.0, - WHITE); + (Vector2){ 0.0, 0.0 }, 0.0, WHITE); - DrawText("lightmap", GetRenderWidth() - 66, 16 + MAP_SIZE*8, 10, GRAY); - DrawText("10x10 pixels", GetRenderWidth() - 76, 30 + MAP_SIZE*8, 10, GRAY); + DrawText(TextFormat("LIGHTMAP: %ix%i pixels", MAP_SIZE, MAP_SIZE), GetRenderWidth() - 130, 20 + MAP_SIZE*8, 10, GREEN); + + DrawFPS(10, 10); EndDrawing(); //---------------------------------------------------------------------------------- diff --git a/examples/textures/textures_tiled_drawing.c b/examples/textures/textures_tiled_drawing.c index 10bcad1ad..39a168850 100644 --- a/examples/textures/textures_tiled_drawing.c +++ b/examples/textures/textures_tiled_drawing.c @@ -40,7 +40,7 @@ int main(void) // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required) Texture texPattern = LoadTexture("resources/patterns.png"); - SetTextureFilter(texPattern, TEXTURE_FILTER_TRILINEAR); // Makes the texture smoother when upscaled + SetTextureFilter(texPattern, TEXTURE_FILTER_BILINEAR); // Makes the texture smoother when upscaled // Coordinates for all patterns inside the texture const Rectangle recPattern[] = { @@ -110,19 +110,17 @@ int main(void) } } - // Handle keys - - // Change scale + // Handle keys: change scale if (IsKeyPressed(KEY_UP)) scale += 0.25f; if (IsKeyPressed(KEY_DOWN)) scale -= 0.25f; if (scale > 10.0f) scale = 10.0f; else if ( scale <= 0.0f) scale = 0.25f; - // Change rotation + // Handle keys: change rotation if (IsKeyPressed(KEY_LEFT)) rotation -= 25.0f; if (IsKeyPressed(KEY_RIGHT)) rotation += 25.0f; - // Reset + // Handle keys: reset if (IsKeyPressed(KEY_SPACE)) { rotation = 0.0f; scale = 1.0f; } //---------------------------------------------------------------------------------- @@ -165,7 +163,7 @@ int main(void) // De-Initialization //-------------------------------------------------------------------------------------- - UnloadTexture(texPattern); // Unload texture + UnloadTexture(texPattern); // Unload texture CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- From 6f4f4cc508e8b9b732f0e535973de054b4492d4c Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 20:44:23 +0100 Subject: [PATCH 105/430] Update rexm.c --- tools/rexm/rexm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index e25081303..42f32ab47 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1485,9 +1485,9 @@ int main(int argc, char *argv[]) strncpy(exCategory, exName, TextFindIndex(exName, "_")); // Skip some examples from building - if ((strcmp(exName, "others") == 0) || - (strcmp(exName, "core_custom_logging") == 0) || - (strcmp(exName, "core_window_should_close") == 0)) continue; + if ((strcmp(exName, "core_custom_logging") == 0) || + (strcmp(exName, "core_window_should_close") == 0) || + (strcmp(exName, "core_custom_frame_control") == 0)) continue; LOG("INFO: [%i/%i] Testing example: [%s]\n", i + 1, exBuildListCount, exName); From 5da90172ac5a8bc0b9037c625a3856f4e9a644ef Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 20:49:21 +0100 Subject: [PATCH 106/430] Update examples_testing_windows.md --- tools/rexm/reports/examples_testing_windows.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/rexm/reports/examples_testing_windows.md b/tools/rexm/reports/examples_testing_windows.md index 07085d39e..8eb14257a 100644 --- a/tools/rexm/reports/examples_testing_windows.md +++ b/tools/rexm/reports/examples_testing_windows.md @@ -15,6 +15,10 @@ Example automated testing elements validated: ``` | **EXAMPLE NAME** | [CWARN] | [LWARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | |:---------------------------------|:-------:|:-------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| +| core_input_actions | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_directory_files | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_clipboard_text | 5 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_compute_hash | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_recursive_tree | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_ring_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_circle_sector_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -24,5 +28,9 @@ Example automated testing elements validated: | shapes_triangle_strip | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_pie_chart | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_math_sine_cosine | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_lines_drawing | 0 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | +| text_font_loading | 0 | 10 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| text_font_sdf | 0 | 73 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| text_codepoints_loading | 0 | 1 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| text_inline_styling | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| models_animation_playing | 0 | 1 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | From 95d58ed988747ac1f33b85195db82de2d0304bde Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 20:52:05 +0100 Subject: [PATCH 107/430] Update examples_testing_windows.md --- tools/rexm/reports/examples_testing_windows.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/rexm/reports/examples_testing_windows.md b/tools/rexm/reports/examples_testing_windows.md index 8eb14257a..37c50a486 100644 --- a/tools/rexm/reports/examples_testing_windows.md +++ b/tools/rexm/reports/examples_testing_windows.md @@ -4,7 +4,8 @@ ``` Example automated testing elements validated: - - [WARN] : WARNING messages count + - [CWARN] : Compilation WARNING messages + - [LWARN] : Log WARNING messages count - [INIT] : Initialization - [CLOSE] : Closing - [ASSETS] : Assets loading From 8455f9d088ea46014d295f8f4f6870e26faaf282 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 20:59:33 +0100 Subject: [PATCH 108/430] Update rexm.c --- tools/rexm/rexm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 42f32ab47..20ceadb91 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1700,7 +1700,8 @@ int main(int argc, char *argv[]) repIndex += sprintf(report + repIndex, "## Tested Platform: Windows\n\n"); repIndex += sprintf(report + repIndex, "```\nExample automated testing elements validated:\n"); - repIndex += sprintf(report + repIndex, " - [WARN] : WARNING messages count\n"); + repIndex += sprintf(report + repIndex, " - [CWARN] : Compilation WARNING messages\n"); + repIndex += sprintf(report + repIndex, " - [LWARN] : Log WARNING messages count\n"); repIndex += sprintf(report + repIndex, " - [INIT] : Initialization\n"); repIndex += sprintf(report + repIndex, " - [CLOSE] : Closing\n"); repIndex += sprintf(report + repIndex, " - [ASSETS] : Assets loading\n"); From f3393b8fd85cabff2ce6d0207eeb56751edc9927 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 20:59:38 +0100 Subject: [PATCH 109/430] Update core_clipboard_text.c --- examples/core/core_clipboard_text.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/core/core_clipboard_text.c b/examples/core/core_clipboard_text.c index 895c235da..2f8e5712b 100644 --- a/examples/core/core_clipboard_text.c +++ b/examples/core/core_clipboard_text.c @@ -41,7 +41,7 @@ int main(void) "Copy and paste me!" }; - char *clipboardText = NULL; + const char *clipboardText = NULL; char inputBuffer[256] = "Hello from raylib!"; // Random initial string // UI required variables @@ -144,7 +144,7 @@ int main(void) GuiSetState(STATE_DISABLED); GuiLabel((Rectangle){ 50, 260, 700, 40 }, "Clipboard current text data:"); GuiSetStyle(TEXTBOX, TEXT_READONLY, 1); - GuiTextBox((Rectangle){ 50, 300, 700, 40 }, clipboardText, 256, false); + GuiTextBox((Rectangle){ 50, 300, 700, 40 }, (char *)clipboardText, 256, false); GuiSetStyle(TEXTBOX, TEXT_READONLY, 0); GuiLabel((Rectangle){ 50, 360, 700, 40 }, "Try copying text from other applications and pasting here!"); GuiSetState(STATE_NORMAL); From 46ca641ec50df8392f571746feef4a3e94f6318a Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 20:59:48 +0100 Subject: [PATCH 110/430] Update raygui to avoid warnings --- examples/core/raygui.h | 6 +- examples/shaders/raygui.h | 961 ++++++++++++++++++++++++-------------- examples/shapes/raygui.h | 135 +++--- 3 files changed, 667 insertions(+), 435 deletions(-) diff --git a/examples/core/raygui.h b/examples/core/raygui.h index 2bd65e478..f86247ac4 100644 --- a/examples/core/raygui.h +++ b/examples/core/raygui.h @@ -3026,9 +3026,9 @@ int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int // NOTE: Requires static variables: frameCounter int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) { - //#if !defined(RAYGUI_VALUEBOX_MAX_CHARS) + #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) #define RAYGUI_VALUEBOX_MAX_CHARS 32 - //#endif + #endif int result = 0; GuiState state = guiState; @@ -3087,7 +3087,7 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in } // Add new digit to text value - if ((keyCount < RAYGUI_VALUEBOX_MAX_CHARS) && (GuiGetTextWidth(textValue) < bounds.width)) + if ((keyCount >= 0) && (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) && (GuiGetTextWidth(textValue) < bounds.width)) { int key = GetCharPressed(); diff --git a/examples/shaders/raygui.h b/examples/shaders/raygui.h index a3fc51f0f..f86247ac4 100644 --- a/examples/shaders/raygui.h +++ b/examples/shaders/raygui.h @@ -4,7 +4,7 @@ * * DESCRIPTION: * raygui is a tools-dev-focused immediate-mode-gui library based on raylib but also -* available as a standalone library, as long as input and drawing functions are provided. +* available as a standalone library, as long as input and drawing functions are provided * * FEATURES: * - Immediate-mode gui, minimal retained data @@ -27,7 +27,7 @@ * - WARNING: GuiLoadStyle() and GuiLoadStyle{Custom}() functions, allocate memory for * font atlas recs and glyphs, freeing that memory is (usually) up to the user, * no unload function is explicitly provided... but note that GuiLoadStyleDefault() unloads -* by default any previously loaded font (texture, recs, glyphs). +* by default any previously loaded font (texture, recs, glyphs) * - Global UI alpha (guiAlpha) is applied inside GuiDrawRectangle() and GuiDrawText() functions * * CONTROLS PROVIDED: @@ -65,7 +65,7 @@ * - MessageBox --> Window, Label, Button * - TextInputBox --> Window, Label, TextBox, Button * -* It also provides a set of functions for styling the controls based on its properties (size, color). +* It also provides a set of functions for styling the controls based on its properties (size, color) * * * RAYGUI STYLE (guiStyle): @@ -77,11 +77,11 @@ * * static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)]; * -* guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB +* guiStyle size is by default: 16*(16 + 8) = 384 int = 384*4 bytes = 1536 bytes = 1.5 KB * * Note that the first set of BASE properties (by default guiStyle[0..15]) belong to the generic style * used for all controls, when any of those base values is set, it is automatically populated to all -* controls, so, specific control values overwriting generic style should be set after base values. +* controls, so, specific control values overwriting generic style should be set after base values * * After the first BASE set we have the EXTENDED properties (by default guiStyle[16..23]), those * properties are actually common to all controls and can not be overwritten individually (like BASE ones) @@ -100,7 +100,7 @@ * Every icon is codified in binary form, using 1 bit per pixel, so, every 16x16 icon * requires 8 integers (16*16/32) to be stored in memory. * -* When the icon is draw, actually one quad per pixel is drawn if the bit for that pixel is set. +* When the icon is draw, actually one quad per pixel is drawn if the bit for that pixel is set * * The global icons array size is fixed and depends on the number of icons and size: * @@ -112,20 +112,20 @@ * * RAYGUI LAYOUT: * raygui currently does not provide an auto-layout mechanism like other libraries, -* layouts must be defined manually on controls drawing, providing the right bounds Rectangle for it. +* layouts must be defined manually on controls drawing, providing the right bounds Rectangle for it * * TOOL: rGuiLayout is a visual tool to create raygui layouts: github.com/raysan5/rguilayout * * CONFIGURATION: * #define RAYGUI_IMPLEMENTATION -* Generates the implementation of the library into the included file. +* Generates the implementation of the library into the included file * If not defined, the library is in header only mode and can be included in other headers -* or source files without problems. But only ONE file should hold the implementation. +* or source files without problems. But only ONE file should hold the implementation * * #define RAYGUI_STANDALONE * Avoid raylib.h header inclusion in this file. Data types defined on raylib are defined * internally in the library and input management and drawing functions must be provided by -* the user (check library implementation for further details). +* the user (check library implementation for further details) * * #define RAYGUI_NO_ICONS * Avoid including embedded ricons data (256 icons, 16x16 pixels, 1-bit per pixel, 2KB) @@ -141,12 +141,17 @@ * Draw text bounds rectangles for debug * * VERSIONS HISTORY: -* 4.5-dev (Sep-2024) Current dev version... +* 5.0 (xx-Nov-2025) ADDED: Support up to 32 controls (v500) * ADDED: guiControlExclusiveMode and guiControlExclusiveRec for exclusive modes * ADDED: GuiValueBoxFloat() * ADDED: GuiDropdonwBox() properties: DROPDOWN_ARROW_HIDDEN, DROPDOWN_ROLL_UP * ADDED: GuiListView() property: LIST_ITEMS_BORDER_WIDTH +* ADDED: GuiLoadIconsFromMemory() * ADDED: Multiple new icons +* REMOVED: GuiSpinner() from controls list, using BUTTON + VALUEBOX properties +* REMOVED: GuiSliderPro(), functionality was redundant +* REVIEWED: Controls using text labels to use LABEL properties +* REVIEWED: Replaced sprintf() by snprintf() for more safety * REVIEWED: GuiTabBar(), close tab with mouse middle button * REVIEWED: GuiScrollPanel(), scroll speed proportional to content * REVIEWED: GuiDropdownBox(), support roll up and hidden arrow @@ -156,6 +161,8 @@ * REVIEWED: GuiIconText(), increase buffer size and reviewed padding * REVIEWED: GuiDrawText(), improved wrap mode drawing * REVIEWED: GuiScrollBar(), minor tweaks +* REVIEWED: GuiProgressBar(), improved borders computing +* REVIEWED: GuiTextBox(), multiple improvements: autocursor and more * REVIEWED: Functions descriptions, removed wrong return value reference * REDESIGNED: GuiColorPanel(), improved HSV <-> RGBA convertion * @@ -259,16 +266,16 @@ * 1.4 (15-Jun-2017) Rewritten all GUI functions (removed useless ones) * 1.3 (12-Jun-2017) Complete redesign of style system * 1.1 (01-Jun-2017) Complete review of the library -* 1.0 (07-Jun-2016) Converted to header-only by Ramon Santamaria. -* 0.9 (07-Mar-2016) Reviewed and tested by Albert Martos, Ian Eito, Sergio Martinez and Ramon Santamaria. -* 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria. +* 1.0 (07-Jun-2016) Converted to header-only by Ramon Santamaria +* 0.9 (07-Mar-2016) Reviewed and tested by Albert Martos, Ian Eito, Sergio Martinez and Ramon Santamaria +* 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria * * DEPENDENCIES: -* raylib 5.0 - Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing +* raylib 5.6-dev - Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing * * STANDALONE MODE: * By default raygui depends on raylib mostly for the inputs and the drawing functionality but that dependency can be disabled -* with the config flag RAYGUI_STANDALONE. In that case is up to the user to provide another backend to cover library needs. +* with the config flag RAYGUI_STANDALONE. In that case is up to the user to provide another backend to cover library needs * * The following functions should be redefined for a custom backend: * @@ -309,7 +316,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2024 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2025 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -334,7 +341,7 @@ #define RAYGUI_VERSION_MAJOR 4 #define RAYGUI_VERSION_MINOR 5 #define RAYGUI_VERSION_PATCH 0 -#define RAYGUI_VERSION "4.5-dev" +#define RAYGUI_VERSION "5.0-dev" #if !defined(RAYGUI_STANDALONE) #include "raylib.h" @@ -358,17 +365,6 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -// Allow custom memory allocators -#ifndef RAYGUI_MALLOC - #define RAYGUI_MALLOC(sz) malloc(sz) -#endif -#ifndef RAYGUI_CALLOC - #define RAYGUI_CALLOC(n,sz) calloc(n,sz) -#endif -#ifndef RAYGUI_FREE - #define RAYGUI_FREE(p) free(p) -#endif - // Simple log system to avoid printf() calls if required // NOTE: Avoiding those calls, also avoids const strings memory usage #define RAYGUI_SUPPORT_LOG_INFO @@ -421,13 +417,16 @@ // TODO: Texture2D type is very coupled to raylib, required by Font type // It should be redesigned to be provided by user - typedef struct Texture2D { + typedef struct Texture { unsigned int id; // OpenGL texture id int width; // Texture base width int height; // Texture base height int mipmaps; // Mipmap levels, 1 by default int format; // Data format (PixelFormat type) - } Texture2D; + } Texture; + + // Texture2D, same as Texture + typedef Texture Texture2D; // Image, pixel data stored in CPU memory (RAM) typedef struct Image { @@ -527,7 +526,7 @@ typedef enum { DROPDOWNBOX, TEXTBOX, // Used also for: TEXTBOXMULTI VALUEBOX, - SPINNER, // Uses: BUTTON, VALUEBOX + CONTROL11, LISTVIEW, COLORPICKER, SCROLLBAR, @@ -549,12 +548,12 @@ typedef enum { BORDER_COLOR_DISABLED, // Control border color in STATE_DISABLED BASE_COLOR_DISABLED, // Control base color in STATE_DISABLED TEXT_COLOR_DISABLED, // Control text color in STATE_DISABLED - BORDER_WIDTH, // Control border size, 0 for no border + BORDER_WIDTH = 12, // Control border size, 0 for no border //TEXT_SIZE, // Control text size (glyphs max height) -> GLOBAL for all controls //TEXT_SPACING, // Control text spacing between glyphs -> GLOBAL for all controls - //TEXT_LINE_SPACING // Control text spacing between lines -> GLOBAL for all controls - TEXT_PADDING, // Control text padding, not considering border - TEXT_ALIGNMENT, // Control text horizontal alignment inside control text bound (after border and padding) + //TEXT_LINE_SPACING, // Control text spacing between lines -> GLOBAL for all controls + TEXT_PADDING = 13, // Control text padding, not considering border + TEXT_ALIGNMENT = 14, // Control text horizontal alignment inside control text bound (after border and padding) //TEXT_WRAP_MODE // Control text wrap-mode inside text bounds -> GLOBAL for all controls } GuiControlProperty; @@ -641,11 +640,14 @@ typedef enum { TEXT_READONLY = 16, // TextBox in read-only mode: 0-text editable, 1-text no-editable } GuiTextBoxProperty; -// Spinner +// ValueBox/Spinner typedef enum { - SPIN_BUTTON_WIDTH = 16, // Spinner left/right buttons width - SPIN_BUTTON_SPACING, // Spinner buttons separation -} GuiSpinnerProperty; + SPINNER_BUTTON_WIDTH = 16, // Spinner left/right buttons width + SPINNER_BUTTON_SPACING, // Spinner buttons separation +} GuiValueBoxProperty; + +// Control11 +//typedef enum { } GuiControl11Property; // ListView typedef enum { @@ -653,6 +655,7 @@ typedef enum { LIST_ITEMS_SPACING, // ListView items separation SCROLLBAR_WIDTH, // ListView scrollbar size (usually width) SCROLLBAR_SIDE, // ListView scrollbar side (0-SCROLLBAR_LEFT_SIDE, 1-SCROLLBAR_RIGHT_SIDE) + LIST_ITEMS_BORDER_NORMAL, // ListView items border enabled in normal state LIST_ITEMS_BORDER_WIDTH // ListView items border width } GuiListViewProperty; @@ -717,6 +720,9 @@ RAYGUIAPI char **GuiLoadIcons(const char *fileName, bool loadIconsName); // Load RAYGUIAPI void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color); // Draw icon using pixel size at specified position #endif +// Utility functions +RAYGUIAPI int GuiGetTextWidth(const char *text); // Get text width considering gui style and icon size (if required) + // Controls //---------------------------------------------------------------------------------------------------------- // Container/separator controls, useful for controls organization @@ -999,33 +1005,33 @@ typedef enum { ICON_MLAYERS = 226, ICON_MAPS = 227, ICON_HOT = 228, - ICON_229 = 229, - ICON_230 = 230, - ICON_231 = 231, - ICON_232 = 232, - ICON_233 = 233, - ICON_234 = 234, - ICON_235 = 235, - ICON_236 = 236, - ICON_237 = 237, - ICON_238 = 238, - ICON_239 = 239, - ICON_240 = 240, - ICON_241 = 241, - ICON_242 = 242, - ICON_243 = 243, - ICON_244 = 244, - ICON_245 = 245, - ICON_246 = 246, - ICON_247 = 247, - ICON_248 = 248, - ICON_249 = 249, + ICON_LABEL = 229, + ICON_NAME_ID = 230, + ICON_SLICING = 231, + ICON_MANUAL_CONTROL = 232, + ICON_COLLISION = 233, + ICON_CIRCLE_ADD = 234, + ICON_CIRCLE_ADD_FILL = 235, + ICON_CIRCLE_WARNING = 236, + ICON_CIRCLE_WARNING_FILL = 237, + ICON_BOX_MORE = 238, + ICON_BOX_MORE_FILL = 239, + ICON_BOX_MINUS = 240, + ICON_BOX_MINUS_FILL = 241, + ICON_UNION = 242, + ICON_INTERSECTION = 243, + ICON_DIFFERENCE = 244, + ICON_SPHERE = 245, + ICON_CYLINDER = 246, + ICON_CONE = 247, + ICON_ELLIPSOID = 248, + ICON_CAPSULE = 249, ICON_250 = 250, ICON_251 = 251, ICON_252 = 252, ICON_253 = 253, ICON_254 = 254, - ICON_255 = 255, + ICON_255 = 255 } GuiIconName; #endif @@ -1046,12 +1052,24 @@ typedef enum { #if defined(RAYGUI_IMPLEMENTATION) #include // required for: isspace() [GuiTextBox()] -#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf(), vsprintf() [GuiLoadStyle(), GuiLoadIcons()] -#include // Required for: malloc(), calloc(), free() [GuiLoadStyle(), GuiLoadIcons()] +#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf(), snprintf(), vsprintf() [GuiLoadStyle(), GuiLoadIcons()] #include // Required for: strlen() [GuiTextBox(), GuiValueBox()], memset(), memcpy() #include // Required for: va_list, va_start(), vfprintf(), va_end() [TextFormat()] #include // Required for: roundf() [GuiColorPicker()] +// Allow custom memory allocators +#if defined(RAYGUI_MALLOC) || defined(RAYGUI_CALLOC) || defined(RAYGUI_FREE) + #if !defined(RAYGUI_MALLOC) || !defined(RAYGUI_CALLOC) || !defined(RAYGUI_FREE) + #error "RAYGUI: if RAYGUI_MALLOC, RAYGUI_CALLOC, or RAYGUI_FREE is customized, all three must be customized" + #endif +#else + #include // Required for: malloc(), calloc(), free() [GuiLoadStyle(), GuiLoadIcons()] + + #define RAYGUI_MALLOC(sz) malloc(sz) + #define RAYGUI_CALLOC(n,sz) calloc(n,sz) + #define RAYGUI_FREE(p) free(p) +#endif + #ifdef __cplusplus #define RAYGUI_CLITERAL(name) name #else @@ -1060,7 +1078,7 @@ typedef enum { // Check if two rectangles are equal, used to validate a slider bounds as an id #ifndef CHECK_BOUNDS_ID - #define CHECK_BOUNDS_ID(src, dst) ((src.x == dst.x) && (src.y == dst.y) && (src.width == dst.width) && (src.height == dst.height)) + #define CHECK_BOUNDS_ID(src, dst) (((int)src.x == (int)dst.x) && ((int)src.y == (int)dst.y) && ((int)src.width == (int)dst.width) && ((int)src.height == (int)dst.height)) #endif #if !defined(RAYGUI_NO_ICONS) && !defined(RAYGUI_CUSTOM_ICONS) @@ -1318,27 +1336,27 @@ static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x422a422a, 0x422e422a, 0x40384e28, 0x00007fe0, // ICON_MLAYERS 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x5b2a512a, 0x512e552a, 0x40385128, 0x00007fe0, // ICON_MAPS 0x04200000, 0x1cf00c60, 0x11f019f0, 0x0f3807b8, 0x1e3c0f3c, 0x1c1c1e1c, 0x1e3c1c1c, 0x00000f70, // ICON_HOT - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_229 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_230 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_231 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_232 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_233 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_234 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_235 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_236 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_237 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_238 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_239 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_240 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_241 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_242 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_243 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_244 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_245 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_246 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_247 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_248 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_249 + 0x00000000, 0x20803f00, 0x2a202e40, 0x20082e10, 0x08021004, 0x02040402, 0x00900108, 0x00000060, // ICON_LABEL + 0x00000000, 0x042007e0, 0x47e27c3e, 0x4ffa4002, 0x47fa4002, 0x4ffa4002, 0x7ffe4002, 0x00000000, // ICON_NAME_ID + 0x7fe00000, 0x402e4020, 0x43ce5e0a, 0x40504078, 0x438e4078, 0x402e5e0a, 0x7fe04020, 0x00000000, // ICON_SLICING + 0x00000000, 0x40027ffe, 0x47c24002, 0x55425d42, 0x55725542, 0x50125552, 0x10105016, 0x00001ff0, // ICON_MANUAL_CONTROL + 0x7ffe0000, 0x43c24002, 0x48124422, 0x500a500a, 0x500a500a, 0x44224812, 0x400243c2, 0x00007ffe, // ICON_COLLISION + 0x03c00000, 0x10080c30, 0x21842184, 0x4ff24182, 0x41824ff2, 0x21842184, 0x0c301008, 0x000003c0, // ICON_CIRCLE_ADD + 0x03c00000, 0x1ff80ff0, 0x3e7c3e7c, 0x700e7e7e, 0x7e7e700e, 0x3e7c3e7c, 0x0ff01ff8, 0x000003c0, // ICON_CIRCLE_ADD_FILL + 0x03c00000, 0x10080c30, 0x21842184, 0x41824182, 0x40024182, 0x21842184, 0x0c301008, 0x000003c0, // ICON_CIRCLE_WARNING + 0x03c00000, 0x1ff80ff0, 0x3e7c3e7c, 0x7e7e7e7e, 0x7ffe7e7e, 0x3e7c3e7c, 0x0ff01ff8, 0x000003c0, // ICON_CIRCLE_WARNING_FILL + 0x00000000, 0x10041ffc, 0x10841004, 0x13e41084, 0x10841084, 0x10041004, 0x00001ffc, 0x00000000, // ICON_BOX_MORE + 0x00000000, 0x1ffc1ffc, 0x1f7c1ffc, 0x1c1c1f7c, 0x1f7c1f7c, 0x1ffc1ffc, 0x00001ffc, 0x00000000, // ICON_BOX_MORE_FILL + 0x00000000, 0x1ffc1ffc, 0x1ffc1ffc, 0x1c1c1ffc, 0x1ffc1ffc, 0x1ffc1ffc, 0x00001ffc, 0x00000000, // ICON_BOX_MINUS + 0x00000000, 0x10041ffc, 0x10041004, 0x13e41004, 0x10041004, 0x10041004, 0x00001ffc, 0x00000000, // ICON_BOX_MINUS_FILL + 0x07fe0000, 0x055606aa, 0x7ff606aa, 0x55766eba, 0x55766eaa, 0x55606ffe, 0x55606aa0, 0x00007fe0, // ICON_UNION + 0x07fe0000, 0x04020402, 0x7fe20402, 0x456246a2, 0x456246a2, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_INTERSECTION + 0x07fe0000, 0x055606aa, 0x7ff606aa, 0x4436442a, 0x4436442a, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_DIFFERENCE + 0x03c00000, 0x10080c30, 0x20042004, 0x60064002, 0x47e2581a, 0x20042004, 0x0c301008, 0x000003c0, // ICON_SPHERE + 0x03e00000, 0x08080410, 0x0c180808, 0x08080be8, 0x08080808, 0x08080808, 0x04100808, 0x000003e0, // ICON_CYLINDER + 0x00800000, 0x01400140, 0x02200220, 0x04100410, 0x08080808, 0x1c1c13e4, 0x08081004, 0x000007f0, // ICON_CONE + 0x00000000, 0x07e00000, 0x20841918, 0x40824082, 0x40824082, 0x19182084, 0x000007e0, 0x00000000, // ICON_ELLIPSOID + 0x00000000, 0x00000000, 0x20041ff8, 0x40024002, 0x40024002, 0x1ff82004, 0x00000000, 0x00000000, // ICON_CAPSULE 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_250 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_251 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_252 @@ -1363,7 +1381,7 @@ static unsigned int *guiIconsPtr = guiIcons; #define RAYGUI_MAX_PROPS_EXTENDED 8 // Maximum number of extended properties //---------------------------------------------------------------------------------- -// Types and Structures Definition +// Module Types and Structures Definition //---------------------------------------------------------------------------------- // Gui control property style color element typedef enum { BORDER = 0, BASE, TEXT, OTHER } GuiPropertyElement; @@ -1387,8 +1405,7 @@ static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*() //static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking -static int autoCursorCooldownCounter = 0; // Cooldown frame counter for automatic cursor movement on key-down -static int autoCursorDelayCounter = 0; // Delay frame counter for automatic cursor movement +static int autoCursorCounter = 0; // Frame counter for automatic repeated cursor movement on key-down (cooldown and delay) //---------------------------------------------------------------------------------- // Style data array for all gui style properties (allocated on data segment by default) @@ -1484,7 +1501,6 @@ static void DrawRectangleGradientV(int posX, int posY, int width, int height, Co //---------------------------------------------------------------------------------- static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize); // Load style from memory (binary only) -static int GetTextWidth(const char *text); // Gui get text width using gui font and style static Rectangle GetTextBounds(int control, Rectangle bounds); // Get text bounds considering control bounds static const char *GetTextIcon(const char *text, int *iconId); // Get text icon if provided and move text cursor @@ -1589,6 +1605,10 @@ int GuiWindowBox(Rectangle bounds, const char *title) #define RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT 24 #endif + #if !defined(RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT) + #define RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT 18 + #endif + int result = 0; //GuiState state = guiState; @@ -1597,9 +1617,10 @@ int GuiWindowBox(Rectangle bounds, const char *title) Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)statusBarHeight }; if (bounds.height < statusBarHeight*2.0f) bounds.height = statusBarHeight*2.0f; + const float vPadding = statusBarHeight/2.0f - RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT/2.0f; Rectangle windowPanel = { bounds.x, bounds.y + (float)statusBarHeight - 1, bounds.width, bounds.height - (float)statusBarHeight + 1 }; - Rectangle closeButtonRec = { statusBar.x + statusBar.width - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - 20, - statusBar.y + statusBarHeight/2.0f - 18.0f/2.0f, 18, 18 }; + Rectangle closeButtonRec = { statusBar.x + statusBar.width - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT - vPadding, + statusBar.y + vPadding, RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT, RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT }; // Update control //-------------------------------------------------------------------- @@ -1653,7 +1674,7 @@ int GuiGroupBox(Rectangle bounds, const char *text) // Line control int GuiLine(Rectangle bounds, const char *text) { - #if !defined(RAYGUI_LINE_ORIGIN_SIZE) + #if !defined(RAYGUI_LINE_MARGIN_TEXT) #define RAYGUI_LINE_MARGIN_TEXT 12 #endif #if !defined(RAYGUI_LINE_TEXT_PADDING) @@ -1671,7 +1692,7 @@ int GuiLine(Rectangle bounds, const char *text) else { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.width = (float)GuiGetTextWidth(text) + 2; textBounds.height = bounds.height; textBounds.x = bounds.x + RAYGUI_LINE_MARGIN_TEXT; textBounds.y = bounds.y; @@ -1711,8 +1732,8 @@ int GuiPanel(Rectangle bounds, const char *text) //-------------------------------------------------------------------- if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar - GuiDrawRectangle(bounds, RAYGUI_PANEL_BORDER_WIDTH, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED: (int)LINE_COLOR)), - GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? BASE_COLOR_DISABLED : BACKGROUND_COLOR))); + GuiDrawRectangle(bounds, RAYGUI_PANEL_BORDER_WIDTH, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR)), + GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BASE_COLOR_DISABLED : (int)BACKGROUND_COLOR))); //-------------------------------------------------------------------- return result; @@ -1722,7 +1743,7 @@ int GuiPanel(Rectangle bounds, const char *text) // NOTE: Using GuiToggle() for the TABS int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) { - #define RAYGUI_TABBAR_ITEM_WIDTH 160 + #define RAYGUI_TABBAR_ITEM_WIDTH 148 int result = -1; //GuiState state = guiState; @@ -1755,12 +1776,12 @@ int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) if (i == (*active)) { toggle = true; - GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); + GuiToggle(tabBounds, text[i], &toggle); } else { toggle = false; - GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); + GuiToggle(tabBounds, text[i], &toggle); if (toggle) *active = i; } @@ -2011,7 +2032,7 @@ int GuiLabelButton(Rectangle bounds, const char *text) bool pressed = false; // NOTE: We force bounds.width to be all text - float textWidth = (float)GetTextWidth(text); + float textWidth = (float)GuiGetTextWidth(text); if ((bounds.width - 2*GuiGetStyle(LABEL, BORDER_WIDTH) - 2*GuiGetStyle(LABEL, TEXT_PADDING)) < textWidth) bounds.width = textWidth + 2*GuiGetStyle(LABEL, BORDER_WIDTH) + 2*GuiGetStyle(LABEL, TEXT_PADDING) + 2; // Update control @@ -2149,7 +2170,9 @@ int GuiToggleSlider(Rectangle bounds, const char *text, int *active) // Get substrings items from text (items pointers) int itemCount = 0; - const char **items = GuiTextSplit(text, ';', &itemCount, NULL); + const char **items = NULL; + + if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); Rectangle slider = { 0, // Calculated later depending on the active toggle @@ -2196,7 +2219,7 @@ int GuiToggleSlider(Rectangle bounds, const char *text, int *active) if (text != NULL) { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(text); + textBounds.width = (float)GuiGetTextWidth(text); textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = slider.x + slider.width/2 - textBounds.width/2; textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; @@ -2221,7 +2244,7 @@ int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) if (text != NULL) { - textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.width = (float)GuiGetTextWidth(text) + 2; textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x + bounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; @@ -2474,7 +2497,7 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) { #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) - #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 40 // Frames to wait for autocursor movement + #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 20 // Frames to wait for autocursor movement #endif #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement @@ -2487,10 +2510,10 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); - int textLength = (int)strlen(text); // Get current text length + int textLength = (text != NULL)? (int)strlen(text) : 0; // Get current text length int thisCursorIndex = textBoxCursorIndex; if (thisCursorIndex > textLength) thisCursorIndex = textLength; - int textWidth = GetTextWidth(text) - GetTextWidth(text + thisCursorIndex); + int textWidth = GuiGetTextWidth(text) - GuiGetTextWidth(text + thisCursorIndex); int textIndexOffset = 0; // Text index offset to start drawing in the box // Cursor rectangle @@ -2511,15 +2534,6 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) mouseCursor.x = -1; mouseCursor.width = 1; - // Auto-cursor movement logic - // NOTE: Cursor moves automatically when key down after some time - if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCooldownCounter++; - else - { - autoCursorCooldownCounter = 0; // GLOBAL: Cursor cooldown counter - autoCursorDelayCounter = 0; // GLOBAL: Cursor delay counter - } - // Blink-cursor frame counter //if (!autoCursorMode) blinkCursorFrameCounter++; //else blinkCursorFrameCounter = 0; @@ -2537,6 +2551,13 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) if (editMode) { + // GLOBAL: Auto-cursor movement logic + // NOTE: Keystrokes are handled repeatedly when button is held down for some time + if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCounter++; + else autoCursorCounter = 0; + + bool autoCursorShouldTrigger = (autoCursorCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) && ((autoCursorCounter % RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0); + state = STATE_PRESSED; if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; @@ -2550,7 +2571,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) textIndexOffset += nextCodepointSize; - textWidth = GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex); + textWidth = GuiGetTextWidth(text + textIndexOffset) - GuiGetTextWidth(text + textBoxCursorIndex); } int codepoint = GetCharPressed(); // Get Unicode codepoint @@ -2560,10 +2581,43 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) int codepointSize = 0; const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); - // Add codepoint to text, at current cursor position - // NOTE: Make sure we do not overflow buffer size - if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) + // Handle text paste action + if (IsKeyPressed(KEY_V) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) { + const char *pasteText = GetClipboardText(); + if (pasteText != NULL) + { + int pasteLength = 0; + int pasteCodepoint; + int pasteCodepointSize; + + // Count how many codepoints to copy, stopping at the first unwanted control character + while (true) + { + pasteCodepoint = GetCodepointNext(pasteText + pasteLength, &pasteCodepointSize); + if (textLength + pasteLength + pasteCodepointSize >= textSize) break; + if (!(multiline && (pasteCodepoint == (int)'\n')) && !(pasteCodepoint >= 32)) break; + pasteLength += pasteCodepointSize; + } + + if (pasteLength > 0) + { + // Move forward data from cursor position + for (int i = textLength + pasteLength; i > textBoxCursorIndex; i--) text[i] = text[i - pasteLength]; + + // Paste data in at cursor + for (int i = 0; i < pasteLength; i++) text[textBoxCursorIndex + i] = pasteText[i]; + + textBoxCursorIndex += pasteLength; + textLength += pasteLength; + text[textLength] = '\0'; + } + } + } + else if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) + { + // Adding codepoint to text, at current cursor position + // Move forward data from cursor position for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; @@ -2583,113 +2637,185 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) // Move cursor to end if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength; - // Delete codepoint from text, after current cursor position - if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + // Delete related codepoints from text, after current cursor position + if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_DELETE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) { - autoCursorDelayCounter++; + int offset = textBoxCursorIndex; + int accCodepointSize = 0; + int nextCodepointSize; + int nextCodepoint; - if (IsKeyPressed(KEY_DELETE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + bool puctuation = ispunct(nextCodepoint & 0xff); + while (offset < textLength) { - int nextCodepointSize = 0; - GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); - - // Move backward text from cursor position - for (int i = textBoxCursorIndex; i < textLength; i++) text[i] = text[i + nextCodepointSize]; - - textLength -= codepointSize; - if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; - - // Make sure text last character is EOL - text[textLength] = '\0'; + if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) + break; + offset += nextCodepointSize; + accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); } + + // Check whitespace to delete (ASCII only) + while (offset < textLength) + { + if (!isspace(nextCodepoint & 0xff)) break; + + offset += nextCodepointSize; + accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + } + + // Move text after cursor forward (including final null terminator) + for (int i = offset; i <= textLength; i++) text[i - accCodepointSize] = text[i]; + + textLength -= accCodepointSize; + } + + else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && autoCursorShouldTrigger))) + { + // Delete single codepoint from text, after current cursor position + + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex + nextCodepointSize; i <= textLength; i++) text[i - nextCodepointSize] = text[i]; + + textLength -= nextCodepointSize; } // Delete related codepoints from text, before current cursor position - if ((textLength > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) { - int i = textBoxCursorIndex - 1; + int offset = textBoxCursorIndex; int accCodepointSize = 0; + int prevCodepointSize; + int prevCodepoint; - // Move cursor to the end of word if on space already - while ((i > 0) && isspace(text[i])) + // Check whitespace to delete (ASCII only) + while (offset > 0) { - int prevCodepointSize = 0; - GetCodepointPrevious(text + i, &prevCodepointSize); - i -= prevCodepointSize; + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if (!isspace(prevCodepoint & 0xff)) break; + + offset -= prevCodepointSize; accCodepointSize += prevCodepointSize; } - // Move cursor to the start of the word - while ((i > 0) && !isspace(text[i])) + // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + bool puctuation = ispunct(prevCodepoint & 0xff); + while (offset > 0) { - int prevCodepointSize = 0; - GetCodepointPrevious(text + i, &prevCodepointSize); - i -= prevCodepointSize; + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break; + + offset -= prevCodepointSize; accCodepointSize += prevCodepointSize; } - // Move forward text from cursor position - for (int j = (textBoxCursorIndex - accCodepointSize); j < textLength; j++) text[j] = text[j + accCodepointSize]; + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - accCodepointSize] = text[i]; - // Prevent cursor index from decrementing past 0 - if (textBoxCursorIndex > 0) - { - textBoxCursorIndex -= accCodepointSize; - textLength -= accCodepointSize; - } + textLength -= accCodepointSize; + textBoxCursorIndex -= accCodepointSize; + } - // Make sure text last character is EOL - text[textLength] = '\0'; - } - else if ((textLength > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && autoCursorShouldTrigger))) { - autoCursorDelayCounter++; + // Delete single codepoint from text, before current cursor position - if (IsKeyPressed(KEY_BACKSPACE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames - { - int prevCodepointSize = 0; + int prevCodepointSize = 0; - // Prevent cursor index from decrementing past 0 - if (textBoxCursorIndex > 0) - { - GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); - // Move backward text from cursor position - for (int i = (textBoxCursorIndex - prevCodepointSize); i < textLength; i++) text[i] = text[i + prevCodepointSize]; + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - prevCodepointSize] = text[i]; - textBoxCursorIndex -= codepointSize; - textLength -= codepointSize; - } - - // Make sure text last character is EOL - text[textLength] = '\0'; - } + textLength -= prevCodepointSize; + textBoxCursorIndex -= prevCodepointSize; } // Move cursor position with keys - if (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) + if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_LEFT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) { - autoCursorDelayCounter++; + int offset = textBoxCursorIndex; + //int accCodepointSize = 0; + int prevCodepointSize; + int prevCodepoint; - if (IsKeyPressed(KEY_LEFT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + // Check whitespace to skip (ASCII only) + while (offset > 0) { - int prevCodepointSize = 0; - if (textBoxCursorIndex > 0) GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if (!isspace(prevCodepoint & 0xff)) break; - if (textBoxCursorIndex >= prevCodepointSize) textBoxCursorIndex -= prevCodepointSize; + offset -= prevCodepointSize; + //accCodepointSize += prevCodepointSize; } + + // Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + bool puctuation = ispunct(prevCodepoint & 0xff); + while (offset > 0) + { + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break; + + offset -= prevCodepointSize; + //accCodepointSize += prevCodepointSize; + } + + textBoxCursorIndex = offset; } - else if (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) + else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && autoCursorShouldTrigger))) { - autoCursorDelayCounter++; + int prevCodepointSize = 0; + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); - if (IsKeyPressed(KEY_RIGHT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + textBoxCursorIndex -= prevCodepointSize; + } + else if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_RIGHT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + int offset = textBoxCursorIndex; + //int accCodepointSize = 0; + int nextCodepointSize; + int nextCodepoint; + + // Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + bool puctuation = ispunct(nextCodepoint & 0xff); + while (offset < textLength) { - int nextCodepointSize = 0; - GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) break; - if ((textBoxCursorIndex + nextCodepointSize) <= textLength) textBoxCursorIndex += nextCodepointSize; + offset += nextCodepointSize; + //accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); } + + // Check whitespace to skip (ASCII only) + while (offset < textLength) + { + if (!isspace(nextCodepoint & 0xff)) break; + + offset += nextCodepointSize; + //accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + } + + textBoxCursorIndex = offset; + } + else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && autoCursorShouldTrigger))) + { + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + textBoxCursorIndex += nextCodepointSize; } // Move cursor position with mouse @@ -2701,7 +2827,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) float widthToMouseX = 0; int mouseCursorIndex = 0; - for (int i = textIndexOffset; i < textLength; i++) + for (int i = textIndexOffset; i < textLength; i += codepointSize) { codepoint = GetCodepointNext(&text[i], &codepointSize); codepointIndex = GetGlyphIndex(guiFont, codepoint); @@ -2720,7 +2846,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) } // Check if mouse cursor is at the last position - int textEndWidth = GetTextWidth(text + textIndexOffset); + int textEndWidth = GuiGetTextWidth(text + textIndexOffset); if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2)) { mouseCursor.x = textBounds.x + textEndWidth; @@ -2737,7 +2863,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) else mouseCursor.x = -1; // Recalculate cursor position.y depending on textBoxCursorIndex - cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); + cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GuiGetTextWidth(text + textIndexOffset) - GuiGetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); //if (multiline) cursor.y = GetTextLines() // Finish text editing on ENTER or mouse click outside bounds @@ -2745,6 +2871,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) (!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) { textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index + autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes result = 1; } } @@ -2757,6 +2884,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text + autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes result = 1; } } @@ -2825,19 +2953,22 @@ int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int int tempValue = *value; - Rectangle spinner = { bounds.x + GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING), bounds.y, - bounds.width - 2*(GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING)), bounds.height }; - Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; - Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; + Rectangle valueBoxBounds = { + bounds.x + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH) + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_SPACING), + bounds.y, + bounds.width - 2*(GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH) + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_SPACING)), bounds.height }; + Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.height }; + Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.y, + (float)GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.height }; Rectangle textBounds = { 0 }; if (text != NULL) { - textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.width = (float)GuiGetTextWidth(text) + 2; textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(SPINNER, TEXT_PADDING); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - if (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SPINNER, TEXT_PADDING); + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); } // Update control @@ -2871,20 +3002,20 @@ int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int // Draw control //-------------------------------------------------------------------- - result = GuiValueBox(spinner, NULL, &tempValue, minValue, maxValue, editMode); + result = GuiValueBox(valueBoxBounds, NULL, &tempValue, minValue, maxValue, editMode); // Draw value selector custom buttons // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(SPINNER, BORDER_WIDTH)); + GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(VALUEBOX, BORDER_WIDTH)); GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); // Draw text label if provided - GuiDrawText(text, textBounds, (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); //-------------------------------------------------------------------- *value = tempValue; @@ -2902,13 +3033,13 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in int result = 0; GuiState state = guiState; - char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; - sprintf(textValue, "%i", *value); + char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = { 0 }; + snprintf(textValue, RAYGUI_VALUEBOX_MAX_CHARS + 1, "%i", *value); Rectangle textBounds = { 0 }; if (text != NULL) { - textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.width = (float)GuiGetTextWidth(text) + 2; textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; @@ -2920,7 +3051,6 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); - bool valueHasChanged = false; if (editMode) @@ -2929,30 +3059,53 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in int keyCount = (int)strlen(textValue); - // Only allow keys in range [48..57] - if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + // Add or remove minus symbol + if (IsKeyPressed(KEY_MINUS)) { - if (GetTextWidth(textValue) < bounds.width) + if (textValue[0] == '-') { - int key = GetCharPressed(); - if ((key >= 48) && (key <= 57)) + for (int i = 0 ; i < keyCount; i++) textValue[i] = textValue[i + 1]; + + keyCount--; + valueHasChanged = true; + } + else if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + { + if (keyCount == 0) { - textValue[keyCount] = (char)key; + textValue[0] = '0'; + textValue[1] = '\0'; keyCount++; - valueHasChanged = true; } + + for (int i = keyCount ; i > -1; i--) textValue[i + 1] = textValue[i]; + + textValue[0] = '-'; + keyCount++; + valueHasChanged = true; + } + } + + // Add new digit to text value + if ((keyCount >= 0) && (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) && (GuiGetTextWidth(textValue) < bounds.width)) + { + int key = GetCharPressed(); + + // Only allow keys in range [48..57] + if ((key >= 48) && (key <= 57)) + { + textValue[keyCount] = (char)key; + keyCount++; + valueHasChanged = true; } } // Delete text - if (keyCount > 0) + if ((keyCount > 0) && IsKeyPressed(KEY_BACKSPACE)) { - if (IsKeyPressed(KEY_BACKSPACE)) - { - keyCount--; - textValue[keyCount] = '\0'; - valueHasChanged = true; - } + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; } if (valueHasChanged) *value = TextToInteger(textValue); @@ -2992,11 +3145,14 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); - // Draw cursor + // Draw cursor rectangle if (editMode) { // NOTE: ValueBox internal text is always centered - Rectangle cursor = { bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH) }; + Rectangle cursor = { bounds.x + GuiGetTextWidth(textValue)/2 + bounds.width/2 + 1, + bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH) + 2, + 2, bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2 - 4 }; + if (cursor.height > bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); } @@ -3019,12 +3175,12 @@ int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float GuiState state = guiState; //char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; - //sprintf(textValue, "%2.2f", *value); + //snprintf(textValue, sizeof(textValue), "%2.2f", *value); - Rectangle textBounds = {0}; + Rectangle textBounds = { 0 }; if (text != NULL) { - textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.width = (float)GuiGetTextWidth(text) + 2; textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; @@ -3045,10 +3201,37 @@ int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float int keyCount = (int)strlen(textValue); + // Add or remove minus symbol + if (IsKeyPressed(KEY_MINUS)) + { + if (textValue[0] == '-') + { + for (int i = 0; i < keyCount; i++) textValue[i] = textValue[i + 1]; + + keyCount--; + valueHasChanged = true; + } + else if (keyCount < (RAYGUI_VALUEBOX_MAX_CHARS - 1)) + { + if (keyCount == 0) + { + textValue[0] = '0'; + textValue[1] = '\0'; + keyCount++; + } + + for (int i = keyCount; i > -1; i--) textValue[i + 1] = textValue[i]; + + textValue[0] = '-'; + keyCount++; + valueHasChanged = true; + } + } + // Only allow keys in range [48..57] if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) { - if (GetTextWidth(textValue) < bounds.width) + if (GuiGetTextWidth(textValue) < bounds.width) { int key = GetCharPressed(); if (((key >= 48) && (key <= 57)) || @@ -3103,7 +3286,7 @@ int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float if (editMode) { // NOTE: ValueBox internal text is always centered - Rectangle cursor = {bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, + Rectangle cursor = {bounds.x + GuiGetTextWidth(textValue)/2 + bounds.width/2 + 1, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH)}; GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); @@ -3120,7 +3303,7 @@ int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float // Slider control with pro parameters // NOTE: Other GuiSlider*() controls use this one -int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue, int sliderWidth) +int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) { int result = 0; GuiState state = guiState; @@ -3129,6 +3312,8 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, if (value == NULL) value = &temp; float oldValue = *value; + int sliderWidth = GuiGetStyle(SLIDER, SLIDER_WIDTH); + Rectangle slider = { bounds.x, bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), 0, bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; @@ -3146,7 +3331,7 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, { state = STATE_PRESSED; // Get equivalent value and slider position from mousePosition.x - *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width - sliderWidth)) + minValue; } } else @@ -3166,7 +3351,7 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, if (!CheckCollisionPointRec(mousePoint, slider)) { // Get equivalent value and slider position from mousePosition.x - *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width - sliderWidth)) + minValue; } } else state = STATE_FOCUSED; @@ -3205,44 +3390,45 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_FOCUSED))); else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_PRESSED))); + else if (state == STATE_DISABLED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_DISABLED))); // Draw left/right text if provided if (textLeft != NULL) { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textLeft); + textBounds.width = (float)GuiGetTextWidth(textLeft); textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SLIDER, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); } if (textRight != NULL) { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textRight); + textBounds.width = (float)GuiGetTextWidth(textRight); textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x + bounds.width + GuiGetStyle(SLIDER, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); } //-------------------------------------------------------------------- return result; } -// Slider control extended, returns selected value and has text -int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) -{ - return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, GuiGetStyle(SLIDER, SLIDER_WIDTH)); -} - // Slider Bar control extended, returns selected value int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) { - return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, 0); + int result = 0; + int preSliderWidth = GuiGetStyle(SLIDER, SLIDER_WIDTH); + GuiSetStyle(SLIDER, SLIDER_WIDTH, 0); + result = GuiSlider(bounds, textLeft, textRight, value, minValue, maxValue); + GuiSetStyle(SLIDER, SLIDER_WIDTH, preSliderWidth); + + return result; } // Progress Bar control extended, shows current progress value @@ -3257,14 +3443,14 @@ int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight // Progress bar Rectangle progress = { bounds.x + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) + GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING), 0, - bounds.height - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - 2*GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING) }; + bounds.height - GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - 2*GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING) -1 }; // Update control //-------------------------------------------------------------------- if (*value > maxValue) *value = maxValue; // WARNING: Working with floats could lead to rounding issues - if ((state != STATE_DISABLED)) progress.width = (float)(*value/(maxValue - minValue))*bounds.width - ((*value >= maxValue)? (float)(2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)) : 0.0f); + if ((state != STATE_DISABLED)) progress.width = ((float)*value/(maxValue - minValue))*(bounds.width - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)); //-------------------------------------------------------------------- // Draw control @@ -3282,15 +3468,15 @@ int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); } - else GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + else GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - if (*value >= maxValue) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + progress.width + 1, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + if (*value >= maxValue) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1}, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); else { // Draw borders not yet reached by value - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y + bounds.height - 1, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y + bounds.height - 1, bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); } // Draw slider internal progress bar (depends on state) @@ -3301,23 +3487,23 @@ int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight if (textLeft != NULL) { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textLeft); + textBounds.width = (float)GuiGetTextWidth(textLeft); textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x - textBounds.width - GuiGetStyle(PROGRESSBAR, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); } if (textRight != NULL) { Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textRight); + textBounds.width = (float)GuiGetTextWidth(textRight); textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); textBounds.x = bounds.x + bounds.width + GuiGetStyle(PROGRESSBAR, TEXT_PADDING); textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); } //-------------------------------------------------------------------- @@ -3467,11 +3653,11 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd // Draw visible items for (int i = 0; ((i < visibleItems) && (text != NULL)); i++) { - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_NORMAL)), BLANK); + if (GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_NORMAL)) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_NORMAL)), BLANK); if (state == STATE_DISABLED) { - if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); + if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED))); } @@ -3480,18 +3666,18 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd if (((startIndex + i) == itemSelected) && (active != NULL)) { // Draw item selected - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED))); + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED))); GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED))); } else if (((startIndex + i) == itemFocused)) // && (focus != NULL)) // NOTE: We want items focused, despite not returned! { // Draw item focused - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED))); + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED))); GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED))); } else { - // Draw item normal + // Draw item normal (no rectangle) GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL))); } } @@ -3531,22 +3717,22 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd return result; } -// Color Panel control - Color (RGBA) variant. +// Color Panel control - Color (RGBA) variant int GuiColorPanel(Rectangle bounds, const char *text, Color *color) { int result = 0; Vector3 vcolor = { (float)color->r/255.0f, (float)color->g/255.0f, (float)color->b/255.0f }; Vector3 hsv = ConvertRGBtoHSV(vcolor); - Vector3 prevHsv = hsv; // workaround to see if GuiColorPanelHSV modifies the hsv. + Vector3 prevHsv = hsv; // workaround to see if GuiColorPanelHSV modifies the hsv GuiColorPanelHSV(bounds, text, &hsv); - // Check if the hsv was changed, only then change the color. - // This is required, because the Color->HSV->Color conversion has precision errors. - // Thus the assignment from HSV to Color should only be made, if the HSV has a new user-entered value. - // Otherwise GuiColorPanel would often modify it's color without user input. - // TODO: GuiColorPanelHSV could return 1 if the slider was dragged, to simplify this check. + // Check if the hsv was changed, only then change the color + // This is required, because the Color->HSV->Color conversion has precision errors + // Thus the assignment from HSV to Color should only be made, if the HSV has a new user-entered value + // Otherwise GuiColorPanel would often modify it's color without user input + // TODO: GuiColorPanelHSV could return 1 if the slider was dragged, to simplify this check if (hsv.x != prevHsv.x || hsv.y != prevHsv.y || hsv.z != prevHsv.z) { Vector3 rgb = ConvertHSVtoRGB(hsv); @@ -3570,7 +3756,10 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) int result = 0; GuiState state = guiState; - Rectangle selector = { (float)bounds.x + (*alpha)*bounds.width - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.y - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT), (float)bounds.height + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2 }; + Rectangle selector = { (float)bounds.x + (*alpha)*bounds.width - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, + (float)bounds.y - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), + (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT), + (float)bounds.height + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2 }; // Update control //-------------------------------------------------------------------- @@ -3617,7 +3806,6 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) // Draw control //-------------------------------------------------------------------- - // Draw alpha bar: checked background if (state != STATE_DISABLED) { @@ -3755,7 +3943,7 @@ int GuiColorPicker(Rectangle bounds, const char *text, Color *color) Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; //Rectangle boundsAlpha = { bounds.x, bounds.y + bounds.height + GuiGetStyle(COLORPICKER, BARS_PADDING), bounds.width, GuiGetStyle(COLORPICKER, BARS_THICK) }; - // NOTE: this conversion can cause low hue-resolution, if the r, g and b value are very similar, which causes the hue bar to shift around when only the GuiColorPanel is used. + // NOTE: this conversion can cause low hue-resolution, if the r, g and b value are very similar, which causes the hue bar to shift around when only the GuiColorPanel is used Vector3 hsv = ConvertRGBtoHSV(RAYGUI_CLITERAL(Vector3){ (*color).r/255.0f, (*color).g/255.0f, (*color).b/255.0f }); GuiColorBarHue(boundsHue, NULL, &hsv.x); @@ -3768,8 +3956,8 @@ int GuiColorPicker(Rectangle bounds, const char *text, Color *color) return result; } -// Color Picker control that avoids conversion to RGB and back to HSV on each call, thus avoiding jittering. -// The user can call ConvertHSVtoRGB() to convert *colorHsv value to RGB. +// Color Picker control that avoids conversion to RGB and back to HSV on each call, thus avoiding jittering +// The user can call ConvertHSVtoRGB() to convert *colorHsv value to RGB // NOTE: It's divided in multiple controls: // int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) // int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) @@ -3917,7 +4105,7 @@ int GuiMessageBox(Rectangle bounds, const char *title, const char *message, cons buttonBounds.width = (bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; buttonBounds.height = RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; - //int textWidth = GetTextWidth(message) + 2; + //int textWidth = GuiGetTextWidth(message) + 2; Rectangle textBounds = { 0 }; textBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; @@ -3981,7 +4169,7 @@ int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, co Rectangle textBounds = { 0 }; if (message != NULL) { - int textSize = GetTextWidth(message) + 2; + int textSize = GuiGetTextWidth(message) + 2; textBounds.x = bounds.x + bounds.width/2 - textSize/2; textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + messageInputHeight/4 - (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/2; @@ -4221,7 +4409,7 @@ void GuiLoadStyle(const char *fileName) if (fileDataSize > 0) { - unsigned char *fileData = (unsigned char *)RAYGUI_MALLOC(fileDataSize*sizeof(unsigned char)); + unsigned char *fileData = (unsigned char *)RAYGUI_CALLOC(fileDataSize, sizeof(unsigned char)); fread(fileData, sizeof(unsigned char), fileDataSize, rgsFile); GuiLoadStyleFromMemory(fileData, fileDataSize); @@ -4283,8 +4471,6 @@ void GuiLoadStyleDefault(void) GuiSetStyle(TEXTBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); GuiSetStyle(VALUEBOX, TEXT_PADDING, 0); GuiSetStyle(VALUEBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - GuiSetStyle(SPINNER, TEXT_PADDING, 0); - GuiSetStyle(SPINNER, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); GuiSetStyle(STATUSBAR, TEXT_PADDING, 8); GuiSetStyle(STATUSBAR, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); @@ -4299,8 +4485,8 @@ void GuiLoadStyleDefault(void) GuiSetStyle(COMBOBOX, COMBO_BUTTON_SPACING, 2); GuiSetStyle(DROPDOWNBOX, ARROW_PADDING, 16); GuiSetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING, 2); - GuiSetStyle(SPINNER, SPIN_BUTTON_WIDTH, 24); - GuiSetStyle(SPINNER, SPIN_BUTTON_SPACING, 2); + GuiSetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH, 24); + GuiSetStyle(VALUEBOX, SPINNER_BUTTON_SPACING, 2); GuiSetStyle(SCROLLBAR, BORDER_WIDTH, 0); GuiSetStyle(SCROLLBAR, ARROWS_VISIBLE, 0); GuiSetStyle(SCROLLBAR, ARROWS_SIZE, 6); @@ -4310,6 +4496,7 @@ void GuiLoadStyleDefault(void) GuiSetStyle(SCROLLBAR, SCROLL_SPEED, 12); GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 28); GuiSetStyle(LISTVIEW, LIST_ITEMS_SPACING, 2); + GuiSetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH, 1); GuiSetStyle(LISTVIEW, SCROLLBAR_WIDTH, 12); GuiSetStyle(LISTVIEW, SCROLLBAR_SIDE, SCROLLBAR_RIGHT_SIDE); GuiSetStyle(COLORPICKER, COLOR_SELECTOR_SIZE, 8); @@ -4322,8 +4509,8 @@ void GuiLoadStyleDefault(void) { // Unload previous font texture UnloadTexture(guiFont.texture); - RL_FREE(guiFont.recs); - RL_FREE(guiFont.glyphs); + RAYGUI_FREE(guiFont.recs); + RAYGUI_FREE(guiFont.glyphs); guiFont.recs = NULL; guiFont.glyphs = NULL; @@ -4352,7 +4539,7 @@ const char *GuiIconText(int iconId, const char *text) if (text != NULL) { memset(buffer, 0, 1024); - sprintf(buffer, "#%03i#", iconId); + snprintf(buffer, 1024, "#%03i#", iconId); for (int i = 5; i < 1024; i++) { @@ -4364,7 +4551,7 @@ const char *GuiIconText(int iconId, const char *text) } else { - sprintf(iconBuffer, "#%03i#", iconId); + snprintf(iconBuffer, 16, "#%03i#", iconId); return iconBuffer; } @@ -4430,17 +4617,17 @@ char **GuiLoadIcons(const char *fileName, bool loadIconsName) { if (loadIconsName) { - guiIconsName = (char **)RAYGUI_MALLOC(iconCount*sizeof(char **)); + guiIconsName = (char **)RAYGUI_CALLOC(iconCount, sizeof(char *)); for (int i = 0; i < iconCount; i++) { - guiIconsName[i] = (char *)RAYGUI_MALLOC(RAYGUI_ICON_MAX_NAME_LENGTH); + guiIconsName[i] = (char *)RAYGUI_CALLOC(RAYGUI_ICON_MAX_NAME_LENGTH, sizeof(char)); fread(guiIconsName[i], 1, RAYGUI_ICON_MAX_NAME_LENGTH, rgiFile); } } else fseek(rgiFile, iconCount*RAYGUI_ICON_MAX_NAME_LENGTH, SEEK_CUR); // Read icons data directly over internal icons array - fread(guiIconsPtr, sizeof(unsigned int), iconCount*(iconSize*iconSize/32), rgiFile); + fread(guiIconsPtr, sizeof(unsigned int), (int)iconCount*((int)iconSize*(int)iconSize/32), rgiFile); } fclose(rgiFile); @@ -4449,6 +4636,56 @@ char **GuiLoadIcons(const char *fileName, bool loadIconsName) return guiIconsName; } +// Load icons from memory +// WARNING: Binary files only +char **GuiLoadIconsFromMemory(const unsigned char *fileData, int dataSize, bool loadIconsName) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + char **guiIconsName = NULL; + + char signature[5] = { 0 }; + short version = 0; + short reserved = 0; + short iconCount = 0; + short iconSize = 0; + + memcpy(signature, fileDataPtr, 4); + memcpy(&version, fileDataPtr + 4, sizeof(short)); + memcpy(&reserved, fileDataPtr + 4 + 2, sizeof(short)); + memcpy(&iconCount, fileDataPtr + 4 + 2 + 2, sizeof(short)); + memcpy(&iconSize, fileDataPtr + 4 + 2 + 2 + 2, sizeof(short)); + fileDataPtr += 12; + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'I') && + (signature[3] == ' ')) + { + if (loadIconsName) + { + guiIconsName = (char **)RAYGUI_CALLOC(iconCount, sizeof(char *)); + for (int i = 0; i < iconCount; i++) + { + guiIconsName[i] = (char *)RAYGUI_CALLOC(RAYGUI_ICON_MAX_NAME_LENGTH, sizeof(char)); + memcpy(guiIconsName[i], fileDataPtr, RAYGUI_ICON_MAX_NAME_LENGTH); + fileDataPtr += RAYGUI_ICON_MAX_NAME_LENGTH; + } + } + else + { + // Skip icon name data if not required + fileDataPtr += iconCount*RAYGUI_ICON_MAX_NAME_LENGTH; + } + + int iconDataSize = iconCount*((int)iconSize*(int)iconSize/32)*(int)sizeof(unsigned int); + guiIconsPtr = (unsigned int *)RAYGUI_CALLOC(iconDataSize, 1); + + memcpy(guiIconsPtr, fileDataPtr, iconDataSize); + } + + return guiIconsName; +} + // Draw selected icon using rectangles pixel-by-pixel void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color) { @@ -4476,12 +4713,73 @@ void GuiSetIconScale(int scale) if (scale >= 1) guiIconScale = scale; } +// Get text width considering gui style and icon size (if required) +int GuiGetTextWidth(const char *text) +{ + #if !defined(ICON_TEXT_PADDING) + #define ICON_TEXT_PADDING 4 + #endif + + Vector2 textSize = { 0 }; + int textIconOffset = 0; + + if ((text != NULL) && (text[0] != '\0')) + { + if (text[0] == '#') + { + for (int i = 1; (i < 5) && (text[i] != '\0'); i++) + { + if (text[i] == '#') + { + textIconOffset = i; + break; + } + } + } + + text += textIconOffset; + + // Make sure guiFont is set, GuiGetStyle() initializes it lazynessly + float fontSize = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + + // Custom MeasureText() implementation + if ((guiFont.texture.id > 0) && (text != NULL)) + { + // Get size in bytes of text, considering end of line and line break + int size = 0; + for (int i = 0; i < MAX_LINE_BUFFER_SIZE; i++) + { + if ((text[i] != '\0') && (text[i] != '\n')) size++; + else break; + } + + float scaleFactor = fontSize/(float)guiFont.baseSize; + textSize.y = (float)guiFont.baseSize*scaleFactor; + float glyphWidth = 0.0f; + + for (int i = 0, codepointSize = 0; i < size; i += codepointSize) + { + int codepoint = GetCodepointNext(&text[i], &codepointSize); + int codepointIndex = GetGlyphIndex(guiFont, codepoint); + + if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); + else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); + + textSize.x += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + } + + if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE + ICON_TEXT_PADDING); + } + + return (int)textSize.x; +} + #endif // !RAYGUI_NO_ICONS //---------------------------------------------------------------------------------- // Module Internal Functions Definition //---------------------------------------------------------------------------------- - // Load style from memory // WARNING: Binary files only static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) @@ -4567,7 +4865,7 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) { // Compressed font atlas image data (DEFLATE), it requires DecompressData() int dataUncompSize = 0; - unsigned char *compData = (unsigned char *)RAYGUI_MALLOC(fontImageCompSize); + unsigned char *compData = (unsigned char *)RAYGUI_CALLOC(fontImageCompSize, sizeof(unsigned char)); memcpy(compData, fileDataPtr, fontImageCompSize); fileDataPtr += fontImageCompSize; @@ -4581,7 +4879,7 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) else { // Font atlas image data is not compressed - imFont.data = (unsigned char *)RAYGUI_MALLOC(fontImageUncompSize); + imFont.data = (unsigned char *)RAYGUI_CALLOC(fontImageUncompSize, sizeof(unsigned char)); memcpy(imFont.data, fileDataPtr, fontImageUncompSize); fileDataPtr += fontImageUncompSize; } @@ -4609,7 +4907,7 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) if ((recsDataCompressedSize > 0) && (recsDataCompressedSize != recsDataSize)) { // Recs data is compressed, uncompress it - unsigned char *recsDataCompressed = (unsigned char *)RAYGUI_MALLOC(recsDataCompressedSize); + unsigned char *recsDataCompressed = (unsigned char *)RAYGUI_CALLOC(recsDataCompressedSize, sizeof(unsigned char)); memcpy(recsDataCompressed, fileDataPtr, recsDataCompressedSize); fileDataPtr += recsDataCompressedSize; @@ -4651,7 +4949,7 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) if ((glyphsDataCompressedSize > 0) && (glyphsDataCompressedSize != glyphsDataSize)) { // Glyphs data is compressed, uncompress it - unsigned char *glypsDataCompressed = (unsigned char *)RAYGUI_MALLOC(glyphsDataCompressedSize); + unsigned char *glypsDataCompressed = (unsigned char *)RAYGUI_CALLOC(glyphsDataCompressedSize, sizeof(unsigned char)); memcpy(glypsDataCompressed, fileDataPtr, glyphsDataCompressedSize); fileDataPtr += glyphsDataCompressedSize; @@ -4704,68 +5002,6 @@ static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) } } -// Gui get text width considering icon -static int GetTextWidth(const char *text) -{ - #if !defined(ICON_TEXT_PADDING) - #define ICON_TEXT_PADDING 4 - #endif - - Vector2 textSize = { 0 }; - int textIconOffset = 0; - - if ((text != NULL) && (text[0] != '\0')) - { - if (text[0] == '#') - { - for (int i = 1; (i < 5) && (text[i] != '\0'); i++) - { - if (text[i] == '#') - { - textIconOffset = i; - break; - } - } - } - - text += textIconOffset; - - // Make sure guiFont is set, GuiGetStyle() initializes it lazynessly - float fontSize = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - - // Custom MeasureText() implementation - if ((guiFont.texture.id > 0) && (text != NULL)) - { - // Get size in bytes of text, considering end of line and line break - int size = 0; - for (int i = 0; i < MAX_LINE_BUFFER_SIZE; i++) - { - if ((text[i] != '\0') && (text[i] != '\n')) size++; - else break; - } - - float scaleFactor = fontSize/(float)guiFont.baseSize; - textSize.y = (float)guiFont.baseSize*scaleFactor; - float glyphWidth = 0.0f; - - for (int i = 0, codepointSize = 0; i < size; i += codepointSize) - { - int codepoint = GetCodepointNext(&text[i], &codepointSize); - int codepointIndex = GetGlyphIndex(guiFont, codepoint); - - if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); - else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); - - textSize.x += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - } - } - - if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE + ICON_TEXT_PADDING); - } - - return (int)textSize.x; -} - // Get text bounds considering control bounds static Rectangle GetTextBounds(int control, Rectangle bounds) { @@ -4786,7 +5022,7 @@ static Rectangle GetTextBounds(int control, Rectangle bounds) case SLIDER: case CHECKBOX: case VALUEBOX: - case SPINNER: + case CONTROL11: // TODO: More special cases (label on side): SLIDER, CHECKBOX, VALUEBOX, SPINNER default: { @@ -4832,7 +5068,8 @@ static const char *GetTextIcon(const char *text, int *iconId) } // Get text divided into lines (by line-breaks '\n') -const char **GetTextLines(const char *text, int *count) +// WARNING: It returns pointers to new lines but it does not add NULL ('\0') terminator! +static const char **GetTextLines(const char *text, int *count) { #define RAYGUI_MAX_TEXT_LINES 128 @@ -4936,8 +5173,8 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C float textBoundsWidthOffset = 0.0f; // NOTE: We get text size after icon has been processed - // WARNING: GetTextWidth() also processes text icon to get width! -> Really needed? - int textSizeX = GetTextWidth(lines[i]); + // WARNING: GuiGetTextWidth() also processes text icon to get width! -> Really needed? + int textSizeX = GuiGetTextWidth(lines[i]); // If text requires an icon, add size to measure if (iconId >= 0) @@ -5000,7 +5237,7 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C float textOffsetX = 0.0f; float glyphWidth = 0; - int ellipsisWidth = GetTextWidth("..."); + int ellipsisWidth = GuiGetTextWidth("..."); bool textOverflow = false; for (int c = 0, codepointSize = 0; c < lineSize; c += codepointSize) { @@ -5144,13 +5381,13 @@ static void GuiTooltip(Rectangle controlRec) if ((controlRec.x + textSize.x + 16) > GetScreenWidth()) controlRec.x -= (textSize.x + 16 - controlRec.width); - GuiPanel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, NULL); + GuiPanel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.0f }, NULL); int textPadding = GuiGetStyle(LABEL, TEXT_PADDING); int textAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); GuiSetStyle(LABEL, TEXT_PADDING, 0); GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - GuiLabel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, guiTooltipPtr); + GuiLabel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.0f }, guiTooltipPtr); GuiSetStyle(LABEL, TEXT_ALIGNMENT, textAlignment); GuiSetStyle(LABEL, TEXT_PADDING, textPadding); } @@ -5204,7 +5441,7 @@ static const char **GuiTextSplit(const char *text, char delimiter, int *count, i buffer[i] = '\0'; // Set an end of string at this point counter++; - if (counter > RAYGUI_TEXTSPLIT_MAX_ITEMS) break; + if (counter >= RAYGUI_TEXTSPLIT_MAX_ITEMS) break; } } @@ -5526,10 +5763,10 @@ static Color GetColor(int hexValue) { Color color; - color.r = (unsigned char)(hexValue >> 24) & 0xFF; - color.g = (unsigned char)(hexValue >> 16) & 0xFF; - color.b = (unsigned char)(hexValue >> 8) & 0xFF; - color.a = (unsigned char)hexValue & 0xFF; + color.r = (unsigned char)(hexValue >> 24) & 0xff; + color.g = (unsigned char)(hexValue >> 16) & 0xff; + color.b = (unsigned char)(hexValue >> 8) & 0xff; + color.a = (unsigned char)hexValue & 0xff; return color; } @@ -5562,7 +5799,7 @@ static const char *TextFormat(const char *text, ...) va_list args; va_start(args, text); - vsprintf(buffer, text, args); + vsnprintf(buffer, RAYGUI_TEXTFORMAT_MAX_SIZE, text, args); va_end(args); return buffer; @@ -5731,7 +5968,7 @@ static int GetCodepointNext(const char *text, int *codepointSize) } else if (0xe0 == (0xf0 & ptr[0])) { - // 3 byte UTF-8 codepoint */ + // 3 byte UTF-8 codepoint if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]); *codepointSize = 3; diff --git a/examples/shapes/raygui.h b/examples/shapes/raygui.h index 17ced6ef5..f86247ac4 100644 --- a/examples/shapes/raygui.h +++ b/examples/shapes/raygui.h @@ -77,7 +77,7 @@ * * static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)]; * -* guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB +* guiStyle size is by default: 16*(16 + 8) = 384 int = 384*4 bytes = 1536 bytes = 1.5 KB * * Note that the first set of BASE properties (by default guiStyle[0..15]) belong to the generic style * used for all controls, when any of those base values is set, it is automatically populated to all @@ -141,7 +141,7 @@ * Draw text bounds rectangles for debug * * VERSIONS HISTORY: -* 5.0-dev (2025) Current dev version... +* 5.0 (xx-Nov-2025) ADDED: Support up to 32 controls (v500) * ADDED: guiControlExclusiveMode and guiControlExclusiveRec for exclusive modes * ADDED: GuiValueBoxFloat() * ADDED: GuiDropdonwBox() properties: DROPDOWN_ARROW_HIDDEN, DROPDOWN_ROLL_UP @@ -271,7 +271,7 @@ * 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria * * DEPENDENCIES: -* raylib 5.0 - Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing +* raylib 5.6-dev - Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing * * STANDALONE MODE: * By default raygui depends on raylib mostly for the inputs and the drawing functionality but that dependency can be disabled @@ -1010,28 +1010,28 @@ typedef enum { ICON_SLICING = 231, ICON_MANUAL_CONTROL = 232, ICON_COLLISION = 233, - ICON_234 = 234, - ICON_235 = 235, - ICON_236 = 236, - ICON_237 = 237, - ICON_238 = 238, - ICON_239 = 239, - ICON_240 = 240, - ICON_241 = 241, - ICON_242 = 242, - ICON_243 = 243, - ICON_244 = 244, - ICON_245 = 245, - ICON_246 = 246, - ICON_247 = 247, - ICON_248 = 248, - ICON_249 = 249, + ICON_CIRCLE_ADD = 234, + ICON_CIRCLE_ADD_FILL = 235, + ICON_CIRCLE_WARNING = 236, + ICON_CIRCLE_WARNING_FILL = 237, + ICON_BOX_MORE = 238, + ICON_BOX_MORE_FILL = 239, + ICON_BOX_MINUS = 240, + ICON_BOX_MINUS_FILL = 241, + ICON_UNION = 242, + ICON_INTERSECTION = 243, + ICON_DIFFERENCE = 244, + ICON_SPHERE = 245, + ICON_CYLINDER = 246, + ICON_CONE = 247, + ICON_ELLIPSOID = 248, + ICON_CAPSULE = 249, ICON_250 = 250, ICON_251 = 251, ICON_252 = 252, ICON_253 = 253, ICON_254 = 254, - ICON_255 = 255, + ICON_255 = 255 } GuiIconName; #endif @@ -1078,7 +1078,7 @@ typedef enum { // Check if two rectangles are equal, used to validate a slider bounds as an id #ifndef CHECK_BOUNDS_ID - #define CHECK_BOUNDS_ID(src, dst) ((src.x == dst.x) && (src.y == dst.y) && (src.width == dst.width) && (src.height == dst.height)) + #define CHECK_BOUNDS_ID(src, dst) (((int)src.x == (int)dst.x) && ((int)src.y == (int)dst.y) && ((int)src.width == (int)dst.width) && ((int)src.height == (int)dst.height)) #endif #if !defined(RAYGUI_NO_ICONS) && !defined(RAYGUI_CUSTOM_ICONS) @@ -1341,22 +1341,22 @@ static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = 0x7fe00000, 0x402e4020, 0x43ce5e0a, 0x40504078, 0x438e4078, 0x402e5e0a, 0x7fe04020, 0x00000000, // ICON_SLICING 0x00000000, 0x40027ffe, 0x47c24002, 0x55425d42, 0x55725542, 0x50125552, 0x10105016, 0x00001ff0, // ICON_MANUAL_CONTROL 0x7ffe0000, 0x43c24002, 0x48124422, 0x500a500a, 0x500a500a, 0x44224812, 0x400243c2, 0x00007ffe, // ICON_COLLISION - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_234 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_235 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_236 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_237 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_238 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_239 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_240 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_241 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_242 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_243 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_244 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_245 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_246 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_247 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_248 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_249 + 0x03c00000, 0x10080c30, 0x21842184, 0x4ff24182, 0x41824ff2, 0x21842184, 0x0c301008, 0x000003c0, // ICON_CIRCLE_ADD + 0x03c00000, 0x1ff80ff0, 0x3e7c3e7c, 0x700e7e7e, 0x7e7e700e, 0x3e7c3e7c, 0x0ff01ff8, 0x000003c0, // ICON_CIRCLE_ADD_FILL + 0x03c00000, 0x10080c30, 0x21842184, 0x41824182, 0x40024182, 0x21842184, 0x0c301008, 0x000003c0, // ICON_CIRCLE_WARNING + 0x03c00000, 0x1ff80ff0, 0x3e7c3e7c, 0x7e7e7e7e, 0x7ffe7e7e, 0x3e7c3e7c, 0x0ff01ff8, 0x000003c0, // ICON_CIRCLE_WARNING_FILL + 0x00000000, 0x10041ffc, 0x10841004, 0x13e41084, 0x10841084, 0x10041004, 0x00001ffc, 0x00000000, // ICON_BOX_MORE + 0x00000000, 0x1ffc1ffc, 0x1f7c1ffc, 0x1c1c1f7c, 0x1f7c1f7c, 0x1ffc1ffc, 0x00001ffc, 0x00000000, // ICON_BOX_MORE_FILL + 0x00000000, 0x1ffc1ffc, 0x1ffc1ffc, 0x1c1c1ffc, 0x1ffc1ffc, 0x1ffc1ffc, 0x00001ffc, 0x00000000, // ICON_BOX_MINUS + 0x00000000, 0x10041ffc, 0x10041004, 0x13e41004, 0x10041004, 0x10041004, 0x00001ffc, 0x00000000, // ICON_BOX_MINUS_FILL + 0x07fe0000, 0x055606aa, 0x7ff606aa, 0x55766eba, 0x55766eaa, 0x55606ffe, 0x55606aa0, 0x00007fe0, // ICON_UNION + 0x07fe0000, 0x04020402, 0x7fe20402, 0x456246a2, 0x456246a2, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_INTERSECTION + 0x07fe0000, 0x055606aa, 0x7ff606aa, 0x4436442a, 0x4436442a, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_DIFFERENCE + 0x03c00000, 0x10080c30, 0x20042004, 0x60064002, 0x47e2581a, 0x20042004, 0x0c301008, 0x000003c0, // ICON_SPHERE + 0x03e00000, 0x08080410, 0x0c180808, 0x08080be8, 0x08080808, 0x08080808, 0x04100808, 0x000003e0, // ICON_CYLINDER + 0x00800000, 0x01400140, 0x02200220, 0x04100410, 0x08080808, 0x1c1c13e4, 0x08081004, 0x000007f0, // ICON_CONE + 0x00000000, 0x07e00000, 0x20841918, 0x40824082, 0x40824082, 0x19182084, 0x000007e0, 0x00000000, // ICON_ELLIPSOID + 0x00000000, 0x00000000, 0x20041ff8, 0x40024002, 0x40024002, 0x1ff82004, 0x00000000, 0x00000000, // ICON_CAPSULE 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_250 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_251 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_252 @@ -1743,7 +1743,7 @@ int GuiPanel(Rectangle bounds, const char *text) // NOTE: Using GuiToggle() for the TABS int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) { - #define RAYGUI_TABBAR_ITEM_WIDTH 160 + #define RAYGUI_TABBAR_ITEM_WIDTH 148 int result = -1; //GuiState state = guiState; @@ -1776,12 +1776,12 @@ int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) if (i == (*active)) { toggle = true; - GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); + GuiToggle(tabBounds, text[i], &toggle); } else { toggle = false; - GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); + GuiToggle(tabBounds, text[i], &toggle); if (toggle) *active = i; } @@ -2590,7 +2590,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) int pasteLength = 0; int pasteCodepoint; int pasteCodepointSize; - + // Count how many codepoints to copy, stopping at the first unwanted control character while (true) { @@ -2599,7 +2599,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) if (!(multiline && (pasteCodepoint == (int)'\n')) && !(pasteCodepoint >= 32)) break; pasteLength += pasteCodepointSize; } - + if (pasteLength > 0) { // Move forward data from cursor position @@ -2662,7 +2662,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) while (offset < textLength) { if (!isspace(nextCodepoint & 0xff)) break; - + offset += nextCodepointSize; accCodepointSize += nextCodepointSize; nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); @@ -2673,11 +2673,11 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) textLength -= accCodepointSize; } - + else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && autoCursorShouldTrigger))) { // Delete single codepoint from text, after current cursor position - + int nextCodepointSize = 0; GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); @@ -2704,7 +2704,7 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) offset -= prevCodepointSize; accCodepointSize += prevCodepointSize; } - + // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace) // Not using isalnum() since it only works on ASCII characters bool puctuation = ispunct(prevCodepoint & 0xff); @@ -2723,11 +2723,11 @@ int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) textLength -= accCodepointSize; textBoxCursorIndex -= accCodepointSize; } - + else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && autoCursorShouldTrigger))) { // Delete single codepoint from text, before current cursor position - + int prevCodepointSize = 0; GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); @@ -3033,7 +3033,7 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in int result = 0; GuiState state = guiState; - char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; + char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = { 0 }; snprintf(textValue, RAYGUI_VALUEBOX_MAX_CHARS + 1, "%i", *value); Rectangle textBounds = { 0 }; @@ -3051,7 +3051,6 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); - bool valueHasChanged = false; if (editMode) @@ -3070,7 +3069,7 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in keyCount--; valueHasChanged = true; } - else if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS -1) + else if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) { if (keyCount == 0) { @@ -3087,30 +3086,26 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in } } - // Only allow keys in range [48..57] - if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + // Add new digit to text value + if ((keyCount >= 0) && (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) && (GuiGetTextWidth(textValue) < bounds.width)) { - if (GuiGetTextWidth(textValue) < bounds.width) + int key = GetCharPressed(); + + // Only allow keys in range [48..57] + if ((key >= 48) && (key <= 57)) { - int key = GetCharPressed(); - if ((key >= 48) && (key <= 57)) - { - textValue[keyCount] = (char)key; - keyCount++; - valueHasChanged = true; - } + textValue[keyCount] = (char)key; + keyCount++; + valueHasChanged = true; } } // Delete text - if (keyCount > 0) + if ((keyCount > 0) && IsKeyPressed(KEY_BACKSPACE)) { - if (IsKeyPressed(KEY_BACKSPACE)) - { - keyCount--; - textValue[keyCount] = '\0'; - valueHasChanged = true; - } + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; } if (valueHasChanged) *value = TextToInteger(textValue); @@ -3224,9 +3219,9 @@ int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float textValue[1] = '\0'; keyCount++; } - + for (int i = keyCount; i > -1; i--) textValue[i + 1] = textValue[i]; - + textValue[0] = '-'; keyCount++; valueHasChanged = true; From c7c6aaf156426e5e955b67135327672471af1b33 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 21:12:25 +0100 Subject: [PATCH 111/430] Update examples_testing_windows.md --- tools/rexm/reports/examples_testing_windows.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tools/rexm/reports/examples_testing_windows.md b/tools/rexm/reports/examples_testing_windows.md index 37c50a486..b68e5c0be 100644 --- a/tools/rexm/reports/examples_testing_windows.md +++ b/tools/rexm/reports/examples_testing_windows.md @@ -17,18 +17,7 @@ Example automated testing elements validated: | **EXAMPLE NAME** | [CWARN] | [LWARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | |:---------------------------------|:-------:|:-------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| | core_input_actions | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| core_directory_files | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| core_clipboard_text | 5 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| core_compute_hash | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_recursive_tree | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_ring_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_circle_sector_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_rounded_rectangle_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_splines_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_digital_clock | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_triangle_strip | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_pie_chart | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_math_sine_cosine | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_font_loading | 0 | 10 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_font_sdf | 0 | 73 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_codepoints_loading | 0 | 1 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | From e6ef99275a575ff5ad5d923609326573c74b0789 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 21:14:48 +0100 Subject: [PATCH 112/430] Update shapes_digital_clock.c --- examples/shapes/shapes_digital_clock.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/shapes/shapes_digital_clock.c b/examples/shapes/shapes_digital_clock.c index 77b020704..5f55fa836 100644 --- a/examples/shapes/shapes_digital_clock.c +++ b/examples/shapes/shapes_digital_clock.c @@ -284,11 +284,13 @@ static void DrawDisplaySegment(Vector2 center, int length, int thick, bool verti if (!vertical) { // Horizontal segment points - // 3___________________________5 - // / \ - // /1 x 6\ - // \ / - // \2___________________________4/ + /* + 3___________________________5 + / \ + /1 x 6\ + \ / + \2___________________________4/ + */ Vector2 segmentPointsH[6] = { (Vector2){ center.x - length/2.0f - thick/2.0f, center.y }, // Point 1 (Vector2){ center.x - length/2.0f, center.y + thick/2.0f }, // Point 2 From 48496e230767280307f14dd4ab01e42fff33d06a Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 21:18:32 +0100 Subject: [PATCH 113/430] Update core_input_actions.c --- examples/core/core_input_actions.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/core/core_input_actions.c b/examples/core/core_input_actions.c index aff356087..cbf7e0e92 100644 --- a/examples/core/core_input_actions.c +++ b/examples/core/core_input_actions.c @@ -71,6 +71,7 @@ int main(void) // Set default actions char actionSet = 0; SetActionsDefault(); + bool releaseAction = false; Vector2 position = (Vector2){ 400.0f, 200.0f }; Vector2 size = (Vector2){ 40.0f, 40.0f }; @@ -83,7 +84,8 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - gamepadIndex = 0; // set this to gamepad being checked + gamepadIndex = 0; // Set gamepad being checked + if (IsActionDown(ACTION_UP)) position.y -= 2; if (IsActionDown(ACTION_DOWN)) position.y += 2; if (IsActionDown(ACTION_LEFT)) position.x -= 2; @@ -93,6 +95,10 @@ int main(void) position.x = (screenWidth-size.x)/2; position.y = (screenHeight-size.y)/2; } + + // Register release action for one frame + releaseAction = false; + if (IsActionReleased(ACTION_FIRE)) releaseAction = true; // Switch control scheme by pressing TAB if (IsKeyPressed(KEY_TAB)) @@ -109,7 +115,7 @@ int main(void) ClearBackground(GRAY); - DrawRectangleV(position, size, RED); + DrawRectangleV(position, size, releaseAction? BLUE : RED); DrawText((actionSet == 0)? "Current input set: WASD (default)" : "Current input set: Cursor", 10, 10, 20, WHITE); DrawText("Use TAB key to toggles Actions keyset", 10, 50, 20, GREEN); From 83a167ca3f90ee61f30193d72252a7ff8204d1d4 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 21:19:55 +0100 Subject: [PATCH 114/430] Update text_inline_styling.c --- examples/text/text_inline_styling.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/text/text_inline_styling.c b/examples/text/text_inline_styling.c index b221c7e6f..0e5e8091f 100644 --- a/examples/text/text_inline_styling.c +++ b/examples/text/text_inline_styling.c @@ -210,7 +210,7 @@ static Vector2 MeasureTextStyled(Font font, const char *text, float fontSize, fl if ((font.texture.id == 0) || (text == NULL) || (text[0] == '\0')) return textSize; // Security check int textLen = TextLength(text); // Get size in bytes of text - float textLineSpacing = fontSize*1.5f; + //float textLineSpacing = fontSize*1.5f; // Not used... float textWidth = 0.0f; float textHeight = fontSize; From 1b6303b9007cfaa48564f28255da1c1b8f9b5a2c Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 21:20:05 +0100 Subject: [PATCH 115/430] Update examples_testing_windows.md --- tools/rexm/reports/examples_testing_windows.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/rexm/reports/examples_testing_windows.md b/tools/rexm/reports/examples_testing_windows.md index b68e5c0be..9a28d47ca 100644 --- a/tools/rexm/reports/examples_testing_windows.md +++ b/tools/rexm/reports/examples_testing_windows.md @@ -16,11 +16,8 @@ Example automated testing elements validated: ``` | **EXAMPLE NAME** | [CWARN] | [LWARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | |:---------------------------------|:-------:|:-------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| -| core_input_actions | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_digital_clock | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_font_loading | 0 | 10 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_font_sdf | 0 | 73 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_codepoints_loading | 0 | 1 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| text_inline_styling | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | models_animation_playing | 0 | 1 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | From 57e22d5fa0781211815a55d27142a22b37b152aa Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 21:26:26 +0100 Subject: [PATCH 116/430] Update rtext.c --- src/rtext.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rtext.c b/src/rtext.c index 1705e5b49..1c70aedc1 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -724,7 +724,8 @@ GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSiz stbtt_GetCodepointHMetrics(&fontInfo, cp, &glyphs[k].advanceX, NULL); glyphs[k].advanceX = (int)((float)glyphs[k].advanceX*scaleFactor); - if (cpHeight > fontSize) TRACELOG(LOG_WARNING, "FONT: [0x%04x] Glyph height is bigger than requested font size: %i > %i", cp, cpHeight, (int)fontSize); + // WARNING: If requested SDF font, sdf-glyph height is definitely bigger than fontSize due to FONT_SDF_CHAR_PADDING + if ((type != FONT_SDF) && (cpHeight > fontSize)) TRACELOG(LOG_WARNING, "FONT: [0x%04x] Glyph height is bigger than requested font size: %i > %i", cp, cpHeight, (int)fontSize); // Load glyph image glyphs[k].image.width = cpWidth; From a24e65d8e17224925ae9ffdb27cc498fb27f6719 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 21:26:54 +0100 Subject: [PATCH 117/430] Update examples_testing_windows.md --- tools/rexm/reports/examples_testing_windows.md | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/rexm/reports/examples_testing_windows.md b/tools/rexm/reports/examples_testing_windows.md index 9a28d47ca..8b8804bda 100644 --- a/tools/rexm/reports/examples_testing_windows.md +++ b/tools/rexm/reports/examples_testing_windows.md @@ -17,7 +17,6 @@ Example automated testing elements validated: | **EXAMPLE NAME** | [CWARN] | [LWARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | |:---------------------------------|:-------:|:-------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| | text_font_loading | 0 | 10 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| text_font_sdf | 0 | 73 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_codepoints_loading | 0 | 1 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | models_animation_playing | 0 | 1 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | From f51204821a371731a38aa3306409bfc3bd2f0dbd Mon Sep 17 00:00:00 2001 From: Serhii Zasenko Date: Tue, 18 Nov 2025 22:27:50 +0200 Subject: [PATCH 118/430] Add vibration test button to core_input_gamepad (#5362) --- examples/core/core_input_gamepad.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/examples/core/core_input_gamepad.c b/examples/core/core_input_gamepad.c index abfdb11c1..9e1bbc386 100644 --- a/examples/core/core_input_gamepad.c +++ b/examples/core/core_input_gamepad.c @@ -72,6 +72,8 @@ int main(void) if (IsKeyPressed(KEY_LEFT) && gamepad > 0) gamepad--; if (IsKeyPressed(KEY_RIGHT)) gamepad++; + Vector2 mousePosition = GetMousePosition(); + bool mousePressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); if (IsGamepadAvailable(gamepad)) { @@ -262,6 +264,14 @@ int main(void) DrawText(TextFormat("AXIS %i: %.02f", i, GetGamepadAxisMovement(gamepad, i)), 20, 70 + 20*i, 10, DARKGRAY); } + Rectangle vibrateButton = (Rectangle){10, 70 + 20*GetGamepadAxisCount(gamepad) + 20, 75, 10}; + if (mousePressed && CheckCollisionPointRec(mousePosition, vibrateButton)){ + SetGamepadVibration(gamepad, 1.0, 1.0, 1.0); + } + DrawRectangleRec(vibrateButton, SKYBLUE); + + DrawText("VIBRATE", vibrateButton.x + 14, vibrateButton.y + 1, 10, DARKGRAY); + if (GetGamepadButtonPressed() != GAMEPAD_BUTTON_UNKNOWN) DrawText(TextFormat("DETECTED BUTTON: %i", GetGamepadButtonPressed()), 10, 430, 10, RED); else DrawText("DETECTED BUTTON: NONE", 10, 430, 10, GRAY); } From d5e8ee77b1a5633d1fff7a8e3836107540ace0cc Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 18 Nov 2025 21:32:20 +0100 Subject: [PATCH 119/430] Update core_input_gamepad.c --- examples/core/core_input_gamepad.c | 42 ++++++++++++------------------ 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/examples/core/core_input_gamepad.c b/examples/core/core_input_gamepad.c index 9e1bbc386..b64e0c1a0 100644 --- a/examples/core/core_input_gamepad.c +++ b/examples/core/core_input_gamepad.c @@ -50,6 +50,8 @@ int main(void) const float rightStickDeadzoneY = 0.1f; const float leftTriggerDeadzone = -0.9f; const float rightTriggerDeadzone = -0.9f; + + Rectangle vibrateButton = { 0 }; SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- @@ -61,7 +63,12 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - // ... + if (IsKeyPressed(KEY_LEFT) && gamepad > 0) gamepad--; + if (IsKeyPressed(KEY_RIGHT)) gamepad++; + Vector2 mousePosition = GetMousePosition(); + + vibrateButton = (Rectangle){ 10, 70 + 20*GetGamepadAxisCount(gamepad) + 20, 75, 24 }; + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && CheckCollisionPointRec(mousePosition, vibrateButton)) SetGamepadVibration(gamepad, 1.0, 1.0, 1.0); //---------------------------------------------------------------------------------- // Draw @@ -70,11 +77,6 @@ int main(void) ClearBackground(RAYWHITE); - if (IsKeyPressed(KEY_LEFT) && gamepad > 0) gamepad--; - if (IsKeyPressed(KEY_RIGHT)) gamepad++; - Vector2 mousePosition = GetMousePosition(); - bool mousePressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); - if (IsGamepadAvailable(gamepad)) { DrawText(TextFormat("GP%d: %s", gamepad, GetGamepadName(gamepad)), 10, 10, 10, BLACK); @@ -95,7 +97,8 @@ int main(void) if (leftTrigger < leftTriggerDeadzone) leftTrigger = -1.0f; if (rightTrigger < rightTriggerDeadzone) rightTrigger = -1.0f; - if (TextFindIndex(TextToLower(GetGamepadName(gamepad)), XBOX_ALIAS_1) > -1 || TextFindIndex(TextToLower(GetGamepadName(gamepad)), XBOX_ALIAS_2) > -1) + if ((TextFindIndex(TextToLower(GetGamepadName(gamepad)), XBOX_ALIAS_1) > -1) || + (TextFindIndex(TextToLower(GetGamepadName(gamepad)), XBOX_ALIAS_2) > -1)) { DrawTexture(texXboxPad, 0, 0, DARKGRAY); @@ -127,16 +130,14 @@ int main(void) if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_THUMB)) leftGamepadColor = RED; DrawCircle(259, 152, 39, BLACK); DrawCircle(259, 152, 34, LIGHTGRAY); - DrawCircle(259 + (int)(leftStickX*20), - 152 + (int)(leftStickY*20), 25, leftGamepadColor); + DrawCircle(259 + (int)(leftStickX*20), 152 + (int)(leftStickY*20), 25, leftGamepadColor); // Draw axis: right joystick Color rightGamepadColor = BLACK; if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_THUMB)) rightGamepadColor = RED; DrawCircle(461, 237, 38, BLACK); DrawCircle(461, 237, 33, LIGHTGRAY); - DrawCircle(461 + (int)(rightStickX*20), - 237 + (int)(rightStickY*20), 25, rightGamepadColor); + DrawCircle(461 + (int)(rightStickX*20), 237 + (int)(rightStickY*20), 25, rightGamepadColor); // Draw axis: left-right triggers DrawRectangle(170, 30, 15, 70, GRAY); @@ -179,16 +180,14 @@ int main(void) if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_THUMB)) leftGamepadColor = RED; DrawCircle(319, 255, 35, BLACK); DrawCircle(319, 255, 31, LIGHTGRAY); - DrawCircle(319 + (int)(leftStickX*20), - 255 + (int)(leftStickY*20), 25, leftGamepadColor); + DrawCircle(319 + (int)(leftStickX*20), 255 + (int)(leftStickY*20), 25, leftGamepadColor); // Draw axis: right joystick Color rightGamepadColor = BLACK; if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_THUMB)) rightGamepadColor = RED; DrawCircle(475, 255, 35, BLACK); DrawCircle(475, 255, 31, LIGHTGRAY); - DrawCircle(475 + (int)(rightStickX*20), - 255 + (int)(rightStickY*20), 25, rightGamepadColor); + DrawCircle(475 + (int)(rightStickX*20), 255 + (int)(rightStickY*20), 25, rightGamepadColor); // Draw axis: left-right triggers DrawRectangle(169, 48, 15, 70, GRAY); @@ -238,23 +237,20 @@ int main(void) if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_LEFT_THUMB)) leftGamepadColor = RED; DrawCircle(345, 260, 40, BLACK); DrawCircle(345, 260, 35, LIGHTGRAY); - DrawCircle(345 + (int)(leftStickX*20), - 260 + (int)(leftStickY*20), 25, leftGamepadColor); + DrawCircle(345 + (int)(leftStickX*20), 260 + (int)(leftStickY*20), 25, leftGamepadColor); // Draw axis: right joystick Color rightGamepadColor = BLACK; if (IsGamepadButtonDown(gamepad, GAMEPAD_BUTTON_RIGHT_THUMB)) rightGamepadColor = RED; DrawCircle(465, 260, 40, BLACK); DrawCircle(465, 260, 35, LIGHTGRAY); - DrawCircle(465 + (int)(rightStickX*20), - 260 + (int)(rightStickY*20), 25, rightGamepadColor); + DrawCircle(465 + (int)(rightStickX*20), 260 + (int)(rightStickY*20), 25, rightGamepadColor); // Draw axis: left-right triggers DrawRectangle(151, 110, 15, 70, GRAY); DrawRectangle(644, 110, 15, 70, GRAY); DrawRectangle(151, 110, 15, (int)(((1 + leftTrigger)/2)*70), RED); DrawRectangle(644, 110, 15, (int)(((1 + rightTrigger)/2)*70), RED); - } DrawText(TextFormat("DETECTED AXIS [%i]:", GetGamepadAxisCount(gamepad)), 10, 50, 10, MAROON); @@ -264,12 +260,8 @@ int main(void) DrawText(TextFormat("AXIS %i: %.02f", i, GetGamepadAxisMovement(gamepad, i)), 20, 70 + 20*i, 10, DARKGRAY); } - Rectangle vibrateButton = (Rectangle){10, 70 + 20*GetGamepadAxisCount(gamepad) + 20, 75, 10}; - if (mousePressed && CheckCollisionPointRec(mousePosition, vibrateButton)){ - SetGamepadVibration(gamepad, 1.0, 1.0, 1.0); - } + // Draw vibrate button DrawRectangleRec(vibrateButton, SKYBLUE); - DrawText("VIBRATE", vibrateButton.x + 14, vibrateButton.y + 1, 10, DARKGRAY); if (GetGamepadButtonPressed() != GAMEPAD_BUTTON_UNKNOWN) DrawText(TextFormat("DETECTED BUTTON: %i", GetGamepadButtonPressed()), 10, 430, 10, RED); From 3f92c396a009ecb51a5efab378802851f805cd76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20=E2=9D=A4=EF=B8=8F?= Date: Wed, 19 Nov 2025 02:56:32 -0500 Subject: [PATCH 120/430] Fixed typo (#5364) --- src/rlgl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rlgl.h b/src/rlgl.h index 99e9037d5..6cdf8a317 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -1252,7 +1252,7 @@ void rlPushMatrix(void) RLGL.State.stackCounter++; } -// Pop lattest inserted matrix from RLGL.State.stack +// Pop latest inserted matrix from RLGL.State.stack void rlPopMatrix(void) { if (RLGL.State.stackCounter > 0) From e2233acdb0d51d5f11f63d1b8550a766a962aac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adi=20=C4=8Cau=C5=A1evi=C4=87?= <31798801+ChocolateChipKookie@users.noreply.github.com> Date: Wed, 19 Nov 2025 08:58:43 +0100 Subject: [PATCH 121/430] feat: Optimize ImageClearBackground and ImageDrawRectangleRec with doubling strategy (#5363) --- src/rtextures.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/rtextures.c b/src/rtextures.c index ddaee2939..00554e418 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -3341,11 +3341,14 @@ void ImageClearBackground(Image *dst, Color color) unsigned char *pSrcPixel = (unsigned char *)dst->data; int bytesPerPixel = GetPixelDataSize(1, 1, dst->format); + int totalPixels = dst->width * dst->height; - // Repeat the first pixel data throughout the image - for (int i = 1; i < dst->width*dst->height; i++) + // Repeat the first pixel data throughout the image, + // doubling the pixels copied on each iteration + for (int i = 1; i < totalPixels; i *= 2) { - memcpy(pSrcPixel + i*bytesPerPixel, pSrcPixel, bytesPerPixel); + int pixelsToCopy = MIN(i, totalPixels - i); + memcpy(pSrcPixel + i * bytesPerPixel, pSrcPixel, pixelsToCopy * bytesPerPixel); } } @@ -3724,9 +3727,10 @@ void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color) unsigned char *pSrcPixel = (unsigned char *)dst->data + bytesOffset; // Repeat the first pixel data throughout the row - for (int x = 1; x < (int)rec.width; x++) + for (int x = 1; x < (int)rec.width; x *= 2) { - memcpy(pSrcPixel + x*bytesPerPixel, pSrcPixel, bytesPerPixel); + int pixelsToCopy = MIN(x, (int)rec.width - x); + memcpy(pSrcPixel + x*bytesPerPixel, pSrcPixel, pixelsToCopy * bytesPerPixel); } // Repeat the first row data for all other rows From 8081d2bd076ed39dbe9f32c1dae6c82ee78bd9e4 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 09:34:13 +0100 Subject: [PATCH 122/430] REDESIGNED: example: `shapes_kaleidoscope`, store lines #5361 This redesign stores lines in Update and draws stored lines in Draw, instead of previous approach of drawing directly to framebuffer with no cleaning. This approach allows some interesting features like line draw replay or reversing. --- examples/shapes/shapes_kaleidoscope.c | 77 +++++++++++++++++++-------- 1 file changed, 56 insertions(+), 21 deletions(-) diff --git a/examples/shapes/shapes_kaleidoscope.c b/examples/shapes/shapes_kaleidoscope.c index 07c96344c..7eeadb8aa 100644 --- a/examples/shapes/shapes_kaleidoscope.c +++ b/examples/shapes/shapes_kaleidoscope.c @@ -11,13 +11,26 @@ * 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) 2025 Hugo ARNAL (@hugoarnal) +* Copyright (c) 2025 Hugo ARNAL (@hugoarnal) and Ramon Santamaria (@raysan5) * ********************************************************************************************/ #include "raylib.h" + #include "raymath.h" +#define MAX_DRAW_LINES 8192 + +// Line data type +typedef struct { + Vector2 start; + Vector2 end; +} Line; + +// Lines array as a global static variable to be stored +// in heap and avoid potential stack overflow (on Web platform) +static Line lines[MAX_DRAW_LINES] = { 0 }; + //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ @@ -30,22 +43,24 @@ int main(void) InitWindow(screenWidth, screenHeight, "raylib [shapes] example - kaleidoscope"); + // Line drawing properties int symmetry = 6; float angle = 360.0f/(float)symmetry; float thickness = 3.0f; + Vector2 mousePos = { 0 }; Vector2 prevMousePos = { 0 }; - - SetTargetFPS(60); - ClearBackground(BLACK); - + Vector2 scaleVector = { 1.0f, -1.0f }; Vector2 offset = { (float)screenWidth/2.0f, (float)screenHeight/2.0f }; + Camera2D camera = { 0 }; camera.target = (Vector2){ 0 }; camera.offset = offset; camera.rotation = 0.0f; camera.zoom = 1.0f; + + int lineCounter = 0; - Vector2 scaleVector = { 1.0f, -1.0f }; + SetTargetFPS(20); //-------------------------------------------------------------------------------------- // Main game loop @@ -53,38 +68,58 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - Vector2 mousePos = GetMousePosition(); + prevMousePos = mousePos; + mousePos = GetMousePosition(); + Vector2 lineStart = Vector2Subtract(mousePos, offset); Vector2 lineEnd = Vector2Subtract(prevMousePos, offset); + + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + for (int s = 0; (s < symmetry) && (lineCounter < (MAX_DRAW_LINES - 1)); s++) + { + lineStart = Vector2Rotate(lineStart, angle*DEG2RAD); + lineEnd = Vector2Rotate(lineEnd, angle*DEG2RAD); + + // Store mouse line + lines[lineCounter].start = lineStart; + lines[lineCounter].end = lineEnd; + + // Store reflective line + lines[lineCounter + 1].start = Vector2Multiply(lineStart, scaleVector); + lines[lineCounter + 1].end = Vector2Multiply(lineEnd, scaleVector); + + lineCounter += 2; + } + } //---------------------------------------------------------------------------------- // Draw //---------------------------------------------------------------------------------- BeginDrawing(); + + ClearBackground(RAYWHITE); + BeginMode2D(camera); - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { - for (int i = 0; i < symmetry; i++) { - lineStart = Vector2Rotate(lineStart, angle*DEG2RAD); - lineEnd = Vector2Rotate(lineEnd, angle*DEG2RAD); - - DrawLineEx(lineStart, lineEnd, thickness, WHITE); - - Vector2 reflectLineStart = Vector2Multiply(lineStart, scaleVector); - Vector2 reflectLineEnd = Vector2Multiply(lineEnd, scaleVector); - - DrawLineEx(reflectLineStart, reflectLineEnd, thickness, WHITE); + for (int s = 0; s < symmetry; s++) + { + for (int i = 0; i < lineCounter; i += 2) + { + DrawLineEx(lines[i].start, lines[i].end, thickness, BLACK); + DrawLineEx(lines[i + 1].start, lines[i + 1].end, thickness, BLACK); } } - - prevMousePos = mousePos; EndMode2D(); + + DrawText(TextFormat("LINES: %i/%i", lineCounter, MAX_DRAW_LINES), 10, screenHeight - 30, 20, MAROON); + DrawFPS(10, 10); + EndDrawing(); //---------------------------------------------------------------------------------- } // De-Initialization //-------------------------------------------------------------------------------------- - CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- From 33cee1146c22e0ef45885d438aec5183845a4e4a Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 09:54:54 +0100 Subject: [PATCH 123/430] REXM: REVIEWED: Automated testing for Web --- tools/rexm/rexm.c | 69 ++++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 20ceadb91..20bf120ce 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1491,6 +1491,9 @@ int main(int argc, char *argv[]) LOG("INFO: [%i/%i] Testing example: [%s]\n", i + 1, exBuildListCount, exName); + // Create directory for logs (build and run logs) + MakeDirectory(TextFormat("%s/%s/logs", exBasePath, exCategory)); + // Steps to follow // STEP 1: Load example.c and replace required code to inject basic testing code: frames to run // OPTION 1: Code injection required multiple changes for testing but it does not require raylib changes! @@ -1510,7 +1513,7 @@ int main(int argc, char *argv[]) TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName)); char *srcText = LoadFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); -//#define BUILD_TESTING_WEB +#define BUILD_TESTING_WEB #if defined(BUILD_TESTING_WEB) static const char *mainReplaceText = "#include \n" @@ -1557,13 +1560,14 @@ int main(int argc, char *argv[]) // Build: raylib.com/examples//_example_name.data // Build: raylib.com/examples//_example_name.wasm // Build: raylib.com/examples//_example_name.js -#if defined(_WIN32) + #if defined(_WIN32) LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: Win32)\n", exName); - system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B > %s/%s/logs/%s.build.log 2>&1", exBasePath, exCategory, exName)); -#else + system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B > %s/%s/logs/%s.build.log 2>&1", + exBasePath, exCategory, exName, exBasePath, exCategory, exName)); + #else LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: POSIX)\n", exName); system(TextFormat("make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exCategory, exName)); -#endif + #endif // Restore original source code before continue FileCopy(TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName), TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); @@ -1571,7 +1575,7 @@ int main(int argc, char *argv[]) // STEP 3: Run example on browser ChangeDirectory(TextFormat("%s/%s", exBasePath, exCategory)); - system("start python -m http.server 8080"); // TODO: Init localhost just once! + if (i == 0) system("start python -m http.server 8080"); // TODO: Init localhost just once! system(TextFormat("start explorer \"http:\\localhost:8080/%s.html", exName)); // NOTE: Example .log is automatically downloaded into system Downloads directory on browser-example exectution @@ -1595,22 +1599,20 @@ int main(int argc, char *argv[]) SaveFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName), srcTextUpdated[2]); for (int i = 0; i < 3; i++) { MemFree(srcTextUpdated[i]); srcTextUpdated[i] = NULL; } - MakeDirectory(TextFormat("%s/%s/logs", exBasePath, exCategory)); - // STEP 2: Build example for DESKTOP platform -#if defined(_WIN32) + #if defined(_WIN32) // Set required environment variables //putenv(TextFormat("RAYLIB_DIR=%s\\..", exBasePath)); _putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin"); //putenv("MAKE=mingw32-make"); //ChangeDirectory(exBasePath); -#endif + #endif // Build example for PLATFORM_DESKTOP -#if defined(_WIN32) + #if defined(_WIN32) LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: Win32)\n", exName); system(TextFormat("mingw32-make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B > %s/%s/logs/%s.build.log 2>&1", exBasePath, exCategory, exName, exBasePath, exCategory, exName)); -#else + #else LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: POSIX)\n", exName); system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName)); #endif @@ -1623,7 +1625,7 @@ int main(int argc, char *argv[]) // NOTE: Not easy to retrieve process return value from system(), it's platform dependant ChangeDirectory(TextFormat("%s/%s", exBasePath, exCategory)); system(TextFormat("%s --frames 2 > logs/%s.log", exName, exName)); - +#endif // STEP 4: Load and validate log info //--------------------------------------------------------------------------------------------- // Load .build.log to check for compilation warnings @@ -1640,7 +1642,11 @@ int main(int argc, char *argv[]) UnloadTextLines(exTestBuildLogLines, exTestBuildLogLinesCount); UnloadFileText(exTestBuildLog); +#if defined(BUILD_TESTING_WEB) + char *exTestLog = LoadFileText(TextFormat("C:/Users/raysa/Downloads/%s.log", exName)); +#else char *exTestLog = LoadFileText(TextFormat("%s/%s/logs/%s.log", exBasePath, exCategory, exName)); +#endif int exTestLogLinesCount = 0; char **exTestLogLines = LoadTextLines(exTestLog, &exTestLogLinesCount); @@ -1670,11 +1676,27 @@ int main(int argc, char *argv[]) UnloadTextLines(exTestLogLines, exTestLogLinesCount); UnloadFileText(exTestLog); //--------------------------------------------------------------------------------------------- -#endif } // STEP 5: Generate testing report/table with results (.md) //----------------------------------------------------------------------------------------------------- +#if defined(BUILD_TESTING_WEB) + const char *osName = "Web"; +#else + #if defined(PLATFORM_DRM) + const char *osName = "DRM"; + #elif defined(PLATFORM_DESKTOP) + #if defined(_WIN32) + const char *osName = "Windows"; + #elif defined(__linux__) + const char *osName = "Linux"; + #elif defined(__FreeBSD__) + const char *osName = "FreeBSD"; + #elif defined(__APPLE__) + const char *osName = "macOS"; + #endif // Desktop OSs + #endif +#endif /* Columns: - [CWARN] : Compilation WARNING messages @@ -1697,7 +1719,7 @@ int main(int argc, char *argv[]) int repIndex = 0; repIndex += sprintf(report + repIndex, "# EXAMPLES COLLECTION - TESTING REPORT\n\n"); - repIndex += sprintf(report + repIndex, "## Tested Platform: Windows\n\n"); + repIndex += sprintf(report + repIndex, TextFormat("## Tested Platform: %s\n\n", osName)); repIndex += sprintf(report + repIndex, "```\nExample automated testing elements validated:\n"); repIndex += sprintf(report + repIndex, " - [CWARN] : Compilation WARNING messages\n"); @@ -1742,22 +1764,7 @@ int main(int argc, char *argv[]) repIndex += sprintf(report + repIndex, "\n"); -#if defined(PLATFORM_DRM) - const char *osName = "drm"; -#elif defined(PLATFORM_WEB) - const char *osName = "web"; -#elif defined(PLATFORM_DESKTOP) - #if defined(_WIN32) - const char *osName = "windows"; - #elif defined(__linux__) - const char *osName = "linux"; - #elif defined(__FreeBSD__) - const char *osName = "freebsd"; - #elif defined(__APPLE__) - const char *osName = "macos"; - #endif // Desktop OSs -#endif - SaveFileText(TextFormat("%s/../tools/rexm/reports/examples_testing_%s.md", exBasePath, osName), report); + SaveFileText(TextFormat("%s/../tools/rexm/reports/examples_testing_%s.md", exBasePath, TextToLower(osName)), report); RL_FREE(report); //----------------------------------------------------------------------------------------------------- From ec828071ef32d8d9ad62d16708b4bd9f25c5a3cb Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 10:04:01 +0100 Subject: [PATCH 124/430] Update rtext.c --- src/rtext.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/rtext.c b/src/rtext.c index 1c70aedc1..9c627559c 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1452,6 +1452,10 @@ Rectangle GetGlyphAtlasRec(Font font, int codepoint) // NOTE: Returned lines end with null terminator '\0' char **LoadTextLines(const char *text, int *count) { + char **lines = NULL; + + if (text == NULL) { *count = 0; return lines; } + int lineCount = 1; int textSize = (int)strlen(text); From f21c1cc6ae4c74ef8cb3a0da8e7a10493a69df71 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 10:08:42 +0100 Subject: [PATCH 125/430] Update rtext.c --- src/rtext.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rtext.c b/src/rtext.c index 9c627559c..52938e3d2 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1459,13 +1459,13 @@ char **LoadTextLines(const char *text, int *count) int lineCount = 1; int textSize = (int)strlen(text); - // Text pass to get required line count + // First text scan pass to get required line count for (int i = 0; i < textSize; i++) { if (text[i] == '\n') lineCount++; } - char **lines = (char **)RL_CALLOC(lineCount, sizeof(char *)); + lines = (char **)RL_CALLOC(lineCount, sizeof(char *)); for (int i = 0, l = 0, lineLen = 0; i <= textSize; i++) { if ((text[i] == '\n') || (text[i] == '\0')) From 265fa7833ca58617e978b8565e23f5c0e9067ef0 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 10:21:16 +0100 Subject: [PATCH 126/430] Update rtext.c --- src/rtext.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rtext.c b/src/rtext.c index 52938e3d2..009aba044 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1891,6 +1891,7 @@ void TextAppend(char *text, const char *append, int *position) int TextFindIndex(const char *text, const char *search) { int position = -1; + if (text == NULL) return position; char *ptr = (char *)strstr(text, search); From d56371ce85c09e556560fd4358bc5d604b9b8881 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 11:41:50 +0100 Subject: [PATCH 127/430] Update Makefile.Web --- examples/Makefile.Web | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 01426d7f5..7a2afc931 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -975,7 +975,7 @@ textures/textures_blend_modes: textures/textures_blend_modes.c textures/textures_bunnymark: textures/textures_bunnymark.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ - --preload-file textures/resources/wabbit_alpha.png@resources/wabbit_alpha.png + --preload-file textures/resources/raybunny.png@resources/raybunny.png textures/textures_fog_of_war: textures/textures_fog_of_war.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) From e3738c1b172880e360f6f7f016802eb44a8b5498 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 11:53:55 +0100 Subject: [PATCH 128/430] REXM: UPDATE: Reviewed all examples requirements --- examples/Makefile | 8 +- examples/Makefile.Web | 31 +- examples/README.md | 21 +- .../audio/audio_fft_spectrum_visualizer.c | 2 +- examples/core/core_3d_camera_fps.c | 4 +- examples/core/core_viewport_scaling.c | 4 +- examples/examples_list.txt | 11 +- .../models/models_directional_billboard.c | 2 +- examples/shapes/shapes_rlgl_color_wheel.c | 2 +- examples/shapes/shapes_rlgl_triangle.c | 2 +- examples/textures/textures_screen_buffer.c | 6 +- examples/textures/textures_sprite_stacking.c | 2 +- .../audio_fft_spectrum_visualizer.vcxproj | 569 ++++++++++++++++++ .../models_directional_billboard.vcxproj | 569 ++++++++++++++++++ .../examples/shapes_rlgl_color_wheel.vcxproj | 569 ++++++++++++++++++ .../examples/shapes_rlgl_triangle.vcxproj | 569 ++++++++++++++++++ .../examples/textures_sprite_stacking.vcxproj | 569 ++++++++++++++++++ projects/VS2022/raylib.sln | 139 ++++- tools/rexm/reports/examples_validation.md | 6 + 19 files changed, 3054 insertions(+), 31 deletions(-) create mode 100644 projects/VS2022/examples/audio_fft_spectrum_visualizer.vcxproj create mode 100644 projects/VS2022/examples/models_directional_billboard.vcxproj create mode 100644 projects/VS2022/examples/shapes_rlgl_color_wheel.vcxproj create mode 100644 projects/VS2022/examples/shapes_rlgl_triangle.vcxproj create mode 100644 projects/VS2022/examples/textures_sprite_stacking.vcxproj diff --git a/examples/Makefile b/examples/Makefile index ccd24bf28..edddb366e 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -587,6 +587,8 @@ SHAPES = \ shapes/shapes_rectangle_scaling \ shapes/shapes_recursive_tree \ shapes/shapes_ring_drawing \ + shapes/shapes_rlgl_color_wheel \ + shapes/shapes_rlgl_triangle \ shapes/shapes_rounded_rectangle_drawing \ shapes/shapes_simple_particles \ shapes/shapes_splines_drawing \ @@ -615,11 +617,12 @@ TEXTURES = \ textures/textures_particles_blending \ textures/textures_polygon_drawing \ textures/textures_raw_data \ + textures/textures_screen_buffer \ textures/textures_sprite_animation \ textures/textures_sprite_button \ textures/textures_sprite_explosion \ + textures/textures_sprite_stacking \ textures/textures_srcrec_dstrec \ - textures/textures_screen_buffer \ textures/textures_textured_curve \ textures/textures_tiled_drawing \ textures/textures_to_image @@ -650,6 +653,7 @@ MODELS = \ models/models_box_collisions \ models/models_cubicmap_rendering \ models/models_decals \ + models/models_directional_billboard \ models/models_first_person_maze \ models/models_geometric_shapes \ models/models_heightmap_rendering \ @@ -704,7 +708,7 @@ SHADERS = \ shaders/shaders_vertex_displacement AUDIO = \ - audio/audio_fft_spectrum_visualizer \ + audio/audio_fft_spectrum_visualizer \ audio/audio_mixed_processor \ audio/audio_module_playing \ audio/audio_music_stream \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 7a2afc931..35024cbd5 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -575,6 +575,8 @@ SHAPES = \ shapes/shapes_rectangle_scaling \ shapes/shapes_recursive_tree \ shapes/shapes_ring_drawing \ + shapes/shapes_rlgl_color_wheel \ + shapes/shapes_rlgl_triangle \ shapes/shapes_rounded_rectangle_drawing \ shapes/shapes_simple_particles \ shapes/shapes_splines_drawing \ @@ -603,11 +605,12 @@ TEXTURES = \ textures/textures_particles_blending \ textures/textures_polygon_drawing \ textures/textures_raw_data \ + textures/textures_screen_buffer \ textures/textures_sprite_animation \ textures/textures_sprite_button \ textures/textures_sprite_explosion \ + textures/textures_sprite_stacking \ textures/textures_srcrec_dstrec \ - textures/textures_screen_buffer \ textures/textures_textured_curve \ textures/textures_tiled_drawing \ textures/textures_to_image @@ -638,6 +641,7 @@ MODELS = \ models/models_box_collisions \ models/models_cubicmap_rendering \ models/models_decals \ + models/models_directional_billboard \ models/models_first_person_maze \ models/models_geometric_shapes \ models/models_heightmap_rendering \ @@ -692,6 +696,7 @@ SHADERS = \ shaders/shaders_vertex_displacement AUDIO = \ + audio/audio_fft_spectrum_visualizer \ audio/audio_mixed_processor \ audio/audio_module_playing \ audio/audio_music_stream \ @@ -940,6 +945,12 @@ shapes/shapes_recursive_tree: shapes/shapes_recursive_tree.c shapes/shapes_ring_drawing: shapes/shapes_ring_drawing.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) +shapes/shapes_rlgl_color_wheel: shapes/shapes_rlgl_color_wheel.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + +shapes/shapes_rlgl_triangle: shapes/shapes_rlgl_triangle.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + shapes/shapes_rounded_rectangle_drawing: shapes/shapes_rounded_rectangle_drawing.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) @@ -1041,6 +1052,9 @@ textures/textures_raw_data: textures/textures_raw_data.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file textures/resources/fudesumi.raw@resources/fudesumi.raw +textures/textures_screen_buffer: textures/textures_screen_buffer.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + textures/textures_sprite_animation: textures/textures_sprite_animation.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file textures/resources/scarfy.png@resources/scarfy.png @@ -1055,13 +1069,14 @@ textures/textures_sprite_explosion: textures/textures_sprite_explosion.c --preload-file textures/resources/boom.wav@resources/boom.wav \ --preload-file textures/resources/explosion.png@resources/explosion.png +textures/textures_sprite_stacking: textures/textures_sprite_stacking.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ + --preload-file textures/resources/booth.png@resources/booth.png + textures/textures_srcrec_dstrec: textures/textures_srcrec_dstrec.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file textures/resources/scarfy.png@resources/scarfy.png -textures/textures_screen_buffer: textures/textures_screen_buffer.c - $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) - textures/textures_textured_curve: textures/textures_textured_curve.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file textures/resources/road.png@resources/road.png @@ -1184,6 +1199,10 @@ models/models_decals: models/models_decals.c --preload-file models/resources/models/obj/character_diffuse.png@resources/models/obj/character_diffuse.png \ --preload-file models/resources/raylib_logo.png@resources/raylib_logo.png +models/models_directional_billboard: models/models_directional_billboard.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ + --preload-file models/resources/skillbot.png@resources/skillbot.png + models/models_first_person_maze: models/models_first_person_maze.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file models/resources/cubicmap.png@resources/cubicmap.png \ @@ -1452,6 +1471,10 @@ shaders/shaders_vertex_displacement: shaders/shaders_vertex_displacement.c --preload-file shaders/resources/shaders/glsl100/vertex_displacement.fs@resources/shaders/glsl100/vertex_displacement.fs # Compile AUDIO examples +audio/audio_fft_spectrum_visualizer: audio/audio_fft_spectrum_visualizer.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ + --preload-file audio/resources/country.mp3@resources/country.mp3 + audio/audio_mixed_processor: audio/audio_mixed_processor.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file audio/resources/country.mp3@resources/country.mp3 \ diff --git a/examples/README.md b/examples/README.md index bdf50cb82..41c3c7c01 100644 --- a/examples/README.md +++ b/examples/README.md @@ -17,7 +17,7 @@ You may find it easier to use than other toolchains, especially when it comes to - `zig build [module]` to compile all examples for a module (e.g. `zig build core`) - `zig build [example]` to compile _and run_ a particular example (e.g. `zig build core_basic_window`) -## EXAMPLES COLLECTION [TOTAL: 195] +## EXAMPLES COLLECTION [TOTAL: 200] ### category: core [47] @@ -43,7 +43,7 @@ Examples using raylib [core](../src/rcore.c) module platform functionality: wind | [core_3d_camera_free](core/core_3d_camera_free.c) | core_3d_camera_free | ⭐☆☆☆ | 1.3 | 1.3 | [Ramon Santamaria](https://github.com/raysan5) | | [core_3d_camera_first_person](core/core_3d_camera_first_person.c) | core_3d_camera_first_person | ⭐⭐☆☆ | 1.3 | 1.3 | [Ramon Santamaria](https://github.com/raysan5) | | [core_3d_camera_split_screen](core/core_3d_camera_split_screen.c) | core_3d_camera_split_screen | ⭐⭐⭐☆ | 3.7 | 4.0 | [Jeffery Myers](https://github.com/JeffM2501) | -| [core_3d_camera_fps](core/core_3d_camera_fps.c) | core_3d_camera_fps | ⭐⭐⭐☆ | 5.5 | 5.5 | [Agnis Aldins](https://github.com/nezvers) | +| [core_3d_camera_fps](core/core_3d_camera_fps.c) | core_3d_camera_fps | ⭐⭐⭐☆ | 5.5 | 5.5 | [Agnis Aldiņš](https://github.com/nezvers) | | [core_3d_picking](core/core_3d_picking.c) | core_3d_picking | ⭐⭐☆☆ | 1.3 | 4.0 | [Ramon Santamaria](https://github.com/raysan5) | | [core_world_screen](core/core_world_screen.c) | core_world_screen | ⭐⭐☆☆ | 1.3 | 1.4 | [Ramon Santamaria](https://github.com/raysan5) | | [core_window_flags](core/core_window_flags.c) | core_window_flags | ⭐⭐⭐☆ | 3.5 | 3.5 | [Ramon Santamaria](https://github.com/raysan5) | @@ -64,7 +64,7 @@ Examples using raylib [core](../src/rcore.c) module platform functionality: wind | [core_high_dpi](core/core_high_dpi.c) | core_high_dpi | ⭐⭐☆☆ | 5.0 | 5.5 | [Jonathan Marler](https://github.com/marler8997) | | [core_render_texture](core/core_render_texture.c) | core_render_texture | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | | [core_undo_redo](core/core_undo_redo.c) | core_undo_redo | ⭐⭐⭐☆ | 5.5 | 5.6 | [Ramon Santamaria](https://github.com/raysan5) | -| [core_viewport_scaling](core/core_viewport_scaling.c) | core_viewport_scaling | ⭐⭐☆☆ | 5.5 | 5.5 | [Agnis Aldins](https://github.com/nezvers) | +| [core_viewport_scaling](core/core_viewport_scaling.c) | core_viewport_scaling | ⭐⭐☆☆ | 5.5 | 5.5 | [Agnis Aldiņš](https://github.com/nezvers) | | [core_input_actions](core/core_input_actions.c) | core_input_actions | ⭐⭐☆☆ | 5.5 | 5.6 | [Jett](https://github.com/JettMonstersGoBoom) | | [core_directory_files](core/core_directory_files.c) | core_directory_files | ⭐☆☆☆ | 5.5 | 5.6 | [Hugo ARNAL](https://github.com/hugoarnal) | | [core_highdpi_testbed](core/core_highdpi_testbed.c) | core_highdpi_testbed | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | @@ -73,7 +73,7 @@ Examples using raylib [core](../src/rcore.c) module platform functionality: wind | [core_text_file_loading](core/core_text_file_loading.c) | core_text_file_loading | ⭐☆☆☆ | 5.5 | 5.6 | [Aanjishnu Bhattacharyya](https://github.com/NimComPoo-04) | | [core_compute_hash](core/core_compute_hash.c) | core_compute_hash | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | -### category: shapes [34] +### category: shapes [36] Examples using raylib shapes drawing functionality, provided by raylib [shapes](../src/rshapes.c) module. @@ -113,8 +113,10 @@ Examples using raylib shapes drawing functionality, provided by raylib [shapes]( | [shapes_starfield_effect](shapes/shapes_starfield_effect.c) | shapes_starfield_effect | ⭐⭐☆☆ | 5.5 | 5.6-dev | [JP Mortiboys](https://github.com/themushroompirates) | | [shapes_lines_drawing](shapes/shapes_lines_drawing.c) | shapes_lines_drawing | ⭐☆☆☆ | 5.6-dev | 5.6 | [Robin](https://github.com/RobinsAviary) | | [shapes_math_angle_rotation](shapes/shapes_math_angle_rotation.c) | shapes_math_angle_rotation | ⭐☆☆☆ | 5.6-dev | 5.6 | [Kris](https://github.com/krispy-snacc) | +| [shapes_rlgl_color_wheel](shapes/shapes_rlgl_color_wheel.c) | shapes_rlgl_color_wheel | ⭐⭐⭐☆ | 5.6-dev | 5.6-dev | [Robin](https://github.com/RobinsAviary) | +| [shapes_rlgl_triangle](shapes/shapes_rlgl_triangle.c) | shapes_rlgl_triangle | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Robin](https://github.com/RobinsAviary) | -### category: textures [27] +### category: textures [28] Examples using raylib textures functionality, including image/textures loading/generation and drawing, provided by raylib [textures](../src/rtextures.c) module. @@ -145,8 +147,9 @@ Examples using raylib textures functionality, including image/textures loading/g | [textures_image_kernel](textures/textures_image_kernel.c) | textures_image_kernel | ⭐⭐⭐⭐️ | 1.3 | 1.3 | [Karim Salem](https://github.com/kimo-s) | | [textures_image_channel](textures/textures_image_channel.c) | textures_image_channel | ⭐⭐☆☆ | 5.5 | 5.5 | [Bruno Cabral](https://github.com/brccabral) | | [textures_image_rotate](textures/textures_image_rotate.c) | textures_image_rotate | ⭐⭐☆☆ | 1.0 | 1.0 | [Ramon Santamaria](https://github.com/raysan5) | -| [textures_screen_buffer](textures/textures_screen_buffer.c) | textures_screen_buffer | ⭐⭐☆☆ | 5.5 | 5.5 | [Agnis Aldins](https://github.com/nezvers) | +| [textures_screen_buffer](textures/textures_screen_buffer.c) | textures_screen_buffer | ⭐⭐☆☆ | 5.5 | 5.5 | [Agnis Aldiņš](https://github.com/nezvers) | | [textures_textured_curve](textures/textures_textured_curve.c) | textures_textured_curve | ⭐⭐⭐☆ | 4.5 | 4.5 | [Jeffery Myers](https://github.com/JeffM2501) | +| [textures_sprite_stacking](textures/textures_sprite_stacking.c) | textures_sprite_stacking | ⭐⭐☆☆ | 5.6-dev | 6.0 | [Robin](https://github.com/RobinsAviary) | ### category: text [15] @@ -170,7 +173,7 @@ Examples using raylib text functionality, including sprite fonts loading/generat | [text_inline_styling](text/text_inline_styling.c) | text_inline_styling | ⭐⭐⭐☆ | 5.6-dev | 5.6-dev | [Wagner Barongello](https://github.com/SultansOfCode) | | [text_words_alignment](text/text_words_alignment.c) | text_words_alignment | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [JP Mortiboys](https://github.com/themushroompirates) | -### category: models [26] +### category: models [27] Examples using raylib models functionality, including models loading/generation and drawing, provided by raylib [models](../src/rmodels.c) module. @@ -202,6 +205,7 @@ Examples using raylib models functionality, including models loading/generation | [models_basic_voxel](models/models_basic_voxel.c) | models_basic_voxel | ⭐⭐☆☆ | 5.5 | 5.5 | [Tim Little](https://github.com/timlittle) | | [models_rotating_cube](models/models_rotating_cube.c) | models_rotating_cube | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Jopestpe](https://github.com/jopestpe) | | [models_decals](models/models_decals.c) | models_decals | ⭐⭐⭐⭐️ | 5.6-dev | 5.6-dev | [JP Mortiboys](https://github.com/themushroompirates) | +| [models_directional_billboard](models/models_directional_billboard.c) | models_directional_billboard | ⭐⭐☆☆ | 5.6-dev | 5.6 | [Robin](https://github.com/RobinsAviary) | ### category: shaders [32] @@ -242,7 +246,7 @@ Examples using raylib shaders functionality, including shaders loading, paramete | [shaders_rounded_rectangle](shaders/shaders_rounded_rectangle.c) | shaders_rounded_rectangle | ⭐⭐⭐☆ | 5.5 | 5.5 | [Anstro Pleuton](https://github.com/anstropleuton) | | [shaders_depth_rendering](shaders/shaders_depth_rendering.c) | shaders_depth_rendering | ⭐⭐⭐☆ | 5.6-dev | 5.6-dev | [Luís Almeida](https://github.com/luis605) | -### category: audio [8] +### category: audio [9] Examples using raylib audio functionality, including sound/music loading and playing. This functionality is provided by raylib [raudio](../src/raudio.c) module. Note this module can be used standalone independently of raylib. @@ -256,6 +260,7 @@ Examples using raylib audio functionality, including sound/music loading and pla | [audio_stream_effects](audio/audio_stream_effects.c) | audio_stream_effects | ⭐⭐⭐⭐️ | 4.2 | 5.0 | [Ramon Santamaria](https://github.com/raysan5) | | [audio_sound_multi](audio/audio_sound_multi.c) | audio_sound_multi | ⭐⭐☆☆ | 5.0 | 5.0 | [Jeffery Myers](https://github.com/JeffM2501) | | [audio_sound_positioning](audio/audio_sound_positioning.c) | audio_sound_positioning | ⭐⭐☆☆ | 5.5 | 5.5 | [Le Juez Victor](https://github.com/Bigfoot71) | +| [audio_fft_spectrum_visualizer](audio/audio_fft_spectrum_visualizer.c) | audio_fft_spectrum_visualizer | ⭐⭐⭐☆ | 6.0 | 5.6-dev | [IANN](https://github.com/meisei4) | ### category: others [6] diff --git a/examples/audio/audio_fft_spectrum_visualizer.c b/examples/audio/audio_fft_spectrum_visualizer.c index ad38020fd..299b610ee 100644 --- a/examples/audio/audio_fft_spectrum_visualizer.c +++ b/examples/audio/audio_fft_spectrum_visualizer.c @@ -4,7 +4,7 @@ * * Example complexity rating: [★★★☆] 3/4 * -* Example originally created with raylib 6.0 +* Example originally created with raylib 6.0, last time updated with raylib 5.6-dev * * Inspired by Inigo Quilez's https://www.shadertoy.com/ * Resources/specification: https://gist.github.com/soulthreads/2efe50da4be1fb5f7ab60ff14ca434b8 diff --git a/examples/core/core_3d_camera_fps.c b/examples/core/core_3d_camera_fps.c index 7aa79c174..ef36e912f 100644 --- a/examples/core/core_3d_camera_fps.c +++ b/examples/core/core_3d_camera_fps.c @@ -6,12 +6,12 @@ * * Example originally created with raylib 5.5, last time updated with raylib 5.5 * -* Example contributed by Agnis Aldins (@nezvers) and reviewed by Ramon Santamaria (@raysan5) +* Example contributed by Agnis Aldiņš (@nezvers) 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) 2025 Agnis Aldins (@nezvers) +* Copyright (c) 2025 Agnis Aldiņš (@nezvers) * ********************************************************************************************/ diff --git a/examples/core/core_viewport_scaling.c b/examples/core/core_viewport_scaling.c index 59e0bd026..3044dd0af 100644 --- a/examples/core/core_viewport_scaling.c +++ b/examples/core/core_viewport_scaling.c @@ -6,12 +6,12 @@ * * Example originally created with raylib 5.5, last time updated with raylib 5.5 * -* Example contributed by Agnis Aldins (@nezvers) and reviewed by Ramon Santamaria (@raysan5) +* Example contributed by Agnis Aldiņš (@nezvers) 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) 2025 Agnis Aldins (@nezvers) +* Copyright (c) 2025 Agnis Aldiņš (@nezvers) * ********************************************************************************************/ diff --git a/examples/examples_list.txt b/examples/examples_list.txt index 4e7130d7d..b6223e09a 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -25,7 +25,7 @@ core;core_3d_camera_mode;★☆☆☆;1.0;1.0;2014;2025;"Ramon Santamaria";@rays core;core_3d_camera_free;★☆☆☆;1.3;1.3;2015;2025;"Ramon Santamaria";@raysan5 core;core_3d_camera_first_person;★★☆☆;1.3;1.3;2015;2025;"Ramon Santamaria";@raysan5 core;core_3d_camera_split_screen;★★★☆;3.7;4.0;2021;2025;"Jeffery Myers";@JeffM2501 -core;core_3d_camera_fps;★★★☆;5.5;5.5;2025;2025;"Agnis Aldins";@nezvers +core;core_3d_camera_fps;★★★☆;5.5;5.5;2025;2025;"Agnis Aldiņš";@nezvers core;core_3d_picking;★★☆☆;1.3;4.0;2015;2025;"Ramon Santamaria";@raysan5 core;core_world_screen;★★☆☆;1.3;1.4;2015;2025;"Ramon Santamaria";@raysan5 core;core_window_flags;★★★☆;3.5;3.5;2020;2025;"Ramon Santamaria";@raysan5 @@ -46,7 +46,7 @@ core;core_automation_events;★★★☆;5.0;5.0;2023;2025;"Ramon Santamaria";@r core;core_high_dpi;★★☆☆;5.0;5.5;2025;2025;"Jonathan Marler";@marler8997 core;core_render_texture;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 core;core_undo_redo;★★★☆;5.5;5.6;2025;2025;"Ramon Santamaria";@raysan5 -core;core_viewport_scaling;★★☆☆;5.5;5.5;2025;2025;"Agnis Aldins";@nezvers +core;core_viewport_scaling;★★☆☆;5.5;5.5;2025;2025;"Agnis Aldiņš";@nezvers core;core_input_actions;★★☆☆;5.5;5.6;2025;2025;"Jett";@JettMonstersGoBoom core;core_directory_files;★☆☆☆;5.5;5.6;2025;2025;"Hugo ARNAL";@hugoarnal core;core_highdpi_testbed;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Ramon Santamaria";@raysan5 @@ -88,6 +88,8 @@ shapes;shapes_simple_particles;★★☆☆;5.6;5.6;2025;2025;"Jordi Santonja";@ shapes;shapes_starfield_effect;★★☆☆;5.5;5.6-dev;2025;2025;"JP Mortiboys";@themushroompirates shapes;shapes_lines_drawing;★☆☆☆;5.6-dev;5.6;2025;2025;"Robin";@RobinsAviary shapes;shapes_math_angle_rotation;★☆☆☆;5.6-dev;5.6;2025;2025;"Kris";@krispy-snacc +shapes;shapes_rlgl_color_wheel;★★★☆;5.6-dev;5.6-dev;2025;2025;"Robin";@RobinsAviary +shapes;shapes_rlgl_triangle;★★☆☆;5.6-dev;5.6-dev;2025;2025;"Robin";@RobinsAviary textures;textures_logo_raylib;★☆☆☆;1.0;1.0;2014;2025;"Ramon Santamaria";@raysan5 textures;textures_srcrec_dstrec;★★★☆;1.3;1.3;2015;2025;"Ramon Santamaria";@raysan5 textures;textures_image_drawing;★★☆☆;1.4;1.4;2016;2025;"Ramon Santamaria";@raysan5 @@ -113,8 +115,9 @@ textures;textures_gif_player;★★★☆;4.2;4.2;2021;2025;"Ramon Santamaria";@ textures;textures_image_kernel;★★★★;1.3;1.3;2015;2025;"Karim Salem";@kimo-s textures;textures_image_channel;★★☆☆;5.5;5.5;2024;2025;"Bruno Cabral";@brccabral textures;textures_image_rotate;★★☆☆;1.0;1.0;2014;2025;"Ramon Santamaria";@raysan5 -textures;textures_screen_buffer;★★☆☆;5.5;5.5;2014;2025;"Agnis Aldins";@nezvers +textures;textures_screen_buffer;★★☆☆;5.5;5.5;2025;2025;"Agnis Aldiņš";@nezvers textures;textures_textured_curve;★★★☆;4.5;4.5;2022;2025;"Jeffery Myers";@JeffM2501 +textures;textures_sprite_stacking;★★☆☆;5.6-dev;6.0;2025;2025;"Robin";@RobinsAviary text;text_sprite_fonts;★☆☆☆;1.7;3.7;2017;2025;"Ramon Santamaria";@raysan5 text;text_font_spritefont;★☆☆☆;1.0;1.0;2014;2025;"Ramon Santamaria";@raysan5 text;text_font_filters;★★☆☆;1.3;4.2;2015;2025;"Ramon Santamaria";@raysan5 @@ -156,6 +159,7 @@ models;models_tesseract_view;★★☆☆;5.6-dev;5.6-dev;2024;2025;"Timothy van models;models_basic_voxel;★★☆☆;5.5;5.5;2025;2025;"Tim Little";@timlittle models;models_rotating_cube;★☆☆☆;5.6-dev;5.6-dev;2025;2025;"Jopestpe";@jopestpe models;models_decals;★★★★;5.6-dev;5.6-dev;2025;2025;"JP Mortiboys";@themushroompirates +models;models_directional_billboard;★★☆☆;5.6-dev;5.6;2025;2025;"Robin";@RobinsAviary shaders;shaders_ascii_rendering;★★☆☆;5.5;5.6;2025;2025;"Maicon Santana";@maiconpintoabreu shaders;shaders_basic_lighting;★★★★;3.0;4.2;2019;2025;"Chris Camacho";@chriscamacho shaders;shaders_model_shader;★★☆☆;1.3;3.7;2014;2025;"Ramon Santamaria";@raysan5 @@ -196,6 +200,7 @@ audio;audio_mixed_processor;★★★★;4.2;4.2;2023;2025;"hkc";@hatkidchan audio;audio_stream_effects;★★★★;4.2;5.0;2022;2025;"Ramon Santamaria";@raysan5 audio;audio_sound_multi;★★☆☆;5.0;5.0;2023;2025;"Jeffery Myers";@JeffM2501 audio;audio_sound_positioning;★★☆☆;5.5;5.5;2025;2025;"Le Juez Victor";@Bigfoot71 +audio;audio_fft_spectrum_visualizer;★★★☆;6.0;5.6-dev;2025;2025;"IANN";@meisei4 others;rlgl_standalone;★★★★;1.6;4.0;2014;2025;"Ramon Santamaria";@raysan5 others;rlgl_compute_shader;★★★★;4.0;4.0;2021;2025;"Teddy Astie";@tsnake41 others;easings_testbed;★★★☆;2.5;3.0;2019;2025;"Juan Miguel López";@flashback-fx diff --git a/examples/models/models_directional_billboard.c b/examples/models/models_directional_billboard.c index fe1b33b2d..f471da4d0 100644 --- a/examples/models/models_directional_billboard.c +++ b/examples/models/models_directional_billboard.c @@ -11,7 +11,7 @@ * 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) 2025-2025 Robin (@RobinsAviary) +* Copyright (c) 2025 Robin (@RobinsAviary) * Killbot art by patvanmackelberg https://opengameart.org/content/killbot-8-directional under CC0 * ********************************************************************************************/ diff --git a/examples/shapes/shapes_rlgl_color_wheel.c b/examples/shapes/shapes_rlgl_color_wheel.c index 323a08956..47ae5f7a4 100644 --- a/examples/shapes/shapes_rlgl_color_wheel.c +++ b/examples/shapes/shapes_rlgl_color_wheel.c @@ -11,7 +11,7 @@ * 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) 2025-2025 Robin (@RobinsAviary) +* Copyright (c) 2025 Robin (@RobinsAviary) * ********************************************************************************************/ diff --git a/examples/shapes/shapes_rlgl_triangle.c b/examples/shapes/shapes_rlgl_triangle.c index 1ce8e7949..37626a5c8 100644 --- a/examples/shapes/shapes_rlgl_triangle.c +++ b/examples/shapes/shapes_rlgl_triangle.c @@ -11,7 +11,7 @@ * 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) 2025-2025 Robin (@RobinsAviary) +* Copyright (c) 2025 Robin (@RobinsAviary) * ********************************************************************************************/ diff --git a/examples/textures/textures_screen_buffer.c b/examples/textures/textures_screen_buffer.c index f5e67f20c..4c737901b 100644 --- a/examples/textures/textures_screen_buffer.c +++ b/examples/textures/textures_screen_buffer.c @@ -1,10 +1,10 @@ /******************************************************************************************* * -* raylib [textures] example - screen buffer / update Image as screen buffer and display with texture +* raylib [textures] example - screen buffer * * Example complexity rating: [★★☆☆] 2/4 * -* Example originally created with raylib 5.5, last time updated with raylib 5.6 +* Example originally created with raylib 5.5, last time updated with raylib 5.5 * * Example contributed by Agnis Aldiņš (@nezvers) and reviewed by Ramon Santamaria (@raysan5) * @@ -40,7 +40,7 @@ int main(void) const int pixelScale = SCALE_FACTOR; const int imageWidth = screenWidth / pixelScale; const int imageHeight = screenHeight / pixelScale; - InitWindow(screenWidth, screenHeight, "raylib [] example - "); + InitWindow(screenWidth, screenHeight, "raylib [textures] example - screen buffer"); Color palette[MAX_COLORS] = {0}; unsigned char indexBuffer[INDEX_BUFFER_SIZE] = {0}; diff --git a/examples/textures/textures_sprite_stacking.c b/examples/textures/textures_sprite_stacking.c index 793a83699..a2cb04d2f 100644 --- a/examples/textures/textures_sprite_stacking.c +++ b/examples/textures/textures_sprite_stacking.c @@ -12,7 +12,7 @@ * BSD-like license that allows static linking with closed source software * * Redbooth model (c) 2017-2025 @kluchek under https://creativecommons.org/licenses/by/4.0/ https://github.com/kluchek/vox-models/ -* Copyright (c) 2025-2025 Robin (@RobinsAviary) +* Copyright (c) 2025 Robin (@RobinsAviary) * ********************************************************************************************/ diff --git a/projects/VS2022/examples/audio_fft_spectrum_visualizer.vcxproj b/projects/VS2022/examples/audio_fft_spectrum_visualizer.vcxproj new file mode 100644 index 000000000..d7c6d8d3f --- /dev/null +++ b/projects/VS2022/examples/audio_fft_spectrum_visualizer.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68} + Win32Proj + audio_fft_spectrum_visualizer + 10.0 + audio_fft_spectrum_visualizer + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\audio + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\audio + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\audio + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\audio + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\audio + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\audio + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\audio + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\audio + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\audio + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\audio + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\audio + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\audio + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/examples/models_directional_billboard.vcxproj b/projects/VS2022/examples/models_directional_billboard.vcxproj new file mode 100644 index 000000000..2e6c0e8b2 --- /dev/null +++ b/projects/VS2022/examples/models_directional_billboard.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {30011884-25EE-42C9-BB15-888CAFB1AA6E} + Win32Proj + models_directional_billboard + 10.0 + models_directional_billboard + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\models + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/examples/shapes_rlgl_color_wheel.vcxproj b/projects/VS2022/examples/shapes_rlgl_color_wheel.vcxproj new file mode 100644 index 000000000..b22703577 --- /dev/null +++ b/projects/VS2022/examples/shapes_rlgl_color_wheel.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B} + Win32Proj + shapes_rlgl_color_wheel + 10.0 + shapes_rlgl_color_wheel + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/examples/shapes_rlgl_triangle.vcxproj b/projects/VS2022/examples/shapes_rlgl_triangle.vcxproj new file mode 100644 index 000000000..780f514a0 --- /dev/null +++ b/projects/VS2022/examples/shapes_rlgl_triangle.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F} + Win32Proj + shapes_rlgl_triangle + 10.0 + shapes_rlgl_triangle + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\shapes + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/examples/textures_sprite_stacking.vcxproj b/projects/VS2022/examples/textures_sprite_stacking.vcxproj new file mode 100644 index 000000000..a3e5be045 --- /dev/null +++ b/projects/VS2022/examples/textures_sprite_stacking.vcxproj @@ -0,0 +1,569 @@ + + + + + Debug.DLL + ARM64 + + + Debug.DLL + Win32 + + + Debug.DLL + x64 + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release.DLL + ARM64 + + + Release.DLL + Win32 + + + Release.DLL + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F} + Win32Proj + textures_sprite_stacking + 10.0 + textures_sprite_stacking + + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + true + $(DefaultPlatformToolset) + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + Application + false + $(DefaultPlatformToolset) + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + false + $(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\ + $(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\ + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + $(SolutionDir)..\..\examples\textures + WindowsLocalDebugger + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions) + CompileAsC + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + + + Console + true + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + Copy Debug DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP + $(SolutionDir)..\..\src;%(AdditionalIncludeDirectories) + CompileAsC + true + + + Console + true + true + true + raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\ + + + xcopy /y /d "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)" + + + Copy Release DLL to output directory + + + + + + + + + + + {e89d61ac-55de-4482-afd4-df7242ebc859} + + + + + + \ No newline at end of file diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index f7bbf1641..4af8d0539 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -411,6 +411,16 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_compute_hash", "exampl EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "textures_screen_buffer", "examples\textures_screen_buffer.vcxproj", "{4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "audio_fft_spectrum_visualizer", "examples\audio_fft_spectrum_visualizer.vcxproj", "{2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "models_directional_billboard", "examples\models_directional_billboard.vcxproj", "{30011884-25EE-42C9-BB15-888CAFB1AA6E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_rlgl_color_wheel", "examples\shapes_rlgl_color_wheel.vcxproj", "{32FE2658-1D70-442E-8672-0AC5C6F0BD7B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shapes_rlgl_triangle", "examples\shapes_rlgl_triangle.vcxproj", "{842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "textures_sprite_stacking", "examples\textures_sprite_stacking.vcxproj", "{FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug.DLL|ARM64 = Debug.DLL|ARM64 @@ -5105,6 +5115,126 @@ Global {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Release|x64.Build.0 = Release|x64 {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Release|x86.ActiveCfg = Release|Win32 {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}.Release|x86.Build.0 = Release|Win32 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Debug|ARM64.Build.0 = Debug|ARM64 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Debug|x64.ActiveCfg = Debug|x64 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Debug|x64.Build.0 = Debug|x64 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Debug|x86.ActiveCfg = Debug|Win32 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Debug|x86.Build.0 = Debug|Win32 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Release|ARM64.ActiveCfg = Release|ARM64 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Release|ARM64.Build.0 = Release|ARM64 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Release|x64.ActiveCfg = Release|x64 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Release|x64.Build.0 = Release|x64 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Release|x86.ActiveCfg = Release|Win32 + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}.Release|x86.Build.0 = Release|Win32 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Debug|ARM64.Build.0 = Debug|ARM64 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Debug|x64.ActiveCfg = Debug|x64 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Debug|x64.Build.0 = Debug|x64 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Debug|x86.ActiveCfg = Debug|Win32 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Debug|x86.Build.0 = Debug|Win32 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Release|ARM64.ActiveCfg = Release|ARM64 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Release|ARM64.Build.0 = Release|ARM64 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Release|x64.ActiveCfg = Release|x64 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Release|x64.Build.0 = Release|x64 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Release|x86.ActiveCfg = Release|Win32 + {30011884-25EE-42C9-BB15-888CAFB1AA6E}.Release|x86.Build.0 = Release|Win32 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Debug|ARM64.Build.0 = Debug|ARM64 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Debug|x64.ActiveCfg = Debug|x64 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Debug|x64.Build.0 = Debug|x64 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Debug|x86.ActiveCfg = Debug|Win32 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Debug|x86.Build.0 = Debug|Win32 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Release|ARM64.ActiveCfg = Release|ARM64 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Release|ARM64.Build.0 = Release|ARM64 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Release|x64.ActiveCfg = Release|x64 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Release|x64.Build.0 = Release|x64 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Release|x86.ActiveCfg = Release|Win32 + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B}.Release|x86.Build.0 = Release|Win32 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Debug|ARM64.Build.0 = Debug|ARM64 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Debug|x64.ActiveCfg = Debug|x64 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Debug|x64.Build.0 = Debug|x64 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Debug|x86.ActiveCfg = Debug|Win32 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Debug|x86.Build.0 = Debug|Win32 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Release|ARM64.ActiveCfg = Release|ARM64 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Release|ARM64.Build.0 = Release|ARM64 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Release|x64.ActiveCfg = Release|x64 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Release|x64.Build.0 = Release|x64 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Release|x86.ActiveCfg = Release|Win32 + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F}.Release|x86.Build.0 = Release|Win32 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Debug.DLL|x64.Build.0 = Debug.DLL|x64 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Debug|ARM64.Build.0 = Debug|ARM64 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Debug|x64.ActiveCfg = Debug|x64 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Debug|x64.Build.0 = Debug|x64 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Debug|x86.ActiveCfg = Debug|Win32 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Debug|x86.Build.0 = Debug|Win32 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release.DLL|x64.ActiveCfg = Release.DLL|x64 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release.DLL|x64.Build.0 = Release.DLL|x64 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release.DLL|x86.Build.0 = Release.DLL|Win32 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release|ARM64.ActiveCfg = Release|ARM64 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release|ARM64.Build.0 = Release|ARM64 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release|x64.ActiveCfg = Release|x64 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release|x64.Build.0 = Release|x64 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release|x86.ActiveCfg = Release|Win32 + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5272,7 +5402,7 @@ Global {C54703BF-D68A-480D-BE27-49B62E45D582} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {9CD8BCAD-F212-4BCC-BA98-899743CE3279} = {CC132A4D-D081-4C26-BFB9-AB11984054F8} {0981CA28-E4A5-4DF1-987F-A41D09131EFC} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} - {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} + {6B1A933E-71B8-4C1F-9E79-02D98830E671} = {CC132A4D-D081-4C26-BFB9-AB11984054F8} {6BFF72EA-7362-4A3B-B6E5-9A3655BBBDA3} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9} {6777EC3C-077C-42FC-B4AD-B799CE55CCE4} = {8D3C83B7-F1E0-4C2E-9E34-EE5F6AB2502A} {A61DAD9C-271C-4E95-81AA-DB4CD58564D4} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} @@ -5299,7 +5429,7 @@ Global {124935CC-73BB-489E-92E8-4F922A85DB5D} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {AC215730-2B5F-4498-B7F5-5DB80AEFCA5F} = {278D8859-20B1-428F-8448-064F46E1F021} {0835E6BF-0170-4E99-A55C-E06E1EF4C3B2} = {278D8859-20B1-428F-8448-064F46E1F021} - {EA4AD5A7-DB95-43C0-9A67-2D94146BCF91} = {278D8859-20B1-428F-8448-064F46E1F021} + {EA4AD5A7-DB95-43C0-9A67-2D94146BCF91} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} {1ACC8236-EF4E-44B0-BD0C-AB1D95D5890F} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {9DE2FC01-A839-4F89-8319-9071D4C54821} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {2F578155-D51F-4C03-AB7F-5C5122CA46CC} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} @@ -5312,6 +5442,11 @@ Global {AD66AA6A-1E36-4FF0-8670-4F9834BCDB91} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {6C897101-BE52-4387-8AA2-062123A76BA1} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035} {4E9D2828-EE83-40C8-97E0-137EEDFBAAAD} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} + {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68} = {CC132A4D-D081-4C26-BFB9-AB11984054F8} + {30011884-25EE-42C9-BB15-888CAFB1AA6E} = {AF5BEC5C-1F2B-4DA8-B12D-D09FE569237C} + {32FE2658-1D70-442E-8672-0AC5C6F0BD7B} = {278D8859-20B1-428F-8448-064F46E1F021} + {842B6472-4AA6-4C2B-A5E5-A62F80DE2C4F} = {278D8859-20B1-428F-8448-064F46E1F021} + {FC4DEBD2-4B17-4534-8EEA-BB24A2DBEB5F} = {DA049009-21FF-4AC0-84E4-830DD1BCD0CE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29} diff --git a/tools/rexm/reports/examples_validation.md b/tools/rexm/reports/examples_validation.md index e3d64137b..79fdf05f9 100644 --- a/tools/rexm/reports/examples_validation.md +++ b/tools/rexm/reports/examples_validation.md @@ -101,6 +101,8 @@ Example elements validated: | shapes_starfield_effect | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_lines_drawing | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_math_angle_rotation | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_rlgl_color_wheel | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_rlgl_triangle | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_logo_raylib | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_srcrec_dstrec | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_image_drawing | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -126,7 +128,9 @@ Example elements validated: | textures_image_kernel | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_image_channel | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_image_rotate | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| textures_screen_buffer | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_textured_curve | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| textures_sprite_stacking | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_sprite_fonts | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_font_spritefont | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_font_filters | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -168,6 +172,7 @@ Example elements validated: | models_basic_voxel | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | models_rotating_cube | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | models_decals | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| models_directional_billboard | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_ascii_rendering | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_basic_lighting | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_model_shader | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | @@ -208,6 +213,7 @@ Example elements validated: | audio_stream_effects | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | audio_sound_multi | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | audio_sound_positioning | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| audio_fft_spectrum_visualizer | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | rlgl_standalone | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | rlgl_compute_shader | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | easings_testbed | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | From 5fdf178969315e9cd4eb594d017fb22fdddf69e1 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 12:30:38 +0100 Subject: [PATCH 129/430] REVIEWED: audio_fft_spectrum_visualizer, not working on web --- .../audio/audio_fft_spectrum_visualizer.c | 43 +++++++++++-------- examples/audio/resources/fft.glsl | 32 -------------- .../audio/resources/shaders/glsl100/fft.fs | 37 ++++++++++++++++ .../audio/resources/shaders/glsl120/fft.fs | 35 +++++++++++++++ .../audio/resources/shaders/glsl330/fft.fs | 35 +++++++++++++++ 5 files changed, 133 insertions(+), 49 deletions(-) delete mode 100644 examples/audio/resources/fft.glsl create mode 100644 examples/audio/resources/shaders/glsl100/fft.fs create mode 100644 examples/audio/resources/shaders/glsl120/fft.fs create mode 100644 examples/audio/resources/shaders/glsl330/fft.fs diff --git a/examples/audio/audio_fft_spectrum_visualizer.c b/examples/audio/audio_fft_spectrum_visualizer.c index 299b610ee..c667b5d6a 100644 --- a/examples/audio/audio_fft_spectrum_visualizer.c +++ b/examples/audio/audio_fft_spectrum_visualizer.c @@ -19,11 +19,19 @@ ********************************************************************************************/ #include "raylib.h" + #include "raymath.h" + #include #include #include +#if defined(PLATFORM_DESKTOP) + #define GLSL_VERSION 330 +#else // PLATFORM_ANDROID, PLATFORM_WEB + #define GLSL_VERSION 100 +#endif + #define MONO 1 #define SAMPLE_RATE 44100 #define SAMPLE_RATE_F 44100.0f @@ -77,7 +85,8 @@ int main(void) RenderTexture2D bufferA = LoadRenderTexture(screenWidth, screenHeight); Vector2 iResolution = { (float)screenWidth, (float)screenHeight }; - Shader shader = LoadShader(NULL, "resources/fft.glsl"); + Shader shader = LoadShader(0, TextFormat("resources/shaders/glsl%i/fft.fs", GLSL_VERSION)); + int iResolutionLocation = GetShaderLocation(shader, "iResolution"); int iChannel0Location = GetShaderLocation(shader, "iChannel0"); SetShaderValue(shader, iResolutionLocation, &iResolution, SHADER_UNIFORM_VEC2); @@ -86,6 +95,7 @@ int main(void) InitAudioDevice(); SetAudioStreamBufferSizeDefault(AUDIO_STREAM_RING_BUFFER_SIZE); + // WARNING: Memory out-of-bounds on PLATFORM_WEB Wave wav = LoadWave("resources/country.mp3"); WaveFormat(&wav, SAMPLE_RATE, PER_SAMPLE_BIT_DEPTH, MONO); @@ -95,10 +105,10 @@ int main(void) int fftHistoryLen = (int)ceilf(FFT_HISTORICAL_SMOOTHING_DUR/WINDOW_TIME) + 1; FFTData fft = { - .spectrum = malloc(sizeof(FFTComplex)*FFT_WINDOW_SIZE), - .workBuffer = malloc(sizeof(FFTComplex)*FFT_WINDOW_SIZE), - .prevMagnitudes = calloc(BUFFER_SIZE, sizeof(float)), - .fftHistory = calloc(fftHistoryLen, sizeof(float[BUFFER_SIZE])), + .spectrum = RL_CALLOC(sizeof(FFTComplex), FFT_WINDOW_SIZE), + .workBuffer = RL_CALLOC(sizeof(FFTComplex), FFT_WINDOW_SIZE), + .prevMagnitudes = RL_CALLOC(BUFFER_SIZE, sizeof(float)), + .fftHistory = RL_CALLOC(fftHistoryLen, sizeof(float[BUFFER_SIZE])), .fftHistoryLen = fftHistoryLen, .historyPos = 0, .lastFftTime = 0.0, @@ -127,15 +137,12 @@ int main(void) int right = (wav.channels == 2)? wavPCM16[wavCursor*2 + 1] : left; chunkSamples[i] = (short)((left + right)/2); - if (++wavCursor >= wav.frameCount) - wavCursor = 0; - + if (++wavCursor >= wav.frameCount) wavCursor = 0; } UpdateAudioStream(audioStream, chunkSamples, AUDIO_STREAM_RING_BUFFER_SIZE); - for (int i = 0; i < FFT_WINDOW_SIZE; i++) - audioSamples[i] = (chunkSamples[i*2] + chunkSamples[i*2 + 1])*0.5f/32767.0f; + for (int i = 0; i < FFT_WINDOW_SIZE; i++) audioSamples[i] = (chunkSamples[i*2] + chunkSamples[i*2 + 1])*0.5f/32767.0f; } CaptureFrame(&fft, audioSamples); @@ -146,14 +153,16 @@ int main(void) // Draw //---------------------------------------------------------------------------------- BeginDrawing(); - ClearBackground(BLACK); + + ClearBackground(RAYWHITE); + BeginShaderMode(shader); SetShaderValueTexture(shader, iChannel0Location, fftTexture); DrawTextureRec(bufferA.texture, (Rectangle){ 0, 0, (float)screenWidth, (float)-screenHeight }, - (Vector2){ 0, 0 }, - WHITE); + (Vector2){ 0, 0 }, WHITE); EndShaderMode(); + EndDrawing(); //------------------------------------------------------------------------------ } @@ -168,10 +177,10 @@ int main(void) UnloadWave(wav); CloseAudioDevice(); - free(fft.spectrum); - free(fft.workBuffer); - free(fft.prevMagnitudes); - free(fft.fftHistory); + RL_FREE(fft.spectrum); + RL_FREE(fft.workBuffer); + RL_FREE(fft.prevMagnitudes); + RL_FREE(fft.fftHistory); CloseWindow(); // Close window and OpenGL context //---------------------------------------------------------------------------------- diff --git a/examples/audio/resources/fft.glsl b/examples/audio/resources/fft.glsl deleted file mode 100644 index 95fd4b38f..000000000 --- a/examples/audio/resources/fft.glsl +++ /dev/null @@ -1,32 +0,0 @@ -#version 330 - -in vec2 fragTexCoord; -in vec4 fragColor; - -out vec4 finalColor; - -uniform vec2 iResolution; -uniform sampler2D iChannel0; - -const vec4 BLACK = vec4(0.0, 0.0, 0.0, 1.0); -const vec4 WHITE = vec4(1.0, 1.0, 1.0, 1.0); -const float FFT_ROW = 0.0; -const float NUM_OF_BINS = 512.0; - -void main() { - vec2 fragCoord = fragTexCoord*iResolution; - float cell_width = iResolution.x/NUM_OF_BINS; - float bin_index = floor(fragCoord.x/cell_width); - float local_x = mod(fragCoord.x, cell_width); - float bar_width = cell_width - 1.0; - vec4 color = BLACK; - if (local_x <= bar_width) { - float sample_x = (bin_index + 0.5)/NUM_OF_BINS; - vec2 sample_coord = vec2(sample_x, FFT_ROW); - float amplitude = texture(iChannel0, sample_coord).r; // only filled the red channel, all channels left open for alternative use - if (fragTexCoord.y < amplitude) { - color = WHITE; - } - } - finalColor = color; -} diff --git a/examples/audio/resources/shaders/glsl100/fft.fs b/examples/audio/resources/shaders/glsl100/fft.fs new file mode 100644 index 000000000..a97bf336b --- /dev/null +++ b/examples/audio/resources/shaders/glsl100/fft.fs @@ -0,0 +1,37 @@ +#version 100 + +precision mediump float; + +// Input vertex attributes (from vertex shader) +varying vec2 fragTexCoord; +varying vec4 fragColor; + +// Input uniform values +uniform vec2 iResolution; +uniform sampler2D iChannel0; + +const vec4 BLACK = vec4(0.0, 0.0, 0.0, 1.0); +const vec4 WHITE = vec4(1.0, 1.0, 1.0, 1.0); +const float FFT_ROW = 0.0; +const float NUM_OF_BINS = 512.0; + +void main() +{ + vec2 fragCoord = fragTexCoord*iResolution; + float cellWidth = iResolution.x/NUM_OF_BINS; + float binIndex = floor(fragCoord.x/cellWidth); + float localX = mod(fragCoord.x, cellWidth); + float barWidth = cellWidth - 1.0; + vec4 color = WHITE; + + if (localX <= barWidth) + { + float sampleX = (binIndex + 0.5)/NUM_OF_BINS; + vec2 sampleCoord = vec2(sampleX, FFT_ROW); + float amplitude = texture2D(iChannel0, sampleCoord).r; // Only filled the red channel, all channels left open for alternative use + + if (fragTexCoord.y < amplitude) color = BLACK; + } + + gl_FragColor = color; +} diff --git a/examples/audio/resources/shaders/glsl120/fft.fs b/examples/audio/resources/shaders/glsl120/fft.fs new file mode 100644 index 000000000..bab5d533b --- /dev/null +++ b/examples/audio/resources/shaders/glsl120/fft.fs @@ -0,0 +1,35 @@ +#version 120 + +// Input vertex attributes (from vertex shader) +varying vec2 fragTexCoord; +varying vec4 fragColor; + +// Input uniform values +uniform vec2 iResolution; +uniform sampler2D iChannel0; + +const vec4 BLACK = vec4(0.0, 0.0, 0.0, 1.0); +const vec4 WHITE = vec4(1.0, 1.0, 1.0, 1.0); +const float FFT_ROW = 0.0; +const float NUM_OF_BINS = 512.0; + +void main() +{ + vec2 fragCoord = fragTexCoord*iResolution; + float cellWidth = iResolution.x/NUM_OF_BINS; + float binIndex = floor(fragCoord.x/cellWidth); + float localX = mod(fragCoord.x, cellWidth); + float barWidth = cellWidth - 1.0; + vec4 color = WHITE; + + if (localX <= barWidth) + { + float sampleX = (binIndex + 0.5)/NUM_OF_BINS; + vec2 sampleCoord = vec2(sampleX, FFT_ROW); + float amplitude = texture2D(iChannel0, sampleCoord).r; // Only filled the red channel, all channels left open for alternative use + + if (fragTexCoord.y < amplitude) color = BLACK; + } + + gl_FragColor = color; +} diff --git a/examples/audio/resources/shaders/glsl330/fft.fs b/examples/audio/resources/shaders/glsl330/fft.fs new file mode 100644 index 000000000..20b2e9dfa --- /dev/null +++ b/examples/audio/resources/shaders/glsl330/fft.fs @@ -0,0 +1,35 @@ +#version 330 + +in vec2 fragTexCoord; +in vec4 fragColor; + +out vec4 finalColor; + +uniform vec2 iResolution; +uniform sampler2D iChannel0; + +const vec4 BLACK = vec4(0.0, 0.0, 0.0, 1.0); +const vec4 WHITE = vec4(1.0, 1.0, 1.0, 1.0); +const float FFT_ROW = 0.0; +const float NUM_OF_BINS = 512.0; + +void main() +{ + vec2 fragCoord = fragTexCoord*iResolution; + float cellWidth = iResolution.x/NUM_OF_BINS; + float binIndex = floor(fragCoord.x/cellWidth); + float localX = mod(fragCoord.x, cellWidth); + float barWidth = cellWidth - 1.0; + vec4 color = WHITE; + + if (localX <= barWidth) + { + float sampleX = (binIndex + 0.5)/NUM_OF_BINS; + vec2 sampleCoord = vec2(sampleX, FFT_ROW); + float amplitude = texture(iChannel0, sampleCoord).r; // Only filled the red channel, all channels left open for alternative use + + if (fragTexCoord.y < amplitude) color = BLACK; + } + + finalColor = color; +} From 39e39216f6599309e9b1a9c9b09dc115c06899e5 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 12:31:15 +0100 Subject: [PATCH 130/430] REXM: ADDED: TestLog option for logs processing (without rebuilding) --- tools/rexm/rexm.c | 60 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 20bf120ce..80dd9f8c8 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -141,7 +141,8 @@ typedef enum { OP_VALIDATE = 5, // Validate examples, using [examples_list.txt] as main source by default OP_UPDATE = 6, // Validate and update required examples (as far as possible): ALL OP_BUILD = 7, // Build example(s) for desktop and web, copy web output - Multiple examples supported - OP_TEST = 8, // Test example(s), checking output log "WARNING" - Multiplee examples supported + OP_TEST = 8, // Test example(s), checking output log "WARNING" - Multiple examples supported + OP_TESTLOG = 9, // Process available examples logs to generate report } rlExampleOperation; static const char *exCategories[REXM_MAX_EXAMPLE_CATEGORIES] = { "core", "shapes", "textures", "text", "models", "shaders", "audio", "others" }; @@ -403,7 +404,7 @@ int main(int argc, char *argv[]) opCode = OP_UPDATE; } - else if ((strcmp(argv[1], "build") == 0) || (strcmp(argv[1], "test") == 0)) + else if ((strcmp(argv[1], "build") == 0) || (strcmp(argv[1], "test") == 0) || (strcmp(argv[1], "testlog") == 0)) { // Build/Test example(s) for PLATFORM_DESKTOP and PLATFORM_WEB // NOTE: Build outputs to default directory, usually where the .c file is located, @@ -431,7 +432,12 @@ int main(int argc, char *argv[]) UnloadExampleData(exBuildListInfo); if (exBuildListCount == 0) LOG("WARNING: BUILD: Example requested not available in the collection\n"); - else opCode = OP_TEST; + else + { + if (strcmp(argv[1], "build") == 0) opCode = OP_BUILD; + else if (strcmp(argv[1], "test") == 0) opCode = OP_TEST; + else if (strcmp(argv[1], "testlog") == 0) opCode = OP_TESTLOG; + } } } @@ -1459,8 +1465,6 @@ int main(int argc, char *argv[]) LOG("INFO: Command requested: TEST\n"); LOG("INFO: Example(s) to be build and tested: %i [%s]\n", exBuildListCount, (exBuildListCount == 1)? exBuildList[0] : argv[2]); - rlExampleTesting *testing = (rlExampleTesting *)RL_CALLOC(exBuildListCount, sizeof(rlExampleTesting)); - #if defined(_WIN32) // Set required environment variables //putenv(TextFormat("RAYLIB_DIR=%s\\..", exBasePath)); @@ -1574,9 +1578,11 @@ int main(int argc, char *argv[]) FileRemove(TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName)); // STEP 3: Run example on browser - ChangeDirectory(TextFormat("%s/%s", exBasePath, exCategory)); - if (i == 0) system("start python -m http.server 8080"); // TODO: Init localhost just once! - system(TextFormat("start explorer \"http:\\localhost:8080/%s.html", exName)); + // WARNING: Example download is asynchronous so reading fails on next step + // when looking for a file that could not have been downloaded yet + ChangeDirectory(TextFormat("%s", exBasePath)); + if (i == 0) system("start python -m http.server 8080"); // Init localhost just once + system(TextFormat("start explorer \"http:\\localhost:8080/%s/%s.html", exCategory, exName)); // NOTE: Example .log is automatically downloaded into system Downloads directory on browser-example exectution @@ -1626,10 +1632,34 @@ int main(int argc, char *argv[]) ChangeDirectory(TextFormat("%s/%s", exBasePath, exCategory)); system(TextFormat("%s --frames 2 > logs/%s.log", exName, exName)); #endif - // STEP 4: Load and validate log info - //--------------------------------------------------------------------------------------------- + } + } break; + case OP_TESTLOG: + { + // STEP 4: Load and validate available logs info + //--------------------------------------------------------------------------------------------- + rlExampleTesting *testing = (rlExampleTesting *)RL_CALLOC(exBuildListCount, sizeof(rlExampleTesting)); + + for (int i = 0; i < exBuildListCount; i++) + { + // Get example name and category + memset(exName, 0, 64); + strcpy(exName, exBuildList[i]); + memset(exCategory, 0, 32); + strncpy(exCategory, exName, TextFindIndex(exName, "_")); + + // Skip some examples from building + if ((strcmp(exName, "core_custom_logging") == 0) || + (strcmp(exName, "core_window_should_close") == 0) || + (strcmp(exName, "core_custom_frame_control") == 0)) continue; + + LOG("INFO: [%i/%i] Checking example log: [%s]\n", i + 1, exBuildListCount, exName); + // Load .build.log to check for compilation warnings char *exTestBuildLog = LoadFileText(TextFormat("%s/%s/logs/%s.build.log", exBasePath, exCategory, exName)); + if (exTestBuildLog == NULL) continue; + + // Load build log text lines int exTestBuildLogLinesCount = 0; char **exTestBuildLogLines = LoadTextLines(exTestBuildLog, &exTestBuildLogLinesCount); @@ -1647,9 +1677,12 @@ int main(int argc, char *argv[]) #else char *exTestLog = LoadFileText(TextFormat("%s/%s/logs/%s.log", exBasePath, exCategory, exName)); #endif + if (exTestLog == NULL) continue; + + // Load build log text lines int exTestLogLinesCount = 0; char **exTestLogLines = LoadTextLines(exTestLog, &exTestLogLinesCount); - + /* TESTING_FAIL_INIT = 1 << 0, // Initialization (InitWindow()) -> "INFO: DISPLAY: Device initialized successfully" TESTING_FAIL_CLOSE = 1 << 1, // Closing (CloseWindow()) -> "INFO: Window closed successfully" @@ -1670,13 +1703,14 @@ int main(int argc, char *argv[]) for (int k = 0, index = 0; k < exTestLogLinesCount; k++) { + if (TextFindIndex(exTestLogLines[k], "WARNING: GL: NPOT") >= 0) continue; // Ignore warning if (TextFindIndex(exTestLogLines[k], "WARNING") >= 0) testing[i].warnings++; } UnloadTextLines(exTestLogLines, exTestLogLinesCount); UnloadFileText(exTestLog); - //--------------------------------------------------------------------------------------------- } + //--------------------------------------------------------------------------------------------- // STEP 5: Generate testing report/table with results (.md) //----------------------------------------------------------------------------------------------------- @@ -2024,7 +2058,7 @@ static int UpdateRequiredFiles(void) { mdIndex += sprintf(mdTextUpdated + mdListStartIndex + mdIndex, TextFormat("\n### category: core [%i]\n\n", exCollectionCount)); mdIndex += sprintf(mdTextUpdated + mdListStartIndex + mdIndex, - "Examples using raylib[core](../src/rcore.c) platform functionality like window creation, inputs, drawing modes and system functionality.\n\n"); + "Examples using raylib [core](../src/rcore.c) module platform functionality: window creation, inputs, drawing modes and system functionality.\n\n"); } else if (i == 1) // "shapes" { From a6976b1930c006a22e5bd8c4e3c5c3dcd7e7a640 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 12:33:49 +0100 Subject: [PATCH 131/430] Create examples_testing_web.md --- tools/rexm/reports/examples_testing_web.md | 47 ++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tools/rexm/reports/examples_testing_web.md diff --git a/tools/rexm/reports/examples_testing_web.md b/tools/rexm/reports/examples_testing_web.md new file mode 100644 index 000000000..2aedfd180 --- /dev/null +++ b/tools/rexm/reports/examples_testing_web.md @@ -0,0 +1,47 @@ +# EXAMPLES COLLECTION - TESTING REPORT + +## Tested Platform: Web + +``` +Example automated testing elements validated: + - [CWARN] : Compilation WARNING messages + - [LWARN] : Log WARNING messages count + - [INIT] : Initialization + - [CLOSE] : Closing + - [ASSETS] : Assets loading + - [RLGL] : OpenGL-wrapped initialization + - [PLAT] : Platform initialization + - [FONT] : Font default initialization + - [TIMER] : Timer initialization +``` +| **EXAMPLE NAME** | [CWARN] | [LWARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | +|:---------------------------------|:-------:|:-------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| +| core_monitor_detector | 0 | 1 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_directory_files | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_clipboard_text | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_compute_hash | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_recursive_tree | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_ring_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_circle_sector_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_rounded_rectangle_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_splines_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_triangle_strip | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_pie_chart | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_math_sine_cosine | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_rlgl_color_wheel | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| textures_sprite_stacking | 0 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | +| text_sprite_fonts | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | +| text_font_loading | 0 | 3 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | +| text_font_sdf | 0 | 22 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | +| text_codepoints_loading | 0 | 1 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| models_animation_playing | 0 | 1 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | +| models_mesh_generation | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | +| models_loading_gltf | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | +| models_bone_socket | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | +| shaders_postprocessing | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | +| shaders_color_correction | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shaders_deferred_rendering | 0 | 2 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shaders_shadowmap_rendering | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | +| shaders_basic_pbr | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | +| audio_module_playing | 0 | 1 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | + From 313659d37d932ac4cc9798d4f062ddafbe533a24 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 12:40:59 +0100 Subject: [PATCH 132/430] Update examples_testing_web.md --- tools/rexm/reports/examples_testing_web.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/rexm/reports/examples_testing_web.md b/tools/rexm/reports/examples_testing_web.md index 2aedfd180..e969c48aa 100644 --- a/tools/rexm/reports/examples_testing_web.md +++ b/tools/rexm/reports/examples_testing_web.md @@ -28,7 +28,9 @@ Example automated testing elements validated: | shapes_triangle_strip | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_pie_chart | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_math_sine_cosine | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_lines_drawing | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | shapes_rlgl_color_wheel | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| textures_screen_buffer | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | textures_sprite_stacking | 0 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | | text_sprite_fonts | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | | text_font_loading | 0 | 3 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | @@ -37,11 +39,15 @@ Example automated testing elements validated: | models_animation_playing | 0 | 1 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | | models_mesh_generation | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | | models_loading_gltf | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | +| models_loading_vox | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | models_bone_socket | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | +| models_decals | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | shaders_postprocessing | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | | shaders_color_correction | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_deferred_rendering | 0 | 2 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_shadowmap_rendering | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | | shaders_basic_pbr | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | | audio_module_playing | 0 | 1 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | +| audio_sound_positioning | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| audio_fft_spectrum_visualizer | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | From 43bd2b1e18489ded5142a0af1113889e15e69826 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 12:41:36 +0100 Subject: [PATCH 133/430] REXM: Report issues if logs can not be loaded --- tools/rexm/rexm.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 80dd9f8c8..89f0145d0 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1657,7 +1657,11 @@ int main(int argc, char *argv[]) // Load .build.log to check for compilation warnings char *exTestBuildLog = LoadFileText(TextFormat("%s/%s/logs/%s.build.log", exBasePath, exCategory, exName)); - if (exTestBuildLog == NULL) continue; + if (exTestBuildLog == NULL) + { + LOG("WARNING: [%s] Build log could not be loaded\n", exName); + continue; + } // Load build log text lines int exTestBuildLogLinesCount = 0; @@ -1677,11 +1681,12 @@ int main(int argc, char *argv[]) #else char *exTestLog = LoadFileText(TextFormat("%s/%s/logs/%s.log", exBasePath, exCategory, exName)); #endif - if (exTestLog == NULL) continue; - - // Load build log text lines - int exTestLogLinesCount = 0; - char **exTestLogLines = LoadTextLines(exTestLog, &exTestLogLinesCount); + if (exTestLog == NULL) + { + LOG("WARNING: [%s] Execution log could not be loaded\n", exName); + testing[i].status = 0b1111111; + continue; + } /* TESTING_FAIL_INIT = 1 << 0, // Initialization (InitWindow()) -> "INFO: DISPLAY: Device initialized successfully" @@ -1701,12 +1706,14 @@ int main(int argc, char *argv[]) if (TextFindIndex(exTestLog, "INFO: FONT: Default font loaded successfully") == -1) testing[i].status |= TESTING_FAIL_FONT; if (TextFindIndex(exTestLog, "INFO: TIMER: Target time per frame:") == -1) testing[i].status |= TESTING_FAIL_TIMER; + // Load build log text lines + int exTestLogLinesCount = 0; + char **exTestLogLines = LoadTextLines(exTestLog, &exTestLogLinesCount); for (int k = 0, index = 0; k < exTestLogLinesCount; k++) { if (TextFindIndex(exTestLogLines[k], "WARNING: GL: NPOT") >= 0) continue; // Ignore warning if (TextFindIndex(exTestLogLines[k], "WARNING") >= 0) testing[i].warnings++; } - UnloadTextLines(exTestLogLines, exTestLogLinesCount); UnloadFileText(exTestLog); } From e1d5adb326a06c4a9aeaa2301cf8d920e5206242 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 12:43:44 +0100 Subject: [PATCH 134/430] Update rexm.c --- tools/rexm/rexm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 89f0145d0..4b5a7dcc7 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1677,7 +1677,8 @@ int main(int argc, char *argv[]) UnloadFileText(exTestBuildLog); #if defined(BUILD_TESTING_WEB) - char *exTestLog = LoadFileText(TextFormat("C:/Users/raysa/Downloads/%s.log", exName)); + // TODO: REVIEW: Hardcoded path where web logs are copied after automatic download + char *exTestLog = LoadFileText(TextFormat("D:/testing_logs_web/%s.log", exName)); #else char *exTestLog = LoadFileText(TextFormat("%s/%s/logs/%s.log", exBasePath, exCategory, exName)); #endif From 80e164fa045812883000b01d306352fbd9d1bf65 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 13:07:45 +0100 Subject: [PATCH 135/430] Update core_monitor_detector.c --- examples/core/core_monitor_detector.c | 37 ++++++++++----------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/examples/core/core_monitor_detector.c b/examples/core/core_monitor_detector.c index 0e94f6895..08e06c811 100644 --- a/examples/core/core_monitor_detector.c +++ b/examples/core/core_monitor_detector.c @@ -40,10 +40,9 @@ int main(void) const int screenWidth = 800; const int screenHeight = 450; - MonitorInfo monitors[MAX_MONITORS] = { 0 }; - InitWindow(screenWidth, screenHeight, "raylib [core] example - monitor detector"); + MonitorInfo monitors[MAX_MONITORS] = { 0 }; int currentMonitorIndex = GetCurrentMonitor(); int monitorCount = 0; @@ -55,7 +54,6 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - // Variables to find the max x and Y to calculate the scale int maxWidth = 1; int maxHeight = 1; @@ -76,7 +74,8 @@ int main(void) GetMonitorPhysicalHeight(i), GetMonitorRefreshRate(i) }; - if (monitors[i].position.x < monitorOffsetX) monitorOffsetX = (int)monitors[i].position.x*-1; + + if (monitors[i].position.x < monitorOffsetX) monitorOffsetX = -(int)monitors[i].position.x; const int width = (int)monitors[i].position.x + monitors[i].width; const int height = (int)monitors[i].position.y + monitors[i].height; @@ -85,25 +84,22 @@ int main(void) if (maxHeight < height) maxHeight = height; } - if (IsKeyPressed(KEY_ENTER) && monitorCount > 1) + if (IsKeyPressed(KEY_ENTER) && (monitorCount > 1)) { currentMonitorIndex += 1; // Set index to 0 if the last one - if(currentMonitorIndex == monitorCount) currentMonitorIndex = 0; + if (currentMonitorIndex == monitorCount) currentMonitorIndex = 0; SetWindowMonitor(currentMonitorIndex); // Move window to currentMonitorIndex } - else - { - // Get currentMonitorIndex if manually moved - currentMonitorIndex = GetCurrentMonitor(); - } + else currentMonitorIndex = GetCurrentMonitor(); // Get currentMonitorIndex if manually moved float monitorScale = 0.6f; - - if(maxHeight > maxWidth + monitorOffsetX) monitorScale *= ((float)screenHeight/(float)maxHeight); + + if (maxHeight > (maxWidth + monitorOffsetX)) monitorScale *= ((float)screenHeight/(float)maxHeight); else monitorScale *= ((float)screenWidth/(float)(maxWidth + monitorOffsetX)); + //---------------------------------------------------------------------------------- // Draw //---------------------------------------------------------------------------------- @@ -120,10 +116,10 @@ int main(void) { // Calculate retangle position and size using monitorScale const Rectangle rec = (Rectangle){ - (monitors[i].position.x + monitorOffsetX) * monitorScale + 140, - monitors[i].position.y * monitorScale + 80, - monitors[i].width * monitorScale, - monitors[i].height * monitorScale + (monitors[i].position.x + monitorOffsetX)*monitorScale + 140, + monitors[i].position.y*monitorScale + 80, + monitors[i].width*monitorScale, + monitors[i].height*monitorScale }; // Draw monitor name and information inside the rectangle @@ -148,14 +144,9 @@ int main(void) // Draw window position based on monitors DrawRectangleV(windowPosition, (Vector2){screenWidth * monitorScale, screenHeight * monitorScale}, Fade(GREEN, 0.5)); } - else - { - DrawRectangleLinesEx(rec, 5, GRAY); - } - + else DrawRectangleLinesEx(rec, 5, GRAY); } - EndDrawing(); //---------------------------------------------------------------------------------- } From 63fb407dc5e8f28570aab7c0f7e48acc8caa7742 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 13:07:57 +0100 Subject: [PATCH 136/430] Update raygui to avoid warnings --- examples/core/raygui.h | 9 +-------- examples/shaders/raygui.h | 9 +-------- examples/shapes/raygui.h | 9 +-------- 3 files changed, 3 insertions(+), 24 deletions(-) diff --git a/examples/core/raygui.h b/examples/core/raygui.h index f86247ac4..88fe5cc5b 100644 --- a/examples/core/raygui.h +++ b/examples/core/raygui.h @@ -5079,25 +5079,18 @@ static const char **GetTextLines(const char *text, int *count) int textSize = (int)strlen(text); lines[0] = text; - int len = 0; *count = 1; - //int lineSize = 0; // Stores current line size, not returned for (int i = 0, k = 0; (i < textSize) && (*count < RAYGUI_MAX_TEXT_LINES); i++) { if (text[i] == '\n') { - //lineSize = len; k++; - lines[k] = &text[i + 1]; // WARNING: next value is valid? - len = 0; + lines[k] = &text[i + 1]; // WARNING: next value is valid? *count += 1; } - else len++; } - //lines[*count - 1].size = len; - return lines; } diff --git a/examples/shaders/raygui.h b/examples/shaders/raygui.h index f86247ac4..88fe5cc5b 100644 --- a/examples/shaders/raygui.h +++ b/examples/shaders/raygui.h @@ -5079,25 +5079,18 @@ static const char **GetTextLines(const char *text, int *count) int textSize = (int)strlen(text); lines[0] = text; - int len = 0; *count = 1; - //int lineSize = 0; // Stores current line size, not returned for (int i = 0, k = 0; (i < textSize) && (*count < RAYGUI_MAX_TEXT_LINES); i++) { if (text[i] == '\n') { - //lineSize = len; k++; - lines[k] = &text[i + 1]; // WARNING: next value is valid? - len = 0; + lines[k] = &text[i + 1]; // WARNING: next value is valid? *count += 1; } - else len++; } - //lines[*count - 1].size = len; - return lines; } diff --git a/examples/shapes/raygui.h b/examples/shapes/raygui.h index f86247ac4..88fe5cc5b 100644 --- a/examples/shapes/raygui.h +++ b/examples/shapes/raygui.h @@ -5079,25 +5079,18 @@ static const char **GetTextLines(const char *text, int *count) int textSize = (int)strlen(text); lines[0] = text; - int len = 0; *count = 1; - //int lineSize = 0; // Stores current line size, not returned for (int i = 0, k = 0; (i < textSize) && (*count < RAYGUI_MAX_TEXT_LINES); i++) { if (text[i] == '\n') { - //lineSize = len; k++; - lines[k] = &text[i + 1]; // WARNING: next value is valid? - len = 0; + lines[k] = &text[i + 1]; // WARNING: next value is valid? *count += 1; } - else len++; } - //lines[*count - 1].size = len; - return lines; } From 4cef89cf04a25d675fc490c0b1a245d983de5f98 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 13:08:02 +0100 Subject: [PATCH 137/430] Update rexm.c --- tools/rexm/rexm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 4b5a7dcc7..5c89a9233 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -1667,7 +1667,7 @@ int main(int argc, char *argv[]) int exTestBuildLogLinesCount = 0; char **exTestBuildLogLines = LoadTextLines(exTestBuildLog, &exTestBuildLogLinesCount); - for (int k = 0, index = 0; k < exTestBuildLogLinesCount; k++) + for (int k = 0; k < exTestBuildLogLinesCount; k++) { // Checking compilation warnings generated if (TextFindIndex(exTestBuildLogLines[k], "warning:") >= 0) testing[i].buildwarns++; @@ -1710,7 +1710,7 @@ int main(int argc, char *argv[]) // Load build log text lines int exTestLogLinesCount = 0; char **exTestLogLines = LoadTextLines(exTestLog, &exTestLogLinesCount); - for (int k = 0, index = 0; k < exTestLogLinesCount; k++) + for (int k = 0; k < exTestLogLinesCount; k++) { if (TextFindIndex(exTestLogLines[k], "WARNING: GL: NPOT") >= 0) continue; // Ignore warning if (TextFindIndex(exTestLogLines[k], "WARNING") >= 0) testing[i].warnings++; @@ -2136,7 +2136,7 @@ static int UpdateRequiredFiles(void) mdIndex += sprintf(mdTextUpdated + mdListStartIndex + mdIndex, "\nSome example missing? As always, contributions are welcome, feel free to send new examples!\n"); mdIndex += sprintf(mdTextUpdated + mdListStartIndex + mdIndex, - "Here is an[examples template](examples_template.c) with instructions to start with!\n"); + "Here is an [examples template](examples_template.c) with instructions to start with!\n"); // Save updated file SaveFileText(TextFormat("%s/README.md", exBasePath), mdTextUpdated); From bd21d749145aa230b26d20d71397b24b22810bef Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 13:08:11 +0100 Subject: [PATCH 138/430] Update examples_testing_web.md --- tools/rexm/reports/examples_testing_web.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tools/rexm/reports/examples_testing_web.md b/tools/rexm/reports/examples_testing_web.md index e969c48aa..2e8cb5fb3 100644 --- a/tools/rexm/reports/examples_testing_web.md +++ b/tools/rexm/reports/examples_testing_web.md @@ -17,19 +17,7 @@ Example automated testing elements validated: | **EXAMPLE NAME** | [CWARN] | [LWARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | |:---------------------------------|:-------:|:-------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| | core_monitor_detector | 0 | 1 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| core_directory_files | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| core_clipboard_text | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| core_compute_hash | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_recursive_tree | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_ring_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_circle_sector_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_rounded_rectangle_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_splines_drawing | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_triangle_strip | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_pie_chart | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_math_sine_cosine | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shapes_lines_drawing | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| shapes_rlgl_color_wheel | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | textures_screen_buffer | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | textures_sprite_stacking | 0 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | | text_sprite_fonts | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | @@ -43,7 +31,6 @@ Example automated testing elements validated: | models_bone_socket | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | | models_decals | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | shaders_postprocessing | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | -| shaders_color_correction | 1 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_deferred_rendering | 0 | 2 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | | shaders_shadowmap_rendering | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | | shaders_basic_pbr | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | From 0b9f463e64c88d82ca8687f99af6cdb965fbee86 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 13:18:10 +0100 Subject: [PATCH 139/430] REVIEWED: examples: Replace TABS and Remove trailing spaces --- .../audio/audio_fft_spectrum_visualizer.c | 8 +- examples/core/core_compute_hash.c | 2 +- examples/core/core_delta_time.c | 4 +- examples/core/core_input_actions.c | 32 +-- examples/core/core_input_gamepad.c | 4 +- examples/core/core_input_gestures.c | 2 +- examples/core/core_input_virtual_controls.c | 6 +- examples/core/core_monitor_detector.c | 20 +- examples/core/core_undo_redo.c | 4 +- examples/core/core_viewport_scaling.c | 4 +- .../models/models_directional_billboard.c | 2 +- examples/models/models_loading_vox.c | 208 +++++++++--------- examples/models/models_rotating_cube.c | 16 +- examples/others/easings_testbed.c | 2 +- examples/shaders/shaders_ascii_rendering.c | 6 +- examples/shaders/shaders_basic_pbr.c | 4 +- examples/shaders/shaders_lightmap_rendering.c | 6 +- .../shaders/shaders_shadowmap_rendering.c | 22 +- .../shaders/shaders_spotlight_rendering.c | 4 +- examples/shapes/shapes_bouncing_ball.c | 10 +- examples/shapes/shapes_clock_of_clocks.c | 76 +++---- examples/shapes/shapes_digital_clock.c | 28 +-- examples/shapes/shapes_kaleidoscope.c | 18 +- examples/shapes/shapes_lines_drawing.c | 181 ++++++++------- examples/shapes/shapes_math_sine_cosine.c | 18 +- examples/shapes/shapes_mouse_trail.c | 20 +- examples/shapes/shapes_pie_chart.c | 16 +- examples/shapes/shapes_recursive_tree.c | 12 +- examples/shapes/shapes_rlgl_triangle.c | 12 +- examples/shapes/shapes_simple_particles.c | 54 ++--- examples/shapes/shapes_triangle_strip.c | 6 +- examples/shapes/shapes_vector_angle.c | 2 +- examples/text/text_inline_styling.c | 34 +-- examples/text/text_unicode_ranges.c | 14 +- examples/text/text_words_alignment.c | 22 +- examples/textures/textures_screen_buffer.c | 8 +- 36 files changed, 440 insertions(+), 447 deletions(-) diff --git a/examples/audio/audio_fft_spectrum_visualizer.c b/examples/audio/audio_fft_spectrum_visualizer.c index c667b5d6a..5993186ab 100644 --- a/examples/audio/audio_fft_spectrum_visualizer.c +++ b/examples/audio/audio_fft_spectrum_visualizer.c @@ -86,7 +86,7 @@ int main(void) Vector2 iResolution = { (float)screenWidth, (float)screenHeight }; Shader shader = LoadShader(0, TextFormat("resources/shaders/glsl%i/fft.fs", GLSL_VERSION)); - + int iResolutionLocation = GetShaderLocation(shader, "iResolution"); int iChannel0Location = GetShaderLocation(shader, "iChannel0"); SetShaderValue(shader, iResolutionLocation, &iResolution, SHADER_UNIFORM_VEC2); @@ -153,16 +153,16 @@ int main(void) // Draw //---------------------------------------------------------------------------------- BeginDrawing(); - + ClearBackground(RAYWHITE); - + BeginShaderMode(shader); SetShaderValueTexture(shader, iChannel0Location, fftTexture); DrawTextureRec(bufferA.texture, (Rectangle){ 0, 0, (float)screenWidth, (float)-screenHeight }, (Vector2){ 0, 0 }, WHITE); EndShaderMode(); - + EndDrawing(); //------------------------------------------------------------------------------ } diff --git a/examples/core/core_compute_hash.c b/examples/core/core_compute_hash.c index 376e2d65c..505ffea7b 100644 --- a/examples/core/core_compute_hash.c +++ b/examples/core/core_compute_hash.c @@ -64,7 +64,7 @@ int main(void) // Encode data to Base64 string (includes NULL terminator), memory must be MemFree() base64Text = EncodeDataBase64((unsigned char *)textInput, textInputLen, &base64TextSize); - + hashCRC32 = ComputeCRC32((unsigned char *)textInput, textInputLen); // Compute CRC32 hash code (4 bytes) hashMD5 = ComputeMD5((unsigned char *)textInput, textInputLen); // Compute MD5 hash code, returns static int[4] (16 bytes) hashSHA1 = ComputeSHA1((unsigned char *)textInput, textInputLen); // Compute SHA1 hash code, returns static int[5] (20 bytes) diff --git a/examples/core/core_delta_time.c b/examples/core/core_delta_time.c index e52c743da..b77957ed6 100644 --- a/examples/core/core_delta_time.c +++ b/examples/core/core_delta_time.c @@ -59,7 +59,7 @@ int main(void) // GetFrameTime() returns the time it took to draw the last frame, in seconds (usually called delta time) // Uses the delta time to make the circle look like it's moving at a "consistent" speed regardless of FPS - // Multiply by 6.0 (an arbitrary value) in order to make the speed + // Multiply by 6.0 (an arbitrary value) in order to make the speed // visually closer to the other circle (at 60 fps), for comparison deltaCircle.x += GetFrameTime()*6.0f*speed; // This circle can move faster or slower visually depending on the FPS @@ -68,7 +68,7 @@ int main(void) // If either circle is off the screen, reset it back to the start if (deltaCircle.x > screenWidth) deltaCircle.x = 0; if (frameCircle.x > screenWidth) frameCircle.x = 0; - + // Reset both circles positions if (IsKeyPressed(KEY_R)) { diff --git a/examples/core/core_input_actions.c b/examples/core/core_input_actions.c index cbf7e0e92..f4b1156d8 100644 --- a/examples/core/core_input_actions.c +++ b/examples/core/core_input_actions.c @@ -17,7 +17,7 @@ // Simple example for decoding input as actions, allowing remapping of input to different keys or gamepad buttons // For example instead of using `IsKeyDown(KEY_LEFT)`, you can use `IsActionDown(ACTION_LEFT)` -// which can be reassigned to e.g. KEY_A and also assigned to a gamepad button. the action will trigger with either gamepad or keys +// which can be reassigned to e.g. KEY_A and also assigned to a gamepad button. the action will trigger with either gamepad or keys #include "raylib.h" @@ -44,7 +44,7 @@ typedef struct ActionInput { // Global Variables Definition //---------------------------------------------------------------------------------- static int gamepadIndex = 0; // Gamepad default index -static ActionInput actionInputs[MAX_ACTION] = { 0 }; +static ActionInput actionInputs[MAX_ACTION] = { 0 }; //---------------------------------------------------------------------------------- // Module Functions Declaration @@ -67,15 +67,15 @@ int main(void) const int screenHeight = 450; InitWindow(screenWidth, screenHeight, "raylib [core] example - input actions"); - - // Set default actions + + // Set default actions char actionSet = 0; SetActionsDefault(); bool releaseAction = false; Vector2 position = (Vector2){ 400.0f, 200.0f }; Vector2 size = (Vector2){ 40.0f, 40.0f }; - + SetTargetFPS(60); //-------------------------------------------------------------------------------------- @@ -85,7 +85,7 @@ int main(void) // Update //---------------------------------------------------------------------------------- gamepadIndex = 0; // Set gamepad being checked - + if (IsActionDown(ACTION_UP)) position.y -= 2; if (IsActionDown(ACTION_DOWN)) position.y += 2; if (IsActionDown(ACTION_LEFT)) position.x -= 2; @@ -95,12 +95,12 @@ int main(void) position.x = (screenWidth-size.x)/2; position.y = (screenHeight-size.y)/2; } - + // Register release action for one frame releaseAction = false; if (IsActionReleased(ACTION_FIRE)) releaseAction = true; - // Switch control scheme by pressing TAB + // Switch control scheme by pressing TAB if (IsKeyPressed(KEY_TAB)) { actionSet = !actionSet; @@ -116,7 +116,7 @@ int main(void) ClearBackground(GRAY); DrawRectangleV(position, size, releaseAction? BLUE : RED); - + DrawText((actionSet == 0)? "Current input set: WASD (default)" : "Current input set: Cursor", 10, 10, 20, WHITE); DrawText("Use TAB key to toggles Actions keyset", 10, 50, 20, GREEN); @@ -140,9 +140,9 @@ int main(void) static bool IsActionPressed(int action) { bool result = false; - + if (action < MAX_ACTION) result = (IsKeyPressed(actionInputs[action].key) || IsGamepadButtonPressed(gamepadIndex, actionInputs[action].button)); - + return result; } @@ -151,20 +151,20 @@ static bool IsActionPressed(int action) static bool IsActionReleased(int action) { bool result = false; - + if (action < MAX_ACTION) result = (IsKeyReleased(actionInputs[action].key) || IsGamepadButtonReleased(gamepadIndex, actionInputs[action].button)); - + return result; } // Check action key/button down // NOTE: Combines key down and gamepad button down in one action -static bool IsActionDown(int action) +static bool IsActionDown(int action) { bool result = false; - + if (action < MAX_ACTION) result = (IsKeyDown(actionInputs[action].key) || IsGamepadButtonDown(gamepadIndex, actionInputs[action].button)); - + return result; } diff --git a/examples/core/core_input_gamepad.c b/examples/core/core_input_gamepad.c index b64e0c1a0..3c9454318 100644 --- a/examples/core/core_input_gamepad.c +++ b/examples/core/core_input_gamepad.c @@ -50,7 +50,7 @@ int main(void) const float rightStickDeadzoneY = 0.1f; const float leftTriggerDeadzone = -0.9f; const float rightTriggerDeadzone = -0.9f; - + Rectangle vibrateButton = { 0 }; SetTargetFPS(60); // Set our game to run at 60 frames-per-second @@ -97,7 +97,7 @@ int main(void) if (leftTrigger < leftTriggerDeadzone) leftTrigger = -1.0f; if (rightTrigger < rightTriggerDeadzone) rightTrigger = -1.0f; - if ((TextFindIndex(TextToLower(GetGamepadName(gamepad)), XBOX_ALIAS_1) > -1) || + if ((TextFindIndex(TextToLower(GetGamepadName(gamepad)), XBOX_ALIAS_1) > -1) || (TextFindIndex(TextToLower(GetGamepadName(gamepad)), XBOX_ALIAS_2) > -1)) { DrawTexture(texXboxPad, 0, 0, DARKGRAY); diff --git a/examples/core/core_input_gestures.c b/examples/core/core_input_gestures.c index 168e2a0c3..e9f43ee3b 100644 --- a/examples/core/core_input_gestures.c +++ b/examples/core/core_input_gestures.c @@ -118,6 +118,6 @@ int main(void) //-------------------------------------------------------------------------------------- CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- - + return 0; } \ No newline at end of file diff --git a/examples/core/core_input_virtual_controls.c b/examples/core/core_input_virtual_controls.c index 6cae4b6c7..db293b993 100644 --- a/examples/core/core_input_virtual_controls.c +++ b/examples/core/core_input_virtual_controls.c @@ -6,7 +6,7 @@ * * Example originally created with raylib 5.0, last time updated with raylib 5.0 * -* Example contributed by GreenSnakeLinux (@GreenSnakeLinux), +* Example contributed by GreenSnakeLinux (@GreenSnakeLinux), * reviewed by Ramon Santamaria (@raysan5), oblerion (@oblerion) and danilwhale (@danilwhale) * * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, @@ -86,7 +86,7 @@ int main(void) pressedButton = BUTTON_NONE; // Make sure user is pressing left mouse button if they're from desktop - if ((GetTouchPointCount() > 0) || + if ((GetTouchPointCount() > 0) || ((GetTouchPointCount() == 0) && IsMouseButtonDown(MOUSE_BUTTON_LEFT))) { // Find nearest D-Pad button to the input position @@ -113,7 +113,7 @@ int main(void) default: break; }; //-------------------------------------------------------------------------- - + // Draw //-------------------------------------------------------------------------- BeginDrawing(); diff --git a/examples/core/core_monitor_detector.c b/examples/core/core_monitor_detector.c index 08e06c811..720449d65 100644 --- a/examples/core/core_monitor_detector.c +++ b/examples/core/core_monitor_detector.c @@ -66,25 +66,25 @@ int main(void) for (int i = 0; i < monitorCount; i++) { monitors[i] = (MonitorInfo){ - GetMonitorPosition(i), - GetMonitorName(i), + GetMonitorPosition(i), + GetMonitorName(i), GetMonitorWidth(i), GetMonitorHeight(i), GetMonitorPhysicalWidth(i), GetMonitorPhysicalHeight(i), GetMonitorRefreshRate(i) }; - + if (monitors[i].position.x < monitorOffsetX) monitorOffsetX = -(int)monitors[i].position.x; const int width = (int)monitors[i].position.x + monitors[i].width; const int height = (int)monitors[i].position.y + monitors[i].height; - + if (maxWidth < width) maxWidth = width; if (maxHeight < height) maxHeight = height; } - if (IsKeyPressed(KEY_ENTER) && (monitorCount > 1)) + if (IsKeyPressed(KEY_ENTER) && (monitorCount > 1)) { currentMonitorIndex += 1; @@ -95,8 +95,8 @@ int main(void) } else currentMonitorIndex = GetCurrentMonitor(); // Get currentMonitorIndex if manually moved - float monitorScale = 0.6f; - + float monitorScale = 0.6f; + if (maxHeight > (maxWidth + monitorOffsetX)) monitorScale *= ((float)screenHeight/(float)maxHeight); else monitorScale *= ((float)screenWidth/(float)(maxWidth + monitorOffsetX)); //---------------------------------------------------------------------------------- @@ -125,9 +125,9 @@ int main(void) // Draw monitor name and information inside the rectangle DrawText(TextFormat("[%i] %s", i, monitors[i].name), (int)rec.x + 10, (int)rec.y + (int)(100*monitorScale), (int)(120*monitorScale), BLUE); DrawText( - TextFormat("Resolution: [%ipx x %ipx]\nRefreshRate: [%ihz]\nPhysical Size: [%imm x %imm]\nPosition: %3.0f x %3.0f", - monitors[i].width, - monitors[i].height, + TextFormat("Resolution: [%ipx x %ipx]\nRefreshRate: [%ihz]\nPhysical Size: [%imm x %imm]\nPosition: %3.0f x %3.0f", + monitors[i].width, + monitors[i].height, monitors[i].refreshRate, monitors[i].physicalWidth, monitors[i].physicalHeight, diff --git a/examples/core/core_undo_redo.c b/examples/core/core_undo_redo.c index 45b19e10f..c49ad9e6f 100644 --- a/examples/core/core_undo_redo.c +++ b/examples/core/core_undo_redo.c @@ -22,7 +22,7 @@ #define MAX_UNDO_STATES 26 // Maximum undo states supported for the ring buffer -#define GRID_CELL_SIZE 24 +#define GRID_CELL_SIZE 24 #define MAX_GRID_CELLS_X 30 #define MAX_GRID_CELLS_Y 13 @@ -57,7 +57,7 @@ int main(void) //-------------------------------------------------------------------------------------- const int screenWidth = 800; const int screenHeight = 450; - + // We have multiple options to implement an Undo/Redo system // Probably the most professional one is using the Command pattern to // define Actions and store those actions into an array as the events happen, diff --git a/examples/core/core_viewport_scaling.c b/examples/core/core_viewport_scaling.c index 3044dd0af..28ee422cd 100644 --- a/examples/core/core_viewport_scaling.c +++ b/examples/core/core_viewport_scaling.c @@ -20,7 +20,7 @@ // For itteration purposes and teaching example #define RESOLUTION_COUNT 4 -enum ViewportType +enum ViewportType { // Only upscale, useful for pixel art KEEP_ASPECT_INTEGER, @@ -113,7 +113,7 @@ int main(void) } Vector2 mousePosition = GetMousePosition(); bool mousePressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); - + // Check buttons and rescale if (CheckCollisionPointRec(mousePosition, decreaseResolutionButton) && mousePressed){ resolutionIndex = (resolutionIndex + RESOLUTION_COUNT - 1) % RESOLUTION_COUNT; diff --git a/examples/models/models_directional_billboard.c b/examples/models/models_directional_billboard.c index f471da4d0..e2c75c15a 100644 --- a/examples/models/models_directional_billboard.c +++ b/examples/models/models_directional_billboard.c @@ -62,7 +62,7 @@ int main(void) anim_timer += GetFrameTime(); // Update frame index after a certain amount of time (half a second) - if (anim_timer > 0.5f) + if (anim_timer > 0.5f) { anim_timer = 0.0f; anim += 1; diff --git a/examples/models/models_loading_vox.c b/examples/models/models_loading_vox.c index 6675f3fd7..47be07ee4 100644 --- a/examples/models/models_loading_vox.c +++ b/examples/models/models_loading_vox.c @@ -35,122 +35,122 @@ //------------------------------------------------------------------------------------ int main(void) { - // Initialization - //-------------------------------------------------------------------------------------- - const int screenWidth = 800; - const int screenHeight = 450; + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; - const char *voxFileNames[] = { - "resources/models/vox/chr_knight.vox", - "resources/models/vox/chr_sword.vox", - "resources/models/vox/monu9.vox", - "resources/models/vox/fez.vox" - }; + const char *voxFileNames[] = { + "resources/models/vox/chr_knight.vox", + "resources/models/vox/chr_sword.vox", + "resources/models/vox/monu9.vox", + "resources/models/vox/fez.vox" + }; - InitWindow(screenWidth, screenHeight, "raylib [models] example - loading vox"); + InitWindow(screenWidth, screenHeight, "raylib [models] example - loading vox"); - // Define the camera to look into our 3d world - Camera camera = { 0 }; - camera.position = (Vector3){ 10.0f, 10.0f, 10.0f }; // Camera position - camera.target = (Vector3){ 0.0f, 0.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 - camera.projection = CAMERA_PERSPECTIVE; // Camera projection type + // Define the camera to look into our 3d world + Camera camera = { 0 }; + camera.position = (Vector3){ 10.0f, 10.0f, 10.0f }; // Camera position + camera.target = (Vector3){ 0.0f, 0.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 + camera.projection = CAMERA_PERSPECTIVE; // Camera projection type - // Load MagicaVoxel files - Model models[MAX_VOX_FILES] = { 0 }; + // Load MagicaVoxel files + Model models[MAX_VOX_FILES] = { 0 }; - for (int i = 0; i < MAX_VOX_FILES; i++) - { - // Load VOX file and measure time - double t0 = GetTime()*1000.0; - models[i] = LoadModel(voxFileNames[i]); - double t1 = GetTime()*1000.0; + for (int i = 0; i < MAX_VOX_FILES; i++) + { + // Load VOX file and measure time + double t0 = GetTime()*1000.0; + models[i] = LoadModel(voxFileNames[i]); + double t1 = GetTime()*1000.0; - TraceLog(LOG_INFO, TextFormat("[%s] Model file loaded in %.3f ms", voxFileNames[i], t1 - t0)); + TraceLog(LOG_INFO, TextFormat("[%s] Model file loaded in %.3f ms", voxFileNames[i], t1 - t0)); - // Compute model translation matrix to center model on draw position (0, 0 , 0) - BoundingBox bb = GetModelBoundingBox(models[i]); - Vector3 center = { 0 }; - center.x = bb.min.x + (((bb.max.x - bb.min.x)/2)); - center.z = bb.min.z + (((bb.max.z - bb.min.z)/2)); + // Compute model translation matrix to center model on draw position (0, 0 , 0) + BoundingBox bb = GetModelBoundingBox(models[i]); + Vector3 center = { 0 }; + center.x = bb.min.x + (((bb.max.x - bb.min.x)/2)); + center.z = bb.min.z + (((bb.max.z - bb.min.z)/2)); - Matrix matTranslate = MatrixTranslate(-center.x, 0, -center.z); - models[i].transform = matTranslate; - } + Matrix matTranslate = MatrixTranslate(-center.x, 0, -center.z); + models[i].transform = matTranslate; + } - int currentModel = 0; - Vector3 modelpos = { 0 }; - Vector3 camerarot = { 0 }; + int currentModel = 0; + Vector3 modelpos = { 0 }; + Vector3 camerarot = { 0 }; - // Load voxel shader - Shader shader = LoadShader(TextFormat("resources/shaders/glsl%i/voxel_lighting.vs", GLSL_VERSION), - TextFormat("resources/shaders/glsl%i/voxel_lighting.fs", GLSL_VERSION)); + // Load voxel shader + Shader shader = LoadShader(TextFormat("resources/shaders/glsl%i/voxel_lighting.vs", GLSL_VERSION), + TextFormat("resources/shaders/glsl%i/voxel_lighting.fs", GLSL_VERSION)); - // Get some required shader locations - shader.locs[SHADER_LOC_VECTOR_VIEW] = GetShaderLocation(shader, "viewPos"); - // NOTE: "matModel" location name is automatically assigned on shader loading, - // no need to get the location again if using that uniform name - //shader.locs[SHADER_LOC_MATRIX_MODEL] = GetShaderLocation(shader, "matModel"); + // Get some required shader locations + shader.locs[SHADER_LOC_VECTOR_VIEW] = GetShaderLocation(shader, "viewPos"); + // NOTE: "matModel" location name is automatically assigned on shader loading, + // no need to get the location again if using that uniform name + //shader.locs[SHADER_LOC_MATRIX_MODEL] = GetShaderLocation(shader, "matModel"); - // Ambient light level (some basic lighting) - int ambientLoc = GetShaderLocation(shader, "ambient"); - SetShaderValue(shader, ambientLoc, (float[4]) { 0.1f, 0.1f, 0.1f, 1.0f }, SHADER_UNIFORM_VEC4); + // Ambient light level (some basic lighting) + int ambientLoc = GetShaderLocation(shader, "ambient"); + SetShaderValue(shader, ambientLoc, (float[4]) { 0.1f, 0.1f, 0.1f, 1.0f }, SHADER_UNIFORM_VEC4); - // Assign out lighting shader to model - for (int i = 0; i < MAX_VOX_FILES; i++) - { - for (int j = 0; j < models[i].materialCount; j++) models[i].materials[j].shader = shader; - } + // Assign out lighting shader to model + for (int i = 0; i < MAX_VOX_FILES; i++) + { + for (int j = 0; j < models[i].materialCount; j++) models[i].materials[j].shader = shader; + } - // Create lights - Light lights[MAX_LIGHTS] = { 0 }; - lights[0] = CreateLight(LIGHT_POINT, (Vector3) { -20, 20, -20 }, Vector3Zero(), GRAY, shader); - lights[1] = CreateLight(LIGHT_POINT, (Vector3) { 20, -20, 20 }, Vector3Zero(), GRAY, shader); - lights[2] = CreateLight(LIGHT_POINT, (Vector3) { -20, 20, 20 }, Vector3Zero(), GRAY, shader); - lights[3] = CreateLight(LIGHT_POINT, (Vector3) { 20, -20, -20 }, Vector3Zero(), GRAY, shader); + // Create lights + Light lights[MAX_LIGHTS] = { 0 }; + lights[0] = CreateLight(LIGHT_POINT, (Vector3) { -20, 20, -20 }, Vector3Zero(), GRAY, shader); + lights[1] = CreateLight(LIGHT_POINT, (Vector3) { 20, -20, 20 }, Vector3Zero(), GRAY, shader); + lights[2] = CreateLight(LIGHT_POINT, (Vector3) { -20, 20, 20 }, Vector3Zero(), GRAY, shader); + lights[3] = CreateLight(LIGHT_POINT, (Vector3) { 20, -20, -20 }, Vector3Zero(), GRAY, shader); - SetTargetFPS(60); // Set our game to run at 60 frames-per-second - //-------------------------------------------------------------------------------------- + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- - // Main game loop - while (!WindowShouldClose()) // Detect window close button or ESC key - { - // Update - //---------------------------------------------------------------------------------- - if (IsMouseButtonDown(MOUSE_BUTTON_MIDDLE)) - { - const Vector2 mouseDelta = GetMouseDelta(); - camerarot.x = mouseDelta.x*0.05f; - camerarot.y = mouseDelta.y*0.05f; - } - else - { - camerarot.x = 0; - camerarot.y = 0; - } + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + if (IsMouseButtonDown(MOUSE_BUTTON_MIDDLE)) + { + const Vector2 mouseDelta = GetMouseDelta(); + camerarot.x = mouseDelta.x*0.05f; + camerarot.y = mouseDelta.y*0.05f; + } + else + { + camerarot.x = 0; + camerarot.y = 0; + } - UpdateCameraPro(&camera, - (Vector3){ (IsKeyDown(KEY_W) || IsKeyDown(KEY_UP))*0.1f - (IsKeyDown(KEY_S) || IsKeyDown(KEY_DOWN))*0.1f, // Move forward-backward + UpdateCameraPro(&camera, + (Vector3){ (IsKeyDown(KEY_W) || IsKeyDown(KEY_UP))*0.1f - (IsKeyDown(KEY_S) || IsKeyDown(KEY_DOWN))*0.1f, // Move forward-backward (IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT))*0.1f - (IsKeyDown(KEY_A) || IsKeyDown(KEY_LEFT))*0.1f, // Move right-left 0.0f }, // Move up-down - camerarot, // Camera rotation - GetMouseWheelMove()*-2.0f); // Move to target (zoom) + camerarot, // Camera rotation + GetMouseWheelMove()*-2.0f); // Move to target (zoom) - // Cycle between models on mouse click - if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) currentModel = (currentModel + 1) % MAX_VOX_FILES; + // Cycle between models on mouse click + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) currentModel = (currentModel + 1) % MAX_VOX_FILES; - // Update the shader with the camera view vector (points towards { 0.0f, 0.0f, 0.0f }) - float cameraPos[3] = { camera.position.x, camera.position.y, camera.position.z }; - SetShaderValue(shader, shader.locs[SHADER_LOC_VECTOR_VIEW], cameraPos, SHADER_UNIFORM_VEC3); + // Update the shader with the camera view vector (points towards { 0.0f, 0.0f, 0.0f }) + float cameraPos[3] = { camera.position.x, camera.position.y, camera.position.z }; + SetShaderValue(shader, shader.locs[SHADER_LOC_VECTOR_VIEW], cameraPos, SHADER_UNIFORM_VEC3); - // Update light values (actually, only enable/disable them) - for (int i = 0; i < MAX_LIGHTS; i++) UpdateLightValues(shader, lights[i]); - //---------------------------------------------------------------------------------- - - // Draw - //---------------------------------------------------------------------------------- - BeginDrawing(); + // Update light values (actually, only enable/disable them) + for (int i = 0; i < MAX_LIGHTS; i++) UpdateLightValues(shader, lights[i]); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); ClearBackground(RAYWHITE); @@ -175,17 +175,17 @@ int main(void) DrawText("- UP-DOWN-LEFT-RIGHT KEYS: MOVE CAMERA", 20, 90, 10, BLUE); DrawText(TextFormat("Model file: %s", GetFileName(voxFileNames[currentModel])), 10, 10, 20, GRAY); - EndDrawing(); - //---------------------------------------------------------------------------------- - } + EndDrawing(); + //---------------------------------------------------------------------------------- + } - // De-Initialization - //-------------------------------------------------------------------------------------- - // Unload models data (GPU VRAM) - for (int i = 0; i < MAX_VOX_FILES; i++) UnloadModel(models[i]); + // De-Initialization + //-------------------------------------------------------------------------------------- + // Unload models data (GPU VRAM) + for (int i = 0; i < MAX_VOX_FILES; i++) UnloadModel(models[i]); - CloseWindow(); // Close window and OpenGL context - //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- - return 0; + return 0; } diff --git a/examples/models/models_rotating_cube.c b/examples/models/models_rotating_cube.c index 930dae516..c5b633fea 100644 --- a/examples/models/models_rotating_cube.c +++ b/examples/models/models_rotating_cube.c @@ -5,7 +5,7 @@ * Example complexity rating: [★☆☆☆] 1/4 * * Example originally created with raylib 5.6-dev, last time updated with raylib 5.6-dev -* +* * Example contributed by Jopestpe (@jopestpe) * * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, @@ -48,7 +48,7 @@ int main(void) model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = texture; float rotation = 0.0f; - + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- @@ -59,7 +59,7 @@ int main(void) //---------------------------------------------------------------------------------- rotation += 1.0f; //---------------------------------------------------------------------------------- - + // Draw //---------------------------------------------------------------------------------- BeginDrawing(); @@ -67,13 +67,13 @@ int main(void) ClearBackground(RAYWHITE); BeginMode3D(camera); - - // Draw model defining: position, size, rotation-axis, rotation (degrees), size, and tint-color - DrawModelEx(model, (Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.5f, 1.0f, 0.0f }, + + // Draw model defining: position, size, rotation-axis, rotation (degrees), size, and tint-color + DrawModelEx(model, (Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.5f, 1.0f, 0.0f }, rotation, (Vector3){ 1.0f, 1.0f, 1.0f }, WHITE); - + DrawGrid(10, 1.0f); - + EndMode3D(); DrawFPS(10, 10); diff --git a/examples/others/easings_testbed.c b/examples/others/easings_testbed.c index b3ff8a8df..fa4a599ee 100644 --- a/examples/others/easings_testbed.c +++ b/examples/others/easings_testbed.c @@ -221,7 +221,7 @@ int main(void) } -// NoEase function, used when "no easing" is selected for any axis +// NoEase function, used when "no easing" is selected for any axis // It just ignores all parameters besides b static float NoEase(float t, float b, float c, float d) { diff --git a/examples/shaders/shaders_ascii_rendering.c b/examples/shaders/shaders_ascii_rendering.c index 523b381b2..d3302bf53 100644 --- a/examples/shaders/shaders_ascii_rendering.c +++ b/examples/shaders/shaders_ascii_rendering.c @@ -87,15 +87,15 @@ int main(void) DrawTexture(fudesumi, 500, -30, WHITE); DrawTextureV(raysan, circlePos, WHITE); EndTextureMode(); - + BeginDrawing(); ClearBackground(RAYWHITE); BeginShaderMode(shader); // Draw the scene texture (that we rendered earlier) to the screen // The shader will process every pixel of this texture - DrawTextureRec(target.texture, - (Rectangle){ 0, 0, (float)target.texture.width, (float)-target.texture.height }, + DrawTextureRec(target.texture, + (Rectangle){ 0, 0, (float)target.texture.width, (float)-target.texture.height }, (Vector2){ 0, 0 }, WHITE); EndShaderMode(); diff --git a/examples/shaders/shaders_basic_pbr.c b/examples/shaders/shaders_basic_pbr.c index cc8583830..7ee05502d 100644 --- a/examples/shaders/shaders_basic_pbr.c +++ b/examples/shaders/shaders_basic_pbr.c @@ -242,7 +242,7 @@ int main(void) SetShaderValue(shader, emissiveColorLoc, &carEmissiveColor, SHADER_UNIFORM_VEC4); float emissiveIntensity = 0.01f; SetShaderValue(shader, emissiveIntensityLoc, &emissiveIntensity, SHADER_UNIFORM_FLOAT); - + // Set old car metallic and roughness values SetShaderValue(shader, metallicValueLoc, &car.materials[0].maps[MATERIAL_MAP_METALNESS].value, SHADER_UNIFORM_FLOAT); SetShaderValue(shader, roughnessValueLoc, &car.materials[0].maps[MATERIAL_MAP_ROUGHNESS].value, SHADER_UNIFORM_FLOAT); @@ -252,7 +252,7 @@ int main(void) // Draw spheres to show the lights positions for (int i = 0; i < MAX_LIGHTS; i++) { - Color lightColor = (Color){ + Color lightColor = (Color){ (unsigned char)(lights[i].color[0]*255), (unsigned char)(lights[i].color[1] * 255), (unsigned char)(lights[i].color[2] * 255), diff --git a/examples/shaders/shaders_lightmap_rendering.c b/examples/shaders/shaders_lightmap_rendering.c index 51651cfd3..05d9c0e09 100644 --- a/examples/shaders/shaders_lightmap_rendering.c +++ b/examples/shaders/shaders_lightmap_rendering.c @@ -124,7 +124,7 @@ int main(void) ); BeginBlendMode(BLEND_ALPHA); EndTextureMode(); - + // NOTE: To enable trilinear filtering we need mipmaps available for texture GenTextureMipmaps(&lightmap.texture); SetTextureFilter(lightmap.texture, TEXTURE_FILTER_TRILINEAR); @@ -143,7 +143,7 @@ int main(void) // Draw //---------------------------------------------------------------------------------- BeginDrawing(); - + ClearBackground(RAYWHITE); BeginMode3D(camera); @@ -155,7 +155,7 @@ int main(void) (Vector2){ 0.0, 0.0 }, 0.0, WHITE); DrawText(TextFormat("LIGHTMAP: %ix%i pixels", MAP_SIZE, MAP_SIZE), GetRenderWidth() - 130, 20 + MAP_SIZE*8, 10, GREEN); - + DrawFPS(10, 10); EndDrawing(); diff --git a/examples/shaders/shaders_shadowmap_rendering.c b/examples/shaders/shaders_shadowmap_rendering.c index 7b75b80e0..a739cdd93 100644 --- a/examples/shaders/shaders_shadowmap_rendering.c +++ b/examples/shaders/shaders_shadowmap_rendering.c @@ -45,7 +45,7 @@ int main(void) // Shadows are a HUGE topic, and this example shows an extremely simple implementation of the shadowmapping algorithm, // which is the industry standard for shadows. This algorithm can be extended in a ridiculous number of ways to improve // realism and also adapt it for different scenes. This is pretty much the simplest possible implementation - + SetConfigFlags(FLAG_MSAA_4X_HINT); InitWindow(screenWidth, screenHeight, "raylib [shaders] example - shadowmap rendering"); @@ -59,7 +59,7 @@ int main(void) Shader shadowShader = LoadShader(TextFormat("resources/shaders/glsl%i/shadowmap.vs", GLSL_VERSION), TextFormat("resources/shaders/glsl%i/shadowmap.fs", GLSL_VERSION)); shadowShader.locs[SHADER_LOC_VECTOR_VIEW] = GetShaderLocation(shadowShader, "viewPos"); - + Vector3 lightDir = Vector3Normalize((Vector3){ 0.35f, -1.0f, -0.35f }); Color lightColor = WHITE; Vector4 lightColorNormalized = ColorNormalize(lightColor); @@ -83,7 +83,7 @@ int main(void) ModelAnimation *robotAnimations = LoadModelAnimations("resources/models/robot.glb", &animCount); RenderTexture2D shadowMap = LoadShadowmapRenderTexture(SHADOWMAP_RESOLUTION, SHADOWMAP_RESOLUTION); - + // For the shadowmapping algorithm, we will be rendering everything from the light's point of view Camera3D lightCamera = { 0 }; lightCamera.position = Vector3Scale(lightDir, -15.0f); @@ -91,9 +91,9 @@ int main(void) lightCamera.projection = CAMERA_ORTHOGRAPHIC; // Use an orthographic projection for directional lights lightCamera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; lightCamera.fovy = 20.0f; - + int frameCounter = 0; - + // Store the light matrices Matrix lightView = { 0 }; Matrix lightProj = { 0 }; @@ -136,7 +136,7 @@ int main(void) { if (lightDir.z > -0.6f) lightDir.z -= cameraSpeed*60.0f*deltaTime; } - + lightDir = Vector3Normalize(lightDir); lightCamera.position = Vector3Scale(lightDir, -15.0f); SetShaderValue(shadowShader, lightDirLoc, &lightDir, SHADER_UNIFORM_VEC3); @@ -151,13 +151,13 @@ int main(void) // to determine whether a given point is "visible" to the light BeginTextureMode(shadowMap); ClearBackground(WHITE); - + BeginMode3D(lightCamera); lightView = rlGetMatrixModelview(); lightProj = rlGetMatrixProjection(); DrawScene(cube, robot); EndMode3D(); - + EndTextureMode(); lightViewProj = MatrixMultiply(lightView, lightProj); @@ -167,7 +167,7 @@ int main(void) SetShaderValueMatrix(shadowShader, lightVPLoc, lightViewProj); rlEnableShader(shadowShader.id); - + rlActiveTextureSlot(textureActiveSlot); rlEnableTexture(shadowMap.depth.id); rlSetUniform(shadowMapLoc, &textureActiveSlot, SHADER_UNIFORM_INT, 1); @@ -178,7 +178,7 @@ int main(void) DrawText("Use the arrow keys to rotate the light!", 10, 10, 30, RED); DrawText("Shadows in raylib using the shadowmapping algorithm!", screenWidth - 280, screenHeight - 20, 10, GRAY); - + EndDrawing(); if (IsKeyPressed(KEY_F)) TakeScreenshot("shaders_shadowmap.png"); @@ -200,7 +200,7 @@ int main(void) } // Load render texture for shadowmap projection -// NOTE: Load framebuffer with only a texture depth attachment, +// NOTE: Load framebuffer with only a texture depth attachment, // no color attachment required for shadowmap static RenderTexture2D LoadShadowmapRenderTexture(int width, int height) { diff --git a/examples/shaders/shaders_spotlight_rendering.c b/examples/shaders/shaders_spotlight_rendering.c index c9ee950c1..3678f9796 100644 --- a/examples/shaders/shaders_spotlight_rendering.c +++ b/examples/shaders/shaders_spotlight_rendering.c @@ -239,7 +239,7 @@ int main(void) static void ResetStar(Star *star) { star->position = (Vector2){ GetScreenWidth()/2.0f, GetScreenHeight()/2.0f }; - + star->speed.x = (float)GetRandomValue(-1000, 1000)/100.0f; star->speed.y = (float)GetRandomValue(-1000, 1000)/100.0f; @@ -247,7 +247,7 @@ static void ResetStar(Star *star) { star->speed.x = (float)GetRandomValue(-1000, 1000)/100.0f; star->speed.y = (float)GetRandomValue(-1000, 1000)/100.0f; - } + } star->position = Vector2Add(star->position, Vector2Multiply(star->speed, (Vector2){ 8.0f, 8.0f })); } diff --git a/examples/shapes/shapes_bouncing_ball.c b/examples/shapes/shapes_bouncing_ball.c index 5ae699944..d55c47f56 100644 --- a/examples/shapes/shapes_bouncing_ball.c +++ b/examples/shapes/shapes_bouncing_ball.c @@ -7,7 +7,7 @@ * Example originally created with raylib 2.5, last time updated with raylib 2.5 * * Example contributed by Ramon Santamaria (@raysan5), reviewed by Jopestpe (@jopestpe) -* +* * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, * BSD-like license that allows static linking with closed source software * @@ -49,14 +49,14 @@ int main(void) //----------------------------------------------------- if (IsKeyPressed(KEY_G)) useGravity = !useGravity; if (IsKeyPressed(KEY_SPACE)) pause = !pause; - + if (!pause) { ballPosition.x += ballSpeed.x; ballPosition.y += ballSpeed.y; if (useGravity) ballSpeed.y += gravity; - + // Check walls collision for bouncing if ((ballPosition.x >= (GetScreenWidth() - ballRadius)) || (ballPosition.x <= ballRadius)) ballSpeed.x *= -1.0f; if ((ballPosition.y >= (GetScreenHeight() - ballRadius)) || (ballPosition.y <= ballRadius)) ballSpeed.y *= -0.95f; @@ -72,7 +72,7 @@ int main(void) DrawCircleV(ballPosition, (float)ballRadius, MAROON); DrawText("PRESS SPACE to PAUSE BALL MOVEMENT", 10, GetScreenHeight() - 25, 20, LIGHTGRAY); - + if (useGravity) DrawText("GRAVITY: ON (Press G to disable)", 10, GetScreenHeight() - 50, 20, DARKGREEN); else DrawText("GRAVITY: OFF (Press G to enable)", 10, GetScreenHeight() - 50, 20, RED); @@ -80,7 +80,7 @@ int main(void) if (pause && ((framesCounter/30)%2)) DrawText("PAUSED", 350, 200, 30, GRAY); DrawFPS(10, 10); - + EndDrawing(); //----------------------------------------------------- } diff --git a/examples/shapes/shapes_clock_of_clocks.c b/examples/shapes/shapes_clock_of_clocks.c index 3fc24aa92..51703a32d 100644 --- a/examples/shapes/shapes_clock_of_clocks.c +++ b/examples/shapes/shapes_clock_of_clocks.c @@ -35,14 +35,14 @@ int main(void) SetConfigFlags(FLAG_MSAA_4X_HINT); InitWindow(screenWidth, screenHeight, "raylib [shapes] example - clock of clocks"); - + const Color bgColor = ColorLerp(DARKBLUE, BLACK, 0.75f); const Color handsColor = ColorLerp(YELLOW, RAYWHITE, .25f); - + const float clockFaceSize = 24; const float clockFaceSpacing = 8.0f; const float sectionSpacing = 16.0f; - + const Vector2 TL = (Vector2){ 0.0f, 90.0f }; // Top-left corner const Vector2 TR = (Vector2){ 90.0f, 180.0f }; // Top-right corner const Vector2 BR = (Vector2){ 180.0f, 270.0f }; // Bottom-right corner @@ -50,7 +50,7 @@ int main(void) const Vector2 HH = (Vector2){ 0.0f, 180.0f }; // Horizontal line const Vector2 VV = (Vector2){ 90.0f, 270.0f }; // Vertical line const Vector2 ZZ = (Vector2){ 135.0f, 135.0f }; // Not relevant - + const Vector2 digitAngles[10][24] = { /* 0 */ { TL,HH,HH,TR, /* */ VV,TL,TR,VV,/* */ VV,VV,VV,VV,/* */ VV,VV,VV,VV,/* */ VV,BL,BR,VV,/* */ BL,HH,HH,BR }, /* 1 */ { TL,HH,TR,ZZ, /* */ BL,TR,VV,ZZ,/* */ ZZ,VV,VV,ZZ,/* */ ZZ,VV,VV,ZZ,/* */ TL,BR,BL,TR,/* */ BL,HH,HH,BR }, @@ -65,21 +65,21 @@ int main(void) }; // Time for the hands to move to the new position (in seconds); this must be <1s const float handsMoveDuration = .5f; - + // We store the previous seconds value so we can see if the time has changed int prevSeconds = -1; - + // This represents the real position where the hands are right now Vector2 currentAngles[6][24] = { 0 }; - + // This is the position where the hands were moving from Vector2 srcAngles[6][24] = { 0 }; // This is the position where the hands are moving to Vector2 dstAngles[6][24] = { 0 }; - + // Current animation timer float handsMoveTimer = 0.0f; - + // 12 or 24 hour mode int hourMode = 24; @@ -91,32 +91,32 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - + // Get the current time time_t rawtime; struct tm *timeinfo; time(&rawtime); timeinfo = localtime(&rawtime); - + if (timeinfo->tm_sec != prevSeconds) { // The time has changed, so we need to move the hands to the new positions prevSeconds = timeinfo->tm_sec; - + // Format the current time so we can access the individual digits const char *clockDigits = TextFormat("%02d%02d%02d", timeinfo->tm_hour % hourMode, timeinfo->tm_min, timeinfo->tm_sec); - + // Fetch where we want all the hands to be for (int digit = 0; digit < 6; digit++) { for (int cell = 0; cell < 24; cell++) { srcAngles[digit][cell] = currentAngles[digit][cell]; dstAngles[digit][cell] = digitAngles[ clockDigits[digit] - '0' ][cell]; - + // Quick exception for 12h mode if (digit == 0 && hourMode == 12 && clockDigits[0] == '0') { dstAngles[digit][cell] = ZZ; } - + if (srcAngles[digit][cell].x > dstAngles[digit][cell].x) { srcAngles[digit][cell].x -= 360.0f; } @@ -125,43 +125,43 @@ int main(void) } } } - + // Reset the timer handsMoveTimer = -GetFrameTime(); } - + // Now let's animate all the hands if we need to if (handsMoveTimer < handsMoveDuration) { // Increase the timer but don't go above the maximum handsMoveTimer = Clamp(handsMoveTimer + GetFrameTime(), 0, handsMoveDuration); - + // Calculate the % completion of the animation float t = handsMoveTimer / handsMoveDuration; - + // A little cheeky smoothstep t = t * t * (3.0f - 2.0f * t); - + for (int digit = 0; digit < 6; digit++) { for (int cell = 0; cell < 24; cell++) { currentAngles[digit][cell].x = Lerp(srcAngles[digit][cell].x, dstAngles[digit][cell].x, t); currentAngles[digit][cell].y = Lerp(srcAngles[digit][cell].y, dstAngles[digit][cell].y, t); } } - + if (handsMoveTimer == handsMoveDuration) { // The animation has now finished } } - + // Handle input - + // Toggle between 12 and 24 hour mode with space if (IsKeyPressed(KEY_SPACE)) { hourMode = 36 - hourMode; } - - - + + + //---------------------------------------------------------------------------------- // Draw @@ -169,13 +169,13 @@ int main(void) BeginDrawing(); ClearBackground(bgColor); - + DrawText(TextFormat("%d-h mode, space to change", hourMode), 10, 30, 20, RAYWHITE); - + float xOffset = 4.0f; - + for (int digit = 0; digit < 6; digit++) { - + for (int row = 0; row < 6; row++) { for (int col = 0; col < 4; col++) { Vector2 centre = (Vector2){ @@ -183,7 +183,7 @@ int main(void) 100 + row*(clockFaceSize+clockFaceSpacing) + clockFaceSize * .5f }; DrawRing(centre, clockFaceSize * 0.5f - 2.0f, clockFaceSize * 0.5f, 0, 360, 24, DARKGRAY); - + // Big hand DrawRectanglePro( (Rectangle){centre.x, centre.y, clockFaceSize*.5f+4.0f, 4.0f}, @@ -191,7 +191,7 @@ int main(void) currentAngles[digit][row*4+col].x, handsColor ); - + // Little hand DrawRectanglePro( (Rectangle){centre.x, centre.y, clockFaceSize*.5f+2.0f, 4.0f}, @@ -201,20 +201,20 @@ int main(void) ); } } - + xOffset += (clockFaceSize+clockFaceSpacing) * 4; if (digit % 2 == 1) { - + DrawRing((Vector2){xOffset + 4.0f, 160.0f}, 6.0f, 8.0f, 0.0f, 360.0f, 24, handsColor); DrawRing((Vector2){xOffset + 4.0f, 225.0f}, 6.0f, 8.0f, 0.0f, 360.0f, 24, handsColor); - + xOffset += sectionSpacing; - + } } - + DrawFPS(10, 10); - + EndDrawing(); //---------------------------------------------------------------------------------- diff --git a/examples/shapes/shapes_digital_clock.c b/examples/shapes/shapes_digital_clock.c index 5f55fa836..cca3f3c44 100644 --- a/examples/shapes/shapes_digital_clock.c +++ b/examples/shapes/shapes_digital_clock.c @@ -116,7 +116,7 @@ int main(void) ClearBackground(RAYWHITE); // Draw clock in selected mode - if (clockMode == CLOCK_ANALOG) DrawClockAnalog(clock, (Vector2){ 400, 240 }); + if (clockMode == CLOCK_ANALOG) DrawClockAnalog(clock, (Vector2){ 400, 240 }); else if (clockMode == CLOCK_DIGITAL) { DrawClockDigital(clock, (Vector2){ 30, 60 }); @@ -128,7 +128,7 @@ int main(void) DrawText(clockTime, GetScreenWidth()/2 - MeasureText(clockTime, 150)/2, 300, 150, BLACK); } - DrawText(TextFormat("Press [SPACE] to switch clock mode: %s", + DrawText(TextFormat("Press [SPACE] to switch clock mode: %s", (clockMode == CLOCK_DIGITAL)? "DIGITAL CLOCK" : "ANALOGUE CLOCK"), 10, 10, 20, DARKGRAY); EndDrawing(); @@ -183,13 +183,13 @@ static void DrawClockAnalog(Clock clock, Vector2 position) // Draw clock minutes/seconds lines for (int i = 0; i < 60; i++) { - DrawLineEx((Vector2){ position.x + (clock.second.length + ((i%5)? 10 : 6))*cosf((6.0f*i - 90.0f)*DEG2RAD), - position.y + (clock.second.length + ((i%5)? 10 : 6))*sinf((6.0f*i - 90.0f)*DEG2RAD) }, - (Vector2){ position.x + (clock.second.length + 20)*cosf((6.0f*i - 90.0f)*DEG2RAD), + DrawLineEx((Vector2){ position.x + (clock.second.length + ((i%5)? 10 : 6))*cosf((6.0f*i - 90.0f)*DEG2RAD), + position.y + (clock.second.length + ((i%5)? 10 : 6))*sinf((6.0f*i - 90.0f)*DEG2RAD) }, + (Vector2){ position.x + (clock.second.length + 20)*cosf((6.0f*i - 90.0f)*DEG2RAD), position.y + (clock.second.length + 20)*sinf((6.0f*i - 90.0f)*DEG2RAD) }, ((i%5)? 1.0f : 3.0f), DARKGRAY); - + // Draw seconds numbers - //DrawText(TextFormat("%02i", i), centerPosition.x + (clock.second.length + 50)*cosf((6.0f*i - 90.0f)*DEG2RAD) - 10/2, + //DrawText(TextFormat("%02i", i), centerPosition.x + (clock.second.length + 50)*cosf((6.0f*i - 90.0f)*DEG2RAD) - 10/2, // centerPosition.y + (clock.second.length + 50)*sinf((6.0f*i - 90.0f)*DEG2RAD) - 10/2, 10, GRAY); } @@ -256,25 +256,25 @@ static void Draw7SDisplay(Vector2 position, char segments, Color colorOn, Color float offsetYAdjust = segmentThick*0.3f; // HACK: Adjust gap space between segment limits // Segment A - DrawDisplaySegment((Vector2){ position.x + segmentThick + segmentLen/2.0f, position.y + segmentThick }, + DrawDisplaySegment((Vector2){ position.x + segmentThick + segmentLen/2.0f, position.y + segmentThick }, segmentLen, segmentThick, false, (segments & 0b00000001)? colorOn : colorOff); // Segment B - DrawDisplaySegment((Vector2){ position.x + segmentThick + segmentLen + segmentThick/2.0f, position.y + 2*segmentThick + segmentLen/2.0f - offsetYAdjust }, + DrawDisplaySegment((Vector2){ position.x + segmentThick + segmentLen + segmentThick/2.0f, position.y + 2*segmentThick + segmentLen/2.0f - offsetYAdjust }, segmentLen, segmentThick, true, (segments & 0b00000010)? colorOn : colorOff); // Segment C - DrawDisplaySegment((Vector2){ position.x + segmentThick + segmentLen + segmentThick/2.0f, position.y + 4*segmentThick + segmentLen + segmentLen/2.0f - 3*offsetYAdjust }, + DrawDisplaySegment((Vector2){ position.x + segmentThick + segmentLen + segmentThick/2.0f, position.y + 4*segmentThick + segmentLen + segmentLen/2.0f - 3*offsetYAdjust }, segmentLen, segmentThick, true, (segments & 0b00000100)? colorOn : colorOff); // Segment D - DrawDisplaySegment((Vector2){ position.x + segmentThick + segmentLen/2.0f, position.y + 5*segmentThick + 2*segmentLen - 4*offsetYAdjust }, + DrawDisplaySegment((Vector2){ position.x + segmentThick + segmentLen/2.0f, position.y + 5*segmentThick + 2*segmentLen - 4*offsetYAdjust }, segmentLen, segmentThick, false, (segments & 0b00001000)? colorOn : colorOff); // Segment E - DrawDisplaySegment((Vector2){ position.x + segmentThick/2.0f, position.y + 4*segmentThick + segmentLen + segmentLen/2.0f - 3*offsetYAdjust }, + DrawDisplaySegment((Vector2){ position.x + segmentThick/2.0f, position.y + 4*segmentThick + segmentLen + segmentLen/2.0f - 3*offsetYAdjust }, segmentLen, segmentThick, true, (segments & 0b00010000)? colorOn : colorOff); // Segment F - DrawDisplaySegment((Vector2){ position.x + segmentThick/2.0f, position.y + 2*segmentThick + segmentLen/2.0f - offsetYAdjust }, + DrawDisplaySegment((Vector2){ position.x + segmentThick/2.0f, position.y + 2*segmentThick + segmentLen/2.0f - offsetYAdjust }, segmentLen, segmentThick, true, (segments & 0b00100000)? colorOn : colorOff); // Segment G - DrawDisplaySegment((Vector2){ position.x + segmentThick + segmentLen/2.0f, position.y + 3*segmentThick + segmentLen - 2*offsetYAdjust }, + DrawDisplaySegment((Vector2){ position.x + segmentThick + segmentLen/2.0f, position.y + 3*segmentThick + segmentLen - 2*offsetYAdjust }, segmentLen, segmentThick, false, (segments & 0b01000000)? colorOn : colorOff); } diff --git a/examples/shapes/shapes_kaleidoscope.c b/examples/shapes/shapes_kaleidoscope.c index 7eeadb8aa..be1409a8d 100644 --- a/examples/shapes/shapes_kaleidoscope.c +++ b/examples/shapes/shapes_kaleidoscope.c @@ -51,13 +51,13 @@ int main(void) Vector2 prevMousePos = { 0 }; Vector2 scaleVector = { 1.0f, -1.0f }; Vector2 offset = { (float)screenWidth/2.0f, (float)screenHeight/2.0f }; - + Camera2D camera = { 0 }; camera.target = (Vector2){ 0 }; camera.offset = offset; camera.rotation = 0.0f; camera.zoom = 1.0f; - + int lineCounter = 0; SetTargetFPS(20); @@ -70,10 +70,10 @@ int main(void) //---------------------------------------------------------------------------------- prevMousePos = mousePos; mousePos = GetMousePosition(); - + Vector2 lineStart = Vector2Subtract(mousePos, offset); Vector2 lineEnd = Vector2Subtract(prevMousePos, offset); - + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { for (int s = 0; (s < symmetry) && (lineCounter < (MAX_DRAW_LINES - 1)); s++) @@ -88,7 +88,7 @@ int main(void) // Store reflective line lines[lineCounter + 1].start = Vector2Multiply(lineStart, scaleVector); lines[lineCounter + 1].end = Vector2Multiply(lineEnd, scaleVector); - + lineCounter += 2; } } @@ -97,9 +97,9 @@ int main(void) // Draw //---------------------------------------------------------------------------------- BeginDrawing(); - + ClearBackground(RAYWHITE); - + BeginMode2D(camera); for (int s = 0; s < symmetry; s++) { @@ -110,10 +110,10 @@ int main(void) } } EndMode2D(); - + DrawText(TextFormat("LINES: %i/%i", lineCounter, MAX_DRAW_LINES), 10, screenHeight - 30, 20, MAROON); DrawFPS(10, 10); - + EndDrawing(); //---------------------------------------------------------------------------------- } diff --git a/examples/shapes/shapes_lines_drawing.c b/examples/shapes/shapes_lines_drawing.c index 16f59884d..76e81abb5 100644 --- a/examples/shapes/shapes_lines_drawing.c +++ b/examples/shapes/shapes_lines_drawing.c @@ -16,6 +16,7 @@ ********************************************************************************************/ #include "raylib.h" + #include "raymath.h" //------------------------------------------------------------------------------------ @@ -23,122 +24,114 @@ //------------------------------------------------------------------------------------ int main(void) { - // Initialization - //-------------------------------------------------------------------------------------- - const int screenWidth = 800; - const int screenHeight = 450; + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; - InitWindow(screenWidth, screenHeight, "raylib [shapes] example - lines drawing"); + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - lines drawing"); - // Hint text that shows before you click the screen - bool startText = true; + // Hint text that shows before you click the screen + bool startText = true; - // The mouse's position on the previous frame - Vector2 mousePositionPrevious = GetMousePosition(); + // The mouse's position on the previous frame + Vector2 mousePositionPrevious = GetMousePosition(); - // The canvas to draw lines on - RenderTexture canvas = LoadRenderTexture(screenWidth, screenHeight); + // The canvas to draw lines on + RenderTexture canvas = LoadRenderTexture(screenWidth, screenHeight); - // The background color of the canvas - const Color backgroundColor = RAYWHITE; + // The line's thickness + float lineThickness = 8.0f; + // The lines hue (in HSV, from 0-360) + float lineHue = 0.0f; - // The line's thickness - float lineThickness = 8.0f; - // The lines hue (in HSV, from 0-360) - float lineHue = 0.0f; + // Clear the canvas to the background color + BeginTextureMode(canvas); + ClearBackground(RAYWHITE); + EndTextureMode(); - // Clear the canvas to the background color - BeginTextureMode(canvas); - ClearBackground(backgroundColor); - EndTextureMode(); - SetTargetFPS(60); - //-------------------------------------------------------------------------------------- + //-------------------------------------------------------------------------------------- - // Main game loop - while (!WindowShouldClose()) // Detect window close button or ESC key - { - // Update - //---------------------------------------------------------------------------------- - // Disable the hint text once the user clicks - if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && startText) startText = false; + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + // Disable the hint text once the user clicks + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && startText) startText = false; - // Clear the canvas when the user middle-clicks - if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) - { - BeginTextureMode(canvas); - ClearBackground(backgroundColor); - EndTextureMode(); - } + // Clear the canvas when the user middle-clicks + if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) + { + BeginTextureMode(canvas); + ClearBackground(RAYWHITE); + EndTextureMode(); + } - // Store whether the left and right buttons are down - bool leftButtonDown = IsMouseButtonDown(MOUSE_BUTTON_LEFT); - bool rightButtonDown = IsMouseButtonDown(MOUSE_BUTTON_RIGHT); + // Store whether the left and right buttons are down + bool leftButtonDown = IsMouseButtonDown(MOUSE_BUTTON_LEFT); + bool rightButtonDown = IsMouseButtonDown(MOUSE_BUTTON_RIGHT); - if (leftButtonDown || rightButtonDown) - { - // The color for the line - Color drawColor = WHITE; + if (leftButtonDown || rightButtonDown) + { + // The color for the line + Color drawColor = WHITE; - if (leftButtonDown) - { - // Increase the hue value by the distance our cursor has moved since the last frame (divided by 3) - lineHue += Vector2Distance(mousePositionPrevious, GetMousePosition())/3.0f; + if (leftButtonDown) + { + // Increase the hue value by the distance our cursor has moved since the last frame (divided by 3) + lineHue += Vector2Distance(mousePositionPrevious, GetMousePosition())/3.0f; - // While the hue is >=360, subtract it to bring it down into the range 0-360 - // This is more visually accurate than resetting to zero - while (lineHue >= 360.0f) lineHue -= 360.0f; + // While the hue is >=360, subtract it to bring it down into the range 0-360 + // This is more visually accurate than resetting to zero + while (lineHue >= 360.0f) lineHue -= 360.0f; - // Create the final color - drawColor = ColorFromHSV(lineHue, 1.0f, 1.0f); - } - else if (rightButtonDown) - { - // Use the background color as an "eraser" - drawColor = backgroundColor; - } + // Create the final color + drawColor = ColorFromHSV(lineHue, 1.0f, 1.0f); + } + else if (rightButtonDown) drawColor = RAYWHITE; // Use the background color as an "eraser" - // Draw the line onto the canvas - BeginTextureMode(canvas); - - // Circles act as "caps", smoothing corners - DrawCircleV(mousePositionPrevious, lineThickness/2.0f, drawColor); - DrawCircleV(GetMousePosition(), lineThickness/2.0f, drawColor); - DrawLineEx(mousePositionPrevious, GetMousePosition(), lineThickness, drawColor); - - EndTextureMode(); - } + // Draw the line onto the canvas + BeginTextureMode(canvas); + // Circles act as "caps", smoothing corners + DrawCircleV(mousePositionPrevious, lineThickness/2.0f, drawColor); + DrawCircleV(GetMousePosition(), lineThickness/2.0f, drawColor); + DrawLineEx(mousePositionPrevious, GetMousePosition(), lineThickness, drawColor); + EndTextureMode(); + } - // Update line thickness based on mousewheel - lineThickness += GetMouseWheelMove(); - lineThickness = Clamp(lineThickness, 1.0, 500.0f); + // Update line thickness based on mousewheel + lineThickness += GetMouseWheelMove(); + lineThickness = Clamp(lineThickness, 1.0, 500.0f); - // Update mouse's previous position - mousePositionPrevious = GetMousePosition(); - //---------------------------------------------------------------------------------- + // Update mouse's previous position + mousePositionPrevious = GetMousePosition(); + //---------------------------------------------------------------------------------- - // Draw - //---------------------------------------------------------------------------------- - BeginDrawing(); - // Draw the render texture to the screen, flipped vertically to make it appear top-side up - DrawTextureRec(canvas.texture, (Rectangle){ 0.0f, 0.0f, (float)canvas.texture.width,(float)-canvas.texture.height }, Vector2Zero(), WHITE); + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); - // Draw the preview circle - if (!leftButtonDown) DrawCircleLinesV(GetMousePosition(), lineThickness/2.0f, (Color){ 127, 127, 127, 127 }); + // Draw the render texture to the screen, flipped vertically to make it appear top-side up + DrawTextureRec(canvas.texture, (Rectangle){ 0.0f, 0.0f, (float)canvas.texture.width,(float)-canvas.texture.height }, Vector2Zero(), WHITE); - // Draw the hint text - if (startText) DrawText("try clicking and dragging!", 275, 215, 20, LIGHTGRAY); - EndDrawing(); - //---------------------------------------------------------------------------------- - } + // Draw the preview circle + if (!leftButtonDown) DrawCircleLinesV(GetMousePosition(), lineThickness/2.0f, (Color){ 127, 127, 127, 127 }); - // De-Initialization - //-------------------------------------------------------------------------------------- - // Unload the canvas render texture - UnloadRenderTexture(canvas); + // Draw the hint text + if (startText) DrawText("try clicking and dragging!", 275, 215, 20, LIGHTGRAY); - CloseWindow(); // Close window and OpenGL context - //-------------------------------------------------------------------------------------- + EndDrawing(); + //---------------------------------------------------------------------------------- + } - return 0; + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadRenderTexture(canvas); // Unload the canvas render texture + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; } \ No newline at end of file diff --git a/examples/shapes/shapes_math_sine_cosine.c b/examples/shapes/shapes_math_sine_cosine.c index d8d13920e..4e5f3fe47 100644 --- a/examples/shapes/shapes_math_sine_cosine.c +++ b/examples/shapes/shapes_math_sine_cosine.c @@ -35,7 +35,7 @@ int main(void) const int screenWidth = 800; const int screenHeight = 450; - SetConfigFlags(FLAG_MSAA_4X_HINT); + SetConfigFlags(FLAG_MSAA_4X_HINT); InitWindow(screenWidth, screenHeight, "raylib [shapes] example - math sine cosine"); Vector2 sinePoints[WAVE_POINTS]; @@ -97,7 +97,7 @@ int main(void) // Base circle and axes DrawCircleLinesV(center, radius, GRAY); - DrawLineEx((Vector2){ center.x, limitMin.y }, (Vector2){ center.x, limitMax.y }, 1.0f, GRAY); + DrawLineEx((Vector2){ center.x, limitMin.y }, (Vector2){ center.x, limitMax.y }, 1.0f, GRAY); DrawLineEx((Vector2){ limitMin.x, center.y }, (Vector2){ limitMax.x, center.y }, 1.f, GRAY); // Wave graph axes @@ -110,17 +110,17 @@ int main(void) DrawText("0", start.x - 8, start.y + start.height/2 - 6, 6, GRAY); DrawText("-1", start.x - 12, start.y + start.height - 8, 6, GRAY); DrawText("0", start.x - 2, start.y + start.height + 4, 6, GRAY); - DrawText("360", start.x + start.width - 8, start.y + start.height + 4, 6, GRAY); + DrawText("360", start.x + start.width - 8, start.y + start.height + 4, 6, GRAY); // Sine (red - vertical) - DrawLineEx((Vector2){ center.x, center.y }, (Vector2){ center.x, point.y }, 2.0f, RED); + DrawLineEx((Vector2){ center.x, center.y }, (Vector2){ center.x, point.y }, 2.0f, RED); DrawLineDashed((Vector2){ point.x, center.y }, (Vector2){ point.x, point.y }, 10.0f, 4.0f, RED); DrawText(TextFormat("Sine %.2f", sinRad), 640, 190, 6, RED); DrawCircleV((Vector2){ start.x + (angle/360.0f)*start.width, start.y + ((-sinRad + 1)*start.height/2.0f) }, 4.0f, RED); DrawSplineLinear(sinePoints, WAVE_POINTS, 1.0f, RED); // Cosine (blue - horizontal) - DrawLineEx((Vector2){ center.x, center.y }, (Vector2){ point.x, center.y }, 2.0f, BLUE); + DrawLineEx((Vector2){ center.x, center.y }, (Vector2){ point.x, center.y }, 2.0f, BLUE); DrawLineDashed((Vector2){ center.x , point.y }, (Vector2){ point.x, point.y }, 10.0f, 4.0f, BLUE); DrawText(TextFormat("Cosine %.2f", cosRad), 640, 210, 6, BLUE); DrawCircleV((Vector2){ start.x + (angle/360.0f)*start.width, start.y + ((-cosRad + 1)*start.height/2.0f) }, 4.0f, BLUE); @@ -135,7 +135,7 @@ int main(void) DrawText(TextFormat("Cotangent %.2f", cotangent), 640, 250, 6, ORANGE); // Complementary angle (beige) - DrawCircleSectorLines(center, radius*0.6f , -angle, -90.f , 36.0f, BEIGE); + DrawCircleSectorLines(center, radius*0.6f , -angle, -90.f , 36.0f, BEIGE); DrawText(TextFormat("Complementary %0.f°",complementary), 640, 150, 6, BEIGE); // Supplementary angle (darkblue) @@ -147,19 +147,19 @@ int main(void) DrawText(TextFormat("Explementary %0.f°",explementary), 640, 170, 6, PINK); // Current angle - arc (lime), radius (black), endpoint (black) - DrawCircleSectorLines(center, radius*0.7f , -angle, 0.f, 36.0f, LIME); + DrawCircleSectorLines(center, radius*0.7f , -angle, 0.f, 36.0f, LIME); DrawLineEx((Vector2){ center.x , center.y }, point, 2.0f, BLACK); DrawCircleV(point, 4.0f, BLACK); // Draw GUI controls //------------------------------------------------------------------------------ GuiSetStyle(LABEL, TEXT_COLOR_NORMAL, ColorToInt(GRAY)); - GuiToggle((Rectangle){ 640, 70, 120, 20}, TextFormat("Pause"), &pause); + GuiToggle((Rectangle){ 640, 70, 120, 20}, TextFormat("Pause"), &pause); GuiSetStyle(LABEL, TEXT_COLOR_NORMAL, ColorToInt(LIME)); GuiSliderBar((Rectangle){ 640, 40, 120, 20}, "Angle", TextFormat("%.0f°", angle), &angle, 0.0f, 360.f); // Angle values panel - GuiGroupBox((Rectangle){ 620, 110, 140, 170}, "Angle Values"); + GuiGroupBox((Rectangle){ 620, 110, 140, 170}, "Angle Values"); //------------------------------------------------------------------------------ DrawFPS(10, 10); diff --git a/examples/shapes/shapes_mouse_trail.c b/examples/shapes/shapes_mouse_trail.c index e0e5a3c1d..819124220 100644 --- a/examples/shapes/shapes_mouse_trail.c +++ b/examples/shapes/shapes_mouse_trail.c @@ -35,7 +35,7 @@ int main(void) InitWindow(screenWidth, screenHeight, "raylib [shapes] example - mouse trail"); // Array to store the history of mouse positions (our fixed-size queue) - Vector2 trailPositions[MAX_TRAIL_LENGTH] = { 0 }; + Vector2 trailPositions[MAX_TRAIL_LENGTH] = { 0 }; SetTargetFPS(60); //-------------------------------------------------------------------------------------- @@ -62,8 +62,8 @@ int main(void) //---------------------------------------------------------------------------------- BeginDrawing(); - ClearBackground(BLACK); - + ClearBackground(BLACK); + // Draw the trail by looping through the history array for (int i = 0; i < MAX_TRAIL_LENGTH; i++) { @@ -71,22 +71,22 @@ int main(void) if ((trailPositions[i].x != 0.0f) || (trailPositions[i].y != 0.0f)) { // Calculate relative trail strength (ratio is near 1.0 for new, near 0.0 for old) - float ratio = (float)(MAX_TRAIL_LENGTH - i) / MAX_TRAIL_LENGTH; - + float ratio = (float)(MAX_TRAIL_LENGTH - i) / MAX_TRAIL_LENGTH; + // Fade effect: oldest positions are more transparent // Fade (color, alpha) - alpha is 0.5 to 1.0 based on ratio - Color trailColor = Fade(SKYBLUE, ratio*0.5f + 0.5f); - + Color trailColor = Fade(SKYBLUE, ratio*0.5f + 0.5f); + // Size effect: oldest positions are smaller - float trailRadius = 15.0f*ratio; - + float trailRadius = 15.0f*ratio; + DrawCircleV(trailPositions[i], trailRadius, trailColor); } } // Draw a distinct white circle for the current mouse position (Index 0) DrawCircleV(mousePosition, 15.0f, WHITE); - + DrawText("Move the mouse to see the trail effect!", 10, screenHeight - 30, 20, LIGHTGRAY); EndDrawing(); diff --git a/examples/shapes/shapes_pie_chart.c b/examples/shapes/shapes_pie_chart.c index 566e3a4d1..6db7f96da 100644 --- a/examples/shapes/shapes_pie_chart.c +++ b/examples/shapes/shapes_pie_chart.c @@ -67,7 +67,7 @@ int main(void) const Rectangle panelRect = { panelPos.x, panelPos.y, (float)panelWidth, - (float)screenHeight - 2.0f*panelMargin + (float)screenHeight - 2.0f*panelMargin }; // Pie chart geometry @@ -108,13 +108,13 @@ int main(void) for (int i = 0; i < sliceCount; i++) { float sweep = (totalValue > 0)? (values[i]/totalValue)*360.0f : 0.0f; - + if ((angle >= currentAngle) && (angle < (currentAngle + sweep))) { hoveredSlice = i; break; } - + currentAngle += sweep; } } @@ -182,11 +182,11 @@ int main(void) GuiLine((Rectangle){ panelPos.x + 10, (float)panelPos.y + 12 + 170, panelRect.width - 20, 1 }, NULL); // Scrollable area for slice editors - scrollPanelBounds = (Rectangle){ - panelPos.x + panelMargin, - (float)panelPos.y + 12 + 190, - panelRect.width - panelMargin*2, - panelRect.y + panelRect.height - panelPos.y + 12 + 190 - panelMargin + scrollPanelBounds = (Rectangle){ + panelPos.x + panelMargin, + (float)panelPos.y + 12 + 190, + panelRect.width - panelMargin*2, + panelRect.y + panelRect.height - panelPos.y + 12 + 190 - panelMargin }; int contentHeight = sliceCount*35; diff --git a/examples/shapes/shapes_recursive_tree.c b/examples/shapes/shapes_recursive_tree.c index e9cadd8ec..4f1f4d5fd 100644 --- a/examples/shapes/shapes_recursive_tree.c +++ b/examples/shapes/shapes_recursive_tree.c @@ -45,7 +45,7 @@ int main(void) Vector2 start = { (screenWidth/2.0f) - 125.0f, (float)screenHeight }; float angle = 40.0f; - float thick = 1.0f; + float thick = 1.0f; float treeDepth = 10.0f; float branchDecay = 0.66f; float length = 120.0f; @@ -67,21 +67,21 @@ int main(void) Vector2 initialEnd = { start.x + length*sinf(0.0f), start.y - length*cosf(0.0f) }; branches[count++] = (Branch){start, initialEnd, 0.0f, length}; - for (int i = 0; i < count; i++) + for (int i = 0; i < count; i++) { Branch branch = branches[i]; if (branch.length < 2) continue; float nextLength = branch.length*branchDecay; - if (count < maxBranches && nextLength >= 2) + if (count < maxBranches && nextLength >= 2) { Vector2 branchStart = branch.end; float angle1 = branch.angle + theta; Vector2 branchEnd1 = { branchStart.x + nextLength*sinf(angle1), branchStart.y - nextLength*cosf(angle1) }; branches[count++] = (Branch){branchStart, branchEnd1, angle1, nextLength}; - + float angle2 = branch.angle - theta; Vector2 branchEnd2 = { branchStart.x + nextLength*sinf(angle2), branchStart.y - nextLength*cosf(angle2) }; branches[count++] = (Branch){branchStart, branchEnd2, angle2, nextLength}; @@ -94,10 +94,10 @@ int main(void) ClearBackground(RAYWHITE); - for (int i = 0; i < count; i++) + for (int i = 0; i < count; i++) { Branch branch = branches[i]; - if (branch.length >= 2) + if (branch.length >= 2) { if (bezier) DrawLineBezier(branch.start, branch.end, thick, RED); else DrawLineEx(branch.start, branch.end, thick, RED); diff --git a/examples/shapes/shapes_rlgl_triangle.c b/examples/shapes/shapes_rlgl_triangle.c index 37626a5c8..56cdc43bc 100644 --- a/examples/shapes/shapes_rlgl_triangle.c +++ b/examples/shapes/shapes_rlgl_triangle.c @@ -50,7 +50,7 @@ int main(void) // Update //---------------------------------------------------------------------------------- if (IsKeyPressed(KEY_SPACE)) linesMode = !linesMode; - + // Check selected vertex for (unsigned int i = 0; i < 3; i++) { @@ -72,7 +72,7 @@ int main(void) position->x += mouseDelta.x; position->y += mouseDelta.y; } - + // Reset index on release if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) triangleIndex = -1; @@ -139,15 +139,15 @@ int main(void) // Render the vertex handles, reacting to mouse movement/input for (unsigned int i = 0; i < 3; i++) { - // Draw handle fill focused by mouse + // Draw handle fill focused by mouse if (CheckCollisionPointCircle(GetMousePosition(), trianglePositions[i], handleRadius)) DrawCircleV(trianglePositions[i], handleRadius, ColorAlpha(DARKGRAY, 0.5f)); - + // Draw handle fill selected if (i == triangleIndex) DrawCircleV(trianglePositions[i], handleRadius, DARKGRAY); - + // Draw handle outline - DrawCircleLinesV(trianglePositions[i], handleRadius, BLACK); + DrawCircleLinesV(trianglePositions[i], handleRadius, BLACK); } // Draw controls diff --git a/examples/shapes/shapes_simple_particles.c b/examples/shapes/shapes_simple_particles.c index c5d9612c3..7be0c82e4 100644 --- a/examples/shapes/shapes_simple_particles.c +++ b/examples/shapes/shapes_simple_particles.c @@ -36,7 +36,7 @@ static const char particleTypeNames[3][10] = { "WATER", "SMOKE", "FIRE" }; typedef struct Particle { ParticleType type; // Particle type (WATER, SMOKE, FIRE) Vector2 position; // Particle position on screen - Vector2 velocity; // Particle current speed and direction + Vector2 velocity; // Particle current speed and direction float radius; // Particle radius Color color; // Particle color @@ -45,9 +45,9 @@ typedef struct Particle { } Particle; typedef struct CircularBuffer { - int head; // Index for the next write - int tail; // Index for the next read - Particle *buffer; // Particle buffer array + int head; // Index for the next write + int tail; // Index for the next read + Particle *buffer; // Particle buffer array } CircularBuffer; //---------------------------------------------------------------------------------- @@ -73,12 +73,12 @@ int main(void) // Definition of particles Particle *particles = (Particle*)RL_CALLOC(MAX_PARTICLES, sizeof(Particle)); // Particle array - CircularBuffer circularBuffer = { 0, 0, particles }; + CircularBuffer circularBuffer = { 0, 0, particles }; - // Particle emitter parameters + // Particle emitter parameters int emissionRate = -2; // Negative: on average every -X frames. Positive: particles per frame - ParticleType currentType = WATER; - Vector2 emitterPosition = { screenWidth/2.0f, screenHeight/2.0f }; + ParticleType currentType = WATER; + Vector2 emitterPosition = { screenWidth/2.0f, screenHeight/2.0f }; SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- @@ -88,7 +88,7 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - // Emit new particles: when emissionRate is 1, emit every frame + // Emit new particles: when emissionRate is 1, emit every frame if (emissionRate < 0) { if (rand()%(-emissionRate) == 0) EmitParticle(&circularBuffer, emitterPosition, currentType); @@ -96,9 +96,9 @@ int main(void) else { for (int i = 0; i <= emissionRate; ++i) EmitParticle(&circularBuffer, emitterPosition, currentType); - } + } - // Update the parameters of each particle + // Update the parameters of each particle UpdateParticles(&circularBuffer, screenWidth, screenHeight); // Remove dead particles from the circular buffer @@ -112,7 +112,7 @@ int main(void) if (IsKeyPressed(KEY_RIGHT)) (currentType == FIRE)? (currentType = WATER) : currentType++; if (IsKeyPressed(KEY_LEFT)) (currentType == WATER)? (currentType = FIRE) : currentType--; - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) emitterPosition = GetMousePosition(); + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) emitterPosition = GetMousePosition(); //---------------------------------------------------------------------------------- // Draw @@ -121,7 +121,7 @@ int main(void) ClearBackground(RAYWHITE); - // Call the function with a loop to draw all particles + // Call the function with a loop to draw all particles DrawParticles(&circularBuffer); // Draw UI and Instructions @@ -133,7 +133,7 @@ int main(void) DrawText("LEFT/RIGHT: Change Particle Type (Water, Smoke, Fire)", 15, 55, 10, BLACK); if (emissionRate < 0) DrawText(TextFormat("Particles every %d frames | Type: %s", -emissionRate, particleTypeNames[currentType]), 15, 95, 10, DARKGRAY); - else DrawText(TextFormat("%d Particles per frame | Type: %s", emissionRate + 1, particleTypeNames[currentType]), 15, 95, 10, DARKGRAY); + else DrawText(TextFormat("%d Particles per frame | Type: %s", emissionRate + 1, particleTypeNames[currentType]), 15, 95, 10, DARKGRAY); DrawFPS(screenWidth - 80, 10); @@ -200,12 +200,12 @@ static Particle *AddToCircularBuffer(CircularBuffer *circularBuffer) // Check if buffer full if (((circularBuffer->head + 1)%MAX_PARTICLES) != circularBuffer->tail) { - // Add new particle to the head position and advance head + // Add new particle to the head position and advance head particle = &circularBuffer->buffer[circularBuffer->head]; circularBuffer->head = (circularBuffer->head + 1)%MAX_PARTICLES; } - return particle; + return particle; } static void UpdateParticles(CircularBuffer *circularBuffer, int screenWidth, int screenHeight) @@ -213,7 +213,7 @@ static void UpdateParticles(CircularBuffer *circularBuffer, int screenWidth, int for (int i = circularBuffer->tail; i != circularBuffer->head; i = (i + 1)%MAX_PARTICLES) { // Update particle life and positions - circularBuffer->buffer[i].lifeTime += 1.0f/60.0f; // 60 FPS -> 1/60 seconds per frame + circularBuffer->buffer[i].lifeTime += 1.0f/60.0f; // 60 FPS -> 1/60 seconds per frame switch (circularBuffer->buffer[i].type) { @@ -226,32 +226,32 @@ static void UpdateParticles(CircularBuffer *circularBuffer, int screenWidth, int case SMOKE: { circularBuffer->buffer[i].position.x += circularBuffer->buffer[i].velocity.x; - circularBuffer->buffer[i].velocity.y -= 0.05f; // Upwards + circularBuffer->buffer[i].velocity.y -= 0.05f; // Upwards circularBuffer->buffer[i].position.y += circularBuffer->buffer[i].velocity.y; circularBuffer->buffer[i].radius += 0.5f; // Increment radius: smoke expands - circularBuffer->buffer[i].color.a -= 4; // Decrement alpha: smoke fades - + circularBuffer->buffer[i].color.a -= 4; // Decrement alpha: smoke fades + // If alpha transparent, particle dies if (circularBuffer->buffer[i].color.a < 4) circularBuffer->buffer[i].alive = false; } break; case FIRE: { - // Add a little horizontal oscillation to fire particles + // Add a little horizontal oscillation to fire particles circularBuffer->buffer[i].position.x += circularBuffer->buffer[i].velocity.x + cosf(circularBuffer->buffer[i].lifeTime*215.0f); circularBuffer->buffer[i].velocity.y -= 0.05f; // Upwards circularBuffer->buffer[i].position.y += circularBuffer->buffer[i].velocity.y; - circularBuffer->buffer[i].radius -= 0.15f; // Decrement radius: fire shrinks - circularBuffer->buffer[i].color.g -= 3; // Decrement green: fire turns reddish starting from yellow - + circularBuffer->buffer[i].radius -= 0.15f; // Decrement radius: fire shrinks + circularBuffer->buffer[i].color.g -= 3; // Decrement green: fire turns reddish starting from yellow + // If radius too small, particle dies if (circularBuffer->buffer[i].radius <= 0.02f) circularBuffer->buffer[i].alive = false; } break; default: break; } - // Disable particle when out of screen + // Disable particle when out of screen Vector2 center = circularBuffer->buffer[i].position; - float radius = circularBuffer->buffer[i].radius; + float radius = circularBuffer->buffer[i].radius; if ((center.x < -radius) || (center.x > (screenWidth + radius)) || (center.y < -radius) || (center.y > (screenHeight + radius))) @@ -267,7 +267,7 @@ static void UpdateCircularBuffer(CircularBuffer *circularBuffer) while ((circularBuffer->tail != circularBuffer->head) && !circularBuffer->buffer[circularBuffer->tail].alive) { circularBuffer->tail = (circularBuffer->tail + 1)%MAX_PARTICLES; - } + } } static void DrawParticles(CircularBuffer *circularBuffer) diff --git a/examples/shapes/shapes_triangle_strip.c b/examples/shapes/shapes_triangle_strip.c index 3172e1f7f..3b44da5e0 100644 --- a/examples/shapes/shapes_triangle_strip.c +++ b/examples/shapes/shapes_triangle_strip.c @@ -39,7 +39,7 @@ int main(void) float insideRadius = 100.0f; float outsideRadius = 150.0f; bool outline = true; - + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- @@ -79,14 +79,14 @@ int main(void) float angle1 = i*angleStep; DrawTriangle(c, b, a, ColorFromHSV(angle1*RAD2DEG, 1.0f, 1.0f)); DrawTriangle(d, b, c, ColorFromHSV((angle1 + angleStep/2)*RAD2DEG, 1.0f, 1.0f)); - + if (outline) { DrawTriangleLines(a, b, c, BLACK); DrawTriangleLines(c, b, d, BLACK); } } - + DrawLine(580, 0, 580, GetScreenHeight(), (Color){ 218, 218, 218, 255 }); DrawRectangle(580, 0, GetScreenWidth(), GetScreenHeight(), (Color){ 232, 232, 232, 255 }); diff --git a/examples/shapes/shapes_vector_angle.c b/examples/shapes/shapes_vector_angle.c index b5faf4c78..464af1bd1 100644 --- a/examples/shapes/shapes_vector_angle.c +++ b/examples/shapes/shapes_vector_angle.c @@ -25,7 +25,7 @@ int main(void) // Initialization //-------------------------------------------------------------------------------------- const int screenWidth = 800; - + const int screenHeight = 450; InitWindow(screenWidth, screenHeight, "raylib [shapes] example - vector angle"); diff --git a/examples/text/text_inline_styling.c b/examples/text/text_inline_styling.c index 0e5e8091f..adedc4056 100644 --- a/examples/text/text_inline_styling.c +++ b/examples/text/text_inline_styling.c @@ -36,11 +36,11 @@ int main(void) const int screenHeight = 450; InitWindow(screenWidth, screenHeight, "raylib [text] example - inline styling"); - + Vector2 textSize = { 0 }; // Measure text box for provided font and text Color colRandom = RED; // Random color used on text int frameCounter = 0; // Used to generate a new random color every certain frames - + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- @@ -50,7 +50,7 @@ int main(void) // Update //---------------------------------------------------------------------------------- frameCounter++; - + if ((frameCounter%20) == 0) { colRandom.r = (unsigned char)GetRandomValue(0, 255); @@ -67,12 +67,12 @@ int main(void) ClearBackground(RAYWHITE); // Text inline styling strategy used: [ ] delimiters for format - // - Define foreground color: [cRRGGBBAA] + // - Define foreground color: [cRRGGBBAA] // - Define background color: [bRRGGBBAA] // - Reset formating: [r] // Example: [bAA00AAFF][cFF0000FF]red text on gray background[r] normal text - - DrawTextStyled(GetFontDefault(), "This changes the [cFF0000FF]foreground color[r] of provided text!!!", + + DrawTextStyled(GetFontDefault(), "This changes the [cFF0000FF]foreground color[r] of provided text!!!", (Vector2){ 100, 80 }, 20.0f, 2.0f, BLACK); DrawTextStyled(GetFontDefault(), "This changes the [bFF00FFFF]background color[r] of provided text!!!", @@ -80,11 +80,11 @@ int main(void) DrawTextStyled(GetFontDefault(), "This changes the [c00ff00ff][bff0000ff]foreground and background colors[r]!!!", (Vector2){ 100, 160 }, 20.0f, 2.0f, BLACK); - + // Get pointer to formated text const char *text = TextFormat("Let's be [c%02x%02x%02xFF]CREATIVE[r] !!!", colRandom.r, colRandom.g, colRandom.b); DrawTextStyled(GetFontDefault(), text, (Vector2){ 100, 220 }, 40.0f, 2.0f, BLACK); - + textSize = MeasureTextStyled(GetFontDefault(), text, 40.0f, 2.0f); DrawRectangleLines(100, 220, (int)textSize.x, (int)textSize.y, GREEN); @@ -108,13 +108,13 @@ int main(void) static void DrawTextStyled(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color color) { // Text inline styling strategy used: [ ] delimiters for format - // - Define foreground color: [cRRGGBBAA] + // - Define foreground color: [cRRGGBBAA] // - Define background color: [bRRGGBBAA] // - Reset formating: [r] // Example: [bAA00AAFF][cFF0000FF]red text on gray background[r] normal text - + if (font.texture.id == 0) font = GetFontDefault(); - + int textLen = TextLength(text); Color colFront = color; @@ -144,14 +144,14 @@ static void DrawTextStyled(Font font, const char *text, Vector2 position, float { colFront = color; colBack = BLANK; - + i += 3; // Skip "[r]" continue; // Do not draw characters } else if (((i + 1) < textLen) && ((text[i + 1] == 'c') || (text[i + 1] == 'b'))) { i += 2; // Skip "[c" or "[b" to start parsing color - + // Parse following color char colHexText[9] = { 0 }; const char *textPtr = &text[i]; // Color should start here, let's see... @@ -168,12 +168,12 @@ static void DrawTextStyled(Font font, const char *text, Vector2 position, float } else break; // Only affects while loop } - + // Convert hex color text into actual Color unsigned int colHexValue = strtoul(colHexText, NULL, 16); if (text[i - 1] == 'c') colFront = GetColor(colHexValue); else if (text[i - 1] == 'b') colBack = GetColor(colHexValue); - + i += (colHexCount + 1); // Skip color value retrieved and ']' continue; // Do not draw characters } @@ -249,7 +249,7 @@ static Vector2 MeasureTextStyled(Font font, const char *text, float fontSize, fl } else break; // Only affects while loop } - + i += (colHexCount + 1); // Skip color value retrieved and ']' continue; // Do not measure characters } @@ -260,7 +260,7 @@ static Vector2 MeasureTextStyled(Font font, const char *text, float fontSize, fl if (font.glyphs[index].advanceX > 0) textWidth += font.glyphs[index].advanceX; else textWidth += (font.recs[index].width + font.glyphs[index].offsetX); - + validCodepointCounter++; i += codepointByteCount; } diff --git a/examples/text/text_unicode_ranges.c b/examples/text/text_unicode_ranges.c index e54c052b1..76ee75f8d 100644 --- a/examples/text/text_unicode_ranges.c +++ b/examples/text/text_unicode_ranges.c @@ -58,7 +58,7 @@ int main(void) // Load font with default Unicode range: Basic ASCII [32-127] font = LoadFont("resources/NotoSansTC-Regular.ttf"); - + // Add required ranges to loaded font switch (unicodeRange) { @@ -128,11 +128,11 @@ int main(void) // Draw //---------------------------------------------------------------------------------- BeginDrawing(); - + ClearBackground(RAYWHITE); DrawText("ADD CODEPOINTS: [1][2][3][4]", 20, 20, 20, MAROON); - + // Render test strings in different languages DrawTextEx(font, "> English: Hello World!", (Vector2){ 50, 70 }, 32, 1, DARKGRAY); // English DrawTextEx(font, "> Español: Hola mundo!", (Vector2){ 50, 120 }, 32, 1, DARKGRAY); // Spanish @@ -141,7 +141,7 @@ int main(void) DrawTextEx(font, "> 中文: 你好世界!", (Vector2){ 50, 270 }, 32, 1, DARKGRAY); // Chinese DrawTextEx(font, "> 日本語: こんにちは世界!", (Vector2){ 50, 320 }, 32, 1, DARKGRAY); // Japanese //DrawTextEx(font, "देवनागरी: होला मुंडो!", (Vector2){ 50, 350 }, 32, 1, DARKGRAY); // Devanagari (glyphs not available in font) - + // Draw font texture scaled to screen float atlasScale = 380.0f/font.texture.width; DrawRectangleRec((Rectangle) { 400.0f, 16.0f, font.texture.width* atlasScale, font.texture.height* atlasScale }, BLACK); @@ -161,7 +161,7 @@ int main(void) DrawRectangle(0, 125, screenWidth, 200, GRAY); DrawText("GENERATING FONT ATLAS...", 120, 210, 40, BLACK); } - + EndDrawing(); //---------------------------------------------------------------------------------- } @@ -184,10 +184,10 @@ static void AddCodepointRange(Font *font, const char *fontPath, int start, int s { int rangeSize = stop - start + 1; int currentRangeSize = font->glyphCount; - + // TODO: Load glyphs from provided vector font (if available), // add them to existing font, regenerating font image and texture - + int updatedCodepointCount = currentRangeSize + rangeSize; int *updatedCodepoints = (int *)RL_CALLOC(updatedCodepointCount, sizeof(int)); diff --git a/examples/text/text_words_alignment.c b/examples/text/text_words_alignment.c index 352b3cd37..103e24e59 100644 --- a/examples/text/text_words_alignment.c +++ b/examples/text/text_words_alignment.c @@ -39,25 +39,25 @@ int main(void) const int screenHeight = 450; InitWindow(screenWidth, screenHeight, "raylib [text] example - words alignment"); - + // Define the rectangle we will draw the text in Rectangle textContainerRect = (Rectangle){ screenWidth/2-screenWidth/4, screenHeight/2-screenHeight/3, screenWidth/2, screenHeight*2/3 }; // Some text to display the current alignment const char *textAlignNameH[] = { "Left", "Centre", "Right" }; const char *textAlignNameV[] = { "Top", "Middle", "Bottom" }; - + // Define the text we're going to draw in the rectangle int wordIndex = 0; int wordCount = 0; char **words = TextSplit("raylib is a simple and easy-to-use library to enjoy videogames programming", ' ', &wordCount); - + // Initialize the font size we're going to use int fontSize = 40; - + // And of course the font... Font font = GetFontDefault(); - + // Intialize the alignment variables TextAlignment hAlign = TEXT_ALIGN_CENTRE; TextAlignment vAlign = TEXT_ALIGN_MIDDLE; @@ -70,7 +70,7 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - + if (IsKeyPressed(KEY_LEFT)) { hAlign = hAlign - 1; if (hAlign < 0) hAlign = 0; @@ -87,10 +87,10 @@ int main(void) vAlign = vAlign + 1; if (vAlign > 2) vAlign = 2; } - + // One word per second wordIndex = (int)GetTime() % wordCount; - + //---------------------------------------------------------------------------------- // Draw @@ -103,16 +103,16 @@ int main(void) DrawText(TextFormat("Alignment: Horizontal = %s, Vertical = %s", textAlignNameH[hAlign], textAlignNameV[vAlign]), 20, 40, 20, LIGHTGRAY); DrawRectangleRec(textContainerRect, BLUE); - + // Get the size of the text to draw Vector2 textSize = MeasureTextEx(font, words[wordIndex], fontSize, fontSize*.1f); - + // Calculate the top-left text position based on the rectangle and alignment Vector2 textPos = (Vector2) { textContainerRect.x + Lerp(0.0f, textContainerRect.width - textSize.x, ((float)hAlign) * 0.5f), textContainerRect.y + Lerp(0.0f, textContainerRect.height - textSize.y, ((float)vAlign) * 0.5f) }; - + // Draw the text DrawTextEx(font, words[wordIndex], textPos, fontSize, fontSize*.1f, RAYWHITE); diff --git a/examples/textures/textures_screen_buffer.c b/examples/textures/textures_screen_buffer.c index 4c737901b..98c9a2ad5 100644 --- a/examples/textures/textures_screen_buffer.c +++ b/examples/textures/textures_screen_buffer.c @@ -45,7 +45,7 @@ int main(void) Color palette[MAX_COLORS] = {0}; unsigned char indexBuffer[INDEX_BUFFER_SIZE] = {0}; unsigned char flameRootBuffer[FLAME_WIDTH] = {0}; - + Image screenImage = GenImageColor(imageWidth, imageHeight, BLACK); Texture screenTexture = LoadTextureFromImage(screenImage); GeneretePalette(palette); @@ -74,7 +74,7 @@ int main(void) int i = x + (imageHeight - 1) * imageWidth; indexBuffer[i] = flameRootBuffer[x]; } - + // Clear top row, because it can't move any higher for (int x = 0; x < imageWidth; ++x) { @@ -90,7 +90,7 @@ int main(void) unsigned i = x + y * imageWidth; unsigned char colorIndex = indexBuffer[i]; if (colorIndex == 0) continue; - + // Move pixel a row above indexBuffer[i] = 0; int moveX = GetRandomValue(0, 2) - 1; @@ -115,7 +115,7 @@ int main(void) ImageDrawPixel(&screenImage, x, y, col); } } - + UpdateTexture(screenTexture, screenImage.data); // Draw //---------------------------------------------------------------------------------- From d26435703f7ddbe33de652b92e0b215fed2bb3d2 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 13:18:13 +0100 Subject: [PATCH 140/430] Update rcore_desktop_win32.c --- src/platforms/rcore_desktop_win32.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index ce80eb41b..f832f410b 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -195,8 +195,8 @@ static PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = NULL; #define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 #define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 #define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 -#define WGL_CONTEXT_ES_PROFILE_BIT_EXT 0x00000004 -#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 +#define WGL_CONTEXT_ES_PROFILE_BIT_EXT 0x00000004 +#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 //---------------------------------------------------------------------------------- // Types and Structures Definition From 82ad486e6b9336d1fc154d06d477fe7b5125374a Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 13:18:47 +0100 Subject: [PATCH 141/430] Update rexm.c --- tools/rexm/rexm.c | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 5c89a9233..e8cb8c93c 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -2598,29 +2598,29 @@ static int AddVSProjectToSolution(const char *slnFile, const char *projFile, con // Add project config lines offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t{%s}.Debug.DLL|ARM64.ActiveCfg = Debug.DLL|ARM64\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug.DLL|x64.Build.0 = Debug.DLL|x64\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug|ARM64.ActiveCfg = Debug|ARM64\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug|ARM64.Build.0 = Debug|ARM64\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug|x64.ActiveCfg = Debug|x64\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug|x64.Build.0 = Debug|x64\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug|x86.ActiveCfg = Debug|Win32\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug|x86.Build.0 = Debug|Win32\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release.DLL|x64.ActiveCfg = Release.DLL|x64\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release.DLL|x64.Build.0 = Release.DLL|x64\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release.DLL|x86.Build.0 = Release.DLL|Win32\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release|ARM64.ActiveCfg = Release|ARM64\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release|ARM64.Build.0 = Release|ARM64\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release|x64.ActiveCfg = Release|x64\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release|x64.Build.0 = Release|x64\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release|x86.ActiveCfg = Release|Win32\n", uuid)); - offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release|x86.Build.0 = Release|Win32\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug.DLL|ARM64.Build.0 = Debug.DLL|ARM64\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug.DLL|x64.Build.0 = Debug.DLL|x64\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug|ARM64.ActiveCfg = Debug|ARM64\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug|ARM64.Build.0 = Debug|ARM64\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug|x64.ActiveCfg = Debug|x64\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug|x64.Build.0 = Debug|x64\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug|x86.ActiveCfg = Debug|Win32\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Debug|x86.Build.0 = Debug|Win32\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release.DLL|ARM64.ActiveCfg = Release.DLL|ARM64\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release.DLL|ARM64.Build.0 = Release.DLL|ARM64\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release.DLL|x64.ActiveCfg = Release.DLL|x64\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release.DLL|x64.Build.0 = Release.DLL|x64\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release.DLL|x86.Build.0 = Release.DLL|Win32\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release|ARM64.ActiveCfg = Release|ARM64\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release|ARM64.Build.0 = Release|ARM64\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release|x64.ActiveCfg = Release|x64\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release|x64.Build.0 = Release|x64\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release|x86.ActiveCfg = Release|Win32\n", uuid)); + offsetIndex += sprintf(slnTextUpdated + offsetIndex, TextFormat("\t\t{%s}.Release|x86.Build.0 = Release|Win32\n", uuid)); // Write next section directly to avoid copy logic offsetIndex += sprintf(slnTextUpdated + offsetIndex, "\tEndGlobalSection\n"); offsetIndex += sprintf(slnTextUpdated + offsetIndex, "\tGlobalSection(SolutionProperties) = preSolution\n"); From ee3d65cbc9f0a68c4d1c07ca3784f1c5e211492d Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 13:25:54 +0100 Subject: [PATCH 142/430] Update examples_testing_web.md --- tools/rexm/reports/examples_testing_web.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/rexm/reports/examples_testing_web.md b/tools/rexm/reports/examples_testing_web.md index 2e8cb5fb3..1704b5986 100644 --- a/tools/rexm/reports/examples_testing_web.md +++ b/tools/rexm/reports/examples_testing_web.md @@ -17,7 +17,6 @@ Example automated testing elements validated: | **EXAMPLE NAME** | [CWARN] | [LWARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | |:---------------------------------|:-------:|:-------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| | core_monitor_detector | 0 | 1 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_lines_drawing | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | textures_screen_buffer | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | textures_sprite_stacking | 0 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | | text_sprite_fonts | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | @@ -27,7 +26,6 @@ Example automated testing elements validated: | models_animation_playing | 0 | 1 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | | models_mesh_generation | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | | models_loading_gltf | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | -| models_loading_vox | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | models_bone_socket | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | | models_decals | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | shaders_postprocessing | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | @@ -35,6 +33,5 @@ Example automated testing elements validated: | shaders_shadowmap_rendering | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | | shaders_basic_pbr | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | | audio_module_playing | 0 | 1 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | -| audio_sound_positioning | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | audio_fft_spectrum_visualizer | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | From 1f7f9ab22b707aeb84ccbaa2bb4c9edc042b9fd9 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 19:30:07 +0100 Subject: [PATCH 143/430] Ignore examples binaries on Linux (and automated logs) --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index ddbc5c11d..3a95edc19 100644 --- a/.gitignore +++ b/.gitignore @@ -62,10 +62,14 @@ packages/ emsdk # Ignore wasm data in examples/ +examples/**/* examples/**/*.wasm examples/**/*.data examples/**/*.js examples/**/*.html +!examples/**/*.* +!examples/**/*/ +examples/**/logs/* # Ignore files build by xcode *.mode*v* From 282c4b0eabb41733f2587129a5dd924ce0d2e2d2 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 19:30:25 +0100 Subject: [PATCH 144/430] Minor teaks to run on Linux --- tools/rexm/rexm.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index e8cb8c93c..6aba90f6f 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -51,9 +51,9 @@ #include "raylib.h" -#include // Required for: NULL, calloc(), free() #include // Required for: rename(), remove() #include // Required for: strcmp(), strcpy() +#include // Required for: NULL, calloc(), free() #define SUPPORT_LOG_INFO #if defined(SUPPORT_LOG_INFO) //&& defined(_DEBUG) @@ -1517,7 +1517,7 @@ int main(int argc, char *argv[]) TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName)); char *srcText = LoadFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); -#define BUILD_TESTING_WEB +//#define BUILD_TESTING_WEB #if defined(BUILD_TESTING_WEB) static const char *mainReplaceText = "#include \n" @@ -1620,8 +1620,9 @@ int main(int argc, char *argv[]) exBasePath, exCategory, exName, exBasePath, exCategory, exName)); #else LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: POSIX)\n", exName); - system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName)); -#endif + system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B > %s/%s/logs/%s.build.log 2>&1", + exBasePath, exCategory, exName, exBasePath, exCategory, exName)); + #endif // Restore original source code before continue FileCopy(TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName), TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName)); @@ -1630,7 +1631,7 @@ int main(int argc, char *argv[]) // STEP 3: Run example with required arguments // NOTE: Not easy to retrieve process return value from system(), it's platform dependant ChangeDirectory(TextFormat("%s/%s", exBasePath, exCategory)); - system(TextFormat("%s --frames 2 > logs/%s.log", exName, exName)); + system(TextFormat("./%s --frames 2 > logs/%s.log", exName, exName)); #endif } } break; @@ -1712,7 +1713,9 @@ int main(int argc, char *argv[]) char **exTestLogLines = LoadTextLines(exTestLog, &exTestLogLinesCount); for (int k = 0; k < exTestLogLinesCount; k++) { - if (TextFindIndex(exTestLogLines[k], "WARNING: GL: NPOT") >= 0) continue; // Ignore warning +#if defined(BUILD_TESTING_WEB) + if (TextFindIndex(exTestLogLines[k], "WARNING: GL: NPOT") >= 0) continue; // Ignore web-specific warning +#endif if (TextFindIndex(exTestLogLines[k], "WARNING") >= 0) testing[i].warnings++; } UnloadTextLines(exTestLogLines, exTestLogLinesCount); @@ -1842,6 +1845,7 @@ int main(int argc, char *argv[]) printf(" rename : Rename an existing example\n"); printf(" remove : Remove an existing example\n"); printf(" build : Build example for Desktop and Web platforms\n"); + printf(" test : Build and Test example for Desktop and Web platforms\n"); printf(" validate : Validate examples collection, generates report\n"); printf(" update : Validate and update examples collection, generates report\n\n"); printf("OPTIONS:\n\n"); From 5aee9f9d509aced47507381c6f88e259551696fa Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 19:31:17 +0100 Subject: [PATCH 145/430] Create examples_testing_linux.md --- tools/rexm/reports/examples_testing_linux.md | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tools/rexm/reports/examples_testing_linux.md diff --git a/tools/rexm/reports/examples_testing_linux.md b/tools/rexm/reports/examples_testing_linux.md new file mode 100644 index 000000000..983ba1b3d --- /dev/null +++ b/tools/rexm/reports/examples_testing_linux.md @@ -0,0 +1,37 @@ +# EXAMPLES COLLECTION - TESTING REPORT + +## Tested Platform: Linux + +``` +Example automated testing elements validated: + - [CWARN] : Compilation WARNING messages + - [LWARN] : Log WARNING messages count + - [INIT] : Initialization + - [CLOSE] : Closing + - [ASSETS] : Assets loading + - [RLGL] : OpenGL-wrapped initialization + - [PLAT] : Platform initialization + - [FONT] : Font default initialization + - [TIMER] : Timer initialization +``` +| **EXAMPLE NAME** | [CWARN] | [LWARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | +|:---------------------------------|:-------:|:-------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| +| core_directory_files | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_clipboard_text | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| core_compute_hash | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_recursive_tree | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_ring_drawing | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_circle_sector_drawing | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_rounded_rectangle_drawing | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_splines_drawing | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_triangle_strip | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_pie_chart | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_math_sine_cosine | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| shapes_rlgl_color_wheel | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| textures_sprite_stacking | 0 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | +| text_font_loading | 0 | 10 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| text_codepoints_loading | 0 | 1 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| models_animation_playing | 0 | 1 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | +| shaders_palette_switch | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| shaders_color_correction | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | + From 646e814baf9c477658ec8306ca346941a5101eee Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 19:41:35 +0100 Subject: [PATCH 146/430] Update Makefile --- examples/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Makefile b/examples/Makefile index edddb366e..b2feec0db 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -231,7 +231,7 @@ endif # -Wno-missing-braces ignore invalid warning (GCC bug 53119) # -Wno-unused-value ignore unused return values of some functions (i.e. fread()) # -D_DEFAULT_SOURCE use with -std=c99 on Linux and PLATFORM_WEB, required for timespec -CFLAGS = -Wall -std=c99 -D_DEFAULT_SOURCE -Wno-missing-braces -Wunused-result +CFLAGS = -Wall -std=c99 -D_DEFAULT_SOURCE -Wno-missing-braces -Wno-unused-result ifeq ($(BUILD_MODE),DEBUG) CFLAGS += -g -D_DEBUG From 8fcd99c8ddbaa3e4a130bbff82e7c36c82758cc6 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 19:41:39 +0100 Subject: [PATCH 147/430] Update textures_sprite_stacking.c --- examples/textures/textures_sprite_stacking.c | 50 ++++++++------------ 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/examples/textures/textures_sprite_stacking.c b/examples/textures/textures_sprite_stacking.c index a2cb04d2f..5edc1af6b 100644 --- a/examples/textures/textures_sprite_stacking.c +++ b/examples/textures/textures_sprite_stacking.c @@ -17,7 +17,8 @@ ********************************************************************************************/ #include "raylib.h" -#include "raymath.h" + +#include "raymath.h" // Required for: Clamp() //------------------------------------------------------------------------------------ // Program main entry point @@ -33,18 +34,14 @@ int main(void) Texture2D booth = LoadTexture("resources/booth.png"); - // The overall scale of the stacked sprite - float stackScale = 3.0f; - // The vertical spacing between each layer - float stackSpacing = 2.0f; - // The number of layers. Used for calculating the size of a single slice - unsigned int stackCount = 122; - // The speed to rotate the stacked sprite - float rotationSpeed = 30.0f; - // The current rotation of the stacked sprite - float rotation = 0.0f; - // The amount that speed will change by when the user presses A/D - const float speedChange = 0.25f; + float stackScale = 3.0f; // Overall scale of the stacked sprite + float stackSpacing = 2.0f; // Vertical spacing between each layer + unsigned int stackCount = 122; // Number of layers, used for calculating the size of a single slice + float rotationSpeed = 30.0f; // Stacked sprites rotation speed + float rotation = 0.0f; // Current rotation of the stacked sprite + const float speedChange = 0.25f; // Amount speed will change by when the user presses A/D + + SetTargetFPS(60); //-------------------------------------------------------------------------------------- // Main game loop @@ -57,22 +54,16 @@ int main(void) stackSpacing = Clamp(stackSpacing, 0.0f, 5.0f); // Add a positive/negative offset to spin right/left at different speeds - if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)) - { - rotationSpeed -= speedChange; - } - - if (IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D)) - { - rotationSpeed += speedChange; - } - - rotation += rotationSpeed * GetFrameTime(); + if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)) rotationSpeed -= speedChange; + if (IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D)) rotationSpeed += speedChange; + + rotation += rotationSpeed*GetFrameTime(); //---------------------------------------------------------------------------------- // Draw //---------------------------------------------------------------------------------- BeginDrawing(); + ClearBackground(RAYWHITE); // Get the size of a single slice @@ -86,20 +77,19 @@ int main(void) // Draw the stacked sprite, rotated to the correct angle, with an vertical offset applied based on its y location for (int i = stackCount - 1; i >= 0; i--) { - Rectangle source = { 0.0f, (float)i*frameHeight, frameWidth, frameHeight }; // Center vertically + Rectangle source = { 0.0f, (float)i*frameHeight, frameWidth, frameHeight }; Rectangle dest = { screenWidth/2.0f, (screenHeight/2.0f) + (i*stackSpacing) - (stackSpacing*stackCount/2.0f), scaledWidth, scaledHeight }; Vector2 origin = { scaledWidth/2.0f, scaledHeight/2.0f }; DrawTexturePro(booth, source, dest, origin, rotation, WHITE); } - DrawText("a/d to spin\nmouse wheel to change separation (aka 'angle')", 10, 10, 20, DARKGRAY); - const char *spacingText = TextFormat("current spacing: %.01f", stackSpacing); - DrawText(spacingText, 10, 50, 20, DARKGRAY); - const char *speedText = TextFormat("current speed: %.02f", rotationSpeed); - DrawText(speedText, 10, 70, 20, DARKGRAY); + DrawText("A/D to spin\nmouse wheel to change separation (aka 'angle')", 10, 10, 20, DARKGRAY); + DrawText(TextFormat("current spacing: %.01f", stackSpacing), 10, 50, 20, DARKGRAY); + DrawText(TextFormat("current speed: %.02f", rotationSpeed), 10, 70, 20, DARKGRAY); DrawText("redbooth model (c) kluchek under cc 4.0", 10, 420, 20, DARKGRAY); + EndDrawing(); //---------------------------------------------------------------------------------- } From 49868b356f47f2a1eafed7458c2904df139511ae Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 19 Nov 2025 19:41:43 +0100 Subject: [PATCH 148/430] Update examples_testing_linux.md --- tools/rexm/reports/examples_testing_linux.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tools/rexm/reports/examples_testing_linux.md b/tools/rexm/reports/examples_testing_linux.md index 983ba1b3d..8f6c377ee 100644 --- a/tools/rexm/reports/examples_testing_linux.md +++ b/tools/rexm/reports/examples_testing_linux.md @@ -16,22 +16,7 @@ Example automated testing elements validated: ``` | **EXAMPLE NAME** | [CWARN] | [LWARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | |:---------------------------------|:-------:|:-------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| -| core_directory_files | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| core_clipboard_text | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| core_compute_hash | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_recursive_tree | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_ring_drawing | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_circle_sector_drawing | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_rounded_rectangle_drawing | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_splines_drawing | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_triangle_strip | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_pie_chart | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_math_sine_cosine | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| shapes_rlgl_color_wheel | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| textures_sprite_stacking | 0 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | | text_font_loading | 0 | 10 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | text_codepoints_loading | 0 | 1 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | models_animation_playing | 0 | 1 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | -| shaders_palette_switch | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | -| shaders_color_correction | 10 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | From 29173a49784f1a4e06fa93f9fd1f50816f90bca6 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 20 Nov 2025 00:00:51 +0100 Subject: [PATCH 149/430] Update .gitignore --- .gitignore | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3a95edc19..e5f6faf4d 100644 --- a/.gitignore +++ b/.gitignore @@ -63,12 +63,13 @@ emsdk # Ignore wasm data in examples/ examples/**/* +!examples/**/*.* +!examples/**/*/ +examples/**/*.exe examples/**/*.wasm examples/**/*.data examples/**/*.js examples/**/*.html -!examples/**/*.* -!examples/**/*/ examples/**/logs/* # Ignore files build by xcode From 67f24b3b41f9d1021cb2161b0f23886185572839 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 20 Nov 2025 00:01:06 +0100 Subject: [PATCH 150/430] Update audio_sound_positioning.c --- examples/audio/audio_sound_positioning.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/audio/audio_sound_positioning.c b/examples/audio/audio_sound_positioning.c index 4d29954f6..9159acf08 100644 --- a/examples/audio/audio_sound_positioning.c +++ b/examples/audio/audio_sound_positioning.c @@ -69,6 +69,7 @@ int main(void) }; SetSoundPosition(camera, sound, spherePos, 20.0f); + if (!IsSoundPlaying(sound)) PlaySound(sound); //---------------------------------------------------------------------------------- @@ -94,6 +95,8 @@ int main(void) CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- + + return 0; } //------------------------------------------------------------------------------------ From ba65bd7f994ef9aeecdc155a2918430642bf21d6 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 20 Nov 2025 00:03:03 +0100 Subject: [PATCH 151/430] WARNING: BREAKING: Redesigned `SetSoundPan()` and `SetMusicPan()` #5350 Now it goes from -1.0 (full left) to 1.0 (full right) being 0.0 center --- src/raudio.c | 12 ++++++------ src/raylib.h | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/raudio.c b/src/raudio.c index de2bf81b2..66e04fad6 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -593,7 +593,7 @@ AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sam // Init audio buffer values audioBuffer->volume = 1.0f; audioBuffer->pitch = 1.0f; - audioBuffer->pan = 0.5f; + audioBuffer->pan = 0.0f; // Center audioBuffer->callback = NULL; audioBuffer->processor = NULL; @@ -720,7 +720,7 @@ void SetAudioBufferPitch(AudioBuffer *buffer, float pitch) // Set pan for an audio buffer void SetAudioBufferPan(AudioBuffer *buffer, float pan) { - if (pan < 0.0f) pan = 0.0f; + if (pan < -1.0f) pan = -1.0f; else if (pan > 1.0f) pan = 1.0f; if (buffer != NULL) @@ -985,10 +985,10 @@ Sound LoadSoundAlias(Sound source) audioBuffer->sizeInFrames = source.stream.buffer->sizeInFrames; audioBuffer->data = source.stream.buffer->data; - // initalize the buffer as if it was new + // Initalize the buffer as if it was new audioBuffer->volume = 1.0f; audioBuffer->pitch = 1.0f; - audioBuffer->pan = 0.5f; + audioBuffer->pan = 0.0f; // Center sound.frameCount = source.frameCount; sound.stream.sampleRate = AUDIO.System.device.sampleRate; @@ -2605,8 +2605,8 @@ static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 fr if (channels == 2) // We consider panning { - const float left = buffer->pan; - const float right = 1.0f - left; + const float right = (buffer->pan + 1.0f)/2.0f; // Normalize: [-1..1] -> [0..1] + const float left = 1.0f - right; // Fast sine approximation in [0..1] for pan law: y = 0.5f*x*(3 - x*x); const float levels[2] = { localVolume*0.5f*left*(3.0f - left*left), localVolume*0.5f*right*(3.0f - right*right) }; diff --git a/src/raylib.h b/src/raylib.h index 2f9ec2268..67279753a 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1672,7 +1672,7 @@ RLAPI void ResumeSound(Sound sound); // Resume RLAPI bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing RLAPI void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) RLAPI void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) -RLAPI void SetSoundPan(Sound sound, float pan); // Set pan for a sound (0.5 is center) +RLAPI void SetSoundPan(Sound sound, float pan); // Set pan for a sound (-1.0 left, 0.0 center, 1.0 right) RLAPI Wave WaveCopy(Wave wave); // Copy a wave to a new wave RLAPI void WaveCrop(Wave *wave, int initFrame, int finalFrame); // Crop a wave to defined frames range RLAPI void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format @@ -1693,7 +1693,7 @@ RLAPI void ResumeMusicStream(Music music); // Resume RLAPI void SeekMusicStream(Music music, float position); // Seek music to a position (in seconds) RLAPI void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) RLAPI void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) -RLAPI void SetMusicPan(Music music, float pan); // Set pan for a music (0.5 is center) +RLAPI void SetMusicPan(Music music, float pan); // Set pan for a music (-1.0 left, 0.0 center, 1.0 right) RLAPI float GetMusicTimeLength(Music music); // Get music time length (in seconds) RLAPI float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) From 30cd36a8a9feaa81d8dd46a2875a961ed85fd48d Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 20 Nov 2025 00:03:08 +0100 Subject: [PATCH 152/430] Update audio_music_stream.c --- examples/audio/audio_music_stream.c | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/examples/audio/audio_music_stream.c b/examples/audio/audio_music_stream.c index 48be49e42..4533ff30b 100644 --- a/examples/audio/audio_music_stream.c +++ b/examples/audio/audio_music_stream.c @@ -35,6 +35,12 @@ int main(void) float timePlayed = 0.0f; // Time played normalized [0.0f..1.0f] bool pause = false; // Music playing paused + + float pan = 0.0f; // Default audio pan center [-1.0f..1.0f] + SetMusicPan(music, pan); + + float volume = 0.8f; // Default audio volume [0.0f..1.0f] + SetMusicVolume(music, volume); SetTargetFPS(30); // Set our game to run at 30 frames-per-second //-------------------------------------------------------------------------------------- @@ -61,6 +67,34 @@ int main(void) if (pause) PauseMusicStream(music); else ResumeMusicStream(music); } + + // Set audio pan + if (IsKeyDown(KEY_LEFT)) + { + pan -= 0.05f; + if (pan < -1.0f) pan = -1.0f; + SetMusicPan(music, pan); + } + else if (IsKeyDown(KEY_RIGHT)) + { + pan += 0.05f; + if (pan > 1.0f) pan = 1.0f; + SetMusicPan(music, pan); + } + + // Set audio volume + if (IsKeyDown(KEY_DOWN)) + { + volume -= 0.05f; + if (volume < 0.0f) volume = 0.0f; + SetMusicVolume(music, volume); + } + else if (IsKeyDown(KEY_UP)) + { + volume += 0.05f; + if (volume > 1.0f) volume = 1.0f; + SetMusicVolume(music, volume); + } // Get normalized time played for current music stream timePlayed = GetMusicTimePlayed(music)/GetMusicTimeLength(music); @@ -75,6 +109,11 @@ int main(void) ClearBackground(RAYWHITE); DrawText("MUSIC SHOULD BE PLAYING!", 255, 150, 20, LIGHTGRAY); + + DrawText("LEFT-RIGHT for PAN CONTROL", 320, 74, 10, DARKBLUE); + DrawRectangle(300, 100, 200, 12, LIGHTGRAY); + DrawRectangleLines(300, 100, 200, 12, GRAY); + DrawRectangle(300 + (pan + 1.0)/2.0f*200 - 5, 92, 10, 28, DARKGRAY); DrawRectangle(200, 200, 400, 12, LIGHTGRAY); DrawRectangle(200, 200, (int)(timePlayed*400.0f), 12, MAROON); @@ -82,6 +121,11 @@ int main(void) DrawText("PRESS SPACE TO RESTART MUSIC", 215, 250, 20, LIGHTGRAY); DrawText("PRESS P TO PAUSE/RESUME MUSIC", 208, 280, 20, LIGHTGRAY); + + DrawText("UP-DOWN for VOLUME CONTROL", 320, 334, 10, DARKGREEN); + DrawRectangle(300, 360, 200, 12, LIGHTGRAY); + DrawRectangleLines(300, 360, 200, 12, GRAY); + DrawRectangle(300 + volume*200 - 5, 352, 10, 28, DARKGRAY); EndDrawing(); //---------------------------------------------------------------------------------- From 8161475c28760238b274e41f3433193edb137366 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 23:03:27 +0000 Subject: [PATCH 153/430] rlparser: update raylib_api.* by CI --- tools/rlparser/output/raylib_api.json | 4 ++-- tools/rlparser/output/raylib_api.lua | 4 ++-- tools/rlparser/output/raylib_api.txt | 4 ++-- tools/rlparser/output/raylib_api.xml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/rlparser/output/raylib_api.json b/tools/rlparser/output/raylib_api.json index c0bc88681..e5815854c 100644 --- a/tools/rlparser/output/raylib_api.json +++ b/tools/rlparser/output/raylib_api.json @@ -11897,7 +11897,7 @@ }, { "name": "SetSoundPan", - "description": "Set pan for a sound (0.5 is center)", + "description": "Set pan for a sound (-1.0 left, 0.0 center, 1.0 right)", "returnType": "void", "params": [ { @@ -12150,7 +12150,7 @@ }, { "name": "SetMusicPan", - "description": "Set pan for a music (0.5 is center)", + "description": "Set pan for a music (-1.0 left, 0.0 center, 1.0 right)", "returnType": "void", "params": [ { diff --git a/tools/rlparser/output/raylib_api.lua b/tools/rlparser/output/raylib_api.lua index 5cb3c1d55..eb3e6567b 100644 --- a/tools/rlparser/output/raylib_api.lua +++ b/tools/rlparser/output/raylib_api.lua @@ -8105,7 +8105,7 @@ return { }, { name = "SetSoundPan", - description = "Set pan for a sound (0.5 is center)", + description = "Set pan for a sound (-1.0 left, 0.0 center, 1.0 right)", returnType = "void", params = { {type = "Sound", name = "sound"}, @@ -8268,7 +8268,7 @@ return { }, { name = "SetMusicPan", - description = "Set pan for a music (0.5 is center)", + description = "Set pan for a music (-1.0 left, 0.0 center, 1.0 right)", returnType = "void", params = { {type = "Music", name = "music"}, diff --git a/tools/rlparser/output/raylib_api.txt b/tools/rlparser/output/raylib_api.txt index 9be8e517c..76d223d97 100644 --- a/tools/rlparser/output/raylib_api.txt +++ b/tools/rlparser/output/raylib_api.txt @@ -4554,7 +4554,7 @@ Function 556: SetSoundPitch() (2 input parameters) Function 557: SetSoundPan() (2 input parameters) Name: SetSoundPan Return type: void - Description: Set pan for a sound (0.5 is center) + Description: Set pan for a sound (-1.0 left, 0.0 center, 1.0 right) Param[1]: sound (type: Sound) Param[2]: pan (type: float) Function 558: WaveCopy() (1 input parameters) @@ -4660,7 +4660,7 @@ Function 575: SetMusicPitch() (2 input parameters) Function 576: SetMusicPan() (2 input parameters) Name: SetMusicPan Return type: void - Description: Set pan for a music (0.5 is center) + Description: Set pan for a music (-1.0 left, 0.0 center, 1.0 right) Param[1]: music (type: Music) Param[2]: pan (type: float) Function 577: GetMusicTimeLength() (1 input parameters) diff --git a/tools/rlparser/output/raylib_api.xml b/tools/rlparser/output/raylib_api.xml index 96dbdbf64..512c4c6df 100644 --- a/tools/rlparser/output/raylib_api.xml +++ b/tools/rlparser/output/raylib_api.xml @@ -3043,7 +3043,7 @@ - + @@ -3111,7 +3111,7 @@ - + From c0179288baa4b662fed182e5e0532d02a7d12790 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 20 Nov 2025 00:59:28 +0100 Subject: [PATCH 154/430] REXM: TEST: Support testing running on `PLATFORM_DRM` --- tools/rexm/rexm.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 6aba90f6f..19f52d3aa 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -931,23 +931,28 @@ int main(int argc, char *argv[]) #if defined(_WIN32) LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: Win32)\n", exName); system(TextFormat("mingw32-make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName)); +#elif defined(PLATFORM_DRM) + LOG("INFO: [%s] Building example for PLATFORM_DRM (Host: POSIX)\n", exName); + system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DRM -B > %s/%s/logs/%s.build.log 2>&1", + exBasePath, exCategory, exName, exBasePath, exCategory, exName)); #else LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: POSIX)\n", exName); system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName)); #endif +#if !defined(PLATFORM_DRM) // Build example for PLATFORM_WEB // Build: raylib.com/examples//_example_name.html // Build: raylib.com/examples//_example_name.data // Build: raylib.com/examples//_example_name.wasm // Build: raylib.com/examples//_example_name.js -#if defined(_WIN32) + #if defined(_WIN32) LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: Win32)\n", exName); system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exCategory, exName)); -#else + #else LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: POSIX)\n", exName); system(TextFormat("make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B", exBasePath, exCategory, exName)); -#endif + #endif // Update generated .html metadata LOG("INFO: [%s] Updating HTML Metadata...\n", TextFormat("%s.html", exName)); UpdateWebMetadata(TextFormat("%s/%s/%s.html", exBasePath, exCategory, exName), @@ -963,6 +968,7 @@ int main(int argc, char *argv[]) TextFormat("%s/%s/%s.wasm", exWebPath, exCategory, exName)); FileCopy(TextFormat("%s/%s/%s.js", exBasePath, exCategory, exName), TextFormat("%s/%s/%s.js", exWebPath, exCategory, exName)); +#endif // !PLATFORM_DRM // Once example processed, free memory from list RL_FREE(exBuildList[i]); @@ -1618,6 +1624,10 @@ int main(int argc, char *argv[]) LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: Win32)\n", exName); system(TextFormat("mingw32-make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B > %s/%s/logs/%s.build.log 2>&1", exBasePath, exCategory, exName, exBasePath, exCategory, exName)); + #elif defined(PLATFORM_DRM) + LOG("INFO: [%s] Building example for PLATFORM_DRM (Host: POSIX)\n", exName); + system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DRM -B > %s/%s/logs/%s.build.log 2>&1", + exBasePath, exCategory, exName, exBasePath, exCategory, exName)); #else LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: POSIX)\n", exName); system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B > %s/%s/logs/%s.build.log 2>&1", @@ -1631,7 +1641,12 @@ int main(int argc, char *argv[]) // STEP 3: Run example with required arguments // NOTE: Not easy to retrieve process return value from system(), it's platform dependant ChangeDirectory(TextFormat("%s/%s", exBasePath, exCategory)); + + #if defined(_WIN32) + system(TextFormat("%s --frames 2 > logs/%s.log", exName, exName)); + #else system(TextFormat("./%s --frames 2 > logs/%s.log", exName, exName)); + #endif #endif } } break; @@ -1715,6 +1730,11 @@ int main(int argc, char *argv[]) { #if defined(BUILD_TESTING_WEB) if (TextFindIndex(exTestLogLines[k], "WARNING: GL: NPOT") >= 0) continue; // Ignore web-specific warning +#endif +#if defined(PLATFORM_DRM) + if (TextFindIndex(exTestLogLines[k], "WARNING: DISPLAY: No graphic") >= 0) continue; // Ignore specific warning + if (TextFindIndex(exTestLogLines[k], "WARNING: GetCurrentMonitor()") >= 0) continue; // Ignore specific warning + if (TextFindIndex(exTestLogLines[k], "WARNING: SetWindowPosition()") >= 0) continue; // Ignore specific warning #endif if (TextFindIndex(exTestLogLines[k], "WARNING") >= 0) testing[i].warnings++; } @@ -1758,7 +1778,7 @@ int main(int argc, char *argv[]) |:---------------------------------|:-------:|:-------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| | core_basic window | 0 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | */ - LOG("INFO: [examples_testing.md] Generating examples testing report...\n"); + LOG("INFO: [examples_testing_os.md] Generating examples testing report...\n"); char *report = (char *)RL_CALLOC(REXM_MAX_BUFFER_SIZE, 1); From c6f4c8e3e02cf18e79edb32d5541a66a50fc372c Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 20 Nov 2025 00:59:43 +0100 Subject: [PATCH 155/430] FIX: Issue on PLATFORM_DRM --- examples/shaders/resources/shaders/glsl100/raymarching.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/shaders/resources/shaders/glsl100/raymarching.fs b/examples/shaders/resources/shaders/glsl100/raymarching.fs index ce9a2faca..58cd41059 100644 --- a/examples/shaders/resources/shaders/glsl100/raymarching.fs +++ b/examples/shaders/resources/shaders/glsl100/raymarching.fs @@ -1,9 +1,9 @@ #version 100 -precision mediump float; - #extension GL_OES_standard_derivatives : enable +precision mediump float; + // Input vertex attributes (from vertex shader) varying vec2 fragTexCoord; varying vec4 fragColor; From 0747e9b5c14eb414932839283b7c463f35f44243 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 20 Nov 2025 01:00:02 +0100 Subject: [PATCH 156/430] Create examples_testing_drm.md --- tools/rexm/reports/examples_testing_drm.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tools/rexm/reports/examples_testing_drm.md diff --git a/tools/rexm/reports/examples_testing_drm.md b/tools/rexm/reports/examples_testing_drm.md new file mode 100644 index 000000000..dba721a10 --- /dev/null +++ b/tools/rexm/reports/examples_testing_drm.md @@ -0,0 +1,21 @@ +# EXAMPLES COLLECTION - TESTING REPORT + +## Tested Platform: DRM + +``` +Example automated testing elements validated: + - [CWARN] : Compilation WARNING messages + - [LWARN] : Log WARNING messages count + - [INIT] : Initialization + - [CLOSE] : Closing + - [ASSETS] : Assets loading + - [RLGL] : OpenGL-wrapped initialization + - [PLAT] : Platform initialization + - [FONT] : Font default initialization + - [TIMER] : Timer initialization +``` +| **EXAMPLE NAME** | [CWARN] | [LWARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | +|:---------------------------------|:-------:|:-------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| +| text_font_loading | 0 | 10 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| text_codepoints_loading | 0 | 1 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| models_animation_playing | 0 | 1 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | From 90af210712e430e4bb411eb0139fdda417838ecb Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Wed, 19 Nov 2025 23:00:23 -0800 Subject: [PATCH 157/430] include malloc.h so the win32 platform can build in MSVC (#5365) --- src/platforms/rcore_desktop_win32.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index f832f410b..37f3fbac4 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -72,6 +72,8 @@ #include #include +#include // Required for alloca() + #if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE) #include #endif From 4ad9e09bb2d9a4b7667fcafc9f52d7c32582af9b Mon Sep 17 00:00:00 2001 From: Mae Brooks <138945353+MaeBrooks@users.noreply.github.com> Date: Thu, 20 Nov 2025 12:10:47 -0800 Subject: [PATCH 158/430] Ran rexm testing for macos (#5366) --- tools/rexm/reports/examples_testing_macos.md | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tools/rexm/reports/examples_testing_macos.md diff --git a/tools/rexm/reports/examples_testing_macos.md b/tools/rexm/reports/examples_testing_macos.md new file mode 100644 index 000000000..1fa686ee6 --- /dev/null +++ b/tools/rexm/reports/examples_testing_macos.md @@ -0,0 +1,24 @@ +# EXAMPLES COLLECTION - TESTING REPORT + +## Tested Platform: macOS + +``` +Example automated testing elements validated: + - [CWARN] : Compilation WARNING messages + - [LWARN] : Log WARNING messages count + - [INIT] : Initialization + - [CLOSE] : Closing + - [ASSETS] : Assets loading + - [RLGL] : OpenGL-wrapped initialization + - [PLAT] : Platform initialization + - [FONT] : Font default initialization + - [TIMER] : Timer initialization +``` +| **EXAMPLE NAME** | [CWARN] | [LWARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | +|:---------------------------------|:-------:|:-------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| +| text_font_loading | 0 | 10 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| text_codepoints_loading | 0 | 1 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| models_animation_playing | 0 | 1 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | +| shaders_palette_switch | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| shaders_hybrid_rendering | 0 | 4 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | + From 6820ff61f1c9f3f9d0dbf04163f2e7486742d905 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 20 Nov 2025 21:33:43 +0100 Subject: [PATCH 159/430] REVIEWED: example: `shaders_hybrid_rendering`, shaders issues --- .../shaders/glsl100/hybrid_raster.fs | 3 +- .../shaders/glsl100/hybrid_raymarch.fs | 13 ++- .../shaders/glsl330/hybrid_raster.fs | 6 +- .../shaders/glsl330/hybrid_raymarch.fs | 91 ++++++++++--------- examples/shaders/shaders_hybrid_rendering.c | 3 +- 5 files changed, 64 insertions(+), 52 deletions(-) diff --git a/examples/shaders/resources/shaders/glsl100/hybrid_raster.fs b/examples/shaders/resources/shaders/glsl100/hybrid_raster.fs index 35bc75d30..9658b3819 100644 --- a/examples/shaders/resources/shaders/glsl100/hybrid_raster.fs +++ b/examples/shaders/resources/shaders/glsl100/hybrid_raster.fs @@ -1,6 +1,7 @@ #version 100 -#extension GL_EXT_frag_depth : enable // Extension required for writing depth +#extension GL_EXT_frag_depth : enable // Extension required for writing depth + precision mediump float; // Precision required for OpenGL ES2 (WebGL) varying vec2 fragTexCoord; diff --git a/examples/shaders/resources/shaders/glsl100/hybrid_raymarch.fs b/examples/shaders/resources/shaders/glsl100/hybrid_raymarch.fs index 44233b3bb..8f9fa0907 100644 --- a/examples/shaders/resources/shaders/glsl100/hybrid_raymarch.fs +++ b/examples/shaders/resources/shaders/glsl100/hybrid_raymarch.fs @@ -2,6 +2,9 @@ #extension GL_EXT_frag_depth : enable //Extension required for writing depth #extension GL_OES_standard_derivatives : enable //Extension used for fwidth() + +#define ZERO 0 + precision mediump float; // Precision required for OpenGL ES2 (WebGL) // Input vertex attributes (from vertex shader) @@ -17,8 +20,6 @@ uniform vec3 camPos; uniform vec3 camDir; uniform vec2 screenCenter; -#define ZERO 0 - // SRC: https://learnopengl.com/Advanced-OpenGL/Depth-testing float CalcDepth(in vec3 rd, in float Idist) { @@ -128,7 +129,7 @@ float calcSoftshadow(in vec3 ro, in vec3 rd, in float mint, in float tmax) float res = 1.0; float t = mint; - for (int i=ZERO; i<24; i++) + for (int i = ZERO; i < 24; i++) { float h = map(ro + rd*t).x; float s = clamp(8.0*h/t,0.0,1.0); @@ -156,7 +157,7 @@ float calcAO(in vec3 pos, in vec3 nor) { float occ = 0.0; float sca = 1.0; - for (int i=ZERO; i<5; i++) + for (int i = ZERO; i < 5; i++) { float h = 0.01 + 0.12*float(i)/4.0; float d = map(pos + h*nor).x; @@ -257,7 +258,8 @@ vec4 render(in vec3 ro, in vec3 rd) return vec4(vec3(clamp(col,0.0,1.0)),t); } -vec3 CalcRayDir(vec2 nCoord){ +vec3 CalcRayDir(vec2 nCoord) +{ vec3 horizontal = normalize(cross(camDir,vec3(.0 , 1.0, .0))); vec3 vertical = normalize(cross(horizontal,camDir)); return normalize(camDir + horizontal*nCoord.x + vertical*nCoord.y); @@ -287,6 +289,7 @@ void main() color = res.xyz; depth = CalcDepth(rd,res.w); } + gl_FragColor = vec4(color , 1.0); gl_FragDepthEXT = depth; } \ No newline at end of file diff --git a/examples/shaders/resources/shaders/glsl330/hybrid_raster.fs b/examples/shaders/resources/shaders/glsl330/hybrid_raster.fs index 12409d137..0b94dbdef 100644 --- a/examples/shaders/resources/shaders/glsl330/hybrid_raster.fs +++ b/examples/shaders/resources/shaders/glsl330/hybrid_raster.fs @@ -9,7 +9,7 @@ uniform sampler2D texture0; uniform vec4 colDiffuse; // Output fragment color -//out vec4 finalColor; +out vec4 finalColor; // NOTE: Add your custom variables here @@ -17,6 +17,6 @@ void main() { vec4 texelColor = texture(texture0, fragTexCoord); - gl_FragColor = texelColor*colDiffuse*fragColor; - gl_FragDepth = gl_FragCoord.z; + finalColor = texelColor*colDiffuse*fragColor; + gl_FragDepth = finalColor.z; } \ No newline at end of file diff --git a/examples/shaders/resources/shaders/glsl330/hybrid_raymarch.fs b/examples/shaders/resources/shaders/glsl330/hybrid_raymarch.fs index 2edb625ad..f1fafc640 100644 --- a/examples/shaders/resources/shaders/glsl330/hybrid_raymarch.fs +++ b/examples/shaders/resources/shaders/glsl330/hybrid_raymarch.fs @@ -1,5 +1,7 @@ # version 330 +#define ZERO 0 + // Input vertex attributes (from vertex shader) in vec2 fragTexCoord; in vec4 fragColor; @@ -13,10 +15,12 @@ uniform vec3 camPos; uniform vec3 camDir; uniform vec2 screenCenter; -#define ZERO 0 +// Output fragment color +out vec4 finalColor; // https://learnopengl.com/Advanced-OpenGL/Depth-testing -float CalcDepth(in vec3 rd, in float Idist){ +float CalcDepth(in vec3 rd, in float Idist) +{ float local_z = dot(normalize(camDir),rd)*Idist; return (1.0/(local_z) - 1.0/0.01)/(1.0/1000.0 -1.0/0.01); } @@ -26,15 +30,13 @@ float sdHorseshoe(in vec3 p, in vec2 c, in float r, in float le, vec2 w) { p.x = abs(p.x); float l = length(p.xy); - p.xy = mat2(-c.x, c.y, - c.y, c.x)*p.xy; - p.xy = vec2((p.y>0.0 || p.x>0.0)?p.x:l*sign(-c.x), - (p.x>0.0)?p.y:l); - p.xy = vec2(p.x,abs(p.y-r))-vec2(le,0.0); + p.xy = mat2(-c.x, c.y, c.y, c.x)*p.xy; + p.xy = vec2(((p.y > 0.0) || (p.x > 0.0))? p.x : l*sign(-c.x), (p.x>0.0)? p.y : l); + p.xy = vec2(p.x, abs(p.y - r)) - vec2(le, 0.0); - vec2 q = vec2(length(max(p.xy,0.0)) + min(0.0,max(p.x,p.y)),p.z); + vec2 q = vec2(length(max(p.xy, 0.0)) + min(0.0, max(p.x, p.y)), p.z); vec2 d = abs(q) - w; - return min(max(d.x,d.y),0.0) + length(max(d,0.0)); + return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)); } // r = sphere's radius @@ -44,17 +46,16 @@ float sdSixWayCutHollowSphere(vec3 p, float r, float h, float t) { // Six way symetry Transformation vec3 ap = abs(p); - if (ap.x < max(ap.y, ap.z)){ + if (ap.x < max(ap.y, ap.z)) + { if (ap.y < ap.z) ap.xz = ap.zx; else ap.xy = ap.yx; } vec2 q = vec2(length(ap.yz), ap.x); - float w = sqrt(r*r-h*h); - return ((h*q.x0.0) + if (tp1 > 0.0) { tmax = min(tmax, tp1); res = vec2(tp1, 1.0); } float t = tmin; - for (int i=0; i<70 ; i++) + for (int i = 0; i < 70 ; i++) { - if (t>tmax) break; - vec2 h = map(ro+rd*t); - if (abs(h.x)<(0.0001*t)) + if (t > tmax) break; + vec2 h = map(ro + rd*t); + if (abs(h.x )< (0.0001*t)) { - res = vec2(t,h.y); + res = vec2(t, h.y); break; } t += h.x; @@ -111,28 +115,28 @@ vec2 raycast(in vec3 ro, in vec3 rd){ return res; } - // https://iquilezles.org/articles/rmshadows float calcSoftshadow(in vec3 ro, in vec3 rd, in float mint, in float tmax) { // bounding volume - float tp = (0.8-ro.y)/rd.y; if (tp>0.0) tmax = min(tmax, tp); + float tp = (0.8 - ro.y)/rd.y; if (tp > 0.0) tmax = min(tmax, tp); float res = 1.0; float t = mint; - for (int i=ZERO; i<24; i++) + for (int i = ZERO; i < 24; i++) { float h = map(ro + rd*t).x; - float s = clamp(8.0*h/t,0.0,1.0); + float s = clamp(8.0*h/t, 0.0, 1.0); res = min(res, s); t += clamp(h, 0.01, 0.2); - if (res<0.004 || t>tmax) break; + if ((res < 0.004) || (t > tmax)) break; } + res = clamp(res, 0.0, 1.0); + return res*res*(3.0-2.0*res); } - // https://iquilezles.org/articles/normalsSDF vec3 calcNormal(in vec3 pos) { @@ -148,7 +152,7 @@ float calcAO(in vec3 pos, in vec3 nor) { float occ = 0.0; float sca = 1.0; - for (int i=ZERO; i<5; i++) + for (int i = ZERO; i < 5; i++) { float h = 0.01 + 0.12*float(i)/4.0; float d = map(pos + h*nor).x; @@ -156,6 +160,7 @@ float calcAO(in vec3 pos, in vec3 nor) sca *= 0.95; if (occ>0.35) break; } + return clamp(1.0 - 3.0*occ, 0.0, 1.0)*(0.5+0.5*nor.y); } @@ -165,9 +170,9 @@ 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; + 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; + return (0.5 - 0.5*i.x*i.y); } // https://www.shadertoy.com/view/tdS3DG @@ -180,7 +185,7 @@ vec4 render(in vec3 ro, in vec3 rd) vec2 res = raycast(ro,rd); float t = res.x; float m = res.y; - if (m>-0.5) + if (m > -0.5) { vec3 pos = ro + t*rd; vec3 nor = (m<1.5) ? vec3(0.0,1.0,0.0) : calcNormal(pos); @@ -190,7 +195,7 @@ vec4 render(in vec3 ro, in vec3 rd) col = 0.2 + 0.2*sin(m*2.0 + vec3(0.0,1.0,2.0)); float ks = 1.0; - if (m<1.5) + if (m < 1.5) { float f = checkersGradBox(3.0*pos.xz); col = 0.15 + f*vec3(0.05); @@ -207,14 +212,14 @@ vec4 render(in vec3 ro, in vec3 rd) vec3 lig = normalize(vec3(-0.5, 0.4, -0.6)); vec3 hal = normalize(lig-rd); float dif = clamp(dot(nor, lig), 0.0, 1.0); - //if (dif>0.0001) + //if (dif>0.0001) dif *= calcSoftshadow(pos, lig, 0.02, 2.5); float spe = pow(clamp(dot(nor, hal), 0.0, 1.0),16.0); spe *= dif; spe *= 0.04+0.96*pow(clamp(1.0-dot(hal,lig),0.0,1.0),5.0); //spe *= 0.04+0.96*pow(clamp(1.0-sqrt(0.5*(1.0-dot(rd,lig))),0.0,1.0),5.0); lin += col*2.20*dif*vec3(1.30,1.00,0.70); - lin += 5.00*spe*vec3(1.30,1.00,0.70)*ks; + lin += 5.00*spe*vec3(1.30,1.00,0.70)*ks; } // sky { @@ -249,7 +254,8 @@ vec4 render(in vec3 ro, in vec3 rd) return vec4(vec3(clamp(col,0.0,1.0)),t); } -vec3 CalcRayDir(vec2 nCoord){ +vec3 CalcRayDir(vec2 nCoord) +{ vec3 horizontal = normalize(cross(camDir,vec3(.0 , 1.0, .0))); vec3 vertical = normalize(cross(horizontal,camDir)); return normalize(camDir + horizontal*nCoord.x + vertical*nCoord.y); @@ -279,6 +285,7 @@ void main() color = res.xyz; depth = CalcDepth(rd,res.w); } - gl_FragColor = vec4(color , 1.0); + + finalColor = vec4(color , 1.0); gl_FragDepth = depth; } \ No newline at end of file diff --git a/examples/shaders/shaders_hybrid_rendering.c b/examples/shaders/shaders_hybrid_rendering.c index ec6b8b0a6..e523a91c8 100644 --- a/examples/shaders/shaders_hybrid_rendering.c +++ b/examples/shaders/shaders_hybrid_rendering.c @@ -118,7 +118,7 @@ int main(void) // Raymarch Scene rlEnableDepthTest(); // Manually enable Depth Test to handle multiple rendering methods BeginShaderMode(shdrRaymarch); - DrawRectangleRec((Rectangle){0,0, (float)screenWidth, (float)screenHeight},WHITE); + DrawRectangleRec((Rectangle){ 0,0, (float)screenWidth, (float)screenHeight },WHITE); EndShaderMode(); // Rasterize Scene @@ -138,6 +138,7 @@ int main(void) ClearBackground(RAYWHITE); DrawTextureRec(target.texture, (Rectangle) { 0, 0, (float)screenWidth, (float)-screenHeight }, (Vector2) { 0, 0 }, WHITE); + DrawFPS(10, 10); EndDrawing(); //---------------------------------------------------------------------------------- From dddc94dc7a763d165a43c47c6ce1ce3c59f0652f Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 22 Nov 2025 20:14:29 +0100 Subject: [PATCH 160/430] Update examples_testing_web.md --- tools/rexm/reports/examples_testing_web.md | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/rexm/reports/examples_testing_web.md b/tools/rexm/reports/examples_testing_web.md index 1704b5986..1f6ed58b3 100644 --- a/tools/rexm/reports/examples_testing_web.md +++ b/tools/rexm/reports/examples_testing_web.md @@ -17,7 +17,6 @@ Example automated testing elements validated: | **EXAMPLE NAME** | [CWARN] | [LWARN] | [INIT] | [CLOSE] | [ASSETS] | [RLGL] | [PLAT] | [FONT] | [TIMER] | |:---------------------------------|:-------:|:-------:|:------:|:-------:|:--------:|:------:|:------:|:------:|:-------:| | core_monitor_detector | 0 | 1 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| textures_screen_buffer | 0 | 0 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | textures_sprite_stacking | 0 | 0 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | | text_sprite_fonts | 0 | 0 | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ❌ | | text_font_loading | 0 | 3 | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | From 12cce1766fed3c4fd555a636f15b63eeff897aab Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 22 Nov 2025 20:14:41 +0100 Subject: [PATCH 161/430] Update textures_screen_buffer.c --- examples/textures/textures_screen_buffer.c | 107 +++++++++------------ 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/examples/textures/textures_screen_buffer.c b/examples/textures/textures_screen_buffer.c index 98c9a2ad5..e620aab31 100644 --- a/examples/textures/textures_screen_buffer.c +++ b/examples/textures/textures_screen_buffer.c @@ -17,16 +17,10 @@ #include "raylib.h" -#define MAX_COLORS 256 -#define SCREEN_WIDTH 800 -#define SCREEN_HEIGHT 450 -#define SCALE_FACTOR 2 -// buffer size at least for screenImage pixel count -#define INDEX_BUFFER_SIZE ((SCREEN_WIDTH * SCREEN_HEIGHT) / SCALE_FACTOR) -#define FLAME_WIDTH (SCREEN_WIDTH / SCALE_FACTOR) +#include // Required for: calloc(), free() -static void GeneretePalette(Color *palette); -static void ClearIndexBuffer(unsigned char *buffer, int count); +#define MAX_COLORS 256 +#define SCALE_FACTOR 2 //------------------------------------------------------------------------------------ // Program main entry point @@ -35,22 +29,31 @@ int main(void) { // Initialization //-------------------------------------------------------------------------------------- - const int screenWidth = SCREEN_WIDTH; - const int screenHeight = SCREEN_HEIGHT; - const int pixelScale = SCALE_FACTOR; - const int imageWidth = screenWidth / pixelScale; - const int imageHeight = screenHeight / pixelScale; + const int screenWidth = 800; + const int screenHeight = 450; + InitWindow(screenWidth, screenHeight, "raylib [textures] example - screen buffer"); - Color palette[MAX_COLORS] = {0}; - unsigned char indexBuffer[INDEX_BUFFER_SIZE] = {0}; - unsigned char flameRootBuffer[FLAME_WIDTH] = {0}; + int imageWidth = screenWidth/SCALE_FACTOR; + int imageHeight = screenHeight/SCALE_FACTOR; + int flameWidth = screenWidth/SCALE_FACTOR; + + Color palette[MAX_COLORS] = { 0 }; + unsigned char *indexBuffer = RL_CALLOC(imageWidth*imageWidth, sizeof(unsigned char)); + unsigned char *flameRootBuffer = RL_CALLOC(flameWidth, sizeof(unsigned char)); Image screenImage = GenImageColor(imageWidth, imageHeight, BLACK); Texture screenTexture = LoadTextureFromImage(screenImage); - GeneretePalette(palette); - ClearIndexBuffer(indexBuffer, INDEX_BUFFER_SIZE); - ClearIndexBuffer(flameRootBuffer, FLAME_WIDTH); + + // Generate flame color palette + for (int i = 0; i < MAX_COLORS; i++) + { + float t = (float)i/(float)(MAX_COLORS - 1); + float hue = t*t; + float saturation = t; + float value = t; + palette[i] = ColorFromHSV(250.0f + 150.0f*hue, saturation, value); + } SetTargetFPS(60); //-------------------------------------------------------------------------------------- @@ -58,8 +61,10 @@ int main(void) // Main game loop while (!WindowShouldClose()) // Detect window close button or ESC key { + // Update + //---------------------------------------------------------------------------------- // Grow flameRoot - for (int x = 2; x < FLAME_WIDTH; ++x) + for (int x = 2; x < flameWidth; x++) { unsigned short flame = flameRootBuffer[x]; if (flame == 255) continue; @@ -68,26 +73,26 @@ int main(void) flameRootBuffer[x] = flame; } - // transfer flameRoot to indexBuffer - for (int x = 0; x < FLAME_WIDTH; ++x) + // Transfer flameRoot to indexBuffer + for (int x = 0; x < flameWidth; x++) { - int i = x + (imageHeight - 1) * imageWidth; + int i = x + (imageHeight - 1)*imageWidth; indexBuffer[i] = flameRootBuffer[x]; } // Clear top row, because it can't move any higher - for (int x = 0; x < imageWidth; ++x) + for (int x = 0; x < imageWidth; x++) { if (indexBuffer[x] == 0) continue; indexBuffer[x] = 0; } // Skip top row, it is already cleared - for (int y = 1; y < imageHeight; ++y) + for (int y = 1; y < imageHeight; y++) { - for (int x = 0; x < imageWidth; ++x) + for (int x = 0; x < imageWidth; x++) { - unsigned i = x + y * imageWidth; + unsigned int i = x + y*imageWidth; unsigned char colorIndex = indexBuffer[i]; if (colorIndex == 0) continue; @@ -97,19 +102,19 @@ int main(void) int newX = x + moveX; if (newX < 0 || newX >= imageWidth) continue; - unsigned i_above = i - imageWidth + moveX; + unsigned int iabove = i - imageWidth + moveX; int decay = GetRandomValue(0, 3); - colorIndex -= (decay < colorIndex) ? decay : colorIndex; - indexBuffer[i_above] = colorIndex; + colorIndex -= (decay < colorIndex)? decay : colorIndex; + indexBuffer[iabove] = colorIndex; } } // Update screenImage with palette colors - for (int y = 1; y < imageHeight; ++y) + for (int y = 1; y < imageHeight; y++) { - for (int x = 0; x < imageWidth; ++x) + for (int x = 0; x < imageWidth; x++) { - unsigned i = x + y * imageWidth; + unsigned int i = x + y*imageWidth; unsigned char colorIndex = indexBuffer[i]; Color col = palette[colorIndex]; ImageDrawPixel(&screenImage, x, y, col); @@ -117,19 +122,24 @@ int main(void) } UpdateTexture(screenTexture, screenImage.data); + //---------------------------------------------------------------------------------- + // Draw //---------------------------------------------------------------------------------- BeginDrawing(); - const Vector2 origin = (Vector2){0, 0}; - const float rotation = 0.f; - DrawTextureEx(screenTexture, origin, rotation, pixelScale, WHITE); + + ClearBackground(RAYWHITE); + + DrawTextureEx(screenTexture, (Vector2){ 0, 0 }, 0.0f, 2.0f, WHITE); + EndDrawing(); //---------------------------------------------------------------------------------- } // De-Initialization //-------------------------------------------------------------------------------------- - + RL_FREE(indexBuffer); + RL_FREE(flameRootBuffer); UnloadTexture(screenTexture); UnloadImage(screenImage); @@ -138,24 +148,3 @@ int main(void) return 0; } - -static void GeneretePalette(Color *palette) -{ - for (int i = 0; i < MAX_COLORS; ++i) - { - float t = (float)i/(float)(MAX_COLORS - 1); - float hue = t * t; - float saturation = t; - float value = t; - palette[i] = ColorFromHSV(250.f + 150.f * hue, saturation, value); - } -} - -static void ClearIndexBuffer(unsigned char *buffer, int count) -{ - // Use memset to set to ZERO, but for demonstration a plain for loop is used - for (int i = 0; i < count; ++i) - { - buffer[i] = 0; - } -} \ No newline at end of file From 6c3ef8d9b4cef36e7358686065ce5d7106f555c2 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 22 Nov 2025 20:15:50 +0100 Subject: [PATCH 162/430] Remove trailing spaces --- src/raudio.c | 2 +- src/rtextures.c | 4 ++-- tools/rexm/rexm.c | 22 +++++++++++----------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/raudio.c b/src/raudio.c index 66e04fad6..1d9edca0a 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -593,7 +593,7 @@ AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sam // Init audio buffer values audioBuffer->volume = 1.0f; audioBuffer->pitch = 1.0f; - audioBuffer->pan = 0.0f; // Center + audioBuffer->pan = 0.0f; // Center audioBuffer->callback = NULL; audioBuffer->processor = NULL; diff --git a/src/rtextures.c b/src/rtextures.c index 00554e418..299ab6793 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -3343,7 +3343,7 @@ void ImageClearBackground(Image *dst, Color color) int bytesPerPixel = GetPixelDataSize(1, 1, dst->format); int totalPixels = dst->width * dst->height; - // Repeat the first pixel data throughout the image, + // Repeat the first pixel data throughout the image, // doubling the pixels copied on each iteration for (int i = 1; i < totalPixels; i *= 2) { @@ -3727,7 +3727,7 @@ void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color) unsigned char *pSrcPixel = (unsigned char *)dst->data + bytesOffset; // Repeat the first pixel data throughout the row - for (int x = 1; x < (int)rec.width; x *= 2) + for (int x = 1; x < (int)rec.width; x *= 2) { int pixelsToCopy = MIN(x, (int)rec.width - x); memcpy(pSrcPixel + x*bytesPerPixel, pSrcPixel, pixelsToCopy * bytesPerPixel); diff --git a/tools/rexm/rexm.c b/tools/rexm/rexm.c index 19f52d3aa..72b962874 100644 --- a/tools/rexm/rexm.c +++ b/tools/rexm/rexm.c @@ -418,7 +418,7 @@ int main(int argc, char *argv[]) // Support building/testing not only individual examples but multiple: ALL/ int exBuildListInfoCount = 0; rlExampleInfo *exBuildListInfo = LoadExampleData(argv[2], false, &exBuildListInfoCount); - + for (int i = 0; i < exBuildListInfoCount; i++) { if (!TextIsEqual(exBuildListInfo[i].category, "others")) @@ -428,9 +428,9 @@ int main(int argc, char *argv[]) exBuildListCount++; } } - + UnloadExampleData(exBuildListInfo); - + if (exBuildListCount == 0) LOG("WARNING: BUILD: Example requested not available in the collection\n"); else { @@ -933,7 +933,7 @@ int main(int argc, char *argv[]) system(TextFormat("mingw32-make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName)); #elif defined(PLATFORM_DRM) LOG("INFO: [%s] Building example for PLATFORM_DRM (Host: POSIX)\n", exName); - system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DRM -B > %s/%s/logs/%s.build.log 2>&1", + system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DRM -B > %s/%s/logs/%s.build.log 2>&1", exBasePath, exCategory, exName, exBasePath, exCategory, exName)); #else LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: POSIX)\n", exName); @@ -1495,7 +1495,7 @@ int main(int argc, char *argv[]) strncpy(exCategory, exName, TextFindIndex(exName, "_")); // Skip some examples from building - if ((strcmp(exName, "core_custom_logging") == 0) || + if ((strcmp(exName, "core_custom_logging") == 0) || (strcmp(exName, "core_window_should_close") == 0) || (strcmp(exName, "core_custom_frame_control") == 0)) continue; @@ -1512,7 +1512,7 @@ int main(int argc, char *argv[]) // STEP 3: Run example with arguments: --frames 2 > .out.log // STEP 4: Load .out.log and check "WARNING:" messages -> Some could maybe be ignored // STEP 5: Generate report with results - + // STEP 1: Load example and inject required code // PROBLEM: As we need to modify the example source code for building, we need to keep a copy or something // WARNING: If we make a copy and something fails, it could not be restored at the end @@ -1572,7 +1572,7 @@ int main(int argc, char *argv[]) // Build: raylib.com/examples//_example_name.js #if defined(_WIN32) LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: Win32)\n", exName); - system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B > %s/%s/logs/%s.build.log 2>&1", + system(TextFormat("mingw32-make -C %s -f Makefile.Web %s/%s PLATFORM=PLATFORM_WEB -B > %s/%s/logs/%s.build.log 2>&1", exBasePath, exCategory, exName, exBasePath, exCategory, exName)); #else LOG("INFO: [%s] Building example for PLATFORM_WEB (Host: POSIX)\n", exName); @@ -1622,15 +1622,15 @@ int main(int argc, char *argv[]) // Build example for PLATFORM_DESKTOP #if defined(_WIN32) LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: Win32)\n", exName); - system(TextFormat("mingw32-make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B > %s/%s/logs/%s.build.log 2>&1", + system(TextFormat("mingw32-make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B > %s/%s/logs/%s.build.log 2>&1", exBasePath, exCategory, exName, exBasePath, exCategory, exName)); #elif defined(PLATFORM_DRM) LOG("INFO: [%s] Building example for PLATFORM_DRM (Host: POSIX)\n", exName); - system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DRM -B > %s/%s/logs/%s.build.log 2>&1", + system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DRM -B > %s/%s/logs/%s.build.log 2>&1", exBasePath, exCategory, exName, exBasePath, exCategory, exName)); #else LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: POSIX)\n", exName); - system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B > %s/%s/logs/%s.build.log 2>&1", + system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B > %s/%s/logs/%s.build.log 2>&1", exBasePath, exCategory, exName, exBasePath, exCategory, exName)); #endif // Restore original source code before continue @@ -1814,7 +1814,7 @@ int main(int argc, char *argv[]) if ((testing[i].buildwarns > 0) || (testing[i].warnings > 0) || (testing[i].status > 0)) { repIndex += sprintf(report + repIndex, "| %-32s | %i | %i | %s | %s | %s | %s | %s | %s | %s |\n", - exBuildList[i], + exBuildList[i], testing[i].buildwarns, testing[i].warnings, (testing[i].status & TESTING_FAIL_INIT)? "❌" : "✔", From 727a90c5d15f82016774b6ccc3a7384edda67f8e Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 22 Nov 2025 20:16:33 +0100 Subject: [PATCH 163/430] REVIEWED: Formatting to follow raylib conventions --- .../audio/audio_fft_spectrum_visualizer.c | 13 +- examples/audio/audio_music_stream.c | 20 +- examples/audio/audio_raw_stream.c | 2 +- examples/audio/audio_sound_positioning.c | 2 +- examples/core/core_highdpi_testbed.c | 12 +- examples/core/core_input_gestures_testbed.c | 2 +- examples/core/core_input_multitouch.c | 4 +- examples/core/core_monitor_detector.c | 2 +- examples/core/core_undo_redo.c | 4 +- examples/core/core_viewport_scaling.c | 266 ++++++++---------- examples/core/core_window_flags.c | 3 +- examples/models/models_loading_vox.c | 2 +- examples/shaders/shaders_basic_pbr.c | 6 +- examples/shaders/shaders_color_correction.c | 2 +- examples/shaders/shaders_hybrid_rendering.c | 2 +- examples/shapes/shapes_bullet_hell.c | 2 +- examples/shapes/shapes_clock_of_clocks.c | 101 +++---- examples/shapes/shapes_double_pendulum.c | 2 +- examples/shapes/shapes_math_angle_rotation.c | 18 +- examples/shapes/shapes_math_sine_cosine.c | 17 +- examples/shapes/shapes_mouse_trail.c | 2 +- examples/shapes/shapes_rectangle_advanced.c | 2 +- examples/shapes/shapes_recursive_tree.c | 2 +- examples/shapes/shapes_rlgl_color_wheel.c | 16 +- examples/shapes/shapes_simple_particles.c | 2 +- examples/shapes/shapes_triangle_strip.c | 4 +- examples/text/text_3d_drawing.c | 4 +- examples/text/text_inline_styling.c | 2 +- examples/text/text_unicode_emojis.c | 4 +- examples/text/text_words_alignment.c | 25 +- examples/textures/textures_mouse_painting.c | 2 +- examples/textures/textures_sprite_stacking.c | 2 +- examples/textures/textures_tiled_drawing.c | 2 +- 33 files changed, 248 insertions(+), 303 deletions(-) diff --git a/examples/audio/audio_fft_spectrum_visualizer.c b/examples/audio/audio_fft_spectrum_visualizer.c index 5993186ab..cad683462 100644 --- a/examples/audio/audio_fft_spectrum_visualizer.c +++ b/examples/audio/audio_fft_spectrum_visualizer.c @@ -148,7 +148,7 @@ int main(void) CaptureFrame(&fft, audioSamples); RenderFrame(&fft, &fftImage); UpdateTexture(fftTexture, fftImage.data); - //------------------------------------------------------------------------------ + //---------------------------------------------------------------------------------- // Draw //---------------------------------------------------------------------------------- @@ -269,7 +269,7 @@ static void CaptureFrame(FFTData *fftData, const float *audioSamples) fftData->lastFftTime = GetTime(); memcpy(fftData->fftHistory[fftData->historyPos], smoothedSpectrum, sizeof(smoothedSpectrum)); - fftData->historyPos = (fftData->historyPos + 1) % fftData->fftHistoryLen; + fftData->historyPos = (fftData->historyPos + 1)%fftData->fftHistoryLen; } static void RenderFrame(const FFTData *fftData, Image *fftImage) @@ -277,12 +277,9 @@ static void RenderFrame(const FFTData *fftData, Image *fftImage) double framesSinceTapback = floor(fftData->tapbackPos/WINDOW_TIME); framesSinceTapback = Clamp(framesSinceTapback, 0.0, fftData->fftHistoryLen - 1); - int historyPosition = (fftData->historyPos - 1 - (int)framesSinceTapback) % fftData->fftHistoryLen; - if (historyPosition < 0) - historyPosition += fftData->fftHistoryLen; + int historyPosition = (fftData->historyPos - 1 - (int)framesSinceTapback)%fftData->fftHistoryLen; + if (historyPosition < 0) historyPosition += fftData->fftHistoryLen; const float *amplitude = fftData->fftHistory[historyPosition]; - for (int bin = 0; bin < BUFFER_SIZE; bin++) { - ImageDrawPixel(fftImage, bin, FFT_ROW, ColorFromNormalized((Vector4){ amplitude[bin], UNUSED_CHANNEL, UNUSED_CHANNEL, UNUSED_CHANNEL })); - } + for (int bin = 0; bin < BUFFER_SIZE; bin++) ImageDrawPixel(fftImage, bin, FFT_ROW, ColorFromNormalized((Vector4){ amplitude[bin], UNUSED_CHANNEL, UNUSED_CHANNEL, UNUSED_CHANNEL })); } \ No newline at end of file diff --git a/examples/audio/audio_music_stream.c b/examples/audio/audio_music_stream.c index 4533ff30b..05ec1c2d6 100644 --- a/examples/audio/audio_music_stream.c +++ b/examples/audio/audio_music_stream.c @@ -35,10 +35,10 @@ int main(void) float timePlayed = 0.0f; // Time played normalized [0.0f..1.0f] bool pause = false; // Music playing paused - + float pan = 0.0f; // Default audio pan center [-1.0f..1.0f] SetMusicPan(music, pan); - + float volume = 0.8f; // Default audio volume [0.0f..1.0f] SetMusicVolume(music, volume); @@ -67,29 +67,29 @@ int main(void) if (pause) PauseMusicStream(music); else ResumeMusicStream(music); } - + // Set audio pan - if (IsKeyDown(KEY_LEFT)) + if (IsKeyDown(KEY_LEFT)) { pan -= 0.05f; if (pan < -1.0f) pan = -1.0f; SetMusicPan(music, pan); } - else if (IsKeyDown(KEY_RIGHT)) + else if (IsKeyDown(KEY_RIGHT)) { pan += 0.05f; if (pan > 1.0f) pan = 1.0f; SetMusicPan(music, pan); } - + // Set audio volume - if (IsKeyDown(KEY_DOWN)) + if (IsKeyDown(KEY_DOWN)) { volume -= 0.05f; if (volume < 0.0f) volume = 0.0f; SetMusicVolume(music, volume); } - else if (IsKeyDown(KEY_UP)) + else if (IsKeyDown(KEY_UP)) { volume += 0.05f; if (volume > 1.0f) volume = 1.0f; @@ -109,7 +109,7 @@ int main(void) ClearBackground(RAYWHITE); DrawText("MUSIC SHOULD BE PLAYING!", 255, 150, 20, LIGHTGRAY); - + DrawText("LEFT-RIGHT for PAN CONTROL", 320, 74, 10, DARKBLUE); DrawRectangle(300, 100, 200, 12, LIGHTGRAY); DrawRectangleLines(300, 100, 200, 12, GRAY); @@ -121,7 +121,7 @@ int main(void) DrawText("PRESS SPACE TO RESTART MUSIC", 215, 250, 20, LIGHTGRAY); DrawText("PRESS P TO PAUSE/RESUME MUSIC", 208, 280, 20, LIGHTGRAY); - + DrawText("UP-DOWN for VOLUME CONTROL", 320, 334, 10, DARKGREEN); DrawRectangle(300, 360, 200, 12, LIGHTGRAY); DrawRectangleLines(300, 360, 200, 12, GRAY); diff --git a/examples/audio/audio_raw_stream.c b/examples/audio/audio_raw_stream.c index 4deae2090..b327e92af 100644 --- a/examples/audio/audio_raw_stream.c +++ b/examples/audio/audio_raw_stream.c @@ -166,7 +166,7 @@ int main(void) memcpy(writeBuf + writeCursor, data + readCursor, writeLength*sizeof(short)); // Update cursors and loop audio - readCursor = (readCursor + writeLength) % waveLength; + readCursor = (readCursor + writeLength)%waveLength; writeCursor += writeLength; } diff --git a/examples/audio/audio_sound_positioning.c b/examples/audio/audio_sound_positioning.c index 9159acf08..34b15c07b 100644 --- a/examples/audio/audio_sound_positioning.c +++ b/examples/audio/audio_sound_positioning.c @@ -95,7 +95,7 @@ int main(void) CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- - + return 0; } diff --git a/examples/core/core_highdpi_testbed.c b/examples/core/core_highdpi_testbed.c index 5cd2a7dc7..a341d081c 100644 --- a/examples/core/core_highdpi_testbed.c +++ b/examples/core/core_highdpi_testbed.c @@ -27,9 +27,10 @@ int main(void) const int screenWidth = 800; const int screenHeight = 450; + SetConfigFlags(FLAG_WINDOW_HIGHDPI | FLAG_WINDOW_RESIZABLE); InitWindow(screenWidth, screenHeight, "raylib [core] example - highdpi testbed"); - // TODO: Load resources / Initialize variables at this point + int gridSpacing = 40; // Grid spacing in pixels SetTargetFPS(60); //-------------------------------------------------------------------------------------- @@ -48,11 +49,12 @@ int main(void) ClearBackground(RAYWHITE); - // TODO: Draw everything that requires to be drawn at this point + // Draw grid + for (int h = 0; h < 20; h++) DrawLine(0, h*gridSpacing, GetRenderWidth(), h*gridSpacing, LIGHTGRAY); + for (int v = 0; v < 40; v++) DrawLine(v*gridSpacing, 0, v*gridSpacing, GetScreenHeight(), LIGHTGRAY); - DrawLineEx((Vector2){ 0, 0 }, (Vector2){ screenWidth, screenHeight }, 2.0f, RED); - DrawLineEx((Vector2){ 0, screenHeight }, (Vector2){ screenWidth, 0 }, 2.0f, RED); - DrawText("example base code template", 260, 400, 20, LIGHTGRAY); + // Draw UI info + DrawText(TextFormat("SCREEN SIZE: %ix%i", GetScreenWidth(), GetScreenHeight()), 10, 10, 20, BLACK); EndDrawing(); //---------------------------------------------------------------------------------- diff --git a/examples/core/core_input_gestures_testbed.c b/examples/core/core_input_gestures_testbed.c index f318ab4a4..e0ffeb13f 100644 --- a/examples/core/core_input_gestures_testbed.c +++ b/examples/core/core_input_gestures_testbed.c @@ -202,7 +202,7 @@ int main(void) DrawText("Log", (int)gestureLogPosition.x, (int)gestureLogPosition.y, 20, BLACK); // Loop in both directions to print the gesture log array in the inverted order (and looping around if the index started somewhere in the middle) - for (i = 0, ii = gestureLogIndex; i < GESTURE_LOG_SIZE; i++, ii = (ii + 1) % GESTURE_LOG_SIZE) DrawText(gestureLog[ii], (int)gestureLogPosition.x, (int)gestureLogPosition.y + 410 - i*20, 20, (i == 0 ? gestureColor : LIGHTGRAY)); + for (i = 0, ii = gestureLogIndex; i < GESTURE_LOG_SIZE; i++, ii = (ii + 1)%GESTURE_LOG_SIZE) DrawText(gestureLog[ii], (int)gestureLogPosition.x, (int)gestureLogPosition.y + 410 - i*20, 20, (i == 0 ? gestureColor : LIGHTGRAY)); Color logButton1Color, logButton2Color; switch (logMode) { diff --git a/examples/core/core_input_multitouch.c b/examples/core/core_input_multitouch.c index 01dd90fae..47ad91d66 100644 --- a/examples/core/core_input_multitouch.c +++ b/examples/core/core_input_multitouch.c @@ -46,7 +46,7 @@ int main(void) // Clamp touch points available ( set the maximum touch points allowed ) if (tCount > MAX_TOUCH_POINTS) tCount = MAX_TOUCH_POINTS; // Get touch points positions - for (int i = 0; i < tCount; ++i) touchPositions[i] = GetTouchPosition(i); + for (int i = 0; i < tCount; i++) touchPositions[i] = GetTouchPosition(i); //---------------------------------------------------------------------------------- // Draw @@ -55,7 +55,7 @@ int main(void) ClearBackground(RAYWHITE); - for (int i = 0; i < tCount; ++i) + for (int i = 0; i < tCount; i++) { // Make sure point is not (0, 0) as this means there is no touch for it if ((touchPositions[i].x > 0) && (touchPositions[i].y > 0)) diff --git a/examples/core/core_monitor_detector.c b/examples/core/core_monitor_detector.c index 720449d65..ab65d8042 100644 --- a/examples/core/core_monitor_detector.c +++ b/examples/core/core_monitor_detector.c @@ -142,7 +142,7 @@ int main(void) Vector2 windowPosition = (Vector2){ (GetWindowPosition().x + monitorOffsetX)*monitorScale + 140, GetWindowPosition().y*monitorScale + 80 }; // Draw window position based on monitors - DrawRectangleV(windowPosition, (Vector2){screenWidth * monitorScale, screenHeight * monitorScale}, Fade(GREEN, 0.5)); + DrawRectangleV(windowPosition, (Vector2){screenWidth*monitorScale, screenHeight*monitorScale}, Fade(GREEN, 0.5)); } else DrawRectangleLinesEx(rec, 5, GRAY); } diff --git a/examples/core/core_undo_redo.c b/examples/core/core_undo_redo.c index c49ad9e6f..78971e689 100644 --- a/examples/core/core_undo_redo.c +++ b/examples/core/core_undo_redo.c @@ -187,7 +187,7 @@ int main(void) if (lastUndoIndex > firstUndoIndex) { for (int i = firstUndoIndex; i < currentUndoIndex; i++) - DrawRectangleRec((Rectangle){gridPosition.x + states[i].cell.x * GRID_CELL_SIZE, gridPosition.y + states[i].cell.y * GRID_CELL_SIZE, + DrawRectangleRec((Rectangle){gridPosition.x + states[i].cell.x*GRID_CELL_SIZE, gridPosition.y + states[i].cell.y*GRID_CELL_SIZE, GRID_CELL_SIZE, GRID_CELL_SIZE }, LIGHTGRAY); } else if (firstUndoIndex > lastUndoIndex) @@ -195,7 +195,7 @@ int main(void) if ((currentUndoIndex < MAX_UNDO_STATES) && (currentUndoIndex > lastUndoIndex)) { for (int i = firstUndoIndex; i < currentUndoIndex; i++) - DrawRectangleRec((Rectangle) { gridPosition.x + states[i].cell.x * GRID_CELL_SIZE, gridPosition.y + states[i].cell.y * GRID_CELL_SIZE, + DrawRectangleRec((Rectangle) { gridPosition.x + states[i].cell.x*GRID_CELL_SIZE, gridPosition.y + states[i].cell.y*GRID_CELL_SIZE, GRID_CELL_SIZE, GRID_CELL_SIZE }, LIGHTGRAY); } else diff --git a/examples/core/core_viewport_scaling.c b/examples/core/core_viewport_scaling.c index 28ee422cd..adcd51ea3 100644 --- a/examples/core/core_viewport_scaling.c +++ b/examples/core/core_viewport_scaling.c @@ -17,11 +17,9 @@ #include "raylib.h" -// For itteration purposes and teaching example -#define RESOLUTION_COUNT 4 +#define RESOLUTION_COUNT 4 // For iteration purposes and teaching example -enum ViewportType -{ +typedef enum { // Only upscale, useful for pixel art KEEP_ASPECT_INTEGER, KEEP_HEIGHT_INTEGER, @@ -32,24 +30,28 @@ enum ViewportType KEEP_WIDTH, // For itteration purposes and as a teaching example VIEWPORT_TYPE_COUNT, +} ViewportType; + +// For displaying on GUI +const char *ViewportTypeNames[VIEWPORT_TYPE_COUNT] = { + "KEEP_ASPECT_INTEGER", + "KEEP_HEIGHT_INTEGER", + "KEEP_WIDTH_INTEGER", + "KEEP_ASPECT", + "KEEP_HEIGHT", + "KEEP_WIDTH", }; //-------------------------------------------------------------------------------------- // Module Functions Declaration //-------------------------------------------------------------------------------------- static void KeepAspectCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect); - static void KeepHeightCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect); - static void KeepWidthCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect); - static void KeepAspectCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect); - static void KeepHeightCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect); - static void KeepWidthCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect); - -static void ResizeRenderSize(enum ViewportType viewportType, int *screenWidth, int *screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect, RenderTexture2D *target); +static void ResizeRenderSize(ViewportType viewportType, int *screenWidth, int *screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect, RenderTexture2D *target); // Example how to calculate position on RenderTexture static Vector2 Screen2RenderTexturePosition(Vector2 point, Rectangle *textureRect, Rectangle *scaledRect); @@ -61,91 +63,89 @@ int main(void) { // Initialization //--------------------------------------------------------- - // Preset resolutions that could be created by subdividing screen resolution - Vector2 resolutionList[RESOLUTION_COUNT] = { - (Vector2){64, 64}, - (Vector2){256, 240}, - (Vector2){320, 180}, - // 4K doesn't work with integer scaling but included for example purposes with non-integer scaling - (Vector2){3840, 2160}, - }; - int resolutionIndex = 0; - int screenWidth = 800; int screenHeight = 450; - int gameWidth = 64; - int gameHeight = 64; - - RenderTexture2D target = (RenderTexture2D){0}; - Rectangle sourceRect = (Rectangle){0}; - Rectangle destRect = (Rectangle){0}; - - // For displaying on GUI - const char *ViewportTypeNames[VIEWPORT_TYPE_COUNT] = { - "KEEP_ASPECT_INTEGER", - "KEEP_HEIGHT_INTEGER", - "KEEP_WIDTH_INTEGER", - "KEEP_ASPECT", - "KEEP_HEIGHT", - "KEEP_WIDTH", - }; - enum ViewportType viewportType = KEEP_ASPECT_INTEGER; SetConfigFlags(FLAG_WINDOW_RESIZABLE); InitWindow(screenWidth, screenHeight, "raylib [core] example - viewport scaling"); + + // Preset resolutions that could be created by subdividing screen resolution + Vector2 resolutionList[RESOLUTION_COUNT] = { + (Vector2){ 64, 64 }, + (Vector2){ 256, 240 }, + (Vector2){ 320, 180 }, + // 4K doesn't work with integer scaling but included for example purposes with non-integer scaling + (Vector2){ 3840, 2160 }, + }; + + int resolutionIndex = 0; + int gameWidth = 64; + int gameHeight = 64; + + RenderTexture2D target = (RenderTexture2D){ 0 }; + Rectangle sourceRect = (Rectangle){ 0 }; + Rectangle destRect = (Rectangle){ 0 }; + + ViewportType viewportType = KEEP_ASPECT_INTEGER; ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target); + // Button rectangles + Rectangle decreaseResolutionButton = (Rectangle){ 200, 30, 10, 10 }; + Rectangle increaseResolutionButton = (Rectangle){ 215, 30, 10, 10 }; + Rectangle decreaseTypeButton = (Rectangle){ 200, 45, 10, 10 }; + Rectangle increaseTypeButton = (Rectangle){ 215, 45, 10, 10 }; + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //---------------------------------------------------------- - // Button rectangles - Rectangle decreaseResolutionButton = (Rectangle){200, 30, 10, 10}; - Rectangle increaseResolutionButton = (Rectangle){215, 30, 10, 10}; - Rectangle decreaseTypeButton = (Rectangle){200, 45, 10, 10}; - Rectangle increaseTypeButton = (Rectangle){215, 45, 10, 10}; // Main game loop while (!WindowShouldClose()) // Detect window close button or ESC key { // Update - //----------------------------------------------------- - if (IsWindowResized()){ - ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target); - } + //---------------------------------------------------------------------------------- + if (IsWindowResized()) ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target); + Vector2 mousePosition = GetMousePosition(); bool mousePressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); // Check buttons and rescale - if (CheckCollisionPointRec(mousePosition, decreaseResolutionButton) && mousePressed){ - resolutionIndex = (resolutionIndex + RESOLUTION_COUNT - 1) % RESOLUTION_COUNT; + if (CheckCollisionPointRec(mousePosition, decreaseResolutionButton) && mousePressed) + { + resolutionIndex = (resolutionIndex + RESOLUTION_COUNT - 1)%RESOLUTION_COUNT; gameWidth = resolutionList[resolutionIndex].x; gameHeight = resolutionList[resolutionIndex].y; ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target); } - if (CheckCollisionPointRec(mousePosition, increaseResolutionButton) && mousePressed){ - resolutionIndex = (resolutionIndex + 1) % RESOLUTION_COUNT; + + if (CheckCollisionPointRec(mousePosition, increaseResolutionButton) && mousePressed) + { + resolutionIndex = (resolutionIndex + 1)%RESOLUTION_COUNT; gameWidth = resolutionList[resolutionIndex].x; gameHeight = resolutionList[resolutionIndex].y; ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target); } - if (CheckCollisionPointRec(mousePosition, decreaseTypeButton) && mousePressed){ - viewportType = (viewportType + VIEWPORT_TYPE_COUNT - 1) % VIEWPORT_TYPE_COUNT; + + if (CheckCollisionPointRec(mousePosition, decreaseTypeButton) && mousePressed) + { + viewportType = (viewportType + VIEWPORT_TYPE_COUNT - 1)%VIEWPORT_TYPE_COUNT; ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target); } - if (CheckCollisionPointRec(mousePosition, increaseTypeButton) && mousePressed){ - viewportType = (viewportType + 1) % VIEWPORT_TYPE_COUNT; + + if (CheckCollisionPointRec(mousePosition, increaseTypeButton) && mousePressed) + { + viewportType = (viewportType + 1)%VIEWPORT_TYPE_COUNT; ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target); } Vector2 textureMousePosition = Screen2RenderTexturePosition(mousePosition, &sourceRect, &destRect); + //---------------------------------------------------------------------------------- // Draw - //----------------------------------------------------- + //---------------------------------------------------------------------------------- // Draw our scene to the render texture BeginTextureMode(target); ClearBackground(WHITE); - DrawCircle(textureMousePosition.x, textureMousePosition.y, 20.f, LIME); - - + DrawCircle(textureMousePosition.x, textureMousePosition.y, 20.0f, LIME); EndTextureMode(); // Draw render texture to main framebuffer @@ -153,9 +153,7 @@ int main(void) ClearBackground(BLACK); // Draw our render texture with rotation applied - const Vector2 ORIGIN_POSITION = (Vector2){ 0.0f, 0.0f }; - const float ROTATION = 0.f; - DrawTexturePro(target.texture, sourceRect, destRect, ORIGIN_POSITION, ROTATION, WHITE); + DrawTexturePro(target.texture, sourceRect, destRect, (Vector2){ 0.0f, 0.0f }, 0.0f, WHITE); // Draw Native resolution (GUI or anything) // Draw info box @@ -167,15 +165,10 @@ int main(void) DrawText(TextFormat("Game Resolution: %d x %d", gameWidth, gameHeight), 15, 30, 10, BLACK); DrawText(TextFormat("Type: %s", ViewportTypeNames[viewportType]), 15, 45, 10, BLACK); - Vector2 scaleRatio = (Vector2){destRect.width / sourceRect.width, destRect.height / -sourceRect.height}; - if (scaleRatio.x < 0.001f || scaleRatio.y < 0.001f) - { - DrawText(TextFormat("Scale ratio: INVALID"), 15, 60, 10, BLACK); - } - else - { - DrawText(TextFormat("Scale ratio: %.2f x %.2f", scaleRatio.x, scaleRatio.y), 15, 60, 10, BLACK); - } + Vector2 scaleRatio = (Vector2){destRect.width/sourceRect.width, -destRect.height/sourceRect.height}; + if (scaleRatio.x < 0.001f || scaleRatio.y < 0.001f) DrawText(TextFormat("Scale ratio: INVALID"), 15, 60, 10, BLACK); + else DrawText(TextFormat("Scale ratio: %.2f x %.2f", scaleRatio.x, scaleRatio.y), 15, 60, 10, BLACK); + DrawText(TextFormat("Source size: %.2f x %.2f", sourceRect.width, -sourceRect.height), 15, 75, 10, BLACK); DrawText(TextFormat("Destination size: %.2f x %.2f", destRect.width, destRect.height), 15, 90, 10, BLACK); @@ -190,13 +183,13 @@ int main(void) DrawText(">", increaseResolutionButton.x + 3, increaseResolutionButton.y + 1, 10, BLACK); EndDrawing(); - //----------------------------------------------------- + //---------------------------------------------------------------------------------- } // De-Initialization - //--------------------------------------------------------- + //---------------------------------------------------------------------------------- CloseWindow(); // Close window and OpenGL context - //---------------------------------------------------------- + //---------------------------------------------------------------------------------- return 0; } @@ -206,54 +199,54 @@ int main(void) //-------------------------------------------------------------------------------------- static void KeepAspectCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect) { - sourceRect->x = 0.f; + sourceRect->x = 0.0f; sourceRect->y = (float)gameHeight; sourceRect->width = (float)gameWidth; sourceRect->height = (float)-gameHeight; const int ratio_x = (screenWidth/gameWidth); const int ratio_y = (screenHeight/gameHeight); - const float resizeRatio = (float)(ratio_x < ratio_y ? ratio_x : ratio_y); + const float resizeRatio = (float)((ratio_x < ratio_y)? ratio_x : ratio_y); - destRect->x = (float)(int)((screenWidth - (gameWidth * resizeRatio)) * 0.5); - destRect->y = (float)(int)((screenHeight - (gameHeight * resizeRatio)) * 0.5); - destRect->width = (float)(int)(gameWidth * resizeRatio); - destRect->height = (float)(int)(gameHeight * resizeRatio); + destRect->x = (float)(int)((screenWidth - (gameWidth*resizeRatio))*0.5f); + destRect->y = (float)(int)((screenHeight - (gameHeight*resizeRatio))*0.5f); + destRect->width = (float)(int)(gameWidth*resizeRatio); + destRect->height = (float)(int)(gameHeight*resizeRatio); } static void KeepHeightCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect) { const float resizeRatio = (float)(screenHeight/gameHeight); - sourceRect->x = 0.f; - sourceRect->y = 0.f; - sourceRect->width = (float)(int)(screenWidth / resizeRatio); + sourceRect->x = 0.0f; + sourceRect->y = 0.0f; + sourceRect->width = (float)(int)(screenWidth/resizeRatio); sourceRect->height = (float)-gameHeight; - destRect->x = (float)(int)((screenWidth - (sourceRect->width * resizeRatio)) * 0.5); - destRect->y = (float)(int)((screenHeight - (gameHeight * resizeRatio)) * 0.5); - destRect->width = (float)(int)(sourceRect->width * resizeRatio); - destRect->height = (float)(int)(gameHeight * resizeRatio); + destRect->x = (float)(int)((screenWidth - (sourceRect->width*resizeRatio))*0.5f); + destRect->y = (float)(int)((screenHeight - (gameHeight*resizeRatio))*0.5f); + destRect->width = (float)(int)(sourceRect->width*resizeRatio); + destRect->height = (float)(int)(gameHeight*resizeRatio); } static void KeepWidthCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect) { const float resizeRatio = (float)(screenWidth/gameWidth); - sourceRect->x = 0.f; - sourceRect->y = 0.f; + sourceRect->x = 0.0f; + sourceRect->y = 0.0f; sourceRect->width = (float)gameWidth; - sourceRect->height = (float)(int)(screenHeight / resizeRatio); + sourceRect->height = (float)(int)(screenHeight/resizeRatio); - destRect->x = (float)(int)((screenWidth - (gameWidth * resizeRatio)) * 0.5); - destRect->y = (float)(int)((screenHeight - (sourceRect->height * resizeRatio)) * 0.5); - destRect->width = (float)(int)(gameWidth * resizeRatio); - destRect->height = (float)(int)(sourceRect->height * resizeRatio); + destRect->x = (float)(int)((screenWidth - (gameWidth*resizeRatio))*0.5f); + destRect->y = (float)(int)((screenHeight - (sourceRect->height*resizeRatio))*0.5f); + destRect->width = (float)(int)(gameWidth*resizeRatio); + destRect->height = (float)(int)(sourceRect->height*resizeRatio); - sourceRect->height *= -1.f; + sourceRect->height *= -1.0f; } static void KeepAspectCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect) { - sourceRect->x = 0.f; + sourceRect->x = 0.0f; sourceRect->y = (float)gameHeight; sourceRect->width = (float)gameWidth; sourceRect->height = (float)-gameHeight; @@ -262,81 +255,58 @@ static void KeepAspectCentered(int screenWidth, int screenHeight, int gameWidth, const float ratio_y = ((float)screenHeight/(float)gameHeight); const float resizeRatio = (ratio_x < ratio_y ? ratio_x : ratio_y); - destRect->x = (float)(int)((screenWidth - (gameWidth * resizeRatio)) * 0.5); - destRect->y = (float)(int)((screenHeight - (gameHeight * resizeRatio)) * 0.5); - destRect->width = (float)(int)(gameWidth * resizeRatio); - destRect->height = (float)(int)(gameHeight * resizeRatio); + destRect->x = (float)(int)((screenWidth - (gameWidth*resizeRatio))*0.5f); + destRect->y = (float)(int)((screenHeight - (gameHeight*resizeRatio))*0.5f); + destRect->width = (float)(int)(gameWidth*resizeRatio); + destRect->height = (float)(int)(gameHeight*resizeRatio); } static void KeepHeightCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect) { const float resizeRatio = ((float)screenHeight/(float)gameHeight); - sourceRect->x = 0.f; - sourceRect->y = 0.f; - sourceRect->width = (float)(int)((float)screenWidth / resizeRatio); + sourceRect->x = 0.0f; + sourceRect->y = 0.0f; + sourceRect->width = (float)(int)((float)screenWidth/resizeRatio); sourceRect->height = (float)-gameHeight; - destRect->x = (float)(int)((screenWidth - (sourceRect->width * resizeRatio)) * 0.5); - destRect->y = (float)(int)((screenHeight - (gameHeight * resizeRatio)) * 0.5); - destRect->width = (float)(int)(sourceRect->width * resizeRatio); - destRect->height = (float)(int)(gameHeight * resizeRatio); + destRect->x = (float)(int)((screenWidth - (sourceRect->width*resizeRatio))*0.5f); + destRect->y = (float)(int)((screenHeight - (gameHeight*resizeRatio))*0.5f); + destRect->width = (float)(int)(sourceRect->width*resizeRatio); + destRect->height = (float)(int)(gameHeight*resizeRatio); } static void KeepWidthCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect) { const float resizeRatio = ((float)screenWidth/(float)gameWidth); - sourceRect->x = 0.f; - sourceRect->y = 0.f; + sourceRect->x = 0.0f; + sourceRect->y = 0.0f; sourceRect->width = (float)gameWidth; - sourceRect->height = (float)(int)((float)screenHeight / resizeRatio); + sourceRect->height = (float)(int)((float)screenHeight/resizeRatio); - destRect->x = (float)(int)((screenWidth - (gameWidth * resizeRatio)) * 0.5); - destRect->y = (float)(int)((screenHeight - (sourceRect->height * resizeRatio)) * 0.5); - destRect->width = (float)(int)(gameWidth * resizeRatio); - destRect->height = (float)(int)(sourceRect->height * resizeRatio); + destRect->x = (float)(int)((screenWidth - (gameWidth*resizeRatio))*0.5f); + destRect->y = (float)(int)((screenHeight - (sourceRect->height*resizeRatio))*0.5f); + destRect->width = (float)(int)(gameWidth*resizeRatio); + destRect->height = (float)(int)(sourceRect->height*resizeRatio); sourceRect->height *= -1.f; } -static void ResizeRenderSize(enum ViewportType viewportType, int *screenWidth, int *screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect, RenderTexture2D *target) +static void ResizeRenderSize(ViewportType viewportType, int *screenWidth, int *screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect, RenderTexture2D *target) { *screenWidth = GetScreenWidth(); *screenHeight = GetScreenHeight(); switch(viewportType) { - case KEEP_ASPECT_INTEGER: - { - KeepAspectCenteredInteger(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); - break; - } - case KEEP_HEIGHT_INTEGER: - { - KeepHeightCenteredInteger(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); - break; - } - case KEEP_WIDTH_INTEGER: - { - KeepWidthCenteredInteger(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); - break; - } - case KEEP_ASPECT: - { - KeepAspectCentered(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); - break; - } - case KEEP_HEIGHT: - { - KeepHeightCentered(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); - break; - } - case KEEP_WIDTH: - { - KeepWidthCentered(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); - break; - } - default: {} + case KEEP_ASPECT_INTEGER: KeepAspectCenteredInteger(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); break; + case KEEP_HEIGHT_INTEGER: KeepHeightCenteredInteger(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); break; + case KEEP_WIDTH_INTEGER: KeepWidthCenteredInteger(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); break; + case KEEP_ASPECT: KeepAspectCentered(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); break; + case KEEP_HEIGHT: KeepHeightCentered(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); break; + case KEEP_WIDTH: KeepWidthCentered(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); break; + default: break; } + UnloadRenderTexture(*target); *target = LoadRenderTexture(sourceRect->width, -sourceRect->height); } @@ -345,7 +315,7 @@ static void ResizeRenderSize(enum ViewportType viewportType, int *screenWidth, i static Vector2 Screen2RenderTexturePosition(Vector2 point, Rectangle *textureRect, Rectangle *scaledRect) { Vector2 relativePosition = {point.x - scaledRect->x, point.y - scaledRect->y}; - Vector2 ratio = {textureRect->width / scaledRect->width, -textureRect->height / scaledRect->height}; + Vector2 ratio = {textureRect->width/scaledRect->width, -textureRect->height/scaledRect->height}; - return (Vector2){relativePosition.x * ratio.x, relativePosition.y * ratio.x}; + return (Vector2){relativePosition.x*ratio.x, relativePosition.y*ratio.x}; } \ No newline at end of file diff --git a/examples/core/core_window_flags.c b/examples/core/core_window_flags.c index 048d2d245..a8096eeb4 100644 --- a/examples/core/core_window_flags.c +++ b/examples/core/core_window_flags.c @@ -97,7 +97,8 @@ int main(void) if (IsWindowState(FLAG_WINDOW_MINIMIZED)) { framesCounter++; - if (framesCounter >= 240) { + if (framesCounter >= 240) + { RestoreWindow(); // Restore window after 3 seconds framesCounter = 0; } diff --git a/examples/models/models_loading_vox.c b/examples/models/models_loading_vox.c index 47be07ee4..06dc651d7 100644 --- a/examples/models/models_loading_vox.c +++ b/examples/models/models_loading_vox.c @@ -138,7 +138,7 @@ int main(void) GetMouseWheelMove()*-2.0f); // Move to target (zoom) // Cycle between models on mouse click - if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) currentModel = (currentModel + 1) % MAX_VOX_FILES; + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) currentModel = (currentModel + 1)%MAX_VOX_FILES; // Update the shader with the camera view vector (points towards { 0.0f, 0.0f, 0.0f }) float cameraPos[3] = { camera.position.x, camera.position.y, camera.position.z }; diff --git a/examples/shaders/shaders_basic_pbr.c b/examples/shaders/shaders_basic_pbr.c index 7ee05502d..dd5f871da 100644 --- a/examples/shaders/shaders_basic_pbr.c +++ b/examples/shaders/shaders_basic_pbr.c @@ -254,9 +254,9 @@ int main(void) { Color lightColor = (Color){ (unsigned char)(lights[i].color[0]*255), - (unsigned char)(lights[i].color[1] * 255), - (unsigned char)(lights[i].color[2] * 255), - (unsigned char)(lights[i].color[3] * 255) }; + (unsigned char)(lights[i].color[1]*255), + (unsigned char)(lights[i].color[2]*255), + (unsigned char)(lights[i].color[3]*255) }; if (lights[i].enabled) DrawSphereEx(lights[i].position, 0.2f, 8, 8, lightColor); else DrawSphereWires(lights[i].position, 0.2f, 8, 8, ColorAlpha(lightColor, 0.3f)); diff --git a/examples/shaders/shaders_color_correction.c b/examples/shaders/shaders_color_correction.c index 3abf29840..826baecc7 100644 --- a/examples/shaders/shaders_color_correction.c +++ b/examples/shaders/shaders_color_correction.c @@ -138,7 +138,7 @@ int main(void) // De-Initialization //-------------------------------------------------------------------------------------- - for (int i = 0; i < MAX_TEXTURES; ++i) UnloadTexture(texture[i]); + for (int i = 0; i < MAX_TEXTURES; i++) UnloadTexture(texture[i]); UnloadShader(shdrColorCorrection); CloseWindow(); // Close window and OpenGL context diff --git a/examples/shaders/shaders_hybrid_rendering.c b/examples/shaders/shaders_hybrid_rendering.c index e523a91c8..439965fd6 100644 --- a/examples/shaders/shaders_hybrid_rendering.c +++ b/examples/shaders/shaders_hybrid_rendering.c @@ -138,7 +138,7 @@ int main(void) ClearBackground(RAYWHITE); DrawTextureRec(target.texture, (Rectangle) { 0, 0, (float)screenWidth, (float)-screenHeight }, (Vector2) { 0, 0 }, WHITE); - + DrawFPS(10, 10); EndDrawing(); //---------------------------------------------------------------------------------- diff --git a/examples/shapes/shapes_bullet_hell.c b/examples/shapes/shapes_bullet_hell.c index 95abc4dc9..2ff3be14b 100644 --- a/examples/shapes/shapes_bullet_hell.c +++ b/examples/shapes/shapes_bullet_hell.c @@ -107,7 +107,7 @@ int main(void) float bulletDirection = baseDirection + (degreesPerRow*row); - // Bullet speed * bullet direction, this will determine how much pixels will be incremented/decremented + // Bullet speed*bullet direction, this will determine how much pixels will be incremented/decremented // from the bullet position every frame. Since the bullets doesn't change its direction and speed, // only need to calculate it at the spawning time // 0 degrees = right, 90 degrees = down, 180 degrees = left and 270 degrees = up, basically clockwise diff --git a/examples/shapes/shapes_clock_of_clocks.c b/examples/shapes/shapes_clock_of_clocks.c index 51703a32d..d87ec6411 100644 --- a/examples/shapes/shapes_clock_of_clocks.c +++ b/examples/shapes/shapes_clock_of_clocks.c @@ -14,9 +14,6 @@ * Copyright (c) 2025 JP Mortiboys (@themushroompirates) * ********************************************************************************************/ -#if defined(WIN32) -#define _CRT_SECURE_NO_WARNINGS -#endif #include "raylib.h" @@ -63,24 +60,16 @@ int main(void) /* 8 */ { TL,HH,HH,TR, /* */ VV,TL,TR,VV,/* */ VV,BL,BR,VV,/* */ VV,TL,TR,VV,/* */ VV,BL,BR,VV,/* */ BL,HH,HH,BR }, /* 9 */ { TL,HH,HH,TR, /* */ VV,TL,TR,VV,/* */ VV,BL,BR,VV,/* */ BL,HH,TR,VV,/* */ TL,HH,BR,VV,/* */ BL,HH,HH,BR }, }; + // Time for the hands to move to the new position (in seconds); this must be <1s - const float handsMoveDuration = .5f; + const float handsMoveDuration = 0.5f; - // We store the previous seconds value so we can see if the time has changed int prevSeconds = -1; - - // This represents the real position where the hands are right now Vector2 currentAngles[6][24] = { 0 }; - - // This is the position where the hands were moving from Vector2 srcAngles[6][24] = { 0 }; - // This is the position where the hands are moving to Vector2 dstAngles[6][24] = { 0 }; - // Current animation timer float handsMoveTimer = 0.0f; - - // 12 or 24 hour mode int hourMode = 24; SetTargetFPS(60); // Set our game to run at 60 frames-per-second @@ -91,7 +80,6 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - // Get the current time time_t rawtime; struct tm *timeinfo; @@ -99,30 +87,26 @@ int main(void) time(&rawtime); timeinfo = localtime(&rawtime); - if (timeinfo->tm_sec != prevSeconds) { + if (timeinfo->tm_sec != prevSeconds) + { // The time has changed, so we need to move the hands to the new positions prevSeconds = timeinfo->tm_sec; // Format the current time so we can access the individual digits - const char *clockDigits = TextFormat("%02d%02d%02d", timeinfo->tm_hour % hourMode, timeinfo->tm_min, timeinfo->tm_sec); + const char *clockDigits = TextFormat("%02d%02d%02d", timeinfo->tm_hour%hourMode, timeinfo->tm_min, timeinfo->tm_sec); // Fetch where we want all the hands to be - for (int digit = 0; digit < 6; digit++) { - for (int cell = 0; cell < 24; cell++) { + for (int digit = 0; digit < 6; digit++) + { + for (int cell = 0; cell < 24; cell++) + { srcAngles[digit][cell] = currentAngles[digit][cell]; - dstAngles[digit][cell] = digitAngles[ clockDigits[digit] - '0' ][cell]; + dstAngles[digit][cell] = digitAngles[clockDigits[digit] - '0'][cell]; // Quick exception for 12h mode - if (digit == 0 && hourMode == 12 && clockDigits[0] == '0') { - dstAngles[digit][cell] = ZZ; - } - - if (srcAngles[digit][cell].x > dstAngles[digit][cell].x) { - srcAngles[digit][cell].x -= 360.0f; - } - if (srcAngles[digit][cell].y > dstAngles[digit][cell].y) { - srcAngles[digit][cell].y -= 360.0f; - } + if ((digit == 0) && (hourMode == 12) && (clockDigits[0] == '0')) dstAngles[digit][cell] = ZZ; + if (srcAngles[digit][cell].x > dstAngles[digit][cell].x) srcAngles[digit][cell].x -= 360.0f; + if (srcAngles[digit][cell].y > dstAngles[digit][cell].y) srcAngles[digit][cell].y -= 360.0f; } } @@ -131,37 +115,29 @@ int main(void) } // Now let's animate all the hands if we need to - if (handsMoveTimer < handsMoveDuration) { + if (handsMoveTimer < handsMoveDuration) + { // Increase the timer but don't go above the maximum handsMoveTimer = Clamp(handsMoveTimer + GetFrameTime(), 0, handsMoveDuration); - // Calculate the % completion of the animation - float t = handsMoveTimer / handsMoveDuration; + // Calculate the%completion of the animation + float t = handsMoveTimer/handsMoveDuration; // A little cheeky smoothstep - t = t * t * (3.0f - 2.0f * t); + t = t*t*(3.0f - 2.0f*t); - for (int digit = 0; digit < 6; digit++) { - for (int cell = 0; cell < 24; cell++) { + for (int digit = 0; digit < 6; digit++) + { + for (int cell = 0; cell < 24; cell++) + { currentAngles[digit][cell].x = Lerp(srcAngles[digit][cell].x, dstAngles[digit][cell].x, t); currentAngles[digit][cell].y = Lerp(srcAngles[digit][cell].y, dstAngles[digit][cell].y, t); } } - - if (handsMoveTimer == handsMoveDuration) { - // The animation has now finished - } } // Handle input - - // Toggle between 12 and 24 hour mode with space - if (IsKeyPressed(KEY_SPACE)) { - hourMode = 36 - hourMode; - } - - - + if (IsKeyPressed(KEY_SPACE)) hourMode = 36 - hourMode; // Toggle between 12 and 24 hour mode with space //---------------------------------------------------------------------------------- // Draw @@ -174,19 +150,22 @@ int main(void) float xOffset = 4.0f; - for (int digit = 0; digit < 6; digit++) { - - for (int row = 0; row < 6; row++) { - for (int col = 0; col < 4; col++) { + for (int digit = 0; digit < 6; digit++) + { + for (int row = 0; row < 6; row++) + { + for (int col = 0; col < 4; col++) + { Vector2 centre = (Vector2){ - xOffset + col*(clockFaceSize+clockFaceSpacing) + clockFaceSize * .5f, - 100 + row*(clockFaceSize+clockFaceSpacing) + clockFaceSize * .5f + xOffset + col*(clockFaceSize+clockFaceSpacing) + clockFaceSize*0.5f, + 100 + row*(clockFaceSize+clockFaceSpacing) + clockFaceSize*0.5f }; - DrawRing(centre, clockFaceSize * 0.5f - 2.0f, clockFaceSize * 0.5f, 0, 360, 24, DARKGRAY); + + DrawRing(centre, clockFaceSize*0.5f - 2.0f, clockFaceSize*0.5f, 0, 360, 24, DARKGRAY); // Big hand DrawRectanglePro( - (Rectangle){centre.x, centre.y, clockFaceSize*.5f+4.0f, 4.0f}, + (Rectangle){centre.x, centre.y, clockFaceSize*0.5f+4.0f, 4.0f}, (Vector2){ 2.0f, 2.0f }, currentAngles[digit][row*4+col].x, handsColor @@ -194,7 +173,7 @@ int main(void) // Little hand DrawRectanglePro( - (Rectangle){centre.x, centre.y, clockFaceSize*.5f+2.0f, 4.0f}, + (Rectangle){centre.x, centre.y, clockFaceSize*0.5f+2.0f, 4.0f}, (Vector2){ 2.0f, 2.0f }, currentAngles[digit][row*4+col].y, handsColor @@ -202,27 +181,23 @@ int main(void) } } - xOffset += (clockFaceSize+clockFaceSpacing) * 4; - if (digit % 2 == 1) { - + xOffset += (clockFaceSize+clockFaceSpacing)*4; + if (digit%2 == 1) + { DrawRing((Vector2){xOffset + 4.0f, 160.0f}, 6.0f, 8.0f, 0.0f, 360.0f, 24, handsColor); DrawRing((Vector2){xOffset + 4.0f, 225.0f}, 6.0f, 8.0f, 0.0f, 360.0f, 24, handsColor); - xOffset += sectionSpacing; - } } DrawFPS(10, 10); - EndDrawing(); //---------------------------------------------------------------------------------- } // De-Initialization //-------------------------------------------------------------------------------------- - CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- diff --git a/examples/shapes/shapes_double_pendulum.c b/examples/shapes/shapes_double_pendulum.c index 4bcac02f9..cbf487f93 100644 --- a/examples/shapes/shapes_double_pendulum.c +++ b/examples/shapes/shapes_double_pendulum.c @@ -76,7 +76,7 @@ int main(void) float step = dt/SIMULATION_STEPS, step2 = step*step; // Update Physics - larger steps = better approximation - for (int i = 0; i < SIMULATION_STEPS; ++i) + for (int i = 0; i < SIMULATION_STEPS; i++) { float delta = theta1 - theta2; float sinD = sinf(delta), cosD = cosf(delta), cos2D = cosf(2*delta); diff --git a/examples/shapes/shapes_math_angle_rotation.c b/examples/shapes/shapes_math_angle_rotation.c index f895026f5..63c9aaa91 100644 --- a/examples/shapes/shapes_math_angle_rotation.c +++ b/examples/shapes/shapes_math_angle_rotation.c @@ -31,7 +31,7 @@ int main(void) InitWindow(screenWidth, screenHeight, "raylib [shapes] example - math angle rotation"); SetTargetFPS(60); - Vector2 center = { screenWidth / 2.0f, screenHeight / 2.0f }; + Vector2 center = { screenWidth/2.0f, screenHeight/2.0f }; const float lineLength = 150.0f; // Predefined angles for fixed lines @@ -60,9 +60,9 @@ int main(void) // Draw fixed-angle lines with colorful gradient for (int i = 0; i < numAngles; i++) { - float rad = angles[i] * DEG2RAD; - Vector2 end = { center.x + cosf(rad) * lineLength, - center.y + sinf(rad) * lineLength }; + float rad = angles[i]*DEG2RAD; + Vector2 end = { center.x + cosf(rad)*lineLength, + center.y + sinf(rad)*lineLength }; // Gradient color from green → cyan → blue → magenta Color col; @@ -78,15 +78,15 @@ int main(void) DrawLineEx(center, end, 5.0f, col); // Draw angle label slightly offset along the line - Vector2 textPos = { center.x + cosf(rad) * (lineLength + 20), - center.y + sinf(rad) * (lineLength + 20) }; + Vector2 textPos = { center.x + cosf(rad)*(lineLength + 20), + center.y + sinf(rad)*(lineLength + 20) }; DrawText(TextFormat("%d°", angles[i]), (int)textPos.x, (int)textPos.y, 20, col); } // Draw animated rotating line with changing color - float animRad = totalAngle * DEG2RAD; - Vector2 animEnd = { center.x + cosf(animRad) * lineLength, - center.y + sinf(animRad) * lineLength }; + float animRad = totalAngle*DEG2RAD; + Vector2 animEnd = { center.x + cosf(animRad)*lineLength, + center.y + sinf(animRad)*lineLength }; // Cycle through HSV colors for animated line Color animCol = ColorFromHSV(fmodf(totalAngle, 360.0f), 0.8f, 0.9f); diff --git a/examples/shapes/shapes_math_sine_cosine.c b/examples/shapes/shapes_math_sine_cosine.c index 4e5f3fe47..c3646cabd 100644 --- a/examples/shapes/shapes_math_sine_cosine.c +++ b/examples/shapes/shapes_math_sine_cosine.c @@ -40,8 +40,8 @@ int main(void) Vector2 sinePoints[WAVE_POINTS]; Vector2 cosPoints[WAVE_POINTS]; - Vector2 center = { (screenWidth/2.0f) - 30.f, screenHeight/2.0f }; - Rectangle start = { 20.f, screenHeight - 120.f , 200.0f, 100.0f}; + Vector2 center = { (screenWidth/2.0f) - 30.0f, screenHeight/2.0f }; + Rectangle start = { 20.0f, screenHeight - 120.f , 200.0f, 100.0f}; float radius = 130.0f; float angle = 0.0f; bool pause = false; @@ -98,7 +98,7 @@ int main(void) // Base circle and axes DrawCircleLinesV(center, radius, GRAY); DrawLineEx((Vector2){ center.x, limitMin.y }, (Vector2){ center.x, limitMax.y }, 1.0f, GRAY); - DrawLineEx((Vector2){ limitMin.x, center.y }, (Vector2){ limitMax.x, center.y }, 1.f, GRAY); + DrawLineEx((Vector2){ limitMin.x, center.y }, (Vector2){ limitMax.x, center.y }, 1.0f, GRAY); // Wave graph axes DrawLineEx((Vector2){ start.x , start.y }, (Vector2){ start.x , start.y + start.height }, 2.0f, GRAY); @@ -135,19 +135,19 @@ int main(void) DrawText(TextFormat("Cotangent %.2f", cotangent), 640, 250, 6, ORANGE); // Complementary angle (beige) - DrawCircleSectorLines(center, radius*0.6f , -angle, -90.f , 36.0f, BEIGE); + DrawCircleSectorLines(center, radius*0.6f , -angle, -90.0f , 36.0f, BEIGE); DrawText(TextFormat("Complementary %0.f°",complementary), 640, 150, 6, BEIGE); // Supplementary angle (darkblue) - DrawCircleSectorLines(center, radius*0.5f , -angle, -180.f , 36.0f, DARKBLUE); + DrawCircleSectorLines(center, radius*0.5f , -angle, -180.0f , 36.0f, DARKBLUE); DrawText(TextFormat("Supplementary %0.f°",supplementary), 640, 130, 6, DARKBLUE); // Explementary angle (pink) - DrawCircleSectorLines(center, radius*0.4f , -angle, -360.f , 36.0f, PINK); + DrawCircleSectorLines(center, radius*0.4f , -angle, -360.0f , 36.0f, PINK); DrawText(TextFormat("Explementary %0.f°",explementary), 640, 170, 6, PINK); // Current angle - arc (lime), radius (black), endpoint (black) - DrawCircleSectorLines(center, radius*0.7f , -angle, 0.f, 36.0f, LIME); + DrawCircleSectorLines(center, radius*0.7f , -angle, 0.0f, 36.0f, LIME); DrawLineEx((Vector2){ center.x , center.y }, point, 2.0f, BLACK); DrawCircleV(point, 4.0f, BLACK); @@ -156,11 +156,12 @@ int main(void) GuiSetStyle(LABEL, TEXT_COLOR_NORMAL, ColorToInt(GRAY)); GuiToggle((Rectangle){ 640, 70, 120, 20}, TextFormat("Pause"), &pause); GuiSetStyle(LABEL, TEXT_COLOR_NORMAL, ColorToInt(LIME)); - GuiSliderBar((Rectangle){ 640, 40, 120, 20}, "Angle", TextFormat("%.0f°", angle), &angle, 0.0f, 360.f); + GuiSliderBar((Rectangle){ 640, 40, 120, 20}, "Angle", TextFormat("%.0f°", angle), &angle, 0.0f, 360.0f); // Angle values panel GuiGroupBox((Rectangle){ 620, 110, 140, 170}, "Angle Values"); //------------------------------------------------------------------------------ + DrawFPS(10, 10); EndDrawing(); diff --git a/examples/shapes/shapes_mouse_trail.c b/examples/shapes/shapes_mouse_trail.c index 819124220..3dd0fbd7f 100644 --- a/examples/shapes/shapes_mouse_trail.c +++ b/examples/shapes/shapes_mouse_trail.c @@ -71,7 +71,7 @@ int main(void) if ((trailPositions[i].x != 0.0f) || (trailPositions[i].y != 0.0f)) { // Calculate relative trail strength (ratio is near 1.0 for new, near 0.0 for old) - float ratio = (float)(MAX_TRAIL_LENGTH - i) / MAX_TRAIL_LENGTH; + float ratio = (float)(MAX_TRAIL_LENGTH - i)/MAX_TRAIL_LENGTH; // Fade effect: oldest positions are more transparent // Fade (color, alpha) - alpha is 0.5 to 1.0 based on ratio diff --git a/examples/shapes/shapes_rectangle_advanced.c b/examples/shapes/shapes_rectangle_advanced.c index 15487ee55..c274cb6f8 100644 --- a/examples/shapes/shapes_rectangle_advanced.c +++ b/examples/shapes/shapes_rectangle_advanced.c @@ -184,7 +184,7 @@ static void DrawRectangleRoundedGradientH(Rectangle rec, float roundnessLeft, fl } // End one even segments - if ( segments % 2) + if ( segments%2) { rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); rlVertex2f(center.x, center.y); diff --git a/examples/shapes/shapes_recursive_tree.c b/examples/shapes/shapes_recursive_tree.c index 4f1f4d5fd..f15758773 100644 --- a/examples/shapes/shapes_recursive_tree.c +++ b/examples/shapes/shapes_recursive_tree.c @@ -112,7 +112,7 @@ int main(void) GuiSliderBar((Rectangle){ 640, 40, 120, 20}, "Angle", TextFormat("%.0f", angle), &angle, 0, 180); GuiSliderBar((Rectangle){ 640, 70, 120, 20 }, "Length", TextFormat("%.0f", length), &length, 12.0f, 240.0f); GuiSliderBar((Rectangle){ 640, 100, 120, 20}, "Decay", TextFormat("%.2f", branchDecay), &branchDecay, 0.1f, 0.78f); - GuiSliderBar((Rectangle){ 640, 130, 120, 20 }, "Depth", TextFormat("%.0f", treeDepth), &treeDepth, 1.0f, 10.f); + GuiSliderBar((Rectangle){ 640, 130, 120, 20 }, "Depth", TextFormat("%.0f", treeDepth), &treeDepth, 1.0f, 10.0f); GuiSliderBar((Rectangle){ 640, 160, 120, 20}, "Thick", TextFormat("%.0f", thick), &thick, 1, 8); GuiCheckBox((Rectangle){ 640, 190, 20, 20 }, "Bezier", &bezier); //------------------------------------------------------------------------------ diff --git a/examples/shapes/shapes_rlgl_color_wheel.c b/examples/shapes/shapes_rlgl_color_wheel.c index 47ae5f7a4..f02226a83 100644 --- a/examples/shapes/shapes_rlgl_color_wheel.c +++ b/examples/shapes/shapes_rlgl_color_wheel.c @@ -122,11 +122,11 @@ int main(void) } float distance = Vector2Distance(center, circlePosition)/pointScale; - float angle = ((Vector2Angle((Vector2){ 0.0f, -pointScale }, Vector2Subtract(center, circlePosition))/PI + 1.0f) / 2.0f); + float angle = ((Vector2Angle((Vector2){ 0.0f, -pointScale }, Vector2Subtract(center, circlePosition))/PI + 1.0f)/2.0f); if (distance > 1.0f) { - circlePosition = Vector2Add((Vector2){ sinf(angle*(PI * 2.0f)) * pointScale, -cosf(angle*(PI*2.0f))*pointScale }, center); + circlePosition = Vector2Add((Vector2){ sinf(angle*(PI*2.0f))*pointScale, -cosf(angle*(PI*2.0f))*pointScale }, center); } } @@ -152,21 +152,15 @@ int main(void) // If the slider or the wheel was clicked, update the current color if (settingColor || sliderClicked) { - if (settingColor) { - circlePosition = GetMousePosition(); - } + if (settingColor) circlePosition = GetMousePosition(); - float distance = Vector2Distance(center, circlePosition) / pointScale; + float distance = Vector2Distance(center, circlePosition)/pointScale; float angle = ((Vector2Angle((Vector2){ 0.0f, -pointScale }, Vector2Subtract(center, circlePosition))/PI + 1.0f)/2.0f); - if (settingColor && distance > 1.0f) { - circlePosition = Vector2Add((Vector2){ sinf(angle*(PI*2.0f))*pointScale, -cosf(angle*(PI* 2.0f))*pointScale }, center); - } + if (settingColor && distance > 1.0f) circlePosition = Vector2Add((Vector2){ sinf(angle*(PI*2.0f))*pointScale, -cosf(angle*(PI* 2.0f))*pointScale }, center); float angle360 = angle*360.0f; - float valueActual = Clamp(distance, 0.0f, 1.0f); - color = ColorLerp((Color){ (int)(value*255.0f), (int)(value*255.0f), (int)(value*255.0f), 255 }, ColorFromHSV(angle360, Clamp(distance, 0.0f, 1.0f), 1.0f), valueActual); } //---------------------------------------------------------------------------------- diff --git a/examples/shapes/shapes_simple_particles.c b/examples/shapes/shapes_simple_particles.c index 7be0c82e4..f8151e7ad 100644 --- a/examples/shapes/shapes_simple_particles.c +++ b/examples/shapes/shapes_simple_particles.c @@ -95,7 +95,7 @@ int main(void) } else { - for (int i = 0; i <= emissionRate; ++i) EmitParticle(&circularBuffer, emitterPosition, currentType); + for (int i = 0; i <= emissionRate; i++) EmitParticle(&circularBuffer, emitterPosition, currentType); } // Update the parameters of each particle diff --git a/examples/shapes/shapes_triangle_strip.c b/examples/shapes/shapes_triangle_strip.c index 3b44da5e0..de712270c 100644 --- a/examples/shapes/shapes_triangle_strip.c +++ b/examples/shapes/shapes_triangle_strip.c @@ -34,7 +34,7 @@ int main(void) InitWindow(screenWidth, screenHeight, "raylib [shapes] example - triangle strip"); Vector2 points[122] = { 0 }; - Vector2 center = { (screenWidth/2.0f) - 125.f, screenHeight/2.0f }; + Vector2 center = { (screenWidth/2.0f) - 125.0f, screenHeight/2.0f }; float segments = 6.0f; float insideRadius = 100.0f; float outsideRadius = 150.0f; @@ -92,7 +92,7 @@ int main(void) // Draw GUI controls //------------------------------------------------------------------------------ - GuiSliderBar((Rectangle){ 640, 40, 120, 20}, "Segments", TextFormat("%.0f", segments), &segments, 6.0f, 60.f); + GuiSliderBar((Rectangle){ 640, 40, 120, 20}, "Segments", TextFormat("%.0f", segments), &segments, 6.0f, 60.0f); GuiCheckBox((Rectangle){ 640, 70, 20, 20 }, "Outline", &outline); //------------------------------------------------------------------------------ diff --git a/examples/text/text_3d_drawing.c b/examples/text/text_3d_drawing.c index 202494bf7..80b617b2e 100644 --- a/examples/text/text_3d_drawing.c +++ b/examples/text/text_3d_drawing.c @@ -231,7 +231,7 @@ int main(void) if (multicolor) { // Fill color array with random colors - for (int i = 0; i < TEXT_MAX_LAYERS; ++i) + for (int i = 0; i < TEXT_MAX_LAYERS; i++) { multi[i] = GenerateRandomColor(0.5f, 0.8f); multi[i].a = GetRandomValue(0, 255); @@ -296,7 +296,7 @@ int main(void) rlRotatef(90.0f, 1.0f, 0.0f, 0.0f); rlRotatef(90.0f, 0.0f, 0.0f, -1.0f); - for (int i = 0; i < layers; ++i) + for (int i = 0; i < layers; i++) { Color clr = light; if (multicolor) clr = multi[i]; diff --git a/examples/text/text_inline_styling.c b/examples/text/text_inline_styling.c index adedc4056..aeebe0abc 100644 --- a/examples/text/text_inline_styling.c +++ b/examples/text/text_inline_styling.c @@ -186,7 +186,7 @@ static void DrawTextStyled(Font font, const char *text, Vector2 position, float else increaseX += ((float)font.glyphs[index].advanceX*scaleFactor + spacing); // Draw background rectangle color (if required) - if (colBack.a > 0) DrawRectangleRec((Rectangle) { position.x + textOffsetX, position.y + textOffsetY - backRecPadding, increaseX, fontSize + 2 * backRecPadding }, colBack); + if (colBack.a > 0) DrawRectangleRec((Rectangle) { position.x + textOffsetX, position.y + textOffsetY - backRecPadding, increaseX, fontSize + 2*backRecPadding }, colBack); if ((codepoint != ' ') && (codepoint != '\t')) { diff --git a/examples/text/text_unicode_emojis.c b/examples/text/text_unicode_emojis.c index 30fcaa8b7..00712745d 100644 --- a/examples/text/text_unicode_emojis.c +++ b/examples/text/text_unicode_emojis.c @@ -210,7 +210,7 @@ int main(void) // Draw random emojis in the background //------------------------------------------------------------------------------ - for (int i = 0; i < SIZEOF(emoji); ++i) + for (int i = 0; i < SIZEOF(emoji); i++) { const char *txt = &emojiCodepoints[emoji[i].index]; Rectangle emojiRect = { position.x, position.y, (float)fontEmoji.baseSize, (float)fontEmoji.baseSize }; @@ -316,7 +316,7 @@ static void RandomizeEmoji(void) hovered = selected = -1; int start = GetRandomValue(45, 360); - for (int i = 0; i < SIZEOF(emoji); ++i) + for (int i = 0; i < SIZEOF(emoji); i++) { // 0-179 emoji codepoints (from emoji char array) each 4bytes + null char emoji[i].index = GetRandomValue(0, 179)*5; diff --git a/examples/text/text_words_alignment.c b/examples/text/text_words_alignment.c index 103e24e59..6cfd2a85c 100644 --- a/examples/text/text_words_alignment.c +++ b/examples/text/text_words_alignment.c @@ -70,27 +70,32 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - - if (IsKeyPressed(KEY_LEFT)) { + if (IsKeyPressed(KEY_LEFT)) + { hAlign = hAlign - 1; if (hAlign < 0) hAlign = 0; } - if (IsKeyPressed(KEY_RIGHT)) { + + if (IsKeyPressed(KEY_RIGHT)) + { hAlign = hAlign + 1; if (hAlign > 2) hAlign = 2; } - if (IsKeyPressed(KEY_UP)) { + + if (IsKeyPressed(KEY_UP)) + { vAlign = vAlign - 1; if (vAlign < 0) vAlign = 0; } - if (IsKeyPressed(KEY_DOWN)) { + + if (IsKeyPressed(KEY_DOWN)) + { vAlign = vAlign + 1; if (vAlign > 2) vAlign = 2; } // One word per second - wordIndex = (int)GetTime() % wordCount; - + wordIndex = (int)GetTime()%wordCount; //---------------------------------------------------------------------------------- // Draw @@ -108,9 +113,9 @@ int main(void) Vector2 textSize = MeasureTextEx(font, words[wordIndex], fontSize, fontSize*.1f); // Calculate the top-left text position based on the rectangle and alignment - Vector2 textPos = (Vector2) { - textContainerRect.x + Lerp(0.0f, textContainerRect.width - textSize.x, ((float)hAlign) * 0.5f), - textContainerRect.y + Lerp(0.0f, textContainerRect.height - textSize.y, ((float)vAlign) * 0.5f) + Vector2 textPos = (Vector2){ + textContainerRect.x + Lerp(0.0f, textContainerRect.width - textSize.x, ((float)hAlign)*0.5f), + textContainerRect.y + Lerp(0.0f, textContainerRect.height - textSize.y, ((float)vAlign)*0.5f) }; // Draw the text diff --git a/examples/textures/textures_mouse_painting.c b/examples/textures/textures_mouse_painting.c index 2575c4450..6996ddac4 100644 --- a/examples/textures/textures_mouse_painting.c +++ b/examples/textures/textures_mouse_painting.c @@ -179,7 +179,7 @@ int main(void) ClearBackground(RAYWHITE); // NOTE: Render texture must be y-flipped due to default OpenGL coordinates (left-bottom) - DrawTextureRec(target.texture, (Rectangle) { 0, 0, (float)target.texture.width, (float)-target.texture.height }, (Vector2) { 0, 0 }, WHITE); + DrawTextureRec(target.texture, (Rectangle){ 0, 0, (float)target.texture.width, (float)-target.texture.height }, (Vector2) { 0, 0 }, WHITE); // Draw drawing circle for reference if (mousePos.y > 50) diff --git a/examples/textures/textures_sprite_stacking.c b/examples/textures/textures_sprite_stacking.c index 5edc1af6b..b20474711 100644 --- a/examples/textures/textures_sprite_stacking.c +++ b/examples/textures/textures_sprite_stacking.c @@ -56,7 +56,7 @@ int main(void) // Add a positive/negative offset to spin right/left at different speeds if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)) rotationSpeed -= speedChange; if (IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D)) rotationSpeed += speedChange; - + rotation += rotationSpeed*GetFrameTime(); //---------------------------------------------------------------------------------- diff --git a/examples/textures/textures_tiled_drawing.c b/examples/textures/textures_tiled_drawing.c index 39a168850..9e5285035 100644 --- a/examples/textures/textures_tiled_drawing.c +++ b/examples/textures/textures_tiled_drawing.c @@ -100,7 +100,7 @@ int main(void) } // Check to see which color was clicked and set it as the active color - for (int i = 0; i < MAX_COLORS; ++i) + for (int i = 0; i < MAX_COLORS; i++) { if (CheckCollisionPointRec(mouse, colorRec[i])) { From 84737a9fc19669b04ce86eb458033b81089bbddc Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 22 Nov 2025 20:25:42 +0100 Subject: [PATCH 164/430] Update CONVENTIONS.md --- CONVENTIONS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONVENTIONS.md b/CONVENTIONS.md index 58cfa6270..86044193c 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -41,6 +41,7 @@ while (!WindowShouldClose()) } +// Always use accumulators as `x++` instead of `++x` for (int i = 0; i < NUM_VALUES; i++) printf("%i", i); // Be careful with the switch formatting! From f1719480e0f61418bf5f5e2a2c2f8bc20b1c3074 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 23 Nov 2025 13:21:31 +0100 Subject: [PATCH 165/430] Minor format tweaks --- src/platforms/rcore_desktop_sdl.c | 8 ++++---- src/platforms/rcore_drm.c | 6 +++--- src/platforms/rcore_web.c | 4 ++-- src/rmodels.c | 2 +- src/rtextures.c | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index d6c7fd476..f3da04fa4 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -1188,7 +1188,7 @@ Image GetClipboardImage(void) size_t dataSize = 0; void *fileData = NULL; - for (int i = 0; i < SDL_arraysize(imageFormats); ++i) + for (int i = 0; i < SDL_arraysize(imageFormats); i++) { // NOTE: This pointer should be free with SDL_free() at some point fileData = SDL_GetClipboardData(imageFormats[i], &dataSize); @@ -1395,7 +1395,7 @@ void PollInputEvents(void) //----------------------------------------------------------------------------- // WARNING: Indexes into this array are obtained by using SDL_Scancode values, not SDL_Keycode values //const Uint8 *keys = SDL_GetKeyboardState(NULL); - //for (int i = 0; i < 256; ++i) CORE.Input.Keyboard.currentKeyState[i] = keys[i]; + //for (int i = 0; i < 256; i++) CORE.Input.Keyboard.currentKeyState[i] = keys[i]; CORE.Window.resizedLastFrame = false; @@ -1562,7 +1562,7 @@ void PollInputEvents(void) case SDL_KEYDOWN: { #if defined(USING_VERSION_SDL3) - // SDL3 Migration: The following structures have been removed: * SDL_Keysym + // SDL3 Migration: The following structures have been removed: SDL_Keysym KeyboardKey key = ConvertScancodeToKey(event.key.scancode); #else KeyboardKey key = ConvertScancodeToKey(event.key.keysym.scancode); @@ -1697,7 +1697,7 @@ void PollInputEvents(void) int jid = event.jdevice.which; // Joystick device index // check if already added at InitPlatform - for (int i = 0; i < MAX_GAMEPADS; ++i) + for (int i = 0; i < MAX_GAMEPADS; i++) { if (jid == platform.gamepadId[i]) { diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index c0fa5a5f3..fa20b9039 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -1456,7 +1456,7 @@ int InitPlatform(void) // find the EGL config that matches the previously setup GBM format int found = 0; - for (EGLint i = 0; i < matchingNumConfigs; ++i) + for (EGLint i = 0; i < matchingNumConfigs; i++) { EGLint id = 0; if (!eglGetConfigAttrib(platform.device, configs[i], EGL_NATIVE_VISUAL_ID, &id)) @@ -1878,7 +1878,7 @@ static void InitEvdevInput(void) platform.mouseFd = -1; // Reset variables - for (int i = 0; i < MAX_TOUCH_POINTS; ++i) + for (int i = 0; i < MAX_TOUCH_POINTS; i++) { CORE.Input.Touch.position[i].x = -1; CORE.Input.Touch.position[i].y = -1; @@ -2463,7 +2463,7 @@ static int FindNearestConnectorMode(const drmModeConnector *connector, uint widt continue; } - const int unusedPixels = (mode->hdisplay - width) * (mode->vdisplay - height); + const int unusedPixels = (mode->hdisplay - width)*(mode->vdisplay - height); const int fpsDiff = mode->vrefresh - fps; if ((unusedPixels < minUnusedPixels) || diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index 5f8afd7e4..97a4b3f29 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -1671,8 +1671,8 @@ static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadE eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state", gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping); - for (int i = 0; i < gamepadEvent->numAxes; ++i) TRACELOGD("Axis %d: %g", i, gamepadEvent->axis[i]); - for (int i = 0; i < gamepadEvent->numButtons; ++i) TRACELOGD("Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); + for (int i = 0; i < gamepadEvent->numAxes; i++) TRACELOGD("Axis %d: %g", i, gamepadEvent->axis[i]); + for (int i = 0; i < gamepadEvent->numButtons; i++) TRACELOGD("Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); */ if (gamepadEvent->connected && (gamepadEvent->index < MAX_GAMEPADS)) diff --git a/src/rmodels.c b/src/rmodels.c index c09a94652..e3800b575 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -3289,7 +3289,7 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) for (int z = 0; z < cubicmap.height; ++z) { - for (int x = 0; x < cubicmap.width; ++x) + for (int x = 0; x < cubicmap.width; x++) { // Define the 8 vertex of the cube, we will combine them accordingly later... Vector3 v1 = { w*(x - 0.5f), h2, h*(z - 0.5f) }; diff --git a/src/rtextures.c b/src/rtextures.c index 299ab6793..3e4666572 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -3341,14 +3341,14 @@ void ImageClearBackground(Image *dst, Color color) unsigned char *pSrcPixel = (unsigned char *)dst->data; int bytesPerPixel = GetPixelDataSize(1, 1, dst->format); - int totalPixels = dst->width * dst->height; + int totalPixels = dst->width*dst->height; // Repeat the first pixel data throughout the image, // doubling the pixels copied on each iteration for (int i = 1; i < totalPixels; i *= 2) { int pixelsToCopy = MIN(i, totalPixels - i); - memcpy(pSrcPixel + i * bytesPerPixel, pSrcPixel, pixelsToCopy * bytesPerPixel); + memcpy(pSrcPixel + i*bytesPerPixel, pSrcPixel, pixelsToCopy*bytesPerPixel); } } @@ -3730,7 +3730,7 @@ void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color) for (int x = 1; x < (int)rec.width; x *= 2) { int pixelsToCopy = MIN(x, (int)rec.width - x); - memcpy(pSrcPixel + x*bytesPerPixel, pSrcPixel, pixelsToCopy * bytesPerPixel); + memcpy(pSrcPixel + x*bytesPerPixel, pSrcPixel, pixelsToCopy*bytesPerPixel); } // Repeat the first row data for all other rows From cf5e84c3c4a984457c9ad6c0353acf0c50a714ba Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 23 Nov 2025 21:37:35 +0100 Subject: [PATCH 166/430] Update models_skybox_rendering.c --- examples/models/models_skybox_rendering.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/examples/models/models_skybox_rendering.c b/examples/models/models_skybox_rendering.c index ddce6a28f..9359e03dc 100644 --- a/examples/models/models_skybox_rendering.c +++ b/examples/models/models_skybox_rendering.c @@ -54,7 +54,8 @@ int main(void) Mesh cube = GenMeshCube(1.0f, 1.0f, 1.0f); Model skybox = LoadModelFromMesh(cube); - // Set this to true to use an HDR Texture, Note that raylib must be built with HDR Support for this to work SUPPORT_FILEFORMAT_HDR + // Set this to true to use an HDR Texture + // NOTE: raylib must be built with HDR Support for this to work: SUPPORT_FILEFORMAT_HDR bool useHDR = false; // Load skybox shader and set required locations @@ -63,8 +64,8 @@ int main(void) TextFormat("resources/shaders/glsl%i/skybox.fs", GLSL_VERSION)); SetShaderValue(skybox.materials[0].shader, GetShaderLocation(skybox.materials[0].shader, "environmentMap"), (int[1]){ MATERIAL_MAP_CUBEMAP }, SHADER_UNIFORM_INT); - SetShaderValue(skybox.materials[0].shader, GetShaderLocation(skybox.materials[0].shader, "doGamma"), (int[1]) { useHDR ? 1 : 0 }, SHADER_UNIFORM_INT); - SetShaderValue(skybox.materials[0].shader, GetShaderLocation(skybox.materials[0].shader, "vflipped"), (int[1]){ useHDR ? 1 : 0 }, SHADER_UNIFORM_INT); + SetShaderValue(skybox.materials[0].shader, GetShaderLocation(skybox.materials[0].shader, "doGamma"), (int[1]){ useHDR? 1 : 0 }, SHADER_UNIFORM_INT); + SetShaderValue(skybox.materials[0].shader, GetShaderLocation(skybox.materials[0].shader, "vflipped"), (int[1]){ useHDR? 1 : 0 }, SHADER_UNIFORM_INT); // Load cubemap shader and setup required shader locations Shader shdrCubemap = LoadShader(TextFormat("resources/shaders/glsl%i/cubemap.vs", GLSL_VERSION), @@ -91,9 +92,11 @@ int main(void) } else { - Image img = LoadImage("resources/skybox.png"); - skybox.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture = LoadTextureCubemap(img, CUBEMAP_LAYOUT_AUTO_DETECT); // CUBEMAP_LAYOUT_PANORAMA - UnloadImage(img); + // TODO: WARNING: On PLATFORM_WEB it requires a big amount of memory to process input image + // and generate the required cubemap image to be passed to rlLoadTextureCubemap() + Image image = LoadImage("resources/skybox.png"); + skybox.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture = LoadTextureCubemap(image, CUBEMAP_LAYOUT_AUTO_DETECT); + UnloadImage(image); } DisableCursor(); // Limit cursor to relative movement inside the window @@ -132,9 +135,9 @@ int main(void) } else { - Image img = LoadImage(droppedFiles.paths[0]); - skybox.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture = LoadTextureCubemap(img, CUBEMAP_LAYOUT_AUTO_DETECT); - UnloadImage(img); + Image image = LoadImage(droppedFiles.paths[0]); + skybox.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture = LoadTextureCubemap(image, CUBEMAP_LAYOUT_AUTO_DETECT); + UnloadImage(image); } TextCopy(skyboxFileName, droppedFiles.paths[0]); From e1b9857b14e216b8d33136a0d2c06886b4bcb330 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 23 Nov 2025 21:40:39 +0100 Subject: [PATCH 167/430] Some TODOs and format reviews --- src/config.h | 8 ++-- src/external/rlsw.h | 54 ++++++++++++-------------- src/platforms/rcore_android.c | 59 ++++++++++------------------- src/platforms/rcore_desktop_glfw.c | 12 +++--- src/platforms/rcore_desktop_rgfw.c | 28 ++++++++------ src/platforms/rcore_desktop_sdl.c | 20 +++++----- src/platforms/rcore_desktop_win32.c | 8 +++- src/platforms/rcore_drm.c | 22 +++++------ src/platforms/rcore_web.c | 39 +++++++++---------- src/raudio.c | 6 +-- src/rcore.c | 45 ++++++++++------------ src/rlgl.h | 12 +++--- src/rmodels.c | 9 ++--- src/rshapes.c | 4 +- src/rtext.c | 2 +- src/rtextures.c | 17 +++------ 16 files changed, 154 insertions(+), 191 deletions(-) diff --git a/src/config.h b/src/config.h index 9152acc8c..89b32d0fe 100644 --- a/src/config.h +++ b/src/config.h @@ -49,10 +49,10 @@ #define SUPPORT_RPRAND_GENERATOR 1 // Mouse gestures are directly mapped like touches and processed by gestures system #define SUPPORT_MOUSE_GESTURES 1 -// Reconfigure standard input to receive key inputs, works with SSH connection. +// Reconfigure standard input to receive key inputs, works with SSH connection #define SUPPORT_SSH_KEYBOARD_RPI 1 -// Setting a higher resolution can improve the accuracy of time-out intervals in wait functions. -// However, it can also reduce overall system performance, because the thread scheduler switches tasks more often. +// Setting a higher resolution can improve the accuracy of time-out intervals in wait functions +// However, it can also reduce overall system performance, because the thread scheduler switches tasks more often #define SUPPORT_WINMM_HIGHRES_TIMER 1 // Use busy wait loop for timing sync, if not defined, a high-resolution timer is set up and used //#define SUPPORT_BUSY_WAIT_LOOP 1 @@ -225,7 +225,7 @@ // On font atlas image generation [GenImageFontAtlas()], add a 3x3 pixels white rectangle // at the bottom-right corner of the atlas. It can be useful to for shapes drawing, to allow -// drawing text and shapes with a single draw call [SetShapesTexture()]. +// drawing text and shapes with a single draw call [SetShapesTexture()] #define SUPPORT_FONT_ATLAS_WHITE_REC 1 // Support conservative font atlas size estimation diff --git a/src/external/rlsw.h b/src/external/rlsw.h index 318d77334..d78da3ff8 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -145,7 +145,7 @@ #define SW_MAX_TEXTURES 128 #endif -// Under normal circumstances, clipping a polygon can add at most one vertex per clipping plane. +// Under normal circumstances, clipping a polygon can add at most one vertex per clipping plane // Considering the largest polygon involved is a quadrilateral (4 vertices), // and that clipping occurs against both the frustum (6 planes) and the scissors (4 planes), // the maximum number of vertices after clipping is: @@ -1530,7 +1530,7 @@ DEFINE_FRAMEBUFFER_COPY_BEGIN(R5G5B5A1, uint16_t) uint8_t r5 = (color[0]*31 + 127)/255; uint8_t g5 = (color[1]*31 + 127)/255; uint8_t b5 = (color[2]*31 + 127)/255; - uint8_t a1 = color[3] >= 128 ? 1 : 0; + uint8_t a1 = (color[3] >= 128)? 1 : 0; #if SW_GL_FRAMEBUFFER_COPY_BGRA uint16_t pixel = (b5 << 11) | (g5 << 6) | (r5 << 1) | a1; @@ -1661,7 +1661,7 @@ DEFINE_FRAMEBUFFER_BLIT_BEGIN(R5G5B5A1, uint16_t) uint8_t r5 = (color[0]*31 + 127)/255; uint8_t g5 = (color[1]*31 + 127)/255; uint8_t b5 = (color[2]*31 + 127)/255; - uint8_t a1 = color[3] >= 128 ? 1 : 0; + uint8_t a1 = (color[3] >= 128)? 1 : 0; #if SW_GL_FRAMEBUFFER_COPY_BGRA uint16_t pixel = (b5 << 11) | (g5 << 6) | (r5 << 1) | a1; @@ -1919,7 +1919,7 @@ static inline void sw_texture_sample_nearest(float *color, const sw_texture_t *t static inline void sw_texture_sample_linear(float *color, const sw_texture_t *tex, float u, float v) { // TODO: With a bit more cleverness we could clearly reduce the - // number of operations here, but for now it works fine. + // number of operations here, but for now it works fine float xf = (u*tex->width) - 0.5f; float yf = (v*tex->height) - 0.5f; @@ -2203,13 +2203,13 @@ static inline bool sw_polygon_clip(sw_vertex_t polygon[SW_MAX_CLIPPED_POLYGON_VE //------------------------------------------------------------------------------------------- static inline bool sw_triangle_face_culling(void) { - // NOTE: Face culling is done before clipping to avoid unnecessary computations. + // NOTE: Face culling is done before clipping to avoid unnecessary computations // To handle triangles crossing the w=0 plane correctly, // we perform the winding order test in homogeneous coordinates directly, - // before the perspective division (division by w). + // before the perspective division (division by w) // This test determines the orientation of the triangle in the (x,y,w) plane, // which corresponds to the projected 2D winding order sign, - // even with negative w values. + // even with negative w values // Preload homogeneous coordinates into local variables const float *h0 = RLSW.vertexBuffer[0].homogeneous; @@ -2221,7 +2221,7 @@ static inline bool sw_triangle_face_culling(void) // This is the determinant of the matrix formed by the (x, y, w) components // of the vertices, which correctly captures the winding order in homogeneous // space and its relationship to the projected 2D winding order, even with - // negative w values. + // negative w values // The determinant formula used here is: // h0.x*(h1.y*h2.w - h2.y*h1.w) + // h1.x*(h2.y*h0.w - h0.y*h2.w) + @@ -2233,20 +2233,18 @@ static inline bool sw_triangle_face_culling(void) h2[0]*(h0[1]*h1[3] - h1[1]*h0[3]); // Discard the triangle if its winding order (determined by the sign - // of the homogeneous area/determinant) matches the culled direction. + // of the homogeneous area/determinant) matches the culled direction // A positive hSgnArea typically corresponds to a counter-clockwise // winding in the projected space when all w > 0. // This test is robust for points with w > 0 or w < 0, correctly - // capturing the change in orientation when crossing the w=0 plane. + // capturing the change in orientation when crossing the w=0 plane - // The culling logic remains the same based on the signed area/determinant. + // The culling logic remains the same based on the signed area/determinant // A value of 0 for hSgnArea means the points are collinear in (x, y, w) // space, which corresponds to a degenerate triangle projection. // Such triangles are typically not culled by this test (0 < 0 is false, 0 > 0 is false) - // and should be handled by the clipper if necessary. - return (RLSW.cullFace == SW_FRONT) - ? (hSgnArea < 0) // Cull if winding is "clockwise" in the projected sense - : (hSgnArea > 0); // Cull if winding is "counter-clockwise" in the projected sense + // and should be handled by the clipper if necessary + return (RLSW.cullFace == SW_FRONT)? (hSgnArea < 0) : (hSgnArea > 0); // Cull if winding is "clockwise" : "counter-clockwise" } static inline void sw_triangle_clip_and_project(void) @@ -2559,14 +2557,14 @@ static inline void sw_triangle_render(void) //------------------------------------------------------------------------------------------- static inline bool sw_quad_face_culling(void) { - // NOTE: Face culling is done before clipping to avoid unnecessary computations. + // NOTE: Face culling is done before clipping to avoid unnecessary computations // To handle quads crossing the w=0 plane correctly, // we perform the winding order test in homogeneous coordinates directly, - // before the perspective division (division by w). + // before the perspective division (division by w) // For a convex quad with vertices P0, P1, P2, P3 in sequential order, // the winding order of the quad is the same as the winding order // of the triangle P0 P1 P2. We use the homogeneous triangle - // winding test on this first triangle. + // winding test on this first triangle // Preload homogeneous coordinates into local variables const float *h0 = RLSW.vertexBuffer[0].homogeneous; @@ -2578,11 +2576,11 @@ static inline bool sw_quad_face_culling(void) // Compute a value proportional to the signed area of the triangle P0 P1 P2 // in the projected 2D plane, calculated directly using homogeneous coordinates - // BEFORE division by w. + // BEFORE division by w // This is the determinant of the matrix formed by the (x, y, w) components // of the vertices P0, P1, and P2. Its sign correctly indicates the winding order // in homogeneous space and its relationship to the projected 2D winding order, - // even with negative w values. + // even with negative w values // The determinant formula used here is: // h0.x*(h1.y*h2.w - h2.y*h1.w) + // h1.x*(h2.y*h0.w - h0.y*h2.w) + @@ -2594,21 +2592,19 @@ static inline bool sw_quad_face_culling(void) h2[0]*(h0[1]*h1[3] - h1[1]*h0[3]); // Perform face culling based on the winding order determined by the sign - // of the homogeneous area/determinant of triangle P0 P1 P2. + // of the homogeneous area/determinant of triangle P0 P1 P2 // This test is robust for points with w > 0 or w < 0 within the triangle, - // correctly capturing the change in orientation when crossing the w=0 plane. + // correctly capturing the change in orientation when crossing the w=0 plane // A positive hSgnArea typically corresponds to a counter-clockwise - // winding in the projected space when all w > 0. + // winding in the projected space when all w > 0 // A value of 0 for hSgnArea means P0, P1, P2 are collinear in (x, y, w) - // space, which corresponds to a degenerate triangle projection. + // space, which corresponds to a degenerate triangle projection // Such quads might also be degenerate or non-planar. They are typically // not culled by this test (0 < 0 is false, 0 > 0 is false) // and should be handled by the clipper if necessary. - return (RLSW.cullFace == SW_FRONT) - ? (hSgnArea < 0.0f) // Cull if winding is "clockwise" in the projected sense - : (hSgnArea > 0.0f); // Cull if winding is "counter-clockwise" in the projected sense + return (RLSW.cullFace == SW_FRONT)? (hSgnArea < 0.0f) : (hSgnArea > 0.0f); // Cull if winding is "clockwise" : "counter-clockwise" } static inline void sw_quad_clip_and_project(void) @@ -4596,8 +4592,8 @@ void swDrawElements(SWdraw mode, int count, int type, const void *indices) for (int i = 0; i < count; i++) { - int index = indicesUb ? indicesUb[i] : - (indicesUs ? indicesUs[i] : indicesUi[i]); + int index = indicesUb? indicesUb[i] : + (indicesUs? indicesUs[i] : indicesUi[i]); float u, v; if (texcoords) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 4f106ee3b..88b3b4bba 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -623,10 +623,9 @@ double GetTime(void) } // Open URL with default system browser (if available) -// NOTE: This function is only safe to use if you control the URL given. -// A user could craft a malicious string performing another action. -// Only call this function yourself not with user input or make sure to check the string yourself. -// Ref: https://github.com/raysan5/raylib/issues/686 +// NOTE: This function is only safe to use if you control the URL given +// A user could craft a malicious string performing another action +// Only call this function yourself not with user input or make sure to check the string yourself void OpenURL(const char *url) { // Security check to (partially) avoid malicious code @@ -687,7 +686,7 @@ void SetMouseCursor(int cursor) TRACELOG(LOG_WARNING, "SetMouseCursor() not implemented on target platform"); } -// Get physical key name. +// Get physical key name const char *GetKeyName(int key) { TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform"); @@ -748,9 +747,9 @@ void PollInputEvents(void) // Process this event if (platform.source != NULL) platform.source->process(platform.app, platform.source); - // NOTE: Allow closing the window in case a configuration change happened. + // NOTE: Allow closing the window in case a configuration change happened // The android_main function should be allowed to return to its caller in order for the - // Android OS to relaunch the activity. + // Android OS to relaunch the activity if (platform.app->destroyRequested != 0) { CORE.Window.shouldClose = true; @@ -829,13 +828,13 @@ int InitPlatform(void) // Wait for window to be initialized (display and context) while (!CORE.Window.ready) { - // Process events until we reach TIMEOUT, which indicates no more events queued. + // Process events until we reach TIMEOUT, which indicates no more events queued while ((pollResult = ALooper_pollOnce(0, NULL, &pollEvents, ((void **)&platform.source)) > ALOOPER_POLL_TIMEOUT)) { // Process this event if (platform.source != NULL) platform.source->process(platform.app, platform.source); - // NOTE: It's highly likely destroyRequested will never be non-zero at the start of the activity lifecycle. + // NOTE: It's highly likely destroyRequested will never be non-zero at the start of the activity lifecycle //if (platform.app->destroyRequested != 0) CORE.Window.shouldClose = true; } } @@ -869,8 +868,9 @@ void ClosePlatform(void) platform.device = EGL_NO_DISPLAY; } - // NOTE: Reset global state in case the activity is being relaunched. - if (platform.app->destroyRequested != 0) { + // NOTE: Reset global state in case the activity is being relaunched + if (platform.app->destroyRequested != 0) + { CORE = (CoreData){0}; platform = (PlatformData){0}; } @@ -925,7 +925,7 @@ static int InitGraphicsDevice(void) // Initialize the EGL device connection if (eglInitialize(platform.device, NULL, NULL) == EGL_FALSE) { - // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred. + // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); return -1; } @@ -1081,21 +1081,6 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd) // Initialize random seed SetRandomSeed((unsigned int)time(NULL)); - - // TODO: GPU assets reload in case of lost focus (lost context) - // NOTE: This problem has been solved just unbinding and rebinding context from display - /* - if (assetsReloadRequired) - { - for (int i = 0; i < assetCount; i++) - { - // TODO: Unload old asset if required - - // Load texture again to pointed texture - (*textureAsset + i) = LoadTexture(assetPath[i]); - } - } - */ } } } break; @@ -1115,7 +1100,7 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd) case APP_CMD_TERM_WINDOW: { // Detach OpenGL context and destroy display surface - // NOTE 1: This case is used when the user exits the app without closing it. We detach the context to ensure everything is recoverable upon resuming. + // NOTE 1: This case is used when the user exits the app without closing it, context is detached to ensure everything is recoverable upon resuming // NOTE 2: Detaching context before destroying display surface avoids losing our resources (textures, shaders, VBOs...) // NOTE 3: In some cases (too many context loaded), OS could unload context automatically... :( if (platform.device != EGL_NO_DISPLAY) @@ -1179,8 +1164,8 @@ static GamepadButton AndroidTranslateGamepadButton(int button) static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) { // If additional inputs are required check: - // https://developer.android.com/ndk/reference/group/input - // https://developer.android.com/training/game-controllers/controller-input + // Ref: https://developer.android.com/ndk/reference/group/input + // Ref: https://developer.android.com/training/game-controllers/controller-input int type = AInputEvent_getType(event); int source = AInputEvent_getSource(event); @@ -1290,7 +1275,7 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) { // Let the OS handle input to avoid app stuck. Behaviour: CMD_PAUSE -> CMD_SAVE_STATE -> CMD_STOP -> CMD_CONFIG_CHANGED -> CMD_LOST_FOCUS // Resuming Behaviour: CMD_START -> CMD_RESUME -> CMD_CONFIG_CHANGED -> CMD_CONFIG_CHANGED -> CMD_GAINED_FOCUS - // It seems like locking mobile, screen size (CMD_CONFIG_CHANGED) is affected. + // It seems like locking mobile, screen size (CMD_CONFIG_CHANGED) is affected // NOTE: AndroidManifest.xml must have // Before that change, activity was calling CMD_TERM_WINDOW and CMD_DESTROY when locking mobile, so that was not a normal behaviour return 0; @@ -1419,15 +1404,9 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) if (CORE.Input.Touch.pointCount > 0) CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 1; else CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 0; - // Stores the previous position of touch[0] only while it's active to calculate the delta. - if (flags == AMOTION_EVENT_ACTION_MOVE) - { - CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; - } - else - { - CORE.Input.Mouse.previousPosition = CORE.Input.Touch.position[0]; - } + // Stores the previous position of touch[0] only while it's active to calculate the delta + if (flags == AMOTION_EVENT_ACTION_MOVE) CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; + else CORE.Input.Mouse.previousPosition = CORE.Input.Touch.position[0]; // Map touch[0] as mouse input for convenience CORE.Input.Mouse.currentPosition = CORE.Input.Touch.position[0]; diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 746dc8a3c..d7f76b9a8 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1220,9 +1220,9 @@ void PollInputEvents(void) // Map touch position to mouse position for convenience // WARNING: If the target desktop device supports touch screen, this behaviour should be reviewed! - // TODO: GLFW does not support multi-touch input just yet - // https://www.codeproject.com/Articles/668404/Programming-for-Multi-Touch - // https://docs.microsoft.com/en-us/windows/win32/wintouch/getting-started-with-multi-touch-messages + // TODO: GLFW does not support multi-touch input yet + // Ref: https://www.codeproject.com/Articles/668404/Programming-for-Multi-Touch + // Ref: https://docs.microsoft.com/en-us/windows/win32/wintouch/getting-started-with-multi-touch-messages CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; // Check if gamepads are ready @@ -1334,7 +1334,7 @@ void PollInputEvents(void) // Function wrappers around RL_*alloc macros, used by glfwInitAllocator() inside of InitPlatform() // We need to provide these because GLFWallocator expects function pointers with specific signatures // Similar wrappers exist in utils.c but we cannot reuse them here due to declaration mismatch -// https://www.glfw.org/docs/latest/intro_guide.html#init_allocator +// Ref: https://www.glfw.org/docs/latest/intro_guide.html#init_allocator static void *AllocateWrapper(size_t size, void *user) { (void)user; @@ -1592,8 +1592,8 @@ int InitPlatform(void) bool requestWindowedFullscreen = (CORE.Window.screen.height == 0) && (CORE.Window.screen.width == 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; + int creationWidth = (CORE.Window.screen.width != 0)? CORE.Window.screen.width : 1; + int creationHeight = (CORE.Window.screen.height != 0)? CORE.Window.screen.height : 1; platform.handle = glfwCreateWindow(creationWidth, creationHeight, (CORE.Window.title != 0)? CORE.Window.title : " ", NULL, NULL); if (!platform.handle) diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 09712a706..a1b13856b 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -870,17 +870,27 @@ double GetTime(void) } // Open URL with default system browser (if available) -// NOTE: This function is only safe to use if you control the URL given. -// A user could craft a malicious string performing another action. -// Only call this function yourself not with user input or make sure to check the string yourself. -// Ref: https://github.com/raysan5/raylib/issues/686 +// NOTE: This function is only safe to use if you control the URL given +// A user could craft a malicious string performing another action void OpenURL(const char *url) { // Security check to (partially) avoid malicious code on target platform if (strchr(url, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'] character"); else { - // TODO: Open URL implementation + char *cmd = (char *)RL_CALLOC(strlen(url) + 32, sizeof(char)); +#if defined(_WIN32) + sprintf(cmd, "explorer \"%s\"", url); +#endif +#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) + sprintf(cmd, "xdg-open '%s'", url); // Alternatives: firefox, x-www-browser +#endif +#if defined(__APPLE__) + sprintf(cmd, "open '%s'", url); +#endif + int result = system(cmd); + if (result == -1) TRACELOG(LOG_WARNING, "OpenURL() child process could not be created"); + RL_FREE(cmd); } } @@ -915,7 +925,7 @@ void SetMouseCursor(int cursor) RGFW_window_setMouseStandard(platform.window, cursor); } -// Get physical key name. +// Get physical key name const char *GetKeyName(int key) { TRACELOG(LOG_WARNING, "GetKeyName() unsupported on target platform"); @@ -1095,11 +1105,7 @@ void PollInputEvents(void) CORE.Input.Keyboard.currentKeyState[key] = 1; } - // TODO: Put exitKey verification outside the switch? - if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey]) - { - CORE.Window.shouldClose = true; - } + if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey]) CORE.Window.shouldClose = true; // NOTE: event.text.text data comes an UTF-8 text sequence but we register codepoints (int) // Check if there is space available in the queue diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index f3da04fa4..36235a6c8 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -1129,7 +1129,7 @@ Vector2 GetWindowScaleDPI(void) #ifndef USING_VERSION_SDL3 // NOTE: SDL_GetWindowDisplayScale was only added on SDL3 - // see https://wiki.libsdl.org/SDL3/SDL_GetWindowDisplayScale + // Ref: https://wiki.libsdl.org/SDL3/SDL_GetWindowDisplayScale // TODO: Implement the window scale factor calculation manually TRACELOG(LOG_WARNING, "GetWindowScaleDPI() not implemented on target platform"); #else @@ -1425,8 +1425,10 @@ void PollInputEvents(void) CORE.Window.dropFilepaths[CORE.Window.dropFileCount] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); #if defined(USING_VERSION_SDL3) - // const char *data; /**< The text for SDL_EVENT_DROP_TEXT and the file name for SDL_EVENT_DROP_FILE, NULL for other events */ - // Event memory is now managed by SDL, so you should not free the data in SDL_EVENT_DROP_FILE, and if you want to hold onto the text in SDL_EVENT_TEXT_EDITING and SDL_EVENT_TEXT_INPUT events, you should make a copy of it. SDL_TEXTINPUTEVENT_TEXT_SIZE is no longer necessary and has been removed. + // const char *data; // The text for SDL_EVENT_DROP_TEXT and the file name for SDL_EVENT_DROP_FILE, NULL for other events + // Event memory is now managed by SDL, so you should not free the data in SDL_EVENT_DROP_FILE, + // and if you want to hold onto the text in SDL_EVENT_TEXT_EDITING and SDL_EVENT_TEXT_INPUT events, + // you should make a copy of it. SDL_TEXTINPUTEVENT_TEXT_SIZE is no longer necessary and has been removed strcpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.data); #else strcpy(CORE.Window.dropFilepaths[CORE.Window.dropFileCount], event.drop.file); @@ -1458,7 +1460,7 @@ void PollInputEvents(void) // SDL3 states: // The SDL_WINDOWEVENT_* events have been moved to top level events, and SDL_WINDOWEVENT has been removed // In general, handling this change just means checking for the individual events instead of first checking for SDL_WINDOWEVENT - // and then checking for window events. You can compare the event >= SDL_EVENT_WINDOW_FIRST and <= SDL_EVENT_WINDOW_LAST if you need to see whether it's a window event. + // and then checking for window events. You can compare the event >= SDL_EVENT_WINDOW_FIRST and <= SDL_EVENT_WINDOW_LAST if you need to see whether it's a window event case SDL_WINDOWEVENT: { switch (event.window.event) @@ -1582,11 +1584,9 @@ void PollInputEvents(void) if (event.key.repeat) CORE.Input.Keyboard.keyRepeatInFrame[key] = 1; - // TODO: Put exitKey verification outside the switch? - if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey]) - { - CORE.Window.shouldClose = true; - } + // Check for registered exit key to request exit game loop on next iteration + if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey]) CORE.Window.shouldClose = true; + } break; case SDL_KEYUP: @@ -2080,7 +2080,7 @@ int InitPlatform(void) // Disable mouse events being interpreted as touch events // NOTE: This is wanted because there are SDL_FINGER* events available which provide unique data - // Due to the way PollInputEvents() and rgestures.h are currently implemented, setting this won't break SUPPORT_MOUSE_GESTURES + // Due to the way PollInputEvents() and rgestures.h are currently implemented, setting this won't break SUPPORT_MOUSE_GESTURES SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); SDL_EventState(SDL_DROPFILE, SDL_ENABLE); diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index 37f3fbac4..b3cbd515d 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -939,7 +939,7 @@ void SetWindowIcon(Image image) // Set icon for window void SetWindowIcons(Image *images, int count) { - // TODO. + // TODO: Implement SetWindowIcons() } void SetWindowTitle(const char *title) @@ -1246,7 +1246,11 @@ void OpenURL(const char *url) if (strchr(url, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'] character"); else { - TRACELOG(LOG_WARNING, "OpenURL not implemented"); + char *cmd = (char *)RL_CALLOC(strlen(url) + 32, sizeof(char)); + sprintf(cmd, "explorer \"%s\"", url); + int result = system(cmd); + if (result == -1) TRACELOG(LOG_WARNING, "OpenURL() child process could not be created"); + RL_FREE(cmd); } } diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index fa20b9039..881f96034 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -220,7 +220,7 @@ static const short linuxToRaylibMap[KEYMAP_SIZE] = { 248, 0, 0, 0, 0, 0, 0, 0, // Gamepads are mapped according to: - // https://www.kernel.org/doc/html/next/input/gamepad.html + // Ref: https://www.kernel.org/doc/html/next/input/gamepad.html // Those mappings are standardized, but that doesn't mean people follow // the standards, so this is more of an approximation [BTN_DPAD_UP] = GAMEPAD_BUTTON_LEFT_FACE_UP, @@ -637,7 +637,7 @@ static uint32_t GetOrCreateFbForBo(struct gbm_bo *bo) } // Renders a blank frame to allocate initial buffers -// TODO: WARNING: Platform layers do not include OpenGL code! +// TODO: WARNING: Platform backend should not include OpenGL code void RenderBlankFrame() { glClearColor(0, 0, 0, 1); @@ -1213,9 +1213,9 @@ int InitPlatform(void) TRACELOG(LOG_TRACE, "DISPLAY: Connector %i modes detected: %i", i, con->count_modes); TRACELOG(LOG_TRACE, "DISPLAY: Connector %i status: %s", i, - (con->connection == DRM_MODE_CONNECTED) ? "CONNECTED" : - (con->connection == DRM_MODE_DISCONNECTED) ? "DISCONNECTED" : - (con->connection == DRM_MODE_UNKNOWNCONNECTION) ? "UNKNOWN" : "OTHER"); + (con->connection == DRM_MODE_CONNECTED)? "CONNECTED" : + (con->connection == DRM_MODE_DISCONNECTED)? "DISCONNECTED" : + (con->connection == DRM_MODE_UNKNOWNCONNECTION)? "UNKNOWN" : "OTHER"); // In certain cases the status of the conneciton is reported as UKNOWN, but it is still connected // This might be a hardware or software limitation like on Raspberry Pi Zero with composite output @@ -1298,7 +1298,7 @@ int InitPlatform(void) } const bool allowInterlaced = FLAG_IS_SET(CORE.Window.flags, FLAG_INTERLACED_HINT); - const int fps = (CORE.Time.target > 0) ? (1.0/CORE.Time.target) : 60; + const int fps = (CORE.Time.target > 0)? (1.0/CORE.Time.target) : 60; // Try to find an exact matching mode platform.modeIndex = FindExactConnectorMode(platform.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced); @@ -1345,7 +1345,7 @@ int InitPlatform(void) platform.connector->modes[0].name, platform.connector->modes[0].hdisplay, platform.connector->modes[0].vdisplay, - (platform.connector->modes[0].flags & DRM_MODE_FLAG_INTERLACE) ? 'i' : 'p', + (platform.connector->modes[0].flags & DRM_MODE_FLAG_INTERLACE)? 'i' : 'p', platform.connector->modes[0].vrefresh); } else @@ -1740,10 +1740,10 @@ static void InitKeyboard(void) else { // Reconfigure keyboard mode to get: - // - scancodes (K_RAW) - // - keycodes (K_MEDIUMRAW) - // - ASCII chars (K_XLATE) - // - UNICODE chars (K_UNICODE) + // - scancodes (K_RAW) + // - keycodes (K_MEDIUMRAW) + // - ASCII chars (K_XLATE) + // - UNICODE chars (K_UNICODE) ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE); // ASCII chars } diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index 97a4b3f29..2d2f8d1c0 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -159,7 +159,7 @@ static const char *GetCanvasId(void); bool WindowShouldClose(void) { // Emscripten Asyncify is required to run synchronous code in asynchronous JS - // REF: https://emscripten.org/docs/porting/asyncify.html + // Ref: https://emscripten.org/docs/porting/asyncify.html // WindowShouldClose() is not called on a web-ready raylib application if using emscripten_set_main_loop() // and encapsulating one frame execution on a UpdateDrawFrame() function, @@ -309,8 +309,8 @@ void ToggleBorderlessWindowed(void) if (enterBorderless) { - // NOTE: 1. The setTimeouts handle the browser mode change delay - // 2. The style unset handles the possibility of a width="value%" like on the default shell.html file + // 1. The setTimeouts handle the browser mode change delay + // 2. The style unset handles the possibility of a width="value%" like on the default shell.html file EM_ASM ( setTimeout(function() @@ -866,7 +866,6 @@ void EnableCursor(void) // Disables cursor (lock cursor) void DisableCursor(void) { - // TODO: figure out how not to hard code the canvas ID here. emscripten_request_pointerlock(GetCanvasId(), 1); // Set cursor position in the middle @@ -893,10 +892,9 @@ double GetTime(void) } // Open URL with default system browser (if available) -// NOTE: This function is only safe to use if you control the URL given. -// A user could craft a malicious string performing another action. -// Only call this function yourself not with user input or make sure to check the string yourself. -// Ref: https://github.com/raysan5/raylib/issues/686 +// NOTE: This function is only safe to use if you control the URL given +// A user could craft a malicious string performing another action +// Only call this function yourself not with user input or make sure to check the string yourself void OpenURL(const char *url) { // Security check to (partially) avoid malicious code on target platform @@ -1090,10 +1088,6 @@ void PollInputEvents(void) } CORE.Window.resizedLastFrame = false; - - // TODO: This code does not seem to do anything?? - //if (CORE.Window.eventWaiting) glfwWaitEvents(); // Wait for in input events before continue (drawing is paused) - //else glfwPollEvents(); // Poll input events: keyboard/mouse/window events (callbacks) --> WARNING: Where is key input reset? } //---------------------------------------------------------------------------------- @@ -1161,8 +1155,8 @@ int InitPlatform(void) } // NOTE: When asking for an OpenGL context version, most drivers provide the highest supported version - // with backward compatibility to older OpenGL versions. - // For example, if using OpenGL 1.1, driver can provide a 4.3 backwards compatible context. + // with backward compatibility to older OpenGL versions + // For example, if using OpenGL 1.1, driver can provide a 4.3 backwards compatible context // Check selection OpenGL version if (rlGetVersion() == RL_OPENGL_21) @@ -1172,10 +1166,12 @@ int InitPlatform(void) } else if (rlGetVersion() == RL_OPENGL_33) { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Choose OpenGL major version (just hint) - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Profiles Hint: Only 3.3 and above! - // Values: GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Choose OpenGL major version (just hint) + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) + // Profiles Hint, only OpenGL 3.3 and above + // Possible values: GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); // Forward Compatibility Hint: Only 3.3 and above! // glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Request OpenGL DEBUG context } @@ -1198,7 +1194,6 @@ int InitPlatform(void) } else if (rlGetVersion() == RL_OPENGL_ES_30) // Request OpenGL ES 3.0 context { - // TODO: It seems WebGL 2.0 context is not set despite being requested glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); @@ -1217,8 +1212,8 @@ int InitPlatform(void) // remember center for switchinging from fullscreen to window if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width)) { - // If screen width/height equal to the display, we can't calculate the window pos for toggling full-screened/windowed. - // Toggling full-screened/windowed with pos(0, 0) can cause problems in some platforms, such as X11. + // If screen width/height equal to the display, we can't calculate the window pos for toggling full-screened/windowed + // Toggling full-screened/windowed with pos(0, 0) can cause problems in some platforms, such as X11 CORE.Window.position.x = CORE.Window.display.width/4; CORE.Window.position.y = CORE.Window.display.height/4; } @@ -1714,7 +1709,7 @@ static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) CORE.Input.Touch.currentTouchState[i] = 0; } - // Update mouse position if we detect a single touch. + // Update mouse position if we detect a single touch if (CORE.Input.Touch.pointCount == 1) { CORE.Input.Mouse.currentPosition.x = CORE.Input.Touch.position[0].x; diff --git a/src/raudio.c b/src/raudio.c index 1d9edca0a..2416f0849 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -2092,9 +2092,7 @@ float GetMusicTimePlayed(Music music) int framesInFirstBuffer = music.stream.buffer->isSubBufferProcessed[0]? 0 : subBufferSize; int framesInSecondBuffer = music.stream.buffer->isSubBufferProcessed[1]? 0 : subBufferSize; int framesInBuffers = framesInFirstBuffer + framesInSecondBuffer; - if ((unsigned int)framesInBuffers > music.frameCount) { - if (!music.looping) framesInBuffers = music.frameCount; - } + if (((unsigned int)framesInBuffers > music.frameCount) && !music.looping) framesInBuffers = music.frameCount; int framesSentToMix = music.stream.buffer->frameCursorPos%subBufferSize; int framesPlayed = (framesProcessed - framesInBuffers + framesSentToMix)%(int)music.frameCount; if (framesPlayed < 0) framesPlayed += music.frameCount; @@ -2125,7 +2123,7 @@ AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, un if (deviceBitsPerSample > 4) deviceBitsPerSample = 4; deviceBitsPerSample *= AUDIO.System.device.playback.channels; - unsigned int subBufferSize = (AUDIO.Buffer.defaultSize == 0) ? (AUDIO.System.device.sampleRate/30*deviceBitsPerSample) : AUDIO.Buffer.defaultSize; + unsigned int subBufferSize = (AUDIO.Buffer.defaultSize == 0)? (AUDIO.System.device.sampleRate/30*deviceBitsPerSample) : AUDIO.Buffer.defaultSize; if (subBufferSize < periodSize) subBufferSize = periodSize; diff --git a/src/rcore.c b/src/rcore.c index be1c7a34b..8c5e0d3db 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -30,7 +30,7 @@ * - Windows (Win32, Win64) * CONFIGURATION: * #define SUPPORT_DEFAULT_FONT (default) -* Default font is loaded on window initialization to be available for the user to render simple text. +* Default font is loaded on window initialization to be available for the user to render simple text * NOTE: If enabled, uses external module functions to load default raylib font (module: text) * * #define SUPPORT_CAMERA_SYSTEM @@ -41,7 +41,7 @@ * Gestures module is included (rgestures.h) to support gestures detection: tap, hold, swipe, drag * * #define SUPPORT_MOUSE_GESTURES -* Mouse gestures are directly mapped like touches and processed by gestures system. +* Mouse gestures are directly mapped like touches and processed by gestures system * * #define SUPPORT_BUSY_WAIT_LOOP * Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used @@ -423,7 +423,7 @@ typedef enum AutomationEventType { } AutomationEventType; // Event type to config events flags -// TODO: Not used at the moment +// WARNING: Not used at the moment typedef enum { EVENT_INPUT_KEYBOARD = 0, EVENT_INPUT_MOUSE = 1, @@ -534,12 +534,10 @@ const char *TextFormat(const char *text, ...); // Formatting of text with variab // Not needed because 'rtexture.c' will automatically defined STBI_REQUIRED when any SUPPORT_FILEFORMAT_* is defined // #if !defined(STBI_REQUIRED) // #pragma message ("WARNING: "STBI_REQUIRED is not defined, that means we can't load images from clipbard" - // #endif - #endif // SUPPORT_CLIPBOARD_IMAGE // Include platform-specific submodules -#if defined(PLATFORM_DESKTOP_GLFW) +#if defined(PLATFORM_MEM) #include "platforms/rcore_desktop_glfw.c" #elif defined(PLATFORM_DESKTOP_SDL) #include "platforms/rcore_desktop_sdl.c" @@ -611,7 +609,9 @@ void InitWindow(int width, int height, const char *title) { TRACELOG(LOG_INFO, "Initializing raylib %s", RAYLIB_VERSION); -#if defined(PLATFORM_DESKTOP_GLFW) +#if defined(PLATFORM_MEM) + TRACELOG(LOG_INFO, "Platform backend: NONE (Memory Buffer)"); +#elif defined(PLATFORM_DESKTOP_GLFW) TRACELOG(LOG_INFO, "Platform backend: DESKTOP (GLFW)"); #elif defined(PLATFORM_DESKTOP_SDL) TRACELOG(LOG_INFO, "Platform backend: DESKTOP (SDL)"); @@ -1267,14 +1267,14 @@ Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode) { // After custom shader loading, we TRY to set default location names // Default shader attribute locations have been binded before linking: - // vertex position location = 0 - // vertex texcoord location = 1 - // vertex normal location = 2 - // vertex color location = 3 - // vertex tangent location = 4 - // vertex texcoord2 location = 5 - // vertex boneIds location = 6 - // vertex boneWeights location = 7 + // - vertex position location = 0 + // - vertex texcoord location = 1 + // - vertex normal location = 2 + // - vertex color location = 3 + // - vertex tangent location = 4 + // - vertex texcoord2 location = 5 + // - vertex boneIds location = 6 + // - vertex boneWeights location = 7 // NOTE: If any location is not found, loc point becomes -1 @@ -1543,8 +1543,6 @@ Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int heigh // Calculate view matrix from camera look at (and transpose it) Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); - // TODO: Why not use Vector3Transform(Vector3 v, Matrix mat)? - // Convert world position vector to quaternion Quaternion worldPos = { position.x, position.y, position.z, 1.0f }; @@ -2484,8 +2482,6 @@ bool IsFileNameValid(const char *fileName) // Check non-glyph characters if ((unsigned char)fileName[i] < 32) { valid = false; break; } - // TODO: Check trailing periods/spaces? - // Check if filename is not all periods if (fileName[i] != '.') allPeriods = false; } @@ -3210,7 +3206,7 @@ bool ExportAutomationEventList(AutomationEventList list, const char *fileName) */ // Export events as text - // TODO: Save to memory buffer and SaveFileText() + // NOTE: Save to memory buffer and SaveFileText() char *txtData = (char *)RL_CALLOC(256*list.count + 2048, sizeof(char)); // 256 characters per line plus some header int byteCount = 0; @@ -3279,7 +3275,7 @@ void PlayAutomationEvent(AutomationEvent event) #if defined(SUPPORT_AUTOMATION_EVENTS) // WARNING: When should event be played? After/before/replace PollInputEvents()? -> Up to the user! - if (!automationEventRecording) // TODO: Allow recording events while playing? + if (!automationEventRecording) { switch (event.type) { @@ -3716,7 +3712,6 @@ int GetTouchY(void) } // Get touch position XY for a touch point index (relative to screen size) -// TODO: Touch position should be scaled depending on display size and render size Vector2 GetTouchPosition(int index) { Vector2 position = { -1.0f, -1.0f }; @@ -4015,13 +4010,11 @@ static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *fi #if defined(SUPPORT_AUTOMATION_EVENTS) // Automation event recording +// Checking events in current frame and save them into currentEventList // NOTE: Recording is by default done at EndDrawing(), before PollInputEvents() static void RecordAutomationEvent(void) { - // Checking events in current frame and save them into currentEventList - // TODO: How important is the current frame? Could it be modified? - - if (currentEventList->count == currentEventList->capacity) return; // Security check + if (currentEventList->count == currentEventList->capacity) return; // Keyboard input events recording //------------------------------------------------------------------------------------- diff --git a/src/rlgl.h b/src/rlgl.h index 6cdf8a317..cf4156b7e 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -903,6 +903,7 @@ RLAPI void rlLoadDrawQuad(void); // Load and draw a quad // It seems OpenGL ES 2.0 instancing entry points are not defined on Raspberry Pi // provided headers (despite being defined in official Khronos GLES2 headers) + // TODO: Avoid raylib platform-dependant code on rlgl, it should be a completely portable library #if defined(PLATFORM_DRM) typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); @@ -2921,7 +2922,7 @@ rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements) batch.bufferCount = numBuffers; // Record buffer count batch.drawCounter = 1; // Reset draws counter - batch.currentDepth = -1.0f; // Reset depth value + batch.currentDepth = -1.0f; // Reset depth value //-------------------------------------------------------------------------------------------- #endif @@ -2982,7 +2983,8 @@ void rlDrawRenderBatch(rlRenderBatch *batch) // Update batch vertex buffers //------------------------------------------------------------------------------------------------------------ // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) - // TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (use a change detector flag?) + // TODO: If no data changed on the CPU arrays there is no need to re-upload data to GPU, + // a flag can be used to detect changes but it would imply keeping a copy buffer and memcmp() both, does it worth it? if (RLGL.State.vertexCounter > 0) { // Activate elements VAO @@ -3900,7 +3902,7 @@ void rlUnloadFramebuffer(unsigned int id) // TODO: Review warning retrieving object name in WebGL // WARNING: WebGL: INVALID_ENUM: getFramebufferAttachmentParameter: invalid parameter name - // https://registry.khronos.org/webgl/specs/latest/1.0/ + // Ref: https://registry.khronos.org/webgl/specs/latest/1.0/ glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthId); unsigned int depthIdU = (unsigned int)depthId; @@ -4427,8 +4429,6 @@ void rlSetUniform(int locIndex, const void *value, int uniformType, int count) #endif case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); - - // TODO: Support glUniform1uiv(), glUniform2uiv(), glUniform3uiv(), glUniform4uiv() } #endif } @@ -4469,7 +4469,7 @@ void rlSetUniformMatrices(int locIndex, const Matrix *matrices, int count) glUniformMatrix4fv(locIndex, count, true, (const float *)matrices); #elif defined(GRAPHICS_API_OPENGL_ES2) // WARNING: WebGL does not support Matrix transpose ("true" parameter) - // REF: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniformMatrix + // Ref: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniformMatrix glUniformMatrix4fv(locIndex, count, false, (const float *)matrices); #endif } diff --git a/src/rmodels.c b/src/rmodels.c index e3800b575..8d6802eb7 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -3845,7 +3845,6 @@ void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float // Draw a model points // WARNING: OpenGL ES 2.0 does not support point mode drawing -// TODO: gate these properly for non es 2.0 versions only void DrawModelPoints(Model model, Vector3 position, float scale, Color tint) { rlEnablePointMode(); @@ -5021,7 +5020,7 @@ static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, int *animCou animations[a].framePoses = (Transform **)RL_MALLOC(anim[a].num_frames*sizeof(Transform *)); memcpy(animations[a].name, fileDataPtr + iqmHeader->ofs_text + anim[a].name, 32); // I don't like this 32 here TRACELOG(LOG_INFO, "IQM Anim %s", animations[a].name); - // animations[a].framerate = anim.framerate; // TODO: Use animation framerate data? + //animations[a].framerate = anim.framerate; // TODO: Use animation framerate data? for (unsigned int j = 0; j < iqmHeader->num_poses; j++) { @@ -5029,7 +5028,7 @@ static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, int *animCou if (iqmHeader->num_joints > 0) memcpy(animations[a].bones[j].name, fileDataPtr + iqmHeader->ofs_text + joints[j].name, BONE_NAME_LENGTH*sizeof(char)); else - strcpy(animations[a].bones[j].name, "ANIMJOINTNAME"); // default bone name otherwise + strcpy(animations[a].bones[j].name, "ANIMJOINTNAME"); // Default bone name otherwise animations[a].bones[j].parent = poses[j].parent; } @@ -5875,8 +5874,8 @@ static Model LoadGLTF(const char *fileName) //---------------------------------------------------------------------------------------------------- // Load animation data - // REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skins - // REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skinned-mesh-attributes + // Ref: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skins + // Ref: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skinned-mesh-attributes // // LIMITATIONS: // - Only supports 1 armature per file, and skips loading it if there are multiple armatures diff --git a/src/rshapes.c b/src/rshapes.c index 2b5854f86..528a362d5 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -1169,9 +1169,9 @@ void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color co } // Draw rectangle with rounded edges -// TODO: This function should be refactored to use RL_LINES, for consistency with other Draw*Lines() void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, Color color) { + // NOTE: For line thicknes <=1.0f we use RL_LINES, otherwise wee use RL_QUADS/RL_TRIANGLES DrawRectangleRoundedLinesEx(rec, roundness, segments, 1.0f, color); } @@ -1395,7 +1395,6 @@ void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness, int segments, f { // Use LINES to draw the outline rlBegin(RL_LINES); - // Draw all the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop { @@ -1418,7 +1417,6 @@ void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness, int segments, f rlVertex2f(point[i].x, point[i].y); rlVertex2f(point[i + 1].x, point[i + 1].y); } - rlEnd(); } } diff --git a/src/rtext.c b/src/rtext.c index 009aba044..8a3961a00 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1701,7 +1701,7 @@ char *GetTextBetween(const char *text, const char *begin, const char *end) // Replace text string // REQUIRES: strstr(), strncpy(), strcpy() -// TODO: If (replacement == NULL) remove "search" text +// TODO: If (replacement == "") remove "search" text // WARNING: Allocated memory must be manually freed char *TextReplace(const char *text, const char *search, const char *replacement) { diff --git a/src/rtextures.c b/src/rtextures.c index 3e4666572..7bc5bdf4b 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -514,7 +514,7 @@ Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, i image.data = qoi_decode(fileData, dataSize, &desc, (int) fileData[12]); image.width = desc.width; image.height = desc.height; - image.format = desc.channels == 4 ? PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 : PIXELFORMAT_UNCOMPRESSED_R8G8B8; + image.format = (desc.channels == 4)? PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 : PIXELFORMAT_UNCOMPRESSED_R8G8B8; image.mipmaps = 1; } } @@ -4001,9 +4001,7 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color // [x] Consider fast path: no alpha blending required cases (src has no alpha) // [x] Consider fast path: same src/dst format with no alpha -> direct line copy // [-] GetPixelColor(): Get Vector4 instead of Color, easier for ColorAlphaBlend() - // [ ] Support f32bit channels drawing - - // TODO: Support PIXELFORMAT_UNCOMPRESSED_R32G32B32A32 and PIXELFORMAT_UNCOMPRESSED_R1616B16A16 + // [ ] TODO: Support 16bit and 32bit (float) channels drawing Color colSrc, colDst, blend; bool blendRequired = true; @@ -4201,7 +4199,7 @@ TextureCubemap LoadTextureCubemap(Image image, int layout) } /*else if (layout == CUBEMAP_LAYOUT_PANORAMA) { - // TODO: implement panorama by converting image to square faces... + // TODO: Implement panorama by converting image to square faces... // Ref: https://github.com/denivip/panorama/blob/master/panorama.cpp } */ else @@ -4227,6 +4225,7 @@ TextureCubemap LoadTextureCubemap(Image image, int layout) } // Convert image data to 6 faces in a vertical column, that's the optimum layout for loading + // NOTE: Image formatting does not work with compressed textures faces = GenImageColor(size, size*6, MAGENTA); ImageFormat(&faces, image.format); @@ -4239,8 +4238,6 @@ TextureCubemap LoadTextureCubemap(Image image, int layout) } #endif - // NOTE: Image formatting does not work with compressed textures - for (int i = 0; i < 6; i++) ImageDraw(&faces, mipmapped, faceRecs[i], (Rectangle){ 0, (float)size*i, (float)size, (float)size }, WHITE); UnloadImage(mipmapped); @@ -4309,13 +4306,11 @@ bool IsTextureValid(Texture2D texture) { bool result = false; - // TODO: Validate maximum texture size supported by GPU - if ((texture.id > 0) && // Validate OpenGL id (texture uplaoded to GPU) (texture.width > 0) && // Validate texture width (texture.height > 0) && // Validate texture height (texture.format > 0) && // Validate texture pixel format - (texture.mipmaps > 0)) result = true; // Validate texture mipmaps (at least 1 for basic mipmap level) + (texture.mipmaps > 0)) result = true; // Validate texture mipmaps (at least 1 for basic mipmap level) return result; } @@ -5412,7 +5407,7 @@ int GetPixelDataSize(int width, int height, int format) // Module Internal Functions Definition //---------------------------------------------------------------------------------- // Convert half-float (stored as unsigned short) to float -// REF: https://stackoverflow.com/questions/1659440/32-bit-to-16-bit-floating-point-conversion/60047308#60047308 +// Ref: https://stackoverflow.com/questions/1659440/32-bit-to-16-bit-floating-point-conversion/60047308#60047308 static float HalfToFloat(unsigned short x) { float result = 0.0f; From aaa893f668a9b7386e9adb3c1985024ce60fefd6 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 23 Nov 2025 22:58:10 +0100 Subject: [PATCH 168/430] Update rcore.c --- src/rcore.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 8c5e0d3db..85adf134f 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -609,9 +609,7 @@ void InitWindow(int width, int height, const char *title) { TRACELOG(LOG_INFO, "Initializing raylib %s", RAYLIB_VERSION); -#if defined(PLATFORM_MEM) - TRACELOG(LOG_INFO, "Platform backend: NONE (Memory Buffer)"); -#elif defined(PLATFORM_DESKTOP_GLFW) +#if defined(PLATFORM_DESKTOP_GLFW) TRACELOG(LOG_INFO, "Platform backend: DESKTOP (GLFW)"); #elif defined(PLATFORM_DESKTOP_SDL) TRACELOG(LOG_INFO, "Platform backend: DESKTOP (SDL)"); From 17dc2bb474228da4375b7cddab24682e79a8201e Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 23 Nov 2025 22:58:15 +0100 Subject: [PATCH 169/430] Update rcore.c --- src/rcore.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 85adf134f..377dd4eb5 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -530,10 +530,6 @@ const char *TextFormat(const char *text, ...); // Formatting of text with variab #if (!defined(SUPPORT_FILEFORMAT_PNG) || !defined(SUPPORT_FILEFORMAT_JPG)) && !defined(_WIN32) #pragma message ("WARNING: Getting image from the clipboard might not work without SUPPORT_FILEFORMAT_PNG or SUPPORT_FILEFORMAT_JPG") #endif - - // Not needed because 'rtexture.c' will automatically defined STBI_REQUIRED when any SUPPORT_FILEFORMAT_* is defined - // #if !defined(STBI_REQUIRED) - // #pragma message ("WARNING: "STBI_REQUIRED is not defined, that means we can't load images from clipbard" #endif // SUPPORT_CLIPBOARD_IMAGE // Include platform-specific submodules From 7e3d6cbfa880ac97638b8ef06235bd9a9022fdc9 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 23 Nov 2025 23:16:32 +0100 Subject: [PATCH 170/430] Update rcore.c --- src/rcore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rcore.c b/src/rcore.c index 377dd4eb5..aae5daa09 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -533,7 +533,7 @@ const char *TextFormat(const char *text, ...); // Formatting of text with variab #endif // SUPPORT_CLIPBOARD_IMAGE // Include platform-specific submodules -#if defined(PLATFORM_MEM) +#if defined(PLATFORM_DESKTOP_GLFW) #include "platforms/rcore_desktop_glfw.c" #elif defined(PLATFORM_DESKTOP_SDL) #include "platforms/rcore_desktop_sdl.c" From bd36610f9167ec9288d91d303093eaec55b55ae4 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 24 Nov 2025 15:37:28 +0100 Subject: [PATCH 171/430] Some formatting --- src/config.h | 2 +- src/external/rlsw.h | 14 +++--- src/platforms/rcore_desktop_glfw.c | 25 ++++++----- src/platforms/rcore_desktop_win32.c | 10 ++--- src/raylib.h | 8 ++-- src/rcore.c | 66 ++++++++++++++++++----------- src/rmodels.c | 2 +- src/rtextures.c | 4 +- src/utils.c | 2 +- 9 files changed, 74 insertions(+), 59 deletions(-) diff --git a/src/config.h b/src/config.h index 89b32d0fe..b749f8952 100644 --- a/src/config.h +++ b/src/config.h @@ -75,7 +75,7 @@ #define SUPPORT_CLIPBOARD_IMAGE 1 // NOTE: Clipboard image loading requires support for some image file formats -// TODO: Those defines should probably be removed from here, I prefer to let the user manage them +// TODO: Those defines should probably be removed from here, letting the user manage them #if defined(SUPPORT_CLIPBOARD_IMAGE) #ifndef SUPPORT_MODULE_RTEXTURES #define SUPPORT_MODULE_RTEXTURES 1 diff --git a/src/external/rlsw.h b/src/external/rlsw.h index d78da3ff8..025216e39 100644 --- a/src/external/rlsw.h +++ b/src/external/rlsw.h @@ -3668,9 +3668,9 @@ void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, x = sw_clampi(x, 0, w); y = sw_clampi(y, 0, h); - if (x >= w || y >= h) return; + if ((x >= w) || (y >= h)) return; - if (x == 0 && y == 0 && w == RLSW.framebuffer.width && h == RLSW.framebuffer.height) + if ((x == 0) && (y == 0) && (w == RLSW.framebuffer.width) && (h == RLSW.framebuffer.height)) { #if SW_COLOR_BUFFER_BITS == 32 if (pFormat == SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) @@ -3695,7 +3695,7 @@ void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8: sw_framebuffer_copy_to_R8G8B8(x, y, w, h, (uint8_t *)pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: sw_framebuffer_copy_to_R5G5B5A1(x, y, w, h, (uint16_t *)pixels); break; case SW_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: sw_framebuffer_copy_to_R4G4B4A4(x, y, w, h, (uint16_t *)pixels); break; - case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_framebuffer_copy_to_R8G8B8A8(x, y, w, h, (uint8_t *)pixels); break; + //case SW_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: sw_framebuffer_copy_to_R8G8B8A8(x, y, w, h, (uint8_t *)pixels); break; // Below: not implemented case SW_PIXELFORMAT_UNCOMPRESSED_R32: case SW_PIXELFORMAT_UNCOMPRESSED_R32G32B32: @@ -3703,9 +3703,7 @@ void swCopyFramebuffer(int x, int y, int w, int h, SWformat format, SWtype type, case SW_PIXELFORMAT_UNCOMPRESSED_R16: case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16: case SW_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: - default: - RLSW.errCode = SW_INVALID_ENUM; - break; + default: RLSW.errCode = SW_INVALID_ENUM; break; } } @@ -4330,7 +4328,7 @@ void swVertex2f(float x, float y) void swVertex2fv(const float *v) { const float v4[4] = { v[0], v[1], 0.0f, 1.0f }; - sw_immediate_push_vertex(v, RLSW.current.color, RLSW.current.texcoord); + sw_immediate_push_vertex(v4, RLSW.current.color, RLSW.current.texcoord); } void swVertex3i(int x, int y, int z) @@ -4348,7 +4346,7 @@ void swVertex3f(float x, float y, float z) void swVertex3fv(const float *v) { const float v4[4] = { v[0], v[1], v[2], 1.0f }; - sw_immediate_push_vertex(v, RLSW.current.color, RLSW.current.texcoord); + sw_immediate_push_vertex(v4, RLSW.current.color, RLSW.current.texcoord); } void swVertex4i(int x, int y, int z, int w) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index d7f76b9a8..a56e1c683 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -78,28 +78,27 @@ #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) #include // Required for: timespec, nanosleep(), select() - POSIX -#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND) + #if defined(_GLFW_X11) || defined(_GLFW_WAYLAND) // Set appropriate expose macros based on available backends - #if defined(_GLFW_X11) - #define GLFW_EXPOSE_NATIVE_X11 - #define Font X11Font // Hack to fix 'Font' name collision + #if defined(_GLFW_X11) + #define GLFW_EXPOSE_NATIVE_X11 + #define Font X11Font // Hack to fix 'Font' name collision // The definition and references to the X11 Font type will be replaced by 'X11Font' // Works as long as the current file consistently references any X11 Font as X11Font // Since it is never referenced (as of writing), this does not pose an issue - #endif + #endif - #if defined(_GLFW_WAYLAND) - #define GLFW_EXPOSE_NATIVE_WAYLAND - #endif + #if defined(_GLFW_WAYLAND) + #define GLFW_EXPOSE_NATIVE_WAYLAND + #endif - #include "GLFW/glfw3native.h" // Include native header only once, regardless of how many backends are defined + #include "GLFW/glfw3native.h" // Include native header only once, regardless of how many backends are defined // Required for: glfwGetX11Window() and glfwGetWaylandWindow() - - #if defined(_GLFW_X11) // Clean up X11-specific hacks - #undef Font // Revert hack and allow normal raylib Font usage + #if defined(_GLFW_X11) // Clean up X11-specific hacks + #undef Font // Revert hack and allow normal raylib Font usage + #endif #endif #endif -#endif #if defined(__APPLE__) #include // Required for: usleep() diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c index b3cbd515d..8a4050332 100644 --- a/src/platforms/rcore_desktop_win32.c +++ b/src/platforms/rcore_desktop_win32.c @@ -262,9 +262,9 @@ static bool DecoratedFromStyle(DWORD style) // Get window style from required flags static DWORD MakeWindowStyle(unsigned flags) { - // We don't need this since we don't have any child windows, but I guess - // it improves efficiency, plus, windows adds this flag automatically anyway - // so it keeps our flags in sync with the OS + // Flag is not needed because there are no child windows, + // but supposedly it improves efficiency, plus, windows adds this + // flag automatically anyway so it keeps flags in sync with the OS DWORD style = WS_CLIPSIBLINGS; style |= (flags & FLAG_WINDOW_HIDDEN)? 0 : WS_VISIBLE; @@ -1230,7 +1230,7 @@ void SwapScreenBuffer(void) // Get elapsed time measure in seconds double GetTime(void) { - LARGE_INTEGER now; + LARGE_INTEGER now = 0; QueryPerformanceCounter(&now); return (double)(now.QuadPart - CORE.Time.base)/(double)platform.timerFrequency.QuadPart; } @@ -1987,7 +1987,7 @@ static void HandleKey(WPARAM wparam, LPARAM lparam, char state) { CORE.Input.Keyboard.currentKeyState[key] = state; - if ((key == KEY_ESCAPE) && (state == 1)) CORE.Window.shouldClose = 1; + if ((key == KEY_ESCAPE) && (state == 1)) CORE.Window.shouldClose = true; } else TRACELOG(LOG_WARNING, "INPUT: Unknown (or currently unhandled) virtual keycode %d (0x%x)", wparam, wparam); diff --git a/src/raylib.h b/src/raylib.h index 67279753a..ece2e6aab 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -99,13 +99,13 @@ #define __declspec(x) __attribute__((x)) #endif #if defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) + #define RLAPI __declspec(dllexport) // Building the library as a Win32 shared library (.dll) #elif defined(USE_LIBTYPE_SHARED) - #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) + #define RLAPI __declspec(dllimport) // Using the library as a Win32 shared library (.dll) #endif #else #if defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __attribute__((visibility("default"))) // We are building as a Unix shared library (.so/.dylib) + #define RLAPI __attribute__((visibility("default"))) // Building as a Unix shared library (.so/.dylib) #endif #endif @@ -157,7 +157,7 @@ #error "C++11 or later is required. Add -std=c++11" #endif -// NOTE: We set some defines with some data types declared by raylib +// NOTE: Set some defines with some data types declared by raylib // Other modules (raymath, rlgl) also require some of those types, so, // to be able to use those other modules as standalone (not depending on raylib) // this defines are very useful for internal check and avoid type (re)definitions diff --git a/src/rcore.c b/src/rcore.c index aae5daa09..61916284c 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -28,6 +28,8 @@ * - Android (ARM, ARM64) * > PLATFORM_DESKTOP_WIN32 (Native Win32): * - Windows (Win32, Win64) +* > PLATFORM_MEMORY +* - Memory framebuffer output, using software renderer, no OS required * CONFIGURATION: * #define SUPPORT_DEFAULT_FONT (default) * Default font is loaded on window initialization to be available for the user to render simple text @@ -92,12 +94,12 @@ //---------------------------------------------------------------------------------- #if (defined(__linux__) || defined(PLATFORM_WEB) || defined(PLATFORM_WEB_RGFW)) && (_XOPEN_SOURCE < 500) #undef _XOPEN_SOURCE - #define _XOPEN_SOURCE 500 // Required for: readlink if compiled with c99 without gnu ext. + #define _XOPEN_SOURCE 500 // Required for: readlink if compiled with c99 without GNU extensions #endif #if (defined(__linux__) || defined(PLATFORM_WEB) || defined(PLATFORM_WEB_RGFW)) && (_POSIX_C_SOURCE < 199309L) #undef _POSIX_C_SOURCE - #define _POSIX_C_SOURCE 199309L // Required for: CLOCK_MONOTONIC if compiled with c99 without gnu ext. + #define _POSIX_C_SOURCE 199309L // Required for: CLOCK_MONOTONIC if compiled with c99 without GNU extensions #endif #include "raylib.h" // Declares module functions @@ -115,6 +117,9 @@ #include // Required for: time() [Used in InitTimer()] #include // Required for: tan() [Used in BeginMode3D()], atan2f() [Used in LoadVrStereoConfig()] +#if defined(PLATFORM_MEMORY) + #define SW_GL_FRAMEBUFFER_COPY_BGRA false +#endif #define RLGL_IMPLEMENTATION #include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 3.3+ or ES2 @@ -155,18 +160,18 @@ #define MAX_PATH 260 #endif -struct HINSTANCE__; -#if defined(__cplusplus) -extern "C" { -#endif -__declspec(dllimport) unsigned long __stdcall GetModuleFileNameA(struct HINSTANCE__ *hModule, char *lpFilename, unsigned long nSize); -__declspec(dllimport) unsigned long __stdcall GetModuleFileNameW(struct HINSTANCE__ *hModule, wchar_t *lpFilename, unsigned long nSize); -__declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); -__declspec(dllimport) unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod); -__declspec(dllimport) unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); -#if defined(__cplusplus) -} -#endif + struct HINSTANCE__; + #if defined(__cplusplus) + extern "C" { + #endif + __declspec(dllimport) unsigned long __stdcall GetModuleFileNameA(struct HINSTANCE__ *hModule, char *lpFilename, unsigned long nSize); + __declspec(dllimport) unsigned long __stdcall GetModuleFileNameW(struct HINSTANCE__ *hModule, wchar_t *lpFilename, unsigned long nSize); + __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + __declspec(dllimport) unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod); + __declspec(dllimport) unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); + #if defined(__cplusplus) + } + #endif #elif defined(__linux__) #include #elif defined(__FreeBSD__) @@ -314,7 +319,8 @@ typedef struct CoreData { char currentKeyState[MAX_KEYBOARD_KEYS]; // Registers current frame key state char previousKeyState[MAX_KEYBOARD_KEYS]; // Registers previous frame key state - // NOTE: Since key press logic involves comparing prev vs cur key state, we need to handle key repeats specially + // NOTE: Since key press logic involves comparing previous vs currrent key state, + // key repeats needs to be handled specially char keyRepeatInFrame[MAX_KEYBOARD_KEYS]; // Registers key repeats for current frame int keyPressedQueue[MAX_KEY_PRESSED_QUEUE]; // Input keys queue @@ -547,6 +553,8 @@ const char *TextFormat(const char *text, ...); // Formatting of text with variab #include "platforms/rcore_drm.c" #elif defined(PLATFORM_ANDROID) #include "platforms/rcore_android.c" +#elif defined(PLATFORM_MEMORY) + #include "platforms/rcore_memory.c" #else // TODO: Include your custom platform backend! // i.e software rendering backend or console backend! @@ -621,6 +629,8 @@ void InitWindow(int width, int height, const char *title) TRACELOG(LOG_INFO, "Platform backend: NATIVE DRM"); #elif defined(PLATFORM_ANDROID) TRACELOG(LOG_INFO, "Platform backend: ANDROID"); +#elif defined(PLATFORM_MEMORY) + TRACELOG(LOG_INFO, "Platform backend: MEMORY (No OS)"); #else // TODO: Include your custom platform backend! // i.e software rendering backend or console backend! @@ -2233,13 +2243,15 @@ const char *GetApplicationDirectory(void) #if defined(_WIN32) int len = 0; -#if defined(UNICODE) + + #if defined(UNICODE) unsigned short widePath[MAX_PATH]; len = GetModuleFileNameW(NULL, (wchar_t *)widePath, MAX_PATH); len = WideCharToMultiByte(0, 0, (wchar_t *)widePath, len, appDir, MAX_PATH, NULL, NULL); -#else + #else len = GetModuleFileNameA(NULL, appDir, MAX_PATH); -#endif + #endif + if (len > 0) { for (int i = len; i >= 0; --i) @@ -2256,8 +2268,9 @@ const char *GetApplicationDirectory(void) appDir[0] = '.'; appDir[1] = '\\'; } - + #elif defined(__linux__) + unsigned int size = sizeof(appDir); ssize_t len = readlink("/proc/self/exe", appDir, size); @@ -2277,7 +2290,9 @@ const char *GetApplicationDirectory(void) appDir[0] = '.'; appDir[1] = '/'; } + #elif defined(__APPLE__) + uint32_t size = sizeof(appDir); if (_NSGetExecutablePath(appDir, &size) == 0) @@ -2297,8 +2312,11 @@ const char *GetApplicationDirectory(void) appDir[0] = '.'; appDir[1] = '/'; } + #elif defined(__FreeBSD__) - size_t size = sizeof(appDir); + + size_t size = sizeof(appD + ir); int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; if (sysctl(mib, 4, appDir, &size, NULL, 0) == 0) @@ -2318,7 +2336,6 @@ const char *GetApplicationDirectory(void) appDir[0] = '.'; appDir[1] = '/'; } - #endif return appDir; @@ -3748,20 +3765,20 @@ void InitTimer(void) // High resolutions can also prevent the CPU power management system from entering power-saving modes // Setting a higher resolution does not improve the accuracy of the high-resolution performance counter #if defined(_WIN32) && defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) && !defined(PLATFORM_DESKTOP_SDL) - timeBeginPeriod(1); // Setup high-resolution timer to 1ms (granularity of 1-2 ms) + timeBeginPeriod(1); // Setup high-resolution timer to 1ms (granularity of 1-2 ms) #endif #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) struct timespec now = { 0 }; - if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success + if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success { CORE.Time.base = (unsigned long long int)now.tv_sec*1000000000LLU + (unsigned long long int)now.tv_nsec; } else TRACELOG(LOG_WARNING, "TIMER: Hi-resolution timer not available"); #endif - CORE.Time.previous = GetTime(); // Get time as double + CORE.Time.previous = GetTime(); // Get time as double } // Set viewport for a provided width and height @@ -3887,6 +3904,7 @@ static void ScanDirectoryFiles(const char *basePath, FilePathList *files, const if ((strcmp(dp->d_name, ".") != 0) && (strcmp(dp->d_name, "..") != 0)) { + // Construct new path from our base path #if defined(_WIN32) int pathLength = snprintf(path, MAX_FILEPATH_LENGTH - 1, "%s\\%s", basePath, dp->d_name); #else diff --git a/src/rmodels.c b/src/rmodels.c index 8d6802eb7..fad6ae78b 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -5018,7 +5018,7 @@ static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, int *animCou animations[a].boneCount = iqmHeader->num_poses; animations[a].bones = (BoneInfo *)RL_MALLOC(iqmHeader->num_poses*sizeof(BoneInfo)); animations[a].framePoses = (Transform **)RL_MALLOC(anim[a].num_frames*sizeof(Transform *)); - memcpy(animations[a].name, fileDataPtr + iqmHeader->ofs_text + anim[a].name, 32); // I don't like this 32 here + memcpy(animations[a].name, fileDataPtr + iqmHeader->ofs_text + anim[a].name, 32); TRACELOG(LOG_INFO, "IQM Anim %s", animations[a].name); //animations[a].framerate = anim.framerate; // TODO: Use animation framerate data? diff --git a/src/rtextures.c b/src/rtextures.c index 7bc5bdf4b..17065822a 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -1099,7 +1099,7 @@ Image GenImageCellular(int width, int height, int tileSize) } } - // I made this up, but it seems to give good results at all tile sizes + // This approach seems to give good results at all tile sizes int intensity = (int)(minDistance*256.0f/tileSize); if (intensity > 255) intensity = 255; @@ -4600,7 +4600,7 @@ void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 // NOTE: Vertex position can be transformed using matrices // but the process is way more costly than just calculating // the vertex positions manually, like done above - // I leave here the old implementation for educational purposes, + // Old implementation is left here for educational purposes, // just in case someone wants to do some performance test /* rlSetTexture(texture.id); diff --git a/src/utils.c b/src/utils.c index 123c7b0b9..892f96cf4 100644 --- a/src/utils.c +++ b/src/utils.c @@ -451,7 +451,7 @@ FILE *android_fopen(const char *fileName, const char *mode) { if (mode[0] == 'w') { - // fopen() is mapped to android_fopen() that only grants read access to + // NOTE: fopen() is mapped to android_fopen() that only grants read access to // assets directory through AAssetManager but we want to also be able to // write data when required using the standard stdio FILE access functions // Ref: https://stackoverflow.com/questions/11294487/android-writing-saving-files-from-native-code-only From fc8049a039b5c4b2bcf3a1bbc77e3f8527638e9f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:37:49 +0000 Subject: [PATCH 172/430] rlparser: update raylib_api.* by CI --- tools/rlparser/output/raylib_api.json | 2 +- tools/rlparser/output/raylib_api.lua | 2 +- tools/rlparser/output/raylib_api.txt | 2 +- tools/rlparser/output/raylib_api.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/rlparser/output/raylib_api.json b/tools/rlparser/output/raylib_api.json index e5815854c..b3d02a928 100644 --- a/tools/rlparser/output/raylib_api.json +++ b/tools/rlparser/output/raylib_api.json @@ -40,7 +40,7 @@ "name": "RLAPI", "type": "UNKNOWN", "value": "__declspec(dllexport)", - "description": "We are building the library as a Win32 shared library (.dll)" + "description": "Building the library as a Win32 shared library (.dll)" }, { "name": "PI", diff --git a/tools/rlparser/output/raylib_api.lua b/tools/rlparser/output/raylib_api.lua index eb3e6567b..e680a5acf 100644 --- a/tools/rlparser/output/raylib_api.lua +++ b/tools/rlparser/output/raylib_api.lua @@ -40,7 +40,7 @@ return { name = "RLAPI", type = "UNKNOWN", value = "__declspec(dllexport)", - description = "We are building the library as a Win32 shared library (.dll)" + description = "Building the library as a Win32 shared library (.dll)" }, { name = "PI", diff --git a/tools/rlparser/output/raylib_api.txt b/tools/rlparser/output/raylib_api.txt index 76d223d97..e2edb8f3f 100644 --- a/tools/rlparser/output/raylib_api.txt +++ b/tools/rlparser/output/raylib_api.txt @@ -35,7 +35,7 @@ Define 007: RLAPI Name: RLAPI Type: UNKNOWN Value: __declspec(dllexport) - Description: We are building the library as a Win32 shared library (.dll) + Description: Building the library as a Win32 shared library (.dll) Define 008: PI Name: PI Type: FLOAT diff --git a/tools/rlparser/output/raylib_api.xml b/tools/rlparser/output/raylib_api.xml index 512c4c6df..3d1892c7c 100644 --- a/tools/rlparser/output/raylib_api.xml +++ b/tools/rlparser/output/raylib_api.xml @@ -7,7 +7,7 @@ - + From 47a8b554bce936596e910aaeecf3d524c83ba97c Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 24 Nov 2025 15:38:58 +0100 Subject: [PATCH 173/430] **NEW**: `PLATFORM_MEMORY` backend New platform backend for software rendering directly on RAM memory buffer --- projects/VS2022/raylib/raylib.vcxproj | 14 + projects/VS2022/raylib/raylib.vcxproj.filters | 3 + src/platforms/rcore_memory.c | 595 ++++++++++++++++++ src/rlgl.h | 9 +- 4 files changed, 617 insertions(+), 4 deletions(-) create mode 100644 src/platforms/rcore_memory.c diff --git a/projects/VS2022/raylib/raylib.vcxproj b/projects/VS2022/raylib/raylib.vcxproj index cf254761e..3a7082d77 100644 --- a/projects/VS2022/raylib/raylib.vcxproj +++ b/projects/VS2022/raylib/raylib.vcxproj @@ -533,6 +533,20 @@ true true + + true + true + true + true + true + true + true + true + true + true + true + true + true true diff --git a/projects/VS2022/raylib/raylib.vcxproj.filters b/projects/VS2022/raylib/raylib.vcxproj.filters index 4588f0521..33030fc9c 100644 --- a/projects/VS2022/raylib/raylib.vcxproj.filters +++ b/projects/VS2022/raylib/raylib.vcxproj.filters @@ -49,6 +49,9 @@ Source Files\Platform Files + + Source Files\Platform Files + diff --git a/src/platforms/rcore_memory.c b/src/platforms/rcore_memory.c new file mode 100644 index 000000000..e49159a85 --- /dev/null +++ b/src/platforms/rcore_memory.c @@ -0,0 +1,595 @@ +/********************************************************************************************** +* +* rcore_memory - Functions to manage window, graphics device and inputs +* +* PLATFORM: MEMORY (No OS) +* - Memory framebuffer output (no os) +* +* LIMITATIONS: +* - Software renderer (rlsw) +* - No input system +* +* POSSIBLE IMPROVEMENTS: +* - Improvement 01 +* - Improvement 02 +* +* 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- +* +* DEPENDENCIES: +* - rlsw: Software renderer +* - gestures: Gestures system for touch-ready devices (or simulated from mouse inputs) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2025 Ramon Santamaria (@raysan5) and contributors +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#if defined(_WIN32) + #include // Required for: kbhit() +#else + // Provide kbhit() function in non-Windows platforms + #include + #include + #include +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Platform-specific required data for timming (Win32) +#if defined(_WIN32) +typedef struct _LARGE_INTEGER { int64_t QuadPart; } LARGE_INTEGER; +__declspec(dllimport) int __stdcall QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount); +__declspec(dllimport) int __stdcall QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency); +#endif + +typedef struct { + unsigned int *pixels; // Pointer to pixel data buffer (RGBA8888 format) +#if defined(_WIN32) + LARGE_INTEGER timerFrequency; +#endif +} PlatformData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +extern CoreData CORE; // Global CORE state context + +static PlatformData platform = { 0 }; // Platform specific data + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +int InitPlatform(void); // Initialize platform (graphics, inputs and more) +bool InitGraphicsDevice(void); // Initialize graphics device + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +// NOTE: Functions declaration is provided by raylib.h + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +#if !defined(_WIN32) +static int kbhit(void); // Check if a key has been pressed +static char getch(void) { return getchar(); } // Get pressed character +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Window and Graphics Device +//---------------------------------------------------------------------------------- + +// Check if application should close +bool WindowShouldClose(void) +{ + if (CORE.Window.ready) return CORE.Window.shouldClose; + else return true; +} + +// Toggle fullscreen mode +void ToggleFullscreen(void) +{ + TRACELOG(LOG_WARNING, "ToggleFullscreen() not available on target platform"); +} + +// Toggle borderless windowed mode +void ToggleBorderlessWindowed(void) +{ + TRACELOG(LOG_WARNING, "ToggleBorderlessWindowed() not available on target platform"); +} + +// Set window state: maximized, if resizable +void MaximizeWindow(void) +{ + TRACELOG(LOG_WARNING, "MaximizeWindow() not available on target platform"); +} + +// Set window state: minimized +void MinimizeWindow(void) +{ + TRACELOG(LOG_WARNING, "MinimizeWindow() not available on target platform"); +} + +// Restore window from being minimized/maximized +void RestoreWindow(void) +{ + TRACELOG(LOG_WARNING, "RestoreWindow() not available on target platform"); +} + +// Set window configuration state using flags +void SetWindowState(unsigned int flags) +{ + TRACELOG(LOG_WARNING, "SetWindowState() not available on target platform"); +} + +// Clear window configuration state flags +void ClearWindowState(unsigned int flags) +{ + TRACELOG(LOG_WARNING, "ClearWindowState() not available on target platform"); +} + +// Set icon for window +void SetWindowIcon(Image image) +{ + TRACELOG(LOG_WARNING, "SetWindowIcon() not available on target platform"); +} + +// Set icon for window +void SetWindowIcons(Image *images, int count) +{ + TRACELOG(LOG_WARNING, "SetWindowIcons() not available on target platform"); +} + +// Set title for window +void SetWindowTitle(const char *title) +{ + CORE.Window.title = title; +} + +// Set window position on screen (windowed mode) +void SetWindowPosition(int x, int y) +{ + TRACELOG(LOG_WARNING, "SetWindowPosition() not available on target platform"); +} + +// Set monitor for the current window +void SetWindowMonitor(int monitor) +{ + TRACELOG(LOG_WARNING, "SetWindowMonitor() not available on target platform"); +} + +// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMinSize(int width, int height) +{ + CORE.Window.screenMin.width = width; + CORE.Window.screenMin.height = height; +} + +// Set window maximum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMaxSize(int width, int height) +{ + CORE.Window.screenMax.width = width; + CORE.Window.screenMax.height = height; +} + +// Set window dimensions +void SetWindowSize(int width, int height) +{ + TRACELOG(LOG_WARNING, "SetWindowSize() not available on target platform"); +} + +// Set window opacity, value opacity is between 0.0 and 1.0 +void SetWindowOpacity(float opacity) +{ + TRACELOG(LOG_WARNING, "SetWindowOpacity() not available on target platform"); +} + +// Set window focused +void SetWindowFocused(void) +{ + TRACELOG(LOG_WARNING, "SetWindowFocused() not available on target platform"); +} + +// Get native window handle +void *GetWindowHandle(void) +{ + TRACELOG(LOG_WARNING, "GetWindowHandle() not implemented on target platform"); + return NULL; +} + +// Get number of monitors +int GetMonitorCount(void) +{ + TRACELOG(LOG_WARNING, "GetMonitorCount() not implemented on target platform"); + return 1; +} + +// Get current monitor where window is placed +int GetCurrentMonitor(void) +{ + TRACELOG(LOG_WARNING, "GetCurrentMonitor() not implemented on target platform"); + return 0; +} + +// Get selected monitor position +Vector2 GetMonitorPosition(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPosition() not implemented on target platform"); + return (Vector2){ 0, 0 }; +} + +// Get selected monitor width (currently used by monitor) +int GetMonitorWidth(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorWidth() not implemented on target platform"); + return 0; +} + +// Get selected monitor height (currently used by monitor) +int GetMonitorHeight(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorHeight() not implemented on target platform"); + return 0; +} + +// Get selected monitor physical width in millimetres +int GetMonitorPhysicalWidth(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth() not implemented on target platform"); + return 0; +} + +// Get selected monitor physical height in millimetres +int GetMonitorPhysicalHeight(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight() not implemented on target platform"); + return 0; +} + +// Get selected monitor refresh rate +int GetMonitorRefreshRate(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorRefreshRate() not implemented on target platform"); + return 0; +} + +// Get the human-readable, UTF-8 encoded name of the selected monitor +const char *GetMonitorName(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorName() not implemented on target platform"); + return ""; +} + +// Get window position XY on monitor +Vector2 GetWindowPosition(void) +{ + TRACELOG(LOG_WARNING, "GetWindowPosition() not implemented on target platform"); + return (Vector2){ 0, 0 }; +} + +// Get window scale DPI factor for current monitor +Vector2 GetWindowScaleDPI(void) +{ + TRACELOG(LOG_WARNING, "GetWindowScaleDPI() not implemented on target platform"); + return (Vector2){ 1.0f, 1.0f }; +} + +// Set clipboard text content +void SetClipboardText(const char *text) +{ + TRACELOG(LOG_WARNING, "SetClipboardText() not implemented on target platform"); +} + +// Get clipboard text content +// NOTE: returned string is allocated and freed by GLFW +const char *GetClipboardText(void) +{ + TRACELOG(LOG_WARNING, "GetClipboardText() not implemented on target platform"); + return NULL; +} + +// Get clipboard image +Image GetClipboardImage(void) +{ + Image image = { 0 }; + + TRACELOG(LOG_WARNING, "GetClipboardImage() not implemented on target platform"); + + return image; +} + +// Show mouse cursor +void ShowCursor(void) +{ + CORE.Input.Mouse.cursorHidden = false; +} + +// Hides mouse cursor +void HideCursor(void) +{ + CORE.Input.Mouse.cursorHidden = true; +} + +// Enables cursor (unlock cursor) +void EnableCursor(void) +{ + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + CORE.Input.Mouse.cursorHidden = false; +} + +// Disables cursor (lock cursor) +void DisableCursor(void) +{ + // Set cursor position in the middle + SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + CORE.Input.Mouse.cursorHidden = true; +} + +// Swap back buffer with front buffer (screen drawing) +void SwapScreenBuffer(void) +{ + // Update framebuffer + rlCopyFramebuffer(0, 0, CORE.Window.render.width, CORE.Window.render.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, platform.pixels); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Misc +//---------------------------------------------------------------------------------- + +// Get elapsed time measure in seconds since InitTimer() +double GetTime(void) +{ + double time = 0.0; +#if defined(_WIN32) + LARGE_INTEGER now = { 0 }; + QueryPerformanceCounter(&now); + return (double)(now.QuadPart - CORE.Time.base)/(double)platform.timerFrequency.QuadPart; +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) + double time = 0.0; + struct timespec ts = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &ts); + unsigned long long int nanoSeconds = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; + time = (double)(nanoSeconds - CORE.Time.base)*1e-9; // Elapsed time since InitTimer() +#endif + return time; +} + +// Open URL with default system browser (if available) +// NOTE: This function is only safe to use if you control the URL given. +// A user could craft a malicious string performing another action. +// Only call this function yourself not with user input or make sure to check the string yourself. +// Ref: https://github.com/raysan5/raylib/issues/686 +void OpenURL(const char *url) +{ + // Security check to (partially) avoid malicious code on target platform + if (strchr(url, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'] character"); + else + { + char *cmd = (char *)RL_CALLOC(strlen(url) + 32, sizeof(char)); + sprintf(cmd, "explorer \"%s\"", url); + int result = system(cmd); + if (result == -1) TRACELOG(LOG_WARNING, "OpenURL() child process could not be created"); + RL_FREE(cmd); + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Inputs +//---------------------------------------------------------------------------------- + +// Set internal gamepad mappings +int SetGamepadMappings(const char *mappings) +{ + TRACELOG(LOG_WARNING, "SetGamepadMappings() not implemented on target platform"); + return 0; +} + +// Set gamepad vibration +void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration) +{ + TRACELOG(LOG_WARNING, "SetGamepadVibration() not implemented on target platform"); +} + +// Set mouse position XY +void SetMousePosition(int x, int y) +{ + CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; + CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; +} + +// Set mouse cursor +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) +{ +#if defined(SUPPORT_GESTURES_SYSTEM) + // NOTE: Gestures update must be called every frame to reset gestures correctly + // because ProcessGestureEvent() is just called on an event, not every frame + UpdateGestures(); +#endif + + // Reset keys/chars pressed registered + CORE.Input.Keyboard.keyPressedQueueCount = 0; + CORE.Input.Keyboard.charPressedQueueCount = 0; + + // Reset key repeats + for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + + // Reset last gamepad button/axis registered state + CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN + //CORE.Input.Gamepad.axisCount = 0; + + // Register previous touch states + for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; + + // Reset touch positions + // TODO: It resets on target platform the mouse position and not filled again until a move-event, + // so, if mouse is not moved it returns a (0, 0) position... this behaviour should be reviewed! + //for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 }; + + // Register previous keys states + // NOTE: Android supports up to 260 keys + for (int i = 0; i < 260; i++) + { + CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; + CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; + } + + // TODO: Poll input events for current platform + + // Check for key pressed to exit + if (kbhit()) + { + int key = getch(); + if (key == 27) CORE.Window.shouldClose = true; // KEY_SCAPE + } +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- + +// Initialize platform: graphics, inputs and more +int InitPlatform(void) +{ + // Memory framebuffer can only work with software renderer + if (rlGetVersion() != RL_OPENGL_11_SOFTWARE) + { + TRACELOG(LOG_WARNING, "DISPLAY: Memory platform requires software renderer (GRAPHICS_API_OPENGL_11_SOFTWARE)"); + TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphics device"); + return -1; + } + else + { + // Load memory framebuffer with desired screen size + platform.pixels = (unsigned int *)RL_CALLOC(CORE.Window.screen.width*CORE.Window.screen.height, sizeof(int)); + } + //---------------------------------------------------------------------------- + + // If everything work as expected, we can continue + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + CORE.Window.currentFbo.width = CORE.Window.render.width; + CORE.Window.currentFbo.height = CORE.Window.render.height; + + TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); + TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + + CORE.Window.ready = true; + + // TODO: Load OpenGL extensions + // NOTE: GL procedures address loader is required to load extensions + //---------------------------------------------------------------------------- + // ... + //---------------------------------------------------------------------------- + + // TODO: Initialize input events system + // It could imply keyboard, mouse, gamepad, touch... + // Depending on the platform libraries/SDK it could use a callback mechanism + // For system events and inputs evens polling on a per-frame basis, use PollInputEvents() + //---------------------------------------------------------------------------- + // ... + //---------------------------------------------------------------------------- + + // Initialize timing system + //---------------------------------------------------------------------------- +#if defined(_WIN32) + LARGE_INTEGER time = { 0 }; + QueryPerformanceCounter(&time); + QueryPerformanceFrequency(&platform.timerFrequency); + CORE.Time.base = time.QuadPart; +#endif + InitTimer(); + //---------------------------------------------------------------------------- + + // Initialize storage system + //---------------------------------------------------------------------------- + CORE.Storage.basePath = GetWorkingDirectory(); + //---------------------------------------------------------------------------- + + TRACELOG(LOG_INFO, "PLATFORM: MEMORY: Initialized successfully"); + + return 0; +} + +// Close platform +void ClosePlatform(void) +{ + RL_FREE(platform.pixels); +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- +#if !defined(_WIN32) +// Check if a key has been pressed +static int kbhit(void) +{ + struct termios oldt = { 0 }; + struct termios newt = { 0 }; + int ch = 0; + int oldf = 0; + + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + oldf = fcntl(STDIN_FILENO, F_GETFL, 0); + fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK); + + ch = getchar(); + + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + fcntl(STDIN_FILENO, F_SETFL, oldf); + + if (ch != EOF) + { + ungetc(ch, stdin); + return 1; + } + + return 0; +} +#endif + +// EOF diff --git a/src/rlgl.h b/src/rlgl.h index cf4156b7e..6884ad183 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -776,10 +776,9 @@ RLAPI unsigned int rlLoadFramebuffer(void); // Loa RLAPI void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel); // Attach texture/renderbuffer to a framebuffer RLAPI bool rlFramebufferComplete(unsigned int id); // Verify framebuffer is complete RLAPI void rlUnloadFramebuffer(unsigned int id); // Delete framebuffer from GPU -#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) +// WARNING: Copy and resize framebuffer functionality only defined for software backend RLAPI void rlCopyFramebuffer(int x, int y, int width, int height, int format, void *pixels); // Copy framebuffer pixel data to internal buffer RLAPI void rlResizeFramebuffer(int width, int height); // Resize internal framebuffer -#endif // Shaders management RLAPI unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode); // Load shader from code strings @@ -3750,21 +3749,23 @@ void *rlReadTexturePixels(unsigned int id, int width, int height, int format) return pixels; } -#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) // Copy framebuffer pixel data to internal buffer void rlCopyFramebuffer(int x, int y, int width, int height, int format, void *pixels) { +#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) unsigned int glInternalFormat, glFormat, glType; rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); // Get OpenGL texture format swCopyFramebuffer(x, y, width, height, glFormat, glType, pixels); +#endif } // Resize internal framebuffer void rlResizeFramebuffer(int width, int height) { +#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE) swResizeFramebuffer(width, height); -} #endif +} // Read screen pixel data (color buffer) unsigned char *rlReadScreenPixels(int width, int height) From 80ed6eadb828fcb13d936c602acfb782d0b9df3f Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 25 Nov 2025 12:15:14 +0100 Subject: [PATCH 174/430] REXM: RENAME: `audio_fft_spectrum_visualizer` -> `audio_spectrum_visualizer` --- examples/Makefile | 2 +- examples/Makefile.Web | 11 ++++++----- examples/README.md | 4 ++-- ...rum_visualizer.c => audio_spectrum_visualizer.c} | 4 ++-- ...visualizer.png => audio_spectrum_visualizer.png} | Bin examples/examples_list.txt | 2 +- ...er.vcxproj => audio_spectrum_visualizer.vcxproj} | 6 +++--- projects/VS2022/raylib.sln | 2 +- tools/rexm/reports/examples_validation.md | 2 +- 9 files changed, 17 insertions(+), 16 deletions(-) rename examples/audio/{audio_fft_spectrum_visualizer.c => audio_spectrum_visualizer.c} (99%) rename examples/audio/{audio_fft_spectrum_visualizer.png => audio_spectrum_visualizer.png} (100%) rename projects/VS2022/examples/{audio_fft_spectrum_visualizer.vcxproj => audio_spectrum_visualizer.vcxproj} (99%) diff --git a/examples/Makefile b/examples/Makefile index b2feec0db..72df8571a 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -708,7 +708,6 @@ SHADERS = \ shaders/shaders_vertex_displacement AUDIO = \ - audio/audio_fft_spectrum_visualizer \ audio/audio_mixed_processor \ audio/audio_module_playing \ audio/audio_music_stream \ @@ -716,6 +715,7 @@ AUDIO = \ audio/audio_sound_loading \ audio/audio_sound_multi \ audio/audio_sound_positioning \ + audio/audio_spectrum_visualizer \ audio/audio_stream_effects OTHERS = \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 35024cbd5..431b2cad9 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -696,7 +696,6 @@ SHADERS = \ shaders/shaders_vertex_displacement AUDIO = \ - audio/audio_fft_spectrum_visualizer \ audio/audio_mixed_processor \ audio/audio_module_playing \ audio/audio_music_stream \ @@ -704,6 +703,7 @@ AUDIO = \ audio/audio_sound_loading \ audio/audio_sound_multi \ audio/audio_sound_positioning \ + audio/audio_spectrum_visualizer \ audio/audio_stream_effects # Default target entry @@ -1471,10 +1471,6 @@ shaders/shaders_vertex_displacement: shaders/shaders_vertex_displacement.c --preload-file shaders/resources/shaders/glsl100/vertex_displacement.fs@resources/shaders/glsl100/vertex_displacement.fs # Compile AUDIO examples -audio/audio_fft_spectrum_visualizer: audio/audio_fft_spectrum_visualizer.c - $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ - --preload-file audio/resources/country.mp3@resources/country.mp3 - audio/audio_mixed_processor: audio/audio_mixed_processor.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file audio/resources/country.mp3@resources/country.mp3 \ @@ -1503,6 +1499,11 @@ audio/audio_sound_positioning: audio/audio_sound_positioning.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file audio/resources/coin.wav@resources/coin.wav +audio/audio_spectrum_visualizer: audio/audio_spectrum_visualizer.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ + --preload-file audio/resources/shaders/glsl100/fft.fs@resources/shaders/glsl100/fft.fs \ + --preload-file audio/resources/country.mp3@resources/country.mp3 + audio/audio_stream_effects: audio/audio_stream_effects.c $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) \ --preload-file audio/resources/country.mp3@resources/country.mp3 diff --git a/examples/README.md b/examples/README.md index 41c3c7c01..77b6ff37e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -260,7 +260,7 @@ Examples using raylib audio functionality, including sound/music loading and pla | [audio_stream_effects](audio/audio_stream_effects.c) | audio_stream_effects | ⭐⭐⭐⭐️ | 4.2 | 5.0 | [Ramon Santamaria](https://github.com/raysan5) | | [audio_sound_multi](audio/audio_sound_multi.c) | audio_sound_multi | ⭐⭐☆☆ | 5.0 | 5.0 | [Jeffery Myers](https://github.com/JeffM2501) | | [audio_sound_positioning](audio/audio_sound_positioning.c) | audio_sound_positioning | ⭐⭐☆☆ | 5.5 | 5.5 | [Le Juez Victor](https://github.com/Bigfoot71) | -| [audio_fft_spectrum_visualizer](audio/audio_fft_spectrum_visualizer.c) | audio_fft_spectrum_visualizer | ⭐⭐⭐☆ | 6.0 | 5.6-dev | [IANN](https://github.com/meisei4) | +| [audio_spectrum_visualizer](audio/audio_spectrum_visualizer.c) | audio_spectrum_visualizer | ⭐⭐⭐☆ | 6.0 | 5.6-dev | [IANN](https://github.com/meisei4) | ### category: others [6] @@ -276,4 +276,4 @@ Examples showing raylib misc functionality that does not fit in other categories | [web_basic_window](others/web_basic_window.c) | web_basic_window | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | Some example missing? As always, contributions are welcome, feel free to send new examples! -Here is an[examples template](examples_template.c) with instructions to start with! +Here is an [examples template](examples_template.c) with instructions to start with! diff --git a/examples/audio/audio_fft_spectrum_visualizer.c b/examples/audio/audio_spectrum_visualizer.c similarity index 99% rename from examples/audio/audio_fft_spectrum_visualizer.c rename to examples/audio/audio_spectrum_visualizer.c index cad683462..f5334c9cc 100644 --- a/examples/audio/audio_fft_spectrum_visualizer.c +++ b/examples/audio/audio_spectrum_visualizer.c @@ -1,6 +1,6 @@ /******************************************************************************************* * -* raylib [audio] example - fft spectrum visualizer +* raylib [audio] example - spectrum visualizer * * Example complexity rating: [★★★☆] 3/4 * @@ -78,7 +78,7 @@ int main(void) const int screenWidth = 800; const int screenHeight = 450; - InitWindow(screenWidth, screenHeight, "raylib [audio] example - fft spectrum visualizer"); + InitWindow(screenWidth, screenHeight, "raylib [audio] example - spectrum visualizer"); Image fftImage = GenImageColor(BUFFER_SIZE, TEXTURE_HEIGHT, WHITE); Texture2D fftTexture = LoadTextureFromImage(fftImage); diff --git a/examples/audio/audio_fft_spectrum_visualizer.png b/examples/audio/audio_spectrum_visualizer.png similarity index 100% rename from examples/audio/audio_fft_spectrum_visualizer.png rename to examples/audio/audio_spectrum_visualizer.png diff --git a/examples/examples_list.txt b/examples/examples_list.txt index b6223e09a..605ddf263 100644 --- a/examples/examples_list.txt +++ b/examples/examples_list.txt @@ -200,7 +200,7 @@ audio;audio_mixed_processor;★★★★;4.2;4.2;2023;2025;"hkc";@hatkidchan audio;audio_stream_effects;★★★★;4.2;5.0;2022;2025;"Ramon Santamaria";@raysan5 audio;audio_sound_multi;★★☆☆;5.0;5.0;2023;2025;"Jeffery Myers";@JeffM2501 audio;audio_sound_positioning;★★☆☆;5.5;5.5;2025;2025;"Le Juez Victor";@Bigfoot71 -audio;audio_fft_spectrum_visualizer;★★★☆;6.0;5.6-dev;2025;2025;"IANN";@meisei4 +audio;audio_spectrum_visualizer;★★★☆;6.0;5.6-dev;2025;2025;"IANN";@meisei4 others;rlgl_standalone;★★★★;1.6;4.0;2014;2025;"Ramon Santamaria";@raysan5 others;rlgl_compute_shader;★★★★;4.0;4.0;2021;2025;"Teddy Astie";@tsnake41 others;easings_testbed;★★★☆;2.5;3.0;2019;2025;"Juan Miguel López";@flashback-fx diff --git a/projects/VS2022/examples/audio_fft_spectrum_visualizer.vcxproj b/projects/VS2022/examples/audio_spectrum_visualizer.vcxproj similarity index 99% rename from projects/VS2022/examples/audio_fft_spectrum_visualizer.vcxproj rename to projects/VS2022/examples/audio_spectrum_visualizer.vcxproj index d7c6d8d3f..f8e5005fd 100644 --- a/projects/VS2022/examples/audio_fft_spectrum_visualizer.vcxproj +++ b/projects/VS2022/examples/audio_spectrum_visualizer.vcxproj @@ -53,9 +53,9 @@ {2B3CED91-973F-4936-9DD4-CC8B1C8ACC68} Win32Proj - audio_fft_spectrum_visualizer + audio_spectrum_visualizer 10.0 - audio_fft_spectrum_visualizer + audio_spectrum_visualizer @@ -553,7 +553,7 @@ - + diff --git a/projects/VS2022/raylib.sln b/projects/VS2022/raylib.sln index 4af8d0539..07068d34c 100644 --- a/projects/VS2022/raylib.sln +++ b/projects/VS2022/raylib.sln @@ -411,7 +411,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_compute_hash", "exampl EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "textures_screen_buffer", "examples\textures_screen_buffer.vcxproj", "{4E9D2828-EE83-40C8-97E0-137EEDFBAAAD}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "audio_fft_spectrum_visualizer", "examples\audio_fft_spectrum_visualizer.vcxproj", "{2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "audio_spectrum_visualizer", "examples\audio_spectrum_visualizer.vcxproj", "{2B3CED91-973F-4936-9DD4-CC8B1C8ACC68}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "models_directional_billboard", "examples\models_directional_billboard.vcxproj", "{30011884-25EE-42C9-BB15-888CAFB1AA6E}" EndProject diff --git a/tools/rexm/reports/examples_validation.md b/tools/rexm/reports/examples_validation.md index 79fdf05f9..af3befad9 100644 --- a/tools/rexm/reports/examples_validation.md +++ b/tools/rexm/reports/examples_validation.md @@ -213,7 +213,7 @@ Example elements validated: | audio_stream_effects | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | audio_sound_multi | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | audio_sound_positioning | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | -| audio_fft_spectrum_visualizer | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | +| audio_spectrum_visualizer | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | rlgl_standalone | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | rlgl_compute_shader | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | easings_testbed | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | From 3d9129e3b47bdb83c47ec77ec01e769522623206 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 25 Nov 2025 12:15:23 +0100 Subject: [PATCH 175/430] Update rexm.rc --- tools/rexm/rexm.rc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/rexm/rexm.rc b/tools/rexm/rexm.rc index ad125f796..4d3785a6a 100644 --- a/tools/rexm/rexm.rc +++ b/tools/rexm/rexm.rc @@ -6,8 +6,8 @@ PRODUCTVERSION 1,0,0,0 BEGIN BLOCK "StringFileInfo" BEGIN - //BLOCK "080904E4" // English UK - BLOCK "040904E4" // English US + //BLOCK "080904E4" // English UK + BLOCK "040904E4" // English US BEGIN VALUE "CompanyName", "Ramon Santamaria" VALUE "FileDescription", "rexm | raylib examples manager" @@ -21,7 +21,7 @@ BEGIN END BLOCK "VarFileInfo" BEGIN - //VALUE "Translation", 0x809, 1252 // English UK - VALUE "Translation", 0x409, 1252 // English US + //VALUE "Translation", 0x809, 1252 // English UK + VALUE "Translation", 0x409, 1252 // English US END END From 2b051afb29900593697437b20cd7719067a99819 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 25 Nov 2025 19:10:56 +0100 Subject: [PATCH 176/430] [examples] `shapes_kaleidoscope` rewind, forward & reset buttons (#5369) * [examples] rewind and forward lines drawing * [examples] reset button * [examples] update screenshot * [examples] applied raylib convention --- examples/shapes/shapes_kaleidoscope.c | 67 ++++++++++++++++++++---- examples/shapes/shapes_kaleidoscope.png | Bin 39307 -> 82644 bytes 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/examples/shapes/shapes_kaleidoscope.c b/examples/shapes/shapes_kaleidoscope.c index be1409a8d..119fca598 100644 --- a/examples/shapes/shapes_kaleidoscope.c +++ b/examples/shapes/shapes_kaleidoscope.c @@ -16,7 +16,10 @@ ********************************************************************************************/ #include "raylib.h" +#include +#define RAYGUI_IMPLEMENTATION +#include "raygui.h" #include "raymath.h" #define MAX_DRAW_LINES 8192 @@ -47,6 +50,9 @@ int main(void) int symmetry = 6; float angle = 360.0f/(float)symmetry; float thickness = 3.0f; + Rectangle resetButtonRec = { screenWidth - 55, 5, 50, 25 }; + Rectangle backButtonRec = { screenWidth - 55, screenHeight - 30, 25, 25 }; + Rectangle nextButtonRec = { screenWidth - 30, screenHeight - 30, 25, 25 }; Vector2 mousePos = { 0 }; Vector2 prevMousePos = { 0 }; Vector2 scaleVector = { 1.0f, -1.0f }; @@ -58,7 +64,11 @@ int main(void) camera.rotation = 0.0f; camera.zoom = 1.0f; - int lineCounter = 0; + int currentLineCounter = 0; + int totalLineCounter = 0; + int resetButtonClicked = false; + int backButtonClicked = false; + int nextButtonClicked = false; SetTargetFPS(20); //-------------------------------------------------------------------------------------- @@ -74,24 +84,47 @@ int main(void) Vector2 lineStart = Vector2Subtract(mousePos, offset); Vector2 lineEnd = Vector2Subtract(prevMousePos, offset); - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + if ( + IsMouseButtonDown(MOUSE_LEFT_BUTTON) + && (CheckCollisionPointRec(mousePos, resetButtonRec) == false) + && (CheckCollisionPointRec(mousePos, backButtonRec) == false) + && (CheckCollisionPointRec(mousePos, nextButtonRec) == false) + ) { - for (int s = 0; (s < symmetry) && (lineCounter < (MAX_DRAW_LINES - 1)); s++) + for (int s = 0; (s < symmetry) && (totalLineCounter < (MAX_DRAW_LINES - 1)); s++) { lineStart = Vector2Rotate(lineStart, angle*DEG2RAD); lineEnd = Vector2Rotate(lineEnd, angle*DEG2RAD); // Store mouse line - lines[lineCounter].start = lineStart; - lines[lineCounter].end = lineEnd; + lines[totalLineCounter].start = lineStart; + lines[totalLineCounter].end = lineEnd; // Store reflective line - lines[lineCounter + 1].start = Vector2Multiply(lineStart, scaleVector); - lines[lineCounter + 1].end = Vector2Multiply(lineEnd, scaleVector); + lines[totalLineCounter + 1].start = Vector2Multiply(lineStart, scaleVector); + lines[totalLineCounter + 1].end = Vector2Multiply(lineEnd, scaleVector); - lineCounter += 2; + totalLineCounter += 2; + currentLineCounter = totalLineCounter; } } + + if (resetButtonClicked) + { + memset(&lines, 0, sizeof(Line)*MAX_DRAW_LINES); + currentLineCounter = 0; + totalLineCounter = 0; + } + + if (backButtonClicked && (currentLineCounter > 0)) + { + currentLineCounter -= 1; + } + + if (nextButtonClicked && (currentLineCounter < MAX_DRAW_LINES) && ((currentLineCounter + 1) <= totalLineCounter)) + { + currentLineCounter += 1; + } //---------------------------------------------------------------------------------- // Draw @@ -99,19 +132,31 @@ int main(void) BeginDrawing(); ClearBackground(RAYWHITE); - BeginMode2D(camera); + for (int s = 0; s < symmetry; s++) { - for (int i = 0; i < lineCounter; i += 2) + for (int i = 0; i < currentLineCounter; i += 2) { DrawLineEx(lines[i].start, lines[i].end, thickness, BLACK); DrawLineEx(lines[i + 1].start, lines[i + 1].end, thickness, BLACK); } } + EndMode2D(); - DrawText(TextFormat("LINES: %i/%i", lineCounter, MAX_DRAW_LINES), 10, screenHeight - 30, 20, MAROON); + if ((currentLineCounter - 1) < 0) GuiDisable(); + + backButtonClicked = GuiButton(backButtonRec, "<"); + GuiEnable(); + + if ((currentLineCounter + 1) > totalLineCounter) GuiDisable(); + + nextButtonClicked = GuiButton(nextButtonRec, ">"); + GuiEnable(); + resetButtonClicked = GuiButton(resetButtonRec, "Reset"); + + DrawText(TextFormat("LINES: %i/%i", currentLineCounter, MAX_DRAW_LINES), 10, screenHeight - 30, 20, MAROON); DrawFPS(10, 10); EndDrawing(); diff --git a/examples/shapes/shapes_kaleidoscope.png b/examples/shapes/shapes_kaleidoscope.png index d14571ab735c1d50b3dac244566dd5a8c04e4137..3c15a52b324aac6807c77c0dc15d03573dbde83b 100644 GIT binary patch literal 82644 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 literal 39307 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 Date: Tue, 25 Nov 2025 19:13:08 +0100 Subject: [PATCH 177/430] Update rcore.c --- src/rcore.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 61916284c..88448bdfe 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2315,8 +2315,7 @@ const char *GetApplicationDirectory(void) #elif defined(__FreeBSD__) - size_t size = sizeof(appD - ir); + size_t size = sizeof(appDir); int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; if (sysctl(mib, 4, appDir, &size, NULL, 0) == 0) From 1ac1309b247b80d3ca8dd447977a72f5b4722f8f Mon Sep 17 00:00:00 2001 From: Rosie Date: Thu, 27 Nov 2025 14:19:22 +0000 Subject: [PATCH 178/430] feat: add elle bindings (#5370) --- BINDINGS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BINDINGS.md b/BINDINGS.md index f2ffaea9e..07f590d7d 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -29,6 +29,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [dray](https://github.com/redthing1/dray) | **5.0** | [D](https://dlang.org) | Apache-2.0 | | [raylib-d](https://github.com/schveiguy/raylib-d) | **5.5** | [D](https://dlang.org) | Zlib | | [rayex](https://github.com/shiryel/rayex) | 3.7 | [elixir](https://elixir-lang.org) | Apache-2.0 | +| [raylib-elle](https://github.com/acquitelol/elle/blob/rewrite/std/raylib.le) | **5.5** | [Elle](https://github.com/acquitelol/elle) | GPL-3.0 | | [raylib-factor](https://github.com/factor/factor/blob/master/extra/raylib/raylib.factor) | 4.5 | [Factor](https://factorcode.org) | BSD | | [raylib-freebasic](https://github.com/WIITD/raylib-freebasic) | **5.0** | [FreeBASIC](https://www.freebasic.net) | MIT | | [raylib.f](https://github.com/cthulhuology/raylib.f) | **5.5** | [Forth](https://forth.com) | Zlib | From e273aaea1ed575d6bc67155dbc2101868fc72669 Mon Sep 17 00:00:00 2001 From: John Jimenez Date: Sun, 30 Nov 2025 01:10:15 +0800 Subject: [PATCH 179/430] [examples] text_inline_styling: make inline text and background colors respect base alpha (#5373) * Added source alpha multiplier for text inline styling examples * Added header description about base alpha multiplier --- examples/text/text_inline_styling.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/examples/text/text_inline_styling.c b/examples/text/text_inline_styling.c index aeebe0abc..24e2704f7 100644 --- a/examples/text/text_inline_styling.c +++ b/examples/text/text_inline_styling.c @@ -70,6 +70,8 @@ int main(void) // - Define foreground color: [cRRGGBBAA] // - Define background color: [bRRGGBBAA] // - Reset formating: [r] + // Colors defined with [cRRGGBBAA] or [bRRGGBBAA] are multiplied by the base color alpha + // This allows global transparency control while keeping per-section styling (ex. text fade effects) // Example: [bAA00AAFF][cFF0000FF]red text on gray background[r] normal text DrawTextStyled(GetFontDefault(), "This changes the [cFF0000FF]foreground color[r] of provided text!!!", @@ -81,12 +83,15 @@ int main(void) DrawTextStyled(GetFontDefault(), "This changes the [c00ff00ff][bff0000ff]foreground and background colors[r]!!!", (Vector2){ 100, 160 }, 20.0f, 2.0f, BLACK); + DrawTextStyled(GetFontDefault(), "This changes the [c00ff00ff]alpha[r] relative [cffffffff][b000000ff]from source[r] [cff000088]color[r]!!!", + (Vector2){ 100, 200 }, 20.0f, 2.0f, (Color){ 0, 0, 0, 100 }); + // Get pointer to formated text const char *text = TextFormat("Let's be [c%02x%02x%02xFF]CREATIVE[r] !!!", colRandom.r, colRandom.g, colRandom.b); - DrawTextStyled(GetFontDefault(), text, (Vector2){ 100, 220 }, 40.0f, 2.0f, BLACK); + DrawTextStyled(GetFontDefault(), text, (Vector2){ 100, 240 }, 40.0f, 2.0f, BLACK); textSize = MeasureTextStyled(GetFontDefault(), text, 40.0f, 2.0f); - DrawRectangleLines(100, 220, (int)textSize.x, (int)textSize.y, GREEN); + DrawRectangleLines(100, 240, (int)textSize.x, (int)textSize.y, GREEN); EndDrawing(); //---------------------------------------------------------------------------------- @@ -103,7 +108,7 @@ int main(void) //---------------------------------------------------------------------------------- // Module Functions Definition //---------------------------------------------------------------------------------- -// Draw text using inline styling +// Draw text using inline styling, using input color as the base alpha multiplied to inline styles // PARAM: color is the default text color, background color is BLANK by default static void DrawTextStyled(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color color) { @@ -171,8 +176,16 @@ static void DrawTextStyled(Font font, const char *text, Vector2 position, float // Convert hex color text into actual Color unsigned int colHexValue = strtoul(colHexText, NULL, 16); - if (text[i - 1] == 'c') colFront = GetColor(colHexValue); - else if (text[i - 1] == 'b') colBack = GetColor(colHexValue); + if (text[i - 1] == 'c') + { + colFront = GetColor(colHexValue); + colFront.a *= (float)color.a / 255.0f; + } + else if (text[i - 1] == 'b') + { + colBack = GetColor(colHexValue); + colBack.a *= (float)color.a / 255.0f; + } i += (colHexCount + 1); // Skip color value retrieved and ']' continue; // Do not draw characters From 9f567e6ee40e2f57628ddae7bf798f87d8d61fda Mon Sep 17 00:00:00 2001 From: David Buzatto Date: Sat, 29 Nov 2025 14:11:15 -0300 Subject: [PATCH 180/430] Example for creating balls with simple physics simulation (#5372) * Example for creating balls with simple physics simulation The goal of this example is to create several colored balls whose movement is simulated and which respond to the action of being grabbed and dragged using the mouse. * renaming example renaming example from physics_bouncing_balls to shapes_ball_physics --- examples/shapes/shapes_ball_physics.c | 222 ++++++++++++++++++++++++ examples/shapes/shapes_ball_physics.png | Bin 0 -> 43216 bytes 2 files changed, 222 insertions(+) create mode 100644 examples/shapes/shapes_ball_physics.c create mode 100644 examples/shapes/shapes_ball_physics.png diff --git a/examples/shapes/shapes_ball_physics.c b/examples/shapes/shapes_ball_physics.c new file mode 100644 index 000000000..1c41d5f1a --- /dev/null +++ b/examples/shapes/shapes_ball_physics.c @@ -0,0 +1,222 @@ +/******************************************************************************************* +* +* raylib [shapes] example - physics bouncing balls +* +* Example complexity rating: [★★☆☆] 2/4 +* +* Example originally created with raylib 5.5 +* +* Example contributed by David Buzatto (@davidbuzatto) 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) 2025 David Buzatto (@davidbuzatto) +* +********************************************************************************************/ + +#include +#include +#include "raylib.h" + +#define MAX_BALLS 5000 // Maximum quantity of balls + +typedef struct Ball { + Vector2 pos; // Position + Vector2 vel; // Velocity + Vector2 ppos; // Previous position + float radius; + float friction; + float elasticity; + Color color; + bool grabbed; +} Ball; + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - physics bouncing balls"); + + Ball balls[MAX_BALLS] = {{ + .pos = {GetScreenWidth()/2, GetScreenHeight()/2}, + .vel = {200, 200}, + .ppos = {0}, + .radius = 40, + .friction = 0.99, + .elasticity = 0.9, + .color = BLUE, + .grabbed = false + }}; + + int ballQuantity = 1; + Ball *grabbedBall = NULL; // A pointer to the current ball that is grabbed + Vector2 pressOffset = {0}; // Mouse press offset relative to the ball that grabbedd + + float gravity = 100; // World gravity + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //--------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + float delta = GetFrameTime(); + Vector2 mousePos = GetMousePosition(); + + // Checks if a ball was grabbed + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) + { + for (int i = ballQuantity - 1; i >= 0; i--) { + + Ball *ball = &balls[i]; + pressOffset.x = mousePos.x - ball->pos.x; + pressOffset.y = mousePos.y - ball->pos.y; + + // If the distance between the ball position and the mouse press position + // is less or equal the ball radius, the event occured inside the ball + if (hypot(pressOffset.x, pressOffset.y) <= ball->radius) + { + ball->grabbed = true; + grabbedBall = ball; + break; + } + + } + } + + // Releases any ball the was grabbed + if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) + { + if (grabbedBall != NULL) + { + grabbedBall->grabbed = false; + grabbedBall = NULL; + } + } + + // Creates a new ball + if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT) || (IsKeyDown(KEY_LEFT_CONTROL) && IsMouseButtonDown(MOUSE_BUTTON_RIGHT))) { + if (ballQuantity < MAX_BALLS) { + balls[ballQuantity++] = (Ball) { + .pos = mousePos, + .vel = {GetRandomValue(-300, 300), GetRandomValue(-300, 300)}, + .ppos = {0}, + .radius = 20 + GetRandomValue(0, 30), + .friction = 0.99, + .elasticity = 0.9, + .color = {GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255}, + .grabbed = false + }; + } + } + + // Shake balls + if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) { + for (int i = 0; i < ballQuantity; i++) { + Ball *ball = &balls[i]; + if (!ball->grabbed) { + ball->vel = (Vector2) {GetRandomValue(-2000, 2000), GetRandomValue(-2000, 2000)}; + } + } + } + + // Changes gravity + gravity += GetMouseWheelMove() * 5; + + // Updates each ball state + for (int i = 0; i < ballQuantity; i++) { + + Ball *ball = &balls[i]; + + // The ball is not grabbed + if (!ball->grabbed) + { + // Ball repositioning using the velocity + ball->pos.x += ball->vel.x * delta; + ball->pos.y += ball->vel.y * delta; + + // Does the ball hit the screen right boundary? + if (ball->pos.x + ball->radius >= screenWidth) + { + ball->pos.x = screenWidth - ball->radius; // Ball repositioning + ball->vel.x = -ball->vel.x * ball->elasticity; // Elasticity makes the ball lose 10% of its velocity on hit + } + // Does the ball hit the screen left boundary? + else if (ball->pos.x - ball->radius <= 0) + { + ball->pos.x = ball->radius; + ball->vel.x = -ball->vel.x * ball->elasticity; + } + + // The same for y axis + if (ball->pos.y + ball->radius >= screenHeight) + { + ball->pos.y = screenHeight - ball->radius; + ball->vel.y = -ball->vel.y * ball->elasticity; + } + else if (ball->pos.y - ball->radius <= 0) + { + ball->pos.y = ball->radius; + ball->vel.y = -ball->vel.y * ball->elasticity; + } + + // Friction makes the ball lose 1% of its velocity each frame + ball->vel.x = ball->vel.x * ball->friction; + // Gravity affects only the y axis + ball->vel.y = ball->vel.y * ball->friction + gravity; + + } + else + { + // Ball repositioning using the mouse position + ball->pos.x = mousePos.x - pressOffset.x; + ball->pos.y = mousePos.y - pressOffset.y; + // While the ball is grabbed, recalculates its velocity + ball->vel.x = (ball->pos.x - ball->ppos.x) / delta; + ball->vel.y = (ball->pos.y - ball->ppos.y) / delta; + ball->ppos = ball->pos; + } + } + + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + for (int i = 0; i < ballQuantity; i++) + { + Ball *ball = &balls[i]; + DrawCircleV(ball->pos, ball->radius, ball->color); + DrawCircleLinesV(ball->pos, ball->radius, BLACK); + } + + DrawText("grab a ball by pressing with the mouse and throw it by releasing", 10, 10, 20, DARKGRAY); + DrawText("right click to create new balls (keep left control pressed to create a lot)", 10, 30, 20, DARKGRAY); + DrawText("use mouse wheel to change gravity", 10, 50, 20, DARKGRAY); + DrawText("middle click to shake", 10, 70, 20, DARKGRAY); + DrawText(TextFormat("ball quantity: %d", ballQuantity), 10, GetScreenHeight() - 55, 20, BLACK); + DrawText(TextFormat("gravity: %.2f", gravity), 10, GetScreenHeight() - 35, 20, BLACK); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/shapes/shapes_ball_physics.png b/examples/shapes/shapes_ball_physics.png new file mode 100644 index 0000000000000000000000000000000000000000..1e4c86f14d077ebd65f47bb47b078f10f2425a0a GIT binary patch literal 43216 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

LJdQv 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%0D$da@vc>)ViVd%CxH*IF6_c=n8B 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