#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
#undef Rectangle
#undef ShowCursor
#undef LoadImage
#undef DrawText
#undef DrawTextA
#undef DrawTextW
#undef DrawTextEx
#undef DrawTextExA
#undef DrawTextExW
#include <windowsx.h>
#include <shellscalingapi.h>
#include <versionhelpers.h>
#include <malloc.h>
#if !defined(GRAPHICS_API_OPENGL_SOFTWARE)
#include <GL/gl.h>
#endif
typedef struct {
HWND hwnd; HDC hdc; HGLRC glContext;
HDC hdcmem; HBITMAP hbitmap; unsigned int *pixels;
unsigned int appScreenWidth;
unsigned int appScreenHeight;
unsigned int desiredFlags;
LARGE_INTEGER timerFrequency;
} PlatformData;
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);
extern CoreData CORE;
static PlatformData platform = { 0 };
static PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL;
static PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = NULL;
static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL;
static PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = NULL;
#define CORE DONT_USE_CORE_HERE
#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 | WS_MAXIMIZEBOX)
#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)
#define FLAG_MASK_NO_UPDATE (FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT)
#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
#define WGL_NO_ACCELERATION_ARB 0x2025
#define WGL_GENERIC_ACCELERATION_ARB 0x2026
#define WGL_FULL_ACCELERATION_ARB 0x2027
#define WGL_SAMPLE_BUFFERS_ARB 0x2041
#define WGL_SAMPLES_ARB 0x2042
#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9
#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
typedef enum {
MIZED_NONE,
MIZED_MIN,
MIZED_MAX
} Mized;
typedef struct {
DWORD set;
DWORD clear;
} FlagsOp;
typedef struct {
HMONITOR needle;
int index;
int matchIndex;
RECT rect;
} MonitorInfo;
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;
}
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; }
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; }
static DWORD MakeWindowStyle(unsigned flags)
{
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;
int mized = MIZED_NONE;
if (flags & FLAG_WINDOW_MINIMIZED) mized = MIZED_MIN;
else 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;
}
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);
}
}
}
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 };
}
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;
}
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
};
RECT rect = { 0 };
GetClientRect(hwnd, &rect);
SIZE clientSize = { rect.right, rect.bottom };
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);
SIZE windowSize = CalcWindowSize(dpi, desiredSize, MakeWindowStyle(flags));
POINT windowPos = { 0 };
UINT swpFlags = SWP_NOZORDER | SWP_FRAMECHANGED;
if (mode == 0) {
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;
SetWindowPos(hwnd, NULL, windowPos.x, windowPos.y, windowSize.cx, windowSize.cy, SWP_NOMOVE | SWP_NOZORDER);
return true;
}
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;
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);
}
static void *WglGetProcAddress(const char *procname)
{
void *proc = (void *)wglGetProcAddress(procname);
if ((proc == NULL) ||
(proc == (void *)0x1) ||
(proc == (void *)0x2) ||
(proc == (void *)0x3) ||
(proc == (void *)-1))
{
HMODULE glModule = LoadLibraryW(L"opengl32.dll");
proc = (void *)GetProcAddress(glModule, procname);
}
return proc;
}
static KeyboardKey GetKeyFromWparam(WPARAM wparam)
{
switch (wparam)
{
case VK_RETURN: return KEY_ENTER;
case VK_ESCAPE: return KEY_ESCAPE;
case VK_SPACE: return KEY_SPACE;
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 '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 '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;
default: return KEY_NULL;
}
}
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;
}
static BOOL CALLBACK CountMonitorsProc(HMONITOR handle, HDC hdc, LPRECT rect, LPARAM lparam)
{
int *count = (int *)lparam;
*count += 1;
return TRUE;
}
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;
return TRUE;
}
static void GetStyleChangeFlagOps(DWORD coreWindowFlags, STYLESTRUCT *style, FlagsOp *deferredFlags)
{
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;
}
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;
}
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;
}
}
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;
}
#undef CORE
int InitPlatform(void); void ClosePlatform(void);
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
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);
static bool IsWglExtensionAvailable(HDC hdc, const char *extension);
bool WindowShouldClose(void)
{
return CORE.Window.shouldClose;
}
void ToggleFullscreen(void)
{
TRACELOG(LOG_WARNING, "WIN32: Toggle full screen functionality not implemented");
}
void ToggleBorderlessWindowed(void)
{
if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) ClearWindowState(FLAG_BORDERLESS_WINDOWED_MODE);
else SetWindowState(FLAG_BORDERLESS_WINDOWED_MODE);
}
void MaximizeWindow(void)
{
SetWindowState(FLAG_WINDOW_MAXIMIZED);
}
void MinimizeWindow(void)
{
SetWindowState(FLAG_WINDOW_MINIMIZED);
}
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);
}
void SetWindowState(unsigned int flags)
{
platform.desiredFlags = SanitizeFlags(1 , CORE.Window.flags | flags);
UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight);
}
void ClearWindowState(unsigned int flags)
{
platform.desiredFlags = SanitizeFlags(1 , CORE.Window.flags & ~flags);
UpdateFlags(platform.hwnd, platform.desiredFlags, platform.appScreenWidth, platform.appScreenHeight);
}
void SetWindowIcon(Image image)
{
if (!platform.hwnd || (image.data == NULL) || (image.width <= 0) || (image.height <= 0)) return;
HDC hdc = GetDC(platform.hwnd);
BITMAPV5HEADER bi = { 0 };
ZeroMemory(&bi, sizeof(bi));
bi.bV5Size = sizeof(bi);
bi.bV5Width = image.width;
bi.bV5Height = -image.height; 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;
}
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]; targetBits[i + 1] = ((unsigned char *)image.data)[i + 1]; targetBits[i + 2] = ((unsigned char *)image.data)[i + 0]; targetBits[i + 3] = ((unsigned char *)image.data)[i + 3]; }
}
HBITMAP hMaskBitmap = CreateBitmap(image.width, image.height, 1, 1, NULL);
ICONINFO ii = { 0 };
ZeroMemory(&ii, sizeof(ii));
ii.fIcon = TRUE;
ii.hbmMask = hMaskBitmap;
ii.hbmColor = hColorBitmap;
HICON hIcon = CreateIconIndirect(&ii);
DeleteObject(hColorBitmap);
DeleteObject(hMaskBitmap);
ReleaseDC(platform.hwnd, hdc);
if (hIcon)
{
SendMessage(platform.hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
SendMessage(platform.hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
}
}
void SetWindowIcons(Image *images, int count)
{
}
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());
}
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;
SetWindowPos(platform.hwnd, NULL, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
}
}
}
void SetWindowMonitor(int monitor)
{
TRACELOG(LOG_WARNING, "SetWindowMonitor not implemented");
}
void SetWindowMinSize(int width, int height)
{
if ((width > CORE.Window.screenMax.width) || (height > CORE.Window.screenMax.height))
{
TRACELOG(LOG_WARNING, "WIN32: WINDOW: Cannot set minimum screen size higher than the maximum");
return;
}
CORE.Window.screenMin.width = width;
CORE.Window.screenMin.height = height;
SetWindowSize(platform.appScreenWidth, platform.appScreenHeight);
}
void SetWindowMaxSize(int width, int height)
{
if ((width < CORE.Window.screenMin.width) || (height < CORE.Window.screenMin.height))
{
TRACELOG(LOG_WARNING, "WIN32: WINDOW: Cannot set maximum screen size lower than the minimum");
return;
}
CORE.Window.screenMax.width = width;
CORE.Window.screenMax.height = height;
SetWindowSize(platform.appScreenWidth, platform.appScreenHeight);
}
void SetWindowSize(int width, int height)
{
int screenWidth = fmaxf(CORE.Window.screenMin.width, fminf(CORE.Window.screenMax.width, width));
int screenHeight = fmaxf(CORE.Window.screenMin.height, fminf(CORE.Window.screenMax.height, height));
UpdateWindowSize(1, platform.hwnd, screenWidth, screenHeight, platform.desiredFlags);
}
void SetWindowOpacity(float opacity)
{
TRACELOG(LOG_WARNING, "SetWindowOpacity not implemented");
}
void SetWindowFocused(void)
{
TRACELOG(LOG_WARNING, "SetWindowFocused not implemented");
}
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;
}
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;
}
Vector2 GetMonitorPosition(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorPosition not implemented");
return (Vector2){ 0, 0 };
}
int GetMonitorWidth(int monitor)
{
return 0;
}
int GetMonitorHeight(int monitor)
{
return 0;
}
int GetMonitorPhysicalWidth(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth not implemented");
return 0;
}
int GetMonitorPhysicalHeight(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight not implemented");
return 0;
}
int GetMonitorRefreshRate(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorRefreshRate not implemented");
return 0;
}
const char *GetMonitorName(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorName not implemented");
return 0;
}
Vector2 GetWindowPosition(void)
{
return (Vector2){ 0, 0 };
}
Vector2 GetWindowScaleDPI(void)
{
float scale = ((float)GetDpiForWindow(platform.hwnd))/96.0f;
return (Vector2){ scale, scale };
}
void SetClipboardText(const char *text)
{
TRACELOG(LOG_WARNING, "SetClipboardText not implemented");
}
const char *GetClipboardText(void)
{
TRACELOG(LOG_WARNING, "GetClipboardText not implemented");
return NULL;
}
Image GetClipboardImage(void)
{
Image image = { 0 };
TRACELOG(LOG_WARNING, "GetClipboardText not implemented");
return image;
}
void ShowCursor(void)
{
SetCursor(LoadCursorW(NULL, (LPCWSTR)IDC_ARROW));
CORE.Input.Mouse.cursorHidden = false;
}
void HideCursor(void)
{
SetCursor(NULL);
CORE.Input.Mouse.cursorHidden = true;
}
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; rid.usUsage = 0x02; rid.dwFlags = RIDEV_REMOVE; 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;
}
}
void DisableCursor(void)
{
if (!CORE.Input.Mouse.cursorLocked)
{
RAWINPUTDEVICE rid = { 0 };
rid.usUsagePage = 0x01; rid.usUsage = 0x02; rid.dwFlags = RIDEV_INPUTSINK; 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;
}
}
void SwapScreenBuffer(void)
{
if (!platform.hdc) abort();
#if defined(GRAPHICS_API_OPENGL_SOFTWARE)
rlCopyFramebuffer(0, 0, CORE.Window.render.width, CORE.Window.render.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, platform.pixels);
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
}
double GetTime(void)
{
double time = 0.0;
LARGE_INTEGER now = { 0 };
QueryPerformanceCounter(&now);
time = (double)(now.QuadPart - CORE.Time.base)/(double)platform.timerFrequency.QuadPart;
return time;
}
void OpenURL(const char *url)
{
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);
}
}
int SetGamepadMappings(const char *mappings)
{
TRACELOG(LOG_WARNING, "SetGamepadMappings not implemented");
return -1;
}
void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration)
{
TRACELOG(LOG_WARNING, "SetGamepadVibration not implemented");
}
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");
}
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;
}
const char *GetKeyName(int key)
{
TRACELOG(LOG_WARNING, "GetKeyName not implemented");
return NULL;
}
void PollInputEvents(void)
{
CORE.Input.Keyboard.keyPressedQueueCount = 0;
CORE.Input.Keyboard.charPressedQueueCount = 0;
for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0;
CORE.Input.Gamepad.lastButtonPressed = 0;
for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i];
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));
CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove;
CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f };
CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
MSG msg = { 0 };
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
HGLRC InitOpenGL(HWND hwnd, HDC hdc)
{
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);
HGLRC tempContext = wglCreateContext(hdc);
BOOL result = wglMakeCurrent(hdc, tempContext);
wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress("wglGetExtensionsStringARB");
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_DEPTH_BITS_ARB, 24,
WGL_STENCIL_BITS_ARB, 8,
0 };
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);
}
}
HGLRC realContext = NULL;
if (wglCreateContextAttribsARB)
{
int glContextVersionMajor = 1;
int glContextVersionMinor = 1;
int glContextProfile = WGL_CONTEXT_CORE_PROFILE_BIT_ARB;
if (rlGetVersion() == RL_OPENGL_21) {
glContextVersionMajor = 2;
glContextVersionMinor = 1;
}
else if (rlGetVersion() == RL_OPENGL_33) {
glContextVersionMajor = 3;
glContextVersionMinor = 3;
}
else if (rlGetVersion() == RL_OPENGL_43) {
glContextVersionMajor = 4;
glContextVersionMinor = 3;
}
else if (rlGetVersion() == RL_OPENGL_ES_20) {
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) {
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, 0 };
realContext = wglCreateContextAttribsARB(hdc, NULL, contextAttribs);
if (realContext == NULL) TRACELOG(LOG_ERROR, "GL: Error creating requested context: %lu", GetLastError());
}
wglMakeCurrent(NULL, NULL);
wglDeleteContext(tempContext);
if (realContext) wglMakeCurrent(hdc, realContext);
rlLoadExtensions(WglGetProcAddress);
return realContext;
}
int InitPlatform(void)
{
int result = 0;
platform.appScreenWidth = CORE.Window.screen.width;
platform.appScreenHeight = CORE.Window.screen.height;
platform.desiredFlags = SanitizeFlags(0 , CORE.Window.flags);
CORE.Window.flags = FLAG_WINDOW_HIDDEN | (platform.desiredFlags & FLAG_MASK_NO_UPDATE);
CORE.Window.screenMax.width = 9999;
CORE.Window.screenMax.height = 9999;
HINSTANCE hInstance = GetModuleHandleW(0);
WNDCLASSEXW windowClass = {
.cbSize = sizeof(WNDCLASSEXW),
.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
.lpfnWndProc = WndProc, .cbWndExtra = sizeof(LONG_PTR), .hInstance = hInstance,
.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW), .lpszClassName = CLASS_NAME };
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);
result = (int)RegisterClassExW(&windowClass);
if (result == 0) TRACELOG(LOG_ERROR, "WIN32: Failed to register window class [ERROR: %lu]", GetLastError());
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());
DWORD style = WS_OVERLAPPEDWINDOW;
RECT rect = { 0, 0, platform.appScreenWidth, platform.appScreenHeight };
AdjustWindowRect(&rect, style, FALSE);
int windowWidth = rect.right - rect.left;
int windowHeight = rect.bottom - rect.top;
WCHAR *titleWide = NULL;
A_TO_W_ALLOCA(titleWide, CORE.Window.title);
platform.hwnd = CreateWindowExW(
WINDOW_STYLE_EX,
CLASS_NAME,
titleWide,
MakeWindowStyle(CORE.Window.flags), CW_USEDEFAULT, CW_USEDEFAULT,
windowWidth, windowHeight, NULL, NULL,
GetModuleHandleW(NULL), NULL);
if (!platform.hwnd)
{
TRACELOG(LOG_ERROR, "WIN32: WINDOW: Failed to create window [ERROR: %lu]", GetLastError());
return -1;
}
platform.hdc = GetDC(platform.hwnd);
if (rlGetVersion() == RL_OPENGL_SOFTWARE) {
BITMAPINFO bmi = { 0 };
ZeroMemory(&bmi, sizeof(bmi));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = platform.appScreenWidth;
bmi.bmiHeader.biHeight = -(int)(platform.appScreenHeight); bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32; 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);
}
else
{
platform.glContext = InitOpenGL(platform.hwnd, platform.hdc);
}
CORE.Window.ready = true;
ShowWindow(platform.hwnd, SW_SHOWDEFAULT);
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 %s",
FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)? "(HighDPI)" : "");
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_SOFTWARE) {
TRACELOG(LOG_INFO, "GL: OpenGL device information:");
TRACELOG(LOG_INFO, " > Vendor: %s", glGetString(GL_VENDOR));
TRACELOG(LOG_INFO, " > Renderer: %s", glGetString(GL_RENDERER));
TRACELOG(LOG_INFO, " > Version: %s", glGetString(GL_VERSION));
TRACELOG(LOG_INFO, " > GLSL: %s", "NOT SUPPORTED");
}
LARGE_INTEGER time = { 0 };
QueryPerformanceCounter(&time);
QueryPerformanceFrequency(&platform.timerFrequency);
CORE.Time.base = time.QuadPart;
InitTimer();
CORE.Storage.basePath = GetWorkingDirectory();
TRACELOG(LOG_INFO, "PLATFORM: DESKTOP: WIN32: Initialized successfully");
return 0;
}
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;
}
}
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
LRESULT result = 0;
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;
switch (msg)
{
case WM_CREATE:
{
} break;
case WM_DESTROY:
{
if (rlGetVersion() == RL_OPENGL_SOFTWARE) {
if (platform.hdcmem)
{
DeleteDC(platform.hdcmem);
platform.hdcmem = NULL;
}
if (platform.hbitmap)
{
DeleteObject(platform.hbitmap); platform.hbitmap = NULL;
platform.pixels = NULL; }
}
else {
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; 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)
{
}
result = TRUE;
} break;
case WM_SIZE:
{
#if defined(GRAPHICS_API_OPENGL_SOFTWARE)
if (CORE.Time.frameCounter > 2) HandleWindowResize(hwnd, &platform.appScreenWidth, &platform.appScreenHeight);
#else
HandleWindowResize(hwnd, &platform.appScreenWidth, &platform.appScreenHeight);
#endif
result = 0; } break;
case WM_GETMINMAXINFO:
{
DWORD style = MakeWindowStyle(platform.desiredFlags);
SIZE maxClientSize = { CORE.Window.screenMax.width, CORE.Window.screenMax.height };
SIZE maxWindowSize = CalcWindowSize(96, maxClientSize, style);
SIZE minClientSize = { CORE.Window.screenMin.width, CORE.Window.screenMin.height };
SIZE minWindowSize = CalcWindowSize(96, minClientSize, style);
LPMINMAXINFO lpmmi = (LPMINMAXINFO) lparam;
lpmmi->ptMaxSize.x = maxWindowSize.cx;
lpmmi->ptMaxSize.y = maxWindowSize.cy;
lpmmi->ptMaxTrackSize.x = maxWindowSize.cx;
lpmmi->ptMaxTrackSize.y = maxWindowSize.cy;
lpmmi->ptMinTrackSize.x = minWindowSize.cx;
lpmmi->ptMinTrackSize.y = minWindowSize.cy;
} break;
case WM_STYLECHANGING:
{
if (wparam == GWL_STYLE)
{
STYLESTRUCT *ss = (STYLESTRUCT *)lparam;
GetStyleChangeFlagOps(CORE.Window.flags, ss, deferredFlags);
UINT dpi = GetDpiForWindow(hwnd);
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)
{
TRACELOG(LOG_INFO, "WIN32: WINDOW: Style change modified 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:
{
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_WINDOWPOSCHANGED:
{
WINDOWPOS *pos = (WINDOWPOS*)lparam;
if (!(pos->flags & SWP_NOSIZE)) HandleWindowResize(hwnd, &platform.appScreenWidth, &platform.appScreenHeight);
DefWindowProc(hwnd, msg, wparam, lparam);
} break;
case WM_GETDPISCALEDSIZE:
{
SIZE *inoutSize = (SIZE *)lparam;
UINT newDpi = (UINT)wparam;
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;
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:
{
float scalex = HIWORD(wparam)/96.0f;
float scaley = LOWORD(wparam)/96.0f;
RECT *suggestedRect = (RECT *)lparam;
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:
{
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_SOFTWARE) {
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
BitBlt(hdc, 0, 0, platform.appScreenWidth, platform.appScreenHeight, platform.hdcmem, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
}
else DefWindowProc(hwnd, msg, wparam, lparam);
}
case WM_INPUT:
{
} 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;
default: result = DefWindowProcW(hwnd, msg, wparam, lparam); }
if (platform.hwnd == hwnd) CheckFlags("After WndProc", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), mask);
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;
}
static void HandleKey(WPARAM wparam, LPARAM lparam, char state)
{
KeyboardKey key = GetKeyFromWparam(wparam);
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);
}
static void HandleMouseButton(int button, char state)
{
CORE.Input.Mouse.currentButtonState[button] = state;
CORE.Input.Touch.currentTouchState[button] = state;
}
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!");
}
static void HandleWindowResize(HWND hwnd, int *width, int *height)
{
if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) return;
RECT rect = { 0 };
GetClientRect(hwnd, &rect);
SIZE clientSize = { rect.right, rect.bottom };
CORE.Window.currentFbo.width = (int)clientSize.cx;
CORE.Window.currentFbo.height = (int)clientSize.cy;
SetupViewport(clientSize.cx, clientSize.cy);
CORE.Window.resizedLastFrame = true;
float dpiScale = ((float)GetDpiForWindow(hwnd))/96.0f;
bool highdpi = !!(CORE.Window.flags & FLAG_WINDOW_HIGHDPI);
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);
#if defined(GRAPHICS_API_OPENGL_SOFTWARE)
swResize(clientSize.cx, clientSize.cy);
#endif
}
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);
}
Mized currentMized = MIZED_NONE;
Mized desiredMized = MIZED_NONE;
if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) currentMized = MIZED_MIN;
else if (CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) currentMized = MIZED_MAX;
if (desiredFlags & FLAG_WINDOW_MINIMIZED) desiredMized = MIZED_MIN;
else if (desiredFlags & FLAG_WINDOW_MAXIMIZED) desiredMized = MIZED_MAX;
if (currentMized != desiredMized)
{
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;
}
}
}
static unsigned SanitizeFlags(int mode, unsigned flags)
{
if (flags & FLAG_WINDOW_MAXIMIZED)
{
if (flags & FLAG_BORDERLESS_WINDOWED_MODE)
{
TRACELOG(LOG_WARNING, "WIN32: WINDOW: Borderless windows mode overriding maximized window flag");
flags &= ~FLAG_WINDOW_MAXIMIZED;
}
if (~flags & FLAG_WINDOW_RESIZABLE)
{
if (!(CORE.Window.flags & FLAG_WINDOW_MAXIMIZED))
{
TRACELOG(LOG_WARNING, "WIN32: WINDOW: Cannot maximize a non-resizable window");
flags &= ~FLAG_WINDOW_MAXIMIZED;
}
else if (CORE.Window.flags & FLAG_WINDOW_RESIZABLE)
{
TRACELOG(LOG_WARNING, "WIN32: WINDOW: Cannot set window as non-resizable when maximized");
flags |= FLAG_WINDOW_RESIZABLE;
}
}
else if (!(CORE.Window.flags & FLAG_WINDOW_MAXIMIZED))
{
if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED)
{
flags &= ~FLAG_WINDOW_MINIMIZED;
}
else if ((flags & FLAG_WINDOW_MINIMIZED) && !(CORE.Window.flags & FLAG_WINDOW_MINIMIZED))
{
TRACELOG(LOG_WARNING, "WIN32: WINDOW: Cannot minimize and maximize a window in the same frame");
flags &= ~FLAG_WINDOW_MINIMIZED;
flags &= ~FLAG_WINDOW_MAXIMIZED;
}
}
}
if (mode == 1)
{
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;
}
static void UpdateFlags(HWND hwnd, unsigned desiredFlags, int width, int height)
{
CORE.Window.flags |= (desiredFlags & FLAG_MASK_NO_UPDATE);
int vsync = (desiredFlags & FLAG_VSYNC_HINT)? 1 : 0;
if (wglSwapIntervalEXT)
{
wglSwapIntervalEXT(vsync);
if (vsync) CORE.Window.flags |= FLAG_VSYNC_HINT;
else CORE.Window.flags &= ~FLAG_VSYNC_HINT;
}
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);
}
}
static bool IsWglExtensionAvailable(HDC hdc, const char *extension)
{
bool result = false;
if (wglGetExtensionsStringARB != NULL)
{
const char *extList = wglGetExtensionsStringARB(hdc);
if (extList != NULL)
{
if (strstr(extList, extension) != NULL) result = true;
}
}
return result;
}