23 Commits

Author SHA1 Message Date
e1efefb7f4 [cmake] Propagate Cocoa backend frameworks to consumers of static raylib on macOS (#6086)
* [cmake] Fix standalone configuration of examples project

Configuring examples/ as a standalone project (cd examples && cmake .)
failed with 'Unknown CMake command add_if_flag_compiles' and a missing
cmake_minimum_required() error on CMake 4.x.

- Add cmake_minimum_required(VERSION 3.22), matching the root project
- Include raylib's AddIfFlagCompiles helper via ../cmake
- Default PLATFORM to Desktop when not inherited from the raylib build

* [cmake] Propagate Cocoa backend frameworks to consumers of static raylib on macOS

Bundled GLFW links '-framework Cocoa/IOKit/QuartzCore' privately, and the
static-install export block removes glfw from the exported dependencies.
As a result the installed CMake package only carried OpenGL.framework,
and linking any consumer of libraylib.a failed with undefined Objective-C
symbols (_objc_retain etc.).

Append the three frameworks to the static install interface when the
bundled GLFW is used, so installed consumers get a complete link line.

* Gate macOS Cocoa framework propagation to the Desktop platform
2026-08-24 16:19:56 +02:00
2c0021ad89 [cmake] Fix standalone configuration of examples project (#6079)
Configuring examples/ as a standalone project (cd examples && cmake .)
failed with 'Unknown CMake command add_if_flag_compiles' and a missing
cmake_minimum_required() error on CMake 4.x.

- Add cmake_minimum_required(VERSION 3.22), matching the root project
- Include raylib's AddIfFlagCompiles helper via ../cmake
- Default PLATFORM to Desktop when not inherited from the raylib build
2026-08-24 13:34:47 +02:00
9bde204f1d [examples] Add textures_portal_window (#6082)
Add a example showing a portal like effect
2026-08-24 13:33:43 +02:00
Ray
47231e37ef REVIEWED: shaders_lights_bloom example 2026-08-23 21:05:34 +02:00
0088b85952 [examples] Add lights_bloom_post (#6081)
Add a bloom light effect example
2026-08-23 20:58:02 +02:00
338458a92b Fix crash when adding a new textures or audio examples (#6089)
rexm could crash and corrupt examples_list.txt when adding a new example in the textures category. This happened because the tool searched for the category name as plain text, and "text" happens to be part of "textures" (and shows up inside names like core_render_texture), so it sometimes found the wrong spot and inserted the new example in the middle of an unrelated line.

This fix makes the search look for the exact category boundary instead of just any matching text, so it always finds the right place. It also fixes a small out-of-bounds issue that could happen specifically with the audio category, since it's the last one in the list.

Tested by creating a new textures example and a new audio example, both now work correctly and the examples list stays intact.
2026-08-23 20:50:26 +02:00
3836f13878 Also include the playsoundapi.h file that is where playsound is now in lean and mean, this ensures that it gets picked up by the replacement macro in more modern SDKs. (#6088) 2026-08-23 15:29:28 +02:00
abe23bf82d [build] Updated examples/Makefile after testing on OSX (#6077)
* Updated examples/Makefile after testing on OSX

I may be wrong, but the best way to install ray lib is through home-brew, and I'd expect that that should be a search path for others.

* Update Makefile

Fixed issues
2026-08-21 21:14:57 +02:00
Ray
6cceb2b873 Update README.md 2026-08-21 21:14:21 +02:00
Ray
86430c156c Update README.md 2026-08-21 21:13:25 +02:00
Ray
3b57140640 Update README.md 2026-08-21 21:11:29 +02:00
Ray
f472c67bb2 Update README.md 2026-08-21 21:08:36 +02:00
Ray
23033d1f18 Update README.md 2026-08-21 21:07:03 +02:00
d7225acd8e Add raymojo binding for Mojo language (#6076) 2026-08-21 20:53:09 +02:00
af3045377a add raystar binding to BINDINGS.md (#6075) 2026-08-20 16:25:23 +02:00
Ray
f9e2215c49 Update README.md 2026-08-19 21:17:17 +02:00
Ray
d75a6589f0 REVIEWED: GetWindowPosition() #5932 2026-08-19 20:25:01 +02:00
47c8e897ce [rcore][GLFW] Fix borderless-windowed mode being always on top (#5868)
* Fix borderless fullscreen always on top

Don't pass a monitor to glfwSetWindowMonitor(). This avoids setting the flag which keeps the window always on top.

* Update for borderless-windowed always on top bug

Only apply the fix for Windows OS, since that seems to be the only
platform with the issue.
2026-08-19 20:02:32 +02:00
1129d42b48 Mostly rewrote GetPrevDirectoryPath() to correct its behavior in a few cases (#6073) 2026-08-19 19:53:56 +02:00
c58072d595 Update emscripten mouse handling (#6070) 2026-08-18 14:33:31 +02:00
b98c4e031d [rcore_web_emscripten] Handle emscripten keyboard events (#6067)
* Handle emscripten keyboard events

* Prevent stuck keys by clearing on blur event
2026-08-17 18:51:06 +02:00
Ray
9506b58633 Update BINDINGS.md 2026-08-17 18:46:33 +02:00
44aa771cc0 add raymod binding for modula 2 and 3 (#6068) 2026-08-17 18:45:49 +02:00
20 changed files with 883 additions and 69 deletions

View File

@ -36,6 +36,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers
| [raylib-elfscript](https://github.com/ohmtal/raylib-elfscript) | 6.0 | [ElfScript](https://github.com/ohmtal/ElfScript) | MIT | | [raylib-elfscript](https://github.com/ohmtal/raylib-elfscript) | 6.0 | [ElfScript](https://github.com/ohmtal/ElfScript) | MIT |
| [raylib-elle](https://github.com/acquitelol/elle/blob/rewrite/std/raylib.le) | **5.5** | [Elle](https://github.com/acquitelol/elle) | GPL-3.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) | 5.5 | [Factor](https://factorcode.org) | BSD | | [raylib-factor](https://github.com/factor/factor/blob/master/extra/raylib/raylib.factor) | 5.5 | [Factor](https://factorcode.org) | BSD |
| [raystar](https://github.com/RobertFlexx/raystar) | **6.0** | [F*](https://www.fstar-lang.org/) | EPL-2.0 |
| [raylib4fb](https://github.com/mudhairless/raylib4fb) | **6.0** | [FreeBASIC](https://www.freebasic.net) | Zlib | | [raylib4fb](https://github.com/mudhairless/raylib4fb) | **6.0** | [FreeBASIC](https://www.freebasic.net) | Zlib |
| [raylib-freebasic](https://github.com/WIITD/raylib-freebasic) | **5.0** | [FreeBASIC](https://www.freebasic.net) | MIT | | [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 | | [raylib.f](https://github.com/cthulhuology/raylib.f) | **5.5** | [Forth](https://forth.com) | Zlib |
@ -59,6 +60,8 @@ Some people ported raylib to other languages in the form of bindings or wrappers
| [raylib-luajit](https://github.com/homma/raylib-luajit) | 5.5 | [Lua](http://www.lua.org) | MIT | | [raylib-luajit](https://github.com/homma/raylib-luajit) | 5.5 | [Lua](http://www.lua.org) | MIT |
| [raylib-luajit-generated](https://github.com/james2doyle/raylib-luajit-generated) | 5.5 | [Lua](http://www.lua.org) | MIT | | [raylib-luajit-generated](https://github.com/james2doyle/raylib-luajit-generated) | 5.5 | [Lua](http://www.lua.org) | MIT |
| [raylib-matte](https://github.com/jcorks/raylib-matte) | 4.6-dev | [Matte](https://github.com/jcorks/matte) | **???** | | [raylib-matte](https://github.com/jcorks/raylib-matte) | 4.6-dev | [Matte](https://github.com/jcorks/matte) | **???** |
| [raymojo](https://github.com/RobertFlexx/raymojo) | **6.0** | [Mojo](https://www.modular.com/mojo) | GPLv3 |
| [raymod](https://github.com/RobertFlexx/raymod) | **6.0** | [Modula-2](https://en.wikipedia.org/wiki/Modula-2) / [Modula-3](https://en.wikipedia.org/wiki/Modula-3) | Apache-2.0 |
| [Raylib.nelua](https://github.com/AuzFox/Raylib.nelua) | **5.5** | [nelua](https://nelua.io) | Zlib | | [Raylib.nelua](https://github.com/AuzFox/Raylib.nelua) | **5.5** | [nelua](https://nelua.io) | Zlib |
| [raylib-bindings](https://github.com/vaiorabbit/raylib-bindings) | 5.6-dev | [Ruby](https://www.ruby-lang.org/en) | Zlib | | [raylib-bindings](https://github.com/vaiorabbit/raylib-bindings) | 5.6-dev | [Ruby](https://www.ruby-lang.org/en) | Zlib |
| [naylib](https://github.com/planetis-m/naylib) | **5.6-dev** | [Nim](https://nim-lang.org) | MIT | | [naylib](https://github.com/planetis-m/naylib) | **5.6-dev** | [Nim](https://nim-lang.org) | MIT |

View File

@ -54,6 +54,17 @@ features
- Bindings to [+70 programming languages](https://github.com/raysan5/raylib/blob/master/BINDINGS.md)! - Bindings to [+70 programming languages](https://github.com/raysan5/raylib/blob/master/BINDINGS.md)!
- **Free and open source** - **Free and open source**
limitations
-----------
raylib presents some limitation by design for code simplicity, some forks, alternatives and samples are available to overcome most of them but it's up to the users to modify the library for their specific needs.
- Single window with single OpenGL context by default, no multi-window support
- Window resize and move stops the rendering loop on platforms supporting a window
- `RenderTextures` are flipped vertically, as provided by OpenGL, it's up to user to draw then flipped to screen
- Font rasterization has lower quality than alternatives using `Freetype2`, `HarfBuzz` or `Slug`
- Text drawing does not support RTL, ligatures or emojis
basic example basic example
-------------- --------------
This is a basic raylib example, it creates a window and draws the text `"Congrats! You created your first window!"` in the middle of the screen. Check this example [running live on web here](https://www.raylib.com/examples/core/loader.html?name=core_basic_window). This is a basic raylib example, it creates a window and draws the text `"Congrats! You created your first window!"` in the middle of the screen. Check this example [running live on web here](https://www.raylib.com/examples/core/loader.html?name=core_basic_window).

View File

@ -1,6 +1,18 @@
# Setup the project and settings # Setup the project and settings
cmake_minimum_required(VERSION 3.22)
project(examples) project(examples)
# Include raylib's cmake helper functions, so the examples can also be
# configured standalone (`cd examples && cmake .`) and not only as part
# of the raylib project.
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../cmake")
include(AddIfFlagCompiles)
# Default to the Desktop platform when configured standalone.
if (NOT DEFINED PLATFORM)
set(PLATFORM "Desktop" CACHE STRING "Platform to build examples for")
endif ()
# Directories that contain examples # Directories that contain examples
set(example_dirs set(example_dirs
audio audio

View File

@ -18,7 +18,7 @@
# > PLATFORM_DESKTOP_RGFW (RGFW backend): # > PLATFORM_DESKTOP_RGFW (RGFW backend):
# - Windows (Win32, Win64) # - Windows (Win32, Win64)
# - Linux (X11 desktop mode) # - Linux (X11 desktop mode)
# - macOS/OSX (x64, arm64 (not tested)) # - macOS/OSX (x64, arm64)
# - Others (not tested) # - Others (not tested)
# > PLATFORM_DESKTOP_WIN32 (native Win32): # > PLATFORM_DESKTOP_WIN32 (native Win32):
# - Windows (Win32, Win64) # - Windows (Win32, Win64)
@ -314,6 +314,9 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW)
ifeq ($(PLATFORM_OS),BSD) ifeq ($(PLATFORM_OS),BSD)
LDFLAGS += -Lsrc -L$(RAYLIB_LIB_PATH) LDFLAGS += -Lsrc -L$(RAYLIB_LIB_PATH)
endif endif
ifeq ($(PLATFORM_OS),OSX)
LDFLAGS += -L/opt/homebrew/lib -L$(RAYLIB_LIB_PATH)
endif
endif endif
ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_SDL) ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_SDL)
ifeq ($(PLATFORM_OS),WINDOWS) ifeq ($(PLATFORM_OS),WINDOWS)

View File

@ -0,0 +1,44 @@
#version 100
precision mediump float;
// Input values from vertex shader
varying vec3 fragPosition;
varying vec3 fragNormal;
varying vec2 fragTexCoord;
// Input uniform values
uniform sampler2D texture0;
uniform vec3 viewPos;
uniform vec3 lightPositions[8];
uniform vec3 lightColors[8];
void main()
{
vec4 texColor = texture2D(texture0, fragTexCoord);
vec3 norm = normalize(fragNormal);
vec3 viewDir = normalize(viewPos - fragPosition);
vec3 ambient = 0.05*texColor.rgb;
vec3 totalDiffuse = vec3(0.0);
vec3 totalSpecular = vec3(0.0);
for (int i = 0; i < 8; i++)
{
vec3 lightDir = normalize(lightPositions[i] - fragPosition);
float dist = length(lightPositions[i] - fragPosition);
float attenuation = 1.0/(1.0 + 0.8*dist + 0.4*dist*dist);
// Diffuse shading
float diff = max(dot(norm, lightDir), 0.0);
totalDiffuse += diff*lightColors[i]*attenuation*1.2;
// Blinn-Phong specular
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(norm, halfwayDir), 0.0), 32.0);
totalSpecular += spec*lightColors[i]*attenuation*0.8;
}
vec3 result = ambient + (totalDiffuse*texColor.rgb) + totalSpecular;
gl_FragColor = vec4(result, texColor.a);
}

View File

@ -0,0 +1,23 @@
#version 100
// Input vertex attributes
attribute vec3 vertexPosition;
attribute vec2 vertexTexCoord;
attribute vec3 vertexNormal;
// Output values to fragment shader
varying vec3 fragPosition;
varying vec3 fragNormal;
varying vec2 fragTexCoord;
// Input uniform values
uniform mat4 mvp;
uniform mat4 matModel;
void main()
{
fragPosition = vec3(matModel*vec4(vertexPosition, 1.0));
fragNormal = normalize(vec3(matModel*vec4(vertexNormal, 0.0)));
fragTexCoord = vertexTexCoord;
gl_Position = mvp*vec4(vertexPosition, 1.0);
}

View File

@ -0,0 +1,33 @@
#version 100
precision mediump float;
varying vec2 fragTexCoord;
uniform sampler2D texture0;
void main()
{
vec4 color = texture2D(texture0, fragTexCoord);
// High-pass threshold blur: only pixels brighter than 0.75 in any channel contribute
vec4 bloom = vec4(0.0);
vec2 texel = vec2(1.0/1280.0, 1.0/720.0)*2.5;
for (int x = -2; x <= 2; x++)
{
for (int y = -2; y <= 2; y++)
{
vec4 smp = texture2D(texture0, fragTexCoord + vec2(float(x), float(y))*texel);
float brightness = max(smp.r, max(smp.g, smp.b));
if (brightness > 0.75) bloom += smp;
}
}
bloom /= 25.0;
// Combine original with the bloom glow, then Reinhard tone-map back to [0, 1]
vec3 hdr = color.rgb + bloom.rgb*1.2;
vec3 ldr = hdr/(hdr + vec3(1.0));
gl_FragColor = vec4(ldr, color.a);
}

View File

@ -0,0 +1,44 @@
#version 330
// Input values from vertex shader
in vec3 fragPosition;
in vec3 fragNormal;
in vec2 fragTexCoord;
out vec4 finalColor;
// Input uniform values
uniform sampler2D texture0;
uniform vec3 viewPos;
uniform vec3 lightPositions[8];
uniform vec3 lightColors[8];
void main()
{
vec4 texColor = texture(texture0, fragTexCoord);
vec3 norm = normalize(fragNormal);
vec3 viewDir = normalize(viewPos - fragPosition);
vec3 ambient = 0.05*texColor.rgb;
vec3 totalDiffuse = vec3(0.0);
vec3 totalSpecular = vec3(0.0);
for (int i = 0; i < 8; i++)
{
vec3 lightDir = normalize(lightPositions[i] - fragPosition);
float dist = length(lightPositions[i] - fragPosition);
float attenuation = 1.0/(1.0 + 0.8*dist + 0.4*dist*dist);
// Diffuse shading
float diff = max(dot(norm, lightDir), 0.0);
totalDiffuse += diff*lightColors[i]*attenuation*1.2;
// Blinn-Phong specular
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(norm, halfwayDir), 0.0), 32.0);
totalSpecular += spec*lightColors[i]*attenuation*0.8;
}
vec3 result = ambient + (totalDiffuse*texColor.rgb) + totalSpecular;
finalColor = vec4(result, texColor.a);
}

View File

@ -0,0 +1,23 @@
#version 330
// Input vertex attributes
in vec3 vertexPosition;
in vec2 vertexTexCoord;
in vec3 vertexNormal;
// Output values to fragment shader
out vec3 fragPosition;
out vec3 fragNormal;
out vec2 fragTexCoord;
// Input uniform values
uniform mat4 mvp;
uniform mat4 matModel;
void main()
{
fragPosition = vec3(matModel*vec4(vertexPosition, 1.0));
fragNormal = normalize(vec3(matModel*vec4(vertexNormal, 0.0)));
fragTexCoord = vertexTexCoord;
gl_Position = mvp*vec4(vertexPosition, 1.0);
}

View File

@ -0,0 +1,32 @@
#version 330
in vec2 fragTexCoord;
out vec4 finalColor;
uniform sampler2D texture0;
void main()
{
vec4 color = texture(texture0, fragTexCoord);
// High-pass threshold blur: only pixels brighter than 0.75 in any channel contribute
vec4 bloom = vec4(0.0);
vec2 texel = vec2(1.0/1280.0, 1.0/720.0)*2.5;
for (int x = -2; x <= 2; x++)
{
for (int y = -2; y <= 2; y++)
{
vec4 smp = texture(texture0, fragTexCoord + vec2(float(x), float(y))*texel);
float brightness = max(smp.r, max(smp.g, smp.b));
if (brightness > 0.75) bloom += smp;
}
}
bloom /= 25.0;
// Combine original with the bloom glow, then Reinhard tone-map back to [0, 1]
vec3 hdr = color.rgb + bloom.rgb*1.2;
vec3 ldr = hdr/(hdr + vec3(1.0));
finalColor = vec4(ldr, color.a);
}

View File

@ -0,0 +1,185 @@
/*******************************************************************************************
*
* raylib [shaders] example - forward multi-lighting with bloom
*
* Example demonstrates forward multi-point lighting (8 point lights, attenuation and
* Blinn-Phong specular) combined with a threshold-based bloom pass and Reinhard tone
* mapping, applied as a full-screen post-process pass
*
* Example complexity rating: [★★★☆] 3/4
*
* Example uses resources/shaders/glsl100 and resources/shaders/glsl330, shared with the
* rest of the shaders examples
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by PanicTitan (@PanicTitan) 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 PanicTitan (@PanicTitan)
*
********************************************************************************************/
#include "raylib.h"
#include "raymath.h"
#include "rlgl.h"
#include <math.h> // Required for: sinf(), cosf()
#if defined(PLATFORM_DESKTOP)
#define GLSL_VERSION 330
#else // PLATFORM_ANDROID, PLATFORM_WEB
#define GLSL_VERSION 100
#endif
//--------------------------------------------------------------------------------------
// Global Definitions
//--------------------------------------------------------------------------------------
#define MAX_LIGHTS 8
//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(void)
{
// Initialization
//--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;
SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_VSYNC_HINT);
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - forward multi-lighting bloom");
Camera3D camera = { 0 };
camera.position = (Vector3){ 0.0f, 5.0f, 9.0f };
camera.target = (Vector3){ 0.0f, 0.5f, 0.0f };
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };
camera.fovy = 45.0f;
camera.projection = CAMERA_PERSPECTIVE;
RenderTexture2D target = LoadRenderTexture(screenWidth, screenHeight);
// Forward multi-light shader and bloom post-process shader, both loaded from the
// resources folder shared with the rest of the shaders examples. If the GLSL version
// guessed from the PLATFORM_DESKTOP build flag turns out to be wrong for whatever
// raylib was built against, linking fails and silently falls back to the default
// shader - so this retries once with the other version, and either way the result
// is checked explicitly and reported on screen rather than staying silently wrong
Shader lightShader = LoadShader(TextFormat("resources/shaders/glsl%i/lights_bloom.vs", GLSL_VERSION),
TextFormat("resources/shaders/glsl%i/lights_bloom.fs", GLSL_VERSION));
Shader bloomShader = LoadShader(0, TextFormat("resources/shaders/glsl%i/lights_bloom_post.fs", GLSL_VERSION));
// Load models from generated cube mesh and plane
// NOTE: Meshes are automatically unloaded on UnloadModel()
Model cube = LoadModelFromMesh(GenMeshCube(2.0f, 2.0f, 2.0f));
Model floor = LoadModelFromMesh(GenMeshPlane(14.0f, 14.0f, 1, 1));
cube.materials[0].shader = lightShader;
floor.materials[0].shader = lightShader;
int lightPosLoc = GetShaderLocation(lightShader, "lightPositions");
int lightColLoc = GetShaderLocation(lightShader, "lightColors");
int viewPosLoc = GetShaderLocation(lightShader, "viewPos");
Vector3 lightPositions[MAX_LIGHTS] = { 0 };
Vector3 lightColors[MAX_LIGHTS] = {
{ 1.0f, 0.2f, 0.2f }, // Red
{ 0.2f, 1.0f, 0.3f }, // Green
{ 0.2f, 0.5f, 1.0f }, // Blue
{ 1.0f, 0.8f, 0.1f }, // Yellow
{ 1.0f, 0.1f, 0.8f }, // Magenta
{ 0.1f, 1.0f, 1.0f }, // Cyan
{ 1.0f, 0.4f, 0.1f }, // Orange
{ 0.7f, 0.2f, 1.0f }, // Purple
};
SetShaderValueV(lightShader, lightColLoc, lightColors, SHADER_UNIFORM_VEC3, MAX_LIGHTS);
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(&camera, CAMERA_ORBITAL);
float time = (float)GetTime();
// Orbital path for each point light, its own radius/height offset by index
for (int i = 0; i < MAX_LIGHTS; i++)
{
float angle = (i/(float)MAX_LIGHTS)*2.0f*PI + time*0.6f;
float radius = 4.2f + sinf(time*1.2f + i)*0.4f;
float height = 1.0f + sinf(time*1.8f + i)*0.6f;
lightPositions[i] = (Vector3){ sinf(angle)*radius, height, cosf(angle)*radius };
}
SetShaderValueV(lightShader, lightPosLoc, lightPositions, SHADER_UNIFORM_VEC3, MAX_LIGHTS);
SetShaderValue(lightShader, viewPosLoc, &camera.position, SHADER_UNIFORM_VEC3);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
// Render the lit scene to an offscreen texture
BeginTextureMode(target);
ClearBackground((Color){ 12, 12, 18, 255 });
BeginMode3D(camera);
DrawModel(cube, (Vector3){ 0.0f, 1.0f, 0.0f }, 1.0f, GRAY);
DrawModel(floor, (Vector3){ 0.0f, 0.0f, 0.0f }, 1.0f, DARKGRAY);
DrawGrid(10, 1.0f);
// Glowing bulbs mark each light's position
for (int i = 0; i < MAX_LIGHTS; i++)
{
Color bulbColor = (Color){
(unsigned char)(lightColors[i].x*255),
(unsigned char)(lightColors[i].y*255),
(unsigned char)(lightColors[i].z*255),
255 };
DrawSphere(lightPositions[i], 0.15f, bulbColor);
}
EndMode3D();
EndTextureMode();
// Present the offscreen texture through the bloom + tone mapping shader
BeginDrawing();
ClearBackground(BLACK);
Rectangle srcRec = { 0, 0, (float)target.texture.width, (float)-target.texture.height };
BeginShaderMode(bloomShader);
// NOTE: Render texture must be y-flipped due to default OpenGL coordinates (left-bottom)
DrawTextureRec(target.texture, srcRec, (Vector2){ 0, 0 }, WHITE);
EndShaderMode();
DrawText("BALANCED MULTI-LIGHT + REINHARD TONE MAPPED BLOOM", 20, 20, 20, GREEN);
EndDrawing();
//----------------------------------------------------------------------------------
}
// De-Initialization
//--------------------------------------------------------------------------------------
UnloadModel(cube);
UnloadModel(floor);
UnloadShader(lightShader);
UnloadShader(bloomShader);
UnloadRenderTexture(target);
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

View File

@ -0,0 +1,224 @@
/*******************************************************************************************
*
* raylib [textures] example - portal window
*
* Example demonstrates rendering a second scene to a texture and projecting it onto a
* quad to create the illusion of a portal window looking into another place. The second
* scene is rendered with an off-axis ("oblique frustum") projection matched to the
* viewer's actual position relative to the window, so the illusion holds up correctly
* as the player moves and looks around, rather than only looking right from one spot
*
* Example complexity rating: [★★★☆] 3/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by PanicTitan (@PanicTitan) 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 PanicTitan (@PanicTitan)
*
********************************************************************************************/
#include "raylib.h"
#include "raymath.h"
#include "rlgl.h"
#include <math.h>
//------------------------------------------------------------------------------------
// Module Functions Declaration
//------------------------------------------------------------------------------------
static void BeginPortalMode3D(Vector3 eye, Vector3 bottomLeft, Vector3 bottomRight, Vector3 topLeft, float nearPlane, float farPlane);
//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(void)
{
// Initialization
//--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;
InitWindow(screenWidth, screenHeight, "raylib [textures] example - portal window");
// Camera to navigate the "real world" (Dimension A)
Camera3D camera = { 0 };
camera.position = (Vector3){ 0.0f, 2.5f, 7.0f };
camera.target = (Vector3){ 0.0f, 1.8f, 0.0f };
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };
camera.fovy = 45.0f;
camera.projection = CAMERA_PERSPECTIVE;
// The archway opening, in Dimension A world space (used both to draw the frame and
// as the window rectangle the oblique projection is built from)
Vector3 archBottomLeft = { -1.5f, 0.0f, 0.0f };
Vector3 archBottomRight = { 1.5f, 0.0f, 0.0f };
Vector3 archTopLeft = { -1.5f, 4.0f, 0.0f };
// The archway sits at portalA and looks out onto portalB, far away in world space.
// Every frame, Dimension B gets rendered to a texture using the same relative eye
// and window position, shifted by the offset between the two portals
Vector3 portalA = { 0.0f, 0.0f, 0.0f };
Vector3 portalB = { 0.0f, 0.0f, -60.0f };
RenderTexture2D portalView = LoadRenderTexture(480, 640);
DisableCursor(); // Lock cursor for first-person free camera controls
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(&camera, CAMERA_FREE);
float time = (float)GetTime();
// Eye and window corners, shifted into Dimension B so they match the player's
// actual position and viewing angle relative to the archway
Vector3 offset = Vector3Subtract(portalB, portalA);
Vector3 eyeInB = Vector3Add(camera.position, offset);
Vector3 blInB = Vector3Add(archBottomLeft, offset);
Vector3 brInB = Vector3Add(archBottomRight, offset);
Vector3 tlInB = Vector3Add(archTopLeft, offset);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
// Render Dimension B into an offscreen texture, using an oblique projection so
// its perspective lines up with the archway exactly as the real camera sees it
BeginTextureMode(portalView);
ClearBackground((Color){ 10, 5, 20, 255 });
BeginPortalMode3D(eyeInB, blInB, brInB, tlInB, 0.05f, 100.0f);
DrawGrid(30, 0.8f);
// Floating pulsing core sphere
Vector3 corePos = Vector3Add(portalB, (Vector3){ 0.0f, 2.0f + sinf(time * 2.5f) * 0.4f, -4.0f });
DrawSphere(corePos, 1.2f, PURPLE);
DrawSphereWires(corePos, 1.25f, 16, 16, MAGENTA);
// Orbiting cubes
for (int i = 0; i < 4; i++)
{
float angle = time * 1.5f + i * (PI / 2.0f);
Vector3 pos = Vector3Add(portalB, (Vector3){
sinf(angle) * 2.5f,
2.0f + cosf(time * 3.0f + i) * 0.5f,
-4.0f + cosf(angle) * 2.5f });
DrawCube(pos, 0.5f, 0.5f, 0.5f, LIME);
DrawCubeWires(pos, 0.52f, 0.52f, 0.52f, DARKGREEN);
}
EndMode3D();
EndTextureMode();
BeginDrawing();
ClearBackground((Color){ 15, 18, 26, 255 });
BeginMode3D(camera);
DrawGrid(20, 1.0f);
// Side pillars
DrawCube((Vector3){ -3.5f, 2.0f, 0.0f }, 0.8f, 4.0f, 0.8f, DARKGRAY);
DrawCubeWires((Vector3){ -3.5f, 2.0f, 0.0f }, 0.8f, 4.0f, 0.8f, ORANGE);
DrawCube((Vector3){ 3.5f, 2.0f, 0.0f }, 0.8f, 4.0f, 0.8f, DARKGRAY);
DrawCubeWires((Vector3){ 3.5f, 2.0f, 0.0f }, 0.8f, 4.0f, 0.8f, ORANGE);
// Golden archway frame and solid base
DrawCubeWires((Vector3){ 0.0f, 2.0f, 0.0f }, 3.2f, 4.2f, 0.2f, GOLD);
DrawCube((Vector3){ 0.0f, 0.05f, 0.0f }, 3.4f, 0.1f, 0.6f, MAROON);
// Solid backing wall, only ever seen if looking at the archway from behind
DrawCube((Vector3){ 0.0f, 2.0f, -0.05f }, 3.1f, 4.1f, 0.05f, DARKBLUE);
// The portal opening itself: a plain quad textured with the Dimension B
// render, filling the archway exactly, so nothing "leaks" outside its shape
rlSetTexture(portalView.texture.id);
rlBegin(RL_QUADS);
rlColor4ub(255, 255, 255, 255);
rlNormal3f(0.0f, 0.0f, 1.0f);
rlTexCoord2f(0.0f, 0.0f); rlVertex3f(archBottomLeft.x, archBottomLeft.y, archBottomLeft.z);
rlTexCoord2f(1.0f, 0.0f); rlVertex3f(archBottomRight.x, archBottomRight.y, archBottomRight.z);
rlTexCoord2f(1.0f, 1.0f); rlVertex3f(archBottomRight.x, 4.0f, archBottomRight.z);
rlTexCoord2f(0.0f, 1.0f); rlVertex3f(archTopLeft.x, archTopLeft.y, archTopLeft.z);
rlEnd();
rlSetTexture(0);
EndMode3D();
// HUD overlay
DrawRectangle(15, 15, 400, 85, Fade(BLACK, 0.75f));
DrawRectangleLines(15, 15, 400, 85, GOLD);
DrawText("PORTAL WINDOW", 28, 25, 20, GOLD);
DrawText("Look through the golden arch into Dimension B", 28, 52, 14, RAYWHITE);
DrawText("Controls: Mouse to look | WASD to move", 28, 72, 12, GRAY);
EndDrawing();
//----------------------------------------------------------------------------------
}
// De-Initialization
//--------------------------------------------------------------------------------------
UnloadRenderTexture(portalView);
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
//----------------------------------------------------------------------------------
// Module Functions Definition
//----------------------------------------------------------------------------------
// Starts a 3D mode using an off-axis ("oblique frustum") projection, built directly from
// an eye point and 3 corners of a rectangular window, instead of a fovy centered straight
// ahead of the camera. This is the standard technique for rendering a scene as seen through
// a window that isn't necessarily faced head-on (also used for multi-monitor and VR
// rendering) - the key difference from a normal Camera3D is that the frustum is allowed
// to be asymmetric, so perspective lines through the window line up correctly from any
// eye position, instead of behaving like a flat image pasted onto the window
static void BeginPortalMode3D(Vector3 eye, Vector3 bottomLeft, Vector3 bottomRight, Vector3 topLeft, float nearPlane, float farPlane)
{
Vector3 right = Vector3Normalize(Vector3Subtract(bottomRight, bottomLeft));
Vector3 up = Vector3Normalize(Vector3Subtract(topLeft, bottomLeft));
Vector3 normal = Vector3Normalize(Vector3CrossProduct(right, up));
// Vectors from the eye to 3 corners of the window, used to project the window onto
// the near plane and read off how far it extends left/right/bottom/top of the eye
Vector3 toBL = Vector3Subtract(bottomLeft, eye);
Vector3 toBR = Vector3Subtract(bottomRight, eye);
Vector3 toTL = Vector3Subtract(topLeft, eye);
float dist = -Vector3DotProduct(toBL, normal);
if (dist < 0.01f) dist = 0.01f; // Keep the eye from crossing the window plane
float scale = nearPlane/dist;
rlDrawRenderBatchActive();
rlMatrixMode(RL_PROJECTION);
rlPushMatrix();
rlSetMatrixProjection(MatrixFrustum(
Vector3DotProduct(right, toBL)*scale, Vector3DotProduct(right, toBR)*scale,
Vector3DotProduct(up, toBL)*scale, Vector3DotProduct(up, toTL)*scale,
nearPlane, farPlane));
// View orientation is fixed to the window's own plane (looking straight through it
// along its normal), NOT aimed at any target - that's what the asymmetric frustum
// above is for, and is what lets the eye move off to one side without distorting
rlMatrixMode(RL_MODELVIEW);
rlLoadIdentity();
rlMultMatrixf(MatrixToFloat(MatrixLookAt(eye, Vector3Subtract(eye, normal), up)));
rlEnableDepthTest();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -114,6 +114,20 @@ if (NOT BUILD_SHARED_LIBS)
if (NOT glfw3_FOUND) if (NOT glfw3_FOUND)
list(REMOVE_ITEM raylib_install_private_libs glfw) list(REMOVE_ITEM raylib_install_private_libs glfw)
# Bundled GLFW links its Cocoa backend frameworks privately, so they
# must be propagated to consumers of the static library, whose final
# link line would otherwise miss them. Only the Desktop platform uses
# bundled GLFW; other platforms (e.g. Memory) have no Cocoa/GLFW symbols.
if (APPLE AND PLATFORM STREQUAL "Desktop")
find_library(COCOA_FRAMEWORK Cocoa)
find_library(IOKIT_FRAMEWORK IOKit)
find_library(QUARTZCORE_FRAMEWORK QuartzCore)
list(APPEND raylib_install_private_libs
${COCOA_FRAMEWORK}
${IOKIT_FRAMEWORK}
${QUARTZCORE_FRAMEWORK})
endif ()
endif() endif()
foreach(lib IN LISTS raylib_install_private_libs) foreach(lib IN LISTS raylib_install_private_libs)

View File

@ -46,6 +46,7 @@
// include windows // include windows
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <windows.h>
#include <playsoundapi.h>
// remove all our redefinitions so that raylib can define them properly // remove all our redefinitions so that raylib can define them properly
#undef CloseWindow #undef CloseWindow

View File

@ -287,8 +287,14 @@ void ToggleBorderlessWindowed(void)
CORE.Window.screen.height = mode->height; CORE.Window.screen.height = mode->height;
// Set screen position and size // Set screen position and size
#if defined(_WIN32)
// NOTE: To prevent the fullscreen window from always staying on top, don't pass a monitor at this point.
glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.position.x, CORE.Window.position.y,
CORE.Window.screen.width, CORE.Window.screen.height, mode->refreshRate);
#else
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); CORE.Window.screen.width, CORE.Window.screen.height, mode->refreshRate);
#endif
// Refocus window // Refocus window
glfwFocusWindow(platform.handle); glfwFocusWindow(platform.handle);
@ -1024,6 +1030,8 @@ const char *GetMonitorName(int monitor)
// Get window position XY on monitor // Get window position XY on monitor
Vector2 GetWindowPosition(void) Vector2 GetWindowPosition(void)
{ {
glfwGetWindowPos(platform.handle, &CORE.Window.position.x, &CORE.Window.position.y);
return (Vector2){ (float)CORE.Window.position.x, (float)CORE.Window.position.y }; return (Vector2){ (float)CORE.Window.position.x, (float)CORE.Window.position.y };
} }

View File

@ -42,6 +42,7 @@
**********************************************************************************************/ **********************************************************************************************/
#include <emscripten/emscripten.h> // Emscripten functionality for C #include <emscripten/emscripten.h> // Emscripten functionality for C
#include <emscripten/dom_pk_codes.h> // Emscripten physical keyboard codes
#include <emscripten/html5.h> // Emscripten HTML5 library #include <emscripten/html5.h> // Emscripten HTML5 library
#include <sys/time.h> // Required for: timespec, nanosleep(), select() - POSIX #include <sys/time.h> // Required for: timespec, nanosleep(), select() - POSIX
@ -54,6 +55,9 @@
#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 ext.
#endif #endif
#define DOM_PK_CODE_MAPPED_NUM 128
#define DOM_PK_CODE_EXTENDED_OFFSET 0xE000
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// Types and Structures Definition // Types and Structures Definition
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
@ -70,9 +74,121 @@ extern CoreData CORE; // Global CORE state context
static PlatformData platform = { 0 }; // Platform specific data static PlatformData platform = { 0 }; // Platform specific data
//---------------------------------------------------------------------------------- static const KeyboardKey mapDomCodeToKey[DOM_PK_CODE_MAPPED_NUM] = {
// Global Variables Definition [DOM_PK_ESCAPE] = KEY_ESCAPE,
//---------------------------------------------------------------------------------- [DOM_PK_0] = KEY_ZERO,
[DOM_PK_1] = KEY_ONE,
[DOM_PK_2] = KEY_TWO,
[DOM_PK_3] = KEY_THREE,
[DOM_PK_4] = KEY_FOUR,
[DOM_PK_5] = KEY_FIVE,
[DOM_PK_6] = KEY_SIX,
[DOM_PK_7] = KEY_SEVEN,
[DOM_PK_8] = KEY_EIGHT,
[DOM_PK_9] = KEY_NINE,
[DOM_PK_MINUS] = KEY_MINUS,
[DOM_PK_EQUAL] = KEY_EQUAL,
[DOM_PK_BACKSPACE] = KEY_BACKSPACE,
[DOM_PK_TAB] = KEY_TAB,
[DOM_PK_Q] = KEY_Q,
[DOM_PK_W] = KEY_W,
[DOM_PK_E] = KEY_E,
[DOM_PK_R] = KEY_R,
[DOM_PK_T] = KEY_T,
[DOM_PK_Y] = KEY_Y,
[DOM_PK_U] = KEY_U,
[DOM_PK_I] = KEY_I,
[DOM_PK_O] = KEY_O,
[DOM_PK_P] = KEY_P,
[DOM_PK_BRACKET_LEFT] = KEY_LEFT_BRACKET,
[DOM_PK_BRACKET_RIGHT] = KEY_RIGHT_BRACKET,
[DOM_PK_ENTER] = KEY_ENTER,
[DOM_PK_CONTROL_LEFT] = KEY_LEFT_CONTROL,
[DOM_PK_A] = KEY_A,
[DOM_PK_S] = KEY_S,
[DOM_PK_D] = KEY_D,
[DOM_PK_F] = KEY_F,
[DOM_PK_G] = KEY_G,
[DOM_PK_H] = KEY_H,
[DOM_PK_J] = KEY_J,
[DOM_PK_K] = KEY_K,
[DOM_PK_L] = KEY_L,
[DOM_PK_SEMICOLON] = KEY_SEMICOLON,
[DOM_PK_QUOTE] = KEY_APOSTROPHE,
[DOM_PK_BACKQUOTE] = KEY_GRAVE,
[DOM_PK_SHIFT_LEFT] = KEY_LEFT_SHIFT,
[DOM_PK_BACKSLASH] = KEY_BACKSLASH,
[DOM_PK_Z] = KEY_Z,
[DOM_PK_X] = KEY_X,
[DOM_PK_C] = KEY_C,
[DOM_PK_V] = KEY_V,
[DOM_PK_B] = KEY_B,
[DOM_PK_N] = KEY_N,
[DOM_PK_M] = KEY_M,
[DOM_PK_COMMA] = KEY_COMMA,
[DOM_PK_PERIOD] = KEY_PERIOD,
[DOM_PK_SLASH] = KEY_SLASH,
[DOM_PK_SHIFT_RIGHT] = KEY_RIGHT_SHIFT,
[DOM_PK_NUMPAD_MULTIPLY] = KEY_KP_MULTIPLY,
[DOM_PK_ALT_LEFT] = KEY_LEFT_ALT,
[DOM_PK_SPACE] = KEY_SPACE,
[DOM_PK_CAPS_LOCK] = KEY_CAPS_LOCK,
[DOM_PK_F1] = KEY_F1,
[DOM_PK_F2] = KEY_F2,
[DOM_PK_F3] = KEY_F3,
[DOM_PK_F4] = KEY_F4,
[DOM_PK_F5] = KEY_F5,
[DOM_PK_F6] = KEY_F6,
[DOM_PK_F7] = KEY_F7,
[DOM_PK_F8] = KEY_F8,
[DOM_PK_F9] = KEY_F9,
[DOM_PK_F10] = KEY_F10,
[DOM_PK_PAUSE] = KEY_PAUSE,
[DOM_PK_SCROLL_LOCK] = KEY_SCROLL_LOCK,
[DOM_PK_NUMPAD_7] = KEY_KP_7,
[DOM_PK_NUMPAD_8] = KEY_KP_8,
[DOM_PK_NUMPAD_9] = KEY_KP_9,
[DOM_PK_NUMPAD_SUBTRACT] = KEY_KP_SUBTRACT,
[DOM_PK_NUMPAD_4] = KEY_KP_4,
[DOM_PK_NUMPAD_5] = KEY_KP_5,
[DOM_PK_NUMPAD_6] = KEY_KP_6,
[DOM_PK_NUMPAD_ADD] = KEY_KP_ADD,
[DOM_PK_NUMPAD_1] = KEY_KP_1,
[DOM_PK_NUMPAD_2] = KEY_KP_2,
[DOM_PK_NUMPAD_3] = KEY_KP_3,
[DOM_PK_NUMPAD_0] = KEY_KP_0,
[DOM_PK_NUMPAD_DECIMAL] = KEY_KP_DECIMAL,
[DOM_PK_PRINT_SCREEN] = KEY_PRINT_SCREEN,
[DOM_PK_F11] = KEY_F11,
[DOM_PK_F12] = KEY_F12,
[DOM_PK_NUMPAD_EQUAL] = KEY_KP_EQUAL,
[DOM_PK_NUMPAD_COMMA] = KEY_KP_DECIMAL
};
// Emscripten places keys with extended scan codes (right-side modifiers, navigation and some keypad keys) in the 0xE0xx range
static const KeyboardKey mapDomCodeToKeyExtended[DOM_PK_CODE_MAPPED_NUM] = {
[DOM_PK_NUMPAD_ENTER - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_KP_ENTER,
[DOM_PK_CONTROL_RIGHT - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_RIGHT_CONTROL,
[DOM_PK_AUDIO_VOLUME_DOWN - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_VOLUME_DOWN,
[DOM_PK_AUDIO_VOLUME_UP - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_VOLUME_UP,
[DOM_PK_NUMPAD_DIVIDE - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_KP_DIVIDE,
[DOM_PK_ALT_RIGHT - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_RIGHT_ALT,
[DOM_PK_NUM_LOCK - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_NUM_LOCK,
[DOM_PK_HOME - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_HOME,
[DOM_PK_ARROW_UP - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_UP,
[DOM_PK_PAGE_UP - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_PAGE_UP,
[DOM_PK_ARROW_LEFT - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_LEFT,
[DOM_PK_ARROW_RIGHT - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_RIGHT,
[DOM_PK_END - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_END,
[DOM_PK_ARROW_DOWN - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_DOWN,
[DOM_PK_PAGE_DOWN - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_PAGE_DOWN,
[DOM_PK_INSERT - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_INSERT,
[DOM_PK_DELETE - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_DELETE,
[DOM_PK_META_LEFT - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_LEFT_SUPER,
[DOM_PK_META_RIGHT - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_RIGHT_SUPER,
[DOM_PK_CONTEXT_MENU - DOM_PK_CODE_EXTENDED_OFFSET] = KEY_KB_MENU
};
static const char cursorLUT[11][12] = { static const char cursorLUT[11][12] = {
"default", // 0 MOUSE_CURSOR_DEFAULT "default", // 0 MOUSE_CURSOR_DEFAULT
"default", // 1 MOUSE_CURSOR_ARROW "default", // 1 MOUSE_CURSOR_ARROW
@ -110,6 +226,8 @@ static EM_BOOL EmscriptenPointerlockCallback(int eventType, const EmscriptenPoin
static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData);
static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData); static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData);
static KeyboardKey ConvertKeyboardEventToKey(const EmscriptenKeyboardEvent *keyboardEvent);
// JS: Set the canvas id provided by the module configuration // JS: Set the canvas id provided by the module configuration
EM_JS(void, SetCanvasIdJs, (char *out, int outSize), { EM_JS(void, SetCanvasIdJs, (char *out, int outSize), {
var canvasId = "#" + Module.canvas.id; var canvasId = "#" + Module.canvas.id;
@ -1391,7 +1509,11 @@ static EM_BOOL EmscriptenFocusCallback(int eventType, const EmscriptenFocusEvent
switch (eventType) switch (eventType)
{ {
case EMSCRIPTEN_EVENT_BLUR: FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); break; case EMSCRIPTEN_EVENT_BLUR:
{
FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED);
memset(CORE.Input.Keyboard.currentKeyState, 0, sizeof(CORE.Input.Keyboard.currentKeyState));
} break;
case EMSCRIPTEN_EVENT_FOCUS: FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); break; case EMSCRIPTEN_EVENT_FOCUS: FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); break;
default: consumed = 0; break; default: consumed = 0; break;
} }
@ -1454,52 +1576,75 @@ static void WindowDropCallback(GLFWwindow *window, int count, const char **paths
} }
*/ */
// Emscripten keyboard event to raylib key mapping
static KeyboardKey ConvertKeyboardEventToKey(const EmscriptenKeyboardEvent *keyboardEvent)
{
DOM_PK_CODE_TYPE code = emscripten_compute_dom_pk_code(keyboardEvent->code);
KeyboardKey key = KEY_NULL;
if ((code > DOM_PK_UNKNOWN) && (code < DOM_PK_CODE_MAPPED_NUM)) key = mapDomCodeToKey[code];
else if ((code >= DOM_PK_CODE_EXTENDED_OFFSET) && (code < (DOM_PK_CODE_EXTENDED_OFFSET + DOM_PK_CODE_MAPPED_NUM)))
{
key = mapDomCodeToKeyExtended[code - DOM_PK_CODE_EXTENDED_OFFSET];
}
return key;
}
// Emscripten: Called on key events // 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) static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyboardEvent, void *userData)
{ {
KeyboardKey key = ConvertKeyboardEventToKey(keyboardEvent);
EM_BOOL preventDefault = 1;
switch (eventType) switch (eventType)
{ {
case EMSCRIPTEN_EVENT_KEYPRESS:
{
if (keyboardEvent->repeat) CORE.Input.Keyboard.keyRepeatInFrame[keyboardEvent->keyCode] = 1;
} break;
case EMSCRIPTEN_EVENT_KEYDOWN: case EMSCRIPTEN_EVENT_KEYDOWN:
{ {
CORE.Input.Keyboard.currentKeyState[keyboardEvent->keyCode] = 1; if (key != KEY_NULL)
{
// Check if there is space available in the key queue
if ((!keyboardEvent->repeat) && (CORE.Input.Keyboard.currentKeyState[key] == 0) &&
(CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE))
{
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key;
CORE.Input.Keyboard.keyPressedQueueCount++;
}
CORE.Input.Keyboard.currentKeyState[key] = 1;
if (keyboardEvent->repeat) CORE.Input.Keyboard.keyRepeatInFrame[key] = 1;
// Check the exit key to set close window
if ((key == CORE.Input.Keyboard.exitKey) && !keyboardEvent->repeat) CORE.Window.shouldClose = true;
}
// Allow printable keydown events so the browser can emit the keypress event used to collect characters
preventDefault = ((key == KEY_BACKSPACE) || (key == KEY_TAB) ||
(key == KEY_INSERT) || (key == KEY_DELETE) ||
(key == KEY_HOME) || (key == KEY_END) ||
(key == KEY_PAGE_UP) || (key == KEY_PAGE_DOWN) ||
(key == KEY_LEFT) || (key == KEY_RIGHT) ||
(key == KEY_UP) || (key == KEY_DOWN) ||
((key >= KEY_F1) && (key <= KEY_F12)) || keyboardEvent->ctrlKey);
} break; } break;
case EMSCRIPTEN_EVENT_KEYUP: case EMSCRIPTEN_EVENT_KEYUP:
{ {
CORE.Input.Keyboard.currentKeyState[keyboardEvent->keyCode] = 0; if (key != KEY_NULL) CORE.Input.Keyboard.currentKeyState[key] = 0;
} break;
case EMSCRIPTEN_EVENT_KEYPRESS:
{
// Check if there is space available in the queue for characters to be added
if ((keyboardEvent->charCode > 0) && (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE))
{
CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = keyboardEvent->charCode;
CORE.Input.Keyboard.charPressedQueueCount++;
}
} break; } break;
default: break; default: break;
} }
// TODO: Add char codes return preventDefault;
//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 // Emscripten: Called on mouse input events
@ -1563,7 +1708,8 @@ static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent
if (GetMouseX() != 0 || GetMouseY() != 0) ProcessGestureEvent(gestureEvent); if (GetMouseX() != 0 || GetMouseY() != 0) ProcessGestureEvent(gestureEvent);
#endif #endif
return 1; // The event was consumed by the callback handler // Don't prevent default to allow focusing on canvas
return 0;
} }
// Emscripten: Called on mouse move events // Emscripten: Called on mouse move events
@ -1577,32 +1723,18 @@ static EM_BOOL EmscriptenMouseMoveCallback(int eventType, const EmscriptenMouseE
else else
{ {
// Get mouse position in canvas CSS pixels // Get mouse position in canvas CSS pixels
float mouseCssX = (float)mouseEvent->canvasX; float mouseCssX = (float)mouseEvent->targetX;
float mouseCssY = (float)mouseEvent->canvasY; float mouseCssY = (float)mouseEvent->targetY;
// Get canvas sizes // Get canvas sizes
double cssWidth = 0.0; double cssWidth = 0.0;
double cssHeight = 0.0; double cssHeight = 0.0;
emscripten_get_element_css_size(platform.canvasId, &cssWidth, &cssHeight); emscripten_get_element_css_size(platform.canvasId, &cssWidth, &cssHeight);
int fbWidth = 0; float normalizedX = mouseCssX/(float)cssWidth;
int fbHeight = 0; float normalizedY = mouseCssY/(float)cssHeight;
emscripten_get_canvas_element_size(platform.canvasId, &fbWidth, &fbHeight); CORE.Input.Mouse.currentPosition.x = normalizedX*(float)CORE.Window.screen.width;
CORE.Input.Mouse.currentPosition.y = normalizedY*(float)CORE.Window.screen.height;
// 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; CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition;
} }

View File

@ -2583,19 +2583,27 @@ const char *GetPrevDirectoryPath(const char *dirPath)
{ {
static char prevDirPath[MAX_FILEPATH_LENGTH] = { 0 }; static char prevDirPath[MAX_FILEPATH_LENGTH] = { 0 };
memset(prevDirPath, 0, MAX_FILEPATH_LENGTH); memset(prevDirPath, 0, MAX_FILEPATH_LENGTH);
int dirPathLength = (int)strlen(dirPath);
if (dirPathLength <= 3) snprintf(prevDirPath, MAX_FILEPATH_LENGTH, "%s", dirPath); const int lastIndex = (int)strlen(dirPath) - 1;
bool isFile = IsPathFile(dirPath);
for (int i = (dirPathLength - 1); (i >= 0) && (dirPathLength > 3); i--) for (int i = lastIndex; i >= 0; i--)
{ {
// If the character is a path separator.
if ((dirPath[i] == '\\') || (dirPath[i] == '/')) if ((dirPath[i] == '\\') || (dirPath[i] == '/'))
{ {
// Check for root: "C:\" or "/" // If this character is a leading '/' (e.g. the '/' in "/usr") or
if (((i == 2) && (dirPath[1] ==':')) || (i == 0)) i++; // part of a drive root (e.g. "C:\"), include it with the result.
if ((i == 0) || ((i == 2) && (dirPath[1] == ':'))) i += 1;
// If this character is a trailing path separator (e.g. the last
// '/' in "/usr/bin/" or the last '\' in "C:\raylib\"), continue.
else if (i == lastIndex) continue;
memcpy(prevDirPath, dirPath, i); if (!isFile)
break; {
memcpy(prevDirPath, dirPath, i);
break;
}
else isFile = false;
} }
} }

View File

@ -588,7 +588,7 @@ int main(int argc, char *argv[])
else if (TextIsEqual(exCategory, "text")) nextCategoryIndex = 4; else if (TextIsEqual(exCategory, "text")) nextCategoryIndex = 4;
else if (TextIsEqual(exCategory, "models")) nextCategoryIndex = 5; else if (TextIsEqual(exCategory, "models")) nextCategoryIndex = 5;
else if (TextIsEqual(exCategory, "shaders")) nextCategoryIndex = 6; else if (TextIsEqual(exCategory, "shaders")) nextCategoryIndex = 6;
else if (TextIsEqual(exCategory, "audio")) nextCategoryIndex = 7; else if (TextIsEqual(exCategory, "audio")) nextCategoryIndex = -1; // EOF, "audio" is the last category, avoid out-of-bounds exCategories[7] access
// Get required example info from example file header (if provided) // Get required example info from example file header (if provided)
@ -621,7 +621,13 @@ int main(int argc, char *argv[])
else else
{ {
// Add example to collection, at the end of the category list // Add example to collection, at the end of the category list
int categoryIndex = TextFindIndex(exCollectionList, exCategories[nextCategoryIndex]); // NOTE: Search is anchored to "\n<category>;" (not just "<category>") to avoid
// false-positive matches when one category name is a text-prefix of another
// (e.g. "text" is a prefix of "textures", so a bare search for "text" matches
// inside "core_render_texture" or at the start of the "textures" block instead
// of the actual "text" category boundary, corrupting the collection list)
int categoryIndex = TextFindIndex(exCollectionList, TextFormat("\n%s;", exCategories[nextCategoryIndex])) + 1;
if (categoryIndex == 0) categoryIndex = (int)strlen(exCollectionList); // Category not found, fallback to EOF
memcpy(exCollectionListUpdated, exCollectionList, categoryIndex); memcpy(exCollectionListUpdated, exCollectionList, categoryIndex);
int textWritenSize = sprintf(exCollectionListUpdated + categoryIndex, TextFormat("%s;%s;%s;%s;%s;%i;%i;\"%s\";@%s\n", int textWritenSize = sprintf(exCollectionListUpdated + categoryIndex, 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)); exInfo->category, exInfo->name, starsText, exInfo->verCreated, exInfo->verUpdated, exInfo->yearCreated, exInfo->yearReviewed, exInfo->author, exInfo->authorGitHub));
@ -1030,8 +1036,16 @@ int main(int argc, char *argv[])
// Find position to add new example on list, just before the following category // Find position to add new example on list, just before the following category
// Category order: core, shapes, textures, text, models, shaders, audio // Category order: core, shapes, textures, text, models, shaders, audio
// NOTE: Search is anchored to "\n<category>;" (not just "\n<category>") to avoid
// false-positive matches when one category name is a text-prefix of another
// (e.g. "text" is a prefix of "textures", so a bare "\ntext" search matches the
// START of the "textures" block instead of the actual "text" category boundary).
int exListNextCatIndex = -1; int exListNextCatIndex = -1;
if (nextCatIndex != -1) exListNextCatIndex = TextFindIndex(exList, TextFormat("\n%s", exCategories[nextCatIndex])) + 1; if (nextCatIndex != -1)
{
exListNextCatIndex = TextFindIndex(exList, TextFormat("\n%s;", exCategories[nextCatIndex])) + 1;
if (exListNextCatIndex == 0) exListNextCatIndex = exListLen; // Category not found, fallback to EOF
}
else exListNextCatIndex = exListLen; // EOF else exListNextCatIndex = exListLen; // EOF
strncpy(exListUpdated, exList, exListNextCatIndex); strncpy(exListUpdated, exList, exListNextCatIndex);