diff --git a/build.zig b/build.zig index b3da659848f8..39e7593f5c0d 100644 --- a/build.zig +++ b/build.zig @@ -17,7 +17,8 @@ fn setDesktopPlatform(raylib: *std.Build.Step.Compile, platform: PlatformBackend .rgfw => raylib.root_module.addCMacro("PLATFORM_DESKTOP_RGFW", ""), .sdl => raylib.root_module.addCMacro("PLATFORM_DESKTOP_SDL", ""), .android => raylib.root_module.addCMacro("PLATFORM_ANDROID", ""), - else => {}, + .drm => {}, + .win32 => raylib.root_module.addCMacro("PLATFORM_DESKTOP_WIN32", ""), } } @@ -180,11 +181,12 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. raylib.addIncludePath(b.path("src/platforms")); switch (target.result.os.tag) { .windows => { + try raylib_flags_arr.append("-DUNICODE"); switch (options.platform) { .glfw => try c_source_files.append("src/rglfw.c"), - .rgfw, .sdl, .drm, .android => {}, + .rgfw, .sdl, .drm, .android, .win32 => {}, } - + raylib.linkSystemLibrary("shcore"); raylib.linkSystemLibrary("winmm"); raylib.linkSystemLibrary("gdi32"); raylib.linkSystemLibrary("opengl32"); @@ -440,6 +442,7 @@ pub const PlatformBackend = enum { sdl, drm, android, + win32, }; pub fn build(b: *std.Build) !void { diff --git a/src/Makefile b/src/Makefile index 833f955a94ed..ef00011860aa 100644 --- a/src/Makefile +++ b/src/Makefile @@ -342,6 +342,10 @@ ifeq ($(PLATFORM_OS), LINUX) CFLAGS += -fPIC endif +ifeq ($(PLATFORM_OS),WINDOWS) + CFLAGS += -DUNICODE +endif + ifeq ($(RAYLIB_BUILD_MODE),DEBUG) CFLAGS += -g -D_DEBUG endif @@ -628,6 +632,9 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_DRM) LDLIBS += -latomic endif endif +ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_WIN32) + LDLIBS = -lgdi32 -lwinmm -lopengl32 -lshcore +endif ifeq ($(TARGET_PLATFORM),PLATFORM_ANDROID) LDLIBS = -llog -landroid -lEGL -lGLESv2 -lOpenSLES -lc -lm endif diff --git a/src/platforms/rcore_desktop_win32.c b/src/platforms/rcore_desktop_win32.c new file mode 100644 index 000000000000..a79fa692e96b --- /dev/null +++ b/src/platforms/rcore_desktop_win32.c @@ -0,0 +1,2183 @@ +/********************************************************************************************** +* +* rcore_desktop_win32 - Functions to manage window, graphics device and inputs +* +* PLATFORM: DESKTOP: WIN32 +* - Windows (Win32, Win64) +* +* LIMITATIONS: +* - currently in initial development stage, alot is missing +* - unsure how to support MOUSE_BUTTON_FORWARD/MOUSE_BUTTON_BACK +* +* POSSIBLE IMPROVEMENTS: +* +* ADDITIONAL NOTES: +* +* CONFIGURATION: +* #define RCORE_PLATFORM_CUSTOM_FLAG +* Custom flag for rcore on target platform -not used- +* +* DEPENDENCIES: +* - the win32 API, i.e. 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 functions to new names to avoid redefining the same functions as raylib +#define CloseWindow CloseWindowWin32 +#define Rectangle RectangleWin32 +#define ShowCursor ShowCursorWin32 +#define LoadImageA LoadImageAWin32 +#define LoadImageW LoadImageWin32 +#define DrawTextA DrawTextAWin32 +#define DrawTextW DrawTextWin32 +#define DrawTextExA DrawTextExAWin32 +#define DrawTextExW DrawTextExWin32 + +#define WIN32_LEAN_AND_MEAN +#include + +#undef CloseWindow +#undef Rectangle +#undef ShowCursor +#undef LoadImage +#undef LoadImageA +#undef LoadImageW +#undef DrawText +#undef DrawTextA +#undef DrawTextW +#undef DrawTextEx +#undef DrawTextExA +#undef DrawTextExW + +#include +#include +#include + +#include +#include + +// -------------------------------------------------------------------------------- +// 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 + +static size_t AToWLen(const char *a) +{ + int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, a, -1, NULL, 0); + if (sizeNeeded < 0) + { + TRACELOG(LOG_ERROR, "failed to calculate wide length, result=%d, error=%u", sizeNeeded, GetLastError()); + abort(); + } + + return sizeNeeded; +} +static void AToWCopy(wchar_t *outPtr, size_t outLen, const char *a) +{ + int size = MultiByteToWideChar(CP_UTF8, 0, a, -1, outPtr, outLen); + if (size != outLen) + { + TRACELOG(LOG_ERROR, "expected to convert %zu utf8 chars to wide but converted %zu", outLen, size); + abort(); + } +} +#define A_TO_W_ALLOCA(outWstr, inAnsi) do { \ + size_t len = AToWLen(inAnsi); \ + outWstr = (WCHAR*)alloca(sizeof(WCHAR)*(len + 1)); \ + AToWCopy(outWstr, len, inAnsi); \ + outWstr[len] = 0; \ +} while (0) + +static void LogFail(const char *what, DWORD error) +{ + TRACELOG(LOG_ERROR, "%s failed, error=%lu", what, error); +} +static void LogFailHr(const char *what, HRESULT hr) +{ + TRACELOG(LOG_ERROR, "%s failed, hresult=0x%lx", what, (DWORD)hr); +} + +#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 + +static bool ResizableFromStyle(DWORD style) +{ + return 0 != (style & STYLE_FLAGS_RESIZABLE); +} +static bool DecoratedFromStyle(DWORD style) +{ + if (style & STYLE_FLAGS_UNDECORATED_ON) + { + if (style & STYLE_FLAGS_UNDECORATED_OFF) + { + TRACELOG(LOG_ERROR, "style 0x%x has both undecorated on/off flags", style); + abort(); + } + + return false; // not decorated + } + + DWORD masked = (style & STYLE_FLAGS_UNDECORATED_OFF); + if (STYLE_FLAGS_UNDECORATED_OFF != masked) + { + TRACELOG(LOG_ERROR, "style 0x%x is missing these flags 0x%x", masked, masked ^ STYLE_FLAGS_UNDECORATED_OFF); + abort(); + } + + return true; // decorated +} +static bool HiddenFromStyle(DWORD style) +{ + return 0 == (style & WS_VISIBLE); +} + +typedef enum { MIZED_NONE, MIZED_MIN, MIZED_MAX } Mized; +Mized MizedFromStyle(DWORD style) +{ + // minimized takes precedence over maximized + if (style & WS_MINIMIZE) return MIZED_MIN; + if (style & WS_MAXIMIZE) return MIZED_MAX; + return MIZED_NONE; +} +Mized MizedFromFlags(unsigned flags) +{ + // minimized takes precedence over maximized + if (flags & FLAG_WINDOW_MINIMIZED) return MIZED_MIN; + if (flags & FLAG_WINDOW_MAXIMIZED) return MIZED_MAX; + return MIZED_NONE; +} + +static DWORD MakeWindowStyle(unsigned flags) +{ + DWORD style = + // we don't need this since we don't have any child windows, but I guess + // it improves efficiency, plus, windows adds this flag automatically anyway + // so it keeps our flags in sync with the OS. + 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; + + switch (MizedFromFlags(flags)) + { + case MIZED_NONE: break; + case MIZED_MIN: style |= WS_MINIMIZE; break; + case MIZED_MAX: style |= WS_MAXIMIZE; break; + default: abort(); + } + + // sanity checks, maybe remove later + if (ResizableFromStyle(style) != !!(flags & FLAG_WINDOW_RESIZABLE)) abort(); + if (DecoratedFromStyle(style) != !(flags & FLAG_WINDOW_UNDECORATED)) abort(); + if (HiddenFromStyle(style) != !!(flags & FLAG_WINDOW_HIDDEN)) abort(); + if (MizedFromStyle(style) != MizedFromFlags(flags)) abort(); + + return style; +} + +static bool IsMinimized2(HWND hwnd) +{ + bool isIconic = IsIconic(hwnd); + bool styleMinimized = !!(WS_MINIMIZE & GetWindowLongPtrW(hwnd, GWL_STYLE)); + if (isIconic != styleMinimized) + { + TRACELOG(LOG_ERROR, "IsIconic(%d) != WS_MINIMIZED(%d)", isIconic, styleMinimized); + abort(); + } + + return isIconic; +} + +#define STYLE_MASK_ALL 0xffffffff +#define STYLE_MASK_READONLY (WS_MINIMIZE | WS_MAXIMIZE) +#define STYLE_MASK_WRITABLE (~STYLE_MASK_READONLY) + +// 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 +) { + //TRACELOG(LOG_INFO, "Verifying Flags 0x%x Style 0x%x Mask 0x%x", flags, expectedStyle & styleCheckMask, styleCheckMask); + + { + DWORD styleFromFlags = MakeWindowStyle(flags); + if ((styleFromFlags & styleCheckMask) != (expectedStyle & styleCheckMask)) + { + TRACELOG( + LOG_ERROR, + "%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 + ); + abort(); + } + } + + SetLastError(0); + LONG actualStyle = GetWindowLongPtrW(hwnd, GWL_STYLE); + if ((actualStyle & styleCheckMask) != (expectedStyle & styleCheckMask)) + { + TRACELOG( + LOG_ERROR, + "%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() + ); + abort(); + } + + if (styleCheckMask & WS_MINIMIZE) + { + bool isIconic = IsIconic(hwnd); + bool styleMinimized = !!(WS_MINIMIZE & actualStyle); + if (isIconic != styleMinimized) { + TRACELOG(LOG_ERROR, "IsIconic(%d) != WS_MINIMIZED(%d)", isIconic, styleMinimized); + abort(); + } + } + + if (styleCheckMask & WS_MAXIMIZE) + { + WINDOWPLACEMENT placement; + placement.length = sizeof(placement); + if (!GetWindowPlacement(hwnd, &placement)) { + LogFail("GetWindowPlacement", GetLastError()); + abort(); + } + bool placementMaximized = (placement.showCmd == SW_SHOWMAXIMIZED); + bool styleMaximized = WS_MAXIMIZE & actualStyle; + if (placementMaximized != styleMaximized) + { + TRACELOG( + LOG_ERROR, + "maximized state desync, placement maximized=%d (showCmd=%lu) style maximized=%d", + placementMaximized, + placement.showCmd, + styleMaximized + ); + abort(); + } + } +} + +static float PtFromPx(float dpiScale, bool highdpiEnabled, int pt) +{ + return highdpiEnabled? (((float)pt)/dpiScale) : pt; +} +static int PxFromPt(float dpiScale, bool highdpiEnabled, float pt) +{ + return highdpiEnabled? roundf(pt*dpiScale) : roundf(pt); +} +static SIZE PxFromPt2(float dpiScale, bool highdpiEnabled, Vector2 screenSize) +{ + return (SIZE){ + PxFromPt(dpiScale, highdpiEnabled, screenSize.x), + PxFromPt(dpiScale, highdpiEnabled, screenSize.y), + }; +} + +static SIZE GetClientSize(HWND hwnd) +{ + RECT rect; + if (0 == GetClientRect(hwnd, &rect)) + { + LogFail("GetClientRect", GetLastError()); + abort(); + } + + if (rect.left != 0) abort(); // never happens AFAIK + if (rect.top != 0) abort(); // never happens AFAIK + return (SIZE){ rect.right, rect.bottom }; +} + +static UINT GetWindowDpi(HWND hwnd) +{ + UINT dpi = GetDpiForWindow(hwnd); + if (dpi == 0) + { + LogFail("GetWindowDpi", GetLastError()); + abort(); + } + + return dpi; +} + +static float ScaleFromDpi(UINT dpi) +{ + return ((float)dpi)/96.0f; +} + +#define WINDOW_STYLE_EX 0 + +static SIZE CalcWindowSize(UINT dpi, SIZE clientSize, DWORD style) +{ + RECT rect = { 0, 0, clientSize.cx, clientSize.cy }; + if (!AdjustWindowRectExForDpi(&rect, style, 0, WINDOW_STYLE_EX, dpi)) + { + LogFail("AdjustWindowRect", GetLastError()); + abort(); + } + + return (SIZE){ rect.right - rect.left, rect.bottom - rect.top }; +} + +typedef enum { + UPDATE_WINDOW_FIRST, + UPDATE_WINDOW_NORMAL, +} UpdateWindowKind; + +// returns true if the window size was updated, false otherwise +static bool UpdateWindowSize( + UpdateWindowKind kind, + HWND hwnd, + Vector2 appScreenSize, + 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) + { + HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); + MONITORINFO info; + info.cbSize = sizeof(info); + if (!GetMonitorInfoW(monitor, &info)) + { + LogFail("GetMonitorInfo", GetLastError()); + abort(); + } + + RECT windowRect; + if (!GetWindowRect(hwnd, &windowRect)) + { + LogFail("GetWindowRect", GetLastError()); + abort(); + } + + 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 + )) { + LogFail("SetWindowPos", GetLastError()); + abort(); + } + + return true; + } + + UINT dpi = GetWindowDpi(hwnd); + float dpiScale = ScaleFromDpi(dpi); + bool dpiScaling = flags & FLAG_WINDOW_HIGHDPI; + SIZE desired = PxFromPt2(dpiScale, dpiScaling, appScreenSize); + SIZE actual = GetClientSize(hwnd); + if (actual.cx == desired.cx || actual.cy == desired.cy) + return false; + + TRACELOG( + LOG_INFO, + "restoring client size from %dx%d to %dx%d (dpi=%lu dpiScaling=%d app=%fx%f)", + actual.cx, actual.cy, + desired.cx, desired.cy, + dpi, dpiScaling, + appScreenSize.x, appScreenSize.y + ); + SIZE windowSize = CalcWindowSize(dpi, desired, MakeWindowStyle(flags)); + POINT windowPos = (POINT){ 0, 0 }; + UINT swpFlags = SWP_NOZORDER | SWP_FRAMECHANGED; + if (kind == UPDATE_WINDOW_FIRST) + { + HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); + if (!monitor) + { + LogFail("MonitorFromWindow", GetLastError()); + abort(); + } + + MONITORINFO info; + info.cbSize = sizeof(info); + if (!GetMonitorInfoW(monitor, &info)) + { + LogFail("GetMonitorInfo", GetLastError()); + abort(); + } + + 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; + } + + if (!SetWindowPos( + hwnd, NULL, + windowPos.x, windowPos.y, + windowSize.cx, windowSize.cy, + swpFlags + )) { + LogFail("SetWindowPos", GetLastError()); + abort(); + } + + return true; +} + +#define CLASS_NAME L"RaylibWindow" + +static void CreateWindowAlloca(const char *title, DWORD style) +{ + WCHAR *titleWide; + A_TO_W_ALLOCA(titleWide, title); + CreateWindowExW( + WINDOW_STYLE_EX, + CLASS_NAME, + titleWide, + style, + 0, 0, + 0, 0, + NULL, + NULL, + GetModuleHandleW(NULL), + NULL + ); +} + +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) + { + LogFail("GetProcAddress 'RtlVerifyVersionInfo'", 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); +} + +static void *FindProc(const char *name) +{ + { + void *proc = wglGetProcAddress(name); + if (proc) return proc; + } + + static HMODULE opengl = NULL; + if (!opengl) + { + opengl = LoadLibraryW(L"opengl32"); + } + if (opengl) + { + void *proc = GetProcAddress(opengl, name); + if (proc) return proc; + } + + return NULL; +} +static void *WglGetProcAddress(const char *name) +{ + void *result = FindProc(name); + if (result) + { + //TRACELOG(LOG_DEBUG, "GetProcAddress '%s' > %p", name, result); + } + else + { + TRACELOG(LOG_ERROR, "GetProcAddress '%s' > %p failed, error=%u", name, result, GetLastError()); + } + + return result; +} + +static KeyboardKey KeyFromWparam(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; + } +} + +static LPCWSTR GetCursorName(int cursor) +{ + switch (cursor) + { + case MOUSE_CURSOR_DEFAULT : return (LPCWSTR)IDC_ARROW; + case MOUSE_CURSOR_ARROW : return (LPCWSTR)IDC_ARROW; + case MOUSE_CURSOR_IBEAM : return (LPCWSTR)IDC_IBEAM; + case MOUSE_CURSOR_CROSSHAIR : return (LPCWSTR)IDC_CROSS; + case MOUSE_CURSOR_POINTING_HAND: return (LPCWSTR)IDC_HAND; + case MOUSE_CURSOR_RESIZE_EW : return (LPCWSTR)IDC_SIZEWE; + case MOUSE_CURSOR_RESIZE_NS : return (LPCWSTR)IDC_SIZENS; + case MOUSE_CURSOR_RESIZE_NWSE : return (LPCWSTR)IDC_SIZENWSE; + case MOUSE_CURSOR_RESIZE_NESW : return (LPCWSTR)IDC_SIZENESW; + case MOUSE_CURSOR_RESIZE_ALL : return (LPCWSTR)IDC_SIZEALL; + case MOUSE_CURSOR_NOT_ALLOWED : return (LPCWSTR)IDC_NO; + default: abort(); + } +} + + +static BOOL CALLBACK CountMonitorsProc(HMONITOR handle, 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; +} + +typedef struct { + HMONITOR needle; + int index; + int matchIndex; + RECT rect; +} FindMonitorContext; + +static BOOL CALLBACK FindMonitorProc(HMONITOR handle, HDC _, LPRECT rect, LPARAM lparam) +{ + FindMonitorContext *c = (FindMonitorContext*)lparam; + if (handle == c->needle) + { + c->matchIndex = c->index; + c->rect = *rect; + } + + c->index += 1; + // always return TRUE to continue the loop, otherwise, the caller + // can't distinguish between stopping the loop and an error + return TRUE; +} + +typedef struct { + DWORD set; + DWORD clear; +} FlagsOp; + +static void GetStyleChangeFlagOps( + DWORD coreWindowFlags, + STYLESTRUCT *ss, + FlagsOp *deferredFlags +) { + { + bool resizable = (coreWindowFlags & FLAG_WINDOW_RESIZABLE); + bool resizableOld = ResizableFromStyle(ss->styleOld); + bool resizableNew = ResizableFromStyle(ss->styleNew); + if (resizable != resizableOld) + { + TRACELOG(LOG_ERROR, "expected resizable %u but got %u", resizable, resizableOld); + abort(); + } + + if (resizableOld != resizableNew) + { + //TRACELOG(LOG_INFO, "resizable = %u", resizableNew); + if (resizableNew) + { + deferredFlags->set |= FLAG_WINDOW_RESIZABLE; + } + else + { + deferredFlags->clear |= FLAG_WINDOW_RESIZABLE; + } + } + } + + { + bool decorated = (0 == (coreWindowFlags & FLAG_WINDOW_UNDECORATED)); + bool decoratedOld = DecoratedFromStyle(ss->styleOld); + bool decoratedNew = DecoratedFromStyle(ss->styleNew); + if (decorated != decoratedOld) + { + TRACELOG(LOG_ERROR, "expected decorated %u but got %u", decorated, decoratedOld); + abort(); + } + + if (decoratedOld != decoratedNew) + { + //TRACELOG(LOG_INFO, "decorated = %u", decoratedNew); + if (decoratedNew) + { + deferredFlags->clear |= FLAG_WINDOW_UNDECORATED; + } + else + { + deferredFlags->set |= FLAG_WINDOW_UNDECORATED; + } + } + } + + { + bool hidden = (coreWindowFlags & FLAG_WINDOW_HIDDEN); + bool hiddenOld = HiddenFromStyle(ss->styleOld); + bool hiddenNew = HiddenFromStyle(ss->styleNew); + if (hidden != hiddenOld) + { + TRACELOG(LOG_ERROR, "expected hidden %u but got %u", hidden, hiddenOld); + abort(); + } + + if (hiddenOld != hiddenNew) + { + TRACELOG(LOG_INFO, "hidden = %u", hiddenNew); + if (hiddenNew) + { + deferredFlags->set |= FLAG_WINDOW_HIDDEN; + } + else + { + deferredFlags->clear |= FLAG_WINDOW_HIDDEN; + } + } + } +} + +// 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 + +// 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. +static Vector2 globalAppScreenSize = (Vector2){0, 0}; +static unsigned globalDesiredFlags = 0; +static HWND globalHwnd = NULL; +static HDC globalHdc = NULL; +static HGLRC globalGlContext = NULL; +static LARGE_INTEGER globalTimerFrequency; +static bool globalCursorEnabled = true; + +static void HandleKey(WPARAM wparam, LPARAM lparam, char state) +{ + KeyboardKey key = KeyFromWparam(wparam); + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + /* BYTE scancode = lparam >> 16; */ + /* TRACELOG(LOG_INFO, "KEY key=%d vk=%lu scan=%u = %u", key, wparam, scancode, state); */ + if (key != KEY_NULL) + { + /* TRACELOG(LOG_INFO, "KEY[%d] = %d (old=%d)\n", key, state, CORE.Input.Keyboard.currentKeyState[key]); */ + CORE.Input.Keyboard.currentKeyState[key] = state; + if (key == KEY_ESCAPE && state == 1) + { + CORE.Window.shouldClose = 1; + } + } + else + { + TRACELOG(LOG_WARNING, "unknown (or currently unhandled) virtual keycode %d (0x%x)", wparam, wparam); + } + + // TODO: add it to the queue as well? +} +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; + + { + UINT inputSize = sizeof(input); + UINT size = GetRawInputData((HRAWINPUT)lparam, RID_INPUT, &input, &inputSize, sizeof(RAWINPUTHEADER)); + if (size == (UINT)-1) { + LogFail("GetRawInputData", GetLastError()); + abort(); + } + if (size != inputSize) abort(); // bug? + } + + if (input.header.dwType != RIM_TYPEMOUSE) + { + TRACELOG(LOG_ERROR, "Unexpected WM_INPUT type %lu", input.header.dwType); + abort(); + } + + if (input.data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) + { + TRACELOG(LOG_ERROR, "TODO: handle absolute mouse inputs!"); + abort(); + } + + if (input.data.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) + { + TRACELOG(LOG_ERROR, "TODO: handle virtual desktop mouse inputs!"); + abort(); + } + + // bit of a trick, we 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(); +} + +static void HandleWindowResize(HWND hwnd, Vector2 *appScreenSizeRef) +{ + if (CORE.Window.flags & FLAG_WINDOW_MINIMIZED) + return; + + SIZE clientSize = GetClientSize(hwnd); + + // TODO: not sure if this function is doing what we need, leaving this disabled + // call to workaround unused function error + if (0) SetupFramebuffer(0, 0); + + TRACELOG(LOG_DEBUG, "NewClientSize %lux%lu", clientSize.cx, clientSize.cy); + /* CORE.Window.currentFbo.width = clientSize.cx; */ + /* CORE.Window.currentFbo.height = clientSize.cy; */ + /* glViewport(0, 0, clientSize.cx, clientSize.cy); */ + SetupViewport(clientSize.cx, clientSize.cy); + CORE.Window.resizedLastFrame = true; + float dpiScale = ScaleFromDpi(GetWindowDpi(hwnd)); + bool highdpi = !!(CORE.Window.flags & FLAG_WINDOW_HIGHDPI); + Vector2 screenSize = (Vector2){ + PtFromPx(dpiScale, highdpi, clientSize.cx), + PtFromPx(dpiScale, highdpi, clientSize.cy), + }; + CORE.Window.screen.width = roundf(screenSize.x); + CORE.Window.screen.height = roundf(screenSize.y); + if (AdoptWindowResize(CORE.Window.flags)) + { + TRACELOG( + LOG_DEBUG, + "updating app size to %fx%f from window resize", + screenSize.x, screenSize.y + ); + *appScreenSizeRef = screenSize; + } + + CORE.Window.screenScale = MatrixScale( + (float)CORE.Window.render.width/CORE.Window.screen.width, + (float)CORE.Window.render.height/CORE.Window.screen.height, + 1.0f + ); +} + +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, + "SetWindowLong returned writable flags 0x%x but expected 0x%x (diff=0x%x, error=%lu)", + previous, + current, + previous ^ current, + GetLastError() + ); + abort(); + } + + CheckFlags("UpdateWindowStyle", hwnd, desiredFlags, desired, STYLE_MASK_WRITABLE); + } + } + + Mized currentMized = MizedFromStyle(MakeWindowStyle(CORE.Window.flags)); + Mized desiredMized = MizedFromStyle(MakeWindowStyle(desiredFlags)); + 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; + } + } +} + +typedef enum { + SANITIZE_FLAGS_FIRST, + SANITIZE_FLAGS_NORMAL, +} SanitizeFlagsKind; + +static unsigned SanitizeFlags(SanitizeFlagsKind kind, unsigned flags) +{ + if ((flags & FLAG_WINDOW_MAXIMIZED) && (flags & FLAG_BORDERLESS_WINDOWED_MODE)) + { + TRACELOG(LOG_INFO, "borderless windows mode is overriding maximized"); + flags &= ~FLAG_WINDOW_MAXIMIZED; + } + + switch (kind) + { + case SANITIZE_FLAGS_FIRST: break; + case SANITIZE_FLAGS_NORMAL: + if ((flags & FLAG_MSAA_4X_HINT) && (!(CORE.Window.flags & FLAG_MSAA_4X_HINT))) + { + TRACELOG(LOG_WARNING, "WINDOW: MSAA can only be configured before window initialization"); + flags &= ~FLAG_MSAA_4X_HINT; + } + break; + } + + return flags; +} + +#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) + + +// 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, Vector2 appScreenSize) +{ + // 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; + PFNWGLSWAPINTERVALEXTPROC f = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT"); + if (f) + { + (*f)(vsync); + if (vsync) + { + CORE.Window.flags |= FLAG_VSYNC_HINT; + } + else + { + CORE.Window.flags &= ~FLAG_VSYNC_HINT; + } + } + } + + DWORD previousStyle; + 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(UPDATE_WINDOW_NORMAL, hwnd, appScreenSize, 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, + // TODO: print more information + "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 + ); + abort(); + } + + previousStyle = MakeWindowStyle(CORE.Window.flags); + UpdateWindowStyle(hwnd, desiredFlags); + } +} + +bool WindowShouldClose(void) +{ + return CORE.Window.shouldClose; +} + +void ToggleFullscreen(void) +{ + TRACELOG(LOG_WARNING, "ToggleFullscreen not implemented"); + assert(0); // crash debug builds only +} + +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); +} + +// 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) +{ + globalDesiredFlags = SanitizeFlags(SANITIZE_FLAGS_NORMAL, CORE.Window.flags | flags); + UpdateFlags(globalHwnd, globalDesiredFlags, globalAppScreenSize); +} + +// Clear window configuration state flags +void ClearWindowState(unsigned int flags) +{ + globalDesiredFlags = SanitizeFlags(SANITIZE_FLAGS_NORMAL, CORE.Window.flags & ~flags); + UpdateFlags(globalHwnd, globalDesiredFlags, globalAppScreenSize); +} + +// Set icon for window +void SetWindowIcon(Image image) +{ + TRACELOG(LOG_WARNING, "SetWindowIcon not implemented"); + assert(0); // crash debug builds only +} + +// Set icon for window +void SetWindowIcons(Image *images, int count) +{ + TRACELOG(LOG_WARNING, "SetWindowIcons not implemented"); + assert(0); // crash debug builds only +} + +void SetWindowTitle(const char *title) +{ + CORE.Window.title = title; + WCHAR *titlew; + A_TO_W_ALLOCA(titlew, CORE.Window.title); + if (!SetWindowTextW(globalHwnd, titlew)) + { + LogFail("SetWindowText", GetLastError()); + abort(); + } +} + +// Set window position on screen (windowed mode) +void SetWindowPosition(int x, int y) +{ + TRACELOG(LOG_WARNING, "SetWindowPosition not implemented"); + assert(0); // crash debug builds only +} + +// Set monitor for the current window +void SetWindowMonitor(int monitor) +{ + TRACELOG(LOG_WARNING, "SetWindowMonitor not implemented"); + assert(0); // crash debug builds only +} + +// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) +void SetWindowMinSize(int width, int height) +{ + TRACELOG(LOG_WARNING, "SetWindowMinSize not implemented"); + assert(0); // crash debug builds only + 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"); + assert(0); // crash debug builds only + 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"); + assert(0); // crash debug builds only +} + +// Set window opacity, value opacity is between 0.0 and 1.0 +void SetWindowOpacity(float opacity) +{ + TRACELOG(LOG_WARNING, "SetWindowOpacity not implemented"); + assert(0); // crash debug builds only +} + +void SetWindowFocused(void) +{ + TRACELOG(LOG_WARNING, "SetWindowFocused not implemented"); + assert(0); // crash debug builds only +} + +// Get native window handle +void *GetWindowHandle(void) +{ + return globalHwnd; +} + +int GetMonitorCount(void) +{ + int count = 0; + if (!EnumDisplayMonitors(NULL, NULL, CountMonitorsProc, (LPARAM)&count)) + { + LogFail("EnumDisplayMonitors", GetLastError()); + abort(); + } + + return count; +} + +// Get current monitor where window is placed +int GetCurrentMonitor(void) +{ + HMONITOR monitor = MonitorFromWindow(globalHwnd, MONITOR_DEFAULTTOPRIMARY); + if (!monitor) + { + LogFail("MonitorFromWindow", GetLastError()); + abort(); + } + + FindMonitorContext context; + context.needle = monitor; + context.index = 0; + context.matchIndex = -1; + if (!EnumDisplayMonitors(NULL, NULL, FindMonitorProc, (LPARAM)&context)) + { + LogFail("EnumDisplayMonitors", GetLastError()); + abort(); + } + + return context.matchIndex; +} + +// Get selected monitor position +Vector2 GetMonitorPosition(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPosition not implemented"); + assert(0); // crash debug builds only + return (Vector2){ 0, 0 }; +} + +// Get selected monitor width (currently used by monitor) +int GetMonitorWidth(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorWidth not implemented"); + assert(0); // crash debug builds only + return 0; +} + +// Get selected monitor height (currently used by monitor) +int GetMonitorHeight(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorHeight not implemented"); + assert(0); // crash debug builds only + return 0; +} + +// Get selected monitor physical width in millimetres +int GetMonitorPhysicalWidth(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth not implemented"); + assert(0); // crash debug builds only + return 0; +} + +// Get selected monitor physical height in millimetres +int GetMonitorPhysicalHeight(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight not implemented"); + assert(0); // crash debug builds only + return 0; +} + +// Get selected monitor refresh rate +int GetMonitorRefreshRate(int monitor) +{ + TRACELOG(LOG_WARNING, "GetMonitorRefreshRate not implemented"); + assert(0); // crash debug builds only + 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"); + assert(0); // crash debug builds only + return 0; +} + +// Get window position XY on monitor +Vector2 GetWindowPosition(void) +{ + TRACELOG(LOG_WARNING, "GetWindowPosition not implemented"); + assert(0); // crash debug builds only + return (Vector2){ 0, 0 }; +} + +// Get window scale DPI factor for current monitor +Vector2 GetWindowScaleDPI(void) +{ + float scale = ScaleFromDpi(GetWindowDpi(globalHwnd)); + return (Vector2){ scale, scale }; +} + +void SetClipboardText(const char *text) +{ + TRACELOG(LOG_WARNING, "SetClipboardText not implemented"); + assert(0); // crash debug builds only +} + +const char *GetClipboardText(void) +{ + TRACELOG(LOG_WARNING, "GetClipboardText not implemented"); + assert(0); // crash debug builds only + return NULL; +} + +Image GetClipboardImage(void) +{ + TRACELOG(LOG_WARNING, "GetClipboardText not implemented"); + assert(0); // crash debug builds only + Image image = { 0 }; + return image; +} + +// Show mouse cursor +void ShowCursor(void) +{ + CORE.Input.Mouse.cursorHidden = false; + SetCursor(LoadCursorW(NULL, (LPCWSTR)IDC_ARROW)); +} + +// 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. + CORE.Input.Mouse.cursorHidden = true; + SetCursor(NULL); +} + +// Enables cursor (unlock cursor) +void EnableCursor(void) +{ + if (globalCursorEnabled) + { + TRACELOG(LOG_INFO, "EnableCursor: already enabled"); + } + else + { + if (!ClipCursor(NULL)) + { + LogFail("ClipCursor", GetLastError()); + abort(); + } + + { + RAWINPUTDEVICE rid; + 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; + if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { + LogFail("RegisterRawInputDevices", GetLastError()); + abort(); + } + } + + ShowCursor(); + globalCursorEnabled = true; + TRACELOG(LOG_INFO, "EnableCursor: enabled"); + } +} + +// Disables cursor (lock cursor) +void DisableCursor(void) +{ + if (globalCursorEnabled) + { + + { + RAWINPUTDEVICE rid; + 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 = globalHwnd; + if (!RegisterRawInputDevices(&rid, 1, sizeof(rid))) { + LogFail("RegisterRawInputDevices", GetLastError()); + abort(); + } + } + + RECT clientRect; + if (!GetClientRect(globalHwnd, &clientRect)) + { + LogFail("GetClientRect", GetLastError()); + abort(); + } + + POINT topleft = { clientRect.left, clientRect.top }; + if (!ClientToScreen(globalHwnd, &topleft)) + { + LogFail("ClientToScreen", GetLastError()); + abort(); + } + + LONG width = clientRect.right - clientRect.left; + LONG height = clientRect.bottom - clientRect.top; + + TRACELOG(LOG_INFO, "ClipCursor client %d,%d %d,%d (topleft %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)) + { + LogFail("ClipCursor", GetLastError()); + abort(); + } + + CORE.Input.Mouse.previousPosition = (Vector2){ 0, 0 }; + CORE.Input.Mouse.currentPosition = (Vector2){ 0, 0 }; + HideCursor(); + + globalCursorEnabled = false; + TRACELOG(LOG_INFO, "DisableCursor: disabled"); + } + else + { + TRACELOG(LOG_INFO, "DisableCursor: already disabled"); + } +} + +void SwapScreenBuffer(void) +{ + if (!globalHdc) abort(); + if (!SwapBuffers(globalHdc)) + { + LogFail("SwapBuffers", GetLastError()); + abort(); + } + + if (!ValidateRect(globalHwnd, NULL)) + { + LogFail("ValidateRect", GetLastError()); + abort(); + } +} + +double GetTime(void) +{ + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + return (double)(now.QuadPart - CORE.Time.base)/(double)globalTimerFrequency.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 + { + TRACELOG(LOG_WARNING, "OpenURL not implemented"); + assert(0); // crash debug builds only + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition: Inputs +//---------------------------------------------------------------------------------- + +int SetGamepadMappings(const char *mappings) +{ + TRACELOG(LOG_WARNING, "SetGamepadMappings not implemented"); + assert(0); // crash debug builds only + return -1; +} + +void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration) +{ + TRACELOG(LOG_WARNING, "SetGamepadVibration not implemented"); + assert(0); // crash debug builds only +} + +void SetMousePosition(int x, int y) +{ + if (globalCursorEnabled) + { + CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; + CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; + TRACELOG(LOG_WARNING, "SetMousePosition not implemented"); + assert(0); // crash debug builds only + } + else + { + TRACELOG(LOG_ERROR, "Possible? Should we allow this?"); + abort(); + } +} + +void SetMouseCursor(int cursor) +{ + LPCWSTR cursorName = GetCursorName(cursor); + HCURSOR hcursor = LoadCursorW(NULL, cursorName); + if (!hcursor) + { + TRACELOG(LOG_ERROR, "LoadCursor %d (win32 %d) failed, error=%lu", cursor, (size_t)cursorName, GetLastError()); + abort(); + } + + SetCursor(hcursor); + CORE.Input.Mouse.cursorHidden = false; +} + +const char *GetKeyName(int key) +{ + TRACELOG(LOG_WARNING, "GetKeyName not implemented"); + assert(0); // crash debug builds only + 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; + + MSG msg; + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) + { + if (msg.message == WM_PAINT) + return; + TranslateMessage(&msg); + DispatchMessageW(&msg); + } +} + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- + +#define WM_APP_UPDATE_WINDOW_SIZE (WM_APP + 1) + +static LRESULT WndProc2(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, FlagsOp *deferredFlags) +{ + switch (msg) + { + case WM_CREATE: + { + globalHdc = GetDC(hwnd); + if (!globalHdc) + { + LogFail("GetDC", GetLastError()); + return -1; + } + + if (CORE.Window.flags & FLAG_MSAA_4X_HINT) + { + TRACELOG(LOG_ERROR, "TODO: implement FLAG_MSAA_4X_HINT"); + assert(0); + } + + PIXELFORMATDESCRIPTOR pixelFormatDesc = {0}; + pixelFormatDesc.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pixelFormatDesc.nVersion = 1; + pixelFormatDesc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL; + pixelFormatDesc.iPixelType = PFD_TYPE_RGBA; + pixelFormatDesc.cColorBits = 32; + pixelFormatDesc.cAlphaBits = 8; + pixelFormatDesc.cDepthBits = 24; + + int pixelFormat = ChoosePixelFormat(globalHdc, &pixelFormatDesc); + if (!pixelFormat) + { + LogFail("ChoosePixelFormat", GetLastError()); + return -1; + } + + if (!SetPixelFormat(globalHdc, pixelFormat, &pixelFormatDesc)) + { + LogFail("SetPixelFormat", GetLastError()); + return -1; + } + + globalGlContext = wglCreateContext(globalHdc); + if (!globalGlContext) + { + LogFail("wglCreateContext", GetLastError()); + return -1; + } + + if (!wglMakeCurrent(globalHdc, globalGlContext)) + { + LogFail("wglMakeCurrent", GetLastError()); + return -1; + } + + rlLoadExtensions(WglGetProcAddress); + + globalHwnd = hwnd; + + } return 0; + case WM_DESTROY: + wglMakeCurrent(globalHdc, NULL); + if (globalGlContext) + { + if (!wglDeleteContext(globalGlContext)) abort(); + globalGlContext = NULL; + } + + if (globalHdc) + { + if (!ReleaseDC(hwnd, globalHdc)) abort(); + globalHdc = NULL; + } + + return 0; + case WM_CLOSE: + CORE.Window.shouldClose = true; + return 0; + 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)); + return 0; + case WM_SIZING: + if (CORE.Window.flags & FLAG_WINDOW_RESIZABLE) { + // TODO: enforce min/max size + TRACELOG(LOG_WARNING, "TODO: enforce min/max size!"); + } else { + TRACELOG(LOG_WARNING, "non-resizable window is being resized"); + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + abort(); // TODO + } + return TRUE; + case WM_STYLECHANGING: + if (wparam == GWL_STYLE) + { + STYLESTRUCT *ss = (STYLESTRUCT*)lparam; + GetStyleChangeFlagOps(CORE.Window.flags, ss, deferredFlags); + + UINT dpi = GetWindowDpi(hwnd); + SIZE clientSize = GetClientSize(hwnd); + 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, + "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, "style change modifed window size, removing maximized flag"); + deferredFlags->clear |= FLAG_WINDOW_MAXIMIZED; + } + } + } + return 0; + case WM_WINDOWPOSCHANGING: + { + WINDOWPOS *pos = (WINDOWPOS*)lparam; + if (pos->flags & SWP_SHOWWINDOW) + { + if (pos->flags & SWP_HIDEWINDOW) abort(); + deferredFlags->clear |= FLAG_WINDOW_HIDDEN; + } + else if (pos->flags & SWP_HIDEWINDOW) + { + deferredFlags->set |= FLAG_WINDOW_HIDDEN; + } + + Mized mized = MIZED_NONE; + if (IsMinimized2(hwnd)) + { + mized = MIZED_MIN; + } + else + { + WINDOWPLACEMENT placement; + placement.length = sizeof(placement); + if (!GetWindowPlacement(hwnd, &placement)) + { + LogFail("GetWindowPlacement", GetLastError()); + abort(); + } + + if (placement.showCmd == SW_SHOWMAXIMIZED) { + mized = MIZED_MAX; + } + } + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + /* TRACELOG(LOG_INFO, "window pos=%d,%d size=%dx%d", pos->x, pos->y, pos->cx, pos->cy); */ + + 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)) + { + LogFail("GetMonitorInfo", GetLastError()); + abort(); + } + + 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; + } + + return 0; + } + case WM_SIZE: + // 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, &globalAppScreenSize); + return 0; + case WM_WINDOWPOSCHANGED: { + WINDOWPOS *pos = (WINDOWPOS*)lparam; + if (!(pos->flags & SWP_NOSIZE)) + { + HandleWindowResize(hwnd, &globalAppScreenSize); + } + + return 0; + } + case WM_GETDPISCALEDSIZE: + { + SIZE *inoutSize = (SIZE*)lparam; + UINT newDpi = wparam; + + // 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 = ScaleFromDpi(newDpi); + bool dpiScaling = CORE.Window.flags & FLAG_WINDOW_HIGHDPI; + SIZE desired = PxFromPt2(dpiScale, dpiScaling, globalAppScreenSize); + inoutSize->cx = desired.cx; + inoutSize->cy = desired.cy; + } return TRUE; + 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. + if (!SetWindowPos( + hwnd, + NULL, + suggestedRect->left, + suggestedRect->top, + suggestedRect->right - suggestedRect->left, + suggestedRect->bottom - suggestedRect->top, + SWP_NOZORDER | SWP_NOACTIVATE + )) { + LogFail("SetWindowPos", GetLastError()); + abort(); + } + + } return 0; + case WM_SETCURSOR: + if (LOWORD(lparam) == HTCLIENT) + { + SetCursor(CORE.Input.Mouse.cursorHidden? NULL : LoadCursorW(NULL, (LPCWSTR)IDC_ARROW)); + return 0; + } + + return DefWindowProc(hwnd, msg, wparam, lparam); + case WM_INPUT: + if (globalCursorEnabled) + { + TRACELOG(LOG_ERROR, "Unexpected raw mouse input"); // impossible right? + abort(); + } + + HandleRawInput(lparam); + return 0; + case WM_MOUSEMOVE: + if (globalCursorEnabled) + { + 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; + } + + return 0; + case WM_KEYDOWN: HandleKey(wparam, lparam, 1); return 0; + case WM_KEYUP: HandleKey(wparam, lparam, 0); return 0; + case WM_LBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_LEFT, 1); return 0; + case WM_LBUTTONUP : HandleMouseButton(MOUSE_BUTTON_LEFT, 0); return 0; + case WM_RBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_RIGHT, 1); return 0; + case WM_RBUTTONUP : HandleMouseButton(MOUSE_BUTTON_RIGHT, 0); return 0; + case WM_MBUTTONDOWN: HandleMouseButton(MOUSE_BUTTON_MIDDLE, 1); return 0; + case WM_MBUTTONUP : HandleMouseButton(MOUSE_BUTTON_MIDDLE, 0); return 0; + case WM_XBUTTONDOWN: switch (HIWORD(wparam)) + { + case XBUTTON1: HandleMouseButton(MOUSE_BUTTON_SIDE, 1); return 0; + case XBUTTON2: HandleMouseButton(MOUSE_BUTTON_EXTRA, 1); return 0; + default: + TRACELOG(LOG_WARNING, "TODO: handle ex mouse button DOWN wparam=%u", HIWORD(wparam)); + return 0; + } + case WM_XBUTTONUP: switch (HIWORD(wparam)) + { + case XBUTTON1: HandleMouseButton(MOUSE_BUTTON_SIDE, 0); return 0; + case XBUTTON2: HandleMouseButton(MOUSE_BUTTON_EXTRA, 0); return 0; + default: + TRACELOG(LOG_WARNING, "TODO: handle ex mouse button UP wparam=%u", HIWORD(wparam)); + return 0; + } + case WM_MOUSEWHEEL: + CORE.Input.Mouse.currentWheelMove.y = ((float)GET_WHEEL_DELTA_WPARAM(wparam))/WHEEL_DELTA; + return 0; + case WM_MOUSEHWHEEL: + CORE.Input.Mouse.currentWheelMove.x = ((float)GET_WHEEL_DELTA_WPARAM(wparam))/WHEEL_DELTA; + return 0; + case WM_APP_UPDATE_WINDOW_SIZE: + UpdateWindowSize(UPDATE_WINDOW_NORMAL, hwnd, globalAppScreenSize, CORE.Window.flags); + return 0; + default: + return DefWindowProcW(hwnd, msg, wparam, lparam); + } +} + +static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + // sanity check, should we remove this? + DWORD mask = STYLE_MASK_ALL; + if (globalHwnd == hwnd) + { + if (msg == WM_WINDOWPOSCHANGING) { + mask &= ~(WS_MINIMIZE | WS_MAXIMIZE); + } + CheckFlags("WndProc", hwnd, CORE.Window.flags, MakeWindowStyle(CORE.Window.flags), mask); + } + + FlagsOp flagsOp; + flagsOp.set = 0; + flagsOp.clear = 0; + LRESULT result = WndProc2(hwnd, msg, wparam, lparam, &flagsOp); + + // sanity check, should we remove this? + if (globalHwnd == 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_ERROR, "the flags 0x%x were both set and cleared!", flagsOp.set & flagsOp.clear); + abort(); + } + + { + DWORD save = CORE.Window.flags; + CORE.Window.flags |= flagsOp.set; + CORE.Window.flags &= ~flagsOp.clear; + if (save != CORE.Window.flags) + { + TRACELOG( + LOG_DEBUG, + "DeferredFlags: 0x%x > 0x%x (diff 0x%x)", + save, + CORE.Window.flags, + save ^ CORE.Window.flags + ); + } + } + + return result; +} + + +int InitPlatform(void) +{ + globalDesiredFlags = SanitizeFlags(SANITIZE_FLAGS_FIRST, CORE.Window.flags); + // from this point CORE.Window.flags should always reflect the actual state of the window + CORE.Window.flags = FLAG_WINDOW_HIDDEN | (globalDesiredFlags & FLAG_MASK_NO_UPDATE); + + globalAppScreenSize = (Vector2){ CORE.Window.screen.width, CORE.Window.screen.height }; + + if (IsWindows10Version1703OrGreaterWin32()) + { + TRACELOG(LOG_INFO, "DpiAware: >=Win10Creators"); + if (!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { + LogFail("SetProcessDpiAwarenessContext", GetLastError()); + abort(); + } + } + else + { + TRACELOG(LOG_INFO, "DpiAware: Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); + TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); + TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); + TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); + + CORE.Storage.basePath = GetWorkingDirectory(); + + QueryPerformanceFrequency(&globalTimerFrequency); + + { + LARGE_INTEGER time; + QueryPerformanceCounter(&time); + CORE.Time.base = time.QuadPart; + } + InitTimer(); + + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP: WIN32: Initialized successfully"); + return 0; +} + +void ClosePlatform(void) +{ + if (globalHwnd) + { + if (0 == DestroyWindow(globalHwnd)) + { + LogFail("DestroyWindow", GetLastError()); + abort(); + } + + globalHwnd = NULL; + } +} diff --git a/src/rcore.c b/src/rcore.c index ea971b52f09e..460250cbcd14 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -553,6 +553,8 @@ const char *TextFormat(const char *text, ...); // Formatting of tex #include "platforms/rcore_desktop_sdl.c" #elif (defined(PLATFORM_DESKTOP_RGFW) || defined(PLATFORM_WEB_RGFW)) #include "platforms/rcore_desktop_rgfw.c" +#elif defined(PLATFORM_DESKTOP_WIN32) + #include "platforms/rcore_desktop_win32.c" #elif defined(PLATFORM_WEB) #include "platforms/rcore_web.c" #elif defined(PLATFORM_DRM) @@ -622,6 +624,8 @@ void InitWindow(int width, int height, const char *title) TRACELOG(LOG_INFO, "Platform backend: DESKTOP (SDL)"); #elif defined(PLATFORM_DESKTOP_RGFW) TRACELOG(LOG_INFO, "Platform backend: DESKTOP (RGFW)"); +#elif defined(PLATFORM_DESKTOP_WIN32) + TRACELOG(LOG_INFO, "Platform backend: DESKTOP (WIN32)"); #elif defined(PLATFORM_WEB_RGFW) TRACELOG(LOG_INFO, "Platform backend: WEB (RGFW) (HTML5)"); #elif defined(PLATFORM_WEB)