|
@@ -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 <windows.h>
|
|
|
+
|
|
|
+#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 <windowsx.h>
|
|
|
+#include <shellscalingapi.h>
|
|
|
+#include <versionhelpers.h>
|
|
|
+
|
|
|
+#include <GL/gl.h>
|
|
|
+#include <GL/wglext.h>
|
|
|
+
|
|
|
+// --------------------------------------------------------------------------------
|
|
|
+// 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: <Win10Creators");
|
|
|
+ HRESULT hr = SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
|
|
|
+ if (hr < 0) {
|
|
|
+ LogFailHr("SetProcessDpiAwareness", hr);
|
|
|
+ abort();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ {
|
|
|
+ WNDCLASSEXW c = {0};
|
|
|
+ c.cbSize = sizeof(c);
|
|
|
+ c.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
|
|
|
+ c.lpfnWndProc = WndProc;
|
|
|
+ c.cbWndExtra = sizeof(LONG_PTR); // extra space for the Tuple object ptr
|
|
|
+ c.hInstance = GetModuleHandleW(0);
|
|
|
+ // TODO: audit if we want to set this since we're implementing WM_SETCURSOR
|
|
|
+ c.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
|
|
|
+ c.lpszClassName = CLASS_NAME;
|
|
|
+ if (0 == RegisterClassExW(&c))
|
|
|
+ {
|
|
|
+ LogFail("RegisterClass", GetLastError());
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
|
+ // TODO: probably remove or at least move this code that sets the display size.
|
|
|
+ // should maybe go somewhere in WndProc?
|
|
|
+ HMONITOR monitor;
|
|
|
+
|
|
|
+ {
|
|
|
+ POINT primaryTopLeft = {0, 0};
|
|
|
+ monitor = MonitorFromPoint(primaryTopLeft, MONITOR_DEFAULTTOPRIMARY);
|
|
|
+ if (!monitor)
|
|
|
+ {
|
|
|
+ LogFail("MonitorFromPoint", GetLastError());
|
|
|
+ // we'll keep going
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ MONITORINFO info;
|
|
|
+ info.cbSize = sizeof(info);
|
|
|
+ if (!GetMonitorInfoW(monitor, &info))
|
|
|
+ {
|
|
|
+ LogFail("GetMonitorInfo", GetLastError());
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ CORE.Window.display.width = info.rcMonitor.right - info.rcMonitor.left;
|
|
|
+ CORE.Window.display.height = info.rcMonitor.bottom - info.rcMonitor.top;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ CreateWindowAlloca(CORE.Window.title, MakeWindowStyle(CORE.Window.flags));
|
|
|
+ if (!globalHwnd)
|
|
|
+ {
|
|
|
+ LogFail("CreateWindow", GetLastError());
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ CORE.Window.ready = true;
|
|
|
+ UpdateWindowSize(UPDATE_WINDOW_FIRST, globalHwnd, globalAppScreenSize, globalDesiredFlags);
|
|
|
+ UpdateFlags(globalHwnd, globalDesiredFlags, globalAppScreenSize);
|
|
|
+
|
|
|
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
|
+ // TODO: not sure why this needs to be here?
|
|
|
+ CORE.Window.currentFbo.width = CORE.Window.render.width;
|
|
|
+ CORE.Window.currentFbo.height = CORE.Window.render.height;
|
|
|
+ TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully");
|
|
|
+ TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height);
|
|
|
+ TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height);
|
|
|
+ TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height);
|
|
|
+ TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y);
|
|
|
+
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+}
|