mirror of
https://github.com/raysan5/raylib.git
synced 2025-12-25 10:22:33 -05:00
2195 lines
85 KiB
C
2195 lines
85 KiB
C
/**********************************************************************************************
|
|
*
|
|
* rcore_desktop_win32 - Functions to manage window, graphics device and inputs
|
|
*
|
|
* PLATFORM: DESKTOP: WIN32
|
|
* - Windows (Win32, Win64)
|
|
*
|
|
* LIMITATIONS:
|
|
* - Initial development stage, lot of functionality missing
|
|
* - No support for MOUSE_BUTTON_FORWARD/MOUSE_BUTTON_BACK
|
|
*
|
|
* POSSIBLE IMPROVEMENTS:
|
|
* - Improvement 01
|
|
* - Improvement 02
|
|
*
|
|
* ADDITIONAL NOTES:
|
|
* - TRACELOG() function is located in raylib [utils] module
|
|
*
|
|
* CONFIGURATION:
|
|
* #define RCORE_PLATFORM_CUSTOM_FLAG
|
|
* Custom flag for rcore on target platform -not used-
|
|
*
|
|
* DEPENDENCIES:
|
|
* - Win32 API (windows.h)
|
|
*
|
|
*
|
|
* LICENSE: zlib/libpng
|
|
*
|
|
* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) and contributors
|
|
*
|
|
* This software is provided "as-is", without any express or implied warranty. In no event
|
|
* will the authors be held liable for any damages arising from the use of this software.
|
|
*
|
|
* Permission is granted to anyone to use this software for any purpose, including commercial
|
|
* applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
|
*
|
|
* 1. The origin of this software must not be misrepresented; you must not claim that you
|
|
* wrote the original software. If you use this software in a product, an acknowledgment
|
|
* in the product documentation would be appreciated but is not required.
|
|
*
|
|
* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
|
|
* as being the original software.
|
|
*
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
|
*
|
|
**********************************************************************************************/
|
|
|
|
// Move windows.h symbols to new names to avoid redefining the same names as raylib
|
|
#define CloseWindow CloseWindowWin32
|
|
#define Rectangle RectangleWin32
|
|
#define ShowCursor ShowCursorWin32
|
|
#define DrawTextA DrawTextAWin32
|
|
#define DrawTextW DrawTextWin32
|
|
#define DrawTextExA DrawTextExAWin32
|
|
#define DrawTextExW DrawTextExWin32
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
|
|
#undef CloseWindow // raylib symbol collision
|
|
#undef Rectangle // raylib symbol collision
|
|
#undef ShowCursor // raylib symbol collision
|
|
#undef LoadImage // raylib symbol collision
|
|
#undef DrawText // raylib symbol collision
|
|
#undef DrawTextA
|
|
#undef DrawTextW
|
|
#undef DrawTextEx // raylib symbol collision
|
|
#undef DrawTextExA
|
|
#undef DrawTextExW
|
|
|
|
#include <windowsx.h>
|
|
#include <shellscalingapi.h>
|
|
#include <versionhelpers.h>
|
|
|
|
#include <malloc.h> // Required for alloca()
|
|
|
|
#if !defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
|
|
#include <GL/gl.h>
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Types and Structures Definition
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// NOTE: appScreenSize is the last screen size requested by the app,
|
|
// the backend must keep the client area this size (after DPI scaling is applied)
|
|
// when the window isn't fullscreen/maximized/minimized
|
|
typedef struct {
|
|
HWND hwnd; // Window handler
|
|
HDC hdc; // Graphic context handler
|
|
HGLRC glContext; // OpenGL context handler
|
|
|
|
// Software renderer variables
|
|
HDC hdcmem; // Memory graphic context handler
|
|
HBITMAP hbitmap; // GDI bitmap handler
|
|
unsigned int *pixels; // Pointer to pixel data buffer (BGRA format)
|
|
|
|
unsigned int appScreenWidth;
|
|
unsigned int appScreenHeight;
|
|
unsigned int desiredFlags;
|
|
|
|
LARGE_INTEGER timerFrequency;
|
|
} PlatformData;
|
|
|
|
// Define WGL function pointer types (no wglext.h needed)
|
|
typedef HGLRC (WINAPI *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int *);
|
|
typedef BOOL (WINAPI *PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC, const int *, const FLOAT *, UINT, int *, UINT *);
|
|
typedef BOOL (WINAPI *PFNWGLSWAPINTERVALEXTPROC)(int);
|
|
typedef const char *(WINAPI *PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC hdc);
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Global Variables Definition
|
|
//----------------------------------------------------------------------------------
|
|
extern CoreData CORE; // Global CORE state context
|
|
|
|
static PlatformData platform = { 0 }; // Platform specific data
|
|
|
|
// Required WGL functions
|
|
static PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL;
|
|
static PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = NULL;
|
|
static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL;
|
|
static PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = NULL;
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// This part of the file contains pure functions that never access global state
|
|
// This distinction helps keep the backend maintainable as the inputs and outputs
|
|
// of every function called in this section can be fully derived from the call-site alone
|
|
// --------------------------------------------------------------------------------
|
|
|
|
// Prevent any code in this part of the file from accessing the global CORE state
|
|
#define CORE DONT_USE_CORE_HERE
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Defines and Macros
|
|
//----------------------------------------------------------------------------------
|
|
#define A_TO_W_ALLOCA(outWstr, inAnsi) do { \
|
|
size_t outLen = AToWLen(inAnsi); \
|
|
outWstr = (WCHAR *)alloca(sizeof(WCHAR)*(outLen + 1)); \
|
|
AToWCopy(inAnsi, outWstr, outLen); \
|
|
outWstr[outLen] = 0; \
|
|
} while (0)
|
|
|
|
#define STYLE_MASK_ALL 0xffffffff
|
|
#define STYLE_MASK_READONLY (WS_MINIMIZE | WS_MAXIMIZE)
|
|
#define STYLE_MASK_WRITABLE (~STYLE_MASK_READONLY)
|
|
|
|
#define STYLE_FLAGS_RESIZABLE WS_THICKFRAME
|
|
|
|
#define STYLE_FLAGS_UNDECORATED_OFF (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)
|
|
#define STYLE_FLAGS_UNDECORATED_ON WS_POPUP
|
|
|
|
#define WINDOW_STYLE_EX 0
|
|
|
|
#define CLASS_NAME L"raylibWindow"
|
|
|
|
#define FLAG_MASK_OPTIONAL (FLAG_VSYNC_HINT)
|
|
#define FLAG_MASK_REQUIRED ~(FLAG_MASK_OPTIONAL)
|
|
|
|
// Flags that have no operations to perform during an update
|
|
#define FLAG_MASK_NO_UPDATE (FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT)
|
|
|
|
#define WM_APP_UPDATE_WINDOW_SIZE (WM_APP + 1)
|
|
|
|
#define WGL_DRAW_TO_WINDOW_ARB 0x2001
|
|
#define WGL_ACCELERATION_ARB 0x2003
|
|
#define WGL_SUPPORT_OPENGL_ARB 0x2010
|
|
#define WGL_DOUBLE_BUFFER_ARB 0x2011
|
|
#define WGL_PIXEL_TYPE_ARB 0x2013
|
|
#define WGL_COLOR_BITS_ARB 0x2014
|
|
#define WGL_RED_BITS_ARB 0x2015
|
|
#define WGL_RED_SHIFT_ARB 0x2016
|
|
#define WGL_GREEN_BITS_ARB 0x2017
|
|
#define WGL_GREEN_SHIFT_ARB 0x2018
|
|
#define WGL_BLUE_BITS_ARB 0x2019
|
|
#define WGL_BLUE_SHIFT_ARB 0x201a
|
|
#define WGL_ALPHA_BITS_ARB 0x201b
|
|
#define WGL_ALPHA_SHIFT_ARB 0x201c
|
|
#define WGL_DEPTH_BITS_ARB 0x2022
|
|
#define WGL_STENCIL_BITS_ARB 0x2023
|
|
#define WGL_TYPE_RGBA_ARB 0x202b
|
|
|
|
// Context acceleration types
|
|
#define WGL_NO_ACCELERATION_ARB 0x2025 // OpenGL 1.1 GDI software rasterizer
|
|
#define WGL_GENERIC_ACCELERATION_ARB 0x2026
|
|
#define WGL_FULL_ACCELERATION_ARB 0x2027 // OpenGL hardware-accelerated, using GPU-drivers provided by vendor
|
|
|
|
// WGL_ARB_multisample extension supported
|
|
#define WGL_SAMPLE_BUFFERS_ARB 0x2041 // Multisampling: 1 if multisample buffers are supported
|
|
#define WGL_SAMPLES_ARB 0x2042 // Multisampling: Number of samples per pixel (4, 8, 16)
|
|
|
|
// WGL_ARB_framebuffer_sRGB extension supported
|
|
#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9 // GL_TRUE if the framebuffer can do sRGB conversion
|
|
|
|
#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000
|
|
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
|
|
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
|
|
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
|
|
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
|
|
#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002
|
|
#define WGL_CONTEXT_ES_PROFILE_BIT_EXT 0x00000004
|
|
#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Types and Structures Definition
|
|
//----------------------------------------------------------------------------------
|
|
// Maximize-minimize request types
|
|
typedef enum {
|
|
MIZED_NONE,
|
|
MIZED_MIN,
|
|
MIZED_MAX
|
|
} Mized;
|
|
|
|
// Flag operations
|
|
// NOTE: Some ops need to be deferred
|
|
typedef struct {
|
|
DWORD set;
|
|
DWORD clear;
|
|
} FlagsOp;
|
|
|
|
// Monitor info type
|
|
typedef struct {
|
|
HMONITOR needle;
|
|
int index;
|
|
int matchIndex;
|
|
RECT rect;
|
|
} MonitorInfo;
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Module Internal Functions Declaration
|
|
//----------------------------------------------------------------------------------
|
|
// Get ASCII to WCHAR length
|
|
static size_t AToWLen(const char *ascii)
|
|
{
|
|
int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, ascii, -1, NULL, 0);
|
|
|
|
if (sizeNeeded < 0) TRACELOG(LOG_ERROR, "WIN32: Failed to calculate wide length [ERROR: %u]", GetLastError());
|
|
|
|
return sizeNeeded;
|
|
}
|
|
|
|
// Copy ASCII to WCHAR string
|
|
static void AToWCopy(const char *ascii, wchar_t *outPtr, size_t outLen)
|
|
{
|
|
int size = MultiByteToWideChar(CP_UTF8, 0, ascii, -1, outPtr, (int)outLen);
|
|
if (size != outLen) TRACELOG(LOG_WARNING, "WIN32: Failed to convert %i UTF-8 chars to WCHAR, converted %i chars", outLen, size);
|
|
}
|
|
|
|
static bool DecoratedFromStyle(DWORD style)
|
|
{
|
|
if (style & STYLE_FLAGS_UNDECORATED_ON)
|
|
{
|
|
if (style & STYLE_FLAGS_UNDECORATED_OFF) TRACELOG(LOG_ERROR, "WIN32: FLAGS: Style 0x%x has both undecorated on/off flags", style);
|
|
return false; // Not decorated
|
|
}
|
|
|
|
DWORD masked = (style & STYLE_FLAGS_UNDECORATED_OFF);
|
|
if (STYLE_FLAGS_UNDECORATED_OFF != masked) TRACELOG(LOG_ERROR, "WIN32: FLAGS: Style 0x%x is missing flags 0x%x", masked, masked ^ STYLE_FLAGS_UNDECORATED_OFF);
|
|
|
|
return true; // Decorated
|
|
}
|
|
|
|
// Get window style from required flags
|
|
static DWORD MakeWindowStyle(unsigned flags)
|
|
{
|
|
// Flag is not needed because there are no child windows,
|
|
// but supposedly it improves efficiency, plus, windows adds this
|
|
// flag automatically anyway so it keeps flags in sync with the OS
|
|
DWORD style = WS_CLIPSIBLINGS;
|
|
|
|
style |= (flags & FLAG_WINDOW_HIDDEN)? 0 : WS_VISIBLE;
|
|
style |= (flags & FLAG_WINDOW_RESIZABLE)? STYLE_FLAGS_RESIZABLE : 0;
|
|
style |= (flags & FLAG_WINDOW_UNDECORATED)? STYLE_FLAGS_UNDECORATED_ON : STYLE_FLAGS_UNDECORATED_OFF;
|
|
|
|
// Minimized takes precedence over maximized
|
|
int mized = MIZED_NONE;
|
|
if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED)) mized = MIZED_MIN;
|
|
if (flags & FLAG_WINDOW_MAXIMIZED) mized = MIZED_MAX;
|
|
|
|
switch (mized)
|
|
{
|
|
case MIZED_NONE: break;
|
|
case MIZED_MIN: style |= WS_MINIMIZE; break;
|
|
case MIZED_MAX: style |= WS_MAXIMIZE; break;
|
|
default: break;
|
|
}
|
|
|
|
return style;
|
|
}
|
|
|
|
// Check flags state, enforces that the actual window/platform state is in sync with raylib's flags
|
|
static void CheckFlags(const char *context, HWND hwnd, DWORD flags, DWORD expectedStyle, DWORD styleCheckMask)
|
|
{
|
|
DWORD styleFromFlags = MakeWindowStyle(flags);
|
|
if ((styleFromFlags & styleCheckMask) != (expectedStyle & styleCheckMask))
|
|
{
|
|
TRACELOG(LOG_ERROR, "WIN32: FLAGS: %s: window flags (0x%x) produced style 0x%x which != expected 0x%x (diff=0x%x, mask=0x%x)",
|
|
context, flags, styleFromFlags & styleCheckMask, expectedStyle & styleCheckMask,
|
|
(styleFromFlags & styleCheckMask) ^ (expectedStyle & styleCheckMask), styleCheckMask);
|
|
}
|
|
|
|
SetLastError(0);
|
|
LONG actualStyle = (LONG)GetWindowLongPtrW(hwnd, GWL_STYLE);
|
|
if ((actualStyle & styleCheckMask) != (expectedStyle & styleCheckMask))
|
|
{
|
|
TRACELOG(LOG_ERROR, "WIN32: FLAGS: %s: expected style 0x%x but got 0x%x (diff=0x%x, mask=0x%x, lasterror=%lu)",
|
|
context, expectedStyle & styleCheckMask, actualStyle & styleCheckMask,
|
|
(expectedStyle & styleCheckMask) ^ (actualStyle & styleCheckMask),
|
|
styleCheckMask, GetLastError());
|
|
}
|
|
|
|
if (styleCheckMask & WS_MINIMIZE)
|
|
{
|
|
bool isIconic = IsIconic(hwnd);
|
|
bool styleMinimized = !!(WS_MINIMIZE & actualStyle);
|
|
if (isIconic != styleMinimized) TRACELOG(LOG_ERROR, "WIN32: FLAGS: IsIconic(%d) != WS_MINIMIZED(%d)", isIconic, styleMinimized);
|
|
}
|
|
|
|
if (styleCheckMask & WS_MAXIMIZE)
|
|
{
|
|
WINDOWPLACEMENT placement;
|
|
placement.length = sizeof(placement);
|
|
if (!GetWindowPlacement(hwnd, &placement))
|
|
{
|
|
TRACELOG(LOG_ERROR, "WIN32: FLAGS: %s failed, error=%lu", "GetWindowPlacement", GetLastError());
|
|
}
|
|
bool placementMaximized = (placement.showCmd == SW_SHOWMAXIMIZED);
|
|
bool styleMaximized = WS_MAXIMIZE & actualStyle;
|
|
if (placementMaximized != styleMaximized)
|
|
{
|
|
TRACELOG(LOG_ERROR, "WIN32: FLAGS: Maximized state desync, placement maximized=%d (showCmd=%lu) style maximized=%d",
|
|
placementMaximized, placement.showCmd, styleMaximized);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate window size (with borders, title-bar...) from desired client size (framebuffer size)
|
|
static SIZE CalcWindowSize(UINT dpi, SIZE clientSize, DWORD style)
|
|
{
|
|
RECT rect = { 0, 0, clientSize.cx, clientSize.cy };
|
|
|
|
int result = AdjustWindowRectExForDpi(&rect, style, 0, WINDOW_STYLE_EX, dpi);
|
|
if (result == 0) TRACELOG(LOG_ERROR, "WIN32: Failed to adjust window rect [ERROR: %lu]", GetLastError());
|
|
|
|
return (SIZE){ rect.right - rect.left, rect.bottom - rect.top };
|
|
}
|
|
|
|
// Update window size if required
|
|
// NOTE: Returns true if the window size was updated, false otherwise
|
|
static bool UpdateWindowSize(int mode, HWND hwnd, int width, int height, unsigned flags)
|
|
{
|
|
if (flags & FLAG_WINDOW_MINIMIZED) return false;
|
|
|
|
if (flags & FLAG_WINDOW_MAXIMIZED)
|
|
{
|
|
CheckFlags("UpdateWindowSize(maximized)", hwnd, flags, MakeWindowStyle(flags), STYLE_MASK_ALL);
|
|
return false;
|
|
}
|
|
|
|
if (flags & FLAG_BORDERLESS_WINDOWED_MODE)
|
|
{
|
|
MONITORINFO info = { 0 };
|
|
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
|
|
info.cbSize = sizeof(info);
|
|
if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "WIN32: Failed to get monitor info [ERROR: %lu]", GetLastError());
|
|
|
|
RECT windowRect = { 0 };
|
|
if (!GetWindowRect(hwnd, &windowRect)) TRACELOG(LOG_ERROR, "WIN32: Failed to get window rect [ERROR: %lu]", GetLastError());
|
|
|
|
if ((windowRect.left == info.rcMonitor.left) &&
|
|
(windowRect.top == info.rcMonitor.top) &&
|
|
((windowRect.right - windowRect.left) == (info.rcMonitor.right - info.rcMonitor.left)) &&
|
|
((windowRect.bottom - windowRect.top) == (info.rcMonitor.bottom - info.rcMonitor.top))) return false;
|
|
|
|
if (!SetWindowPos(hwnd, HWND_TOP,
|
|
info.rcMonitor.left, info.rcMonitor.top,
|
|
info.rcMonitor.right - info.rcMonitor.left,
|
|
info.rcMonitor.bottom - info.rcMonitor.top,
|
|
SWP_NOOWNERZORDER))
|
|
{
|
|
TRACELOG(LOG_ERROR, "WIN32: Failed to set window position [ERROR: %lu]", GetLastError());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Get size in pixels from points, considering high-dpi
|
|
UINT dpi = GetDpiForWindow(hwnd);
|
|
float dpiScale = ((float)dpi)/96.0f;
|
|
bool dpiScaling = flags & FLAG_WINDOW_HIGHDPI;
|
|
SIZE desiredSize = {
|
|
.cx = dpiScaling? (int)((float)width*dpiScale) : width,
|
|
.cy = dpiScaling? (int)((float)height*dpiScale) : height
|
|
};
|
|
|
|
// Get client size (framebuffer inside the window)
|
|
RECT rect = { 0 };
|
|
GetClientRect(hwnd, &rect);
|
|
SIZE clientSize = { rect.right, rect.bottom };
|
|
|
|
// If client size is alread desired size, no need to update
|
|
if ((clientSize.cx == desiredSize.cx) || (clientSize.cy == desiredSize.cy)) return false;
|
|
|
|
TRACELOG(LOG_INFO, "WIN32: Restoring client size from [%dx%d] to [%dx%d] (dpi:%lu dpiScaling:%d app:%ix%i)",
|
|
clientSize.cx, clientSize.cy, desiredSize.cx, desiredSize.cy, dpi, dpiScaling, width, height);
|
|
|
|
// Calculate window size from desired framebuffer size and window flags
|
|
SIZE windowSize = CalcWindowSize(dpi, desiredSize, MakeWindowStyle(flags));
|
|
POINT windowPos = { 0 };
|
|
UINT swpFlags = SWP_NOZORDER | SWP_FRAMECHANGED;
|
|
|
|
if (mode == 0) // UPDATE_WINDOW_FIRST
|
|
{
|
|
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
|
|
if (!monitor) TRACELOG(LOG_ERROR, "WIN32: Failed to get monitor from window [ERROR: %lu]", GetLastError());
|
|
|
|
MONITORINFO info = { 0 };
|
|
info.cbSize = sizeof(info);
|
|
if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "WIN32: Failed to get monitor info [ERROR: %lu]", GetLastError());
|
|
|
|
#define MAX(a,b) (((a)>(b))? (a):(b))
|
|
|
|
LONG monitorWidth = info.rcMonitor.right - info.rcMonitor.left;
|
|
LONG monitorHeight = info.rcMonitor.bottom - info.rcMonitor.top;
|
|
windowPos = (POINT){
|
|
MAX(0, (monitorWidth - windowSize.cx)/2),
|
|
MAX(0, (monitorHeight - windowSize.cy)/2),
|
|
};
|
|
}
|
|
else swpFlags |= SWP_NOMOVE;
|
|
|
|
// WARNING: This code must be called after swInit() has been called, after InitPlatform() in [rcore]
|
|
//RECT rc = {0, 0, desired.cx, desired.cy};
|
|
//AdjustWindowRectEx(&rc, WS_OVERLAPPEDWINDOW, FALSE, 0);
|
|
//SetWindowPos(hwnd, NULL, windowPos.x, windowPos.y, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Verify if we are running in Windows 10 version 1703 (Creators Update)
|
|
static BOOL IsWindows10Version1703OrGreaterWin32(void)
|
|
{
|
|
HMODULE ntdll = LoadLibraryW(L"ntdll.dll");
|
|
|
|
DWORD (*Verify)(RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG) =
|
|
(DWORD (*)(RTL_OSVERSIONINFOEXW*, ULONG, ULONGLONG))GetProcAddress(ntdll, "RtlVerifyVersionInfo");
|
|
if (!Verify)
|
|
{
|
|
TRACELOG(LOG_ERROR, "WIN32: Failed to verify Windows version [ERROR: %lu]", GetLastError());
|
|
return 0;
|
|
}
|
|
|
|
RTL_OSVERSIONINFOEXW osvi = { 0 };
|
|
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
|
|
osvi.dwMajorVersion = 10;
|
|
osvi.dwMinorVersion = 0;
|
|
osvi.dwBuildNumber = 15063; // Build 15063 corresponds to Windows 10 version 1703 (Creators Update)
|
|
|
|
DWORDLONG cond = 0;
|
|
VER_SET_CONDITION(cond, VER_MAJORVERSION, VER_GREATER_EQUAL);
|
|
VER_SET_CONDITION(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
|
|
VER_SET_CONDITION(cond, VER_BUILDNUMBER, VER_GREATER_EQUAL);
|
|
|
|
return 0 == (*Verify)(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER, cond);
|
|
}
|
|
|
|
// Get OpenGL function pointers
|
|
static void *WglGetProcAddress(const char *procname)
|
|
{
|
|
void *proc = (void *)wglGetProcAddress(procname);
|
|
|
|
if ((proc == NULL) ||
|
|
// NOTE: Some GPU drivers could return following
|
|
// invalid sentinel values instead of NULL
|
|
(proc == (void *)0x1) ||
|
|
(proc == (void *)0x2) ||
|
|
(proc == (void *)0x3) ||
|
|
(proc == (void *)-1))
|
|
{
|
|
// TODO: Keep gl module pointer as global platform data?
|
|
HMODULE glModule = LoadLibraryW(L"opengl32.dll");
|
|
proc = (void *)GetProcAddress(glModule, procname);
|
|
|
|
//if (proc == NULL) TRACELOG(LOG_ERROR, "GL: GetProcAddress() failed to get %s [%p], error=%u", procname, proc, GetLastError());
|
|
//else TRACELOG(LOG_INFO, "GL: Found entry point for %s [%p]", procname, proc);
|
|
}
|
|
|
|
return proc;
|
|
}
|
|
|
|
// Get key from wparam (mapping)
|
|
static KeyboardKey GetKeyFromWparam(WPARAM wparam)
|
|
{
|
|
switch (wparam)
|
|
{
|
|
/* case VK_LBUTTON: return KEY_; */
|
|
/* case VK_RBUTTON: return KEY_; */
|
|
/* case VK_CANCEL: return KEY_; */
|
|
/* case VK_MBUTTON: return KEY_; */
|
|
/* case VK_XBUTTON1: return KEY_; */
|
|
/* case VK_XBUTTON2: return KEY_; */
|
|
/* case VK_BACK: return KEY_; */
|
|
/* case VK_TAB: return KEY_; */
|
|
/* case VK_CLEAR: return KEY_; */
|
|
case VK_RETURN: return KEY_ENTER;
|
|
/* case VK_SHIFT: return KEY_; */
|
|
/* case VK_CONTROL: return KEY_; */
|
|
/* case VK_MENU: return KEY_; */
|
|
/* case VK_PAUSE: return KEY_; */
|
|
/* case VK_CAPITAL: return KEY_; */
|
|
/* case VK_KANA: return KEY_; */
|
|
/* case VK_HANGUL: return KEY_; */
|
|
/* case VK_IME_ON: return KEY_; */
|
|
/* case VK_JUNJA: return KEY_; */
|
|
/* case VK_FINAL: return KEY_; */
|
|
/* case VK_HANJA: return KEY_; */
|
|
/* case VK_KANJI: return KEY_; */
|
|
/* case VK_IME_OFF: return KEY_; */
|
|
case VK_ESCAPE: return KEY_ESCAPE;
|
|
/* case VK_CONVERT: return KEY_; */
|
|
/* case VK_NONCONVERT: return KEY_; */
|
|
/* case VK_ACCEPT: return KEY_; */
|
|
/* case VK_MODECHANGE: return KEY_; */
|
|
case VK_SPACE: return KEY_SPACE;
|
|
/* case VK_PRIOR: return KEY_; */
|
|
/* case VK_NEXT: return KEY_; */
|
|
/* case VK_END: return KEY_; */
|
|
/* case VK_HOME: return KEY_; */
|
|
case VK_LEFT: return KEY_LEFT;
|
|
case VK_UP: return KEY_UP;
|
|
case VK_RIGHT: return KEY_RIGHT;
|
|
case VK_DOWN: return KEY_DOWN;
|
|
/* case VK_SELECT: return KEY_; */
|
|
/* case VK_PRINT: return KEY_; */
|
|
/* case VK_EXECUTE: return KEY_; */
|
|
/* case VK_SNAPSHOT: return KEY_; */
|
|
/* case VK_INSERT: return KEY_; */
|
|
/* case VK_DELETE: return KEY_; */
|
|
/* case VK_HELP: return KEY_; */
|
|
case '0': return KEY_ZERO;
|
|
case '1': return KEY_ONE;
|
|
case '2': return KEY_TWO;
|
|
case '3': return KEY_THREE;
|
|
case '4': return KEY_FOUR;
|
|
case '5': return KEY_FIVE;
|
|
case '6': return KEY_SIX;
|
|
case '7': return KEY_SEVEN;
|
|
case '8': return KEY_EIGHT;
|
|
case '9': return KEY_NINE;
|
|
/* case 0x3A-40: return KEY_; */
|
|
case 'A': return KEY_A;
|
|
case 'B': return KEY_B;
|
|
case 'C': return KEY_C;
|
|
case 'D': return KEY_D;
|
|
case 'E': return KEY_E;
|
|
case 'F': return KEY_F;
|
|
case 'G': return KEY_G;
|
|
case 'H': return KEY_H;
|
|
case 'I': return KEY_I;
|
|
case 'J': return KEY_J;
|
|
case 'K': return KEY_K;
|
|
case 'L': return KEY_L;
|
|
case 'M': return KEY_M;
|
|
case 'N': return KEY_N;
|
|
case 'O': return KEY_O;
|
|
case 'P': return KEY_P;
|
|
case 'Q': return KEY_Q;
|
|
case 'R': return KEY_R;
|
|
case 'S': return KEY_S;
|
|
case 'T': return KEY_T;
|
|
case 'U': return KEY_U;
|
|
case 'V': return KEY_V;
|
|
case 'W': return KEY_W;
|
|
case 'X': return KEY_X;
|
|
case 'Y': return KEY_Y;
|
|
case 'Z': return KEY_Z;
|
|
/* case VK_LWIN: return KEY_; */
|
|
/* case VK_RWIN: return KEY_; */
|
|
/* case VK_APPS: return KEY_; */
|
|
/* case VK_SLEEP: return KEY_; */
|
|
/* case VK_NUMPAD0: return KEY_; */
|
|
/* case VK_NUMPAD1: return KEY_; */
|
|
/* case VK_NUMPAD2: return KEY_; */
|
|
/* case VK_NUMPAD3: return KEY_; */
|
|
/* case VK_NUMPAD4: return KEY_; */
|
|
/* case VK_NUMPAD5: return KEY_; */
|
|
/* case VK_NUMPAD6: return KEY_; */
|
|
/* case VK_NUMPAD7: return KEY_; */
|
|
/* case VK_NUMPAD8: return KEY_; */
|
|
/* case VK_NUMPAD9: return KEY_; */
|
|
/* case VK_MULTIPLY: return KEY_; */
|
|
/* case VK_ADD: return KEY_; */
|
|
/* case VK_SEPARATOR: return KEY_; */
|
|
/* case VK_SUBTRACT: return KEY_; */
|
|
/* case VK_DECIMAL: return KEY_; */
|
|
/* case VK_DIVIDE: return KEY_; */
|
|
/* case VK_F1: return KEY_; */
|
|
/* case VK_F2: return KEY_; */
|
|
/* case VK_F3: return KEY_; */
|
|
/* case VK_F4: return KEY_; */
|
|
/* case VK_F5: return KEY_; */
|
|
/* case VK_F6: return KEY_; */
|
|
/* case VK_F7: return KEY_; */
|
|
/* case VK_F8: return KEY_; */
|
|
/* case VK_F9: return KEY_; */
|
|
/* case VK_F10: return KEY_; */
|
|
/* case VK_F11: return KEY_; */
|
|
/* case VK_F12: return KEY_; */
|
|
/* case VK_F13: return KEY_; */
|
|
/* case VK_F14: return KEY_; */
|
|
/* case VK_F15: return KEY_; */
|
|
/* case VK_F16: return KEY_; */
|
|
/* case VK_F17: return KEY_; */
|
|
/* case VK_F18: return KEY_; */
|
|
/* case VK_F19: return KEY_; */
|
|
/* case VK_F20: return KEY_; */
|
|
/* case VK_F21: return KEY_; */
|
|
/* case VK_F22: return KEY_; */
|
|
/* case VK_F23: return KEY_; */
|
|
/* case VK_F24: return KEY_; */
|
|
/* case VK_NUMLOCK: return KEY_; */
|
|
/* case VK_SCROLL: return KEY_; */
|
|
/* case VK_LSHIFT: return KEY_; */
|
|
/* case VK_RSHIFT: return KEY_; */
|
|
/* case VK_LCONTROL: return KEY_; */
|
|
/* case VK_RCONTROL: return KEY_; */
|
|
/* case VK_LMENU: return KEY_; */
|
|
/* case VK_RMENU: return KEY_; */
|
|
/* case VK_BROWSER_BACK: return KEY_; */
|
|
/* case VK_BROWSER_FORWARD: return KEY_; */
|
|
/* case VK_BROWSER_REFRESH: return KEY_; */
|
|
/* case VK_BROWSER_STOP: return KEY_; */
|
|
/* case VK_BROWSER_SEARCH: return KEY_; */
|
|
/* case VK_BROWSER_FAVORITES: return KEY_; */
|
|
/* case VK_BROWSER_HOME: return KEY_; */
|
|
/* case VK_VOLUME_MUTE: return KEY_; */
|
|
/* case VK_VOLUME_DOWN: return KEY_; */
|
|
/* case VK_VOLUME_UP: return KEY_; */
|
|
/* case VK_MEDIA_NEXT_TRACK: return KEY_; */
|
|
/* case VK_MEDIA_PREV_TRACK: return KEY_; */
|
|
/* case VK_MEDIA_STOP: return KEY_; */
|
|
/* case VK_MEDIA_PLAY_PAUSE: return KEY_; */
|
|
/* case VK_LAUNCH_MAIL: return KEY_; */
|
|
/* case VK_LAUNCH_MEDIA_SELECT: return KEY_; */
|
|
/* case VK_LAUNCH_APP1: return KEY_; */
|
|
/* case VK_LAUNCH_APP2: return KEY_; */
|
|
/* case VK_OEM_1: return KEY_; */
|
|
/* case VK_OEM_PLUS: return KEY_; */
|
|
/* case VK_OEM_COMMA: return KEY_; */
|
|
/* case VK_OEM_MINUS: return KEY_; */
|
|
/* case VK_OEM_PERIOD: return KEY_; */
|
|
/* case VK_OEM_2: return KEY_; */
|
|
/* case VK_OEM_3: return KEY_; */
|
|
/* case VK_OEM_4: return KEY_; */
|
|
/* case VK_OEM_5: return KEY_; */
|
|
/* case VK_OEM_6: return KEY_; */
|
|
/* case VK_OEM_7: return KEY_; */
|
|
/* case VK_OEM_8: return KEY_; */
|
|
/* case VK_OEM_102: return KEY_; */
|
|
/* case VK_PROCESSKEY: return KEY_; */
|
|
/* case VK_PACKET: return KEY_; */
|
|
/* case VK_ATTN: return KEY_; */
|
|
/* case VK_CRSEL: return KEY_; */
|
|
/* case VK_EXSEL: return KEY_; */
|
|
/* case VK_EREOF: return KEY_; */
|
|
/* case VK_PLAY: return KEY_; */
|
|
/* case VK_ZOOM: return KEY_; */
|
|
/* case VK_NONAME: return KEY_; */
|
|
/* case VK_PA1: return KEY_; */
|
|
/* case VK_OEM_CLEAR: return KEY_; */
|
|
default: return KEY_NULL;
|
|
}
|
|
}
|
|
|
|
// Get cursor name
|
|
static LPCWSTR GetCursorName(int cursor)
|
|
{
|
|
LPCWSTR name = (LPCWSTR)IDC_ARROW;
|
|
|
|
switch (cursor)
|
|
{
|
|
case MOUSE_CURSOR_DEFAULT: name = (LPCWSTR)IDC_ARROW; break;
|
|
case MOUSE_CURSOR_ARROW: name = (LPCWSTR)IDC_ARROW; break;
|
|
case MOUSE_CURSOR_IBEAM: name = (LPCWSTR)IDC_IBEAM; break;
|
|
case MOUSE_CURSOR_CROSSHAIR: name = (LPCWSTR)IDC_CROSS; break;
|
|
case MOUSE_CURSOR_POINTING_HAND: name = (LPCWSTR)IDC_HAND; break;
|
|
case MOUSE_CURSOR_RESIZE_EW: name = (LPCWSTR)IDC_SIZEWE; break;
|
|
case MOUSE_CURSOR_RESIZE_NS: name = (LPCWSTR)IDC_SIZENS; break;
|
|
case MOUSE_CURSOR_RESIZE_NWSE: name = (LPCWSTR)IDC_SIZENWSE; break;
|
|
case MOUSE_CURSOR_RESIZE_NESW: name = (LPCWSTR)IDC_SIZENESW; break;
|
|
case MOUSE_CURSOR_RESIZE_ALL: name = (LPCWSTR)IDC_SIZEALL; break;
|
|
case MOUSE_CURSOR_NOT_ALLOWED: name = (LPCWSTR)IDC_NO; break;
|
|
default: break;
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
// Count monitors process
|
|
// NOTE: Required by GetMonitorCount()
|
|
static BOOL CALLBACK CountMonitorsProc(HMONITOR handle, HDC hdc, LPRECT rect, LPARAM lparam)
|
|
{
|
|
int *count = (int *)lparam;
|
|
*count += 1;
|
|
|
|
// Always return TRUE to continue the loop, otherwise, the caller
|
|
// can't distinguish between stopping the loop and an error
|
|
return TRUE;
|
|
}
|
|
|
|
// Find monitor process
|
|
// NOTE: Required by GetCurrentMonitor()
|
|
static BOOL CALLBACK FindMonitorProc(HMONITOR handle, HDC hdc, LPRECT rect, LPARAM lparam)
|
|
{
|
|
MonitorInfo *monitor = (MonitorInfo *)lparam;
|
|
|
|
if (handle == monitor->needle)
|
|
{
|
|
monitor->matchIndex = monitor->index;
|
|
monitor->rect = *rect;
|
|
}
|
|
|
|
monitor->index += 1;
|
|
|
|
// Always return TRUE to continue the loop, otherwise, the caller
|
|
// can't distinguish between stopping the loop and an error
|
|
return TRUE;
|
|
}
|
|
|
|
// Get style changed required operations flags
|
|
// NOTE: Required for deferred operations
|
|
static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *style, FlagsOp *deferredFlags)
|
|
{
|
|
// Check window resizable flag change
|
|
bool resizable = (coreWindowFlags & FLAG_WINDOW_RESIZABLE);
|
|
bool resizableOld = ((style->styleOld & STYLE_FLAGS_RESIZABLE) != 0);
|
|
bool resizableNew = ((style->styleNew & STYLE_FLAGS_RESIZABLE) != 0);
|
|
if (resizable != resizableOld) TRACELOG(LOG_ERROR, "WIN32: Expected resizable %u but got %u", resizable, resizableOld);
|
|
if (resizableOld != resizableNew)
|
|
{
|
|
if (resizableNew) deferredFlags->set |= FLAG_WINDOW_RESIZABLE;
|
|
else deferredFlags->clear |= FLAG_WINDOW_RESIZABLE;
|
|
}
|
|
|
|
// Check window decorated flag change
|
|
bool decorated = (0 == (coreWindowFlags & FLAG_WINDOW_UNDECORATED));
|
|
bool decoratedOld = DecoratedFromStyle(style->styleOld);
|
|
bool decoratedNew = DecoratedFromStyle(style->styleNew);
|
|
if (decorated != decoratedOld) TRACELOG(LOG_ERROR, "WIN32: Expected decorated %u but got %u", decorated, decoratedOld);
|
|
if (decoratedOld != decoratedNew)
|
|
{
|
|
if (decoratedNew) deferredFlags->clear |= FLAG_WINDOW_UNDECORATED;
|
|
else deferredFlags->set |= FLAG_WINDOW_UNDECORATED;
|
|
}
|
|
|
|
// Check window hidden flag change
|
|
bool hidden = (coreWindowFlags & FLAG_WINDOW_HIDDEN);
|
|
bool hiddenOld = ((style->styleOld & WS_VISIBLE) == 0);
|
|
bool hiddenNew = ((style->styleNew & WS_VISIBLE) == 0);
|
|
if (hidden != hiddenOld) TRACELOG(LOG_ERROR, "WIN32: Expected hidden %u but got %u", hidden, hiddenOld);
|
|
if (hiddenOld != hiddenNew)
|
|
{
|
|
if (hiddenNew) deferredFlags->set |= FLAG_WINDOW_HIDDEN;
|
|
else deferredFlags->clear |= FLAG_WINDOW_HIDDEN;
|
|
}
|
|
}
|
|
|
|
// Adopt window resize
|
|
// NOTE: Call when the window is rezised, returns true
|
|
// if the new window size should update the desired app size
|
|
static bool AdoptWindowResize(unsigned flags)
|
|
{
|
|
if (flags & FLAG_WINDOW_MINIMIZED) return false;
|
|
if (flags & FLAG_WINDOW_MAXIMIZED) return false;
|
|
if (flags & FLAG_FULLSCREEN_MODE) return false;
|
|
if (flags & FLAG_BORDERLESS_WINDOWED_MODE) return false;
|
|
if (!(flags & FLAG_WINDOW_RESIZABLE)) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
// Here's the end of the "pure function section", the rest of the file can access global state
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
// Unlock the ability to use CORE in the rest of the file
|
|
#undef CORE
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Module Internal Functions Declaration
|
|
//----------------------------------------------------------------------------------
|
|
int InitPlatform(void); // Initialize platform (graphics, inputs and more)
|
|
void ClosePlatform(void); // Close platform
|
|
|
|
// Win32 process messages management function
|
|
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
|
|
|
|
// Win32: Handle inputs functions
|
|
static void HandleKey(WPARAM wparam, LPARAM lparam, char state);
|
|
static void HandleMouseButton(int button, char state);
|
|
static void HandleRawInput(LPARAM lparam);
|
|
static void HandleWindowResize(HWND hwnd, int *width, int *height);
|
|
|
|
static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags);
|
|
static unsigned SanitizeFlags(int mode, unsigned flags);
|
|
static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height); // Update window flags
|
|
|
|
// Check if OpenGL extension is available
|
|
static bool IsWglExtensionAvailable(HDC hdc, const char *extension);
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Module Functions Declaration
|
|
//----------------------------------------------------------------------------------
|
|
// NOTE: Functions declaration is provided by raylib.h
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Module Functions Definition: Window and Graphics Device
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Check if application should close
|
|
bool WindowShouldClose(void)
|
|
{
|
|
return CORE.Window.shouldClose;
|
|
}
|
|
|
|
// Toggle fullscreen mode
|
|
void ToggleFullscreen(void)
|
|
{
|
|
TRACELOG(LOG_WARNING, "WIN32: Toggle full screen functionality not implemented");
|
|
}
|
|
|
|
// Toggle borderless windowed mode
|
|
void ToggleBorderlessWindowed(void)
|
|
{
|
|
if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) ClearWindowState(FLAG_BORDERLESS_WINDOWED_MODE);
|
|
else SetWindowState(FLAG_BORDERLESS_WINDOWED_MODE);
|
|
}
|
|
|
|
// Set window state: maximized, if resizable
|
|
void MaximizeWindow(void)
|
|
{
|
|
SetWindowState(FLAG_WINDOW_MAXIMIZED);
|
|
}
|
|
|
|
// Set window state: minimized
|
|
void MinimizeWindow(void)
|
|
{
|
|
SetWindowState(FLAG_WINDOW_MINIMIZED);
|
|
}
|
|
|
|
// Restore window from being minimized/maximized
|
|
void RestoreWindow(void)
|
|
{
|
|
if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) &&
|
|
(CORE.Window.flags & FLAG_WINDOW_MINIMIZED)) ClearWindowState(FLAG_WINDOW_MINIMIZED);
|
|
else ClearWindowState(FLAG_WINDOW_MINIMIZED | FLAG_WINDOW_MAXIMIZED);
|
|
}
|
|
|
|
// Set window configuration state using flags
|
|
void SetWindowState(unsigned int flags)
|
|
{
|
|
platform.desiredFlags = SanitizeFlags(1 /*SANITIZE_FLAGS_NORMAL*/, CORE.Window.flags | flags);
|
|
UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight);
|
|
}
|
|
|
|
// Clear window configuration state flags
|
|
void ClearWindowState(unsigned int flags)
|
|
{
|
|
platform.desiredFlags = SanitizeFlags(1 /*SANITIZE_FLAGS_NORMAL*/, CORE.Window.flags & ~flags);
|
|
UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight);
|
|
}
|
|
|
|
// Set icon for window
|
|
void SetWindowIcon(Image image)
|
|
{
|
|
if (!platform.hwnd || (image.data == NULL) || (image.width <= 0) || (image.height <= 0)) return;
|
|
|
|
HDC hdc = GetDC(platform.hwnd);
|
|
|
|
// Create 32-bit BGRA DIB for color
|
|
BITMAPV5HEADER bi = { 0 };
|
|
ZeroMemory(&bi, sizeof(bi));
|
|
bi.bV5Size = sizeof(bi);
|
|
bi.bV5Width = image.width;
|
|
bi.bV5Height = -image.height; // Negative = top-down bitmap
|
|
bi.bV5Planes = 1;
|
|
bi.bV5BitCount = 32;
|
|
bi.bV5Compression = BI_BITFIELDS;
|
|
bi.bV5RedMask = 0x00FF0000;
|
|
bi.bV5GreenMask = 0x0000FF00;
|
|
bi.bV5BlueMask = 0x000000FF;
|
|
bi.bV5AlphaMask = 0xFF000000;
|
|
|
|
unsigned char *targetBits = NULL;
|
|
HBITMAP hColorBitmap = CreateDIBSection(hdc, (BITMAPINFO *)&bi, DIB_RGB_COLORS, (void **)&targetBits, NULL, 0);
|
|
if (!hColorBitmap)
|
|
{
|
|
ReleaseDC(platform.hwnd, hdc);
|
|
return;
|
|
}
|
|
|
|
// Copy RGBA > BGRA (Win32 expects BGRA)
|
|
for (int y = 0; y < image.height; y++)
|
|
{
|
|
for (int x = 0; x < image.width; x++)
|
|
{
|
|
int i = (y*image.width + x)*4;
|
|
targetBits[i + 0] = ((unsigned char *)image.data)[i + 2]; // B
|
|
targetBits[i + 1] = ((unsigned char *)image.data)[i + 1]; // G
|
|
targetBits[i + 2] = ((unsigned char *)image.data)[i + 0]; // R
|
|
targetBits[i + 3] = ((unsigned char *)image.data)[i + 3]; // A
|
|
}
|
|
}
|
|
|
|
// Create mask bitmap (1-bit, all opaque)
|
|
HBITMAP hMaskBitmap = CreateBitmap(image.width, image.height, 1, 1, NULL);
|
|
|
|
// Build icon info
|
|
ICONINFO ii = { 0 };
|
|
ZeroMemory(&ii, sizeof(ii));
|
|
ii.fIcon = TRUE;
|
|
ii.hbmMask = hMaskBitmap;
|
|
ii.hbmColor = hColorBitmap;
|
|
|
|
HICON hIcon = CreateIconIndirect(&ii);
|
|
|
|
// Clean up GDI bitmaps (icon keeps copies internally)
|
|
DeleteObject(hColorBitmap);
|
|
DeleteObject(hMaskBitmap);
|
|
ReleaseDC(platform.hwnd, hdc);
|
|
|
|
if (hIcon)
|
|
{
|
|
// Set both large and small icons
|
|
SendMessage(platform.hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
|
|
SendMessage(platform.hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
|
|
}
|
|
}
|
|
|
|
// Set icon for window
|
|
void SetWindowIcons(Image *images, int count)
|
|
{
|
|
// TODO: Implement SetWindowIcons()
|
|
}
|
|
|
|
void SetWindowTitle(const char *title)
|
|
{
|
|
CORE.Window.title = title;
|
|
|
|
WCHAR *titleWide = NULL;
|
|
A_TO_W_ALLOCA(titleWide, CORE.Window.title);
|
|
|
|
int result = SetWindowTextW(platform.hwnd, titleWide);
|
|
if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to set window title [ERROR: %lu]", GetLastError());
|
|
}
|
|
|
|
// Set window position on screen (windowed mode)
|
|
void SetWindowPosition(int x, int y)
|
|
{
|
|
if (platform.hwnd != NULL)
|
|
{
|
|
RECT rect = { 0 };
|
|
if (GetWindowRect(platform.hwnd, &rect))
|
|
{
|
|
int width = rect.right - rect.left;
|
|
int height = rect.bottom - rect.top;
|
|
|
|
// Move the window to the new position (keeping size and z-order)
|
|
SetWindowPos(platform.hwnd, NULL, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set monitor for the current window
|
|
void SetWindowMonitor(int monitor)
|
|
{
|
|
TRACELOG(LOG_WARNING, "SetWindowMonitor not implemented");
|
|
}
|
|
|
|
// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE)
|
|
void SetWindowMinSize(int width, int height)
|
|
{
|
|
TRACELOG(LOG_WARNING, "SetWindowMinSize not implemented");
|
|
|
|
CORE.Window.screenMin.width = width;
|
|
CORE.Window.screenMin.height = height;
|
|
}
|
|
|
|
// Set window maximum dimensions (FLAG_WINDOW_RESIZABLE)
|
|
void SetWindowMaxSize(int width, int height)
|
|
{
|
|
TRACELOG(LOG_WARNING, "SetWindowMaxSize not implemented");
|
|
|
|
CORE.Window.screenMax.width = width;
|
|
CORE.Window.screenMax.height = height;
|
|
}
|
|
|
|
// Set window dimensions
|
|
void SetWindowSize(int width, int height)
|
|
{
|
|
TRACELOG(LOG_WARNING, "SetWindowSize not implemented");
|
|
}
|
|
|
|
// Set window opacity, value opacity is between 0.0 and 1.0
|
|
void SetWindowOpacity(float opacity)
|
|
{
|
|
TRACELOG(LOG_WARNING, "SetWindowOpacity not implemented");
|
|
}
|
|
|
|
// Set window focused
|
|
void SetWindowFocused(void)
|
|
{
|
|
TRACELOG(LOG_WARNING, "SetWindowFocused not implemented");
|
|
}
|
|
|
|
// Get native window handle
|
|
void *GetWindowHandle(void)
|
|
{
|
|
return platform.hwnd;
|
|
}
|
|
|
|
int GetMonitorCount(void)
|
|
{
|
|
int count = 0;
|
|
|
|
int result = EnumDisplayMonitors(NULL, NULL, CountMonitorsProc, (LPARAM)&count);
|
|
if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "EnumDisplayMonitors", GetLastError());
|
|
|
|
return count;
|
|
}
|
|
|
|
// Get current monitor where window is placed
|
|
int GetCurrentMonitor(void)
|
|
{
|
|
HMONITOR monitor = MonitorFromWindow(platform.hwnd, MONITOR_DEFAULTTOPRIMARY);
|
|
if (!monitor) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "MonitorFromWindow", GetLastError());
|
|
|
|
MonitorInfo info = { 0 };
|
|
info.needle = monitor;
|
|
info.index = 0;
|
|
info.matchIndex = -1;
|
|
|
|
int result = EnumDisplayMonitors(NULL, NULL, FindMonitorProc, (LPARAM)&info);
|
|
if (result == 0) TRACELOG(LOG_ERROR, "%s failed, error=%lu", "EnumDisplayMonitors", GetLastError());
|
|
|
|
return info.matchIndex;
|
|
}
|
|
|
|
// Get selected monitor position
|
|
Vector2 GetMonitorPosition(int monitor)
|
|
{
|
|
TRACELOG(LOG_WARNING, "GetMonitorPosition not implemented");
|
|
return (Vector2){ 0, 0 };
|
|
}
|
|
|
|
// Get selected monitor width (currently used by monitor)
|
|
int GetMonitorWidth(int monitor)
|
|
{
|
|
TRACELOG(LOG_WARNING, "GetMonitorWidth not implemented");
|
|
return 0;
|
|
}
|
|
|
|
// Get selected monitor height (currently used by monitor)
|
|
int GetMonitorHeight(int monitor)
|
|
{
|
|
TRACELOG(LOG_WARNING, "GetMonitorHeight not implemented");
|
|
return 0;
|
|
}
|
|
|
|
// Get selected monitor physical width in millimetres
|
|
int GetMonitorPhysicalWidth(int monitor)
|
|
{
|
|
TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth not implemented");
|
|
return 0;
|
|
}
|
|
|
|
// Get selected monitor physical height in millimetres
|
|
int GetMonitorPhysicalHeight(int monitor)
|
|
{
|
|
TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight not implemented");
|
|
return 0;
|
|
}
|
|
|
|
// Get selected monitor refresh rate
|
|
int GetMonitorRefreshRate(int monitor)
|
|
{
|
|
TRACELOG(LOG_WARNING, "GetMonitorRefreshRate not implemented");
|
|
return 0;
|
|
}
|
|
|
|
// Get the human-readable, UTF-8 encoded name of the selected monitor
|
|
const char *GetMonitorName(int monitor)
|
|
{
|
|
TRACELOG(LOG_WARNING, "GetMonitorName not implemented");
|
|
return 0;
|
|
}
|
|
|
|
// Get window position XY on monitor
|
|
Vector2 GetWindowPosition(void)
|
|
{
|
|
TRACELOG(LOG_WARNING, "GetWindowPosition not implemented");
|
|
return (Vector2){ 0, 0 };
|
|
}
|
|
|
|
// Get window scale DPI factor for current monitor
|
|
Vector2 GetWindowScaleDPI(void)
|
|
{
|
|
float scale = ((float)GetDpiForWindow(platform.hwnd))/96.0f;
|
|
return (Vector2){ scale, scale };
|
|
}
|
|
|
|
// Set clipboard text content
|
|
void SetClipboardText(const char *text)
|
|
{
|
|
TRACELOG(LOG_WARNING, "SetClipboardText not implemented");
|
|
}
|
|
|
|
// Get clipboard text content
|
|
const char *GetClipboardText(void)
|
|
{
|
|
TRACELOG(LOG_WARNING, "GetClipboardText not implemented");
|
|
return NULL;
|
|
}
|
|
|
|
// Get clipboard image
|
|
Image GetClipboardImage(void)
|
|
{
|
|
Image image = { 0 };
|
|
|
|
TRACELOG(LOG_WARNING, "GetClipboardText not implemented");
|
|
|
|
return image;
|
|
}
|
|
|
|
// Show mouse cursor
|
|
void ShowCursor(void)
|
|
{
|
|
SetCursor(LoadCursorW(NULL, (LPCWSTR)IDC_ARROW));
|
|
CORE.Input.Mouse.cursorHidden = false;
|
|
}
|
|
|
|
// Hides mouse cursor
|
|
void HideCursor(void)
|
|
{
|
|
// NOTE: We use SetCursor() instead of ShowCursor() because
|
|
// it makes it easy to only hide the cursor while it's inside the client area
|
|
SetCursor(NULL);
|
|
CORE.Input.Mouse.cursorHidden = true;
|
|
}
|
|
|
|
// Enables cursor (unlock cursor)
|
|
void EnableCursor(void)
|
|
{
|
|
if (CORE.Input.Mouse.cursorLocked)
|
|
{
|
|
if (!ClipCursor(NULL)) TRACELOG(LOG_WARNING, "WIN32: Failed to clip cursor [ERROR: %lu]", GetLastError());
|
|
|
|
RAWINPUTDEVICE rid = { 0 };
|
|
rid.usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC
|
|
rid.usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE
|
|
rid.dwFlags = RIDEV_REMOVE; // Add to this window even in background
|
|
rid.hwndTarget = NULL;
|
|
int result = RegisterRawInputDevices(&rid, 1, sizeof(rid));
|
|
if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to register raw input devices [ERROR: %lu]", GetLastError());
|
|
|
|
ShowCursor();
|
|
CORE.Input.Mouse.cursorLocked = false;
|
|
}
|
|
}
|
|
|
|
// Disables cursor (lock cursor)
|
|
void DisableCursor(void)
|
|
{
|
|
if (!CORE.Input.Mouse.cursorLocked)
|
|
{
|
|
RAWINPUTDEVICE rid = { 0 };
|
|
rid.usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC
|
|
rid.usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE
|
|
rid.dwFlags = RIDEV_INPUTSINK; // Add to this window even in background
|
|
rid.hwndTarget = platform.hwnd;
|
|
int result = RegisterRawInputDevices(&rid, 1, sizeof(rid));
|
|
if (result == 0) TRACELOG(LOG_WARNING, "WIN32: Failed to register raw input devices [ERROR: %lu]", GetLastError());
|
|
|
|
RECT clientRect = { 0 };
|
|
if (!GetClientRect(platform.hwnd, &clientRect)) TRACELOG(LOG_WARNING, "WIN32: Failed to get client rectangle [ERROR: %lu]", GetLastError());
|
|
|
|
POINT topleft = { clientRect.left, clientRect.top };
|
|
if (!ClientToScreen(platform.hwnd, &topleft)) TRACELOG(LOG_WARNING, "WIN32: Failed to get client to screen size [ERROR: %lu]", GetLastError());
|
|
|
|
LONG width = clientRect.right - clientRect.left;
|
|
LONG height = clientRect.bottom - clientRect.top;
|
|
|
|
TRACELOG(LOG_INFO, "WIN32: Clip cursor client rect: [%d,%d %d,%d], top-left: (%d,%d)",
|
|
clientRect.left, clientRect.top, clientRect.right, clientRect.bottom, topleft.x, topleft.y);
|
|
|
|
LONG centerX = topleft.x + width/2;
|
|
LONG centerY = topleft.y + height/2;
|
|
RECT clipRect = { centerX, centerY, centerX + 1, centerY + 1 };
|
|
if (!ClipCursor(&clipRect)) TRACELOG(LOG_WARNING, "WIN32: Failed to clip cursor [ERROR: %lu]", GetLastError());
|
|
|
|
CORE.Input.Mouse.previousPosition = (Vector2){ 0, 0 };
|
|
CORE.Input.Mouse.currentPosition = (Vector2){ 0, 0 };
|
|
HideCursor();
|
|
|
|
CORE.Input.Mouse.cursorLocked = true;
|
|
}
|
|
}
|
|
|
|
// Swap back buffer with front buffer (screen drawing)
|
|
void SwapScreenBuffer(void)
|
|
{
|
|
if (!platform.hdc) abort();
|
|
|
|
#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
|
|
// Update framebuffer
|
|
rlCopyFramebuffer(0, 0, CORE.Window.render.width, CORE.Window.render.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, platform.pixels);
|
|
|
|
// Force redraw
|
|
InvalidateRect(platform.hwnd, NULL, FALSE);
|
|
UpdateWindow(platform.hwnd);
|
|
#else
|
|
if (!SwapBuffers(platform.hdc)) TRACELOG(LOG_ERROR, "WIN32: Failed to swap buffers [ERROR: %lu]", GetLastError());
|
|
if (!ValidateRect(platform.hwnd, NULL)) TRACELOG(LOG_ERROR, "WIN32: Failed to validate screen rect [ERROR: %lu]", GetLastError());
|
|
#endif
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Module Functions Definition: Misc
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Get elapsed time measure in seconds
|
|
double GetTime(void)
|
|
{
|
|
LARGE_INTEGER now = 0;
|
|
QueryPerformanceCounter(&now);
|
|
return (double)(now.QuadPart - CORE.Time.base)/(double)platform.timerFrequency.QuadPart;
|
|
}
|
|
|
|
// Open URL with default system browser (if available)
|
|
// NOTE: This function is only safe to use if you control the URL given
|
|
// A user could craft a malicious string performing another action
|
|
// Only call this function yourself not with user input or make sure to check the string yourself
|
|
// REF: https://github.com/raysan5/raylib/issues/686
|
|
void OpenURL(const char *url)
|
|
{
|
|
// Security check to (partially) avoid malicious code on target platform
|
|
if (strchr(url, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'] character");
|
|
else
|
|
{
|
|
char *cmd = (char *)RL_CALLOC(strlen(url) + 32, sizeof(char));
|
|
sprintf(cmd, "explorer \"%s\"", url);
|
|
int result = system(cmd);
|
|
if (result == -1) TRACELOG(LOG_WARNING, "OpenURL() child process could not be created");
|
|
RL_FREE(cmd);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Module Functions Definition: Inputs
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Set internal gamepad mappings
|
|
int SetGamepadMappings(const char *mappings)
|
|
{
|
|
TRACELOG(LOG_WARNING, "SetGamepadMappings not implemented");
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Set gamepad vibration
|
|
void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration)
|
|
{
|
|
TRACELOG(LOG_WARNING, "SetGamepadVibration not implemented");
|
|
}
|
|
|
|
// Set mouse position XY
|
|
void SetMousePosition(int x, int y)
|
|
{
|
|
if (!CORE.Input.Mouse.cursorLocked)
|
|
{
|
|
CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y };
|
|
CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
|
|
TRACELOG(LOG_WARNING, "SetMousePosition not implemented");
|
|
}
|
|
else TRACELOG(LOG_WARNING, "INPUT: MOUSE: Cursor not enabled");
|
|
}
|
|
|
|
// Set mouse cursor
|
|
void SetMouseCursor(int cursor)
|
|
{
|
|
LPCWSTR cursorName = GetCursorName(cursor);
|
|
HCURSOR hcursor = LoadCursorW(NULL, cursorName);
|
|
if (!hcursor) TRACELOG(LOG_ERROR, "WIN32: Failed to load requested cursor [ERROR: %lu]", GetLastError());
|
|
|
|
SetCursor(hcursor);
|
|
CORE.Input.Mouse.cursorHidden = false;
|
|
}
|
|
|
|
// Get physical key name
|
|
const char *GetKeyName(int key)
|
|
{
|
|
TRACELOG(LOG_WARNING, "GetKeyName not implemented");
|
|
return NULL;
|
|
}
|
|
|
|
// Register all input events
|
|
void PollInputEvents(void)
|
|
{
|
|
// Reset keys/chars pressed registered
|
|
CORE.Input.Keyboard.keyPressedQueueCount = 0;
|
|
CORE.Input.Keyboard.charPressedQueueCount = 0;
|
|
|
|
// Reset key repeats
|
|
for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0;
|
|
|
|
// Reset last gamepad button/axis registered state
|
|
CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN
|
|
//CORE.Input.Gamepad.axisCount = 0;
|
|
|
|
// Register previous touch states
|
|
for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i];
|
|
|
|
// Reset touch positions
|
|
// TODO: It resets on target platform the mouse position and not filled again until a move-event,
|
|
// so, if mouse is not moved it returns a (0, 0) position... this behaviour should be reviewed!
|
|
//for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 };
|
|
|
|
memcpy(CORE.Input.Keyboard.previousKeyState, CORE.Input.Keyboard.currentKeyState, sizeof(CORE.Input.Keyboard.previousKeyState));
|
|
memset(CORE.Input.Keyboard.keyRepeatInFrame, 0, sizeof(CORE.Input.Keyboard.keyRepeatInFrame));
|
|
|
|
// Register previous mouse wheel state
|
|
CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove;
|
|
CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f };
|
|
|
|
// Register previous mouse position
|
|
CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
|
|
|
|
// Process windows messages
|
|
MSG msg = { 0 };
|
|
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessageW(&msg);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Module Internal Functions Definition
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Initialize modern OpenGL context
|
|
// NOTE: We need to create a dummy context first to query required extensions
|
|
HGLRC InitOpenGL(HWND hwnd, HDC hdc)
|
|
{
|
|
// First, create a dummy context to get WGL extensions
|
|
PIXELFORMATDESCRIPTOR pixelFormatDesc = {
|
|
.nSize = sizeof(PIXELFORMATDESCRIPTOR),
|
|
.nVersion = 1,
|
|
.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
|
|
.iPixelType = PFD_TYPE_RGBA,
|
|
.cColorBits = 32,
|
|
.cAlphaBits = 8,
|
|
.cDepthBits = 24,
|
|
.iLayerType = PFD_MAIN_PLANE
|
|
};
|
|
|
|
int pixelFormat = ChoosePixelFormat(hdc, &pixelFormatDesc);
|
|
SetPixelFormat(hdc, pixelFormat, &pixelFormatDesc);
|
|
//int pixelFormat = ChoosePixelFormat(platform.hdc, &pixelFormatDesc);
|
|
//if (!pixelFormat) { TRACELOG(LOG_ERROR, "%s failed, error=%lu", "ChoosePixelFormat", GetLastError()); return -1; }
|
|
//if (!SetPixelFormat(platform.hdc, pixelFormat, &pixelFormatDesc)) { TRACELOG(LOG_ERROR, "%s failed, error=%lu", "SetPixelFormat", GetLastError()); return -1; }
|
|
|
|
HGLRC tempContext = wglCreateContext(hdc);
|
|
//if (!tempContext) { TRACELOG(LOG_ERROR, "%s failed, error=%lu", "wglCreateContext", GetLastError()); return -1; }
|
|
BOOL result = wglMakeCurrent(hdc, tempContext);
|
|
//if (!result) { TRACELOG(LOG_ERROR, "%s failed, error=%lu", "wglMakeCurrent", GetLastError()); return -1; }
|
|
|
|
// Load WGL extension entry points
|
|
wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
|
|
wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
|
|
wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
|
|
wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress("wglGetExtensionsStringARB");
|
|
|
|
// Setup modern pixel format if extension is available
|
|
if (wglChoosePixelFormatARB)
|
|
{
|
|
int pixelFormatAttribs[] = {
|
|
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
|
|
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
|
|
WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
|
|
WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
|
|
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
|
|
WGL_COLOR_BITS_ARB, 32,
|
|
//WGL_RED_BITS_ARB, 8,
|
|
//WGL_GREEN_BITS_ARB, 8,
|
|
//WGL_BLUE_BITS_ARB, 8,
|
|
//WGL_ALPHA_BITS_ARB, 8,
|
|
WGL_DEPTH_BITS_ARB, 24,
|
|
WGL_STENCIL_BITS_ARB, 8,
|
|
0 // Terminator
|
|
};
|
|
|
|
int format = 0;
|
|
UINT numFormats = 0;
|
|
if (wglChoosePixelFormatARB(hdc, pixelFormatAttribs, NULL, 1, &format, &numFormats) && (numFormats > 0))
|
|
{
|
|
PIXELFORMATDESCRIPTOR newPixelFormatDescriptor = { 0 };
|
|
DescribePixelFormat(hdc, format, sizeof(newPixelFormatDescriptor), &newPixelFormatDescriptor);
|
|
SetPixelFormat(hdc, format, &newPixelFormatDescriptor);
|
|
}
|
|
}
|
|
|
|
// Create real modern OpenGL context (3.3 core)
|
|
HGLRC realContext = NULL;
|
|
if (wglCreateContextAttribsARB)
|
|
{
|
|
int glContextVersionMajor = 1;
|
|
int glContextVersionMinor = 1;
|
|
int glContextProfile = WGL_CONTEXT_CORE_PROFILE_BIT_ARB;
|
|
|
|
if (rlGetVersion() == RL_OPENGL_21) // Request OpenGL 2.1 context
|
|
{
|
|
glContextVersionMajor = 2;
|
|
glContextVersionMinor = 1;
|
|
}
|
|
else if (rlGetVersion() == RL_OPENGL_33) // Request OpenGL 3.3 context
|
|
{
|
|
glContextVersionMajor = 3;
|
|
glContextVersionMinor = 3;
|
|
}
|
|
else if (rlGetVersion() == RL_OPENGL_43) // Request OpenGL 4.3 context
|
|
{
|
|
glContextVersionMajor = 4;
|
|
glContextVersionMinor = 3;
|
|
}
|
|
else if (rlGetVersion() == RL_OPENGL_ES_20) // Request OpenGL ES 2.0 context
|
|
{
|
|
if (IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es_profile") ||
|
|
IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es2_profile"))
|
|
{
|
|
glContextVersionMajor = 2;
|
|
glContextVersionMinor = 0;
|
|
glContextProfile = WGL_CONTEXT_ES_PROFILE_BIT_EXT;
|
|
}
|
|
else TRACELOG(LOG_WARNING, "GL: OpenGL ES context not supported by GPU");
|
|
}
|
|
else if (rlGetVersion() == RL_OPENGL_ES_30) // Request OpenGL ES 3.0 context
|
|
{
|
|
if (IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es_profile") ||
|
|
IsWglExtensionAvailable(platform.hdc, "WGL_EXT_create_context_es2_profile"))
|
|
{
|
|
glContextVersionMajor = 3;
|
|
glContextVersionMinor = 0;
|
|
glContextProfile = WGL_CONTEXT_ES_PROFILE_BIT_EXT;
|
|
}
|
|
else TRACELOG(LOG_WARNING, "GL: OpenGL ES context not supported by GPU");
|
|
}
|
|
|
|
int contextAttribs[] = {
|
|
WGL_CONTEXT_MAJOR_VERSION_ARB, glContextVersionMajor,
|
|
WGL_CONTEXT_MINOR_VERSION_ARB, glContextVersionMinor,
|
|
WGL_CONTEXT_PROFILE_MASK_ARB, glContextProfile, // WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, WGL_CONTEXT_ES_PROFILE_BIT_EXT (if supported)
|
|
//WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB | WGL_CONTEXT_DEBUG_BIT_ARB [glDebugMessageCallback()]
|
|
0 // Terminator
|
|
};
|
|
|
|
// NOTE: We are not sharing context resources so, second parameters is NULL
|
|
realContext = wglCreateContextAttribsARB(hdc, NULL, contextAttribs);
|
|
|
|
// Check for error context creation errors
|
|
// ERROR_INVALID_VERSION_ARB (0x2095)
|
|
// ERROR_INVALID_PROFILE_ARB (0x2096)
|
|
if (realContext == NULL) TRACELOG(LOG_ERROR, "GL: Error creating requested context: %lu", GetLastError());
|
|
}
|
|
|
|
// Cleanup dummy temp context
|
|
wglMakeCurrent(NULL, NULL);
|
|
wglDeleteContext(tempContext);
|
|
|
|
// Activate real context
|
|
if (realContext) wglMakeCurrent(hdc, realContext);
|
|
|
|
// Once we got a real modern OpenGL context,
|
|
// we can load required extensions (function pointers)
|
|
rlLoadExtensions(WglGetProcAddress);
|
|
|
|
return realContext;
|
|
}
|
|
|
|
// Initialize platform: graphics, inputs and more
|
|
int InitPlatform(void)
|
|
{
|
|
int result = 0;
|
|
|
|
platform.appScreenWidth = CORE.Window.screen.width;
|
|
platform.appScreenHeight = CORE.Window.screen.height;
|
|
platform.desiredFlags = SanitizeFlags(0 /*SANITIZE_FLAGS_FIRST*/, CORE.Window.flags);
|
|
|
|
// NOTE: From this point CORE.Window.flags should always reflect the actual state of the window
|
|
CORE.Window.flags = FLAG_WINDOW_HIDDEN | (platform.desiredFlags & FLAG_MASK_NO_UPDATE);
|
|
|
|
/*
|
|
// TODO: Review SetProcessDpiAwarenessContext()
|
|
// NOTE: SetProcessDpiAwarenessContext() requires Windows 10, version 1703 and shcore.lib linkage
|
|
if (IsWindows10Version1703OrGreaterWin32())
|
|
{
|
|
TRACELOG(LOG_INFO, "DpiAware: >=Win10Creators");
|
|
if (!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
|
|
TRACELOG(LOG_ERROR, "%s failed, error %u", "SetProcessDpiAwarenessContext", GetLastError());
|
|
}
|
|
else
|
|
{
|
|
TRACELOG(LOG_INFO, "DpiAware: <Win10Creators");
|
|
HRESULT hr = SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
|
|
if (hr < 0) TRACELOG(LOG_ERROR, "%s failed, hresult=0x%lx", "SetProcessDpiAwareness", (DWORD)hr);
|
|
}
|
|
*/
|
|
|
|
HINSTANCE hInstance = GetModuleHandleW(0);
|
|
|
|
// Define window class
|
|
WNDCLASSEXW windowClass = {
|
|
.cbSize = sizeof(WNDCLASSEXW),
|
|
.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
|
|
.lpfnWndProc = WndProc, // Custom procedure assigned
|
|
.cbWndExtra = sizeof(LONG_PTR), // extra space for the Tuple object ptr
|
|
.hInstance = hInstance,
|
|
.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW), // TODO: Audit if we want to set this since we're implementing WM_SETCURSOR
|
|
.lpszClassName = CLASS_NAME // Class name: L"raylibWindow"
|
|
};
|
|
|
|
// Load user-provided icon if available
|
|
// NOTE: raylib resource file defaults to GLFW_ICON id, so looking for same identifier
|
|
windowClass.hIcon = LoadImageW(hInstance, L"GLFW_ICON", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
|
|
if (!windowClass.hIcon) windowClass.hIcon = LoadImageW(NULL, (LPCWSTR)IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
|
|
|
|
// Register window class
|
|
result = (int)RegisterClassExW(&windowClass);
|
|
if (result == 0) TRACELOG(LOG_ERROR, "WIN32: Failed to register window class [ERROR: %lu]", GetLastError());
|
|
|
|
// Get primary monitor info
|
|
POINT primaryTopLeft = { 0 };
|
|
HMONITOR monitor = MonitorFromPoint(primaryTopLeft, MONITOR_DEFAULTTOPRIMARY);
|
|
if (monitor != NULL)
|
|
{
|
|
MONITORINFO info = { 0 };
|
|
info.cbSize = sizeof(info);
|
|
result = (int)GetMonitorInfoW(monitor, &info);
|
|
|
|
if (result == 0) TRACELOG(LOG_WARNING, "WIN32: DISPLAY: Failed to get monitor info [ERROR: %u]", GetLastError());
|
|
else
|
|
{
|
|
CORE.Window.display.width = info.rcMonitor.right - info.rcMonitor.left;
|
|
CORE.Window.display.height = info.rcMonitor.bottom - info.rcMonitor.top;
|
|
}
|
|
}
|
|
else TRACELOG(LOG_WARNING, "WIN32: DISPLAY: Failed to get primary monitor from point [ERROR: %u]", GetLastError());
|
|
|
|
// Adjust the window rectangle so the *client area* matches desired size
|
|
// NOTE: Window width/height includes borders and title-bar
|
|
DWORD style = WS_OVERLAPPEDWINDOW;
|
|
RECT rect = { 0, 0, platform.appScreenWidth, platform.appScreenHeight };
|
|
AdjustWindowRect(&rect, style, FALSE);
|
|
//AdjustWindowRectEx(&rect, WS_OVERLAPPEDWINDOW, FALSE, WINDOW_STYLE_EX);
|
|
//AdjustWindowRectExForDpi(&rect, style, FALSE, WINDOW_STYLE_EX, dpi);
|
|
int windowWidth = rect.right - rect.left;
|
|
int windowHeight = rect.bottom - rect.top;
|
|
|
|
// Create window
|
|
// NOTE: Title string needs to be converted to WCHAR
|
|
WCHAR *titleWide = NULL;
|
|
A_TO_W_ALLOCA(titleWide, CORE.Window.title);
|
|
|
|
// Create window and get handle
|
|
platform.hwnd = CreateWindowExW(
|
|
WINDOW_STYLE_EX,
|
|
CLASS_NAME,
|
|
titleWide,
|
|
MakeWindowStyle(CORE.Window.flags), // WS_OVERLAPPEDWINDOW | WS_VISIBLE
|
|
CW_USEDEFAULT, CW_USEDEFAULT,
|
|
windowWidth, windowHeight, // TODO: Window size [width, height], needs to be updated?
|
|
NULL, NULL,
|
|
GetModuleHandleW(NULL), NULL);
|
|
|
|
if (!platform.hwnd)
|
|
{
|
|
TRACELOG(LOG_ERROR, "WIN32: WINDOW: Failed to create window [ERROR: %lu]", GetLastError());
|
|
return -1;
|
|
}
|
|
|
|
// Get handle to device drawing context
|
|
// NOTE: Windows GDI object that represents a drawing surface
|
|
platform.hdc = GetDC(platform.hwnd);
|
|
|
|
if (rlGetVersion() == RL_OPENGL_11_SOFTWARE) // Using software renderer
|
|
{
|
|
//ShowWindow(platform.hwnd, SW_SHOWDEFAULT); //SW_SHOWNORMAL
|
|
|
|
// Initialize software framebuffer
|
|
BITMAPINFO bmi = { 0 };
|
|
ZeroMemory(&bmi, sizeof(bmi));
|
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bmi.bmiHeader.biWidth = platform.appScreenWidth;
|
|
bmi.bmiHeader.biHeight = -(int)(platform.appScreenHeight); // Top-down bitmap
|
|
bmi.bmiHeader.biPlanes = 1;
|
|
bmi.bmiHeader.biBitCount = 32; // 32-bit BGRA
|
|
bmi.bmiHeader.biCompression = BI_RGB;
|
|
|
|
platform.hdcmem = CreateCompatibleDC(platform.hdc);
|
|
|
|
platform.hbitmap = CreateDIBSection(
|
|
platform.hdcmem, &bmi, DIB_RGB_COLORS,
|
|
(void **)&platform.pixels, NULL, 0);
|
|
|
|
SelectObject(platform.hdcmem, platform.hbitmap);
|
|
|
|
//ReleaseDC(platform.hwnd, platform.hdc); // Required?
|
|
}
|
|
else
|
|
{
|
|
// Init hardware-accelerated OpenGL modern context
|
|
platform.glContext = InitOpenGL(platform.hwnd, platform.hdc);
|
|
}
|
|
|
|
CORE.Window.ready = true;
|
|
|
|
// Update flags (in case of deferred state change required)
|
|
UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight);
|
|
|
|
CORE.Window.render.width = CORE.Window.screen.width;
|
|
CORE.Window.render.height = CORE.Window.screen.height;
|
|
CORE.Window.currentFbo.width = CORE.Window.render.width;
|
|
CORE.Window.currentFbo.height = CORE.Window.render.height;
|
|
TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully");
|
|
TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height);
|
|
TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height);
|
|
TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height);
|
|
TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y);
|
|
|
|
if (rlGetVersion() == RL_OPENGL_11_SOFTWARE) // Using software renderer
|
|
{
|
|
TRACELOG(LOG_INFO, "GL: OpenGL device information:");
|
|
TRACELOG(LOG_INFO, " > Vendor: %s", "raylib");
|
|
TRACELOG(LOG_INFO, " > Renderer: %s", "rlsw - OpenGL 1.1 Software Renderer");
|
|
TRACELOG(LOG_INFO, " > Version: %s", "1.0");
|
|
TRACELOG(LOG_INFO, " > GLSL: %s", "NOT SUPPORTED");
|
|
}
|
|
|
|
// Initialize timming system
|
|
//----------------------------------------------------------------------------
|
|
LARGE_INTEGER time = { 0 };
|
|
QueryPerformanceCounter(&time);
|
|
QueryPerformanceFrequency(&platform.timerFrequency);
|
|
CORE.Time.base = time.QuadPart;
|
|
|
|
InitTimer();
|
|
//----------------------------------------------------------------------------
|
|
|
|
// Initialize storage system
|
|
//----------------------------------------------------------------------------
|
|
CORE.Storage.basePath = GetWorkingDirectory();
|
|
//----------------------------------------------------------------------------
|
|
|
|
TRACELOG(LOG_INFO, "PLATFORM: DESKTOP: WIN32: Initialized successfully");
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Close platform
|
|
void ClosePlatform(void)
|
|
{
|
|
if (platform.hwnd)
|
|
{
|
|
int result = DestroyWindow(platform.hwnd);
|
|
if (result == 0) TRACELOG(LOG_WARNING, "WIN32: WINDOW: Failed on window destroy [ERROR: %u]", GetLastError());
|
|
platform.hwnd = NULL;
|
|
}
|
|
}
|
|
|
|
// Window procedure, message processing callback
|
|
// NOTE: All window event messages are processed here
|
|
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
|
|
{
|
|
LRESULT result = 0;
|
|
|
|
// Sanity check
|
|
DWORD mask = STYLE_MASK_ALL;
|
|
if (platform.hwnd == hwnd)
|
|
{
|
|
if (msg == WM_WINDOWPOSCHANGING) mask &= ~(WS_MINIMIZE | WS_MAXIMIZE);
|
|
CheckFlags("WndProc", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), mask);
|
|
}
|
|
|
|
FlagsOp flagsOp = { 0 };
|
|
FlagsOp *deferredFlags = &flagsOp;
|
|
|
|
// Message processing
|
|
//------------------------------------------------------------------------------------
|
|
switch (msg)
|
|
{
|
|
case WM_CREATE:
|
|
{
|
|
// WARNING: Not recommended to do OpenGL intialization at this point
|
|
|
|
} break;
|
|
//case WM_ACTIVATE
|
|
case WM_DESTROY:
|
|
{
|
|
// Clean up for window destruction
|
|
if (rlGetVersion() == RL_OPENGL_11_SOFTWARE) // Using software renderer
|
|
{
|
|
if (platform.hdcmem)
|
|
{
|
|
DeleteDC(platform.hdcmem);
|
|
platform.hdcmem = NULL;
|
|
}
|
|
|
|
if (platform.hbitmap)
|
|
{
|
|
DeleteObject(platform.hbitmap); // Clears platform.pixels data
|
|
platform.hbitmap = NULL;
|
|
platform.pixels = NULL; // NOTE: Pointer invalid after DeleteObject()
|
|
}
|
|
}
|
|
else // OpenGL hardware renderer
|
|
{
|
|
wglMakeCurrent(platform.hdc, NULL);
|
|
if (platform.glContext)
|
|
{
|
|
if (!wglDeleteContext(platform.glContext)) abort();
|
|
platform.glContext = NULL;
|
|
}
|
|
}
|
|
|
|
if (platform.hdc)
|
|
{
|
|
if (!ReleaseDC(hwnd, platform.hdc)) abort();
|
|
platform.hdc = NULL;
|
|
}
|
|
|
|
PostQuitMessage(0);
|
|
|
|
} break;
|
|
case WM_CLOSE: CORE.Window.shouldClose = true; break; // Window close button [x], ALT+F4
|
|
//case WM_QUIT: // Application closing, not related to window
|
|
case WM_KILLFOCUS:
|
|
{
|
|
memset(CORE.Input.Keyboard.previousKeyState, 0, sizeof(CORE.Input.Keyboard.previousKeyState));
|
|
memset(CORE.Input.Keyboard.currentKeyState, 0, sizeof(CORE.Input.Keyboard.currentKeyState));
|
|
} break;
|
|
case WM_SIZING:
|
|
{
|
|
if (CORE.Window.flags & FLAG_WINDOW_RESIZABLE)
|
|
{
|
|
// TODO: Enforce min/max size
|
|
}
|
|
else TRACELOG(LOG_WARNING, "WIN32: WINDOW: Trying to resize a non-resizable window");
|
|
|
|
result = TRUE;
|
|
} break;
|
|
case WM_STYLECHANGING:
|
|
{
|
|
if (wparam == GWL_STYLE)
|
|
{
|
|
STYLESTRUCT *ss = (STYLESTRUCT *)lparam;
|
|
GetStyleChangeFlagOps(CORE.Window.flags, ss, deferredFlags);
|
|
|
|
UINT dpi = GetDpiForWindow(hwnd);
|
|
// Get client size (framebuffer inside the window)
|
|
RECT rect = { 0 };
|
|
GetClientRect(hwnd, &rect);
|
|
SIZE clientSize = { rect.right, rect.bottom };
|
|
SIZE oldSize = CalcWindowSize(dpi, clientSize, ss->styleOld);
|
|
SIZE newSize = CalcWindowSize(dpi, clientSize, ss->styleNew);
|
|
|
|
if (oldSize.cx != newSize.cx || oldSize.cy != newSize.cy)
|
|
{
|
|
TRACELOG(LOG_INFO, "WIN32: WINDOW: Resize from style change [%dx%d] to [%dx%d]", oldSize.cx, oldSize.cy, newSize.cx, newSize.cy);
|
|
|
|
if (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED)
|
|
{
|
|
// looks like windows will automatically "unminimize" a window
|
|
// if a style changes modifies it's size
|
|
TRACELOG(LOG_INFO, "WIN32: WINDOW: Style change modifed window size, removing maximized flag");
|
|
deferredFlags->clear |= FLAG_WINDOW_MAXIMIZED;
|
|
}
|
|
}
|
|
}
|
|
} break;
|
|
case WM_WINDOWPOSCHANGING:
|
|
{
|
|
WINDOWPOS *pos = (WINDOWPOS *)lparam;
|
|
if (pos->flags & SWP_SHOWWINDOW) deferredFlags->clear |= FLAG_WINDOW_HIDDEN;
|
|
else if (pos->flags & SWP_HIDEWINDOW) deferredFlags->set |= FLAG_WINDOW_HIDDEN;
|
|
|
|
Mized mized = MIZED_NONE;
|
|
bool isIconic = IsIconic(hwnd);
|
|
bool styleMinimized = !!(WS_MINIMIZE & GetWindowLongPtrW(hwnd, GWL_STYLE));
|
|
if (isIconic != styleMinimized) TRACELOG(LOG_WARNING, "WIN32: IsIconic state different from WS_MINIMIZED state");
|
|
|
|
if (isIconic) mized = MIZED_MIN;
|
|
else
|
|
{
|
|
WINDOWPLACEMENT placement;
|
|
placement.length = sizeof(placement);
|
|
if (!GetWindowPlacement(hwnd, &placement)) TRACELOG(LOG_ERROR, "WIN32: WINDOW: FAiled to get monitor placement [ERROR: %lu]", GetLastError());
|
|
|
|
if (placement.showCmd == SW_SHOWMAXIMIZED) mized = MIZED_MAX;
|
|
}
|
|
|
|
switch (mized)
|
|
{
|
|
case MIZED_NONE:
|
|
{
|
|
deferredFlags->clear |= (FLAG_WINDOW_MINIMIZED | FLAG_WINDOW_MAXIMIZED);
|
|
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
|
|
MONITORINFO info;
|
|
info.cbSize = sizeof(info);
|
|
if (!GetMonitorInfoW(monitor, &info)) TRACELOG(LOG_ERROR, "WIN32: MONITOR: Failed to get monitor info [ERROR: %lu]", GetLastError());
|
|
|
|
if ((pos->x == info.rcMonitor.left) &&
|
|
(pos->y == info.rcMonitor.top) &&
|
|
(pos->cx == (info.rcMonitor.right - info.rcMonitor.left)) &&
|
|
(pos->cy == (info.rcMonitor.bottom - info.rcMonitor.top))) deferredFlags->set |= FLAG_BORDERLESS_WINDOWED_MODE;
|
|
else deferredFlags->clear |= FLAG_BORDERLESS_WINDOWED_MODE;
|
|
|
|
} break;
|
|
case MIZED_MIN:
|
|
{
|
|
// !!! NOTE !!! Do not update the maximized/borderless
|
|
// flags because when hwnd is minimized it temporarily overrides
|
|
// the maximized state/flag which gets restored on SW_RESTORE
|
|
deferredFlags->set |= FLAG_WINDOW_MINIMIZED;
|
|
} break;
|
|
case MIZED_MAX:
|
|
{
|
|
deferredFlags->clear |= FLAG_WINDOW_MINIMIZED;
|
|
deferredFlags->set |= FLAG_WINDOW_MAXIMIZED;
|
|
} break;
|
|
default: break;
|
|
}
|
|
} break;
|
|
case WM_SIZE:
|
|
{
|
|
// WARNING: Don't trust the docs, they say you won't get this message if you don't call DefWindowProc
|
|
// in response to WM_WINDOWPOSCHANGED but looks like when a window is created you'll get this
|
|
// message without getting WM_WINDOWPOSCHANGED
|
|
HandleWindowResize(hwnd, &platform.appScreenWidth, &platform.appScreenHeight);
|
|
} break;
|
|
//case WM_MOVE
|
|
case WM_WINDOWPOSCHANGED:
|
|
{
|
|
WINDOWPOS *pos = (WINDOWPOS*)lparam;
|
|
if (!(pos->flags & SWP_NOSIZE)) HandleWindowResize(hwnd, &platform.appScreenWidth, &platform.appScreenHeight);
|
|
} break;
|
|
case WM_GETDPISCALEDSIZE:
|
|
{
|
|
SIZE *inoutSize = (SIZE *)lparam;
|
|
UINT newDpi = (UINT)wparam; // TODO: WARNING: Converting from WPARAM = UINT_PTR
|
|
|
|
// for any of these other cases, we might want to post a window
|
|
// resize event after the dpi changes?
|
|
if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) return TRUE;
|
|
if (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) return TRUE;
|
|
if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) return TRUE;
|
|
|
|
float dpiScale = ((float)newDpi)/96.0f;
|
|
bool dpiScaling = CORE.Window.flags & FLAG_WINDOW_HIGHDPI;
|
|
// Get size in pixels from points
|
|
SIZE desired = {
|
|
.cx = dpiScaling? (int)((float)platform.appScreenWidth*dpiScale) : platform.appScreenWidth,
|
|
.cy = dpiScaling? (int)((float)platform.appScreenHeight*dpiScale) : platform.appScreenHeight
|
|
};
|
|
inoutSize->cx = desired.cx;
|
|
inoutSize->cy = desired.cy;
|
|
|
|
result = TRUE;
|
|
} break;
|
|
case WM_DPICHANGED:
|
|
{
|
|
RECT *suggestedRect = (RECT *)lparam;
|
|
|
|
// Never set the window size to anything other than the suggested rect here
|
|
// Doing so can cause a window to stutter between monitors when transitioning between them
|
|
int result = (int)SetWindowPos(hwnd, NULL, suggestedRect->left, suggestedRect->top,
|
|
suggestedRect->right - suggestedRect->left, suggestedRect->bottom - suggestedRect->top, SWP_NOZORDER | SWP_NOACTIVATE);
|
|
if (result == 0) TRACELOG(LOG_ERROR, "Failed to set window position [ERROR: %lu]", GetLastError());
|
|
|
|
} break;
|
|
case WM_SETCURSOR:
|
|
{
|
|
// Called when mouse moves, enters/leaves window...
|
|
if (LOWORD(lparam) == HTCLIENT)
|
|
{
|
|
SetCursor(CORE.Input.Mouse.cursorHidden? NULL : LoadCursorW(NULL, (LPCWSTR)IDC_ARROW));
|
|
return 0;
|
|
}
|
|
|
|
result = DefWindowProc(hwnd, msg, wparam, lparam);
|
|
} break;
|
|
case WM_PAINT:
|
|
{
|
|
if (rlGetVersion() == RL_OPENGL_11_SOFTWARE) // Using software renderer
|
|
{
|
|
PAINTSTRUCT ps = { 0 };
|
|
HDC hdc = BeginPaint(hwnd, &ps);
|
|
|
|
// Blit from memory DC to window DC
|
|
BitBlt(hdc, 0, 0, platform.appScreenWidth, platform.appScreenHeight, platform.hdcmem, 0, 0, SRCCOPY);
|
|
|
|
EndPaint(hwnd, &ps);
|
|
}
|
|
}
|
|
case WM_INPUT:
|
|
{
|
|
//HandleRawInput(lparam);
|
|
} break;
|
|
case WM_MOUSEMOVE:
|
|
{
|
|
if (!CORE.Input.Mouse.cursorLocked)
|
|
{
|
|
CORE.Input.Mouse.currentPosition.x = (float)GET_X_LPARAM(lparam);
|
|
CORE.Input.Mouse.currentPosition.y = (float)GET_Y_LPARAM(lparam);
|
|
CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition;
|
|
}
|
|
} break;
|
|
case WM_KEYDOWN: HandleKey(wparam, lparam, 1); break;
|
|
case WM_KEYUP: HandleKey(wparam, lparam, 0); break;
|
|
case WM_LBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_LEFT, 1); break;
|
|
case WM_LBUTTONUP : HandleMouseButton(MOUSE_BUTTON_LEFT, 0); break;
|
|
case WM_RBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_RIGHT, 1); break;
|
|
case WM_RBUTTONUP : HandleMouseButton(MOUSE_BUTTON_RIGHT, 0); break;
|
|
case WM_MBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_MIDDLE, 1); break;
|
|
case WM_MBUTTONUP : HandleMouseButton(MOUSE_BUTTON_MIDDLE, 0); break;
|
|
case WM_XBUTTONDOWN:
|
|
{
|
|
switch (HIWORD(wparam))
|
|
{
|
|
case XBUTTON1: HandleMouseButton(MOUSE_BUTTON_SIDE, 1); break;
|
|
case XBUTTON2: HandleMouseButton(MOUSE_BUTTON_EXTRA, 1); break;
|
|
default: TRACELOG(LOG_WARNING, "TODO: handle ex mouse button DOWN wparam=%u", HIWORD(wparam)); break;
|
|
}
|
|
} break;
|
|
case WM_XBUTTONUP:
|
|
{
|
|
switch (HIWORD(wparam))
|
|
{
|
|
case XBUTTON1: HandleMouseButton(MOUSE_BUTTON_SIDE, 0); break;
|
|
case XBUTTON2: HandleMouseButton(MOUSE_BUTTON_EXTRA, 0); break;
|
|
default: TRACELOG(LOG_WARNING, "TODO: handle ex mouse button UP wparam=%u", HIWORD(wparam)); break;
|
|
}
|
|
} break;
|
|
case WM_MOUSEWHEEL: CORE.Input.Mouse.currentWheelMove.y = ((float)GET_WHEEL_DELTA_WPARAM(wparam))/WHEEL_DELTA; break;
|
|
case WM_MOUSEHWHEEL: CORE.Input.Mouse.currentWheelMove.x = ((float)GET_WHEEL_DELTA_WPARAM(wparam))/WHEEL_DELTA; break;
|
|
case WM_APP_UPDATE_WINDOW_SIZE:
|
|
{
|
|
//UpdateWindowSize(UPDATE_WINDOW_NORMAL, hwnd, platform.appScreenWidth, platform.appScreenHeight, CORE.Window.flags);
|
|
} break;
|
|
|
|
default: result = DefWindowProcW(hwnd, msg, wparam, lparam); // Message passed directly for execution (default behaviour)
|
|
}
|
|
//------------------------------------------------------------------------------------
|
|
|
|
// Sanity check for flags
|
|
if (platform.hwnd == hwnd) CheckFlags("After WndProc", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), mask);
|
|
|
|
// Operations to execute after the above check
|
|
if (flagsOp.set & flagsOp.clear) TRACELOG(LOG_WARNING, "WIN32: FLAGS: Flags 0x%x were both set and cleared", flagsOp.set & flagsOp.clear);
|
|
|
|
DWORD save = CORE.Window.flags;
|
|
CORE.Window.flags |= flagsOp.set;
|
|
CORE.Window.flags &= ~flagsOp.clear;
|
|
if (save != CORE.Window.flags) TRACELOG(LOG_DEBUG, "WIN32: FLAGS: Current deferred flags: 0x%x > 0x%x (diff 0x%x)", save, CORE.Window.flags, save ^ CORE.Window.flags);
|
|
|
|
return result;
|
|
}
|
|
|
|
// Handle keyboard input event
|
|
static void HandleKey(WPARAM wparam, LPARAM lparam, char state)
|
|
{
|
|
KeyboardKey key = GetKeyFromWparam(wparam);
|
|
|
|
// TODO: Use scancode?
|
|
//BYTE scancode = lparam >> 16;
|
|
//TRACELOG(LOG_INFO, "KEY key=%d vk=%lu scan=%u = %u", key, wparam, scancode, state);
|
|
|
|
if (key != KEY_NULL)
|
|
{
|
|
CORE.Input.Keyboard.currentKeyState[key] = state;
|
|
|
|
if ((key == KEY_ESCAPE) && (state == 1)) CORE.Window.shouldClose = true;
|
|
}
|
|
else TRACELOG(LOG_WARNING, "INPUT: Unknown (or currently unhandled) virtual keycode %d (0x%x)", wparam, wparam);
|
|
|
|
// TODO: Add key to the queue as well?
|
|
}
|
|
|
|
// Handle mouse button input event
|
|
static void HandleMouseButton(int button, char state)
|
|
{
|
|
// Register current mouse button state
|
|
CORE.Input.Mouse.currentButtonState[button] = state;
|
|
CORE.Input.Touch.currentTouchState[button] = state;
|
|
}
|
|
|
|
// Handle raw input event
|
|
static void HandleRawInput(LPARAM lparam)
|
|
{
|
|
RAWINPUT input = { 0 };
|
|
|
|
UINT inputSize = sizeof(input);
|
|
UINT size = GetRawInputData((HRAWINPUT)lparam, RID_INPUT, &input, &inputSize, sizeof(RAWINPUTHEADER));
|
|
|
|
if (size == (UINT)-1) TRACELOG(LOG_ERROR, "WIN32: Failed to get raw input data [ERROR: %lu]", GetLastError());
|
|
|
|
if (input.header.dwType != RIM_TYPEMOUSE) TRACELOG(LOG_ERROR, "WIN32: Unexpected WM_INPUT type %lu", input.header.dwType);
|
|
|
|
if (input.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) TRACELOG(LOG_ERROR, "TODO: handle absolute mouse inputs!");
|
|
|
|
if (input.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) TRACELOG(LOG_ERROR, "TODO: handle virtual desktop mouse inputs!");
|
|
|
|
// Trick to keep the mouse position at 0,0 and instead move
|
|
// the previous position so we can still get a proper mouse delta
|
|
//CORE.Input.Mouse.previousPosition.x -= input.data.mouse.lLastX;
|
|
//CORE.Input.Mouse.previousPosition.y -= input.data.mouse.lLastY;
|
|
//if (CORE.Input.Mouse.currentPosition.x != 0) abort();
|
|
//if (CORE.Input.Mouse.currentPosition.y != 0) abort();
|
|
}
|
|
|
|
// Handle window resizing event
|
|
static void HandleWindowResize(HWND hwnd, int *width, int *height)
|
|
{
|
|
if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) return;
|
|
|
|
// Get client size (framebuffer inside the window)
|
|
RECT rect = { 0 };
|
|
GetClientRect(hwnd, &rect);
|
|
SIZE clientSize = { rect.right, rect.bottom };
|
|
|
|
// TODO: Update framebuffer on resize
|
|
CORE.Window.currentFbo.width = (int)clientSize.cx;
|
|
CORE.Window.currentFbo.height = (int)clientSize.cy;
|
|
//glViewport(0, 0, clientSize.cx, clientSize.cy);
|
|
//SetupFramebuffer(0, 0);
|
|
|
|
SetupViewport(clientSize.cx, clientSize.cy);
|
|
CORE.Window.resizedLastFrame = true;
|
|
float dpiScale = ((float)GetDpiForWindow(hwnd))/96.0f;
|
|
bool highdpi = !!(CORE.Window.flags & FLAG_WINDOW_HIGHDPI);
|
|
unsigned int screenWidth = highdpi? (unsigned int)(((float)clientSize.cx)/dpiScale) : clientSize.cx;
|
|
unsigned int screenHeight = highdpi? (unsigned int)(((float)clientSize.cy)/dpiScale) : clientSize.cy;
|
|
CORE.Window.screen.width = screenWidth;
|
|
CORE.Window.screen.height = screenHeight;
|
|
|
|
if (AdoptWindowResize(CORE.Window.flags))
|
|
{
|
|
TRACELOG(LOG_DEBUG, "WIN32: WINDOW: Updating app size to [%ix%i] from window resize", screenWidth, screenHeight);
|
|
*width = screenWidth;
|
|
*height = screenHeight;
|
|
}
|
|
|
|
CORE.Window.screenScale = MatrixScale( (float)CORE.Window.render.width/CORE.Window.screen.width,
|
|
(float)CORE.Window.render.height/CORE.Window.screen.height, 1.0f);
|
|
}
|
|
|
|
// Update window style
|
|
static void UpdateWindowStyle(HWND hwnd, unsigned desiredFlags)
|
|
{
|
|
DWORD current = STYLE_MASK_WRITABLE & MakeWindowStyle(CORE.Window.flags);
|
|
DWORD desired = STYLE_MASK_WRITABLE & MakeWindowStyle(desiredFlags);
|
|
|
|
if (current != desired)
|
|
{
|
|
SetLastError(0);
|
|
DWORD previous = STYLE_MASK_WRITABLE & SetWindowLongPtrW(hwnd, GWL_STYLE, desired);
|
|
if (previous != current)
|
|
{
|
|
TRACELOG(LOG_ERROR, "WIN32: WINDOW: SetWindowLongPtr() returned writable flags 0x%x but expected 0x%x (diff=0x%x, error=%lu)",
|
|
previous, current, previous ^ current, GetLastError());
|
|
}
|
|
|
|
CheckFlags("UpdateWindowStyle", hwnd, desiredFlags, desired, STYLE_MASK_WRITABLE);
|
|
}
|
|
|
|
// Minimized takes precedence over maximized
|
|
Mized currentMized = MIZED_NONE;
|
|
Mized desiredMized = MIZED_NONE;
|
|
if (CORE.Window.flags & WS_MINIMIZE) currentMized = MIZED_MIN;
|
|
else if (CORE.Window.flags & WS_MAXIMIZE) currentMized = MIZED_MAX;
|
|
if (desiredFlags & WS_MINIMIZE) currentMized = MIZED_MIN;
|
|
else if (desiredFlags & WS_MAXIMIZE) currentMized = MIZED_MAX;
|
|
|
|
if (currentMized != desiredMized)
|
|
{
|
|
switch (desiredMized)
|
|
{
|
|
case MIZED_NONE: ShowWindow(hwnd, SW_RESTORE); break;
|
|
case MIZED_MIN: ShowWindow(hwnd, SW_MINIMIZE); break;
|
|
case MIZED_MAX: ShowWindow(hwnd, SW_MAXIMIZE); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sanitize flags
|
|
static unsigned SanitizeFlags(int mode, unsigned flags)
|
|
{
|
|
if ((flags & FLAG_WINDOW_MAXIMIZED) && (flags & FLAG_BORDERLESS_WINDOWED_MODE))
|
|
{
|
|
TRACELOG(LOG_WARNING, "WIN32: WINDOW: Borderless windows mode overriding maximized window flag");
|
|
flags &= ~FLAG_WINDOW_MAXIMIZED;
|
|
}
|
|
|
|
if (mode == 1)
|
|
{
|
|
if ((flags & FLAG_MSAA_4X_HINT) && (!(CORE.Window.flags & FLAG_MSAA_4X_HINT)))
|
|
{
|
|
TRACELOG(LOG_WARNING, "WIN32: WINDOW: MSAA can only be configured before window initialization");
|
|
flags &= ~FLAG_MSAA_4X_HINT;
|
|
}
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
// All window state changes from raylib flags go through this function. It performs
|
|
// whatever operations are needed to update the window state to match the desired flags
|
|
// In most cases this function should not update CORE.Window.flags directly, instead,
|
|
// the window itself should update CORE.Window.flags in response to actual state changes
|
|
// This means that CORE.Window.flags should always represent the actual state of the
|
|
// window. This function will continue to perform these update operations so long as
|
|
// the state continues to change
|
|
//
|
|
// This design takes care of many odd corner cases. For example, if you want to restore
|
|
// a window that was previously maximized AND minimized and you want to remove both these
|
|
// flags, you actually need to call ShowWindow with SW_RESTORE twice. Another example is
|
|
// if you have a maximized window, if the undecorated flag is modified then we'd need to
|
|
// update the window style, but updating the style would mean the window size would change
|
|
// causing the window to lose its Maximized state which would mean we'd need to update the
|
|
// window size and then update the window style a second time to restore that maximized
|
|
// state. This implementation is able to handle any/all of these special situations with a
|
|
// retry loop that continues until we either reach the desired state or the state stops changing
|
|
static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height)
|
|
{
|
|
// Flags that just apply immediately without needing any operations
|
|
CORE.Window.flags |= (desiredFlags & FLAG_MASK_NO_UPDATE);
|
|
|
|
int vsync = (CORE.Window.flags & FLAG_VSYNC_HINT)? 1 : 0;
|
|
if (wglSwapIntervalEXT)
|
|
{
|
|
(*wglSwapIntervalEXT)(vsync);
|
|
if (vsync) CORE.Window.flags |= FLAG_VSYNC_HINT;
|
|
else CORE.Window.flags &= ~FLAG_VSYNC_HINT;
|
|
}
|
|
|
|
// TODO: Review all this code...
|
|
DWORD previousStyle = 0;
|
|
for (unsigned attempt = 1; ; attempt++)
|
|
{
|
|
CheckFlags("UpdateFlags", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), STYLE_MASK_ALL);
|
|
|
|
bool windowSizeUpdated = false;
|
|
if (MakeWindowStyle(CORE.Window.flags) == MakeWindowStyle(desiredFlags))
|
|
{
|
|
windowSizeUpdated = UpdateWindowSize(1, hwnd, width, height, desiredFlags);
|
|
if ((FLAG_MASK_REQUIRED & desiredFlags) == (FLAG_MASK_REQUIRED & CORE.Window.flags)) break;
|
|
}
|
|
|
|
|
|
if ((attempt > 1) && (previousStyle == MakeWindowStyle(CORE.Window.flags)) && !windowSizeUpdated)
|
|
{
|
|
TRACELOG(LOG_ERROR, "WIN32: WINDOW: UpdateFlags() failed after %u attempt(s) wanted 0x%x but is 0x%x (diff=0x%x)",
|
|
attempt, desiredFlags, CORE.Window.flags, desiredFlags ^ CORE.Window.flags);
|
|
}
|
|
|
|
previousStyle = MakeWindowStyle(CORE.Window.flags);
|
|
UpdateWindowStyle(hwnd, desiredFlags);
|
|
}
|
|
}
|
|
|
|
// Check if OpenGL extension is available
|
|
static bool IsWglExtensionAvailable(HDC hdc, const char *extension)
|
|
{
|
|
bool result = false;
|
|
|
|
if (wglGetExtensionsStringARB != NULL)
|
|
{
|
|
const char *extList = wglGetExtensionsStringARB(hdc);
|
|
if (extList != NULL)
|
|
{
|
|
// Simple substring search (could use strtok or strstr)
|
|
if (strstr(extList, extension) != NULL) result = true;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
} |