Better WindowClass handling, Added Clipboard

This commit is contained in:
Simon Lübeß
2024-09-15 18:45:30 +02:00
parent 2e3c0d0aa4
commit 4d7863dcb3
7 changed files with 726 additions and 83 deletions
+4 -12
View File
@@ -4,7 +4,6 @@ namespace GlitchyEngine.Events
{
public class MouseMovedEvent : Event, IEvent
{
private Window _window;
private int32 _mouseX, _mouseY;
public override EventType EventType => .MouseMoved;
@@ -15,14 +14,11 @@ namespace GlitchyEngine.Events
public static EventType StaticType => .MouseMoved;
public Window Window => _window;
public int32 PositionX => _mouseX;
public int32 PositionY => _mouseY;
public this(Window window, int32 x, int32 y)
public this(int32 x, int32 y)
{
_window = window;
_mouseX = x;
_mouseY = y;
}
@@ -99,18 +95,14 @@ namespace GlitchyEngine.Events
public abstract class MouseButtonEvent : Event
{
protected Window _window;
protected MouseButton _mouseButton;
public override EventCategory Category => .Input | .Mouse;
public Window Window => _window;
public MouseButton MouseButton => _mouseButton;
protected this(Window window, MouseButton mouseButton)
protected this(MouseButton mouseButton)
{
_window = window;
_mouseButton = mouseButton;
}
}
@@ -123,7 +115,7 @@ namespace GlitchyEngine.Events
public static EventType StaticType => .MouseButtonPressed;
public this(Window window, MouseButton mouseButton) : base(window, mouseButton) { }
public this(MouseButton mouseButton) : base(mouseButton) { }
public override void ToString(String strBuffer)
{
@@ -140,7 +132,7 @@ namespace GlitchyEngine.Events
public static EventType StaticType => .MouseButtonReleased;
public this(Window window, MouseButton mouseButton) : base(window, mouseButton) { }
public this(MouseButton mouseButton) : base(mouseButton) { }
public override void ToString(String strBuffer)
{
@@ -0,0 +1,174 @@
#if BF_PLATFORM_WINDOWS
using System;
using DirectX.Common;
using System.Text;
using static System.Windows;
namespace GlitchyEngine.System;
extension Clipboard
{
[CLink, CallingConvention(.Stdcall)]
private static extern IntBool OpenClipboard(HWnd hWndNewOwner);
[CLink, CallingConvention(.Stdcall)]
private static extern IntBool CloseClipboard();
[CLink, CallingConvention(.Stdcall)]
private static extern IntBool EmptyClipboard();
[AllowDuplicates]
private enum ClipboardFormat : uint32
{
Bitmap = 2,
Dib = 8,
DibV5 = 17,
Dif = 5,
DspBitmap = 0,
Dspenhmetafile = 0,
DspMetaFilepict = 0,
DspText = 0,
Enhmetafile = 14,
Gdiobjfirst = 0,
Gdiobjlast = 0,
Hdrop = 15,
Locale = 16,
Metafilepict = 3,
Oemtext = 7,
Ownerdisplay = 0,
Palette = 9,
Pendata = 10,
Privatefirst = 0,
Privatelast = 0,
Riff = 11,
Sylk = 4,
Text = 1,
Tiff = 6,
UnicodeText = 13,
Wave = 12
}
[CLink, CallingConvention(.Stdcall)]
private static extern Handle GetClipboardData(ClipboardFormat format);
[CLink, CallingConvention(.Stdcall)]
private static extern Handle SetClipboardData(ClipboardFormat format, Handle memory);
[CLink, CallingConvention(.Stdcall)]
private static extern void* GlobalLock(Handle memory);
[CLink, CallingConvention(.Stdcall)]
private static extern IntBool GlobalUnlock(Handle memory);
[AllowDuplicates]
private enum GlobalMemoryFlags : uint32
{
Fixed = 0x0000,
Moveable = 0x0002,
ZeroInit = 0x0040,
/// Combines Fixed and ZeroInit
GPTR = 0x0040,
/// Combines Moveable and ZeroInit
GHND = 0x0042
}
[CLink, CallingConvention(.Stdcall)]
private static extern Handle GlobalAlloc(GlobalMemoryFlags uFlags, int dwBytes);
[CLink, CallingConvention(.Stdcall)]
private static extern Handle GlobalFree(Handle memory);
public static mixin TryLogging(IntBool result)
{
if (!result)
{
HResult hresult = HResult.FromWin32((uint32)GetLastError());
Log.EngineLogger.Error($"{hresult.Underlying}: {hresult}");
return;
}
}
public static mixin TryLoggingSilent(IntBool result)
{
if (!result)
{
HResult hresult = HResult.FromWin32((uint32)GetLastError());
Log.EngineLogger.Error($"{hresult.Underlying}: {hresult}");
}
}
public override static void Clear()
{
TryLogging!(OpenClipboard(0));
TryLogging!(EmptyClipboard());
TryLogging!(CloseClipboard());
}
public override static void Read(String outBuffer)
{
TryLogging!(OpenClipboard(0));
do
{
Handle handle = GetClipboardData(.UnicodeText);
if (handle == 0)
break;
char16* clipboardData = (char16*)GlobalLock(handle);
if (clipboardData != null)
{
outBuffer.Append(clipboardData);
TryLoggingSilent!(GlobalUnlock(handle));
}
else
{
HResult hresult = HResult.FromWin32((uint32)GetLastError());
Log.EngineLogger.Error($"{hresult.Underlying}: {hresult}");
}
}
TryLogging!(CloseClipboard());
}
public override static void Set(StringView text)
{
TryLogging!(OpenClipboard(0));
do
{
char16* nativeTextPtr = text.ToScopedNativeWChar!();
Span<char16> nativeText = .(nativeTextPtr, UTF16.CStrLen(nativeTextPtr) + 1);
Handle handle = GlobalAlloc(.Moveable, sizeof(char16) * nativeText.Length);
if (handle == 0)
break;
char16* clipboardDataPtr = (char16*)GlobalLock(handle);
Span<char16> clipboardData = .(clipboardDataPtr, nativeText.Length);
if (clipboardDataPtr == null)
{
HResult hresult = HResult.FromWin32((uint32)GetLastError());
Log.EngineLogger.Error($"{hresult.Underlying}: {hresult}");
break;
}
nativeText.CopyTo(clipboardData);
TryLoggingSilent!(GlobalUnlock(handle));
TryLoggingSilent!(EmptyClipboard());
if (SetClipboardData(.UnicodeText, handle) == 0)
{
HResult hresult = HResult.FromWin32((uint32)GetLastError());
Log.EngineLogger.Error($"{hresult.Underlying}: {hresult}");
GlobalFree(handle);
}
}
TryLogging!(CloseClipboard());
}
}
#endif
@@ -21,12 +21,13 @@ namespace GlitchyEngine
public extension Window
{
const String WindowClassName = "GlitchyEngineWindow";
static readonly char16[?] WindowClassNameW = WindowClassName.ToConstNativeW();
private Windows.HInstance _instanceHandle;
private static WindowClassExW WindowClass;
private static Windows.HInstance _instanceHandle;
internal Windows.HWnd _windowHandle;
private WindowClassExW _windowClass;
private WindowRectangle _clientRect;
private MinMaxInfo _minMaxInfo;
@@ -183,52 +184,65 @@ namespace GlitchyEngine
Log.EngineLogger.Error($"Failed to destroy window: Message ({(int)res}): {res}");
}
// TODO manage window classes somehow
UnregisterClass(WindowClassName.ToScopedNativeWChar!(), _instanceHandle);
Application.Instance.Windows.Remove(this);
}
static ~this()
{
#unwarn
UnregisterClass(WindowClass.ClassName, _instanceHandle);
}
[LinkName(.C)]
static extern HWnd GetActiveWindow();
private void CreateWindowClass(StringView iconPath)
{
_instanceHandle = (.)GetModuleHandleW(null);
WindowClass = .();
WindowClass.Style = .HorizontalRedrawOnChange | .VerticalRedrawOnChange;
WindowClass.WindowProcedure = => MessageHandler;
WindowClass.HInstance = (.)_instanceHandle;
if (iconPath.Ptr != null)
{
Debug.Profiler.ProfileScope!("LoadIcon");
WindowClass.Icon = LoadImageW(0, iconPath.ToScopedNativeWChar!(), .Icon, 0, 0, .LoadFromFile);
}
else
WindowClass.Icon = 0;
WindowClass.Cursor = LoadCursorW(0, IDC_ARROW);
WindowClass.BackgroundBrush = (HBRUSH)SystemColor.WindowFrame;
#unwarn
WindowClass.ClassName = &WindowClassNameW;
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");
}
}
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)
if (WindowClass == default)
{
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");
CreateWindowClass(desc.Icon);
}
{
Debug.Profiler.ProfileScope!("CreateWindow");
_windowHandle = CreateWindowExW(.None, _windowClass.ClassName, desc.Title.ToScopedNativeWChar!(), .WS_OVERLAPPEDWINDOW | .WS_VISIBLE,
_windowHandle = CreateWindowExW(.None, WindowClass.ClassName, desc.Title.ToScopedNativeWChar!(), .WS_OVERLAPPEDWINDOW | .WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, desc.Width, desc.Height, 0, 0, (.)_instanceHandle, null);
}
@@ -244,6 +258,8 @@ namespace GlitchyEngine
// Determine whether or not the window is currently active
_isActive = GetActiveWindow() == _windowHandle;
SetIcon(desc.Icon);
}
private bool _isResizingOrMoving;
@@ -252,7 +268,7 @@ namespace GlitchyEngine
[CLink]
static extern IntBool IsWindowUnicode(HWND whnd);
public Result<void> SetIcon(StringView filePath)
public override Result<void> SetIcon(StringView filePath)
{
HICON hIcon = LoadImageW(0, filePath.ToScopedNativeWChar!(), .Icon, 0, 0, .LoadFromFile);
if (hIcon == 0)
@@ -386,34 +402,34 @@ namespace GlitchyEngine
// Left mouse button
case WM_LBUTTONDOWN:
{
var event = scope MouseButtonPressedEvent(window, .LeftButton);
var event = scope MouseButtonPressedEvent(.LeftButton);
window._eventCallback(event);
}
case WM_LBUTTONUP:
{
var event = scope MouseButtonReleasedEvent(window, .LeftButton);
var event = scope MouseButtonReleasedEvent(.LeftButton);
window._eventCallback(event);
}
// Right mouse button
case WM_RBUTTONDOWN:
{
var event = scope MouseButtonPressedEvent(window, .RightButton);
var event = scope MouseButtonPressedEvent(.RightButton);
window._eventCallback(event);
}
case WM_RBUTTONUP:
{
var event = scope MouseButtonReleasedEvent(window, .RightButton);
var event = scope MouseButtonReleasedEvent(.RightButton);
window._eventCallback(event);
}
// Middle mouse button
case WM_MBUTTONDOWN:
{
var event = scope MouseButtonPressedEvent(window, .MiddleButton);
var event = scope MouseButtonPressedEvent(.MiddleButton);
window._eventCallback(event);
}
case WM_MBUTTONUP:
{
var event = scope MouseButtonReleasedEvent(window, .MiddleButton);
var event = scope MouseButtonReleasedEvent(.MiddleButton);
window._eventCallback(event);
}
// X button
@@ -425,7 +441,7 @@ namespace GlitchyEngine
else
button = .XButton2;
var event = scope MouseButtonPressedEvent(window, button);
var event = scope MouseButtonPressedEvent(button);
window._eventCallback(event);
}
case WM_XBUTTONUP:
@@ -436,7 +452,7 @@ namespace GlitchyEngine
else
button = .XButton2;
var event = scope MouseButtonReleasedEvent(window, button);
var event = scope MouseButtonReleasedEvent(button);
window._eventCallback(event);
}
// Vertical scrolling
@@ -459,10 +475,10 @@ namespace GlitchyEngine
{
SplitHighAndLowOrder!(lParam, let x, let y);
var event = scope MouseMovedEvent(window, x, y);
var event = scope MouseMovedEvent(x, y);
window._eventCallback(event);
}
case WM_INPUT:
/*case WM_INPUT:
{
uint32 dataSize = ?;
GetRawInputData((.)lParam, RID_INPUT, null, &dataSize, sizeof(RAWINPUTHEADER));
@@ -483,7 +499,7 @@ namespace GlitchyEngine
}
}
}
}
}*/
// Todo: DirectInput
+15
View File
@@ -0,0 +1,15 @@
using System;
namespace GlitchyEngine.System;
class Clipboard
{
/// Clears the clipboard.
public static extern void Clear();
/// Reads a unicode text from the clipboard.
public static extern void Read(String outBuffer);
/// Sets the content of the clipboard to the given unicode text.
public static extern void Set(StringView text);
}