mirror of
https://github.com/raysan5/raylib.git
synced 2025-09-20 10:18:14 +00:00

* Fixed the build for web using CMake. I found that the build for me was failing and I added some if defined checks in the core.c file where the glfwSetWindowAttrib was used. (error: implicit declaration of function 'glfwSetWindowAttrib' is invalid in C99 [-Werror,-Wimplicit-function-declaration]) I also changed some values in the toolchain file so that it correctly uses the .bat files when on windows. * Cleaned up the additional variables (they are not important) * Added more improvements to cmakelists Added the option to use the system provided Emscripten toolchain to be more uniform with other libraries. Fixed and issue which prevented example being built from cmake and also building with html extensions properly. * Fixed ENUM to STRING because of a missed warning
6184 lines
242 KiB
C
6184 lines
242 KiB
C
/**********************************************************************************************
|
|
*
|
|
* raylib.core - Basic functions to manage windows, OpenGL context and input on multiple platforms
|
|
*
|
|
* PLATFORMS SUPPORTED:
|
|
* - PLATFORM_DESKTOP: Windows (Win32, Win64)
|
|
* - PLATFORM_DESKTOP: Linux (X11 desktop mode)
|
|
* - PLATFORM_DESKTOP: FreeBSD, OpenBSD, NetBSD, DragonFly (X11 desktop)
|
|
* - PLATFORM_DESKTOP: OSX/macOS
|
|
* - PLATFORM_ANDROID: Android 4.0 (ARM, ARM64)
|
|
* - PLATFORM_RPI: Raspberry Pi 0,1,2,3,4 (Raspbian)
|
|
* - PLATFORM_DRM: Linux native mode, including Raspberry Pi 4 with V3D fkms driver
|
|
* - PLATFORM_WEB: HTML5 with asm.js (Chrome, Firefox)
|
|
* - PLATFORM_UWP: Windows 10 App, Windows Phone, Xbox One
|
|
*
|
|
* CONFIGURATION:
|
|
*
|
|
* #define PLATFORM_DESKTOP
|
|
* Windowing and input system configured for desktop platforms: Windows, Linux, OSX, FreeBSD, OpenBSD, NetBSD, DragonFly
|
|
* NOTE: Oculus Rift CV1 requires PLATFORM_DESKTOP for mirror rendering - View [rlgl] module to enable it
|
|
*
|
|
* #define PLATFORM_ANDROID
|
|
* Windowing and input system configured for Android device, app activity managed internally in this module.
|
|
* NOTE: OpenGL ES 2.0 is required and graphic device is managed by EGL
|
|
*
|
|
* #define PLATFORM_RPI
|
|
* Windowing and input system configured for Raspberry Pi i native mode (no X.org required, tested on Raspbian),
|
|
* graphic device is managed by EGL and inputs are processed is raw mode, reading from /dev/input/
|
|
*
|
|
* #define PLATFORM_WEB
|
|
* Windowing and input system configured for HTML5 (run on browser), code converted from C to asm.js
|
|
* using emscripten compiler. OpenGL ES 2.0 required for direct translation to WebGL equivalent code.
|
|
*
|
|
* #define PLATFORM_UWP
|
|
* Universal Windows Platform support, using OpenGL ES 2.0 through ANGLE on multiple Windows platforms,
|
|
* including Windows 10 App, Windows Phone and Xbox One platforms.
|
|
*
|
|
* #define SUPPORT_DEFAULT_FONT (default)
|
|
* 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
|
|
* Camera module is included (camera.h) and multiple predefined cameras are available: free, 1st/3rd person, orbital
|
|
*
|
|
* #define SUPPORT_GESTURES_SYSTEM
|
|
* Gestures module is included (gestures.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.
|
|
*
|
|
* #define SUPPORT_TOUCH_AS_MOUSE
|
|
* Touch input and mouse input are shared. Mouse functions also return touch information.
|
|
*
|
|
* #define SUPPORT_SSH_KEYBOARD_RPI (Raspberry Pi only)
|
|
* Reconfigure standard input to receive key inputs, works with SSH connection.
|
|
* WARNING: Reconfiguring standard input could lead to undesired effects, like breaking other running processes or
|
|
* blocking the device is not restored properly. Use with care.
|
|
*
|
|
* #define SUPPORT_MOUSE_CURSOR_NATIVE (Raspberry Pi and DRM only)
|
|
* Draw a mouse pointer on screen
|
|
*
|
|
* #define SUPPORT_BUSY_WAIT_LOOP
|
|
* Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used
|
|
*
|
|
* #define SUPPORT_HALFBUSY_WAIT_LOOP
|
|
* Use a half-busy wait loop, in this case frame sleeps for some time and runs a busy-wait-loop at the end
|
|
*
|
|
* #define SUPPORT_EVENTS_WAITING
|
|
* Wait for events passively (sleeping while no events) instead of polling them actively every frame
|
|
*
|
|
* #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
|
|
* for linkage
|
|
*
|
|
* #define SUPPORT_DATA_STORAGE
|
|
* Support saving binary data automatically to a generated storage.data file. This file is managed internally.
|
|
*
|
|
* DEPENDENCIES:
|
|
* rglfw - Manage graphic device, OpenGL context and inputs on PLATFORM_DESKTOP (Windows, Linux, OSX. FreeBSD, OpenBSD, NetBSD, DragonFly)
|
|
* raymath - 3D math functionality (Vector2, Vector3, Matrix, Quaternion)
|
|
* camera - Multiple 3D camera modes (free, orbital, 1st person, 3rd person)
|
|
* gestures - Gestures system for touch-ready devices (or simulated from mouse inputs)
|
|
*
|
|
*
|
|
* LICENSE: zlib/libpng
|
|
*
|
|
* Copyright (c) 2013-2020 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" // 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
|
|
#else
|
|
#define RAYLIB_VERSION "3.1-dev"
|
|
#endif
|
|
|
|
#include "utils.h" // Required for: TRACELOG macros
|
|
|
|
#if (defined(__linux__) || defined(PLATFORM_WEB)) && _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
|
|
|
|
#define RAYMATH_IMPLEMENTATION // Define external out-of-line implementation of raymath here
|
|
#include "raymath.h" // Required for: Vector3 and Matrix functions
|
|
|
|
#define RLGL_IMPLEMENTATION
|
|
#include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3+ or ES2
|
|
|
|
#if defined(SUPPORT_GESTURES_SYSTEM)
|
|
#define GESTURES_IMPLEMENTATION
|
|
#include "gestures.h" // Gestures detection functionality
|
|
#endif
|
|
|
|
#if defined(SUPPORT_CAMERA_SYSTEM)
|
|
#define CAMERA_IMPLEMENTATION
|
|
#include "camera.h" // Camera system functionality
|
|
#endif
|
|
|
|
#if defined(SUPPORT_GIF_RECORDING)
|
|
//#define MSF_GIF_MALLOC RL_MALLOC
|
|
//#define MSF_GIF_FREE RL_FREE
|
|
|
|
#define MSF_GIF_IMPL
|
|
#include "external/msf_gif.h" // Support GIF recording
|
|
#endif
|
|
|
|
#include <stdlib.h> // Required for: srand(), rand(), atexit()
|
|
#include <stdio.h> // Required for: sprintf() [Used in OpenURL()]
|
|
#include <string.h> // Required for: strrchr(), strcmp(), strlen()
|
|
#include <time.h> // Required for: time() [Used in InitTimer()]
|
|
#include <math.h> // Required for: tan() [Used in BeginMode3D()]
|
|
|
|
#include <sys/stat.h> // Required for: stat() [Used in GetFileModTime()]
|
|
|
|
#if (defined(PLATFORM_DESKTOP) || defined(PLATFORM_UWP)) && defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__))
|
|
#define DIRENT_MALLOC RL_MALLOC
|
|
#define DIRENT_FREE RL_FREE
|
|
|
|
#include "external/dirent.h" // Required for: DIR, opendir(), closedir() [Used in GetDirectoryFiles()]
|
|
#else
|
|
#include <dirent.h> // Required for: DIR, opendir(), closedir() [Used in GetDirectoryFiles()]
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
#include <direct.h> // Required for: _getch(), _chdir()
|
|
#define GETCWD _getcwd // NOTE: MSDN recommends not to use getcwd(), chdir()
|
|
#define CHDIR _chdir
|
|
#include <io.h> // Required for _access() [Used in FileExists()]
|
|
#else
|
|
#include <unistd.h> // Required for: getch(), chdir() (POSIX), access()
|
|
#define GETCWD getcwd
|
|
#define CHDIR chdir
|
|
#endif
|
|
|
|
#if defined(PLATFORM_DESKTOP)
|
|
#define GLFW_INCLUDE_NONE // Disable the standard OpenGL header inclusion on GLFW3
|
|
// NOTE: Already provided by rlgl implementation (on glad.h)
|
|
#include <GLFW/glfw3.h> // GLFW3 library: Windows, OpenGL context and Input management
|
|
// NOTE: GLFW3 already includes gl.h (OpenGL) headers
|
|
|
|
// Support retrieving native window handlers
|
|
#if defined(_WIN32)
|
|
#define GLFW_EXPOSE_NATIVE_WIN32
|
|
#include <GLFW/glfw3native.h> // WARNING: It requires customization to avoid windows.h inclusion!
|
|
|
|
#if !defined(SUPPORT_BUSY_WAIT_LOOP)
|
|
// NOTE: Those functions require linking with winmm library
|
|
unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod);
|
|
unsigned int __stdcall timeEndPeriod(unsigned int uPeriod);
|
|
#endif
|
|
|
|
#elif defined(__linux__)
|
|
#include <sys/time.h> // Required for: timespec, nanosleep(), select() - POSIX
|
|
|
|
//#define GLFW_EXPOSE_NATIVE_X11 // WARNING: Exposing Xlib.h > X.h results in dup symbols for Font type
|
|
//#define GLFW_EXPOSE_NATIVE_WAYLAND
|
|
//#define GLFW_EXPOSE_NATIVE_MIR
|
|
#include <GLFW/glfw3native.h> // Required for: glfwGetX11Window()
|
|
#elif defined(__APPLE__)
|
|
#include <unistd.h> // Required for: usleep()
|
|
|
|
//#define GLFW_EXPOSE_NATIVE_COCOA // WARNING: Fails due to type redefinition
|
|
#include <GLFW/glfw3native.h> // Required for: glfwGetCocoaWindow()
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(PLATFORM_ANDROID)
|
|
//#include <android/sensor.h> // Android sensors functions (accelerometer, gyroscope, light...)
|
|
#include <android/window.h> // Defines AWINDOW_FLAG_FULLSCREEN and others
|
|
#include <android_native_app_glue.h> // Defines basic app state struct and manages activity
|
|
|
|
#include <EGL/egl.h> // EGL library - Native platform display device control functions
|
|
#include <GLES2/gl2.h> // OpenGL ES 2.0 library
|
|
#endif
|
|
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
#include <fcntl.h> // POSIX file control definitions - open(), creat(), fcntl()
|
|
#include <unistd.h> // POSIX standard function definitions - read(), close(), STDIN_FILENO
|
|
#include <termios.h> // POSIX terminal control definitions - tcgetattr(), tcsetattr()
|
|
#include <pthread.h> // POSIX threads management (inputs reading)
|
|
#include <dirent.h> // POSIX directory browsing
|
|
|
|
#include <sys/ioctl.h> // UNIX System call for device-specific input/output operations - ioctl()
|
|
#include <linux/kd.h> // Linux: KDSKBMODE, K_MEDIUMRAM constants definition
|
|
#include <linux/input.h> // Linux: Keycodes constants definition (KEY_A, ...)
|
|
#include <linux/joystick.h> // Linux: Joystick support library
|
|
|
|
#if defined(PLATFORM_RPI)
|
|
#include "bcm_host.h" // Raspberry Pi VideoCore IV access functions
|
|
#endif
|
|
|
|
#if defined(PLATFORM_DRM)
|
|
#include <gbm.h> // Generic Buffer Management
|
|
#include <xf86drm.h> // Direct Rendering Manager user-level library interface
|
|
#include <xf86drmMode.h> // Direct Rendering Manager modesetting interface
|
|
#endif
|
|
|
|
#include "EGL/egl.h" // EGL library - Native platform display device control functions
|
|
#include "EGL/eglext.h" // EGL library - Extensions
|
|
#include "GLES2/gl2.h" // OpenGL ES 2.0 library
|
|
#endif
|
|
|
|
#if defined(PLATFORM_UWP)
|
|
#include "EGL/egl.h" // EGL library - Native platform display device control functions
|
|
#include "EGL/eglext.h" // EGL library - Extensions
|
|
#include "GLES2/gl2.h" // OpenGL ES 2.0 library
|
|
#include "uwp_events.h" // UWP bootstrapping functions
|
|
#endif
|
|
|
|
#if defined(PLATFORM_WEB)
|
|
#define GLFW_INCLUDE_ES2 // GLFW3: Enable OpenGL ES 2.0 (translated to WebGL)
|
|
#include <GLFW/glfw3.h> // GLFW3 library: Windows, OpenGL context and Input management
|
|
#include <sys/time.h> // Required for: timespec, nanosleep(), select() - POSIX
|
|
|
|
#include <emscripten/emscripten.h> // Emscripten library - LLVM to JavaScript compiler
|
|
#include <emscripten/html5.h> // Emscripten HTML5 library
|
|
#endif
|
|
|
|
#if defined(SUPPORT_COMPRESSION_API)
|
|
// NOTE: Those declarations require stb_image and stb_image_write definitions, included in textures module
|
|
unsigned char *stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality);
|
|
char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen);
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Defines and Macros
|
|
//----------------------------------------------------------------------------------
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
#define USE_LAST_TOUCH_DEVICE // When multiple touchscreens are connected, only use the one with the highest event<N> number
|
|
|
|
#define DEFAULT_GAMEPAD_DEV "/dev/input/js" // Gamepad input (base dev for all gamepads: js0, js1, ...)
|
|
#define DEFAULT_EVDEV_PATH "/dev/input/" // Path to the linux input events
|
|
#endif
|
|
|
|
#ifndef MAX_FILEPATH_LENGTH
|
|
#if defined(__linux__)
|
|
#define MAX_FILEPATH_LENGTH 4096 // Maximum length for filepaths (Linux PATH_MAX default value)
|
|
#else
|
|
#define MAX_FILEPATH_LENGTH 512 // Maximum length supported for filepaths
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef MAX_GAMEPADS
|
|
#define MAX_GAMEPADS 4 // Max number of gamepads supported
|
|
#endif
|
|
#ifndef MAX_GAMEPAD_AXIS
|
|
#define MAX_GAMEPAD_AXIS 8 // Max number of axis supported (per gamepad)
|
|
#endif
|
|
#ifndef MAX_GAMEPAD_BUTTONS
|
|
#define MAX_GAMEPAD_BUTTONS 32 // Max bumber of buttons supported (per gamepad)
|
|
#endif
|
|
#ifndef MAX_TOUCH_POINTS
|
|
#define MAX_TOUCH_POINTS 10 // Maximum number of touch points supported
|
|
#endif
|
|
#ifndef MAX_KEY_PRESSED_QUEUE
|
|
#define MAX_KEY_PRESSED_QUEUE 16 // Max number of characters in the key input queue
|
|
#endif
|
|
|
|
#if defined(SUPPORT_DATA_STORAGE)
|
|
#ifndef STORAGE_DATA_FILE
|
|
#define STORAGE_DATA_FILE "storage.data" // Automatic storage filename
|
|
#endif
|
|
#endif
|
|
|
|
// Flags operation macros
|
|
#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))
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Types and Structures Definition
|
|
//----------------------------------------------------------------------------------
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
typedef struct {
|
|
pthread_t threadId; // Event reading thread id
|
|
int fd; // File descriptor to the device it is assigned to
|
|
int eventNum; // Number of 'event<N>' device
|
|
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 isMouse; // True if device supports relative X Y movements
|
|
bool isTouch; // True if device supports absolute X Y movements and has BTN_TOUCH
|
|
bool isMultitouch; // True if device supports multiple absolute movevents and has BTN_TOUCH
|
|
bool isKeyboard; // True if device has letter keycodes
|
|
bool isGamepad; // True if device has gamepad buttons
|
|
} InputEventWorker;
|
|
|
|
typedef struct {
|
|
int contents[8]; // Key events FIFO contents (8 positions)
|
|
char head; // Key events FIFO head position
|
|
char tail; // Key events FIFO tail position
|
|
} KeyEventFifo;
|
|
#endif
|
|
|
|
typedef struct { int x; int y; } Point;
|
|
typedef struct { unsigned int width; unsigned int height; } Size;
|
|
|
|
// Core global state context data
|
|
typedef struct CoreData {
|
|
struct {
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
|
|
GLFWwindow *handle; // Native window handle (graphic device)
|
|
#endif
|
|
#if defined(PLATFORM_RPI)
|
|
EGL_DISPMANX_WINDOW_T handle; // Native window handle (graphic device)
|
|
#endif
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP)
|
|
#if defined(PLATFORM_DRM)
|
|
int fd; // /dev/dri/... file descriptor
|
|
drmModeConnector *connector; // Direct Rendering Manager (DRM) mode connector
|
|
int modeIndex; // index of the used mode of connector->modes
|
|
drmModeCrtc *crtc; // crt controller
|
|
struct gbm_device *gbmDevice; // device of Generic Buffer Management (GBM, native platform for EGL on DRM)
|
|
struct gbm_surface *gbmSurface; // surface of GBM
|
|
struct gbm_bo *prevBO; // previous used GBM buffer object (during frame swapping)
|
|
uint32_t prevFB; // previous used GBM framebufer (during frame swapping)
|
|
#endif
|
|
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
|
|
#endif
|
|
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
|
|
|
|
Point position; // Window position on screen (required on fullscreen toggle)
|
|
Size display; // Display width and height (monitor, device-screen, LCD, ...)
|
|
Size screen; // Screen width and height (used render area)
|
|
Size currentFbo; // Current render width and height, it could change on BeginTextureMode()
|
|
Size render; // Framebuffer width and height (render area, including black bars if required)
|
|
Point renderOffset; // Offset from render area (must be divided by 2)
|
|
Matrix screenScale; // Matrix to scale screen (framebuffer rendering)
|
|
|
|
char **dropFilesPath; // Store dropped files paths as strings
|
|
int dropFilesCount; // Count dropped files strings
|
|
|
|
} Window;
|
|
#if defined(PLATFORM_ANDROID)
|
|
struct {
|
|
bool appEnabled; // Flag to detect if app is active ** = true
|
|
struct android_app *app; // Android activity
|
|
struct android_poll_source *source; // Android events polling source
|
|
const char *internalDataPath; // Android internal data path to write data (/data/data/<package>/files)
|
|
bool contextRebindRequired; // Used to know context rebind required
|
|
} Android;
|
|
#endif
|
|
#if defined(PLATFORM_UWP)
|
|
struct {
|
|
const char *internalDataPath; // UWP App data path
|
|
} UWP;
|
|
#endif
|
|
struct {
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
InputEventWorker eventWorker[10]; // List of worker threads for every monitored "/dev/input/event<N>"
|
|
#endif
|
|
struct {
|
|
int exitKey; // Default exit key
|
|
char currentKeyState[512]; // Registers current frame key state
|
|
char previousKeyState[512]; // Registers previous frame key state
|
|
|
|
int keyPressedQueue[MAX_KEY_PRESSED_QUEUE]; // Input characters queue
|
|
int keyPressedQueueCount; // Input characters queue count
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
int defaultMode; // Default keyboard mode
|
|
struct termios defaultSettings; // Default keyboard settings
|
|
KeyEventFifo lastKeyPressed; // Buffer for holding keydown events as they arrive (Needed due to multitreading of event workers)
|
|
#endif
|
|
} Keyboard;
|
|
struct {
|
|
Vector2 position; // Mouse position on screen
|
|
Vector2 offset; // Mouse offset
|
|
Vector2 scale; // Mouse scaling
|
|
|
|
int cursor; // Tracks current mouse cursor
|
|
bool cursorHidden; // Track if cursor is hidden
|
|
bool cursorOnScreen; // Tracks if cursor is inside client area
|
|
|
|
char currentButtonState[3]; // Registers current mouse button state
|
|
char previousButtonState[3]; // Registers previous mouse button state
|
|
float currentWheelMove; // Registers current mouse wheel variation
|
|
float previousWheelMove; // Registers previous mouse wheel variation
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
char currentButtonStateEvdev[3]; // Holds the new mouse state for the next polling event to grab (Can't be written directly due to multithreading, app could miss the update)
|
|
#endif
|
|
} Mouse;
|
|
struct {
|
|
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
|
|
} Touch;
|
|
struct {
|
|
int lastButtonPressed; // Register last gamepad button pressed
|
|
int axisCount; // Register number of available gamepad axis
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP)
|
|
bool ready[MAX_GAMEPADS]; // Flag to know if gamepad is ready
|
|
float axisState[MAX_GAMEPADS][MAX_GAMEPAD_AXIS]; // Gamepad axis state
|
|
char currentState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Current gamepad buttons state
|
|
char previousState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Previous gamepad buttons state
|
|
#endif
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
pthread_t threadId; // Gamepad reading thread id
|
|
int streamId[MAX_GAMEPADS]; // Gamepad device file descriptor
|
|
char name[64]; // Gamepad name holder
|
|
#endif
|
|
} Gamepad;
|
|
} Input;
|
|
struct {
|
|
double current; // Current time measure
|
|
double previous; // Previous time measure
|
|
double update; // Time measure for frame update
|
|
double draw; // Time measure for frame draw
|
|
double frame; // Time measure for one frame
|
|
double target; // Desired time for one frame, if 0 not applied
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP)
|
|
unsigned long long base; // Base time measure for hi-res timer
|
|
#endif
|
|
} Time;
|
|
} CoreData;
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Global Variables Definition
|
|
//----------------------------------------------------------------------------------
|
|
static CoreData CORE = { 0 }; // Global CORE state context
|
|
|
|
static char **dirFilesPath = NULL; // Store directory files paths as strings
|
|
static int dirFilesCount = 0; // Count directory files strings
|
|
|
|
#if defined(SUPPORT_SCREEN_CAPTURE)
|
|
static int screenshotCounter = 0; // Screenshots counter
|
|
#endif
|
|
|
|
#if defined(SUPPORT_GIF_RECORDING)
|
|
static int gifFramesCounter = 0; // GIF frames counter
|
|
static bool gifRecording = false; // GIF recording state
|
|
static MsfGifState gifState = { 0 }; // MSGIF context state
|
|
#endif
|
|
//-----------------------------------------------------------------------------------
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Other Modules Functions Declaration (required by core)
|
|
//----------------------------------------------------------------------------------
|
|
#if defined(SUPPORT_DEFAULT_FONT)
|
|
extern void LoadFontDefault(void); // [Module: text] Loads default font on InitWindow()
|
|
extern void UnloadFontDefault(void); // [Module: text] Unloads default font from GPU memory
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Module specific Functions Declaration
|
|
//----------------------------------------------------------------------------------
|
|
static bool InitGraphicsDevice(int width, int height); // Initialize graphics device
|
|
static void SetupFramebuffer(int width, int height); // Setup main framebuffer
|
|
static void SetupViewport(int width, int height); // Set viewport for a provided width and height
|
|
static void SwapBuffers(void); // Copy back buffer to front buffers
|
|
|
|
static void InitTimer(void); // Initialize timer
|
|
static void Wait(float ms); // Wait for some milliseconds (stop program execution)
|
|
|
|
static int GetGamepadButton(int button); // Get gamepad button generic to all platforms
|
|
static void PollInputEvents(void); // Register user events
|
|
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
|
|
static void ErrorCallback(int error, const char *description); // GLFW3 Error Callback, runs on GLFW3 error
|
|
static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods); // GLFW3 Keyboard Callback, runs on key pressed
|
|
static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods); // GLFW3 Mouse Button Callback, runs on mouse button pressed
|
|
static void MouseCursorPosCallback(GLFWwindow *window, double x, double y); // GLFW3 Cursor Position Callback, runs on mouse move
|
|
static void CharCallback(GLFWwindow *window, unsigned int key); // GLFW3 Char Key Callback, runs on key pressed (get char value)
|
|
static void ScrollCallback(GLFWwindow *window, double xoffset, double yoffset); // GLFW3 Srolling Callback, runs on mouse wheel
|
|
static void CursorEnterCallback(GLFWwindow *window, int enter); // GLFW3 Cursor Enter Callback, cursor enters client area
|
|
static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized
|
|
static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored
|
|
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 WindowMaximizeCallback(GLFWwindow *window, int maximized); // GLFW3 Window Maximize Callback, runs when window is maximized
|
|
#endif
|
|
|
|
#if defined(PLATFORM_ANDROID)
|
|
static void AndroidCommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands
|
|
static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); // Process Android inputs
|
|
#endif
|
|
|
|
#if defined(PLATFORM_WEB)
|
|
static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData);
|
|
static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const void *reserved, void *userData);
|
|
static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData);
|
|
static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData);
|
|
static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData);
|
|
static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData);
|
|
#endif
|
|
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
#if defined(SUPPORT_SSH_KEYBOARD_RPI)
|
|
static void InitKeyboard(void); // Init raw keyboard system (standard input reading)
|
|
static void ProcessKeyboard(void); // Process keyboard events
|
|
static void RestoreKeyboard(void); // Restore keyboard system
|
|
#else
|
|
static void InitTerminal(void); // Init terminal (block echo and signal short cuts)
|
|
static void RestoreTerminal(void); // Restore terminal
|
|
#endif
|
|
|
|
static void InitEvdevInput(void); // Evdev inputs initialization
|
|
static void EventThreadSpawn(char *device); // Identifies a input device and spawns a thread to handle it if needed
|
|
static void *EventThread(void *arg); // Input device events reading thread
|
|
|
|
static void InitGamepad(void); // Init raw gamepad input
|
|
static void *GamepadThread(void *arg); // Mouse reading thread
|
|
|
|
#if defined(PLATFORM_DRM)
|
|
static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode); // Search matching DRM mode in connector's mode list
|
|
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
|
|
#endif
|
|
|
|
#endif // PLATFORM_RPI || PLATFORM_DRM
|
|
|
|
#if defined(_WIN32)
|
|
// NOTE: We include Sleep() function signature here to avoid windows.h inclusion
|
|
void __stdcall Sleep(unsigned long msTimeout); // Required for Wait()
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Module Functions Definition - Window and OpenGL Context Functions
|
|
//----------------------------------------------------------------------------------
|
|
|
|
#if defined(PLATFORM_ANDROID)
|
|
// To allow easier porting to android, we allow the user to define a
|
|
// main function which we call from android_main, defined by ourselves
|
|
extern int main(int argc, char *argv[]);
|
|
|
|
void android_main(struct android_app *app)
|
|
{
|
|
char arg0[] = "raylib"; // NOTE: argv[] are mutable
|
|
CORE.Android.app = app;
|
|
|
|
// TODO: Should we maybe report != 0 return codes somewhere?
|
|
(void)main(1, (char *[]) { arg0, NULL });
|
|
}
|
|
|
|
// TODO: Add this to header (if apps really need it)
|
|
struct android_app *GetAndroidApp(void)
|
|
{
|
|
return CORE.Android.app;
|
|
}
|
|
#endif
|
|
#if (defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) && !defined(SUPPORT_SSH_KEYBOARD_RPI)
|
|
// Init terminal (block echo and signal short cuts)
|
|
static void InitTerminal(void)
|
|
{
|
|
TRACELOG(LOG_INFO, "RPI: Reconfiguring terminal...");
|
|
|
|
// Save terminal keyboard settings and reconfigure terminal with new settings
|
|
struct termios keyboardNewSettings;
|
|
tcgetattr(STDIN_FILENO, &CORE.Input.Keyboard.defaultSettings); // Get current keyboard settings
|
|
keyboardNewSettings = CORE.Input.Keyboard.defaultSettings;
|
|
|
|
// New terminal settings for keyboard: turn off buffering (non-canonical mode), echo
|
|
// NOTE: ISIG controls if ^C and ^Z generate break signals or not
|
|
keyboardNewSettings.c_lflag &= ~(ICANON | ECHO | ISIG);
|
|
keyboardNewSettings.c_cc[VMIN] = 1;
|
|
keyboardNewSettings.c_cc[VTIME] = 0;
|
|
|
|
// Set new keyboard settings (change occurs immediately)
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings);
|
|
|
|
// Save old keyboard mode to restore it at the end
|
|
if (ioctl(STDIN_FILENO, KDGKBMODE, &CORE.Input.Keyboard.defaultMode) < 0)
|
|
{
|
|
// NOTE: It could mean we are using a remote keyboard through ssh or from the desktop
|
|
TRACELOG(LOG_WARNING, "RPI: Failed to change keyboard mode (not a local terminal)");
|
|
}
|
|
else ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE);
|
|
|
|
// Register terminal restore when program finishes
|
|
atexit(RestoreTerminal);
|
|
}
|
|
// Restore terminal
|
|
static void RestoreTerminal(void)
|
|
{
|
|
TRACELOG(LOG_INFO, "RPI: Restoring terminal...");
|
|
|
|
// Reset to default keyboard settings
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &CORE.Input.Keyboard.defaultSettings);
|
|
|
|
// Reconfigure keyboard to default mode
|
|
ioctl(STDIN_FILENO, KDSKBMODE, CORE.Input.Keyboard.defaultMode);
|
|
}
|
|
#endif
|
|
// Initialize window and OpenGL context
|
|
// NOTE: data parameter could be used to pass any kind of required data to the initialization
|
|
void InitWindow(int width, int height, const char *title)
|
|
{
|
|
#if defined(PLATFORM_UWP)
|
|
if (!UWPIsConfigured())
|
|
{
|
|
TRACELOG(LOG_ERROR, "UWP Functions have not been set yet, please set these before initializing raylib!");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
TRACELOG(LOG_INFO, "Initializing raylib %s", RAYLIB_VERSION);
|
|
|
|
if ((title != NULL) && (title[0] != 0)) CORE.Window.title = title;
|
|
|
|
// Initialize required global values different than 0
|
|
CORE.Input.Keyboard.exitKey = KEY_ESCAPE;
|
|
CORE.Input.Mouse.scale = (Vector2){ 1.0f, 1.0f };
|
|
CORE.Input.Mouse.cursor = MOUSE_CURSOR_ARROW;
|
|
CORE.Input.Gamepad.lastButtonPressed = -1;
|
|
|
|
#if defined(PLATFORM_UWP)
|
|
// The axis count is 6 (2 thumbsticks and left and right trigger)
|
|
CORE.Input.Gamepad.axisCount = 6;
|
|
#endif
|
|
|
|
#if defined(PLATFORM_ANDROID)
|
|
CORE.Window.screen.width = width;
|
|
CORE.Window.screen.height = height;
|
|
CORE.Window.currentFbo.width = width;
|
|
CORE.Window.currentFbo.height = height;
|
|
|
|
// Input data is android app pointer
|
|
CORE.Android.internalDataPath = CORE.Android.app->activity->internalDataPath;
|
|
|
|
// Set desired windows flags before initializing anything
|
|
ANativeActivity_setWindowFlags(CORE.Android.app->activity, AWINDOW_FLAG_FULLSCREEN, 0); //AWINDOW_FLAG_SCALED, AWINDOW_FLAG_DITHER
|
|
|
|
int orientation = AConfiguration_getOrientation(CORE.Android.app->config);
|
|
|
|
if (orientation == ACONFIGURATION_ORIENTATION_PORT) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as portrait");
|
|
else if (orientation == ACONFIGURATION_ORIENTATION_LAND) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as landscape");
|
|
|
|
// TODO: Automatic orientation doesn't seem to work
|
|
if (width <= height)
|
|
{
|
|
AConfiguration_setOrientation(CORE.Android.app->config, ACONFIGURATION_ORIENTATION_PORT);
|
|
TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to portrait");
|
|
}
|
|
else
|
|
{
|
|
AConfiguration_setOrientation(CORE.Android.app->config, ACONFIGURATION_ORIENTATION_LAND);
|
|
TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to landscape");
|
|
}
|
|
|
|
//AConfiguration_getDensity(CORE.Android.app->config);
|
|
//AConfiguration_getKeyboard(CORE.Android.app->config);
|
|
//AConfiguration_getScreenSize(CORE.Android.app->config);
|
|
//AConfiguration_getScreenLong(CORE.Android.app->config);
|
|
|
|
CORE.Android.app->onAppCmd = AndroidCommandCallback;
|
|
CORE.Android.app->onInputEvent = AndroidInputCallback;
|
|
|
|
InitAssetManager(CORE.Android.app->activity->assetManager, CORE.Android.app->activity->internalDataPath);
|
|
|
|
TRACELOG(LOG_INFO, "ANDROID: App initialized successfully");
|
|
|
|
// Android ALooper_pollAll() variables
|
|
int pollResult = 0;
|
|
int pollEvents = 0;
|
|
|
|
// Wait for window to be initialized (display and context)
|
|
while (!CORE.Window.ready)
|
|
{
|
|
// Process events loop
|
|
while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void**)&CORE.Android.source)) >= 0)
|
|
{
|
|
// Process this event
|
|
if (CORE.Android.source != NULL) CORE.Android.source->process(CORE.Android.app, CORE.Android.source);
|
|
|
|
// NOTE: Never close window, native activity is controlled by the system!
|
|
//if (CORE.Android.app->destroyRequested != 0) CORE.Window.shouldClose = true;
|
|
}
|
|
}
|
|
#endif
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) || defined(PLATFORM_DRM)
|
|
// Init graphics device (display device and OpenGL context)
|
|
// NOTE: returns true if window and graphic device has been initialized successfully
|
|
CORE.Window.ready = InitGraphicsDevice(width, height);
|
|
|
|
if (!CORE.Window.ready) return;
|
|
|
|
// Init hi-res timer
|
|
InitTimer();
|
|
|
|
#if defined(SUPPORT_DEFAULT_FONT)
|
|
// Load default font
|
|
// NOTE: External functions (defined in module: text)
|
|
LoadFontDefault();
|
|
Rectangle rec = GetFontDefault().recs[95];
|
|
// NOTE: We setup a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering
|
|
SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 });
|
|
#endif
|
|
#if defined(PLATFORM_DESKTOP)
|
|
if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0)
|
|
{
|
|
// Set default font texture filter for HighDPI (blurry)
|
|
SetTextureFilter(GetFontDefault().texture, FILTER_BILINEAR);
|
|
}
|
|
#endif
|
|
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
// Init raw input system
|
|
InitEvdevInput(); // Evdev inputs initialization
|
|
InitGamepad(); // Gamepad init
|
|
#if defined(SUPPORT_SSH_KEYBOARD_RPI)
|
|
InitKeyboard(); // Keyboard init
|
|
#else
|
|
InitTerminal(); // Terminal init
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(PLATFORM_WEB)
|
|
// Detect fullscreen change events
|
|
emscripten_set_fullscreenchange_callback("#canvas", NULL, 1, EmscriptenFullscreenChangeCallback);
|
|
|
|
// Support keyboard events
|
|
//emscripten_set_keypress_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback);
|
|
emscripten_set_keydown_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback);
|
|
|
|
// Support mouse events
|
|
emscripten_set_click_callback("#canvas", NULL, 1, EmscriptenMouseCallback);
|
|
|
|
// Support touch events
|
|
emscripten_set_touchstart_callback("#canvas", NULL, 1, EmscriptenTouchCallback);
|
|
emscripten_set_touchend_callback("#canvas", NULL, 1, EmscriptenTouchCallback);
|
|
emscripten_set_touchmove_callback("#canvas", NULL, 1, EmscriptenTouchCallback);
|
|
emscripten_set_touchcancel_callback("#canvas", NULL, 1, EmscriptenTouchCallback);
|
|
|
|
// Support gamepad events (not provided by GLFW3 on emscripten)
|
|
emscripten_set_gamepadconnected_callback(NULL, 1, EmscriptenGamepadCallback);
|
|
emscripten_set_gamepaddisconnected_callback(NULL, 1, EmscriptenGamepadCallback);
|
|
#endif
|
|
|
|
CORE.Input.Mouse.position.x = (float)CORE.Window.screen.width/2.0f;
|
|
CORE.Input.Mouse.position.y = (float)CORE.Window.screen.height/2.0f;
|
|
#endif // PLATFORM_ANDROID
|
|
}
|
|
|
|
// 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_DEFAULT_FONT)
|
|
UnloadFontDefault();
|
|
#endif
|
|
|
|
rlglClose(); // De-init rlgl
|
|
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
|
|
glfwDestroyWindow(CORE.Window.handle);
|
|
glfwTerminate();
|
|
#endif
|
|
|
|
#if !defined(SUPPORT_BUSY_WAIT_LOOP) && defined(_WIN32) && !defined(PLATFORM_UWP)
|
|
timeEndPeriod(1); // Restore time period
|
|
#endif
|
|
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP)
|
|
#if defined(PLATFORM_DRM)
|
|
if (CORE.Window.prevFB)
|
|
{
|
|
drmModeRmFB(CORE.Window.fd, CORE.Window.prevFB);
|
|
CORE.Window.prevFB = 0;
|
|
}
|
|
|
|
if (CORE.Window.prevBO)
|
|
{
|
|
gbm_surface_release_buffer(CORE.Window.gbmSurface, CORE.Window.prevBO);
|
|
CORE.Window.prevBO = NULL;
|
|
}
|
|
|
|
if (CORE.Window.gbmSurface)
|
|
{
|
|
gbm_surface_destroy(CORE.Window.gbmSurface);
|
|
CORE.Window.gbmSurface = NULL;
|
|
}
|
|
|
|
if (CORE.Window.gbmDevice)
|
|
{
|
|
gbm_device_destroy(CORE.Window.gbmDevice);
|
|
CORE.Window.gbmDevice = NULL;
|
|
}
|
|
|
|
if (CORE.Window.crtc)
|
|
{
|
|
if (CORE.Window.connector)
|
|
{
|
|
drmModeSetCrtc(CORE.Window.fd, CORE.Window.crtc->crtc_id, CORE.Window.crtc->buffer_id,
|
|
CORE.Window.crtc->x, CORE.Window.crtc->y, &CORE.Window.connector->connector_id, 1, &CORE.Window.crtc->mode);
|
|
drmModeFreeConnector(CORE.Window.connector);
|
|
CORE.Window.connector = NULL;
|
|
}
|
|
|
|
drmModeFreeCrtc(CORE.Window.crtc);
|
|
CORE.Window.crtc = NULL;
|
|
}
|
|
|
|
if (CORE.Window.fd != -1)
|
|
{
|
|
close(CORE.Window.fd);
|
|
CORE.Window.fd = -1;
|
|
}
|
|
#endif
|
|
|
|
// Close surface, context and display
|
|
if (CORE.Window.device != EGL_NO_DISPLAY)
|
|
{
|
|
#if !defined(PLATFORM_DRM)
|
|
eglMakeCurrent(CORE.Window.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
|
#endif
|
|
if (CORE.Window.surface != EGL_NO_SURFACE)
|
|
{
|
|
eglDestroySurface(CORE.Window.device, CORE.Window.surface);
|
|
CORE.Window.surface = EGL_NO_SURFACE;
|
|
}
|
|
|
|
if (CORE.Window.context != EGL_NO_CONTEXT)
|
|
{
|
|
eglDestroyContext(CORE.Window.device, CORE.Window.context);
|
|
CORE.Window.context = EGL_NO_CONTEXT;
|
|
}
|
|
|
|
eglTerminate(CORE.Window.device);
|
|
CORE.Window.device = EGL_NO_DISPLAY;
|
|
}
|
|
#endif
|
|
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
// Wait for mouse and gamepad threads to finish before closing
|
|
// NOTE: Those threads should already have finished at this point
|
|
// because they are controlled by CORE.Window.shouldClose variable
|
|
|
|
CORE.Window.shouldClose = true; // Added to force threads to exit when the close window is called
|
|
|
|
for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i)
|
|
{
|
|
if (CORE.Input.eventWorker[i].threadId)
|
|
{
|
|
pthread_join(CORE.Input.eventWorker[i].threadId, NULL);
|
|
}
|
|
}
|
|
|
|
if (CORE.Input.Gamepad.threadId) pthread_join(CORE.Input.Gamepad.threadId, NULL);
|
|
#endif
|
|
|
|
TRACELOG(LOG_INFO, "Window closed successfully");
|
|
}
|
|
|
|
// Check if KEY_ESCAPE pressed or Close icon pressed
|
|
bool WindowShouldClose(void)
|
|
{
|
|
#if defined(PLATFORM_WEB)
|
|
// Emterpreter-Async required to run sync code
|
|
// https://github.com/emscripten-core/emscripten/wiki/Emterpreter#emterpreter-async-run-synchronous-code
|
|
// By default, this function is never called on a web-ready raylib example because we encapsulate
|
|
// frame code in a UpdateDrawFrame() function, to allow browser manage execution asynchronously
|
|
// but now emscripten allows sync code to be executed in an interpreted way, using emterpreter!
|
|
emscripten_sleep(16);
|
|
return false;
|
|
#endif
|
|
|
|
#if defined(PLATFORM_DESKTOP)
|
|
if (CORE.Window.ready)
|
|
{
|
|
// While window minimized, stop loop execution
|
|
while (IsWindowState(FLAG_WINDOW_MINIMIZED) && !IsWindowState(FLAG_WINDOW_ALWAYS_RUN)) glfwWaitEvents();
|
|
|
|
CORE.Window.shouldClose = glfwWindowShouldClose(CORE.Window.handle);
|
|
|
|
// Reset close status for next frame
|
|
glfwSetWindowShouldClose(CORE.Window.handle, GLFW_FALSE);
|
|
|
|
return CORE.Window.shouldClose;
|
|
}
|
|
else return true;
|
|
#endif
|
|
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP)
|
|
if (CORE.Window.ready) return CORE.Window.shouldClose;
|
|
else return true;
|
|
#endif
|
|
}
|
|
|
|
// Check if window has been initialized successfully
|
|
bool IsWindowReady(void)
|
|
{
|
|
return CORE.Window.ready;
|
|
}
|
|
|
|
// Check if window is currently fullscreen
|
|
bool IsWindowFullscreen(void)
|
|
{
|
|
return CORE.Window.fullscreen;
|
|
}
|
|
|
|
// Check if window is currently hidden
|
|
bool IsWindowHidden(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
return ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0);
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
// Check if window has been minimized
|
|
bool IsWindowMinimized(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP)
|
|
return ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0);
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
// Check if window has been maximized (only PLATFORM_DESKTOP)
|
|
bool IsWindowMaximized(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
return ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0);
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
// Check if window has the focus
|
|
bool IsWindowFocused(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP)
|
|
return ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) == 0); // TODO!
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
// Check if window has been resizedLastFrame
|
|
bool IsWindowResized(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP)
|
|
return CORE.Window.resizedLastFrame;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
// Check if one specific window flag is enabled
|
|
bool IsWindowState(unsigned int flag)
|
|
{
|
|
return ((CORE.Window.flags & flag) > 0);
|
|
}
|
|
|
|
// Toggle fullscreen mode (only PLATFORM_DESKTOP)
|
|
void ToggleFullscreen(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
// NOTE: glfwSetWindowMonitor() doesn't work properly (bugs)
|
|
if (!CORE.Window.fullscreen)
|
|
{
|
|
// Store previous window position (in case we exit fullscreen)
|
|
glfwGetWindowPos(CORE.Window.handle, &CORE.Window.position.x, &CORE.Window.position.y);
|
|
|
|
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
|
|
if (!monitor)
|
|
{
|
|
TRACELOG(LOG_WARNING, "GLFW: Failed to get monitor");
|
|
glfwSetWindowMonitor(CORE.Window.handle, glfwGetPrimaryMonitor(), 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE);
|
|
return;
|
|
}
|
|
|
|
const GLFWvidmode *mode = glfwGetVideoMode(monitor);
|
|
glfwSetWindowMonitor(CORE.Window.handle, glfwGetPrimaryMonitor(), 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, mode->refreshRate);
|
|
|
|
// 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);
|
|
}
|
|
else glfwSetWindowMonitor(CORE.Window.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE);
|
|
|
|
CORE.Window.fullscreen = !CORE.Window.fullscreen; // Toggle fullscreen flag
|
|
CORE.Window.flags ^= FLAG_FULLSCREEN_MODE;
|
|
|
|
#endif
|
|
#if defined(PLATFORM_WEB)
|
|
/*
|
|
EM_ASM(
|
|
if (document.fullscreenElement) document.exitFullscreen();
|
|
else Module.requestFullscreen(true, true);
|
|
);
|
|
*/
|
|
|
|
//EM_ASM(Module.requestFullscreen(false, false););
|
|
|
|
/*
|
|
if (!CORE.Window.fullscreen)
|
|
{
|
|
//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, //on_canvassize_changed,
|
|
.canvasResizedCallbackUserData = NULL
|
|
};
|
|
|
|
emscripten_request_fullscreen("#canvas", false);
|
|
//emscripten_request_fullscreen_strategy("#canvas", EM_FALSE, &strategy);
|
|
//emscripten_enter_soft_fullscreen("canvas", &strategy);
|
|
TRACELOG(LOG_INFO, "emscripten_request_fullscreen_strategy");
|
|
}
|
|
else
|
|
{
|
|
TRACELOG(LOG_INFO, "emscripten_exit_fullscreen");
|
|
emscripten_exit_fullscreen();
|
|
}
|
|
*/
|
|
|
|
CORE.Window.fullscreen = !CORE.Window.fullscreen; // Toggle fullscreen flag
|
|
#endif
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
TRACELOG(LOG_WARNING, "SYSTEM: Failed to toggle to windowed mode");
|
|
#endif
|
|
}
|
|
|
|
// Set window state: maximized, if resizable (only PLATFORM_DESKTOP)
|
|
void MaximizeWindow(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
if (glfwGetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE) == GLFW_TRUE)
|
|
{
|
|
glfwMaximizeWindow(CORE.Window.handle);
|
|
CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Set window state: minimized (only PLATFORM_DESKTOP)
|
|
void MinimizeWindow(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
// NOTE: Following function launches callback that sets appropiate flag!
|
|
glfwIconifyWindow(CORE.Window.handle);
|
|
#endif
|
|
}
|
|
|
|
// Set window state: not minimized/maximized (only PLATFORM_DESKTOP)
|
|
void RestoreWindow(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
if (glfwGetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE) == GLFW_TRUE)
|
|
{
|
|
// Restores the specified window if it was previously iconified (minimized) or maximized
|
|
glfwRestoreWindow(CORE.Window.handle);
|
|
CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED;
|
|
CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Set window configuration state using flags
|
|
void SetWindowState(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 (((CORE.Window.flags & FLAG_VSYNC_HINT) != (flags & FLAG_VSYNC_HINT)) && ((flags & FLAG_VSYNC_HINT) > 0))
|
|
{
|
|
glfwSwapInterval(1);
|
|
CORE.Window.flags |= FLAG_VSYNC_HINT;
|
|
}
|
|
|
|
// State change: FLAG_FULLSCREEN_MODE
|
|
if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) != (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 defined(PLATFORM_DESKTOP)
|
|
glfwSetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE, GLFW_TRUE);
|
|
CORE.Window.flags |= FLAG_WINDOW_RESIZABLE;
|
|
#endif
|
|
}
|
|
|
|
// State change: FLAG_WINDOW_UNDECORATED
|
|
if (((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) != (flags & FLAG_WINDOW_UNDECORATED)) && (flags & FLAG_WINDOW_UNDECORATED))
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_FALSE);
|
|
CORE.Window.flags |= FLAG_WINDOW_UNDECORATED;
|
|
#endif
|
|
}
|
|
|
|
// State change: FLAG_WINDOW_HIDDEN
|
|
if (((CORE.Window.flags & FLAG_WINDOW_HIDDEN) != (flags & FLAG_WINDOW_HIDDEN)) && ((flags & FLAG_WINDOW_HIDDEN) > 0))
|
|
{
|
|
glfwHideWindow(CORE.Window.handle);
|
|
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))
|
|
{
|
|
//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))
|
|
{
|
|
//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 defined(PLATFORM_DESKTOP)
|
|
glfwSetWindowAttrib(CORE.Window.handle, GLFW_FOCUS_ON_SHOW, GLFW_FALSE);
|
|
CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED;
|
|
#endif
|
|
}
|
|
|
|
// State change: FLAG_WINDOW_TOPMOST
|
|
if (((CORE.Window.flags & FLAG_WINDOW_TOPMOST) != (flags & FLAG_WINDOW_TOPMOST)) && ((flags & FLAG_WINDOW_TOPMOST) > 0))
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_TRUE);
|
|
CORE.Window.flags |= FLAG_WINDOW_TOPMOST;
|
|
#endif
|
|
}
|
|
|
|
// 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))
|
|
{
|
|
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))
|
|
{
|
|
TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only by 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))
|
|
{
|
|
TRACELOG(LOG_WARNING, "WINDOW: High DPI can only by configured before window initialization");
|
|
}
|
|
|
|
// 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))
|
|
{
|
|
TRACELOG(LOG_WARNING, "WINDOW: MSAA can only by 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))
|
|
{
|
|
TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only by configured before window initialization");
|
|
}
|
|
}
|
|
|
|
// 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 (((CORE.Window.flags & FLAG_VSYNC_HINT) > 0) && ((flags & FLAG_VSYNC_HINT) > 0))
|
|
{
|
|
glfwSwapInterval(0);
|
|
CORE.Window.flags &= ~FLAG_VSYNC_HINT;
|
|
}
|
|
|
|
// State change: FLAG_FULLSCREEN_MODE
|
|
if (((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) && ((flags & FLAG_FULLSCREEN_MODE) > 0))
|
|
{
|
|
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 defined(PLATFORM_DESKTOP)
|
|
glfwSetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE, GLFW_FALSE);
|
|
CORE.Window.flags &= ~FLAG_WINDOW_RESIZABLE;
|
|
#endif
|
|
}
|
|
|
|
// State change: FLAG_WINDOW_UNDECORATED
|
|
if (((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) && ((flags & FLAG_WINDOW_UNDECORATED) > 0))
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_TRUE);
|
|
CORE.Window.flags &= ~FLAG_WINDOW_UNDECORATED;
|
|
#endif
|
|
}
|
|
|
|
// State change: FLAG_WINDOW_HIDDEN
|
|
if (((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) && ((flags & FLAG_WINDOW_HIDDEN) > 0))
|
|
{
|
|
glfwShowWindow(CORE.Window.handle);
|
|
CORE.Window.flags &= ~FLAG_WINDOW_HIDDEN;
|
|
}
|
|
|
|
// State change: FLAG_WINDOW_MINIMIZED
|
|
if (((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) && ((flags & FLAG_WINDOW_MINIMIZED) > 0))
|
|
{
|
|
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))
|
|
{
|
|
RestoreWindow(); // NOTE: Window state flag updated inside function
|
|
}
|
|
|
|
// State change: FLAG_WINDOW_UNFOCUSED
|
|
if (((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) && ((flags & FLAG_WINDOW_UNFOCUSED) > 0))
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
glfwSetWindowAttrib(CORE.Window.handle, GLFW_FOCUS_ON_SHOW, GLFW_TRUE);
|
|
CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED;
|
|
#endif
|
|
}
|
|
|
|
// State change: FLAG_WINDOW_TOPMOST
|
|
if (((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) && ((flags & FLAG_WINDOW_TOPMOST) > 0))
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_FALSE);
|
|
CORE.Window.flags &= ~FLAG_WINDOW_TOPMOST;
|
|
#endif
|
|
}
|
|
|
|
// State change: FLAG_WINDOW_ALWAYS_RUN
|
|
if (((CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) > 0) && ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0))
|
|
{
|
|
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))
|
|
{
|
|
TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only by configured before window initialization");
|
|
}
|
|
|
|
// State change: FLAG_WINDOW_HIGHDPI
|
|
if (((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) && ((flags & FLAG_WINDOW_HIGHDPI) > 0))
|
|
{
|
|
TRACELOG(LOG_WARNING, "WINDOW: High DPI can only by configured before window initialization");
|
|
}
|
|
|
|
// State change: FLAG_MSAA_4X_HINT
|
|
if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) > 0) && ((flags & FLAG_MSAA_4X_HINT) > 0))
|
|
{
|
|
TRACELOG(LOG_WARNING, "WINDOW: MSAA can only by configured before window initialization");
|
|
}
|
|
|
|
// State change: FLAG_INTERLACED_HINT
|
|
if (((CORE.Window.flags & FLAG_INTERLACED_HINT) > 0) && ((flags & FLAG_INTERLACED_HINT) > 0))
|
|
{
|
|
TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only by configured before window initialization");
|
|
}
|
|
}
|
|
|
|
// Set icon for window (only PLATFORM_DESKTOP)
|
|
// NOTE: Image must be in RGBA format, 8bit per channel
|
|
void SetWindowIcon(Image image)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
if (image.format == UNCOMPRESSED_R8G8B8A8)
|
|
{
|
|
GLFWimage icon[1] = { 0 };
|
|
|
|
icon[0].width = image.width;
|
|
icon[0].height = image.height;
|
|
icon[0].pixels = (unsigned char *)image.data;
|
|
|
|
// NOTE 1: We only support one image icon
|
|
// NOTE 2: The specified image data is copied before this function returns
|
|
glfwSetWindowIcon(CORE.Window.handle, 1, icon);
|
|
}
|
|
else TRACELOG(LOG_WARNING, "GLFW: Window icon image must be in R8G8B8A8 pixel format");
|
|
#endif
|
|
}
|
|
|
|
// Set title for window (only PLATFORM_DESKTOP)
|
|
void SetWindowTitle(const char *title)
|
|
{
|
|
CORE.Window.title = title;
|
|
#if defined(PLATFORM_DESKTOP)
|
|
glfwSetWindowTitle(CORE.Window.handle, title);
|
|
#endif
|
|
}
|
|
|
|
// Set window position on screen (windowed mode)
|
|
void SetWindowPosition(int x, int y)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
glfwSetWindowPos(CORE.Window.handle, x, y);
|
|
#endif
|
|
}
|
|
|
|
// Set monitor for the current window (fullscreen mode)
|
|
void SetWindowMonitor(int monitor)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
int monitorCount = 0;
|
|
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
|
|
|
|
if ((monitor >= 0) && (monitor < monitorCount))
|
|
{
|
|
TRACELOG(LOG_INFO, "GLFW: Selected fullscreen monitor: [%i] %s", monitor, glfwGetMonitorName(monitors[monitor]));
|
|
|
|
const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]);
|
|
glfwSetWindowMonitor(CORE.Window.handle, monitors[monitor], 0, 0, mode->width, mode->height, mode->refreshRate);
|
|
}
|
|
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
|
|
#endif
|
|
}
|
|
|
|
// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE)
|
|
void SetWindowMinSize(int width, int height)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
const GLFWvidmode *mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
|
glfwSetWindowSizeLimits(CORE.Window.handle, width, height, mode->width, mode->height);
|
|
#endif
|
|
}
|
|
|
|
// Set window dimensions
|
|
// TODO: Issues on HighDPI scaling
|
|
void SetWindowSize(int width, int height)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
glfwSetWindowSize(CORE.Window.handle, width, height);
|
|
#endif
|
|
#if defined(PLATFORM_WEB)
|
|
emscripten_set_canvas_size(width, height); // DEPRECATED!
|
|
|
|
// TODO: Below functions should be used to replace previous one but
|
|
// they do not seem to work properly
|
|
//emscripten_set_canvas_element_size("canvas", width, height);
|
|
//emscripten_set_element_css_size("canvas", width, height);
|
|
#endif
|
|
}
|
|
|
|
// Get current screen width
|
|
int GetScreenWidth(void)
|
|
{
|
|
return CORE.Window.screen.width;
|
|
}
|
|
|
|
// Get current screen height
|
|
int GetScreenHeight(void)
|
|
{
|
|
return CORE.Window.screen.height;
|
|
}
|
|
|
|
// Get native window handle
|
|
void *GetWindowHandle(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP) && defined(_WIN32)
|
|
// NOTE: Returned handle is: void *HWND (windows.h)
|
|
return glfwGetWin32Window(CORE.Window.handle);
|
|
#elif defined(__linux__)
|
|
// NOTE: Returned handle is: unsigned long Window (X.h)
|
|
// typedef unsigned long XID;
|
|
// typedef XID Window;
|
|
//unsigned long id = (unsigned long)glfwGetX11Window(window);
|
|
return NULL; // TODO: Find a way to return value... cast to void *?
|
|
#elif defined(__APPLE__)
|
|
// NOTE: Returned handle is: (objc_object *)
|
|
return NULL; // TODO: return (void *)glfwGetCocoaWindow(window);
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
// Get number of monitors
|
|
int GetMonitorCount(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
int monitorCount;
|
|
glfwGetMonitors(&monitorCount);
|
|
return monitorCount;
|
|
#else
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
// Get selected monitor width
|
|
Vector2 GetMonitorPosition(int monitor)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
int monitorCount;
|
|
GLFWmonitor** monitors = glfwGetMonitors(&monitorCount);
|
|
|
|
if ((monitor >= 0) && (monitor < monitorCount))
|
|
{
|
|
int x, y;
|
|
glfwGetMonitorPos(monitors[monitor], &x, &y);
|
|
const GLFWvidmode* mode = glfwGetVideoMode(monitors[monitor]);
|
|
return (Vector2){ (float)x, (float)y };
|
|
}
|
|
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
|
|
#endif
|
|
return (Vector2){ 0, 0 };
|
|
}
|
|
|
|
// Get selected monitor width
|
|
int GetMonitorWidth(int monitor)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
int monitorCount;
|
|
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
|
|
|
|
if ((monitor >= 0) && (monitor < monitorCount))
|
|
{
|
|
const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]);
|
|
return mode->width;
|
|
}
|
|
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
// Get selected monitor width
|
|
int GetMonitorHeight(int monitor)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
int monitorCount;
|
|
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
|
|
|
|
if ((monitor >= 0) && (monitor < monitorCount))
|
|
{
|
|
const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]);
|
|
return mode->height;
|
|
}
|
|
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
// Get selected monitor physical width in millimetres
|
|
int GetMonitorPhysicalWidth(int monitor)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
int monitorCount;
|
|
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
|
|
|
|
if ((monitor >= 0) && (monitor < monitorCount))
|
|
{
|
|
int physicalWidth;
|
|
glfwGetMonitorPhysicalSize(monitors[monitor], &physicalWidth, NULL);
|
|
return physicalWidth;
|
|
}
|
|
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
// Get primary monitor physical height in millimetres
|
|
int GetMonitorPhysicalHeight(int monitor)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
int monitorCount;
|
|
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
|
|
|
|
if ((monitor >= 0) && (monitor < monitorCount))
|
|
{
|
|
int physicalHeight;
|
|
glfwGetMonitorPhysicalSize(monitors[monitor], NULL, &physicalHeight);
|
|
return physicalHeight;
|
|
}
|
|
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int GetMonitorRefreshRate(int monitor)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
int monitorCount;
|
|
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
|
|
|
|
if ((monitor >= 0) && (monitor < monitorCount))
|
|
{
|
|
const GLFWvidmode *vidmode = glfwGetVideoMode(monitors[monitor]);
|
|
return vidmode->refreshRate;
|
|
}
|
|
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
|
|
#endif
|
|
#if defined(PLATFORM_DRM)
|
|
if ((CORE.Window.connector) && (CORE.Window.modeIndex >= 0))
|
|
{
|
|
return CORE.Window.connector->modes[CORE.Window.modeIndex].vrefresh;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
// Get window position XY on monitor
|
|
Vector2 GetWindowPosition(void)
|
|
{
|
|
int x = 0;
|
|
int y = 0;
|
|
#if defined(PLATFORM_DESKTOP)
|
|
glfwGetWindowPos(CORE.Window.handle, &x, &y);
|
|
#endif
|
|
return (Vector2){ (float)x, (float)y };
|
|
}
|
|
|
|
// Get window scale DPI factor
|
|
Vector2 GetWindowScaleDPI(void)
|
|
{
|
|
Vector2 scale = { 1.0f, 1.0f };
|
|
|
|
#if defined(PLATFORM_DESKTOP)
|
|
float xdpi = 1.0;
|
|
float ydpi = 1.0;
|
|
Vector2 windowPos = GetWindowPosition();
|
|
|
|
int monitorCount = 0;
|
|
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
|
|
|
|
// Check window monitor
|
|
for (int i = 0; i < monitorCount; i++)
|
|
{
|
|
glfwGetMonitorContentScale(monitors[i], &xdpi, &ydpi);
|
|
|
|
int xpos, ypos, width, height;
|
|
glfwGetMonitorWorkarea(monitors[i], &xpos, &ypos, &width, &height);
|
|
|
|
if ((windowPos.x >= xpos) && (windowPos.x < xpos + width) &&
|
|
(windowPos.y >= ypos) && (windowPos.y < ypos + height))
|
|
{
|
|
scale.x = xdpi;
|
|
scale.y = ydpi;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return scale;
|
|
}
|
|
|
|
// Get the human-readable, UTF-8 encoded name of the primary monitor
|
|
const char *GetMonitorName(int monitor)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
int monitorCount;
|
|
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount);
|
|
|
|
if ((monitor >= 0) && (monitor < monitorCount))
|
|
{
|
|
return glfwGetMonitorName(monitors[monitor]);
|
|
}
|
|
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor");
|
|
#endif
|
|
return "";
|
|
}
|
|
|
|
// Get clipboard text content
|
|
// NOTE: returned string is allocated and freed by GLFW
|
|
const char *GetClipboardText(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
return glfwGetClipboardString(CORE.Window.handle);
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
// Set clipboard text content
|
|
void SetClipboardText(const char *text)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
glfwSetClipboardString(CORE.Window.handle, text);
|
|
#endif
|
|
}
|
|
|
|
// Show mouse cursor
|
|
void ShowCursor(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
|
#endif
|
|
#if defined(PLATFORM_UWP)
|
|
UWPGetMouseShowFunc()();
|
|
#endif
|
|
CORE.Input.Mouse.cursorHidden = false;
|
|
}
|
|
|
|
// Hides mouse cursor
|
|
void HideCursor(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
|
|
#endif
|
|
#if defined(PLATFORM_UWP)
|
|
UWPGetMouseHideFunc()();
|
|
#endif
|
|
CORE.Input.Mouse.cursorHidden = true;
|
|
}
|
|
|
|
// Check if cursor is not visible
|
|
bool IsCursorHidden(void)
|
|
{
|
|
return CORE.Input.Mouse.cursorHidden;
|
|
}
|
|
|
|
// Enables cursor (unlock cursor)
|
|
void EnableCursor(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
|
#endif
|
|
#if defined(PLATFORM_UWP)
|
|
UWPGetMouseUnlockFunc()();
|
|
#endif
|
|
CORE.Input.Mouse.cursorHidden = false;
|
|
}
|
|
|
|
// Disables cursor (lock cursor)
|
|
void DisableCursor(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
|
#endif
|
|
#if defined(PLATFORM_UWP)
|
|
UWPGetMouseLockFunc()();
|
|
#endif
|
|
CORE.Input.Mouse.cursorHidden = true;
|
|
}
|
|
|
|
// Check if cursor is on the current screen.
|
|
bool IsCursorOnScreen(void)
|
|
{
|
|
return CORE.Input.Mouse.cursorOnScreen;
|
|
}
|
|
|
|
// Set background color (framebuffer clear color)
|
|
void ClearBackground(Color color)
|
|
{
|
|
rlClearColor(color.r, color.g, color.b, color.a); // Set clear color
|
|
rlClearScreenBuffers(); // Clear current framebuffers
|
|
}
|
|
|
|
// Setup canvas (framebuffer) to start drawing
|
|
void BeginDrawing(void)
|
|
{
|
|
CORE.Time.current = GetTime(); // Number of elapsed seconds since InitTimer()
|
|
CORE.Time.update = CORE.Time.current - CORE.Time.previous;
|
|
CORE.Time.previous = CORE.Time.current;
|
|
|
|
rlLoadIdentity(); // Reset current matrix (modelview)
|
|
rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling
|
|
|
|
//rlTranslatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1
|
|
// NOTE: Not required with OpenGL 3.3+
|
|
}
|
|
|
|
// End canvas drawing and swap buffers (double buffering)
|
|
void EndDrawing(void)
|
|
{
|
|
#if (defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) && defined(SUPPORT_MOUSE_CURSOR_NATIVE)
|
|
// On native mode we have no system mouse cursor, so,
|
|
// we draw a small rectangle for user reference
|
|
if (!CORE.Input.Mouse.cursorHidden)
|
|
{
|
|
DrawRectangle(CORE.Input.Mouse.position.x, CORE.Input.Mouse.position.y, 3, 3, MAROON);
|
|
}
|
|
#endif
|
|
|
|
rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2)
|
|
|
|
#if defined(SUPPORT_GIF_RECORDING)
|
|
#define GIF_RECORD_FRAMERATE 10
|
|
|
|
if (gifRecording)
|
|
{
|
|
gifFramesCounter++;
|
|
|
|
// NOTE: We record one gif frame every 10 game frames
|
|
if ((gifFramesCounter%GIF_RECORD_FRAMERATE) == 0)
|
|
{
|
|
// Get image data for the current frame (from backbuffer)
|
|
// NOTE: This process is quite slow... :(
|
|
unsigned char *screenData = rlReadScreenPixels(CORE.Window.screen.width, CORE.Window.screen.height);
|
|
msf_gif_frame(&gifState, screenData, 10, 16, CORE.Window.screen.width*4);
|
|
|
|
RL_FREE(screenData); // Free image data
|
|
}
|
|
|
|
if (((gifFramesCounter/15)%2) == 1)
|
|
{
|
|
DrawCircle(30, CORE.Window.screen.height - 20, 10, RED);
|
|
DrawText("RECORDING", 50, CORE.Window.screen.height - 25, 10, MAROON);
|
|
}
|
|
|
|
rlglDraw(); // Draw RECORDING message
|
|
}
|
|
#endif
|
|
|
|
SwapBuffers(); // Copy back buffer to front buffer
|
|
PollInputEvents(); // Poll user events
|
|
|
|
// Frame time control system
|
|
CORE.Time.current = GetTime();
|
|
CORE.Time.draw = CORE.Time.current - CORE.Time.previous;
|
|
CORE.Time.previous = CORE.Time.current;
|
|
|
|
CORE.Time.frame = CORE.Time.update + CORE.Time.draw;
|
|
|
|
// Wait for some milliseconds...
|
|
if (CORE.Time.frame < CORE.Time.target)
|
|
{
|
|
Wait((float)(CORE.Time.target - CORE.Time.frame)*1000.0f);
|
|
|
|
CORE.Time.current = GetTime();
|
|
double waitTime = CORE.Time.current - CORE.Time.previous;
|
|
CORE.Time.previous = CORE.Time.current;
|
|
|
|
CORE.Time.frame += waitTime; // Total frame time: update + draw + wait
|
|
}
|
|
}
|
|
|
|
// Initialize 2D mode with custom camera (2D)
|
|
void BeginMode2D(Camera2D camera)
|
|
{
|
|
rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2)
|
|
|
|
rlLoadIdentity(); // Reset current matrix (modelview)
|
|
|
|
// Apply 2d camera transformation to modelview
|
|
rlMultMatrixf(MatrixToFloat(GetCameraMatrix2D(camera)));
|
|
|
|
// Apply screen scaling if required
|
|
rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale));
|
|
}
|
|
|
|
// Ends 2D mode with custom camera
|
|
void EndMode2D(void)
|
|
{
|
|
rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2)
|
|
|
|
rlLoadIdentity(); // Reset current matrix (modelview)
|
|
rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required
|
|
}
|
|
|
|
// Initializes 3D mode with custom camera (3D)
|
|
void BeginMode3D(Camera3D camera)
|
|
{
|
|
rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2)
|
|
|
|
rlMatrixMode(RL_PROJECTION); // Switch to projection matrix
|
|
rlPushMatrix(); // Save previous matrix, which contains the settings for the 2d ortho projection
|
|
rlLoadIdentity(); // Reset current matrix (projection)
|
|
|
|
float aspect = (float)CORE.Window.currentFbo.width/(float)CORE.Window.currentFbo.height;
|
|
|
|
if (camera.type == CAMERA_PERSPECTIVE)
|
|
{
|
|
// Setup perspective projection
|
|
double top = RL_CULL_DISTANCE_NEAR*tan(camera.fovy*0.5*DEG2RAD);
|
|
double right = top*aspect;
|
|
|
|
rlFrustum(-right, right, -top, top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR);
|
|
}
|
|
else if (camera.type == CAMERA_ORTHOGRAPHIC)
|
|
{
|
|
// Setup orthographic projection
|
|
double top = camera.fovy/2.0;
|
|
double right = top*aspect;
|
|
|
|
rlOrtho(-right, right, -top,top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR);
|
|
}
|
|
|
|
// NOTE: zNear and zFar values are important when computing depth buffer values
|
|
|
|
rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix
|
|
rlLoadIdentity(); // Reset current matrix (modelview)
|
|
|
|
// Setup Camera view
|
|
Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up);
|
|
rlMultMatrixf(MatrixToFloat(matView)); // Multiply modelview matrix by view matrix (camera)
|
|
|
|
rlEnableDepthTest(); // Enable DEPTH_TEST for 3D
|
|
}
|
|
|
|
// Ends 3D mode and returns to default 2D orthographic mode
|
|
void EndMode3D(void)
|
|
{
|
|
rlglDraw(); // Process internal buffers (update + draw)
|
|
|
|
rlMatrixMode(RL_PROJECTION); // Switch to projection matrix
|
|
rlPopMatrix(); // Restore previous matrix (projection) from matrix stack
|
|
|
|
rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix
|
|
rlLoadIdentity(); // Reset current matrix (modelview)
|
|
|
|
rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required
|
|
|
|
rlDisableDepthTest(); // Disable DEPTH_TEST for 2D
|
|
}
|
|
|
|
// Initializes render texture for drawing
|
|
void BeginTextureMode(RenderTexture2D target)
|
|
{
|
|
rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2)
|
|
|
|
rlEnableFramebuffer(target.id); // Enable render target
|
|
|
|
// Set viewport to framebuffer size
|
|
rlViewport(0, 0, target.texture.width, target.texture.height);
|
|
|
|
rlMatrixMode(RL_PROJECTION); // Switch to projection matrix
|
|
rlLoadIdentity(); // Reset current matrix (projection)
|
|
|
|
// Set orthographic projection to current framebuffer size
|
|
// NOTE: Configured top-left corner as (0, 0)
|
|
rlOrtho(0, target.texture.width, target.texture.height, 0, 0.0f, 1.0f);
|
|
|
|
rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix
|
|
rlLoadIdentity(); // Reset current matrix (modelview)
|
|
|
|
//rlScalef(0.0f, -1.0f, 0.0f); // Flip Y-drawing (?)
|
|
|
|
// Setup current width/height for proper aspect ratio
|
|
// calculation when using BeginMode3D()
|
|
CORE.Window.currentFbo.width = target.texture.width;
|
|
CORE.Window.currentFbo.height = target.texture.height;
|
|
}
|
|
|
|
// Ends drawing to render texture
|
|
void EndTextureMode(void)
|
|
{
|
|
rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2)
|
|
|
|
rlDisableFramebuffer(); // Disable render target (fbo)
|
|
|
|
// Set viewport to default framebuffer size
|
|
SetupViewport(CORE.Window.render.width, CORE.Window.render.height);
|
|
|
|
// Reset current screen size
|
|
CORE.Window.currentFbo.width = GetScreenWidth();
|
|
CORE.Window.currentFbo.height = GetScreenHeight();
|
|
}
|
|
|
|
// Begin scissor mode (define screen area for following drawing)
|
|
// NOTE: Scissor rec refers to bottom-left corner, we change it to upper-left
|
|
void BeginScissorMode(int x, int y, int width, int height)
|
|
{
|
|
rlglDraw(); // Force drawing elements
|
|
|
|
rlEnableScissorTest();
|
|
rlScissor(x, CORE.Window.currentFbo.height - (y + height), width, height);
|
|
}
|
|
|
|
// End scissor mode
|
|
void EndScissorMode(void)
|
|
{
|
|
rlglDraw(); // Force drawing elements
|
|
rlDisableScissorTest();
|
|
}
|
|
|
|
// Returns a ray trace from mouse position
|
|
Ray GetMouseRay(Vector2 mouse, Camera camera)
|
|
{
|
|
Ray ray;
|
|
|
|
// Calculate normalized device coordinates
|
|
// NOTE: y value is negative
|
|
float x = (2.0f*mouse.x)/(float)GetScreenWidth() - 1.0f;
|
|
float y = 1.0f - (2.0f*mouse.y)/(float)GetScreenHeight();
|
|
float z = 1.0f;
|
|
|
|
// Store values in a vector
|
|
Vector3 deviceCoords = { x, y, z };
|
|
|
|
// Calculate view matrix from camera look at
|
|
Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up);
|
|
|
|
Matrix matProj = MatrixIdentity();
|
|
|
|
if (camera.type == CAMERA_PERSPECTIVE)
|
|
{
|
|
// Calculate projection matrix from perspective
|
|
matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)GetScreenWidth()/(double)GetScreenHeight()), RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR);
|
|
}
|
|
else if (camera.type == CAMERA_ORTHOGRAPHIC)
|
|
{
|
|
float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height;
|
|
double top = camera.fovy/2.0;
|
|
double right = top*aspect;
|
|
|
|
// Calculate projection matrix from orthographic
|
|
matProj = MatrixOrtho(-right, right, -top, top, 0.01, 1000.0);
|
|
}
|
|
|
|
// Unproject far/near points
|
|
Vector3 nearPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 0.0f }, matProj, matView);
|
|
Vector3 farPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView);
|
|
|
|
// Unproject the mouse cursor in the near plane.
|
|
// We need this as the source position because orthographic projects, compared to perspect doesn't have a
|
|
// convergence point, meaning that the "eye" of the camera is more like a plane than a point.
|
|
Vector3 cameraPlanePointerPos = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, -1.0f }, matProj, matView);
|
|
|
|
// Calculate normalized direction vector
|
|
Vector3 direction = Vector3Normalize(Vector3Subtract(farPoint, nearPoint));
|
|
|
|
if (camera.type == CAMERA_PERSPECTIVE) ray.position = camera.position;
|
|
else if (camera.type == CAMERA_ORTHOGRAPHIC) ray.position = cameraPlanePointerPos;
|
|
|
|
// Apply calculated vectors to ray
|
|
ray.direction = direction;
|
|
|
|
return ray;
|
|
}
|
|
|
|
// Get transform matrix for camera
|
|
Matrix GetCameraMatrix(Camera camera)
|
|
{
|
|
return MatrixLookAt(camera.position, camera.target, camera.up);
|
|
}
|
|
|
|
// Returns camera 2d transform matrix
|
|
Matrix GetCameraMatrix2D(Camera2D camera)
|
|
{
|
|
Matrix matTransform = { 0 };
|
|
// The camera in world-space is set by
|
|
// 1. Move it to target
|
|
// 2. Rotate by -rotation and scale by (1/zoom)
|
|
// 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)
|
|
// we need to do it into opposite direction (inverse transform)
|
|
|
|
// Having camera transform in world-space, inverse of it gives the modelview transform.
|
|
// Since (A*B*C)' = C'*B'*A', the modelview is
|
|
// 1. Move to offset
|
|
// 2. Rotate and Scale
|
|
// 3. Move by -target
|
|
Matrix matOrigin = MatrixTranslate(-camera.target.x, -camera.target.y, 0.0f);
|
|
Matrix matRotation = MatrixRotate((Vector3){ 0.0f, 0.0f, 1.0f }, camera.rotation*DEG2RAD);
|
|
Matrix matScale = MatrixScale(camera.zoom, camera.zoom, 1.0f);
|
|
Matrix matTranslation = MatrixTranslate(camera.offset.x, camera.offset.y, 0.0f);
|
|
|
|
matTransform = MatrixMultiply(MatrixMultiply(matOrigin, MatrixMultiply(matScale, matRotation)), matTranslation);
|
|
|
|
return matTransform;
|
|
}
|
|
|
|
// Returns the screen space position from a 3d world space position
|
|
Vector2 GetWorldToScreen(Vector3 position, Camera camera)
|
|
{
|
|
Vector2 screenPosition = GetWorldToScreenEx(position, camera, GetScreenWidth(), GetScreenHeight());
|
|
|
|
return screenPosition;
|
|
}
|
|
|
|
// Returns size position for a 3d world space position (useful for texture drawing)
|
|
Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height)
|
|
{
|
|
// Calculate projection matrix (from perspective instead of frustum
|
|
Matrix matProj = MatrixIdentity();
|
|
|
|
if (camera.type == CAMERA_PERSPECTIVE)
|
|
{
|
|
// Calculate projection matrix from perspective
|
|
matProj = MatrixPerspective(camera.fovy * DEG2RAD, ((double)width/(double)height), RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR);
|
|
}
|
|
else if (camera.type == CAMERA_ORTHOGRAPHIC)
|
|
{
|
|
float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height;
|
|
double top = camera.fovy/2.0;
|
|
double right = top*aspect;
|
|
|
|
// Calculate projection matrix from orthographic
|
|
matProj = MatrixOrtho(-right, right, -top, top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR);
|
|
}
|
|
|
|
// Calculate view matrix from camera look at (and transpose it)
|
|
Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up);
|
|
|
|
// Convert world position vector to quaternion
|
|
Quaternion worldPos = { position.x, position.y, position.z, 1.0f };
|
|
|
|
// Transform world position to view
|
|
worldPos = QuaternionTransform(worldPos, matView);
|
|
|
|
// Transform result to projection (clip space position)
|
|
worldPos = QuaternionTransform(worldPos, matProj);
|
|
|
|
// Calculate normalized device coordinates (inverted y)
|
|
Vector3 ndcPos = { worldPos.x/worldPos.w, -worldPos.y/worldPos.w, worldPos.z/worldPos.w };
|
|
|
|
// Calculate 2d screen position vector
|
|
Vector2 screenPosition = { (ndcPos.x + 1.0f)/2.0f*(float)width, (ndcPos.y + 1.0f)/2.0f*(float)height };
|
|
|
|
return screenPosition;
|
|
}
|
|
|
|
// Returns the screen space position for a 2d camera world space position
|
|
Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera)
|
|
{
|
|
Matrix matCamera = GetCameraMatrix2D(camera);
|
|
Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, matCamera);
|
|
|
|
return (Vector2){ transform.x, transform.y };
|
|
}
|
|
|
|
// Returns the world space position for a 2d camera screen space position
|
|
Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera)
|
|
{
|
|
Matrix invMatCamera = MatrixInvert(GetCameraMatrix2D(camera));
|
|
Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, invMatCamera);
|
|
|
|
return (Vector2){ transform.x, transform.y };
|
|
}
|
|
|
|
// Set target FPS (maximum)
|
|
void SetTargetFPS(int fps)
|
|
{
|
|
if (fps < 1) CORE.Time.target = 0.0;
|
|
else CORE.Time.target = 1.0/(double)fps;
|
|
|
|
TRACELOG(LOG_INFO, "TIMER: Target time per frame: %02.03f milliseconds", (float)CORE.Time.target*1000);
|
|
}
|
|
|
|
// Returns current FPS
|
|
// NOTE: We calculate an average framerate
|
|
int GetFPS(void)
|
|
{
|
|
#define FPS_CAPTURE_FRAMES_COUNT 30 // 30 captures
|
|
#define FPS_AVERAGE_TIME_SECONDS 0.5f // 500 millisecondes
|
|
#define FPS_STEP (FPS_AVERAGE_TIME_SECONDS/FPS_CAPTURE_FRAMES_COUNT)
|
|
|
|
static int index = 0;
|
|
static float history[FPS_CAPTURE_FRAMES_COUNT] = { 0 };
|
|
static float average = 0, last = 0;
|
|
float fpsFrame = GetFrameTime();
|
|
|
|
if (fpsFrame == 0) return 0;
|
|
|
|
if ((GetTime() - last) > FPS_STEP)
|
|
{
|
|
last = (float)GetTime();
|
|
index = (index + 1)%FPS_CAPTURE_FRAMES_COUNT;
|
|
average -= history[index];
|
|
history[index] = fpsFrame/FPS_CAPTURE_FRAMES_COUNT;
|
|
average += history[index];
|
|
}
|
|
|
|
return (int)roundf(1.0f/average);
|
|
}
|
|
|
|
// Returns time in seconds for last frame drawn
|
|
float GetFrameTime(void)
|
|
{
|
|
return (float)CORE.Time.frame;
|
|
}
|
|
|
|
// Get elapsed time measure in seconds since InitTimer()
|
|
// NOTE: On PLATFORM_DESKTOP InitTimer() is called on InitWindow()
|
|
// NOTE: On PLATFORM_DESKTOP, timer is initialized on glfwInit()
|
|
double GetTime(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
|
|
return glfwGetTime(); // Elapsed time since glfwInit()
|
|
#endif
|
|
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
unsigned long long int time = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec;
|
|
|
|
return (double)(time - CORE.Time.base)*1e-9; // Elapsed time since InitTimer()
|
|
#endif
|
|
|
|
#if defined(PLATFORM_UWP)
|
|
return UWPGetQueryTimeFunc()();
|
|
#endif
|
|
}
|
|
|
|
// Setup window configuration flags (view FLAGS)
|
|
// NOTE: This function is expected to be called before window creation,
|
|
// because it setups some flags for the window creation process.
|
|
// To configure window states after creation, just use SetWindowState()
|
|
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;
|
|
}
|
|
|
|
// NOTE TRACELOG() function is located in [utils.h]
|
|
|
|
// Takes a screenshot of current screen (saved a .png)
|
|
// NOTE: This function could work in any platform but some platforms: PLATFORM_ANDROID and PLATFORM_WEB
|
|
// have their own internal file-systems, to dowload image to user file-system some additional mechanism is required
|
|
void TakeScreenshot(const char *fileName)
|
|
{
|
|
unsigned char *imgData = rlReadScreenPixels(CORE.Window.render.width, CORE.Window.render.height);
|
|
Image image = { imgData, CORE.Window.render.width, CORE.Window.render.height, 1, UNCOMPRESSED_R8G8B8A8 };
|
|
|
|
char path[512] = { 0 };
|
|
#if defined(PLATFORM_ANDROID)
|
|
strcpy(path, CORE.Android.internalDataPath);
|
|
strcat(path, "/");
|
|
strcat(path, fileName);
|
|
#elif defined(PLATFORM_UWP)
|
|
strcpy(path, CORE.UWP.internalDataPath);
|
|
strcat(path, "/");
|
|
strcat(path, fileName);
|
|
#else
|
|
strcpy(path, fileName);
|
|
#endif
|
|
|
|
ExportImage(image, path);
|
|
RL_FREE(imgData);
|
|
|
|
#if defined(PLATFORM_WEB)
|
|
// Download file from MEMFS (emscripten memory filesystem)
|
|
// saveFileFromMEMFSToDisk() function is defined in raylib/src/shell.html
|
|
emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", GetFileName(path), GetFileName(path)));
|
|
#endif
|
|
|
|
// TODO: Verification required for log
|
|
TRACELOG(LOG_INFO, "SYSTEM: [%s] Screenshot taken successfully", path);
|
|
}
|
|
|
|
// Returns a random value between min and max (both included)
|
|
int GetRandomValue(int min, int max)
|
|
{
|
|
if (min > max)
|
|
{
|
|
int tmp = max;
|
|
max = min;
|
|
min = tmp;
|
|
}
|
|
|
|
return (rand()%(abs(max - min) + 1) + min);
|
|
}
|
|
|
|
// Check if the file exists
|
|
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
|
|
|
|
return result;
|
|
}
|
|
|
|
// Check file extension
|
|
// NOTE: Extensions checking is not case-sensitive
|
|
bool IsFileExtension(const char *fileName, const char *ext)
|
|
{
|
|
bool result = false;
|
|
const char *fileExt = GetFileExtension(fileName);
|
|
|
|
if (fileExt != NULL)
|
|
{
|
|
#if defined(SUPPORT_TEXT_MANIPULATION)
|
|
int extCount = 0;
|
|
const char **checkExts = TextSplit(ext, ';', &extCount);
|
|
|
|
char fileExtLower[16] = { 0 };
|
|
strcpy(fileExtLower, TextToLower(fileExt));
|
|
|
|
for (int i = 0; i < extCount; i++)
|
|
{
|
|
if (TextIsEqual(fileExtLower, TextToLower(checkExts[i] + 1)))
|
|
{
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
#else
|
|
if (strcmp(fileExt, ext) == 0) result = true;
|
|
#endif
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Check if a directory path exists
|
|
bool DirectoryExists(const char *dirPath)
|
|
{
|
|
bool result = false;
|
|
DIR *dir = opendir(dirPath);
|
|
|
|
if (dir != NULL)
|
|
{
|
|
result = true;
|
|
closedir(dir);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Get pointer to extension for a filename string
|
|
const char *GetFileExtension(const char *fileName)
|
|
{
|
|
const char *dot = strrchr(fileName, '.');
|
|
|
|
if (!dot || dot == fileName) return NULL;
|
|
|
|
return (dot + 1);
|
|
}
|
|
|
|
// String pointer reverse break: returns right-most occurrence of charset in s
|
|
static const char *strprbrk(const char *s, const char *charset)
|
|
{
|
|
const char *latestMatch = NULL;
|
|
for (; s = strpbrk(s, charset), s != NULL; latestMatch = s++) { }
|
|
return latestMatch;
|
|
}
|
|
|
|
// Get pointer to filename for a path string
|
|
const char *GetFileName(const char *filePath)
|
|
{
|
|
const char *fileName = NULL;
|
|
if (filePath != NULL) fileName = strprbrk(filePath, "\\/");
|
|
|
|
if (!fileName || (fileName == filePath)) return filePath;
|
|
|
|
return fileName + 1;
|
|
}
|
|
|
|
// Get filename string without extension (uses static string)
|
|
const char *GetFileNameWithoutExt(const char *filePath)
|
|
{
|
|
#define MAX_FILENAMEWITHOUTEXT_LENGTH 128
|
|
|
|
static char fileName[MAX_FILENAMEWITHOUTEXT_LENGTH];
|
|
memset(fileName, 0, MAX_FILENAMEWITHOUTEXT_LENGTH);
|
|
|
|
if (filePath != NULL) strcpy(fileName, GetFileName(filePath)); // Get filename with extension
|
|
|
|
int len = (int)strlen(fileName);
|
|
|
|
for (int i = 0; (i < len) && (i < MAX_FILENAMEWITHOUTEXT_LENGTH); i++)
|
|
{
|
|
if (fileName[i] == '.')
|
|
{
|
|
// NOTE: We break on first '.' found
|
|
fileName[i] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
|
|
return fileName;
|
|
}
|
|
|
|
// Get directory for a given filePath
|
|
const char *GetDirectoryPath(const char *filePath)
|
|
{
|
|
/*
|
|
// NOTE: Directory separator is different in Windows and other platforms,
|
|
// fortunately, Windows also support the '/' separator, that's the one should be used
|
|
#if defined(_WIN32)
|
|
char separator = '\\';
|
|
#else
|
|
char separator = '/';
|
|
#endif
|
|
*/
|
|
const char *lastSlash = NULL;
|
|
static char dirPath[MAX_FILEPATH_LENGTH];
|
|
memset(dirPath, 0, MAX_FILEPATH_LENGTH);
|
|
|
|
// In case provided path does not contains a root drive letter (C:\, D:\),
|
|
// we add the current directory path to dirPath
|
|
if (filePath[1] != ':')
|
|
{
|
|
// For security, we set starting path to current directory,
|
|
// obtained path will be concated to this
|
|
dirPath[0] = '.';
|
|
dirPath[1] = '/';
|
|
}
|
|
|
|
lastSlash = strprbrk(filePath, "\\/");
|
|
if (lastSlash)
|
|
{
|
|
// NOTE: Be careful, strncpy() is not safe, it does not care about '\0'
|
|
memcpy(dirPath + ((filePath[1] != ':')? 2 : 0), filePath, strlen(filePath) - (strlen(lastSlash) - 1));
|
|
dirPath[strlen(filePath) - strlen(lastSlash) + ((filePath[1] != ':')? 2 : 0)] = '\0'; // Add '\0' manually
|
|
}
|
|
|
|
return dirPath;
|
|
}
|
|
|
|
// Get previous directory path for a given path
|
|
const char *GetPrevDirectoryPath(const char *dirPath)
|
|
{
|
|
static char prevDirPath[MAX_FILEPATH_LENGTH];
|
|
memset(prevDirPath, 0, MAX_FILEPATH_LENGTH);
|
|
int pathLen = (int)strlen(dirPath);
|
|
|
|
if (pathLen <= 3) strcpy(prevDirPath, dirPath);
|
|
|
|
for (int i = (pathLen - 1); (i >= 0) && (pathLen > 3); i--)
|
|
{
|
|
if ((dirPath[i] == '\\') || (dirPath[i] == '/'))
|
|
{
|
|
// Check for root: "C:\" or "/"
|
|
if (((i == 2) && (dirPath[1] ==':')) || (i == 0)) i++;
|
|
|
|
strncpy(prevDirPath, dirPath, i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return prevDirPath;
|
|
}
|
|
|
|
// Get current working directory
|
|
const char *GetWorkingDirectory(void)
|
|
{
|
|
static char currentDir[MAX_FILEPATH_LENGTH];
|
|
memset(currentDir, 0, MAX_FILEPATH_LENGTH);
|
|
|
|
char *ptr = GETCWD(currentDir, MAX_FILEPATH_LENGTH - 1);
|
|
|
|
return ptr;
|
|
}
|
|
|
|
// Get filenames in a directory path (max 512 files)
|
|
// NOTE: Files count is returned by parameters pointer
|
|
char **GetDirectoryFiles(const char *dirPath, int *fileCount)
|
|
{
|
|
#define MAX_DIRECTORY_FILES 512
|
|
|
|
ClearDirectoryFiles();
|
|
|
|
// Memory allocation for MAX_DIRECTORY_FILES
|
|
dirFilesPath = (char **)RL_MALLOC(sizeof(char *)*MAX_DIRECTORY_FILES);
|
|
for (int i = 0; i < MAX_DIRECTORY_FILES; i++) dirFilesPath[i] = (char *)RL_MALLOC(sizeof(char)*MAX_FILEPATH_LENGTH);
|
|
|
|
int counter = 0;
|
|
struct dirent *entity;
|
|
DIR *dir = opendir(dirPath);
|
|
|
|
if (dir != NULL) // It's a directory
|
|
{
|
|
// TODO: Reading could be done in two passes,
|
|
// first one to count files and second one to read names
|
|
// That way we can allocate required memory, instead of a limited pool
|
|
|
|
while ((entity = readdir(dir)) != NULL)
|
|
{
|
|
strcpy(dirFilesPath[counter], entity->d_name);
|
|
counter++;
|
|
}
|
|
|
|
closedir(dir);
|
|
}
|
|
else TRACELOG(LOG_WARNING, "FILEIO: Failed to open requested directory"); // Maybe it's a file...
|
|
|
|
dirFilesCount = counter;
|
|
*fileCount = dirFilesCount;
|
|
|
|
return dirFilesPath;
|
|
}
|
|
|
|
// Clear directory files paths buffers
|
|
void ClearDirectoryFiles(void)
|
|
{
|
|
if (dirFilesCount > 0)
|
|
{
|
|
for (int i = 0; i < MAX_DIRECTORY_FILES; i++) RL_FREE(dirFilesPath[i]);
|
|
|
|
RL_FREE(dirFilesPath);
|
|
}
|
|
|
|
dirFilesCount = 0;
|
|
}
|
|
|
|
// Change working directory, returns true on success
|
|
bool ChangeDirectory(const char *dir)
|
|
{
|
|
bool result = CHDIR(dir);
|
|
|
|
if (result != 0) TRACELOG(LOG_WARNING, "SYSTEM: Failed to change to directory: %s", dir);
|
|
|
|
return (result == 0);
|
|
}
|
|
|
|
// Check if a file has been dropped into window
|
|
bool IsFileDropped(void)
|
|
{
|
|
if (CORE.Window.dropFilesCount > 0) return true;
|
|
else return false;
|
|
}
|
|
|
|
// Get dropped files names
|
|
char **GetDroppedFiles(int *count)
|
|
{
|
|
*count = CORE.Window.dropFilesCount;
|
|
return CORE.Window.dropFilesPath;
|
|
}
|
|
|
|
// Clear dropped files paths buffer
|
|
void ClearDroppedFiles(void)
|
|
{
|
|
if (CORE.Window.dropFilesCount > 0)
|
|
{
|
|
for (int i = 0; i < CORE.Window.dropFilesCount; i++) RL_FREE(CORE.Window.dropFilesPath[i]);
|
|
|
|
RL_FREE(CORE.Window.dropFilesPath);
|
|
|
|
CORE.Window.dropFilesCount = 0;
|
|
}
|
|
}
|
|
|
|
// Get file modification time (last write time)
|
|
long GetFileModTime(const char *fileName)
|
|
{
|
|
struct stat result = { 0 };
|
|
|
|
if (stat(fileName, &result) == 0)
|
|
{
|
|
time_t mod = result.st_mtime;
|
|
|
|
return (long)mod;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Compress data (DEFLATE algorythm)
|
|
unsigned char *CompressData(unsigned char *data, int dataLength, int *compDataLength)
|
|
{
|
|
#define COMPRESSION_QUALITY_DEFLATE 8
|
|
|
|
unsigned char *compData = NULL;
|
|
|
|
#if defined(SUPPORT_COMPRESSION_API)
|
|
compData = stbi_zlib_compress(data, dataLength, compDataLength, COMPRESSION_QUALITY_DEFLATE);
|
|
#endif
|
|
|
|
return compData;
|
|
}
|
|
|
|
// Decompress data (DEFLATE algorythm)
|
|
unsigned char *DecompressData(unsigned char *compData, int compDataLength, int *dataLength)
|
|
{
|
|
char *data = NULL;
|
|
|
|
#if defined(SUPPORT_COMPRESSION_API)
|
|
data = stbi_zlib_decode_malloc((char *)compData, compDataLength, dataLength);
|
|
#endif
|
|
|
|
return (unsigned char *)data;
|
|
}
|
|
|
|
// Save integer value to storage file (to defined position)
|
|
// NOTE: Storage positions is directly related to file memory layout (4 bytes each integer)
|
|
bool SaveStorageValue(unsigned int position, int value)
|
|
{
|
|
bool success = false;
|
|
|
|
#if defined(SUPPORT_DATA_STORAGE)
|
|
char path[512] = { 0 };
|
|
#if defined(PLATFORM_ANDROID)
|
|
strcpy(path, CORE.Android.internalDataPath);
|
|
strcat(path, "/");
|
|
strcat(path, STORAGE_DATA_FILE);
|
|
#elif defined(PLATFORM_UWP)
|
|
strcpy(path, CORE.UWP.internalDataPath);
|
|
strcat(path, "/");
|
|
strcat(path, STORAGE_DATA_FILE);
|
|
#else
|
|
strcpy(path, STORAGE_DATA_FILE);
|
|
#endif
|
|
|
|
unsigned int dataSize = 0;
|
|
unsigned int newDataSize = 0;
|
|
unsigned char *fileData = LoadFileData(path, &dataSize);
|
|
unsigned char *newFileData = NULL;
|
|
|
|
if (fileData != NULL)
|
|
{
|
|
if (dataSize <= (position*sizeof(int)))
|
|
{
|
|
// Increase data size up to position and store value
|
|
newDataSize = (position + 1)*sizeof(int);
|
|
newFileData = (unsigned char *)RL_REALLOC(fileData, newDataSize);
|
|
|
|
if (newFileData != NULL)
|
|
{
|
|
// RL_REALLOC succeded
|
|
int *dataPtr = (int *)newFileData;
|
|
dataPtr[position] = value;
|
|
}
|
|
else
|
|
{
|
|
// RL_REALLOC failed
|
|
TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to realloc data (%u), position in bytes (%u) bigger than actual file size", path, dataSize, position*sizeof(int));
|
|
|
|
// We store the old size of the file
|
|
newFileData = fileData;
|
|
newDataSize = dataSize;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Store the old size of the file
|
|
newFileData = fileData;
|
|
newDataSize = dataSize;
|
|
|
|
// Replace value on selected position
|
|
int *dataPtr = (int *)newFileData;
|
|
dataPtr[position] = value;
|
|
}
|
|
|
|
success = SaveFileData(path, newFileData, newDataSize);
|
|
RL_FREE(newFileData);
|
|
}
|
|
else
|
|
{
|
|
TRACELOG(LOG_INFO, "FILEIO: [%s] File not found, creating it", path);
|
|
|
|
dataSize = (position + 1)*sizeof(int);
|
|
fileData = (unsigned char *)RL_MALLOC(dataSize);
|
|
int *dataPtr = (int *)fileData;
|
|
dataPtr[position] = value;
|
|
|
|
success = SaveFileData(path, fileData, dataSize);
|
|
RL_FREE(fileData);
|
|
}
|
|
#endif
|
|
|
|
return success;
|
|
}
|
|
|
|
// Load integer value from storage file (from defined position)
|
|
// NOTE: If requested position could not be found, value 0 is returned
|
|
int LoadStorageValue(unsigned int position)
|
|
{
|
|
int value = 0;
|
|
#if defined(SUPPORT_DATA_STORAGE)
|
|
char path[512] = { 0 };
|
|
#if defined(PLATFORM_ANDROID)
|
|
strcpy(path, CORE.Android.internalDataPath);
|
|
strcat(path, "/");
|
|
strcat(path, STORAGE_DATA_FILE);
|
|
#elif defined(PLATFORM_UWP)
|
|
strcpy(path, CORE.UWP.internalDataPath);
|
|
strcat(path, "/");
|
|
strcat(path, STORAGE_DATA_FILE);
|
|
#else
|
|
strcpy(path, STORAGE_DATA_FILE);
|
|
#endif
|
|
|
|
unsigned int dataSize = 0;
|
|
unsigned char *fileData = LoadFileData(path, &dataSize);
|
|
|
|
if (fileData != NULL)
|
|
{
|
|
if (dataSize < (position*4)) TRACELOG(LOG_WARNING, "SYSTEM: Failed to find storage position");
|
|
else
|
|
{
|
|
int *dataPtr = (int *)fileData;
|
|
value = dataPtr[position];
|
|
}
|
|
|
|
RL_FREE(fileData);
|
|
}
|
|
#endif
|
|
return value;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
// Small security check trying to avoid (partially) malicious code...
|
|
// sorry for the inconvenience when you hit this point...
|
|
if (strchr(url, '\'') != NULL)
|
|
{
|
|
TRACELOG(LOG_WARNING, "SYSTEM: Provided URL is not valid");
|
|
}
|
|
else
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
char *cmd = (char *)RL_CALLOC(strlen(url) + 10, sizeof(char));
|
|
#if defined(_WIN32)
|
|
sprintf(cmd, "explorer %s", url);
|
|
#elif defined(__linux__)
|
|
sprintf(cmd, "xdg-open '%s'", url); // Alternatives: firefox, x-www-browser
|
|
#elif defined(__APPLE__)
|
|
sprintf(cmd, "open '%s'", url);
|
|
#endif
|
|
system(cmd);
|
|
RL_FREE(cmd);
|
|
#endif
|
|
#if defined(PLATFORM_WEB)
|
|
emscripten_run_script(TextFormat("window.open('%s', '_blank')", url));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Module Functions Definition - Input (Keyboard, Mouse, Gamepad) Functions
|
|
//----------------------------------------------------------------------------------
|
|
// Detect if a key has been pressed once
|
|
bool IsKeyPressed(int key)
|
|
{
|
|
bool pressed = false;
|
|
|
|
if ((CORE.Input.Keyboard.previousKeyState[key] == 0) && (CORE.Input.Keyboard.currentKeyState[key] == 1)) pressed = true;
|
|
else pressed = false;
|
|
|
|
return pressed;
|
|
}
|
|
|
|
// Detect if a key is being pressed (key held down)
|
|
bool IsKeyDown(int key)
|
|
{
|
|
if (CORE.Input.Keyboard.currentKeyState[key] == 1) return true;
|
|
else return false;
|
|
}
|
|
|
|
// Detect if a key has been released once
|
|
bool IsKeyReleased(int key)
|
|
{
|
|
bool released = false;
|
|
|
|
if ((CORE.Input.Keyboard.previousKeyState[key] == 1) && (CORE.Input.Keyboard.currentKeyState[key] == 0)) released = true;
|
|
else released = false;
|
|
|
|
return released;
|
|
}
|
|
|
|
// Detect if a key is NOT being pressed (key not held down)
|
|
bool IsKeyUp(int key)
|
|
{
|
|
if (CORE.Input.Keyboard.currentKeyState[key] == 0) return true;
|
|
else return false;
|
|
}
|
|
|
|
// Get the last key pressed
|
|
int GetKeyPressed(void)
|
|
{
|
|
int value = 0;
|
|
|
|
if (CORE.Input.Keyboard.keyPressedQueueCount > 0)
|
|
{
|
|
// Get character from the queue head
|
|
value = CORE.Input.Keyboard.keyPressedQueue[0];
|
|
|
|
// Shift elements 1 step toward the head.
|
|
for (int i = 0; i < (CORE.Input.Keyboard.keyPressedQueueCount - 1); i++) CORE.Input.Keyboard.keyPressedQueue[i] = CORE.Input.Keyboard.keyPressedQueue[i + 1];
|
|
|
|
// Reset last character in the queue
|
|
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 0;
|
|
CORE.Input.Keyboard.keyPressedQueueCount--;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
// Set a custom key to exit program
|
|
// NOTE: default exitKey is ESCAPE
|
|
void SetExitKey(int key)
|
|
{
|
|
#if !defined(PLATFORM_ANDROID)
|
|
CORE.Input.Keyboard.exitKey = key;
|
|
#endif
|
|
}
|
|
|
|
// NOTE: Gamepad support not implemented in emscripten GLFW3 (PLATFORM_WEB)
|
|
|
|
// Detect if a gamepad is available
|
|
bool IsGamepadAvailable(int gamepad)
|
|
{
|
|
bool result = false;
|
|
|
|
#if !defined(PLATFORM_ANDROID)
|
|
if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad]) result = true;
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
// Check gamepad name (if available)
|
|
bool IsGamepadName(int gamepad, const char *name)
|
|
{
|
|
bool result = false;
|
|
|
|
#if !defined(PLATFORM_ANDROID)
|
|
const char *currentName = NULL;
|
|
|
|
if (CORE.Input.Gamepad.ready[gamepad]) currentName = GetGamepadName(gamepad);
|
|
if ((name != NULL) && (currentName != NULL)) result = (strcmp(name, currentName) == 0);
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
// Return gamepad internal name id
|
|
const char *GetGamepadName(int gamepad)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
if (CORE.Input.Gamepad.ready[gamepad]) return glfwGetJoystickName(gamepad);
|
|
else return NULL;
|
|
#elif defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGNAME(64), &CORE.Input.Gamepad.name);
|
|
|
|
return CORE.Input.Gamepad.name;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
// Return gamepad axis count
|
|
int GetGamepadAxisCount(int gamepad)
|
|
{
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
int axisCount = 0;
|
|
if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGAXES, &axisCount);
|
|
CORE.Input.Gamepad.axisCount = axisCount;
|
|
#endif
|
|
return CORE.Input.Gamepad.axisCount;
|
|
}
|
|
|
|
// Return axis movement vector for a gamepad
|
|
float GetGamepadAxisMovement(int gamepad, int axis)
|
|
{
|
|
float value = 0;
|
|
|
|
#if !defined(PLATFORM_ANDROID)
|
|
if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (axis < MAX_GAMEPAD_AXIS) &&
|
|
((axis == GAMEPAD_AXIS_LEFT_TRIGGER) || (axis == GAMEPAD_AXIS_RIGHT_TRIGGER) ||
|
|
(fabsf(CORE.Input.Gamepad.axisState[gamepad][axis]) >= 0.2f))) value = CORE.Input.Gamepad.axisState[gamepad][axis];
|
|
#endif
|
|
|
|
return value;
|
|
}
|
|
|
|
// Detect if a gamepad button has been pressed once
|
|
bool IsGamepadButtonPressed(int gamepad, int button)
|
|
{
|
|
bool pressed = false;
|
|
|
|
#if !defined(PLATFORM_ANDROID)
|
|
if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) &&
|
|
(CORE.Input.Gamepad.currentState[gamepad][button] != CORE.Input.Gamepad.previousState[gamepad][button]) &&
|
|
(CORE.Input.Gamepad.currentState[gamepad][button] == 1)) pressed = true;
|
|
#endif
|
|
|
|
return pressed;
|
|
}
|
|
|
|
// Detect if a gamepad button is being pressed
|
|
bool IsGamepadButtonDown(int gamepad, int button)
|
|
{
|
|
bool result = false;
|
|
|
|
#if !defined(PLATFORM_ANDROID)
|
|
if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) &&
|
|
(CORE.Input.Gamepad.currentState[gamepad][button] == 1)) result = true;
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
// Detect if a gamepad button has NOT been pressed once
|
|
bool IsGamepadButtonReleased(int gamepad, int button)
|
|
{
|
|
bool released = false;
|
|
|
|
#if !defined(PLATFORM_ANDROID)
|
|
if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) &&
|
|
(CORE.Input.Gamepad.currentState[gamepad][button] != CORE.Input.Gamepad.previousState[gamepad][button]) &&
|
|
(CORE.Input.Gamepad.currentState[gamepad][button] == 0)) released = true;
|
|
#endif
|
|
|
|
return released;
|
|
}
|
|
|
|
// Detect if a mouse button is NOT being pressed
|
|
bool IsGamepadButtonUp(int gamepad, int button)
|
|
{
|
|
bool result = false;
|
|
|
|
#if !defined(PLATFORM_ANDROID)
|
|
if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) &&
|
|
(CORE.Input.Gamepad.currentState[gamepad][button] == 0)) result = true;
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
// Get the last gamepad button pressed
|
|
int GetGamepadButtonPressed(void)
|
|
{
|
|
return CORE.Input.Gamepad.lastButtonPressed;
|
|
}
|
|
|
|
// Detect if a mouse button has been pressed once
|
|
bool IsMouseButtonPressed(int button)
|
|
{
|
|
bool pressed = false;
|
|
|
|
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;
|
|
}
|
|
|
|
// Detect if a mouse button is being pressed
|
|
bool IsMouseButtonDown(int button)
|
|
{
|
|
bool down = false;
|
|
|
|
if (CORE.Input.Mouse.currentButtonState[button] == 1) down = true;
|
|
|
|
// Map touches to mouse buttons checking
|
|
if (CORE.Input.Touch.currentTouchState[button] == 1) down = true;
|
|
|
|
return down;
|
|
}
|
|
|
|
// Detect if a mouse button has been released once
|
|
bool IsMouseButtonReleased(int button)
|
|
{
|
|
bool released = false;
|
|
|
|
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;
|
|
}
|
|
|
|
// Detect if a mouse button is NOT being pressed
|
|
bool IsMouseButtonUp(int button)
|
|
{
|
|
return !IsMouseButtonDown(button);
|
|
}
|
|
|
|
// Returns mouse position X
|
|
int GetMouseX(void)
|
|
{
|
|
#if defined(PLATFORM_ANDROID)
|
|
return (int)CORE.Input.Touch.position[0].x;
|
|
#else
|
|
return (int)((CORE.Input.Mouse.position.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x);
|
|
#endif
|
|
}
|
|
|
|
// Returns mouse position Y
|
|
int GetMouseY(void)
|
|
{
|
|
#if defined(PLATFORM_ANDROID)
|
|
return (int)CORE.Input.Touch.position[0].y;
|
|
#else
|
|
return (int)((CORE.Input.Mouse.position.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y);
|
|
#endif
|
|
}
|
|
|
|
// Returns mouse position XY
|
|
Vector2 GetMousePosition(void)
|
|
{
|
|
Vector2 position = { 0 };
|
|
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB)
|
|
position = GetTouchPosition(0);
|
|
#else
|
|
position.x = (CORE.Input.Mouse.position.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x;
|
|
position.y = (CORE.Input.Mouse.position.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y;
|
|
#endif
|
|
|
|
return position;
|
|
}
|
|
|
|
// Set mouse position XY
|
|
void SetMousePosition(int x, int y)
|
|
{
|
|
CORE.Input.Mouse.position = (Vector2){ (float)x, (float)y };
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
|
|
// NOTE: emscripten not implemented
|
|
glfwSetCursorPos(CORE.Window.handle, CORE.Input.Mouse.position.x, CORE.Input.Mouse.position.y);
|
|
#endif
|
|
#if defined(PLATFORM_UWP)
|
|
UWPGetMouseSetPosFunc()(x, y);
|
|
#endif
|
|
}
|
|
|
|
// Set mouse offset
|
|
// NOTE: Useful when rendering to different size targets
|
|
void SetMouseOffset(int offsetX, int offsetY)
|
|
{
|
|
CORE.Input.Mouse.offset = (Vector2){ (float)offsetX, (float)offsetY };
|
|
}
|
|
|
|
// Set mouse scaling
|
|
// NOTE: Useful when rendering to different size targets
|
|
void SetMouseScale(float scaleX, float scaleY)
|
|
{
|
|
CORE.Input.Mouse.scale = (Vector2){ scaleX, scaleY };
|
|
}
|
|
|
|
// Returns mouse wheel movement Y
|
|
float GetMouseWheelMove(void)
|
|
{
|
|
#if defined(PLATFORM_ANDROID)
|
|
return 0.0f;
|
|
#elif defined(PLATFORM_WEB)
|
|
return CORE.Input.Mouse.previousWheelMove/100.0f;
|
|
#else
|
|
return CORE.Input.Mouse.previousWheelMove;
|
|
#endif
|
|
}
|
|
|
|
// Returns mouse cursor
|
|
int GetMouseCursor(void)
|
|
{
|
|
return CORE.Input.Mouse.cursor;
|
|
}
|
|
|
|
// Set mouse cursor
|
|
// NOTE: This is a no-op on platforms other than PLATFORM_DESKTOP
|
|
void SetMouseCursor(int cursor)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
CORE.Input.Mouse.cursor = cursor;
|
|
if (cursor == MOUSE_CURSOR_DEFAULT) glfwSetCursor(CORE.Window.handle, NULL);
|
|
else
|
|
{
|
|
// NOTE: We are relating internal GLFW enum values to our MouseCursor enum values
|
|
glfwSetCursor(CORE.Window.handle, glfwCreateStandardCursor(0x00036000 + cursor));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Returns touch position X for touch point 0 (relative to screen size)
|
|
int GetTouchX(void)
|
|
{
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP)
|
|
return (int)CORE.Input.Touch.position[0].x;
|
|
#else // PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_DRM
|
|
return GetMouseX();
|
|
#endif
|
|
}
|
|
|
|
// Returns touch position Y for touch point 0 (relative to screen size)
|
|
int GetTouchY(void)
|
|
{
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP)
|
|
return (int)CORE.Input.Touch.position[0].y;
|
|
#else // PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_DRM
|
|
return GetMouseY();
|
|
#endif
|
|
}
|
|
|
|
// Returns 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 };
|
|
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP)
|
|
if (index < MAX_TOUCH_POINTS) position = CORE.Input.Touch.position[index];
|
|
else TRACELOG(LOG_WARNING, "INPUT: Required touch point out of range (Max touch points: %i)", MAX_TOUCH_POINTS);
|
|
|
|
#if defined(PLATFORM_ANDROID)
|
|
if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height))
|
|
{
|
|
position.x = position.x*((float)CORE.Window.screen.width/(float)(CORE.Window.display.width - CORE.Window.renderOffset.x)) - CORE.Window.renderOffset.x/2;
|
|
position.y = position.y*((float)CORE.Window.screen.height/(float)(CORE.Window.display.height - CORE.Window.renderOffset.y)) - CORE.Window.renderOffset.y/2;
|
|
}
|
|
else
|
|
{
|
|
position.x = position.x*((float)CORE.Window.render.width/(float)CORE.Window.display.width) - CORE.Window.renderOffset.x/2;
|
|
position.y = position.y*((float)CORE.Window.render.height/(float)CORE.Window.display.height) - CORE.Window.renderOffset.y/2;
|
|
}
|
|
#endif
|
|
|
|
#elif defined(PLATFORM_DESKTOP)
|
|
// TODO: GLFW is not supporting 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
|
|
if (index == 0) position = GetMousePosition();
|
|
#endif
|
|
|
|
return position;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Module specific Functions Definition
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Initialize display device and framebuffer
|
|
// NOTE: width and height represent the screen (framebuffer) desired size, not actual display size
|
|
// If width or height are 0, default display size will be used for framebuffer size
|
|
// NOTE: returns false in case graphic device could not be created
|
|
static bool InitGraphicsDevice(int width, int height)
|
|
{
|
|
CORE.Window.screen.width = width; // User desired width
|
|
CORE.Window.screen.height = height; // User desired height
|
|
CORE.Window.screenScale = MatrixIdentity(); // No draw scaling required by default
|
|
|
|
// NOTE: Framebuffer (render area - CORE.Window.render.width, CORE.Window.render.height) could include black bars...
|
|
// ...in top-down or left-right to match display aspect ratio (no weird scalings)
|
|
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
|
|
glfwSetErrorCallback(ErrorCallback);
|
|
|
|
#if defined(__APPLE__)
|
|
glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE);
|
|
#endif
|
|
|
|
if (!glfwInit())
|
|
{
|
|
TRACELOG(LOG_WARNING, "GLFW: Failed to initialize GLFW");
|
|
return false;
|
|
}
|
|
|
|
// NOTE: Getting video modes is not implemented in emscripten GLFW3 version
|
|
#if defined(PLATFORM_DESKTOP)
|
|
// Find monitor resolution
|
|
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
|
|
if (!monitor)
|
|
{
|
|
TRACELOG(LOG_WARNING, "GLFW: Failed to get primary monitor");
|
|
return false;
|
|
}
|
|
const GLFWvidmode *mode = glfwGetVideoMode(monitor);
|
|
|
|
CORE.Window.display.width = mode->width;
|
|
CORE.Window.display.height = mode->height;
|
|
|
|
// Screen size security check
|
|
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;
|
|
#endif // PLATFORM_DESKTOP
|
|
|
|
#if defined(PLATFORM_WEB)
|
|
CORE.Window.display.width = CORE.Window.screen.width;
|
|
CORE.Window.display.height = CORE.Window.screen.height;
|
|
#endif // PLATFORM_WEB
|
|
|
|
glfwDefaultWindowHints(); // Set default windows hints
|
|
//glfwWindowHint(GLFW_RED_BITS, 8); // Framebuffer red color component bits
|
|
//glfwWindowHint(GLFW_GREEN_BITS, 8); // Framebuffer green color component bits
|
|
//glfwWindowHint(GLFW_BLUE_BITS, 8); // Framebuffer blue color component bits
|
|
//glfwWindowHint(GLFW_ALPHA_BITS, 8); // Framebuffer alpha color component bits
|
|
//glfwWindowHint(GLFW_DEPTH_BITS, 24); // Depthbuffer bits
|
|
//glfwWindowHint(GLFW_REFRESH_RATE, 0); // Refresh rate for fullscreen window
|
|
//glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // OpenGL API to use. Alternative: GLFW_OPENGL_ES_API
|
|
//glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers
|
|
|
|
// Check window creation flags
|
|
if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) CORE.Window.fullscreen = true;
|
|
|
|
if ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) 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
|
|
else glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); // Decorated window
|
|
|
|
if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) 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;
|
|
|
|
// Disable FLAG_WINDOW_MAXIMIZED, not supported on initialization
|
|
if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED;
|
|
|
|
if ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE);
|
|
else glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE);
|
|
|
|
if ((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) glfwWindowHint(GLFW_FLOATING, GLFW_TRUE);
|
|
else glfwWindowHint(GLFW_FLOATING, GLFW_FALSE);
|
|
|
|
// NOTE: Some GLFW flags are not supported on HTML5
|
|
#if defined(PLATFORM_DESKTOP)
|
|
if ((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); // Transparent framebuffer
|
|
else glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_FALSE); // Opaque framebuffer
|
|
|
|
if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0)
|
|
{
|
|
// 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.
|
|
// 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__)
|
|
glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE);
|
|
#endif
|
|
}
|
|
else glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE);
|
|
#endif
|
|
|
|
if (CORE.Window.flags & FLAG_MSAA_4X_HINT) glfwWindowHint(GLFW_SAMPLES, 4); // Tries to enable multisampling x4 (MSAA), default is 0
|
|
|
|
// NOTE: When asking for an OpenGL context version, most drivers provide highest supported version
|
|
// with forward compatibility to older OpenGL versions.
|
|
// For example, if using OpenGL 1.1, driver can provide a 4.3 context forward compatible.
|
|
|
|
// Check selection OpenGL version
|
|
if (rlGetVersion() == OPENGL_21)
|
|
{
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); // Choose OpenGL major version (just hint)
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); // Choose OpenGL minor version (just hint)
|
|
}
|
|
else if (rlGetVersion() == 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
|
|
#if defined(__APPLE__)
|
|
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); // OSX Requires fordward compatibility
|
|
#else
|
|
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); // Fordward Compatibility Hint: Only 3.3 and above!
|
|
#endif
|
|
//glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Request OpenGL DEBUG context
|
|
}
|
|
else if (rlGetVersion() == OPENGL_ES_20) // Request OpenGL ES 2.0 context
|
|
{
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
|
|
#if defined(PLATFORM_DESKTOP)
|
|
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
|
|
#else
|
|
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
|
|
#endif
|
|
}
|
|
|
|
if (CORE.Window.fullscreen)
|
|
{
|
|
// remember center for switchinging from fullscreen to window
|
|
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(glfwGetPrimaryMonitor(), &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;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(PLATFORM_DESKTOP)
|
|
// If we are windowed fullscreen, ensures that window does not minimize when focus is lost
|
|
if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width))
|
|
{
|
|
glfwWindowHint(GLFW_AUTO_ICONIFY, 0);
|
|
}
|
|
#endif
|
|
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 appropiate 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);
|
|
|
|
CORE.Window.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...
|
|
//glfwSetWindowMonitor(CORE.Window.handle, glfwGetPrimaryMonitor(), 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE);
|
|
}
|
|
else
|
|
{
|
|
// No-fullscreen window creation
|
|
CORE.Window.handle = glfwCreateWindow(CORE.Window.screen.width, CORE.Window.screen.height, (CORE.Window.title != 0)? CORE.Window.title : " ", NULL, NULL);
|
|
|
|
if (CORE.Window.handle)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP)
|
|
// Center window on screen
|
|
int windowPosX = CORE.Window.display.width/2 - CORE.Window.screen.width/2;
|
|
int windowPosY = CORE.Window.display.height/2 - CORE.Window.screen.height/2;
|
|
|
|
if (windowPosX < 0) windowPosX = 0;
|
|
if (windowPosY < 0) windowPosY = 0;
|
|
|
|
glfwSetWindowPos(CORE.Window.handle, windowPosX, windowPosY);
|
|
#endif
|
|
CORE.Window.render.width = CORE.Window.screen.width;
|
|
CORE.Window.render.height = CORE.Window.screen.height;
|
|
}
|
|
}
|
|
|
|
if (!CORE.Window.handle)
|
|
{
|
|
glfwTerminate();
|
|
TRACELOG(LOG_WARNING, "GLFW: Failed to initialize Window");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully");
|
|
#if defined(PLATFORM_DESKTOP)
|
|
TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height);
|
|
#endif
|
|
TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height);
|
|
TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height);
|
|
TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y);
|
|
}
|
|
|
|
glfwSetWindowSizeCallback(CORE.Window.handle, WindowSizeCallback); // NOTE: Resizing not allowed by default!
|
|
glfwSetCursorEnterCallback(CORE.Window.handle, CursorEnterCallback);
|
|
glfwSetKeyCallback(CORE.Window.handle, KeyCallback);
|
|
glfwSetMouseButtonCallback(CORE.Window.handle, MouseButtonCallback);
|
|
glfwSetCursorPosCallback(CORE.Window.handle, MouseCursorPosCallback); // Track mouse position changes
|
|
glfwSetCharCallback(CORE.Window.handle, CharCallback);
|
|
glfwSetScrollCallback(CORE.Window.handle, ScrollCallback);
|
|
glfwSetWindowIconifyCallback(CORE.Window.handle, WindowIconifyCallback);
|
|
glfwSetWindowFocusCallback(CORE.Window.handle, WindowFocusCallback);
|
|
glfwSetDropCallback(CORE.Window.handle, WindowDropCallback);
|
|
#if !defined(PLATFORM_WEB)
|
|
glfwSetWindowMaximizeCallback(CORE.Window.handle, WindowMaximizeCallback);
|
|
#endif
|
|
|
|
glfwMakeContextCurrent(CORE.Window.handle);
|
|
|
|
#if !defined(PLATFORM_WEB)
|
|
glfwSwapInterval(0); // No V-Sync by default
|
|
#endif
|
|
|
|
#if defined(PLATFORM_DESKTOP)
|
|
// Load OpenGL 3.3 extensions
|
|
// NOTE: GLFW loader function is passed as parameter
|
|
rlLoadExtensions(glfwGetProcAddress);
|
|
#endif
|
|
|
|
// 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)
|
|
{
|
|
// WARNING: It seems to hits a critical render path in Intel HD Graphics
|
|
glfwSwapInterval(1);
|
|
TRACELOG(LOG_INFO, "DISPLAY: Trying to enable VSYNC");
|
|
}
|
|
#endif // PLATFORM_DESKTOP || PLATFORM_WEB
|
|
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP)
|
|
CORE.Window.fullscreen = true;
|
|
CORE.Window.flags &= FLAG_FULLSCREEN_MODE;
|
|
|
|
#if defined(PLATFORM_RPI)
|
|
bcm_host_init();
|
|
|
|
DISPMANX_ELEMENT_HANDLE_T dispmanElement;
|
|
DISPMANX_DISPLAY_HANDLE_T dispmanDisplay;
|
|
DISPMANX_UPDATE_HANDLE_T dispmanUpdate;
|
|
|
|
VC_RECT_T dstRect;
|
|
VC_RECT_T srcRect;
|
|
#endif
|
|
|
|
#if defined(PLATFORM_DRM)
|
|
CORE.Window.fd = -1;
|
|
CORE.Window.connector = NULL;
|
|
CORE.Window.modeIndex = -1;
|
|
CORE.Window.crtc = NULL;
|
|
CORE.Window.gbmDevice = NULL;
|
|
CORE.Window.gbmSurface = NULL;
|
|
CORE.Window.prevBO = NULL;
|
|
CORE.Window.prevFB = 0;
|
|
|
|
#if defined(DEFAULT_GRAPHIC_DEVICE_DRM)
|
|
CORE.Window.fd = open(DEFAULT_GRAPHIC_DEVICE_DRM, O_RDWR);
|
|
#else
|
|
TRACELOG(LOG_INFO, "DISPLAY: no graphic card set, trying card1");
|
|
CORE.Window.fd = open("/dev/dri/card1", O_RDWR); // VideoCore VI (Raspberry Pi 4)
|
|
if (-1 == CORE.Window.fd)
|
|
{
|
|
TRACELOG(LOG_INFO, "DISPLAY: failed to open graphic card1, trying card0");
|
|
CORE.Window.fd = open("/dev/dri/card0", O_RDWR); // VideoCore IV (Raspberry Pi 1-3)
|
|
}
|
|
#endif
|
|
if (-1 == CORE.Window.fd)
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: failed to open graphic card");
|
|
return false;
|
|
}
|
|
|
|
drmModeRes *res = drmModeGetResources(CORE.Window.fd);
|
|
if (!res)
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: failed get DRM resources");
|
|
return false;
|
|
}
|
|
|
|
TRACELOG(LOG_TRACE, "DISPLAY: %i connectors found", res->count_connectors);
|
|
for (size_t i = 0; i < res->count_connectors; i++)
|
|
{
|
|
TRACELOG(LOG_TRACE, "DISPLAY: connector index %i", i);
|
|
drmModeConnector *con = drmModeGetConnector(CORE.Window.fd, res->connectors[i]);
|
|
TRACELOG(LOG_TRACE, "DISPLAY: there are %i connector modes", con->count_modes);
|
|
if ((con->connection == DRM_MODE_CONNECTED) && (con->encoder_id))
|
|
{
|
|
TRACELOG(LOG_TRACE, "DRM mode connected");
|
|
CORE.Window.connector = con;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
TRACELOG(LOG_TRACE, "DRM mode NOT connected (deleting)");
|
|
drmModeFreeConnector(con);
|
|
}
|
|
}
|
|
if (!CORE.Window.connector)
|
|
{
|
|
TRACELOG(LOG_WARNING, "no suitable DRM connector found");
|
|
drmModeFreeResources(res);
|
|
return false;
|
|
}
|
|
|
|
drmModeEncoder *enc = drmModeGetEncoder(CORE.Window.fd, CORE.Window.connector->encoder_id);
|
|
if (!enc)
|
|
{
|
|
TRACELOG(LOG_WARNING, "failed to get DRM mode encoder");
|
|
drmModeFreeResources(res);
|
|
return false;
|
|
}
|
|
|
|
CORE.Window.crtc = drmModeGetCrtc(CORE.Window.fd, enc->crtc_id);
|
|
if (!CORE.Window.crtc)
|
|
{
|
|
TRACELOG(LOG_WARNING, "failed to get DRM mode crtc");
|
|
drmModeFreeEncoder(enc);
|
|
drmModeFreeResources(res);
|
|
return false;
|
|
}
|
|
|
|
// If InitWindow should use the current mode find it in the connector's mode list
|
|
if ((CORE.Window.screen.width <= 0) || (CORE.Window.screen.height <= 0))
|
|
{
|
|
TRACELOG(LOG_TRACE, "selecting DRM connector mode for current used mode");
|
|
|
|
CORE.Window.modeIndex = FindMatchingConnectorMode(CORE.Window.connector, &CORE.Window.crtc->mode);
|
|
|
|
if (CORE.Window.modeIndex < 0)
|
|
{
|
|
TRACELOG(LOG_WARNING, "no matching DRM connector mode found");
|
|
drmModeFreeEncoder(enc);
|
|
drmModeFreeResources(res);
|
|
return false;
|
|
}
|
|
|
|
CORE.Window.screen.width = CORE.Window.display.width;
|
|
CORE.Window.screen.height = CORE.Window.display.height;
|
|
}
|
|
|
|
const bool allowInterlaced = 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
|
|
CORE.Window.modeIndex = FindExactConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced);
|
|
// if nothing found, try to find a nearly matching mode
|
|
if (CORE.Window.modeIndex < 0)
|
|
CORE.Window.modeIndex = FindNearestConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced);
|
|
// if nothing found, try to find an exactly matching mode including interlaced
|
|
if (CORE.Window.modeIndex < 0)
|
|
CORE.Window.modeIndex = FindExactConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true);
|
|
// if nothing found, try to find a nearly matching mode including interlaced
|
|
if (CORE.Window.modeIndex < 0)
|
|
CORE.Window.modeIndex = FindNearestConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true);
|
|
// if nothing found, there is no suitable mode
|
|
if (CORE.Window.modeIndex < 0)
|
|
{
|
|
TRACELOG(LOG_WARNING, "no suitable DRM connector mode found");
|
|
drmModeFreeEncoder(enc);
|
|
drmModeFreeResources(res);
|
|
return false;
|
|
}
|
|
|
|
CORE.Window.display.width = CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay;
|
|
CORE.Window.display.height = CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay;
|
|
|
|
TRACELOG(LOG_INFO, "DRM: choosen mode %s (%ux%u%c@%u)", CORE.Window.connector->modes[CORE.Window.modeIndex].name,
|
|
CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay,
|
|
(CORE.Window.connector->modes[CORE.Window.modeIndex].flags & DRM_MODE_FLAG_INTERLACE) ? 'i' : 'p',
|
|
CORE.Window.connector->modes[CORE.Window.modeIndex].vrefresh);
|
|
|
|
// Use the width and height of the surface for render
|
|
CORE.Window.render.width = CORE.Window.screen.width;
|
|
CORE.Window.render.height = CORE.Window.screen.height;
|
|
|
|
drmModeFreeEncoder(enc);
|
|
enc = NULL;
|
|
|
|
drmModeFreeResources(res);
|
|
res = NULL;
|
|
|
|
CORE.Window.gbmDevice = gbm_create_device(CORE.Window.fd);
|
|
if (!CORE.Window.gbmDevice)
|
|
{
|
|
TRACELOG(LOG_WARNING, "failed to create GBM device");
|
|
return false;
|
|
}
|
|
|
|
CORE.Window.gbmSurface = gbm_surface_create(CORE.Window.gbmDevice, CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay,
|
|
CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, GBM_FORMAT_ARGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
|
|
if (!CORE.Window.gbmSurface)
|
|
{
|
|
TRACELOG(LOG_WARNING, "failed to create GBM surface");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
EGLint samples = 0;
|
|
EGLint sampleBuffer = 0;
|
|
if (CORE.Window.flags & FLAG_MSAA_4X_HINT)
|
|
{
|
|
samples = 4;
|
|
sampleBuffer = 1;
|
|
TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4");
|
|
}
|
|
|
|
const EGLint framebufferAttribs[] =
|
|
{
|
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // Type of context support -> Required on RPI?
|
|
#if defined(PLATFORM_DRM)
|
|
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, // Don't use it on Android!
|
|
#endif
|
|
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)
|
|
#if defined(PLATFORM_DRM)
|
|
EGL_ALPHA_SIZE, 8, // ALPHA bit depth (required for transparent framebuffer)
|
|
#endif
|
|
//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
|
|
};
|
|
|
|
const EGLint contextAttribs[] =
|
|
{
|
|
EGL_CONTEXT_CLIENT_VERSION, 2,
|
|
EGL_NONE
|
|
};
|
|
|
|
#if defined(PLATFORM_UWP)
|
|
const EGLint surfaceAttributes[] =
|
|
{
|
|
// EGL_ANGLE_SURFACE_RENDER_TO_BACK_BUFFER is part of the same optimization as EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER (see above).
|
|
// If you have compilation issues with it then please update your Visual Studio templates.
|
|
EGL_ANGLE_SURFACE_RENDER_TO_BACK_BUFFER, EGL_TRUE,
|
|
EGL_NONE
|
|
};
|
|
|
|
const EGLint defaultDisplayAttributes[] =
|
|
{
|
|
// These are the default display attributes, used to request ANGLE's D3D11 renderer.
|
|
// eglInitialize will only succeed with these attributes if the hardware supports D3D11 Feature Level 10_0+.
|
|
EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
|
|
|
|
// EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER is an optimization that can have large performance benefits on mobile devices.
|
|
// Its syntax is subject to change, though. Please update your Visual Studio templates if you experience compilation issues with it.
|
|
EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER, EGL_TRUE,
|
|
|
|
// EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE is an option that enables ANGLE to automatically call
|
|
// the IDXGIDevice3::Trim method on behalf of the application when it gets suspended.
|
|
// Calling IDXGIDevice3::Trim when an application is suspended is a Windows Store application certification requirement.
|
|
EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE,
|
|
EGL_NONE,
|
|
};
|
|
|
|
const EGLint fl9_3DisplayAttributes[] =
|
|
{
|
|
// These can be used to request ANGLE's D3D11 renderer, with D3D11 Feature Level 9_3.
|
|
// These attributes are used if the call to eglInitialize fails with the default display attributes.
|
|
EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
|
|
EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, 9,
|
|
EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, 3,
|
|
EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER, EGL_TRUE,
|
|
EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE,
|
|
EGL_NONE,
|
|
};
|
|
|
|
const EGLint warpDisplayAttributes[] =
|
|
{
|
|
// These attributes can be used to request D3D11 WARP.
|
|
// They are used if eglInitialize fails with both the default display attributes and the 9_3 display attributes.
|
|
EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
|
|
EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE,
|
|
EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER, EGL_TRUE,
|
|
EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE,
|
|
EGL_NONE,
|
|
};
|
|
|
|
// eglGetPlatformDisplayEXT is an alternative to eglGetDisplay. It allows us to pass in display attributes, used to configure D3D11.
|
|
PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)(eglGetProcAddress("eglGetPlatformDisplayEXT"));
|
|
if (!eglGetPlatformDisplayEXT)
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to get function eglGetPlatformDisplayEXT");
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// To initialize the display, we make three sets of calls to eglGetPlatformDisplayEXT and eglInitialize, with varying
|
|
// parameters passed to eglGetPlatformDisplayEXT:
|
|
// 1) The first calls uses "defaultDisplayAttributes" as a parameter. This corresponds to D3D11 Feature Level 10_0+.
|
|
// 2) If eglInitialize fails for step 1 (e.g. because 10_0+ isn't supported by the default GPU), then we try again
|
|
// using "fl9_3DisplayAttributes". This corresponds to D3D11 Feature Level 9_3.
|
|
// 3) If eglInitialize fails for step 2 (e.g. because 9_3+ isn't supported by the default GPU), then we try again
|
|
// using "warpDisplayAttributes". This corresponds to D3D11 Feature Level 11_0 on WARP, a D3D11 software rasterizer.
|
|
//
|
|
|
|
// This tries to initialize EGL to D3D11 Feature Level 10_0+. See above comment for details.
|
|
CORE.Window.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, defaultDisplayAttributes);
|
|
if (CORE.Window.device == EGL_NO_DISPLAY)
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device");
|
|
return false;
|
|
}
|
|
|
|
if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE)
|
|
{
|
|
// This tries to initialize EGL to D3D11 Feature Level 9_3, if 10_0+ is unavailable (e.g. on some mobile devices).
|
|
CORE.Window.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, fl9_3DisplayAttributes);
|
|
if (CORE.Window.device == EGL_NO_DISPLAY)
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device");
|
|
return false;
|
|
}
|
|
|
|
if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE)
|
|
{
|
|
// This initializes EGL to D3D11 Feature Level 11_0 on WARP, if 9_3+ is unavailable on the default GPU.
|
|
CORE.Window.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, warpDisplayAttributes);
|
|
if (CORE.Window.device == EGL_NO_DISPLAY)
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device");
|
|
return false;
|
|
}
|
|
|
|
if (eglInitialize(CORE.Window.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;
|
|
}
|
|
}
|
|
}
|
|
|
|
EGLint numConfigs = 0;
|
|
if ((eglChooseConfig(CORE.Window.device, framebufferAttribs, &CORE.Window.config, 1, &numConfigs) == EGL_FALSE) || (numConfigs == 0))
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to choose first EGL configuration");
|
|
return false;
|
|
}
|
|
|
|
// Create a PropertySet and initialize with the EGLNativeWindowType.
|
|
//PropertySet^ surfaceCreationProperties = ref new PropertySet();
|
|
//surfaceCreationProperties->Insert(ref new String(EGLNativeWindowTypeProperty), window); // CoreWindow^ window
|
|
|
|
// You can configure the surface to render at a lower resolution and be scaled up to
|
|
// the full window size. The scaling is often free on mobile hardware.
|
|
//
|
|
// One way to configure the SwapChainPanel is to specify precisely which resolution it should render at.
|
|
// Size customRenderSurfaceSize = Size(800, 600);
|
|
// surfaceCreationProperties->Insert(ref new String(EGLRenderSurfaceSizeProperty), PropertyValue::CreateSize(customRenderSurfaceSize));
|
|
//
|
|
// Another way is to tell the SwapChainPanel to render at a certain scale factor compared to its size.
|
|
// e.g. if the SwapChainPanel is 1920x1280 then setting a factor of 0.5f will make the app render at 960x640
|
|
// float customResolutionScale = 0.5f;
|
|
// surfaceCreationProperties->Insert(ref new String(EGLRenderResolutionScaleProperty), PropertyValue::CreateSingle(customResolutionScale));
|
|
|
|
|
|
// eglCreateWindowSurface() requires a EGLNativeWindowType parameter,
|
|
// In Windows platform: typedef HWND EGLNativeWindowType;
|
|
|
|
|
|
// Property: EGLNativeWindowTypeProperty
|
|
// Type: IInspectable
|
|
// Description: Set this property to specify the window type to use for creating a surface.
|
|
// If this property is missing, surface creation will fail.
|
|
//
|
|
//const wchar_t EGLNativeWindowTypeProperty[] = L"EGLNativeWindowTypeProperty";
|
|
|
|
//https://stackoverflow.com/questions/46550182/how-to-create-eglsurface-using-c-winrt-and-angle
|
|
|
|
//CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, reinterpret_cast<IInspectable*>(surfaceCreationProperties), surfaceAttributes);
|
|
CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, (EGLNativeWindowType) UWPGetCoreWindowPtr(), surfaceAttributes);
|
|
if (CORE.Window.surface == EGL_NO_SURFACE)
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL fullscreen surface");
|
|
return false;
|
|
}
|
|
|
|
CORE.Window.context = eglCreateContext(CORE.Window.device, CORE.Window.config, EGL_NO_CONTEXT, contextAttribs);
|
|
if (CORE.Window.context == EGL_NO_CONTEXT)
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context");
|
|
return false;
|
|
}
|
|
|
|
// Get EGL device window size
|
|
eglQuerySurface(CORE.Window.device, CORE.Window.surface, EGL_WIDTH, &CORE.Window.screen.width);
|
|
eglQuerySurface(CORE.Window.device, CORE.Window.surface, EGL_HEIGHT, &CORE.Window.screen.height);
|
|
|
|
// Get display size
|
|
UWPGetDisplaySizeFunc()(&CORE.Window.display.width, &CORE.Window.display.height);
|
|
|
|
// Use the width and height of the surface for render
|
|
CORE.Window.render.width = CORE.Window.screen.width;
|
|
CORE.Window.render.height = CORE.Window.screen.height;
|
|
|
|
#endif // PLATFORM_UWP
|
|
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
EGLint numConfigs = 0;
|
|
|
|
// Get an EGL device connection
|
|
#if defined(PLATFORM_DRM)
|
|
CORE.Window.device = eglGetDisplay((EGLNativeDisplayType)CORE.Window.gbmDevice);
|
|
#else
|
|
CORE.Window.device = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
#endif
|
|
if (CORE.Window.device == EGL_NO_DISPLAY)
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device");
|
|
return false;
|
|
}
|
|
|
|
// Initialize the EGL device connection
|
|
if (eglInitialize(CORE.Window.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;
|
|
}
|
|
|
|
#if defined(PLATFORM_DRM)
|
|
if (!eglGetConfigs(CORE.Window.device, NULL, 0, &numConfigs))
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config count: 0x%x", eglGetError());
|
|
return false;
|
|
}
|
|
|
|
TRACELOG(LOG_TRACE, "DISPLAY: %d EGL configs available", numConfigs);
|
|
|
|
EGLConfig *configs = calloc(numConfigs, sizeof(*configs));
|
|
if (!configs)
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to get memory for EGL configs");
|
|
return false;
|
|
}
|
|
|
|
EGLint matchingNumConfigs = 0;
|
|
if (!eglGetConfigs(CORE.Window.device, configs, numConfigs, &matchingNumConfigs))
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL configs: 0x%x", eglGetError());
|
|
free(configs);
|
|
return false;
|
|
}
|
|
|
|
TRACELOG(LOG_TRACE, "DISPLAY: %d matching EGL configs available", matchingNumConfigs);
|
|
|
|
if (!eglChooseConfig(CORE.Window.device, framebufferAttribs, configs, numConfigs, &matchingNumConfigs))
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to choose EGL config: 0x%x", eglGetError());
|
|
free(configs);
|
|
return false;
|
|
}
|
|
|
|
// find the EGL config that matches the previously setup GBM format
|
|
int found = 0;
|
|
for (EGLint i = 0; i < matchingNumConfigs; ++i)
|
|
{
|
|
EGLint id = 0;
|
|
if (!eglGetConfigAttrib(CORE.Window.device, configs[i], EGL_NATIVE_VISUAL_ID, &id))
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config attribute: 0x%x", eglGetError());
|
|
continue;
|
|
}
|
|
|
|
if (GBM_FORMAT_ARGB8888 == id)
|
|
{
|
|
TRACELOG(LOG_TRACE, "DISPLAY: using EGL config %d", i);
|
|
CORE.Window.config = configs[i];
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(configs);
|
|
|
|
if (!found)
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable EGL config");
|
|
return false;
|
|
}
|
|
#else
|
|
// Get an appropriate EGL framebuffer configuration
|
|
eglChooseConfig(CORE.Window.device, framebufferAttribs, &CORE.Window.config, 1, &numConfigs);
|
|
#endif
|
|
|
|
// Set rendering API
|
|
eglBindAPI(EGL_OPENGL_ES_API);
|
|
|
|
// Create an EGL rendering context
|
|
CORE.Window.context = eglCreateContext(CORE.Window.device, CORE.Window.config, EGL_NO_CONTEXT, contextAttribs);
|
|
if (CORE.Window.context == EGL_NO_CONTEXT)
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// Create an EGL window surface
|
|
//---------------------------------------------------------------------------------
|
|
#if defined(PLATFORM_ANDROID)
|
|
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(CORE.Window.device, CORE.Window.config, EGL_NATIVE_VISUAL_ID, &displayFormat);
|
|
|
|
// At this point we need to manage render size vs screen size
|
|
// NOTE: This function use and modify global module variables:
|
|
// -> CORE.Window.screen.width/CORE.Window.screen.height
|
|
// -> CORE.Window.render.width/CORE.Window.render.height
|
|
// -> CORE.Window.screenScale
|
|
SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height);
|
|
|
|
ANativeWindow_setBuffersGeometry(CORE.Android.app->window, CORE.Window.render.width, CORE.Window.render.height, displayFormat);
|
|
//ANativeWindow_setBuffersGeometry(CORE.Android.app->window, 0, 0, displayFormat); // Force use of native display size
|
|
|
|
CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, CORE.Android.app->window, NULL);
|
|
#endif // PLATFORM_ANDROID
|
|
|
|
#if defined(PLATFORM_RPI)
|
|
graphics_get_display_size(0, &CORE.Window.display.width, &CORE.Window.display.height);
|
|
|
|
// Screen size security check
|
|
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;
|
|
|
|
// At this point we need to manage render size vs screen size
|
|
// NOTE: This function use and modify global module variables:
|
|
// -> CORE.Window.screen.width/CORE.Window.screen.height
|
|
// -> CORE.Window.render.width/CORE.Window.render.height
|
|
// -> CORE.Window.screenScale
|
|
SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height);
|
|
|
|
dstRect.x = 0;
|
|
dstRect.y = 0;
|
|
dstRect.width = CORE.Window.display.width;
|
|
dstRect.height = CORE.Window.display.height;
|
|
|
|
srcRect.x = 0;
|
|
srcRect.y = 0;
|
|
srcRect.width = CORE.Window.render.width << 16;
|
|
srcRect.height = CORE.Window.render.height << 16;
|
|
|
|
// NOTE: RPI dispmanx windowing system takes care of srcRec scaling to dstRec by hardware (no cost)
|
|
// Take care that renderWidth/renderHeight fit on displayWidth/displayHeight aspect ratio
|
|
|
|
VC_DISPMANX_ALPHA_T alpha;
|
|
alpha.flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS;
|
|
//alpha.flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE; // TODO: Allow transparent framebuffer! -> FLAG_WINDOW_TRANSPARENT
|
|
alpha.opacity = 255; // Set transparency level for framebuffer, requires EGLAttrib: EGL_TRANSPARENT_TYPE
|
|
alpha.mask = 0;
|
|
|
|
dispmanDisplay = vc_dispmanx_display_open(0); // LCD
|
|
dispmanUpdate = vc_dispmanx_update_start(0);
|
|
|
|
dispmanElement = vc_dispmanx_element_add(dispmanUpdate, dispmanDisplay, 0/*layer*/, &dstRect, 0/*src*/,
|
|
&srcRect, DISPMANX_PROTECTION_NONE, &alpha, 0/*clamp*/, DISPMANX_NO_ROTATE);
|
|
|
|
CORE.Window.handle.element = dispmanElement;
|
|
CORE.Window.handle.width = CORE.Window.render.width;
|
|
CORE.Window.handle.height = CORE.Window.render.height;
|
|
vc_dispmanx_update_submit_sync(dispmanUpdate);
|
|
|
|
CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, &CORE.Window.handle, NULL);
|
|
|
|
const unsigned char *const renderer = glGetString(GL_RENDERER);
|
|
if (renderer) TRACELOG(LOG_INFO, "Renderer is: %s\n", renderer);
|
|
else TRACELOG(LOG_WARNING, "failed to get renderer\n");
|
|
//---------------------------------------------------------------------------------
|
|
#endif // PLATFORM_RPI
|
|
|
|
#if defined(PLATFORM_DRM)
|
|
CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, (EGLNativeWindowType)CORE.Window.gbmSurface, NULL);
|
|
if (EGL_NO_SURFACE == CORE.Window.surface)
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL window surface: 0x%04x", eglGetError());
|
|
return false;
|
|
}
|
|
|
|
// At this point we need to manage render size vs screen size
|
|
// NOTE: This function use and modify global module variables:
|
|
// -> CORE.Window.screen.width/CORE.Window.screen.height
|
|
// -> CORE.Window.render.width/CORE.Window.render.height
|
|
// -> CORE.Window.screenScale
|
|
SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height);
|
|
#endif // PLATFORM_DRM
|
|
|
|
// There must be at least one frame displayed before the buffers are swapped
|
|
//eglSwapInterval(CORE.Window.device, 1);
|
|
|
|
if (eglMakeCurrent(CORE.Window.device, CORE.Window.surface, CORE.Window.surface, CORE.Window.context) == EGL_FALSE)
|
|
{
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to attach EGL rendering context to EGL surface");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
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, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height);
|
|
TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height);
|
|
TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y);
|
|
}
|
|
#endif // PLATFORM_ANDROID || PLATFORM_RPI || PLATFORM_DRM || PLATFORM_UWP
|
|
|
|
// Initialize OpenGL context (states and resources)
|
|
// NOTE: CORE.Window.screen.width and CORE.Window.screen.height not used, just stored as globals in rlgl
|
|
rlglInit(CORE.Window.screen.width, CORE.Window.screen.height);
|
|
|
|
int fbWidth = CORE.Window.render.width;
|
|
int fbHeight = CORE.Window.render.height;
|
|
|
|
#if defined(PLATFORM_DESKTOP)
|
|
if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0)
|
|
{
|
|
glfwGetFramebufferSize(CORE.Window.handle, &fbWidth, &fbHeight);
|
|
|
|
// Screen scaling matrix is required in case desired screen area is different than display area
|
|
CORE.Window.screenScale = MatrixScale((float)fbWidth/CORE.Window.screen.width, (float)fbHeight/CORE.Window.screen.height, 1.0f);
|
|
#if !defined(__APPLE__)
|
|
SetMouseScale((float)CORE.Window.screen.width/fbWidth, (float)CORE.Window.screen.height/fbHeight);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
// Setup default viewport
|
|
SetupViewport(fbWidth, fbHeight);
|
|
|
|
CORE.Window.currentFbo.width = CORE.Window.screen.width;
|
|
CORE.Window.currentFbo.height = CORE.Window.screen.height;
|
|
|
|
ClearBackground(RAYWHITE); // Default background color for raylib games :P
|
|
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_UWP)
|
|
CORE.Window.ready = true;
|
|
#endif
|
|
|
|
if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) MinimizeWindow();
|
|
|
|
return true;
|
|
}
|
|
|
|
// Set viewport for a provided width and height
|
|
static void SetupViewport(int width, int height)
|
|
{
|
|
CORE.Window.render.width = width;
|
|
CORE.Window.render.height = height;
|
|
|
|
// Set viewport width and height
|
|
// NOTE: We consider render size 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)
|
|
rlViewport(CORE.Window.renderOffset.x/2, CORE.Window.renderOffset.y/2, CORE.Window.render.width - CORE.Window.renderOffset.x, CORE.Window.render.height - CORE.Window.renderOffset.y);
|
|
|
|
rlMatrixMode(RL_PROJECTION); // Switch to projection matrix
|
|
rlLoadIdentity(); // Reset current matrix (projection)
|
|
|
|
// Set orthographic projection to current framebuffer size
|
|
// NOTE: Configured top-left corner as (0, 0)
|
|
rlOrtho(0, CORE.Window.render.width, CORE.Window.render.height, 0, 0.0f, 1.0f);
|
|
|
|
rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix
|
|
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
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Initialize hi-resolution timer
|
|
static void InitTimer(void)
|
|
{
|
|
srand((unsigned int)time(NULL)); // Initialize random seed
|
|
|
|
#if !defined(SUPPORT_BUSY_WAIT_LOOP) && defined(_WIN32) && !defined(PLATFORM_UWP)
|
|
timeBeginPeriod(1); // Setup high-resolution timer to 1ms (granularity of 1-2 ms)
|
|
#endif
|
|
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
struct timespec now;
|
|
|
|
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
|
|
}
|
|
|
|
// Wait for some milliseconds (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 timming on Win32!
|
|
static void Wait(float ms)
|
|
{
|
|
#if defined(PLATFORM_UWP)
|
|
UWPGetSleepFunc()(ms/1000);
|
|
return;
|
|
#endif
|
|
|
|
#if defined(SUPPORT_BUSY_WAIT_LOOP)
|
|
double prevTime = GetTime();
|
|
double nextTime = 0.0;
|
|
|
|
// Busy wait loop
|
|
while ((nextTime - prevTime) < ms/1000.0f) nextTime = GetTime();
|
|
#else
|
|
#if defined(SUPPORT_HALFBUSY_WAIT_LOOP)
|
|
#define MAX_HALFBUSY_WAIT_TIME 4
|
|
double destTime = GetTime() + ms/1000;
|
|
if (ms > MAX_HALFBUSY_WAIT_TIME) ms -= MAX_HALFBUSY_WAIT_TIME;
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
Sleep((unsigned int)ms);
|
|
#elif defined(__linux__) || defined(PLATFORM_WEB)
|
|
struct timespec req = { 0 };
|
|
time_t sec = (int)(ms/1000.0f);
|
|
ms -= (sec*1000);
|
|
req.tv_sec = sec;
|
|
req.tv_nsec = ms*1000000L;
|
|
|
|
// NOTE: Use nanosleep() on Unix platforms... usleep() it's deprecated.
|
|
while (nanosleep(&req, &req) == -1) continue;
|
|
#elif defined(__APPLE__)
|
|
usleep(ms*1000.0f);
|
|
#endif
|
|
|
|
#if defined(SUPPORT_HALFBUSY_WAIT_LOOP)
|
|
while (GetTime() < destTime) { }
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
// Get gamepad button generic to all platforms
|
|
static int GetGamepadButton(int button)
|
|
{
|
|
int btn = GAMEPAD_BUTTON_UNKNOWN;
|
|
#if defined(PLATFORM_DESKTOP)
|
|
switch (button)
|
|
{
|
|
case GLFW_GAMEPAD_BUTTON_Y: btn = GAMEPAD_BUTTON_RIGHT_FACE_UP; break;
|
|
case GLFW_GAMEPAD_BUTTON_B: btn = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break;
|
|
case GLFW_GAMEPAD_BUTTON_A: btn = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break;
|
|
case GLFW_GAMEPAD_BUTTON_X: btn = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break;
|
|
|
|
case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER: btn = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break;
|
|
case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER: btn = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break;
|
|
|
|
case GLFW_GAMEPAD_BUTTON_BACK: btn = GAMEPAD_BUTTON_MIDDLE_LEFT; break;
|
|
case GLFW_GAMEPAD_BUTTON_GUIDE: btn = GAMEPAD_BUTTON_MIDDLE; break;
|
|
case GLFW_GAMEPAD_BUTTON_START: btn = GAMEPAD_BUTTON_MIDDLE_RIGHT; break;
|
|
|
|
case GLFW_GAMEPAD_BUTTON_DPAD_UP: btn = GAMEPAD_BUTTON_LEFT_FACE_UP; break;
|
|
case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT: btn = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break;
|
|
case GLFW_GAMEPAD_BUTTON_DPAD_DOWN: btn = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break;
|
|
case GLFW_GAMEPAD_BUTTON_DPAD_LEFT: btn = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break;
|
|
|
|
case GLFW_GAMEPAD_BUTTON_LEFT_THUMB: btn = GAMEPAD_BUTTON_LEFT_THUMB; break;
|
|
case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB: btn = GAMEPAD_BUTTON_RIGHT_THUMB; break;
|
|
}
|
|
#endif
|
|
|
|
#if defined(PLATFORM_UWP)
|
|
btn = button; // UWP will provide the correct button
|
|
#endif
|
|
|
|
#if defined(PLATFORM_WEB)
|
|
// Gamepad Buttons reference: https://www.w3.org/TR/gamepad/#gamepad-interface
|
|
switch (button)
|
|
{
|
|
case 0: btn = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break;
|
|
case 1: btn = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break;
|
|
case 2: btn = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break;
|
|
case 3: btn = GAMEPAD_BUTTON_RIGHT_FACE_UP; break;
|
|
case 4: btn = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break;
|
|
case 5: btn = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break;
|
|
case 6: btn = GAMEPAD_BUTTON_LEFT_TRIGGER_2; break;
|
|
case 7: btn = GAMEPAD_BUTTON_RIGHT_TRIGGER_2; break;
|
|
case 8: btn = GAMEPAD_BUTTON_MIDDLE_LEFT; break;
|
|
case 9: btn = GAMEPAD_BUTTON_MIDDLE_RIGHT; break;
|
|
case 10: btn = GAMEPAD_BUTTON_LEFT_THUMB; break;
|
|
case 11: btn = GAMEPAD_BUTTON_RIGHT_THUMB; break;
|
|
case 12: btn = GAMEPAD_BUTTON_LEFT_FACE_UP; break;
|
|
case 13: btn = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break;
|
|
case 14: btn = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break;
|
|
case 15: btn = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break;
|
|
}
|
|
#endif
|
|
|
|
return btn;
|
|
}
|
|
|
|
// Poll (store) all input events
|
|
static 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 key pressed registered
|
|
CORE.Input.Keyboard.keyPressedQueueCount = 0;
|
|
|
|
#if !(defined(PLATFORM_RPI) || defined(PLATFORM_DRM))
|
|
// Reset last gamepad button/axis registered state
|
|
CORE.Input.Gamepad.lastButtonPressed = -1;
|
|
CORE.Input.Gamepad.axisCount = 0;
|
|
#endif
|
|
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
// Register previous keys states
|
|
for (int i = 0; i < 512; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i];
|
|
|
|
// Grab a keypress from the evdev fifo if avalable
|
|
if (CORE.Input.Keyboard.lastKeyPressed.head != CORE.Input.Keyboard.lastKeyPressed.tail)
|
|
{
|
|
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = CORE.Input.Keyboard.lastKeyPressed.contents[CORE.Input.Keyboard.lastKeyPressed.tail]; // Read the key from the buffer
|
|
CORE.Input.Keyboard.keyPressedQueueCount++;
|
|
|
|
CORE.Input.Keyboard.lastKeyPressed.tail = (CORE.Input.Keyboard.lastKeyPressed.tail + 1) & 0x07; // Increment the tail pointer forwards and binary wraparound after 7 (fifo is 8 elements long)
|
|
}
|
|
|
|
// Register previous mouse states
|
|
CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove;
|
|
CORE.Input.Mouse.currentWheelMove = 0.0f;
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i];
|
|
CORE.Input.Mouse.currentButtonState[i] = CORE.Input.Mouse.currentButtonStateEvdev[i];
|
|
}
|
|
#endif
|
|
|
|
#if defined(PLATFORM_UWP)
|
|
// Register previous keys states
|
|
for (int i = 0; i < 512; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i];
|
|
|
|
for (int i = 0; i < MAX_GAMEPADS; i++)
|
|
{
|
|
if (CORE.Input.Gamepad.ready[i])
|
|
{
|
|
for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousState[i][k] = CORE.Input.Gamepad.currentState[i][k];
|
|
}
|
|
}
|
|
|
|
// Register previous mouse states
|
|
CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove;
|
|
CORE.Input.Mouse.currentWheelMove = 0.0f;
|
|
|
|
for (int i = 0; i < 3; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i];
|
|
#endif // PLATFORM_UWP
|
|
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
|
|
// Keyboard/Mouse input polling (automatically managed by GLFW3 through callback)
|
|
|
|
// Register previous keys states
|
|
for (int i = 0; i < 512; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i];
|
|
|
|
// Register previous mouse states
|
|
for (int i = 0; i < 3; 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 = 0.0f;
|
|
#endif
|
|
|
|
// Register previous touch states
|
|
for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i];
|
|
|
|
#if defined(PLATFORM_DESKTOP)
|
|
// Check if gamepads are ready
|
|
// NOTE: We do it here in case of disconnection
|
|
for (int i = 0; i < MAX_GAMEPADS; i++)
|
|
{
|
|
if (glfwJoystickPresent(i)) CORE.Input.Gamepad.ready[i] = true;
|
|
else CORE.Input.Gamepad.ready[i] = false;
|
|
}
|
|
|
|
// Register gamepads buttons events
|
|
for (int i = 0; i < MAX_GAMEPADS; i++)
|
|
{
|
|
if (CORE.Input.Gamepad.ready[i]) // Check if gamepad is available
|
|
{
|
|
// Register previous gamepad states
|
|
for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousState[i][k] = CORE.Input.Gamepad.currentState[i][k];
|
|
|
|
// Get current gamepad state
|
|
// NOTE: There is no callback available, so we get it manually
|
|
// Get remapped buttons
|
|
GLFWgamepadstate state = { 0 };
|
|
glfwGetGamepadState(i, &state); // This remapps all gamepads so they have their buttons mapped like an xbox controller
|
|
|
|
const unsigned char *buttons = state.buttons;
|
|
|
|
for (int k = 0; (buttons != NULL) && (k < GLFW_GAMEPAD_BUTTON_DPAD_LEFT + 1) && (k < MAX_GAMEPAD_BUTTONS); k++)
|
|
{
|
|
const GamepadButton button = GetGamepadButton(k);
|
|
|
|
if (buttons[k] == GLFW_PRESS)
|
|
{
|
|
CORE.Input.Gamepad.currentState[i][button] = 1;
|
|
CORE.Input.Gamepad.lastButtonPressed = button;
|
|
}
|
|
else CORE.Input.Gamepad.currentState[i][button] = 0;
|
|
}
|
|
|
|
// Get current axis state
|
|
const float *axes = state.axes;
|
|
|
|
for (int k = 0; (axes != NULL) && (k < GLFW_GAMEPAD_AXIS_LAST + 1) && (k < MAX_GAMEPAD_AXIS); k++)
|
|
{
|
|
CORE.Input.Gamepad.axisState[i][k] = axes[k];
|
|
}
|
|
|
|
// Register buttons for 2nd triggers (because GLFW doesn't count these as buttons but rather axis)
|
|
CORE.Input.Gamepad.currentState[i][GAMEPAD_BUTTON_LEFT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_LEFT_TRIGGER] > 0.1);
|
|
CORE.Input.Gamepad.currentState[i][GAMEPAD_BUTTON_RIGHT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_RIGHT_TRIGGER] > 0.1);
|
|
|
|
CORE.Input.Gamepad.axisCount = GLFW_GAMEPAD_AXIS_LAST;
|
|
}
|
|
}
|
|
|
|
CORE.Window.resizedLastFrame = false;
|
|
|
|
#if defined(SUPPORT_EVENTS_WAITING)
|
|
glfwWaitEvents();
|
|
#else
|
|
glfwPollEvents(); // Register keyboard/mouse events (callbacks)... and window events!
|
|
#endif
|
|
#endif //defined(PLATFORM_DESKTOP)
|
|
|
|
// Gamepad support using emscripten API
|
|
// NOTE: GLFW3 joystick functionality not available in web
|
|
#if defined(PLATFORM_WEB)
|
|
// 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.previousState[i][k] = CORE.Input.Gamepad.currentState[i][k];
|
|
|
|
EmscriptenGamepadEvent gamepadState;
|
|
|
|
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++)
|
|
{
|
|
const GamepadButton button = GetGamepadButton(j);
|
|
if (gamepadState.digitalButton[j] == 1)
|
|
{
|
|
CORE.Input.Gamepad.currentState[i][button] = 1;
|
|
CORE.Input.Gamepad.lastButtonPressed = button;
|
|
}
|
|
else CORE.Input.Gamepad.currentState[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_AXIS); j++)
|
|
{
|
|
CORE.Input.Gamepad.axisState[i][j] = gamepadState.axis[j];
|
|
}
|
|
|
|
CORE.Input.Gamepad.axisCount = gamepadState.numAxes;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(PLATFORM_ANDROID)
|
|
// 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];
|
|
|
|
// Android ALooper_pollAll() variables
|
|
int pollResult = 0;
|
|
int pollEvents = 0;
|
|
|
|
// Poll Events (registered events)
|
|
// NOTE: Activity is paused if not enabled (CORE.Android.appEnabled)
|
|
while ((pollResult = ALooper_pollAll(CORE.Android.appEnabled? 0 : -1, NULL, &pollEvents, (void**)&CORE.Android.source)) >= 0)
|
|
{
|
|
// Process this event
|
|
if (CORE.Android.source != NULL) CORE.Android.source->process(CORE.Android.app, CORE.Android.source);
|
|
|
|
// NOTE: Never close window, native activity is controlled by the system!
|
|
if (CORE.Android.app->destroyRequested != 0)
|
|
{
|
|
//CORE.Window.shouldClose = true;
|
|
//ANativeActivity_finish(CORE.Android.app->activity);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if (defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) && defined(SUPPORT_SSH_KEYBOARD_RPI)
|
|
// NOTE: Keyboard reading could be done using input_event(s) reading or just read from stdin,
|
|
// we now use both methods inside here. 2nd method is still used for legacy purposes (Allows for input trough SSH console)
|
|
ProcessKeyboard();
|
|
|
|
// NOTE: Mouse input events polling is done asynchronously in another pthread - EventThread()
|
|
// NOTE: Gamepad (Joystick) input events polling is done asynchonously in another pthread - GamepadThread()
|
|
#endif
|
|
}
|
|
|
|
// Copy back buffer to front buffers
|
|
static void SwapBuffers(void)
|
|
{
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
|
|
glfwSwapBuffers(CORE.Window.handle);
|
|
#endif
|
|
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP)
|
|
eglSwapBuffers(CORE.Window.device, CORE.Window.surface);
|
|
|
|
#if defined(PLATFORM_DRM)
|
|
if (!CORE.Window.gbmSurface || (-1 == CORE.Window.fd) || !CORE.Window.connector || !CORE.Window.crtc)
|
|
{
|
|
TRACELOG(LOG_ERROR, "DRM initialization failed, can't swap");
|
|
abort();
|
|
}
|
|
|
|
struct gbm_bo *bo = gbm_surface_lock_front_buffer(CORE.Window.gbmSurface);
|
|
if (!bo)
|
|
{
|
|
TRACELOG(LOG_ERROR, "GBM failed to lock front buffer");
|
|
abort();
|
|
}
|
|
|
|
uint32_t fb = 0;
|
|
int result = drmModeAddFB(CORE.Window.fd, CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay,
|
|
CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, 24, 32, gbm_bo_get_stride(bo), gbm_bo_get_handle(bo).u32, &fb);
|
|
if (0 != result)
|
|
{
|
|
TRACELOG(LOG_ERROR, "drmModeAddFB failed with %d", result);
|
|
abort();
|
|
}
|
|
|
|
result = drmModeSetCrtc(CORE.Window.fd, CORE.Window.crtc->crtc_id, fb, 0, 0,
|
|
&CORE.Window.connector->connector_id, 1, &CORE.Window.connector->modes[CORE.Window.modeIndex]);
|
|
if (0 != result)
|
|
{
|
|
TRACELOG(LOG_ERROR, "drmModeSetCrtc failed with %d", result);
|
|
abort();
|
|
}
|
|
|
|
if (CORE.Window.prevFB)
|
|
{
|
|
result = drmModeRmFB(CORE.Window.fd, CORE.Window.prevFB);
|
|
if (0 != result)
|
|
{
|
|
TRACELOG(LOG_ERROR, "drmModeRmFB failed with %d", result);
|
|
abort();
|
|
}
|
|
}
|
|
CORE.Window.prevFB = fb;
|
|
|
|
if (CORE.Window.prevBO)
|
|
{
|
|
gbm_surface_release_buffer(CORE.Window.gbmSurface, CORE.Window.prevBO);
|
|
}
|
|
CORE.Window.prevBO = bo;
|
|
#endif // defined(PLATFORM_DRM)
|
|
#endif // defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP)
|
|
}
|
|
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
|
|
// 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 Srolling Callback, runs on mouse wheel
|
|
static void ScrollCallback(GLFWwindow *window, double xoffset, double yoffset)
|
|
{
|
|
CORE.Input.Mouse.currentWheelMove = (float)yoffset;
|
|
}
|
|
|
|
// GLFW3 Keyboard Callback, runs on key pressed
|
|
static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
|
|
{
|
|
if (key == CORE.Input.Keyboard.exitKey && action == GLFW_PRESS)
|
|
{
|
|
glfwSetWindowShouldClose(CORE.Window.handle, GLFW_TRUE);
|
|
|
|
// NOTE: Before closing window, while loop must be left!
|
|
}
|
|
else if (key == GLFW_KEY_F12 && action == GLFW_PRESS)
|
|
{
|
|
#if defined(SUPPORT_GIF_RECORDING)
|
|
if (mods == GLFW_MOD_CONTROL)
|
|
{
|
|
if (gifRecording)
|
|
{
|
|
gifRecording = false;
|
|
|
|
MsfGifResult result = msf_gif_end(&gifState);
|
|
|
|
char path[512] = { 0 };
|
|
#if defined(PLATFORM_ANDROID)
|
|
strcpy(path, CORE.Android.internalDataPath);
|
|
strcat(path, TextFormat("./screenrec%03i.gif", screenshotCounter));
|
|
#else
|
|
strcpy(path, TextFormat("./screenrec%03i.gif", screenshotCounter));
|
|
#endif
|
|
|
|
SaveFileData(path, result.data, (unsigned int)result.dataSize);
|
|
msf_gif_free(result);
|
|
|
|
#if defined(PLATFORM_WEB)
|
|
// Download file from MEMFS (emscripten memory filesystem)
|
|
// saveFileFromMEMFSToDisk() function is defined in raylib/templates/web_shel/shell.html
|
|
emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", TextFormat("screenrec%03i.gif", screenshotCounter - 1), TextFormat("screenrec%03i.gif", screenshotCounter - 1)));
|
|
#endif
|
|
|
|
TRACELOG(LOG_INFO, "SYSTEM: Finish animated GIF recording");
|
|
}
|
|
else
|
|
{
|
|
gifRecording = true;
|
|
gifFramesCounter = 0;
|
|
|
|
msf_gif_begin(&gifState, CORE.Window.screen.width, CORE.Window.screen.height);
|
|
screenshotCounter++;
|
|
|
|
TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter));
|
|
}
|
|
}
|
|
else
|
|
#endif // SUPPORT_GIF_RECORDING
|
|
#if defined(SUPPORT_SCREEN_CAPTURE)
|
|
{
|
|
TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter));
|
|
screenshotCounter++;
|
|
}
|
|
#endif // SUPPORT_SCREEN_CAPTURE
|
|
}
|
|
else
|
|
{
|
|
// WARNING: GLFW could return GLFW_REPEAT, we need to consider it as 1
|
|
// to work properly with our implementation (IsKeyDown/IsKeyUp checks)
|
|
if (action == GLFW_RELEASE) CORE.Input.Keyboard.currentKeyState[key] = 0;
|
|
else CORE.Input.Keyboard.currentKeyState[key] = 1;
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
// but future releases may add more actions (i.e. GLFW_REPEAT)
|
|
CORE.Input.Mouse.currentButtonState[button] = action;
|
|
|
|
#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[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) gestureEvent.touchAction = TOUCH_DOWN;
|
|
else if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) gestureEvent.touchAction = TOUCH_UP;
|
|
|
|
// NOTE: TOUCH_MOVE event is registered in MouseCursorPosCallback()
|
|
|
|
// Assign a pointer ID
|
|
gestureEvent.pointerId[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
|
|
ProcessGestureEvent(gestureEvent);
|
|
#endif
|
|
}
|
|
|
|
// GLFW3 Cursor Position Callback, runs on mouse move
|
|
static void MouseCursorPosCallback(GLFWwindow *window, double x, double y)
|
|
{
|
|
CORE.Input.Mouse.position.x = (float)x;
|
|
CORE.Input.Mouse.position.y = (float)y;
|
|
CORE.Input.Touch.position[0] = CORE.Input.Mouse.position;
|
|
|
|
#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_MOVE;
|
|
|
|
// Assign a pointer ID
|
|
gestureEvent.pointerId[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
|
|
}
|
|
|
|
// GLFW3 Char Key Callback, runs on key down (get unicode char value)
|
|
static void CharCallback(GLFWwindow *window, unsigned int key)
|
|
{
|
|
// NOTE: Registers any key down considering OS keyboard layout but
|
|
// do not detects 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
|
|
|
|
// Check if there is space available in the queue
|
|
if (CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE)
|
|
{
|
|
// Add character to the queue
|
|
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key;
|
|
CORE.Input.Keyboard.keyPressedQueueCount++;
|
|
}
|
|
}
|
|
|
|
// GLFW3 CursorEnter Callback, when cursor enters the window
|
|
static void CursorEnterCallback(GLFWwindow *window, int enter)
|
|
{
|
|
if (enter == true) CORE.Input.Mouse.cursorOnScreen = true;
|
|
else CORE.Input.Mouse.cursorOnScreen = false;
|
|
}
|
|
|
|
// GLFW3 WindowSize Callback, runs when window is resizedLastFrame
|
|
// NOTE: Window resizing not allowed by default
|
|
static void WindowSizeCallback(GLFWwindow *window, int width, int height)
|
|
{
|
|
SetupViewport(width, height); // Reset viewport and projection matrix for new size
|
|
|
|
// Set current screen size
|
|
CORE.Window.screen.width = width;
|
|
CORE.Window.screen.height = height;
|
|
CORE.Window.currentFbo.width = width;
|
|
CORE.Window.currentFbo.height = height;
|
|
|
|
// NOTE: Postprocessing texture is not scaled to new size
|
|
|
|
CORE.Window.resizedLastFrame = true;
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// GLFW3 Window Drop Callback, runs when drop files into window
|
|
// NOTE: Paths are stored in dynamic memory for further retrieval
|
|
// Everytime new files are dropped, old ones are discarded
|
|
static void WindowDropCallback(GLFWwindow *window, int count, const char **paths)
|
|
{
|
|
ClearDroppedFiles();
|
|
|
|
CORE.Window.dropFilesPath = (char **)RL_MALLOC(sizeof(char *)*count);
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
CORE.Window.dropFilesPath[i] = (char *)RL_MALLOC(sizeof(char)*MAX_FILEPATH_LENGTH);
|
|
strcpy(CORE.Window.dropFilesPath[i], paths[i]);
|
|
}
|
|
|
|
CORE.Window.dropFilesCount = count;
|
|
}
|
|
#endif
|
|
|
|
#if defined(PLATFORM_ANDROID)
|
|
// ANDROID: Process activity lifecycle commands
|
|
static void AndroidCommandCallback(struct android_app *app, int32_t cmd)
|
|
{
|
|
switch (cmd)
|
|
{
|
|
case APP_CMD_START:
|
|
{
|
|
//rendering = true;
|
|
} break;
|
|
case APP_CMD_RESUME: break;
|
|
case APP_CMD_INIT_WINDOW:
|
|
{
|
|
if (app->window != NULL)
|
|
{
|
|
if (CORE.Android.contextRebindRequired)
|
|
{
|
|
// Reset screen scaling to full display size
|
|
EGLint displayFormat;
|
|
eglGetConfigAttrib(CORE.Window.device, CORE.Window.config, EGL_NATIVE_VISUAL_ID, &displayFormat);
|
|
ANativeWindow_setBuffersGeometry(app->window, CORE.Window.render.width, CORE.Window.render.height, displayFormat);
|
|
|
|
// Recreate display surface and re-attach OpenGL context
|
|
CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, app->window, NULL);
|
|
eglMakeCurrent(CORE.Window.device, CORE.Window.surface, CORE.Window.surface, CORE.Window.context);
|
|
|
|
CORE.Android.contextRebindRequired = false;
|
|
}
|
|
else
|
|
{
|
|
CORE.Window.display.width = ANativeWindow_getWidth(CORE.Android.app->window);
|
|
CORE.Window.display.height = ANativeWindow_getHeight(CORE.Android.app->window);
|
|
|
|
// Init graphics device (display device and OpenGL context)
|
|
InitGraphicsDevice(CORE.Window.screen.width, CORE.Window.screen.height);
|
|
|
|
// Init hi-res timer
|
|
InitTimer();
|
|
|
|
#if defined(SUPPORT_DEFAULT_FONT)
|
|
// Load default font
|
|
// NOTE: External function (defined in module: text)
|
|
LoadFontDefault();
|
|
Rectangle rec = GetFontDefault().recs[95];
|
|
// NOTE: We setup a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering
|
|
SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 });
|
|
#endif
|
|
|
|
// 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 < assetsCount; i++)
|
|
{
|
|
// TODO: Unload old asset if required
|
|
|
|
// Load texture again to pointed texture
|
|
(*textureAsset + i) = LoadTexture(assetPath[i]);
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
} break;
|
|
case APP_CMD_GAINED_FOCUS:
|
|
{
|
|
CORE.Android.appEnabled = true;
|
|
//ResumeMusicStream();
|
|
} break;
|
|
case APP_CMD_PAUSE: break;
|
|
case APP_CMD_LOST_FOCUS:
|
|
{
|
|
CORE.Android.appEnabled = false;
|
|
//PauseMusicStream();
|
|
} break;
|
|
case APP_CMD_TERM_WINDOW:
|
|
{
|
|
// Dettach OpenGL context and destroy display surface
|
|
// NOTE 1: Detaching context before destroying display surface avoids losing our resources (textures, shaders, VBOs...)
|
|
// NOTE 2: In some cases (too many context loaded), OS could unload context automatically... :(
|
|
eglMakeCurrent(CORE.Window.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
|
eglDestroySurface(CORE.Window.device, CORE.Window.surface);
|
|
|
|
CORE.Android.contextRebindRequired = true;
|
|
} break;
|
|
case APP_CMD_SAVE_STATE: break;
|
|
case APP_CMD_STOP: break;
|
|
case APP_CMD_DESTROY:
|
|
{
|
|
// TODO: Finish activity?
|
|
//ANativeActivity_finish(CORE.Android.app->activity);
|
|
} break;
|
|
case APP_CMD_CONFIG_CHANGED:
|
|
{
|
|
//AConfiguration_fromAssetManager(CORE.Android.app->config, CORE.Android.app->activity->assetManager);
|
|
//print_cur_config(CORE.Android.app);
|
|
|
|
// Check screen orientation here!
|
|
} break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
// ANDROID: Get input events
|
|
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
|
|
|
|
int type = AInputEvent_getType(event);
|
|
int source = AInputEvent_getSource(event);
|
|
|
|
if (type == AINPUT_EVENT_TYPE_MOTION)
|
|
{
|
|
if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) ||
|
|
((source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD))
|
|
{
|
|
// Get first touch position
|
|
CORE.Input.Touch.position[0].x = AMotionEvent_getX(event, 0);
|
|
CORE.Input.Touch.position[0].y = AMotionEvent_getY(event, 0);
|
|
|
|
// Get second touch position
|
|
CORE.Input.Touch.position[1].x = AMotionEvent_getX(event, 1);
|
|
CORE.Input.Touch.position[1].y = AMotionEvent_getY(event, 1);
|
|
|
|
int32_t keycode = AKeyEvent_getKeyCode(event);
|
|
if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN)
|
|
{
|
|
CORE.Input.Keyboard.currentKeyState[keycode] = 1; // Key down
|
|
|
|
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode;
|
|
CORE.Input.Keyboard.keyPressedQueueCount++;
|
|
}
|
|
else CORE.Input.Keyboard.currentKeyState[keycode] = 0; // Key up
|
|
|
|
// Stop processing gamepad buttons
|
|
return 1;
|
|
}
|
|
}
|
|
else if (type == AINPUT_EVENT_TYPE_KEY)
|
|
{
|
|
int32_t keycode = AKeyEvent_getKeyCode(event);
|
|
//int32_t AKeyEvent_getMetaState(event);
|
|
|
|
// Save current button and its state
|
|
// NOTE: Android key action is 0 for down and 1 for up
|
|
if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN)
|
|
{
|
|
CORE.Input.Keyboard.currentKeyState[keycode] = 1; // Key down
|
|
|
|
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode;
|
|
CORE.Input.Keyboard.keyPressedQueueCount++;
|
|
}
|
|
else CORE.Input.Keyboard.currentKeyState[keycode] = 0; // Key up
|
|
|
|
if (keycode == AKEYCODE_POWER)
|
|
{
|
|
// 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.
|
|
// NOTE: AndroidManifest.xml must have <activity android:configChanges="orientation|keyboardHidden|screenSize" >
|
|
// Before that change, activity was calling CMD_TERM_WINDOW and CMD_DESTROY when locking mobile, so that was not a normal behaviour
|
|
return 0;
|
|
}
|
|
else if ((keycode == AKEYCODE_BACK) || (keycode == AKEYCODE_MENU))
|
|
{
|
|
// Eat BACK_BUTTON and AKEYCODE_MENU, just do nothing... and don't let to be handled by OS!
|
|
return 1;
|
|
}
|
|
else if ((keycode == AKEYCODE_VOLUME_UP) || (keycode == AKEYCODE_VOLUME_DOWN))
|
|
{
|
|
// Set default OS behaviour
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
CORE.Input.Touch.position[0].x = AMotionEvent_getX(event, 0);
|
|
CORE.Input.Touch.position[0].y = AMotionEvent_getY(event, 0);
|
|
|
|
int32_t action = AMotionEvent_getAction(event);
|
|
unsigned int flags = action & AMOTION_EVENT_ACTION_MASK;
|
|
|
|
if (flags == AMOTION_EVENT_ACTION_DOWN || flags == AMOTION_EVENT_ACTION_MOVE)
|
|
{
|
|
CORE.Input.Touch.currentTouchState[MOUSE_LEFT_BUTTON] = 1;
|
|
}
|
|
else if (flags == AMOTION_EVENT_ACTION_UP)
|
|
{
|
|
CORE.Input.Touch.currentTouchState[MOUSE_LEFT_BUTTON] = 0;
|
|
}
|
|
|
|
#if defined(SUPPORT_GESTURES_SYSTEM)
|
|
|
|
GestureEvent gestureEvent;
|
|
|
|
// Register touch actions
|
|
if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.touchAction = TOUCH_DOWN;
|
|
else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.touchAction = TOUCH_UP;
|
|
else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.touchAction = TOUCH_MOVE;
|
|
|
|
// Register touch points count
|
|
// NOTE: Documentation says pointerCount is Always >= 1,
|
|
// but in practice it can be 0 or over a million
|
|
gestureEvent.pointCount = AMotionEvent_getPointerCount(event);
|
|
|
|
// Only enable gestures for 1-3 touch points
|
|
if ((gestureEvent.pointCount > 0) && (gestureEvent.pointCount < 4))
|
|
{
|
|
// Register touch points id
|
|
// NOTE: Only two points registered
|
|
gestureEvent.pointerId[0] = AMotionEvent_getPointerId(event, 0);
|
|
gestureEvent.pointerId[1] = AMotionEvent_getPointerId(event, 1);
|
|
|
|
// Register touch points position
|
|
gestureEvent.position[0] = (Vector2){ AMotionEvent_getX(event, 0), AMotionEvent_getY(event, 0) };
|
|
gestureEvent.position[1] = (Vector2){ AMotionEvent_getX(event, 1), AMotionEvent_getY(event, 1) };
|
|
|
|
// Normalize gestureEvent.position[x] for screenWidth and screenHeight
|
|
gestureEvent.position[0].x /= (float)GetScreenWidth();
|
|
gestureEvent.position[0].y /= (float)GetScreenHeight();
|
|
|
|
gestureEvent.position[1].x /= (float)GetScreenWidth();
|
|
gestureEvent.position[1].y /= (float)GetScreenHeight();
|
|
|
|
// Gesture data is sent to gestures system for processing
|
|
ProcessGestureEvent(gestureEvent);
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if defined(PLATFORM_WEB)
|
|
// Register fullscreen change events
|
|
static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData)
|
|
{
|
|
//isFullscreen: int event->isFullscreen
|
|
//fullscreenEnabled: int event->fullscreenEnabled
|
|
//fs element nodeName: (char *) event->nodeName
|
|
//fs element id: (char *) event->id
|
|
//Current element size: (int) event->elementWidth, (int) event->elementHeight
|
|
//Screen size:(int) event->screenWidth, (int) event->screenHeight
|
|
/*
|
|
if (event->isFullscreen)
|
|
{
|
|
CORE.Window.fullscreen = true;
|
|
TRACELOG(LOG_INFO, "WEB: Canvas scaled to fullscreen. ElementSize: (%ix%i), ScreenSize(%ix%i)", event->elementWidth, event->elementHeight, event->screenWidth, event->screenHeight);
|
|
}
|
|
else
|
|
{
|
|
CORE.Window.fullscreen = false;
|
|
TRACELOG(LOG_INFO, "WEB: Canvas scaled to windowed. ElementSize: (%ix%i), ScreenSize(%ix%i)", event->elementWidth, event->elementHeight, event->screenWidth, event->screenHeight);
|
|
}
|
|
|
|
// TODO: Depending on scaling factor (screen vs element), calculate factor to scale mouse/touch input
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
// Register keyboard input events
|
|
static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData)
|
|
{
|
|
if ((eventType == EMSCRIPTEN_EVENT_KEYDOWN) && (keyEvent->keyCode == 27)) // ESCAPE key (strcmp(keyEvent->code, "Escape") == 0)
|
|
{
|
|
// WARNING: Not executed when pressing Esc to exit fullscreen, it seems document has priority over #canvas
|
|
|
|
emscripten_exit_pointerlock();
|
|
CORE.Window.fullscreen = false;
|
|
//TRACELOG(LOG_INFO, "CORE.Window.fullscreen = %s", CORE.Window.fullscreen? "true" : "false");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Register mouse input events
|
|
static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
|
|
{
|
|
// Lock mouse pointer when click on screen
|
|
if (eventType == EMSCRIPTEN_EVENT_CLICK)
|
|
{
|
|
EmscriptenPointerlockChangeEvent plce;
|
|
emscripten_get_pointerlock_status(&plce);
|
|
|
|
int result = emscripten_request_pointerlock("#canvas", 1); // TODO: It does not work!
|
|
|
|
// result -> EMSCRIPTEN_RESULT_DEFERRED
|
|
// The requested operation cannot be completed now for web security reasons,
|
|
// and has been deferred for completion in the next event handler. --> but it never happens!
|
|
|
|
//if (!plce.isActive) emscripten_request_pointerlock(0, 1);
|
|
//else emscripten_exit_pointerlock();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Register touch input events
|
|
static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData)
|
|
{
|
|
for (int i = 0; i < touchEvent->numTouches; i++)
|
|
{
|
|
if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) CORE.Input.Touch.currentTouchState[i] = 1;
|
|
else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) CORE.Input.Touch.currentTouchState[i] = 0;
|
|
}
|
|
|
|
#if defined(SUPPORT_GESTURES_SYSTEM)
|
|
GestureEvent gestureEvent = { 0 };
|
|
|
|
// Register touch actions
|
|
if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.touchAction = TOUCH_DOWN;
|
|
else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.touchAction = TOUCH_UP;
|
|
else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.touchAction = TOUCH_MOVE;
|
|
|
|
// Register touch points count
|
|
gestureEvent.pointCount = touchEvent->numTouches;
|
|
|
|
// Register touch points id
|
|
gestureEvent.pointerId[0] = touchEvent->touches[0].identifier;
|
|
gestureEvent.pointerId[1] = touchEvent->touches[1].identifier;
|
|
|
|
// Register touch points position
|
|
// NOTE: Only two points registered
|
|
gestureEvent.position[0] = (Vector2){ touchEvent->touches[0].targetX, touchEvent->touches[0].targetY };
|
|
gestureEvent.position[1] = (Vector2){ touchEvent->touches[1].targetX, touchEvent->touches[1].targetY };
|
|
|
|
double canvasWidth, canvasHeight;
|
|
// 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("#canvas", &canvasWidth, &canvasHeight);
|
|
|
|
// Normalize gestureEvent.position[x] for CORE.Window.screen.width and CORE.Window.screen.height
|
|
gestureEvent.position[0].x *= ((float)GetScreenWidth()/(float)canvasWidth);
|
|
gestureEvent.position[0].y *= ((float)GetScreenHeight()/(float)canvasHeight);
|
|
gestureEvent.position[1].x *= ((float)GetScreenWidth()/(float)canvasWidth);
|
|
gestureEvent.position[1].y *= ((float)GetScreenHeight()/(float)canvasHeight);
|
|
|
|
CORE.Input.Touch.position[0] = gestureEvent.position[0];
|
|
CORE.Input.Touch.position[1] = gestureEvent.position[1];
|
|
|
|
// Gesture data is sent to gestures system for processing
|
|
ProcessGestureEvent(gestureEvent);
|
|
#else
|
|
// Support only simple touch position
|
|
if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART)
|
|
{
|
|
// Get first touch position
|
|
CORE.Input.Touch.position[0] = (Vector2){ touchEvent->touches[0].targetX, touchEvent->touches[0].targetY };
|
|
|
|
double canvasWidth, canvasHeight;
|
|
//EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight);
|
|
emscripten_get_element_css_size("#canvas", &canvasWidth, &canvasHeight);
|
|
|
|
// Normalize gestureEvent.position[x] for screenWidth and screenHeight
|
|
CORE.Input.Touch.position[0].x *= ((float)GetScreenWidth()/(float)canvasWidth);
|
|
CORE.Input.Touch.position[0].y *= ((float)GetScreenHeight()/(float)canvasHeight);
|
|
}
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
// Register connected/disconnected 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;
|
|
else CORE.Input.Gamepad.ready[gamepadEvent->index] = false;
|
|
|
|
// TODO: Test gamepadEvent->index
|
|
|
|
return 0;
|
|
}
|
|
|
|
static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const void *reserved, void *userData)
|
|
{
|
|
double width, height;
|
|
emscripten_get_element_css_size("canvas", &width, &height);
|
|
|
|
// TODO.
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM)
|
|
|
|
#if defined(SUPPORT_SSH_KEYBOARD_RPI)
|
|
// Initialize Keyboard system (using standard input)
|
|
static void InitKeyboard(void)
|
|
{
|
|
// NOTE: We read directly from Standard Input (stdin) - STDIN_FILENO file descriptor
|
|
|
|
// Make stdin non-blocking (not enough, need to configure to non-canonical mode)
|
|
int flags = fcntl(STDIN_FILENO, F_GETFL, 0); // F_GETFL: Get the file access mode and the file status flags
|
|
fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); // F_SETFL: Set the file status flags to the value specified
|
|
|
|
// Save terminal keyboard settings and reconfigure terminal with new settings
|
|
struct termios keyboardNewSettings;
|
|
tcgetattr(STDIN_FILENO, &CORE.Input.Keyboard.defaultSettings); // Get current keyboard settings
|
|
keyboardNewSettings = CORE.Input.Keyboard.defaultSettings;
|
|
|
|
// 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);
|
|
keyboardNewSettings.c_cc[VMIN] = 1;
|
|
keyboardNewSettings.c_cc[VTIME] = 0;
|
|
|
|
// Set new keyboard settings (change occurs immediately)
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings);
|
|
|
|
// NOTE: Reading directly from stdin will give chars already key-mapped by kernel to ASCII or UNICODE
|
|
|
|
// Save old keyboard mode to restore it at the end
|
|
if (ioctl(STDIN_FILENO, KDGKBMODE, &CORE.Input.Keyboard.defaultMode) < 0)
|
|
{
|
|
// NOTE: It could mean we are using a remote keyboard through ssh!
|
|
TRACELOG(LOG_WARNING, "RPI: Failed to change keyboard mode (SSH keyboard?)");
|
|
}
|
|
else
|
|
{
|
|
// We reconfigure keyboard mode to get:
|
|
// - scancodes (K_RAW)
|
|
// - keycodes (K_MEDIUMRAW)
|
|
// - ASCII chars (K_XLATE)
|
|
// - UNICODE chars (K_UNICODE)
|
|
ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE);
|
|
}
|
|
|
|
// Register keyboard restore when program finishes
|
|
atexit(RestoreKeyboard);
|
|
}
|
|
|
|
// Process keyboard inputs
|
|
// TODO: Most probably input reading and processing should be in a separate thread
|
|
static void ProcessKeyboard(void)
|
|
{
|
|
#define MAX_KEYBUFFER_SIZE 32 // Max size in bytes to read
|
|
|
|
// Keyboard input polling (fill keys[256] array with status)
|
|
int bufferByteCount = 0; // Bytes available on the buffer
|
|
char keysBuffer[MAX_KEYBUFFER_SIZE]; // Max keys to be read at a time
|
|
|
|
// Read availables keycodes from stdin
|
|
bufferByteCount = read(STDIN_FILENO, keysBuffer, MAX_KEYBUFFER_SIZE); // POSIX system call
|
|
|
|
// Reset pressed keys array (it will be filled below)
|
|
if (bufferByteCount > 0) for (int i = 0; i < 512; i++) CORE.Input.Keyboard.currentKeyState[i] = 0;
|
|
|
|
// Check keys from event input workers (This is the new keyboard reading method)
|
|
//for (int i = 0; i < 512; i++) CORE.Input.Keyboard.currentKeyState[i] = CORE.Input.Keyboard.currentKeyStateEvdev[i];
|
|
|
|
// Fill all read bytes (looking for keys)
|
|
for (int i = 0; i < bufferByteCount; i++)
|
|
{
|
|
// NOTE: If (key == 0x1b), depending on next key, it could be a special keymap code!
|
|
// Up -> 1b 5b 41 / Left -> 1b 5b 44 / Right -> 1b 5b 43 / Down -> 1b 5b 42
|
|
if (keysBuffer[i] == 0x1b)
|
|
{
|
|
// Detect ESC to stop program
|
|
if (bufferByteCount == 1) CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] = 1;
|
|
else
|
|
{
|
|
if (keysBuffer[i + 1] == 0x5b) // Special function key
|
|
{
|
|
if ((keysBuffer[i + 2] == 0x5b) || (keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32))
|
|
{
|
|
// Process special function keys (F1 - F12)
|
|
switch (keysBuffer[i + 3])
|
|
{
|
|
case 0x41: CORE.Input.Keyboard.currentKeyState[290] = 1; break; // raylib KEY_F1
|
|
case 0x42: CORE.Input.Keyboard.currentKeyState[291] = 1; break; // raylib KEY_F2
|
|
case 0x43: CORE.Input.Keyboard.currentKeyState[292] = 1; break; // raylib KEY_F3
|
|
case 0x44: CORE.Input.Keyboard.currentKeyState[293] = 1; break; // raylib KEY_F4
|
|
case 0x45: CORE.Input.Keyboard.currentKeyState[294] = 1; break; // raylib KEY_F5
|
|
case 0x37: CORE.Input.Keyboard.currentKeyState[295] = 1; break; // raylib KEY_F6
|
|
case 0x38: CORE.Input.Keyboard.currentKeyState[296] = 1; break; // raylib KEY_F7
|
|
case 0x39: CORE.Input.Keyboard.currentKeyState[297] = 1; break; // raylib KEY_F8
|
|
case 0x30: CORE.Input.Keyboard.currentKeyState[298] = 1; break; // raylib KEY_F9
|
|
case 0x31: CORE.Input.Keyboard.currentKeyState[299] = 1; break; // raylib KEY_F10
|
|
case 0x33: CORE.Input.Keyboard.currentKeyState[300] = 1; break; // raylib KEY_F11
|
|
case 0x34: CORE.Input.Keyboard.currentKeyState[301] = 1; break; // raylib KEY_F12
|
|
default: break;
|
|
}
|
|
|
|
if (keysBuffer[i + 2] == 0x5b) i += 4;
|
|
else if ((keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) i += 5;
|
|
}
|
|
else
|
|
{
|
|
switch (keysBuffer[i + 2])
|
|
{
|
|
case 0x41: CORE.Input.Keyboard.currentKeyState[265] = 1; break; // raylib KEY_UP
|
|
case 0x42: CORE.Input.Keyboard.currentKeyState[264] = 1; break; // raylib KEY_DOWN
|
|
case 0x43: CORE.Input.Keyboard.currentKeyState[262] = 1; break; // raylib KEY_RIGHT
|
|
case 0x44: CORE.Input.Keyboard.currentKeyState[263] = 1; break; // raylib KEY_LEFT
|
|
default: break;
|
|
}
|
|
|
|
i += 3; // Jump to next key
|
|
}
|
|
|
|
// NOTE: Some keys are not directly keymapped (CTRL, ALT, SHIFT)
|
|
}
|
|
}
|
|
}
|
|
else if (keysBuffer[i] == 0x0a) // raylib KEY_ENTER (don't mix with <linux/input.h> KEY_*)
|
|
{
|
|
CORE.Input.Keyboard.currentKeyState[257] = 1;
|
|
|
|
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue
|
|
CORE.Input.Keyboard.keyPressedQueueCount++;
|
|
}
|
|
else if (keysBuffer[i] == 0x7f) // raylib KEY_BACKSPACE
|
|
{
|
|
CORE.Input.Keyboard.currentKeyState[259] = 1;
|
|
|
|
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue
|
|
CORE.Input.Keyboard.keyPressedQueueCount++;
|
|
}
|
|
else
|
|
{
|
|
// Translate lowercase a-z letters to A-Z
|
|
if ((keysBuffer[i] >= 97) && (keysBuffer[i] <= 122))
|
|
{
|
|
CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i] - 32] = 1;
|
|
}
|
|
else CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i]] = 1;
|
|
|
|
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keysBuffer[i]; // Add keys pressed into queue
|
|
CORE.Input.Keyboard.keyPressedQueueCount++;
|
|
}
|
|
}
|
|
|
|
// Check exit key (same functionality as GLFW3 KeyCallback())
|
|
if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true;
|
|
|
|
#if defined(SUPPORT_SCREEN_CAPTURE)
|
|
// Check screen capture key (raylib key: KEY_F12)
|
|
if (CORE.Input.Keyboard.currentKeyState[301] == 1)
|
|
{
|
|
TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter));
|
|
screenshotCounter++;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Restore default keyboard input
|
|
static void RestoreKeyboard(void)
|
|
{
|
|
// Reset to default keyboard settings
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &CORE.Input.Keyboard.defaultSettings);
|
|
|
|
// Reconfigure keyboard to default mode
|
|
ioctl(STDIN_FILENO, KDSKBMODE, CORE.Input.Keyboard.defaultMode);
|
|
}
|
|
#endif //SUPPORT_SSH_KEYBOARD_RPI
|
|
|
|
// Initialise user input from evdev(/dev/input/event<N>) this means mouse, keyboard or gamepad devices
|
|
static void InitEvdevInput(void)
|
|
{
|
|
char path[MAX_FILEPATH_LENGTH];
|
|
DIR *directory;
|
|
struct dirent *entity;
|
|
|
|
// Reset variables
|
|
for (int i = 0; i < MAX_TOUCH_POINTS; ++i)
|
|
{
|
|
CORE.Input.Touch.position[i].x = -1;
|
|
CORE.Input.Touch.position[i].y = -1;
|
|
}
|
|
|
|
// Reset keypress buffer
|
|
CORE.Input.Keyboard.lastKeyPressed.head = 0;
|
|
CORE.Input.Keyboard.lastKeyPressed.tail = 0;
|
|
|
|
// Reset keyboard key state
|
|
for (int i = 0; i < 512; i++) CORE.Input.Keyboard.currentKeyState[i] = 0;
|
|
|
|
// Open the linux directory of "/dev/input"
|
|
directory = opendir(DEFAULT_EVDEV_PATH);
|
|
|
|
if (directory)
|
|
{
|
|
while ((entity = readdir(directory)) != NULL)
|
|
{
|
|
if (strncmp("event", entity->d_name, strlen("event")) == 0) // Search for devices named "event*"
|
|
{
|
|
sprintf(path, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name);
|
|
EventThreadSpawn(path); // Identify the device and spawn a thread for it
|
|
}
|
|
}
|
|
|
|
closedir(directory);
|
|
}
|
|
else TRACELOG(LOG_WARNING, "RPI: Failed to open linux event directory: %s", DEFAULT_EVDEV_PATH);
|
|
}
|
|
|
|
// Identifies a input device and spawns a thread to handle it if needed
|
|
static void EventThreadSpawn(char *device)
|
|
{
|
|
#define BITS_PER_LONG (8*sizeof(long))
|
|
#define NBITS(x) ((((x) - 1)/BITS_PER_LONG) + 1)
|
|
#define OFF(x) ((x)%BITS_PER_LONG)
|
|
#define BIT(x) (1UL<<OFF(x))
|
|
#define LONG(x) ((x)/BITS_PER_LONG)
|
|
#define TEST_BIT(array, bit) ((array[LONG(bit)] >> OFF(bit)) & 1)
|
|
|
|
struct input_absinfo absinfo;
|
|
unsigned long evBits[NBITS(EV_MAX)];
|
|
unsigned long absBits[NBITS(ABS_MAX)];
|
|
unsigned long relBits[NBITS(REL_MAX)];
|
|
unsigned long keyBits[NBITS(KEY_MAX)];
|
|
bool hasAbs = false;
|
|
bool hasRel = false;
|
|
bool hasAbsMulti = false;
|
|
int freeWorkerId = -1;
|
|
int fd = -1;
|
|
|
|
InputEventWorker *worker;
|
|
|
|
// Open the device and allocate worker
|
|
//-------------------------------------------------------------------------------------------------------
|
|
// Find a free spot in the workers array
|
|
for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i)
|
|
{
|
|
if (CORE.Input.eventWorker[i].threadId == 0)
|
|
{
|
|
freeWorkerId = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Select the free worker from array
|
|
if (freeWorkerId >= 0)
|
|
{
|
|
worker = &(CORE.Input.eventWorker[freeWorkerId]); // Grab a pointer to the worker
|
|
memset(worker, 0, sizeof(InputEventWorker)); // Clear the worker
|
|
}
|
|
else
|
|
{
|
|
TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread for %s, out of worker slots", device);
|
|
return;
|
|
}
|
|
|
|
// Open the device
|
|
fd = open(device, O_RDONLY | O_NONBLOCK);
|
|
if (fd < 0)
|
|
{
|
|
TRACELOG(LOG_WARNING, "RPI: Failed to open input device %s", device);
|
|
return;
|
|
}
|
|
worker->fd = fd;
|
|
|
|
// Grab number on the end of the devices name "event<N>"
|
|
int devNum = 0;
|
|
char *ptrDevName = strrchr(device, 't');
|
|
worker->eventNum = -1;
|
|
|
|
if (ptrDevName != NULL)
|
|
{
|
|
if (sscanf(ptrDevName, "t%d", &devNum) == 1)
|
|
worker->eventNum = devNum;
|
|
}
|
|
|
|
// At this point we have a connection to the device, but we don't yet know what the device is.
|
|
// It could be many things, even as simple as a power button...
|
|
//-------------------------------------------------------------------------------------------------------
|
|
|
|
// Identify the device
|
|
//-------------------------------------------------------------------------------------------------------
|
|
ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits); // Read a bitfield of the avalable device properties
|
|
|
|
// Check for absolute input devices
|
|
if (TEST_BIT(evBits, EV_ABS))
|
|
{
|
|
ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits);
|
|
|
|
// Check for absolute movement support (usualy touchscreens, but also joysticks)
|
|
if (TEST_BIT(absBits, ABS_X) && TEST_BIT(absBits, ABS_Y))
|
|
{
|
|
hasAbs = true;
|
|
|
|
// Get the scaling values
|
|
ioctl(fd, EVIOCGABS(ABS_X), &absinfo);
|
|
worker->absRange.x = absinfo.minimum;
|
|
worker->absRange.width = absinfo.maximum - absinfo.minimum;
|
|
ioctl(fd, EVIOCGABS(ABS_Y), &absinfo);
|
|
worker->absRange.y = absinfo.minimum;
|
|
worker->absRange.height = absinfo.maximum - absinfo.minimum;
|
|
}
|
|
|
|
// Check for multiple absolute movement support (usualy multitouch touchscreens)
|
|
if (TEST_BIT(absBits, ABS_MT_POSITION_X) && TEST_BIT(absBits, ABS_MT_POSITION_Y))
|
|
{
|
|
hasAbsMulti = true;
|
|
|
|
// Get the scaling values
|
|
ioctl(fd, EVIOCGABS(ABS_X), &absinfo);
|
|
worker->absRange.x = absinfo.minimum;
|
|
worker->absRange.width = absinfo.maximum - absinfo.minimum;
|
|
ioctl(fd, EVIOCGABS(ABS_Y), &absinfo);
|
|
worker->absRange.y = absinfo.minimum;
|
|
worker->absRange.height = absinfo.maximum - absinfo.minimum;
|
|
}
|
|
}
|
|
|
|
// Check for relative movement support (usualy mouse)
|
|
if (TEST_BIT(evBits, EV_REL))
|
|
{
|
|
ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relBits)), relBits);
|
|
|
|
if (TEST_BIT(relBits, REL_X) && TEST_BIT(relBits, REL_Y)) hasRel = true;
|
|
}
|
|
|
|
// Check for button support to determine the device type(usualy on all input devices)
|
|
if (TEST_BIT(evBits, EV_KEY))
|
|
{
|
|
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits);
|
|
|
|
if (hasAbs || hasAbsMulti)
|
|
{
|
|
if (TEST_BIT(keyBits, BTN_TOUCH)) worker->isTouch = true; // This is a touchscreen
|
|
if (TEST_BIT(keyBits, BTN_TOOL_FINGER)) worker->isTouch = true; // This is a drawing tablet
|
|
if (TEST_BIT(keyBits, BTN_TOOL_PEN)) worker->isTouch = true; // This is a drawing tablet
|
|
if (TEST_BIT(keyBits, BTN_STYLUS)) worker->isTouch = true; // This is a drawing tablet
|
|
if (worker->isTouch || hasAbsMulti) worker->isMultitouch = true; // This is a multitouch capable device
|
|
}
|
|
|
|
if (hasRel)
|
|
{
|
|
if (TEST_BIT(keyBits, BTN_LEFT)) worker->isMouse = true; // This is a mouse
|
|
if (TEST_BIT(keyBits, BTN_RIGHT)) worker->isMouse = true; // This is a mouse
|
|
}
|
|
|
|
if (TEST_BIT(keyBits, BTN_A)) worker->isGamepad = true; // This is a gamepad
|
|
if (TEST_BIT(keyBits, BTN_TRIGGER)) worker->isGamepad = true; // This is a gamepad
|
|
if (TEST_BIT(keyBits, BTN_START)) worker->isGamepad = true; // This is a gamepad
|
|
if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad
|
|
if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad
|
|
|
|
if (TEST_BIT(keyBits, KEY_SPACE)) worker->isKeyboard = true; // This is a keyboard
|
|
}
|
|
//-------------------------------------------------------------------------------------------------------
|
|
|
|
// Decide what to do with the device
|
|
//-------------------------------------------------------------------------------------------------------
|
|
if (worker->isTouch || worker->isMouse || worker->isKeyboard)
|
|
{
|
|
// Looks like an interesting device
|
|
TRACELOG(LOG_INFO, "RPI: Opening input device: %s (%s%s%s%s%s)", device,
|
|
worker->isMouse? "mouse " : "",
|
|
worker->isMultitouch? "multitouch " : "",
|
|
worker->isTouch? "touchscreen " : "",
|
|
worker->isGamepad? "gamepad " : "",
|
|
worker->isKeyboard? "keyboard " : "");
|
|
|
|
// Create a thread for this device
|
|
int error = pthread_create(&worker->threadId, NULL, &EventThread, (void *)worker);
|
|
if (error != 0)
|
|
{
|
|
TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread: %s (error: %d)", device, error);
|
|
worker->threadId = 0;
|
|
close(fd);
|
|
}
|
|
|
|
#if defined(USE_LAST_TOUCH_DEVICE)
|
|
// Find touchscreen with the highest index
|
|
int maxTouchNumber = -1;
|
|
|
|
for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i)
|
|
{
|
|
if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum > maxTouchNumber)) maxTouchNumber = CORE.Input.eventWorker[i].eventNum;
|
|
}
|
|
|
|
// Find toucnscreens with lower indexes
|
|
for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i)
|
|
{
|
|
if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum < maxTouchNumber))
|
|
{
|
|
if (CORE.Input.eventWorker[i].threadId != 0)
|
|
{
|
|
TRACELOG(LOG_WARNING, "RPI: Found duplicate touchscreen, killing touchscreen on event: %d", i);
|
|
pthread_cancel(CORE.Input.eventWorker[i].threadId);
|
|
close(CORE.Input.eventWorker[i].fd);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
else close(fd); // We are not interested in this device
|
|
//-------------------------------------------------------------------------------------------------------
|
|
}
|
|
|
|
// Input device events reading thread
|
|
static void *EventThread(void *arg)
|
|
{
|
|
// Scancode to keycode mapping for US keyboards
|
|
// TODO: Probably replace this with a keymap from the X11 to get the correct regional map for the keyboard:
|
|
// Currently non US keyboards will have the wrong mapping for some keys
|
|
static const int keymap_US[] =
|
|
{ 0,256,49,50,51,52,53,54,55,56,57,48,45,61,259,258,81,87,69,82,84,
|
|
89,85,73,79,80,91,93,257,341,65,83,68,70,71,72,74,75,76,59,39,96,
|
|
340,92,90,88,67,86,66,78,77,44,46,47,344,332,342,32,280,290,291,
|
|
292,293,294,295,296,297,298,299,282,281,327,328,329,333,324,325,
|
|
326,334,321,322,323,320,330,0,85,86,300,301,89,90,91,92,93,94,95,
|
|
335,345,331,283,346,101,268,265,266,263,262,269,264,267,260,261,
|
|
112,113,114,115,116,117,118,119,120,121,122,123,124,125,347,127,
|
|
128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
|
|
144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
|
|
160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
|
|
176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
|
|
192,193,194,0,0,0,0,0,200,201,202,203,204,205,206,207,208,209,210,
|
|
211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,
|
|
227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,
|
|
243,244,245,246,247,248,0,0,0,0,0,0,0, };
|
|
|
|
struct input_event event;
|
|
InputEventWorker *worker = (InputEventWorker *)arg;
|
|
|
|
int touchAction = -1;
|
|
bool gestureUpdate = false;
|
|
int keycode;
|
|
|
|
while (!CORE.Window.shouldClose)
|
|
{
|
|
// Try to read data from the device and only continue if successful
|
|
while (read(worker->fd, &event, sizeof(event)) == (int)sizeof(event))
|
|
{
|
|
// Relative movement parsing
|
|
if (event.type == EV_REL)
|
|
{
|
|
if (event.code == REL_X)
|
|
{
|
|
CORE.Input.Mouse.position.x += event.value;
|
|
CORE.Input.Touch.position[0].x = CORE.Input.Mouse.position.x;
|
|
|
|
#if defined(SUPPORT_GESTURES_SYSTEM)
|
|
touchAction = TOUCH_MOVE;
|
|
gestureUpdate = true;
|
|
#endif
|
|
}
|
|
|
|
if (event.code == REL_Y)
|
|
{
|
|
CORE.Input.Mouse.position.y += event.value;
|
|
CORE.Input.Touch.position[0].y = CORE.Input.Mouse.position.y;
|
|
|
|
#if defined(SUPPORT_GESTURES_SYSTEM)
|
|
touchAction = TOUCH_MOVE;
|
|
gestureUpdate = true;
|
|
#endif
|
|
}
|
|
|
|
if (event.code == REL_WHEEL) CORE.Input.Mouse.currentWheelMove += event.value;
|
|
}
|
|
|
|
// Absolute movement parsing
|
|
if (event.type == EV_ABS)
|
|
{
|
|
// Basic movement
|
|
if (event.code == ABS_X)
|
|
{
|
|
CORE.Input.Mouse.position.x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale acording to absRange
|
|
|
|
#if defined(SUPPORT_GESTURES_SYSTEM)
|
|
touchAction = TOUCH_MOVE;
|
|
gestureUpdate = true;
|
|
#endif
|
|
}
|
|
|
|
if (event.code == ABS_Y)
|
|
{
|
|
CORE.Input.Mouse.position.y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale acording to absRange
|
|
|
|
#if defined(SUPPORT_GESTURES_SYSTEM)
|
|
touchAction = TOUCH_MOVE;
|
|
gestureUpdate = true;
|
|
#endif
|
|
}
|
|
|
|
// Multitouch movement
|
|
if (event.code == ABS_MT_SLOT) worker->touchSlot = event.value; // Remeber the slot number for the folowing events
|
|
|
|
if (event.code == ABS_MT_POSITION_X)
|
|
{
|
|
if (worker->touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[worker->touchSlot].x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale acording to absRange
|
|
}
|
|
|
|
if (event.code == ABS_MT_POSITION_Y)
|
|
{
|
|
if (worker->touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[worker->touchSlot].y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale acording to absRange
|
|
}
|
|
|
|
if (event.code == ABS_MT_TRACKING_ID)
|
|
{
|
|
if ((event.value < 0) && (worker->touchSlot < MAX_TOUCH_POINTS))
|
|
{
|
|
// Touch has ended for this point
|
|
CORE.Input.Touch.position[worker->touchSlot].x = -1;
|
|
CORE.Input.Touch.position[worker->touchSlot].y = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Button parsing
|
|
if (event.type == EV_KEY)
|
|
{
|
|
// Mouse button parsing
|
|
if ((event.code == BTN_TOUCH) || (event.code == BTN_LEFT))
|
|
{
|
|
CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_LEFT_BUTTON] = event.value;
|
|
|
|
#if defined(SUPPORT_GESTURES_SYSTEM)
|
|
if (event.value > 0) touchAction = TOUCH_DOWN;
|
|
else touchAction = TOUCH_UP;
|
|
gestureUpdate = true;
|
|
#endif
|
|
}
|
|
|
|
if (event.code == BTN_RIGHT) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_RIGHT_BUTTON] = event.value;
|
|
if (event.code == BTN_MIDDLE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_MIDDLE_BUTTON] = event.value;
|
|
|
|
// Keyboard button parsing
|
|
if ((event.code >= 1) && (event.code <= 255)) //Keyboard keys appear for codes 1 to 255
|
|
{
|
|
keycode = keymap_US[event.code & 0xFF]; // The code we get is a scancode so we look up the apropriate keycode
|
|
|
|
// Make sure we got a valid keycode
|
|
if ((keycode > 0) && (keycode < sizeof(CORE.Input.Keyboard.currentKeyState)))
|
|
{
|
|
// 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
|
|
CORE.Input.Keyboard.currentKeyState[keycode] = (event.value >= 1)? 1 : 0;
|
|
if (event.value >= 1)
|
|
{
|
|
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; // Register last key pressed
|
|
CORE.Input.Keyboard.keyPressedQueueCount++;
|
|
}
|
|
|
|
#if defined(SUPPORT_SCREEN_CAPTURE)
|
|
// Check screen capture key (raylib key: KEY_F12)
|
|
if (CORE.Input.Keyboard.currentKeyState[301] == 1)
|
|
{
|
|
TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter));
|
|
screenshotCounter++;
|
|
}
|
|
#endif
|
|
|
|
if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true;
|
|
|
|
TRACELOGD("RPI: KEY_%s ScanCode: %4i KeyCode: %4i", event.value == 0 ? "UP":"DOWN", event.code, keycode);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Screen confinement
|
|
if (!CORE.Input.Mouse.cursorHidden)
|
|
{
|
|
if (CORE.Input.Mouse.position.x < 0) CORE.Input.Mouse.position.x = 0;
|
|
if (CORE.Input.Mouse.position.x > CORE.Window.screen.width/CORE.Input.Mouse.scale.x) CORE.Input.Mouse.position.x = CORE.Window.screen.width/CORE.Input.Mouse.scale.x;
|
|
|
|
if (CORE.Input.Mouse.position.y < 0) CORE.Input.Mouse.position.y = 0;
|
|
if (CORE.Input.Mouse.position.y > CORE.Window.screen.height/CORE.Input.Mouse.scale.y) CORE.Input.Mouse.position.y = CORE.Window.screen.height/CORE.Input.Mouse.scale.y;
|
|
}
|
|
|
|
// Gesture update
|
|
if (gestureUpdate)
|
|
{
|
|
#if defined(SUPPORT_GESTURES_SYSTEM)
|
|
GestureEvent gestureEvent = { 0 };
|
|
|
|
gestureEvent.pointCount = 0;
|
|
gestureEvent.touchAction = touchAction;
|
|
|
|
if (CORE.Input.Touch.position[0].x >= 0) gestureEvent.pointCount++;
|
|
if (CORE.Input.Touch.position[1].x >= 0) gestureEvent.pointCount++;
|
|
if (CORE.Input.Touch.position[2].x >= 0) gestureEvent.pointCount++;
|
|
if (CORE.Input.Touch.position[3].x >= 0) gestureEvent.pointCount++;
|
|
|
|
gestureEvent.pointerId[0] = 0;
|
|
gestureEvent.pointerId[1] = 1;
|
|
gestureEvent.pointerId[2] = 2;
|
|
gestureEvent.pointerId[3] = 3;
|
|
|
|
gestureEvent.position[0] = CORE.Input.Touch.position[0];
|
|
gestureEvent.position[1] = CORE.Input.Touch.position[1];
|
|
gestureEvent.position[2] = CORE.Input.Touch.position[2];
|
|
gestureEvent.position[3] = CORE.Input.Touch.position[3];
|
|
|
|
ProcessGestureEvent(gestureEvent);
|
|
#endif
|
|
}
|
|
}
|
|
Wait(5); // Sleep for 5ms to avoid hogging CPU time
|
|
}
|
|
|
|
close(worker->fd);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// Init gamepad system
|
|
static void InitGamepad(void)
|
|
{
|
|
char gamepadDev[128] = "";
|
|
|
|
for (int i = 0; i < MAX_GAMEPADS; i++)
|
|
{
|
|
sprintf(gamepadDev, "%s%i", DEFAULT_GAMEPAD_DEV, i);
|
|
|
|
if ((CORE.Input.Gamepad.streamId[i] = open(gamepadDev, O_RDONLY|O_NONBLOCK)) < 0)
|
|
{
|
|
// NOTE: Only show message for first gamepad
|
|
if (i == 0) TRACELOG(LOG_WARNING, "RPI: Failed to open Gamepad device, no gamepad available");
|
|
}
|
|
else
|
|
{
|
|
CORE.Input.Gamepad.ready[i] = true;
|
|
|
|
// NOTE: Only create one thread
|
|
if (i == 0)
|
|
{
|
|
int error = pthread_create(&CORE.Input.Gamepad.threadId, NULL, &GamepadThread, NULL);
|
|
|
|
if (error != 0) TRACELOG(LOG_WARNING, "RPI: Failed to create gamepad input event thread");
|
|
else TRACELOG(LOG_INFO, "RPI: Gamepad device initialized successfully");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process Gamepad (/dev/input/js0)
|
|
static void *GamepadThread(void *arg)
|
|
{
|
|
#define JS_EVENT_BUTTON 0x01 // Button pressed/released
|
|
#define JS_EVENT_AXIS 0x02 // Joystick axis moved
|
|
#define JS_EVENT_INIT 0x80 // Initial state of device
|
|
|
|
struct js_event {
|
|
unsigned int time; // event timestamp in milliseconds
|
|
short value; // event value
|
|
unsigned char type; // event type
|
|
unsigned char number; // event axis/button number
|
|
};
|
|
|
|
// Read gamepad event
|
|
struct js_event gamepadEvent;
|
|
|
|
while (!CORE.Window.shouldClose)
|
|
{
|
|
for (int i = 0; i < MAX_GAMEPADS; i++)
|
|
{
|
|
if (read(CORE.Input.Gamepad.streamId[i], &gamepadEvent, sizeof(struct js_event)) == (int)sizeof(struct js_event))
|
|
{
|
|
gamepadEvent.type &= ~JS_EVENT_INIT; // Ignore synthetic events
|
|
|
|
// Process gamepad events by type
|
|
if (gamepadEvent.type == JS_EVENT_BUTTON)
|
|
{
|
|
TRACELOGD("RPI: Gamepad button: %i, value: %i", gamepadEvent.number, gamepadEvent.value);
|
|
|
|
if (gamepadEvent.number < MAX_GAMEPAD_BUTTONS)
|
|
{
|
|
// 1 - button pressed, 0 - button released
|
|
CORE.Input.Gamepad.currentState[i][gamepadEvent.number] = (int)gamepadEvent.value;
|
|
|
|
if ((int)gamepadEvent.value == 1) CORE.Input.Gamepad.lastButtonPressed = gamepadEvent.number;
|
|
else CORE.Input.Gamepad.lastButtonPressed = -1;
|
|
}
|
|
}
|
|
else if (gamepadEvent.type == JS_EVENT_AXIS)
|
|
{
|
|
TRACELOGD("RPI: Gamepad axis: %i, value: %i", gamepadEvent.number, gamepadEvent.value);
|
|
|
|
if (gamepadEvent.number < MAX_GAMEPAD_AXIS)
|
|
{
|
|
// NOTE: Scaling of gamepadEvent.value to get values between -1..1
|
|
CORE.Input.Gamepad.axisState[i][gamepadEvent.number] = (float)gamepadEvent.value/32768;
|
|
}
|
|
}
|
|
}
|
|
else Wait(1); // Sleep for 1 ms to avoid hogging CPU time
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
#endif // PLATFORM_RPI || PLATFORM_DRM
|
|
|
|
#if defined(PLATFORM_UWP)
|
|
// UWP function pointers
|
|
// NOTE: Those pointers are set by UWP App
|
|
static UWPQueryTimeFunc uwpQueryTimeFunc = NULL;
|
|
static UWPSleepFunc uwpSleepFunc = NULL;
|
|
static UWPDisplaySizeFunc uwpDisplaySizeFunc = NULL;
|
|
static UWPMouseFunc uwpMouseLockFunc = NULL;
|
|
static UWPMouseFunc uwpMouseUnlockFunc = NULL;
|
|
static UWPMouseFunc uwpMouseShowFunc = NULL;
|
|
static UWPMouseFunc uwpMouseHideFunc = NULL;
|
|
static UWPMouseSetPosFunc uwpMouseSetPosFunc = NULL;
|
|
static void *uwpCoreWindow = NULL;
|
|
|
|
// Check all required UWP function pointers have been set
|
|
bool UWPIsConfigured()
|
|
{
|
|
bool pass = true;
|
|
|
|
if (uwpQueryTimeFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetQueryTimeFunc() must be called with a valid function before InitWindow()"); pass = false; }
|
|
if (uwpSleepFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetSleepFunc() must be called with a valid function before InitWindow()"); pass = false; }
|
|
if (uwpDisplaySizeFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetDisplaySizeFunc() must be called with a valid function before InitWindow()"); pass = false; }
|
|
if (uwpMouseLockFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetMouseLockFunc() must be called with a valid function before InitWindow()"); pass = false; }
|
|
if (uwpMouseUnlockFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetMouseUnlockFunc() must be called with a valid function before InitWindow()"); pass = false; }
|
|
if (uwpMouseShowFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetMouseShowFunc() must be called with a valid function before InitWindow()"); pass = false; }
|
|
if (uwpMouseHideFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetMouseHideFunc() must be called with a valid function before InitWindow()"); pass = false; }
|
|
if (uwpMouseSetPosFunc == NULL) { TRACELOG(LOG_ERROR, "UWP: UWPSetMouseSetPosFunc() must be called with a valid function before InitWindow()"); pass = false; }
|
|
if (uwpCoreWindow == NULL) { TRACELOG(LOG_ERROR, "UWP: A pointer to the UWP core window must be set before InitWindow()"); pass = false; }
|
|
|
|
return pass;
|
|
}
|
|
|
|
// UWP function handlers get/set
|
|
void UWPSetDataPath(const char* path) { CORE.UWP.internalDataPath = path; }
|
|
UWPQueryTimeFunc UWPGetQueryTimeFunc(void) { return uwpQueryTimeFunc; }
|
|
void UWPSetQueryTimeFunc(UWPQueryTimeFunc func) { uwpQueryTimeFunc = func; }
|
|
UWPSleepFunc UWPGetSleepFunc(void) { return uwpSleepFunc; }
|
|
void UWPSetSleepFunc(UWPSleepFunc func) { uwpSleepFunc = func; }
|
|
UWPDisplaySizeFunc UWPGetDisplaySizeFunc(void) { return uwpDisplaySizeFunc; }
|
|
void UWPSetDisplaySizeFunc(UWPDisplaySizeFunc func) { uwpDisplaySizeFunc = func; }
|
|
UWPMouseFunc UWPGetMouseLockFunc() { return uwpMouseLockFunc; }
|
|
void UWPSetMouseLockFunc(UWPMouseFunc func) { uwpMouseLockFunc = func; }
|
|
UWPMouseFunc UWPGetMouseUnlockFunc() { return uwpMouseUnlockFunc; }
|
|
void UWPSetMouseUnlockFunc(UWPMouseFunc func) { uwpMouseUnlockFunc = func; }
|
|
UWPMouseFunc UWPGetMouseShowFunc() { return uwpMouseShowFunc; }
|
|
void UWPSetMouseShowFunc(UWPMouseFunc func) { uwpMouseShowFunc = func; }
|
|
UWPMouseFunc UWPGetMouseHideFunc() { return uwpMouseHideFunc; }
|
|
void UWPSetMouseHideFunc(UWPMouseFunc func) { uwpMouseHideFunc = func; }
|
|
UWPMouseSetPosFunc UWPGetMouseSetPosFunc() { return uwpMouseSetPosFunc; }
|
|
void UWPSetMouseSetPosFunc(UWPMouseSetPosFunc func) { uwpMouseSetPosFunc = func; }
|
|
|
|
void *UWPGetCoreWindowPtr() { return uwpCoreWindow; }
|
|
void UWPSetCoreWindowPtr(void* ptr) { uwpCoreWindow = ptr; }
|
|
void UWPMouseWheelEvent(int deltaY) { CORE.Input.Mouse.currentWheelMove = (float)deltaY; }
|
|
|
|
void UWPKeyDownEvent(int key, bool down, bool controlKey)
|
|
{
|
|
if (key == CORE.Input.Keyboard.exitKey && down)
|
|
{
|
|
// Time to close the window.
|
|
CORE.Window.shouldClose = true;
|
|
}
|
|
else if (key == KEY_F12 && down)
|
|
{
|
|
#if defined(SUPPORT_GIF_RECORDING)
|
|
if (controlKey)
|
|
{
|
|
if (gifRecording)
|
|
{
|
|
gifRecording = false;
|
|
|
|
MsfGifResult result = msf_gif_end(&gifState);
|
|
|
|
SaveFileData(TextFormat("%s/screenrec%03i.gif", CORE.UWP.internalDataPath, screenshotCounter), result.data, result.dataSize);
|
|
msf_gif_free(result);
|
|
|
|
#if defined(PLATFORM_WEB)
|
|
// Download file from MEMFS (emscripten memory filesystem)
|
|
// saveFileFromMEMFSToDisk() function is defined in raylib/templates/web_shel/shell.html
|
|
emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", TextFormat("screenrec%03i.gif", screenshotCounter - 1), TextFormat("screenrec%03i.gif", screenshotCounter - 1)));
|
|
#endif
|
|
TRACELOG(LOG_INFO, "SYSTEM: Finish animated GIF recording");
|
|
}
|
|
else
|
|
{
|
|
gifRecording = true;
|
|
gifFramesCounter = 0;
|
|
|
|
msf_gif_begin(&gifState, CORE.Window.screen.width, CORE.Window.screen.height);
|
|
screenshotCounter++;
|
|
|
|
TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter));
|
|
}
|
|
}
|
|
else
|
|
#endif // SUPPORT_GIF_RECORDING
|
|
#if defined(SUPPORT_SCREEN_CAPTURE)
|
|
{
|
|
TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter));
|
|
screenshotCounter++;
|
|
}
|
|
#endif // SUPPORT_SCREEN_CAPTURE
|
|
}
|
|
else
|
|
{
|
|
CORE.Input.Keyboard.currentKeyState[key] = down;
|
|
}
|
|
}
|
|
|
|
void UWPKeyCharEvent(int key)
|
|
{
|
|
if (CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE)
|
|
{
|
|
// Add character to the queue
|
|
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key;
|
|
CORE.Input.Keyboard.keyPressedQueueCount++;
|
|
}
|
|
}
|
|
|
|
void UWPMouseButtonEvent(int button, bool down)
|
|
{
|
|
CORE.Input.Mouse.currentButtonState[button] = down;
|
|
|
|
#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[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) gestureEvent.touchAction = TOUCH_DOWN;
|
|
else if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) gestureEvent.touchAction = TOUCH_UP;
|
|
|
|
// NOTE: TOUCH_MOVE event is registered in MouseCursorPosCallback()
|
|
|
|
// Assign a pointer ID
|
|
gestureEvent.pointerId[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
|
|
ProcessGestureEvent(gestureEvent);
|
|
#endif
|
|
}
|
|
|
|
void UWPMousePosEvent(double x, double y)
|
|
{
|
|
CORE.Input.Mouse.position.x = (float)x;
|
|
CORE.Input.Mouse.position.y = (float)y;
|
|
CORE.Input.Touch.position[0] = CORE.Input.Mouse.position;
|
|
|
|
#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_MOVE;
|
|
|
|
// Assign a pointer ID
|
|
gestureEvent.pointerId[0] = 0;
|
|
|
|
// Register touch points count
|
|
gestureEvent.pointCount = 1;
|
|
|
|
// Register touch points position, only one point registered
|
|
gestureEvent.position[0] = CORE.Input.Mouse.position;
|
|
|
|
// 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
|
|
}
|
|
|
|
void UWPResizeEvent(int width, int height)
|
|
{
|
|
SetupViewport(width, height); // Reset viewport and projection matrix for new size
|
|
|
|
// Set current screen size
|
|
CORE.Window.screen.width = width;
|
|
CORE.Window.screen.height = height;
|
|
CORE.Window.currentFbo.width = width;
|
|
CORE.Window.currentFbo.height = height;
|
|
|
|
// NOTE: Postprocessing texture is not scaled to new size
|
|
|
|
CORE.Window.resizedLastFrame = true;
|
|
}
|
|
|
|
void UWPActivateGamepadEvent(int gamepad, bool active)
|
|
{
|
|
if (gamepad < MAX_GAMEPADS) CORE.Input.Gamepad.ready[gamepad] = active;
|
|
}
|
|
|
|
void UWPRegisterGamepadButton(int gamepad, int button, bool down)
|
|
{
|
|
if (gamepad < MAX_GAMEPADS)
|
|
{
|
|
if (button < MAX_GAMEPAD_BUTTONS)
|
|
{
|
|
CORE.Input.Gamepad.currentState[gamepad][button] = down;
|
|
CORE.Input.Gamepad.lastButtonPressed = button;
|
|
}
|
|
}
|
|
}
|
|
|
|
void UWPRegisterGamepadAxis(int gamepad, int axis, float value)
|
|
{
|
|
if (gamepad < MAX_GAMEPADS)
|
|
{
|
|
if (axis < MAX_GAMEPAD_AXIS) CORE.Input.Gamepad.axisState[gamepad][axis] = value;
|
|
}
|
|
}
|
|
|
|
void UWPGestureMove(int pointer, float x, float y)
|
|
{
|
|
#if defined(SUPPORT_GESTURES_SYSTEM)
|
|
GestureEvent gestureEvent = { 0 };
|
|
|
|
// Assign the pointer ID and touch action
|
|
gestureEvent.pointerId[0] = pointer;
|
|
gestureEvent.touchAction = TOUCH_MOVE;
|
|
|
|
// Register touch points count
|
|
gestureEvent.pointCount = 1;
|
|
|
|
// Register touch points position, only one point registered
|
|
gestureEvent.position[0].x = x;
|
|
gestureEvent.position[0].y = y;
|
|
|
|
// 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
|
|
}
|
|
|
|
void UWPGestureTouch(int pointer, float x, float y, bool touch)
|
|
{
|
|
#if defined(SUPPORT_GESTURES_SYSTEM)
|
|
GestureEvent gestureEvent = { 0 };
|
|
|
|
// Assign the pointer ID and touch action
|
|
gestureEvent.pointerId[0] = pointer;
|
|
gestureEvent.touchAction = touch ? TOUCH_DOWN : TOUCH_UP;
|
|
|
|
// Register touch points count
|
|
gestureEvent.pointCount = 1;
|
|
|
|
// Register touch points position, only one point registered
|
|
gestureEvent.position[0].x = x;
|
|
gestureEvent.position[0].y = y;
|
|
|
|
// 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
|
|
}
|
|
|
|
#endif // PLATFORM_UWP
|
|
|
|
#if defined(PLATFORM_DRM)
|
|
// Search matching DRM mode in connector's mode list
|
|
static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode)
|
|
{
|
|
if (NULL == connector) return -1;
|
|
if (NULL == mode) return -1;
|
|
|
|
// safe bitwise comparison of two modes
|
|
#define BINCMP(a, b) memcmp((a), (b), (sizeof(a) < sizeof(b)) ? sizeof(a) : sizeof(b))
|
|
|
|
for (size_t i = 0; i < connector->count_modes; i++)
|
|
{
|
|
TRACELOG(LOG_TRACE, "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");
|
|
|
|
if (0 == BINCMP(&CORE.Window.crtc->mode, &CORE.Window.connector->modes[i]))
|
|
{
|
|
TRACELOG(LOG_TRACE, "above mode selected");
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
|
|
#undef BINCMP
|
|
}
|
|
|
|
// Search exactly matching DRM connector mode in connector's list
|
|
static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced)
|
|
{
|
|
TRACELOG(LOG_TRACE, "searching exact connector mode for %ux%u@%u, selecting an interlaced mode is allowed: %s", width, height, fps, allowInterlaced ? "yes" : "no");
|
|
|
|
if (NULL == connector) return -1;
|
|
|
|
for (int i = 0; i < CORE.Window.connector->count_modes; i++)
|
|
{
|
|
const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i];
|
|
|
|
TRACELOG(LOG_TRACE, "mode %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive");
|
|
|
|
if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced))
|
|
{
|
|
TRACELOG(LOG_TRACE, "but shouldn't choose an interlaced mode");
|
|
continue;
|
|
}
|
|
|
|
if ((mode->hdisplay == width) && (mode->vdisplay == height) && (mode->vrefresh == fps))
|
|
{
|
|
TRACELOG(LOG_TRACE, "mode selected");
|
|
return i;
|
|
}
|
|
}
|
|
|
|
TRACELOG(LOG_TRACE, "no exact matching mode found");
|
|
return -1;
|
|
}
|
|
|
|
// Search the nearest matching DRM connector mode in connector's list
|
|
static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced)
|
|
{
|
|
TRACELOG(LOG_TRACE, "searching nearest connector mode for %ux%u@%u, selecting an interlaced mode is allowed: %s", width, height, fps, allowInterlaced ? "yes" : "no");
|
|
|
|
if (NULL == connector) return -1;
|
|
|
|
int nearestIndex = -1;
|
|
for (int i = 0; i < CORE.Window.connector->count_modes; i++)
|
|
{
|
|
const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i];
|
|
|
|
TRACELOG(LOG_TRACE, "mode %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh,
|
|
(mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive");
|
|
|
|
if ((mode->hdisplay < width) || (mode->vdisplay < height) | (mode->vrefresh < fps))
|
|
{
|
|
TRACELOG(LOG_TRACE, "mode is too small");
|
|
continue;
|
|
}
|
|
|
|
if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced))
|
|
{
|
|
TRACELOG(LOG_TRACE, "shouldn't choose an interlaced mode");
|
|
continue;
|
|
}
|
|
|
|
if ((mode->hdisplay >= width) && (mode->vdisplay >= height) && (mode->vrefresh >= fps))
|
|
{
|
|
const int widthDiff = mode->hdisplay - width;
|
|
const int heightDiff = mode->vdisplay - height;
|
|
const int fpsDiff = mode->vrefresh - fps;
|
|
|
|
if (nearestIndex < 0)
|
|
{
|
|
TRACELOG(LOG_TRACE, "first suitable mode");
|
|
nearestIndex = i;
|
|
continue;
|
|
}
|
|
|
|
const int nearestWidthDiff = CORE.Window.connector->modes[nearestIndex].hdisplay - width;
|
|
const int nearestHeightDiff = CORE.Window.connector->modes[nearestIndex].vdisplay - height;
|
|
const int nearestFpsDiff = CORE.Window.connector->modes[nearestIndex].vrefresh - fps;
|
|
|
|
if ((widthDiff < nearestWidthDiff) || (heightDiff < nearestHeightDiff) || (fpsDiff < nearestFpsDiff))
|
|
{
|
|
TRACELOG(LOG_TRACE, "mode is nearer than the previous one");
|
|
nearestIndex = i;
|
|
}
|
|
else
|
|
{
|
|
TRACELOG(LOG_TRACE, "mode is not nearer");
|
|
}
|
|
}
|
|
}
|
|
|
|
TRACELOG(LOG_TRACE, "returning nearest mode: %d", nearestIndex);
|
|
|
|
return nearestIndex;
|
|
}
|
|
#endif
|