mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
600 lines
15 KiB
Beef
600 lines
15 KiB
Beef
#if BF_PLATFORM_WINDOWS
|
|
|
|
using System;
|
|
using DirectX.Common;
|
|
using DirectX.Windows;
|
|
using DirectX.Windows.Winuser;
|
|
using DirectX.Windows.Kernel32;
|
|
using DirectX.Windows.WindowMessages;
|
|
using GlitchyEngine.Events;
|
|
using System.Diagnostics;
|
|
using GlitchyEngine.Math;
|
|
using GlitchyEngine.Renderer;
|
|
using DirectX.Windows.Winuser.RawInput;
|
|
using static System.Windows;
|
|
|
|
using internal GlitchyEngine;
|
|
|
|
namespace GlitchyEngine
|
|
{
|
|
/// The windows specific Window implementation
|
|
public extension Window
|
|
{
|
|
const String WindowClassName = "GlitchyEngineWindow";
|
|
|
|
private Windows.HInstance _instanceHandle;
|
|
private Windows.HWnd _windowHandle;
|
|
|
|
private WindowClassExW _windowClass;
|
|
|
|
private WindowRectangle _clientRect;
|
|
|
|
private MinMaxInfo _minMaxInfo;
|
|
|
|
private String _title ~ delete _;
|
|
|
|
private bool _isVSync = true;
|
|
|
|
private bool _isActive;
|
|
|
|
private GraphicsContext _graphicsContext ~ _?.ReleaseRef();
|
|
|
|
public override GraphicsContext Context => _graphicsContext;
|
|
|
|
public override int32 MinWidth
|
|
{
|
|
get => _minMaxInfo.MinimumTrackingSize.x;
|
|
set => _minMaxInfo.MinimumTrackingSize.x = value;
|
|
}
|
|
public override int32 MinHeight
|
|
{
|
|
get => _minMaxInfo.MinimumTrackingSize.y;
|
|
set => _minMaxInfo.MinimumTrackingSize.y = value;
|
|
}
|
|
|
|
public override int32 MaxWidth
|
|
{
|
|
get => _minMaxInfo.MaximumTrackingSize.x;
|
|
set => _minMaxInfo.MaximumTrackingSize.x = value;
|
|
}
|
|
|
|
public override int32 MaxHeight
|
|
{
|
|
get => _minMaxInfo.MaximumTrackingSize.y;
|
|
set => _minMaxInfo.MaximumTrackingSize.y = value;
|
|
}
|
|
|
|
//
|
|
// Size
|
|
//
|
|
public override int2 Size
|
|
{
|
|
get => *(int2*)&_clientRect.Width;
|
|
|
|
set
|
|
{
|
|
*(int2*)&_clientRect.Width = value;
|
|
ApplyRectangle();
|
|
}
|
|
}
|
|
|
|
public override int32 Width
|
|
{
|
|
get => _clientRect.Width;
|
|
|
|
set
|
|
{
|
|
_clientRect.Width = value;
|
|
ApplyRectangle();
|
|
}
|
|
}
|
|
|
|
public override int32 Height
|
|
{
|
|
get => _clientRect.Height;
|
|
|
|
set
|
|
{
|
|
_clientRect.Height = value;
|
|
ApplyRectangle();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Position
|
|
//
|
|
public override int2 Position
|
|
{
|
|
get => *(int2*)&_clientRect;
|
|
|
|
set
|
|
{
|
|
*(int2*)&_clientRect = value;
|
|
ApplyRectangle();
|
|
}
|
|
}
|
|
|
|
public override int32 PositionX
|
|
{
|
|
get => _clientRect.X;
|
|
|
|
set
|
|
{
|
|
_clientRect.X = value;
|
|
ApplyRectangle();
|
|
}
|
|
}
|
|
|
|
public override int32 PositionY
|
|
{
|
|
get => _clientRect.Y;
|
|
|
|
set
|
|
{
|
|
_clientRect.Y = value;
|
|
ApplyRectangle();
|
|
}
|
|
}
|
|
|
|
public override StringView Title
|
|
{
|
|
get
|
|
{
|
|
if (_title == null)
|
|
LoadTitle();
|
|
|
|
return _title;
|
|
}
|
|
|
|
set => SetWindowTextW(_windowHandle, value.ToScopedNativeWChar!());
|
|
}
|
|
|
|
public override bool IsVSync
|
|
{
|
|
get => _isVSync;
|
|
set => _isVSync = value;
|
|
}
|
|
|
|
public override bool IsActive => _isActive;
|
|
|
|
public override void* NativeWindow => (void*)(int)_windowHandle;
|
|
|
|
public override this(WindowDescription desc)
|
|
{
|
|
Debug.Profiler.ProfileFunction!();
|
|
|
|
_minMaxInfo.MaximumTrackingSize.x = int32.MaxValue;
|
|
_minMaxInfo.MaximumTrackingSize.y = int32.MaxValue;
|
|
|
|
Init(desc);
|
|
}
|
|
|
|
[LinkName(.C)]
|
|
static extern Windows.IntBool DestroyWindow(Windows.HWnd hWnd);
|
|
|
|
[LinkName("UnregisterClassW")]
|
|
static extern Windows.IntBool UnregisterClass(char16* className, Windows.HInstance hInstance);
|
|
|
|
public ~this()
|
|
{
|
|
Debug.Profiler.ProfileFunction!();
|
|
|
|
if(!DestroyWindow(_windowHandle))
|
|
{
|
|
HResult res = (.)GetLastError();
|
|
Log.EngineLogger.Error($"Failed to destroy window: Message ({(int)res}): {res}");
|
|
}
|
|
|
|
UnregisterClass(WindowClassName.ToScopedNativeWChar!(), _instanceHandle);
|
|
}
|
|
|
|
[LinkName(.C)]
|
|
static extern HWnd GetActiveWindow();
|
|
|
|
private void Init(WindowDescription desc)
|
|
{
|
|
Debug.Profiler.ProfileFunction!();
|
|
|
|
Log.EngineLogger.Trace($"Creating window \"{desc.Title}\" ({desc.Width}, {desc.Height})...");
|
|
|
|
_instanceHandle = (.)GetModuleHandleW(null);
|
|
|
|
_windowClass = .();
|
|
_windowClass.Style = .HorizontalRedrawOnChange | .VerticalRedrawOnChange;
|
|
_windowClass.WindowProcedure = => MessageHandler;
|
|
_windowClass.HInstance = (.)_instanceHandle;
|
|
|
|
if (desc.Icon.Ptr != null)
|
|
{
|
|
Debug.Profiler.ProfileScope!("LoadIcon");
|
|
|
|
_windowClass.Icon = LoadImageW(0, desc.Icon.ToScopedNativeWChar!(), .Icon, 0, 0, .LoadFromFile);
|
|
}
|
|
else
|
|
_windowClass.Icon = 0;
|
|
|
|
_windowClass.Cursor = LoadCursorW(0, IDC_ARROW);
|
|
_windowClass.BackgroundBrush = (HBRUSH)SystemColor.WindowFrame;
|
|
_windowClass.ClassName = WindowClassName.ToScopedNativeWChar!();
|
|
|
|
if (RegisterClassExW(ref _windowClass) == 0)
|
|
{
|
|
uint32 lastError = GetLastError();
|
|
Log.EngineLogger.Error($"Failed to register window class. Message({(int)lastError}): {(HResult)lastError}");
|
|
Runtime.FatalError("Failed to register window class");
|
|
}
|
|
|
|
{
|
|
Debug.Profiler.ProfileScope!("CreateWindow");
|
|
|
|
_windowHandle = CreateWindowExW(.None, _windowClass.ClassName, desc.Title.ToScopedNativeWChar!(), .WS_OVERLAPPEDWINDOW | .WS_VISIBLE,
|
|
CW_USEDEFAULT, CW_USEDEFAULT, desc.Width, desc.Height, 0, 0, (.)_instanceHandle, null);
|
|
}
|
|
|
|
_graphicsContext = new GraphicsContext(_windowHandle);
|
|
_graphicsContext.Init();
|
|
|
|
void* myPtr = Internal.UnsafeCastToPtr(this);
|
|
SetWindowLongPtrW(_windowHandle, GWL_USERDATA, (int)myPtr);
|
|
|
|
LoadWindowRectangle();
|
|
|
|
Log.EngineLogger.Trace($"Created window \"{Title}\" ({Width}, {Height})");
|
|
|
|
// Determine whether or not the window is currently active
|
|
_isActive = GetActiveWindow() == _windowHandle;
|
|
}
|
|
|
|
private bool _isResizingOrMoving;
|
|
private bool _isMinimized;
|
|
|
|
[CLink]
|
|
static extern IntBool IsWindowUnicode(HWND whnd);
|
|
|
|
public override Result<void> SetIcon(StringView filePath)
|
|
{
|
|
HICON hIcon = LoadImageW(0, filePath.ToScopedNativeWChar!(), .Icon, 0, 0, .LoadFromFile);
|
|
if (hIcon == 0)
|
|
return .Err;
|
|
|
|
// WM_SETICON 0x0080
|
|
SendMessageW(_windowHandle, 0x0080, 1 /* ICON_BIG */, (int)hIcon);
|
|
|
|
return .Ok;
|
|
}
|
|
|
|
internal int2 _rawMouseMovementAccumulator;
|
|
|
|
private static LRESULT MessageHandler(HWND hwnd, uint32 uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
void* windowPtr = (void*)GetWindowLongPtrW(hwnd, GWL_USERDATA);
|
|
Window window = (Window)Internal.UnsafeCastToObject(windowPtr);
|
|
|
|
if (window == null)
|
|
{
|
|
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
// TODO: This line is the reason why GlitchyEngine depends on ImGui...
|
|
#if IMGUI
|
|
ImGui.ImGuiImplWin32.WndProcHandler(hwnd, uMsg, wParam, lParam);
|
|
#endif
|
|
|
|
switch (uMsg)
|
|
{
|
|
////
|
|
//// Sizing and Moving
|
|
////
|
|
|
|
// Window min/max size requested
|
|
case WM_GETMINMAXINFO:
|
|
{
|
|
MinMaxInfo* info = (.)(void*)lParam;
|
|
info.MinimumTrackingSize = window._minMaxInfo.MinimumTrackingSize;
|
|
info.MaximumTrackingSize = window._minMaxInfo.MaximumTrackingSize;
|
|
}
|
|
// Window size changed
|
|
case WM_SIZE:
|
|
{
|
|
window._isMinimized = wParam == (.)ResizingType.Minimized;
|
|
|
|
|
|
SplitHighAndLowOrder!(lParam, out window._clientRect.Width, out window._clientRect.Height);
|
|
|
|
if(window._clientRect.Width != 0 && window._clientRect.Height != 0)
|
|
{
|
|
window._graphicsContext.SwapChain.Width = (.)window._clientRect.Width;
|
|
window._graphicsContext.SwapChain.Height = (.)window._clientRect.Height;
|
|
|
|
window._graphicsContext.SwapChain.ApplyChanges();
|
|
}
|
|
|
|
WindowResizeEvent event = scope WindowResizeEvent(window._clientRect.Width, window._clientRect.Height, window._isResizingOrMoving);
|
|
window._eventCallback(event);
|
|
}
|
|
// Window position changed
|
|
case WM_MOVE:
|
|
{
|
|
if (wParam == (.)ResizingType.Minimized)
|
|
{
|
|
window._isMinimized = true;
|
|
}
|
|
else if (window._isMinimized)
|
|
{
|
|
window._isMinimized = false;
|
|
}
|
|
else
|
|
{
|
|
SplitHighAndLowOrder!(lParam, out window._clientRect.X, out window._clientRect.Y);
|
|
|
|
var event = scope WindowMoveEvent(window._clientRect.X, window._clientRect.Y, window._isResizingOrMoving);
|
|
window._eventCallback(event);
|
|
}
|
|
}
|
|
// Window resizing/moving started
|
|
case WM_ENTERSIZEMOVE:
|
|
window._isResizingOrMoving = true;
|
|
// Window resizing/moving ended
|
|
case WM_EXITSIZEMOVE:
|
|
{
|
|
window._isResizingOrMoving = false;
|
|
|
|
var resEvent = scope WindowResizeEvent(window._clientRect.Width, window._clientRect.Height, false);
|
|
window._eventCallback(resEvent);
|
|
var moveEvent = scope WindowMoveEvent(window._clientRect.X, window._clientRect.Y, false);
|
|
window._eventCallback(moveEvent);
|
|
}
|
|
|
|
case 0x0006: //WM_ACTIVATE
|
|
{
|
|
SplitHighAndLowOrder!((int64)wParam, let lowWParam, let highWParam);
|
|
|
|
bool isActivate = lowWParam > 0;
|
|
|
|
//HWnd windowHandle = (.)lParam;
|
|
|
|
if(window._isActive != isActivate)
|
|
{
|
|
window._isActive = isActivate;
|
|
|
|
if(window._isActive)
|
|
window._eventCallback(scope WindowActivateEvent());
|
|
else
|
|
window._eventCallback(scope WindowDeactivateEvent());
|
|
|
|
}
|
|
}
|
|
////
|
|
//// Keyboard input
|
|
////
|
|
|
|
case WM_KEYDOWN, WM_SYSKEYDOWN:
|
|
{
|
|
Key key = WindowMessageToKey(wParam, lParam);
|
|
var event = scope KeyPressedEvent(key, (int32)lParam & 0xFFFF);
|
|
window._eventCallback(event);
|
|
}
|
|
case WM_KEYUP, WM_SYSKEYUP:
|
|
{
|
|
Key key = WindowMessageToKey(wParam, lParam);
|
|
var event = scope KeyReleasedEvent(key);
|
|
window._eventCallback(event);
|
|
}
|
|
|
|
////
|
|
//// Mouse input
|
|
////
|
|
|
|
// Left mouse button
|
|
case WM_LBUTTONDOWN:
|
|
{
|
|
var event = scope MouseButtonPressedEvent(.LeftButton);
|
|
window._eventCallback(event);
|
|
}
|
|
case WM_LBUTTONUP:
|
|
{
|
|
var event = scope MouseButtonReleasedEvent(.LeftButton);
|
|
window._eventCallback(event);
|
|
}
|
|
// Right mouse button
|
|
case WM_RBUTTONDOWN:
|
|
{
|
|
var event = scope MouseButtonPressedEvent(.RightButton);
|
|
window._eventCallback(event);
|
|
}
|
|
case WM_RBUTTONUP:
|
|
{
|
|
var event = scope MouseButtonReleasedEvent(.RightButton);
|
|
window._eventCallback(event);
|
|
}
|
|
// Middle mouse button
|
|
case WM_MBUTTONDOWN:
|
|
{
|
|
var event = scope MouseButtonPressedEvent(.MiddleButton);
|
|
window._eventCallback(event);
|
|
}
|
|
case WM_MBUTTONUP:
|
|
{
|
|
var event = scope MouseButtonReleasedEvent(.MiddleButton);
|
|
window._eventCallback(event);
|
|
}
|
|
// X button
|
|
case WM_XBUTTONDOWN:
|
|
{
|
|
MouseButton button = .None;
|
|
if (HighOrder!((int64)wParam) == 1)
|
|
button = .XButton1;
|
|
else
|
|
button = .XButton2;
|
|
|
|
var event = scope MouseButtonPressedEvent(button);
|
|
window._eventCallback(event);
|
|
}
|
|
case WM_XBUTTONUP:
|
|
{
|
|
MouseButton button = .None;
|
|
if (HighOrder!((int64)wParam) == 1)
|
|
button = .XButton1;
|
|
else
|
|
button = .XButton2;
|
|
|
|
var event = scope MouseButtonReleasedEvent(button);
|
|
window._eventCallback(event);
|
|
}
|
|
// Vertical scrolling
|
|
case WM_MOUSEWHEEL:
|
|
{
|
|
int32 rotation = (int16)HighOrder!((int64)wParam) / WHEEL_DELTA;
|
|
|
|
var event = scope MouseScrolledEvent(0, rotation);
|
|
window._eventCallback(event);
|
|
}
|
|
// Horizontal scrolling
|
|
case WM_MOUSEHWHEEL:
|
|
{
|
|
int32 rotation = (int16)HighOrder!((int64)wParam) / WHEEL_DELTA;
|
|
|
|
var event = scope MouseScrolledEvent(rotation, 0);
|
|
window._eventCallback(event);
|
|
}
|
|
case WM_MOUSEMOVE:
|
|
{
|
|
SplitHighAndLowOrder!(lParam, let x, let y);
|
|
|
|
var event = scope MouseMovedEvent(x, y);
|
|
window._eventCallback(event);
|
|
}
|
|
case WM_INPUT:
|
|
{
|
|
uint32 dataSize = ?;
|
|
GetRawInputData((.)lParam, RID_INPUT, null, &dataSize, sizeof(RAWINPUTHEADER));
|
|
|
|
if (dataSize > 0)
|
|
{
|
|
uint8[] rawData = scope .[dataSize];
|
|
if (GetRawInputData((.)lParam, RID_INPUT, rawData.CArray(), &dataSize, sizeof(RAWINPUTHEADER)) == dataSize)
|
|
{
|
|
RAWINPUT* raw = (.)rawData.CArray();
|
|
if (raw.Header.Type == RIM_TYPEMOUSE)
|
|
{
|
|
var event = scope MouseMovedEvent(raw.Data.Mouse.lLastX, raw.Data.Mouse.lLastY);
|
|
window._eventCallback(event);
|
|
|
|
// We accumulate the raw movements an collect them once each frame in WindowsInput.Impl_NewFrame()
|
|
window._rawMouseMovementAccumulator += int2(raw.Data.Mouse.lLastX, raw.Data.Mouse.lLastY);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Todo: DirectInput
|
|
|
|
////
|
|
//// Application
|
|
////
|
|
case WM_CHAR:
|
|
{
|
|
var event = scope KeyTypedEvent((char16)wParam);
|
|
window._eventCallback(event);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Window title changed
|
|
case WM_SETTEXT:
|
|
{
|
|
// Deletes and nulls _title so that it will be reloaded on the next call of Title.get
|
|
delete window._title;
|
|
window._title = null;
|
|
}
|
|
case WM_SYSCOMMAND:
|
|
{
|
|
/* Remove beeping sound when ALT + some key is pressed. */
|
|
if (wParam == SC_KEYMENU)
|
|
return 0;
|
|
}
|
|
// Window closing
|
|
case WM_CLOSE:
|
|
{
|
|
var event = scope WindowCloseEvent();
|
|
window._eventCallback(event);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
/**
|
|
* Converts the wParam and lParam of a WM_KEYDOWN or WM_KEYUP message to the corresponding engine keycode.
|
|
*/
|
|
static Key WindowMessageToKey(WPARAM wParam, LPARAM lParam)
|
|
{
|
|
Key key = Key.FromWindowsKeyCode((.)wParam);
|
|
|
|
var scancode = (lParam & 0x00ff0000) >> 16;
|
|
bool isExtended = (lParam & 0x01000000) > 0;
|
|
|
|
switch(key)
|
|
{
|
|
case .Shift:
|
|
return Key.FromWindowsKeyCode((.)MapVirtualKeyW((.)scancode, MAPVK_VSC_TO_VK_EX));
|
|
case .Control:
|
|
return isExtended ? .RightControl : .LeftControl;
|
|
case .Alt:
|
|
return isExtended ? .RightAlt : .LeftAlt;
|
|
default:
|
|
return key;
|
|
}
|
|
}
|
|
|
|
public override void Update()
|
|
{
|
|
Debug.Profiler.ProfileFunction!();
|
|
|
|
Message message = .();
|
|
while (PeekMessageW(&message, 0, 0, 0, .Remove))
|
|
{
|
|
TranslateMessage(&message);
|
|
DispatchMessageW(&message);
|
|
}
|
|
}
|
|
|
|
private void LoadWindowRectangle()
|
|
{
|
|
GetClientRect(_windowHandle, let rectangle);
|
|
|
|
_clientRect.X = rectangle.Left;
|
|
_clientRect.Y = rectangle.Top;
|
|
_clientRect.Width = rectangle.Right - rectangle.Left;
|
|
_clientRect.Height = rectangle.Bottom - rectangle.Top;
|
|
}
|
|
|
|
private void ApplyRectangle()
|
|
{
|
|
SetWindowPos(_windowHandle, 0, _clientRect.X, _clientRect.Y, _clientRect.Width, _clientRect.Height, 0);
|
|
}
|
|
|
|
[LinkName(.C)]
|
|
private static extern IntBool SetWindowPos(HWND hWnd, HWND hWndInsertAfter, int32 X, int32 Y, int32 cx, int32 cy, uint32 uFlags);
|
|
|
|
/**
|
|
* Gets the window title via WinApi and stores it in _title.
|
|
*/
|
|
private void LoadTitle()
|
|
{
|
|
int32 length = GetWindowTextLengthW(_windowHandle) + 1;
|
|
|
|
char16[] chars = scope char16[length];
|
|
|
|
Span<char16> titleSpan = .(chars, 0, length - 1);
|
|
|
|
GetWindowTextW(_windowHandle, chars.CArray(), length);
|
|
|
|
_title = new String(titleSpan);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif |