/**********************************************************************************************
|
|
*
|
|
* 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;
|
|
}
|
|
}
|