mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
Start of custom title, call beef delegates from JS
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using Ultralight.CAPI;
|
||||
using GlitchyEngine;
|
||||
|
||||
namespace GlitchyEditor.Ultralight;
|
||||
|
||||
delegate void JSCallback(JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null);
|
||||
|
||||
static class UltralightHelper
|
||||
{
|
||||
private static JSValueRef NativeFunctionCallback(JSContextRef ctx, JSObjectRef fn, JSObjectRef thisObject, uint32 argumentCount, JSValueRef* arguments, JSValueRef* exception)
|
||||
{
|
||||
JSCallback callback = (JSCallback)Internal.UnsafeCastToObject(JSObjectGetPrivate(fn));
|
||||
|
||||
if (callback == null)
|
||||
{
|
||||
Log.EngineLogger.Error("The native callback of the function is null.");
|
||||
return JSValueMakeNull(ctx);
|
||||
}
|
||||
|
||||
callback(ctx, thisObject, Span<JSValueRef>(arguments, argumentCount));
|
||||
|
||||
return JSValueMakeNull(ctx);
|
||||
}
|
||||
|
||||
private static void NativeFunctionFinalize(JSObjectRef object)
|
||||
{
|
||||
JSCallback callback = (JSCallback)Internal.UnsafeCastToObject(JSObjectGetPrivate(object));
|
||||
delete:(StdAllocator()) callback;
|
||||
}
|
||||
|
||||
private static JSClassRef NativeFunctionClass()
|
||||
{
|
||||
static JSClassRef instance = null;
|
||||
if (instance == null)
|
||||
{
|
||||
JSClassDefinition def = .();
|
||||
def.className = "NativeFunction";
|
||||
def.attributes = (.)JSClassAttribute.kJSClassAttributeNone;
|
||||
def.callAsFunction = => NativeFunctionCallback;
|
||||
def.finalize = => NativeFunctionFinalize;
|
||||
instance = JSClassCreate(&def);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static JSObjectRef CreateJsFunctionFromDelegate(JSContextRef context, JSCallback ownCallback)
|
||||
{
|
||||
return JSObjectMake(context, NativeFunctionClass(), Internal.UnsafeCastToPtr(ownCallback));
|
||||
}
|
||||
}
|
||||
@@ -1,86 +1,78 @@
|
||||
using Ultralight.CAPI;
|
||||
using System;
|
||||
using GlitchyEngine;
|
||||
|
||||
namespace GlitchyEditor.Ultralight;
|
||||
|
||||
class UltralightMainWindow : UltralightWindow
|
||||
{
|
||||
public this() : base("Glitchy Engine", "file:///dist/index.html") // "file:///react-ui-prototype/dist/index.html"
|
||||
//public this() : base("Entity Hierarchy", "file:///another-react-test/index.html")
|
||||
public this() : base("Glitchy Engine", "file:///dist/index.html")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
///
|
||||
/// This callback is bound to a JavaScript function on the page.
|
||||
///
|
||||
static JSValueRef GetMessage(JSContextRef ctx, JSObjectRef fn, JSObjectRef thisObject, uint32 argumentCount, JSValueRef* arguments, JSValueRef* exception) {
|
||||
///
|
||||
/// Create a JavaScript String from a C-string, initialize it with our
|
||||
/// welcome message.
|
||||
///
|
||||
JSStringRef str = JSStringCreateWithUTF8CString("Hello from Beef!");
|
||||
private bool _hoveringNonClientArea;
|
||||
|
||||
///
|
||||
/// Create a garbage-collected JSValue using the String we just created.
|
||||
///
|
||||
/// **Note**:
|
||||
/// Both JSValueRef and JSObjectRef types are garbage-collected types. (And actually,
|
||||
/// JSObjectRef is just a typedef of JSValueRef, they share definitions).
|
||||
///
|
||||
/// The garbage collector in JavaScriptCore periodically scans the entire stack to check if
|
||||
/// there are any active JSValueRefs, and marks those with no references for destruction.
|
||||
///
|
||||
/// If you happen to store a JSValueRef/JSObjectRef in heap memory or in memory unreachable
|
||||
/// by the stack-based garbage-collector, you should explicitly call JSValueProtect() and
|
||||
/// JSValueUnprotect() on the reference to ensure it is kept alive.
|
||||
///
|
||||
JSValueRef value = JSValueMakeString(ctx, str);
|
||||
void HandleHoverNonClientArea(JSContextRef context, JSObjectRef thisObject, Span<JSValueRef> arguments, JSValueRef* exception = null)
|
||||
{
|
||||
Log.EngineLogger.Info("HandleHoverNonClientArea");
|
||||
|
||||
///
|
||||
/// Release the string we created earlier (we only Release what we Create).
|
||||
///
|
||||
JSStringRelease(str);
|
||||
if (arguments.Length != 1)
|
||||
{
|
||||
Log.EngineLogger.Error("EngineGlue.setHoverNonClientArea: called with wrong number of arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
return value;
|
||||
if (!JSValueIsBoolean(context, arguments[0]))
|
||||
{
|
||||
Log.EngineLogger.Error($"EngineGlue.setHoverNonClientArea: expected boolean, but received {JSValueGetType(context, arguments[0])} instead.");
|
||||
return;
|
||||
}
|
||||
|
||||
_hoveringNonClientArea = JSValueToBoolean(context, arguments[0]);
|
||||
|
||||
Log.ClientLogger.Info($"_hoveringNonClientArea: {_hoveringNonClientArea}");
|
||||
}
|
||||
|
||||
private void RegisterBeefFunction(JSContextRef context, JSObjectRef object, StringView functionName, JSCallback callback)
|
||||
{
|
||||
JSObjectRef func = UltralightHelper.CreateJsFunctionFromDelegate(context, callback);
|
||||
|
||||
JSStringRef name = JSStringCreateWithUTF8CString(functionName.ToScopeCStr!());
|
||||
|
||||
JSObjectRef exception;
|
||||
JSObjectSetProperty(context, object, name, func, 0, &exception);
|
||||
|
||||
JSStringRelease(name);
|
||||
}
|
||||
|
||||
|
||||
private void RegisterEngineGlueFunctions()
|
||||
{
|
||||
JSContextRef context = ulViewLockJSContext(_view);
|
||||
defer ulViewUnlockJSContext(_view);
|
||||
|
||||
JSObjectRef globalObject = JSContextGetGlobalObject(context);
|
||||
|
||||
JSValueRef exception = null;
|
||||
JSStringRef scriptGlueName = JSStringCreateWithUTF8CString("EngineGlue");
|
||||
JSValueRef scriptGlue = JSObjectGetProperty(context, globalObject, scriptGlueName, &exception);
|
||||
JSStringRelease(scriptGlueName);
|
||||
|
||||
if (JSValueGetType(context, scriptGlue) == .kJSTypeUndefined)
|
||||
{
|
||||
Log.EngineLogger.Error("Failed to get EngineGlue object from JS context.");
|
||||
return;
|
||||
}
|
||||
|
||||
StdAllocator stdAlloc = StdAllocator();
|
||||
|
||||
RegisterBeefFunction(context, scriptGlue, "setHoverNonClientArea", new:stdAlloc => HandleHoverNonClientArea);
|
||||
}
|
||||
|
||||
protected override void OnDOMReady(C_View* caller, uint64 frame_id, bool is_main_frame, C_String* url)
|
||||
{
|
||||
///
|
||||
/// Acquire the page's JavaScript execution context.
|
||||
///
|
||||
/// This locks the JavaScript context so we can modify it safely on this thread, we need to
|
||||
/// unlock it when we're done via ulViewUnlockJSContext().
|
||||
///
|
||||
JSContextRef ctx = ulViewLockJSContext(_view);
|
||||
RegisterEngineGlueFunctions();
|
||||
|
||||
///
|
||||
/// Create a JavaScript String containing the name of our callback.
|
||||
///
|
||||
JSStringRef name = JSStringCreateWithUTF8CString("OnCreateEntityClick");
|
||||
|
||||
///
|
||||
/// Create a garbage-collected JavaScript function that is bound to our native C callback
|
||||
/// 'GetMessage()'.
|
||||
///
|
||||
JSObjectRef func = JSObjectMakeFunctionWithCallback(ctx, name, => GetMessage);
|
||||
|
||||
///
|
||||
/// Store our function in the page's global JavaScript object so that it is accessible from the
|
||||
/// page as 'GetMessage()'.
|
||||
///
|
||||
/// The global JavaScript object is also known as 'window' in JS.
|
||||
///
|
||||
JSObjectSetProperty(ctx, JSContextGetGlobalObject(ctx), name, func, 0, null);
|
||||
|
||||
///
|
||||
/// Release the JavaScript String we created earlier.
|
||||
///
|
||||
JSStringRelease(name);
|
||||
|
||||
///
|
||||
/// Unlock the JS context so other threads can modify JavaScript state.
|
||||
///
|
||||
ulViewUnlockJSContext(_view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,8 +61,9 @@ abstract class UltralightWindow
|
||||
{
|
||||
void* userData = Internal.UnsafeCastToPtr(this);
|
||||
|
||||
ulViewSetDOMReadyCallback(_view, => OnDOMReady, userData);
|
||||
ulViewSetFailLoadingCallback(_view, => OnFailedLoading, userData);
|
||||
ulViewSetWindowObjectReadyCallback(_view, => OnWindowObjectReady, userData);
|
||||
ulViewSetDOMReadyCallback(_view, => OnDOMReady, userData);
|
||||
|
||||
ulViewSetAddConsoleMessageCallback(_view, => OnAddConsoleMessage, userData);
|
||||
|
||||
@@ -235,12 +236,15 @@ abstract class UltralightWindow
|
||||
ulDestroyMouseEvent(evt);
|
||||
|
||||
_cursorPosition = .(e.PositionX, e.PositionY);
|
||||
Log.EngineLogger.Warning($"{_cursorPosition.X} {_cursorPosition.Y}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool MousePressed(MouseButtonEvent e, bool press)
|
||||
{
|
||||
Log.ClientLogger.Warning($"{e.MouseButton} {press}");
|
||||
|
||||
ULMouseButton button = .kMouseButton_None;
|
||||
|
||||
switch (e.MouseButton)
|
||||
@@ -262,6 +266,14 @@ abstract class UltralightWindow
|
||||
|
||||
#region Ultralight Callbacks
|
||||
|
||||
private static void OnWindowObjectReady(void* user_data, ULView caller, uint64 frame_id, bool is_main_frame, ULString url)
|
||||
{
|
||||
UltralightWindow window = (UltralightWindow)Internal.UnsafeCastToObject(user_data);
|
||||
window.OnWindowObjectReady(caller, frame_id, is_main_frame, url);
|
||||
}
|
||||
|
||||
protected virtual void OnWindowObjectReady(ULView caller, uint64 frame_id, bool is_main_frame, ULString url) { }
|
||||
|
||||
private static void OnDOMReady(void* user_data, ULView caller, uint64 frame_id, bool is_main_frame, ULString url)
|
||||
{
|
||||
UltralightWindow window = (UltralightWindow)Internal.UnsafeCastToObject(user_data);
|
||||
|
||||
@@ -41,6 +41,18 @@ namespace GlitchyEngine.UI
|
||||
|
||||
private HCURSOR _cursor;
|
||||
|
||||
private WindowStyle _windowStyle;
|
||||
|
||||
enum TitleBarButton
|
||||
{
|
||||
Minimize,
|
||||
Maximize,
|
||||
Close,
|
||||
None
|
||||
}
|
||||
|
||||
private TitleBarButton _hoveredTitleBarButton;
|
||||
|
||||
public override int32 MinWidth
|
||||
{
|
||||
get => _minMaxInfo.MinimumTrackingSize.x;
|
||||
@@ -217,7 +229,7 @@ namespace GlitchyEngine.UI
|
||||
else
|
||||
WindowClass.Icon = 0;
|
||||
|
||||
WindowClass.Cursor = LoadCursorW(0, IDC_ARROW);
|
||||
_cursor = WindowClass.Cursor = LoadCursorW(0, IDC_ARROW);
|
||||
WindowClass.BackgroundBrush = (HBRUSH)SystemColor.WindowFrame;
|
||||
|
||||
#unwarn
|
||||
@@ -234,6 +246,19 @@ namespace GlitchyEngine.UI
|
||||
[LinkName(.C)]
|
||||
static extern Windows.IntBool SetLayeredWindowAttributes(HWND hwnd, uint32 crKey, uint8 bAlpha, DWORD dwFlags);
|
||||
|
||||
[CRepr]
|
||||
enum DpiAwarenessContext {
|
||||
Invalid = -1,
|
||||
Unaware = 0,
|
||||
SystemAware = 1,
|
||||
PerMonitorAware = 2
|
||||
}
|
||||
|
||||
Handle DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = (.)-4;
|
||||
|
||||
[CLink]
|
||||
static extern Windows.IntBool SetProcessDpiAwarenessContext(HANDLE value);
|
||||
|
||||
private void Init(WindowDescription desc)
|
||||
{
|
||||
Debug.Profiler.ProfileFunction!();
|
||||
@@ -242,6 +267,12 @@ namespace GlitchyEngine.UI
|
||||
|
||||
if (WindowClass == default)
|
||||
{
|
||||
// Support high-dpi screens
|
||||
if (!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
|
||||
{
|
||||
Log.EngineLogger.Error("Could not set DPI awareness.");
|
||||
}
|
||||
|
||||
CreateWindowClass(desc.Icon);
|
||||
}
|
||||
|
||||
@@ -250,25 +281,30 @@ namespace GlitchyEngine.UI
|
||||
|
||||
DirectX.Windows.WindowStyles style = .WS_VISIBLE;
|
||||
|
||||
_windowStyle = desc.WindowStyle;
|
||||
|
||||
switch (desc.WindowStyle)
|
||||
{
|
||||
case .Normal:
|
||||
style |= .WS_OVERLAPPEDWINDOW | .WS_VISIBLE;
|
||||
case .Borderless:
|
||||
style |= .WS_POPUP | .WS_THICKFRAME | .WS_CAPTION | .WS_SYSMENU | .WS_MAXIMIZEBOX | .WS_MINIMIZEBOX;
|
||||
case .CustomTitle:
|
||||
style |= .WS_THICKFRAME // required for a standard resizeable window
|
||||
| .WS_SYSMENU // Explicitly ask for the titlebar to support snapping via Win + ← / Win + →
|
||||
| .WS_MAXIMIZEBOX // Add maximize button to support maximizing via mouse dragging to the top of the screen
|
||||
| .WS_MINIMIZEBOX; // Add minimize button to support minimizing by clicking on the taskbar icon
|
||||
}
|
||||
|
||||
_windowHandle = CreateWindowExW(.None, WindowClass.ClassName, desc.Title.ToScopedNativeWChar!(),
|
||||
void* selfPointer = Internal.UnsafeCastToPtr(this);
|
||||
_windowHandle = CreateWindowExW(.WS_EX_APPWINDOW, WindowClass.ClassName, desc.Title.ToScopedNativeWChar!(),
|
||||
style,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, desc.Width, desc.Height, 0, 0, (.)_instanceHandle, null);
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, desc.Width, desc.Height, 0, 0, (.)_instanceHandle, selfPointer);
|
||||
}
|
||||
|
||||
_swapChain = new SwapChain(this);
|
||||
_swapChain.Init();
|
||||
|
||||
void* myPtr = Internal.UnsafeCastToPtr(this);
|
||||
SetWindowLongPtrW(_windowHandle, GWL_USERDATA, (int)myPtr);
|
||||
|
||||
LoadWindowRectangle();
|
||||
|
||||
Log.EngineLogger.Trace($"Created window \"{Title}\" ({Width}, {Height})");
|
||||
@@ -300,6 +336,26 @@ namespace GlitchyEngine.UI
|
||||
|
||||
internal int2 _rawMouseMovementAccumulator;
|
||||
|
||||
[CLink]
|
||||
private static extern IntBool InvalidateRect(HWND hWnd, in DirectX.Math.Rectangle lpRect, IntBool bErase);
|
||||
|
||||
[CRepr]
|
||||
private struct CreateStructW
|
||||
{
|
||||
public LPVOID lpCreateParams;
|
||||
public HINSTANCE hInstance;
|
||||
public HMENU hMenu;
|
||||
public HWND hwndParent;
|
||||
public int cy;
|
||||
public int cx;
|
||||
public int y;
|
||||
public int x;
|
||||
public LONG style;
|
||||
public LPCWSTR lpszName;
|
||||
public LPCWSTR lpszClass;
|
||||
public DWORD dwExStyle;
|
||||
}
|
||||
|
||||
private static LRESULT MessageHandler(HWND hwnd, uint32 uMsg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
void* windowPtr = (void*)GetWindowLongPtrW(hwnd, GWL_USERDATA);
|
||||
@@ -307,7 +363,18 @@ namespace GlitchyEngine.UI
|
||||
|
||||
if (window == null)
|
||||
{
|
||||
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
||||
if (uMsg == WM_CREATE)
|
||||
{
|
||||
// The pointer to our beef window is passed via lpParam of CreateWindowExW
|
||||
CreateStructW* createStruct = (CreateStructW*)(void*)lParam;
|
||||
void* windowPointer = createStruct.lpCreateParams;
|
||||
window = (Window)Internal.UnsafeCastToObject(windowPointer);
|
||||
SetWindowLongPtrW(hwnd, GWL_USERDATA, (int)windowPointer);
|
||||
}
|
||||
else
|
||||
{
|
||||
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.ImGuiImplWin32.WndProcHandler(hwnd, uMsg, wParam, lParam);
|
||||
@@ -333,7 +400,7 @@ namespace GlitchyEngine.UI
|
||||
|
||||
SplitHighAndLowOrder!(lParam, out window._clientRect.Width, out window._clientRect.Height);
|
||||
|
||||
if(window._clientRect.Width != 0 && window._clientRect.Height != 0)
|
||||
if(window._swapChain != null && window._clientRect.Width != 0 && window._clientRect.Height != 0)
|
||||
{
|
||||
window._swapChain.Width = (.)window._clientRect.Width;
|
||||
window._swapChain.Height = (.)window._clientRect.Height;
|
||||
@@ -379,6 +446,12 @@ namespace GlitchyEngine.UI
|
||||
|
||||
case 0x0006: //WM_ACTIVATE
|
||||
{
|
||||
if (window._windowStyle == .CustomTitle)
|
||||
{
|
||||
DirectX.Math.Rectangle title_bar_rect = win32_titlebar_rect(hwnd);
|
||||
InvalidateRect(hwnd, title_bar_rect, false);
|
||||
}
|
||||
|
||||
SplitHighAndLowOrder!((int64)wParam, let lowWParam, let highWParam);
|
||||
|
||||
bool isActivate = lowWParam > 0;
|
||||
@@ -418,11 +491,31 @@ namespace GlitchyEngine.UI
|
||||
////
|
||||
|
||||
// Left mouse button
|
||||
case 0x00A1 /* WM_NCLBUTTONDOWN */:
|
||||
// Custom title windows need to handle mouse events outside of client area.
|
||||
if (window._windowStyle == .CustomTitle)
|
||||
{
|
||||
var event = scope MouseButtonPressedEvent(.LeftButton);
|
||||
window._eventCallback(event);
|
||||
|
||||
// if hovering a button
|
||||
return 0;
|
||||
}
|
||||
case WM_LBUTTONDOWN:
|
||||
{
|
||||
var event = scope MouseButtonPressedEvent(.LeftButton);
|
||||
window._eventCallback(event);
|
||||
}
|
||||
case 0x00A2 /* WM_NCLBUTTONUP */:
|
||||
// Custom title windows need to handle mouse events outside of client area.
|
||||
if (window._windowStyle == .CustomTitle)
|
||||
{
|
||||
var event = scope MouseButtonReleasedEvent(.LeftButton);
|
||||
window._eventCallback(event);
|
||||
|
||||
// if hovering a button
|
||||
return 0;
|
||||
}
|
||||
case WM_LBUTTONUP:
|
||||
{
|
||||
var event = scope MouseButtonReleasedEvent(.LeftButton);
|
||||
@@ -489,11 +582,29 @@ namespace GlitchyEngine.UI
|
||||
var event = scope MouseScrolledEvent(rotation, 0);
|
||||
window._eventCallback(event);
|
||||
}
|
||||
case 0x00A0 /*WM_NCMOUSEMOVE*/:
|
||||
// Custom title windows need to handle mouse events outside of client area.
|
||||
if (window._windowStyle == .CustomTitle)
|
||||
fallthrough;
|
||||
case WM_MOUSEMOVE:
|
||||
{
|
||||
// If the mouse gets into the client area then no title bar buttons are hovered
|
||||
// so need to reset the hover state
|
||||
if (window._windowStyle == .CustomTitle && window._hoveredTitleBarButton != .None)
|
||||
{
|
||||
DirectX.Math.Rectangle title_bar_rect = win32_titlebar_rect(hwnd);
|
||||
// You could do tighter invalidation here but probably doesn't matter
|
||||
InvalidateRect(hwnd, title_bar_rect, false);
|
||||
window._hoveredTitleBarButton = .None;
|
||||
}
|
||||
|
||||
SplitHighAndLowOrder!(lParam, let x, let y);
|
||||
|
||||
var event = scope MouseMovedEvent(x, y);
|
||||
DirectX.Windows.POINT p = .(x, y);
|
||||
|
||||
ScreenToClient(hwnd, &p);
|
||||
|
||||
var event = scope MouseMovedEvent(p.x, p.y);
|
||||
window._eventCallback(event);
|
||||
}
|
||||
/*case WM_INPUT:
|
||||
@@ -561,11 +672,151 @@ namespace GlitchyEngine.UI
|
||||
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* Removes the Windowframe
|
||||
*/
|
||||
case WM_NCCALCSIZE:
|
||||
if (window._windowStyle != .CustomTitle)
|
||||
break;
|
||||
|
||||
if (wParam == 0)
|
||||
break;
|
||||
|
||||
uint32 dpi = GetDpiForWindow(hwnd);
|
||||
|
||||
int32 frame_x = GetSystemMetricsForDpi(SM_CXFRAME, dpi);
|
||||
int32 frame_y = GetSystemMetricsForDpi(SM_CYFRAME, dpi);
|
||||
int32 padding = GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
|
||||
|
||||
NCCALCSIZE_PARAMS* sizeParams = (NCCALCSIZE_PARAMS*)(void*)lParam;
|
||||
Rect* requested_client_rect = &sizeParams.rgrc[0];
|
||||
|
||||
requested_client_rect.right -= frame_x + padding;
|
||||
requested_client_rect.left += frame_x + padding;
|
||||
requested_client_rect.bottom -= frame_y + padding;
|
||||
|
||||
if (win32_window_is_maximized(hwnd)) {
|
||||
requested_client_rect.top += padding;
|
||||
}
|
||||
|
||||
return 0;
|
||||
case WM_CREATE:
|
||||
if (window._windowStyle != .CustomTitle)
|
||||
break;
|
||||
|
||||
GetWindowRect(hwnd, let size_rect);
|
||||
|
||||
// Inform the application of the frame change to force redrawing with the new
|
||||
// client area that is extended into the title bar
|
||||
SetWindowPos(
|
||||
hwnd, 0,
|
||||
size_rect.Left, size_rect.Top,
|
||||
size_rect.Right - size_rect.Left, size_rect.Bottom - size_rect.Top,
|
||||
0x0020 | 0x0002 | 0x0001 /*SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE*/
|
||||
);
|
||||
case 0x0084: /* WM_NCHITTEST */
|
||||
if (window._windowStyle != .CustomTitle)
|
||||
break;
|
||||
|
||||
// Let the default procedure handle resizing areas
|
||||
LRESULT hit = DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
||||
switch (hit)
|
||||
{
|
||||
case 0 /* HTNOWHERE */,
|
||||
10 /* HTLEFT */,
|
||||
11 /* HTRIGHT */,
|
||||
12 /* HTTOP */,
|
||||
13 /* HTTOPLEFT */,
|
||||
14 /* HTTOPRIGHT */,
|
||||
15 /* HTBOTTOM */,
|
||||
16 /* HTBOTTOMLEFT */,
|
||||
17 /* HTBOTTOMRIGHT */:
|
||||
return hit;
|
||||
}
|
||||
|
||||
// Check if hover button is on maximize to support SnapLayout on Windows 11
|
||||
if (window._hoveredTitleBarButton == .Maximize) {
|
||||
return 9 /* HTMAXBUTTON */;
|
||||
}
|
||||
|
||||
// Looks like adjustment happening in NCCALCSIZE is messing with the detection
|
||||
// of the top hit area so manually fixing that.
|
||||
uint32 dpi = GetDpiForWindow(hwnd);
|
||||
int frame_y = GetSystemMetricsForDpi(SM_CYFRAME, dpi);
|
||||
int padding = GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
|
||||
POINT cursor_point = .();
|
||||
|
||||
SplitHighAndLowOrder(lParam, out cursor_point.x, out cursor_point.y);
|
||||
ScreenToClient(hwnd, &cursor_point);
|
||||
if (cursor_point.y > 0 && cursor_point.y < frame_y + padding) {
|
||||
return 12 /* HTTOP */;
|
||||
}
|
||||
|
||||
// Since we are drawing our own caption, this needs to be a custom test
|
||||
if (cursor_point.y < win32_titlebar_rect(hwnd).Bottom) {
|
||||
return 2 /* HTCAPTION */;
|
||||
}
|
||||
|
||||
return 1 /* HTCLIENT */;
|
||||
}
|
||||
|
||||
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
static bool win32_window_is_maximized(HWND handle)
|
||||
{
|
||||
WINDOWPLACEMENT placement = .();
|
||||
placement.length = sizeof(WINDOWPLACEMENT);
|
||||
if (GetWindowPlacement(handle, &placement))
|
||||
{
|
||||
return placement.showCmd == SW_SHOWMAXIMIZED;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[CRepr]
|
||||
struct WINDOWPLACEMENT
|
||||
{
|
||||
public uint32 length;
|
||||
public uint32 flags;
|
||||
public uint32 showCmd;
|
||||
public POINT ptMinPosition;
|
||||
public POINT ptMaxPosition;
|
||||
public Rect rcNormalPosition;
|
||||
}
|
||||
|
||||
[CLink]
|
||||
private static extern IntBool GetWindowPlacement(HWND hWnd, WINDOWPLACEMENT* lpwndpl);
|
||||
|
||||
[CRepr]
|
||||
private struct NCCALCSIZE_PARAMS
|
||||
{
|
||||
public Rect[3] rgrc;
|
||||
public WINDOWPOS* lppos;
|
||||
}
|
||||
|
||||
[CRepr]
|
||||
struct WINDOWPOS
|
||||
{
|
||||
public HWND hwnd;
|
||||
public HWND hwndInsertAfter;
|
||||
public int32 x;
|
||||
public int32 y;
|
||||
public int32 cx;
|
||||
public int32 cy;
|
||||
public uint32 flags;
|
||||
}
|
||||
|
||||
[CLink]
|
||||
private static extern uint32 GetDpiForWindow(HWND hwnd);
|
||||
|
||||
[CLink]
|
||||
private static extern int32 GetSystemMetricsForDpi(int32 nIndex, uint32 dpi);
|
||||
|
||||
private const int32 SM_CXFRAME = 32;
|
||||
private const int32 SM_CYFRAME = 33;
|
||||
private const int32 SM_CXPADDEDBORDER = 92;
|
||||
|
||||
/**
|
||||
* Converts the wParam and lParam of a WM_KEYDOWN or WM_KEYUP message to the corresponding engine keycode.
|
||||
*/
|
||||
@@ -589,6 +840,51 @@ namespace GlitchyEngine.UI
|
||||
}
|
||||
}
|
||||
|
||||
struct SIZE
|
||||
{
|
||||
public LONG cx;
|
||||
public LONG cy;
|
||||
};
|
||||
|
||||
[CLink, Import("UxTheme.lib")]
|
||||
private static extern Handle OpenThemeData(HWND hwnd, LPCWSTR pszClassList);
|
||||
|
||||
[CLink, Import("UxTheme.lib")]
|
||||
private static extern HResult CloseThemeData(Handle hTheme);
|
||||
|
||||
enum ThemeSize
|
||||
{
|
||||
Min, // minimum size
|
||||
True, // size without stretching
|
||||
Draw // size that theme mgr will use to draw part
|
||||
};
|
||||
|
||||
[CLink, Import("UxTheme.lib")]
|
||||
private static extern HResult GetThemePartSize(Handle hTheme, Handle hdc, int32 iPartId, int32 iStateId, Rect* prc, ThemeSize eSize, SIZE* psz);
|
||||
|
||||
static int32 win32_dpi_scale(int32 value, uint32 dpi)
|
||||
{
|
||||
return (int32)((float)value * dpi / 96);
|
||||
}
|
||||
|
||||
// Adopted from:
|
||||
// https://github.com/oberth/custom-chrome/blob/master/source/gui/window_helper.hpp#L52-L64
|
||||
static DirectX.Math.Rectangle win32_titlebar_rect(HWND handle)
|
||||
{
|
||||
SIZE title_bar_size = .();
|
||||
const int top_and_bottom_borders = 2;
|
||||
Handle theme = OpenThemeData(handle, "WINDOW".ToScopedNativeWChar!());
|
||||
uint32 dpi = GetDpiForWindow(handle);
|
||||
GetThemePartSize(theme, 0, 1/*WP_CAPTION*/, 1/*CS_ACTIVE*/, null, .True, &title_bar_size);
|
||||
CloseThemeData(theme);
|
||||
|
||||
int32 height = win32_dpi_scale(title_bar_size.cy, dpi) + top_and_bottom_borders;
|
||||
|
||||
GetClientRect(handle, var rect);
|
||||
rect.Bottom = rect.Top + height;
|
||||
return rect;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
Debug.Profiler.ProfileFunction!();
|
||||
|
||||
@@ -8,7 +8,8 @@ namespace GlitchyEngine.UI
|
||||
enum WindowStyle
|
||||
{
|
||||
Normal,
|
||||
Borderless
|
||||
Borderless,
|
||||
CustomTitle
|
||||
}
|
||||
|
||||
public struct WindowDescription
|
||||
|
||||
Vendored
+1
-1
Submodule GlitchyEngine/vendor/Ultralight-beef updated: 7d019ad1ac...391b045d6b
Reference in New Issue
Block a user