mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
760 lines
28 KiB
C#
760 lines
28 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading.Tasks;
|
|
using OpenToolkit.GraphicsLibraryFramework;
|
|
using Robust.Client.Utility;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Utility;
|
|
using SixLabors.ImageSharp.PixelFormats;
|
|
using TerraFX.Interop.Windows;
|
|
using TerraFX.Interop.Xlib;
|
|
using GlfwImage = OpenToolkit.GraphicsLibraryFramework.Image;
|
|
using Monitor = OpenToolkit.GraphicsLibraryFramework.Monitor;
|
|
using Window = OpenToolkit.GraphicsLibraryFramework.Window;
|
|
using X11Window = TerraFX.Interop.Xlib.Window;
|
|
|
|
namespace Robust.Client.Graphics.Clyde
|
|
{
|
|
internal partial class Clyde
|
|
{
|
|
private sealed unsafe partial class GlfwWindowingImpl
|
|
{
|
|
private int _nextWindowId = 1;
|
|
|
|
public void WindowSetTitle(WindowReg window, string title)
|
|
{
|
|
CheckWindowDisposed(window);
|
|
|
|
if (title == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(title));
|
|
}
|
|
|
|
var reg = (GlfwWindowReg) window;
|
|
|
|
SendCmd(new CmdWinSetTitle((nint) reg.GlfwWindow, title));
|
|
}
|
|
|
|
private void WinThreadWinSetTitle(CmdWinSetTitle cmd)
|
|
{
|
|
GLFW.SetWindowTitle((Window*) cmd.Window, cmd.Title);
|
|
}
|
|
|
|
public void WindowSetMonitor(WindowReg window, IClydeMonitor monitor)
|
|
{
|
|
CheckWindowDisposed(window);
|
|
|
|
var winReg = (GlfwWindowReg) window;
|
|
|
|
var monitorImpl = (MonitorHandle) monitor;
|
|
|
|
SendCmd(new CmdWinSetMonitor(
|
|
(nint) winReg.GlfwWindow,
|
|
monitorImpl.Id,
|
|
0, 0,
|
|
monitorImpl.Size.X, monitorImpl.Size.Y,
|
|
monitorImpl.RefreshRate));
|
|
}
|
|
|
|
private void WinThreadWinSetMonitor(CmdWinSetMonitor cmd)
|
|
{
|
|
Monitor* monitorPtr;
|
|
if (cmd.MonitorId == 0)
|
|
{
|
|
monitorPtr = null;
|
|
}
|
|
else if (_winThreadMonitors.TryGetValue(cmd.MonitorId, out var monitorReg))
|
|
{
|
|
monitorPtr = monitorReg.Ptr;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
GLFW.SetWindowMonitor(
|
|
(Window*) cmd.Window,
|
|
monitorPtr,
|
|
cmd.X, cmd.Y,
|
|
cmd.W, cmd.H,
|
|
cmd.RefreshRate
|
|
);
|
|
}
|
|
|
|
public void WindowSetSize(WindowReg window, Vector2i size)
|
|
{
|
|
var reg = (GlfwWindowReg) window;
|
|
|
|
SendCmd(new CmdWinSetSize((nint) reg.GlfwWindow, size.X, size.Y));
|
|
}
|
|
|
|
public void WindowSetVisible(WindowReg window, bool visible)
|
|
{
|
|
var reg = (GlfwWindowReg) window;
|
|
reg.IsVisible = visible;
|
|
|
|
SendCmd(new CmdWinSetVisible((nint) reg.GlfwWindow, visible));
|
|
}
|
|
|
|
private void WinThreadWinSetSize(CmdWinSetSize cmd)
|
|
{
|
|
var win = (Window*) cmd.Window;
|
|
|
|
GLFW.SetWindowSize(win, cmd.W, cmd.H);
|
|
}
|
|
|
|
private void WinThreadWinSetVisible(CmdWinSetVisible cmd)
|
|
{
|
|
var win = (Window*) cmd.Window;
|
|
|
|
if (cmd.Visible)
|
|
{
|
|
GLFW.ShowWindow(win);
|
|
}
|
|
else
|
|
{
|
|
GLFW.HideWindow(win);
|
|
}
|
|
}
|
|
|
|
public void WindowRequestAttention(WindowReg window)
|
|
{
|
|
CheckWindowDisposed(window);
|
|
|
|
var reg = (GlfwWindowReg) window;
|
|
|
|
SendCmd(new CmdWinRequestAttention((nint) reg.GlfwWindow));
|
|
}
|
|
|
|
private void WinThreadWinRequestAttention(CmdWinRequestAttention cmd)
|
|
{
|
|
var win = (Window*) cmd.Window;
|
|
|
|
GLFW.RequestWindowAttention(win);
|
|
}
|
|
|
|
public void WindowSwapBuffers(WindowReg window)
|
|
{
|
|
CheckWindowDisposed(window);
|
|
|
|
var reg = (GlfwWindowReg) window;
|
|
|
|
GLFW.SwapBuffers(reg.GlfwWindow);
|
|
}
|
|
|
|
public void UpdateMainWindowMode()
|
|
{
|
|
if (_clyde._mainWindow == null)
|
|
return;
|
|
|
|
var win = (GlfwWindowReg) _clyde._mainWindow;
|
|
if (_clyde._windowMode == WindowMode.Fullscreen)
|
|
{
|
|
win.PrevWindowSize = win.WindowSize;
|
|
win.PrevWindowPos = win.WindowPos;
|
|
|
|
SendCmd(new CmdWinSetFullscreen((nint) win.GlfwWindow));
|
|
}
|
|
else
|
|
{
|
|
SendCmd(new CmdWinSetMonitor(
|
|
(nint) win.GlfwWindow,
|
|
0,
|
|
win.PrevWindowPos.X, win.PrevWindowPos.Y,
|
|
win.PrevWindowSize.X, win.PrevWindowSize.Y,
|
|
0
|
|
));
|
|
}
|
|
}
|
|
|
|
private void WinThreadWinSetFullscreen(CmdWinSetFullscreen cmd)
|
|
{
|
|
var ptr = (Window*) cmd.Window;
|
|
//GLFW.GetWindowSize(ptr, out var w, out var h);
|
|
//GLFW.GetWindowPos(ptr, out var x, out var y);
|
|
|
|
var monitor = MonitorForWindow(ptr);
|
|
var mode = GLFW.GetVideoMode(monitor);
|
|
|
|
GLFW.SetWindowMonitor(
|
|
ptr,
|
|
monitor,
|
|
0, 0,
|
|
mode->Width, mode->Height,
|
|
mode->RefreshRate);
|
|
|
|
SendEvent(new EventSetFullscreenAck());
|
|
}
|
|
|
|
// glfwGetWindowMonitor only works for fullscreen windows.
|
|
// Picks the monitor with the top-left corner of the window.
|
|
private Monitor* MonitorForWindow(Window* window)
|
|
{
|
|
GLFW.GetWindowPos(window, out var winPosX, out var winPosY);
|
|
var monitors = GLFW.GetMonitorsRaw(out var count);
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
var monitor = monitors[i];
|
|
GLFW.GetMonitorPos(monitor, out var monPosX, out var monPosY);
|
|
var videoMode = GLFW.GetVideoMode(monitor);
|
|
|
|
var box = Box2i.FromDimensions(monPosX, monPosY, videoMode->Width, videoMode->Height);
|
|
if (box.Contains(winPosX, winPosY))
|
|
return monitor;
|
|
}
|
|
|
|
// Fallback
|
|
return GLFW.GetPrimaryMonitor();
|
|
}
|
|
|
|
public uint? WindowGetX11Id(WindowReg window)
|
|
{
|
|
CheckWindowDisposed(window);
|
|
|
|
var reg = (GlfwWindowReg) window;
|
|
try
|
|
{
|
|
return GLFW.GetX11Window(reg.GlfwWindow);
|
|
}
|
|
catch (EntryPointNotFoundException)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public nint? WindowGetX11Display(WindowReg window)
|
|
{
|
|
CheckWindowDisposed(window);
|
|
|
|
var reg = (GlfwWindowReg) window;
|
|
try
|
|
{
|
|
return GLFW.GetX11Display(reg.GlfwWindow);
|
|
}
|
|
catch (EntryPointNotFoundException)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public nint? WindowGetWin32Window(WindowReg window)
|
|
{
|
|
if (!OperatingSystem.IsWindows())
|
|
return null;
|
|
|
|
var reg = (GlfwWindowReg) window;
|
|
try
|
|
{
|
|
return GLFW.GetWin32Window(reg.GlfwWindow);
|
|
}
|
|
catch (EntryPointNotFoundException)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public (WindowReg?, string? error) WindowCreate(
|
|
GLContextSpec? spec,
|
|
WindowCreateParameters parameters,
|
|
WindowReg? share,
|
|
WindowReg? owner)
|
|
{
|
|
Window* sharePtr = null;
|
|
if (share is GlfwWindowReg glfwReg)
|
|
sharePtr = glfwReg.GlfwWindow;
|
|
|
|
Window* ownerPtr = null;
|
|
if (owner is GlfwWindowReg glfwOwnerReg)
|
|
ownerPtr = glfwOwnerReg.GlfwWindow;
|
|
|
|
var task = SharedWindowCreate(
|
|
spec,
|
|
parameters,
|
|
sharePtr,
|
|
ownerPtr);
|
|
|
|
// Block the main thread (to avoid stuff like texture uploads being problematic).
|
|
WaitWindowCreate(task);
|
|
#pragma warning disable RA0004
|
|
var (reg, errorResult) = task.Result;
|
|
#pragma warning restore RA0004
|
|
|
|
if (reg != null)
|
|
{
|
|
reg.Owner = reg.Handle;
|
|
return (reg, null);
|
|
}
|
|
|
|
var (desc, errCode) = errorResult!.Value;
|
|
return (null, (string)$"[{errCode}]: {desc}");
|
|
}
|
|
|
|
public void WindowDestroy(WindowReg window)
|
|
{
|
|
var reg = (GlfwWindowReg) window;
|
|
SendCmd(new CmdWinDestroy((nint) reg.GlfwWindow, window.Owner != null));
|
|
}
|
|
|
|
private void WaitWindowCreate(Task<GlfwWindowCreateResult> windowTask)
|
|
{
|
|
while (!windowTask.IsCompleted)
|
|
{
|
|
// Keep processing events until the window task gives either an error or success.
|
|
WaitEvents();
|
|
ProcessEvents(single: true);
|
|
}
|
|
}
|
|
|
|
private Task<GlfwWindowCreateResult> SharedWindowCreate(
|
|
GLContextSpec? glSpec,
|
|
WindowCreateParameters parameters,
|
|
Window* share, Window* owner)
|
|
{
|
|
//
|
|
// IF YOU'RE WONDERING WHY THIS IS TASK-BASED:
|
|
// I originally wanted this to be async so we could avoid blocking the main thread
|
|
// while the OS takes its stupid 100~ms just to initialize a fucking GL context.
|
|
// This doesn't *work* because
|
|
// we have to release the GL context while the shared context is being created.
|
|
// (at least on WGL, I didn't test other platforms and I don't care to.)
|
|
// Not worth it to avoid a main thread blockage by allowing Clyde to temporarily release the GL context,
|
|
// because rendering would be locked up *anyways*.
|
|
//
|
|
// Basically what I'm saying is that everything about OpenGL is a fucking mistake
|
|
// and I should get on either Veldrid or Vulkan some time.
|
|
// Probably Veldrid tbh.
|
|
//
|
|
|
|
// Yes we ping-pong this TCS through the window thread and back, deal with it.
|
|
var tcs = new TaskCompletionSource<GlfwWindowCreateResult>();
|
|
SendCmd(new CmdWinCreate(
|
|
glSpec,
|
|
parameters,
|
|
(nint) share,
|
|
(nint) owner,
|
|
tcs));
|
|
|
|
return tcs.Task;
|
|
}
|
|
|
|
private static void FinishWindowCreate(EventWindowCreate ev)
|
|
{
|
|
var (res, tcs) = ev;
|
|
|
|
tcs.TrySetResult(res);
|
|
}
|
|
|
|
private void WinThreadWinCreate(CmdWinCreate cmd)
|
|
{
|
|
var (glSpec, parameters, share, owner, tcs) = cmd;
|
|
|
|
var window = CreateGlfwWindowForRenderer(glSpec, parameters, (Window*) share, (Window*) owner);
|
|
|
|
if (window == null)
|
|
{
|
|
var err = GLFW.GetError(out var desc);
|
|
|
|
SendEvent(new EventWindowCreate(new GlfwWindowCreateResult(null, (desc, err)), tcs));
|
|
return;
|
|
}
|
|
|
|
// We can't invoke the TCS directly from the windowing thread because:
|
|
// * it'd hit the synchronization context,
|
|
// which would make (blocking) main window init more annoying.
|
|
// * it'd not be synchronized to other incoming window events correctly which might be icky.
|
|
// So we send the TCS back to the game thread
|
|
// which processes events in the correct order and has better control of stuff during init.
|
|
var reg = WinThreadSetupWindow(window);
|
|
|
|
SendEvent(new EventWindowCreate(new GlfwWindowCreateResult(reg, null), tcs));
|
|
}
|
|
|
|
private static void WinThreadWinDestroy(CmdWinDestroy cmd)
|
|
{
|
|
var window = (Window*) cmd.Window;
|
|
|
|
if (OperatingSystem.IsWindows() && cmd.hadOwner)
|
|
{
|
|
// On Windows, closing the child window causes the owner to be minimized, apparently.
|
|
// Clear owner on close to avoid this.
|
|
|
|
var hWnd = (HWND) GLFW.GetWin32Window(window);
|
|
DebugTools.Assert(hWnd != HWND.NULL);
|
|
|
|
Windows.SetWindowLongPtrW(
|
|
hWnd,
|
|
GWLP.GWLP_HWNDPARENT,
|
|
0);
|
|
}
|
|
|
|
GLFW.DestroyWindow((Window*) cmd.Window);
|
|
}
|
|
|
|
private Window* CreateGlfwWindowForRenderer(
|
|
GLContextSpec? spec,
|
|
WindowCreateParameters parameters,
|
|
Window* contextShare,
|
|
Window* ownerWindow)
|
|
{
|
|
GLFW.WindowHint(WindowHintString.X11ClassName, "RobustToolbox");
|
|
GLFW.WindowHint(WindowHintString.X11InstanceName, "RobustToolbox");
|
|
GLFW.WindowHint(WindowHintBool.ScaleToMonitor, true);
|
|
|
|
if (spec == null)
|
|
{
|
|
// No OpenGL context requested.
|
|
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.NoApi);
|
|
}
|
|
else
|
|
{
|
|
var s = spec.Value;
|
|
|
|
#if DEBUG
|
|
GLFW.WindowHint(WindowHintBool.OpenGLDebugContext, true);
|
|
#endif
|
|
|
|
GLFW.WindowHint(WindowHintInt.ContextVersionMajor, s.Major);
|
|
GLFW.WindowHint(WindowHintInt.ContextVersionMinor, s.Minor);
|
|
GLFW.WindowHint(WindowHintBool.OpenGLForwardCompat, s.Profile != GLContextProfile.Compatibility);
|
|
GLFW.WindowHint(WindowHintBool.SrgbCapable, true);
|
|
|
|
switch (s.Profile)
|
|
{
|
|
case GLContextProfile.Compatibility:
|
|
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Any);
|
|
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGlApi);
|
|
break;
|
|
case GLContextProfile.Core:
|
|
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Core);
|
|
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGlApi);
|
|
break;
|
|
case GLContextProfile.Es:
|
|
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Any);
|
|
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGlEsApi);
|
|
break;
|
|
}
|
|
|
|
GLFW.WindowHint(WindowHintContextApi.ContextCreationApi,
|
|
s.CreationApi == GLContextCreationApi.Egl
|
|
? ContextApi.EglContextApi
|
|
: ContextApi.NativeContextApi);
|
|
|
|
if (s.CreationApi == GLContextCreationApi.Egl)
|
|
WsiShared.EnsureEglAvailable();
|
|
}
|
|
|
|
Monitor* monitor = null;
|
|
if (parameters.Monitor != null &&
|
|
_winThreadMonitors.TryGetValue(parameters.Monitor.Id, out var monitorReg))
|
|
{
|
|
monitor = monitorReg.Ptr;
|
|
var mode = GLFW.GetVideoMode(monitor);
|
|
// Set refresh rate to monitor's so that GLFW doesn't manually select one.
|
|
GLFW.WindowHint(WindowHintInt.RefreshRate, mode->RefreshRate);
|
|
}
|
|
else
|
|
{
|
|
GLFW.WindowHint(WindowHintInt.RefreshRate, -1);
|
|
}
|
|
|
|
GLFW.WindowHint(WindowHintBool.Visible, false);
|
|
|
|
GLFW.WindowHint(WindowHintInt.RedBits, 8);
|
|
GLFW.WindowHint(WindowHintInt.GreenBits, 8);
|
|
GLFW.WindowHint(WindowHintInt.BlueBits, 8);
|
|
GLFW.WindowHint(WindowHintInt.AlphaBits, 8);
|
|
GLFW.WindowHint(WindowHintInt.StencilBits, 8);
|
|
|
|
GLFW.WindowHint(WindowHintBool.Decorated, (parameters.Styles & OSWindowStyles.NoTitleBar) == 0);
|
|
|
|
var window = GLFW.CreateWindow(
|
|
parameters.Width, parameters.Height,
|
|
parameters.Title,
|
|
parameters.Fullscreen ? monitor : null,
|
|
contextShare);
|
|
|
|
// Check if window failed to create.
|
|
if (window == null)
|
|
return null;
|
|
|
|
if (parameters.Maximized)
|
|
{
|
|
GLFW.GetMonitorPos(monitor, out var x, out var y);
|
|
GLFW.SetWindowPos(window, x, y);
|
|
GLFW.MaximizeWindow(window);
|
|
}
|
|
|
|
if ((parameters.Styles & OSWindowStyles.NoTitleOptions) != 0)
|
|
{
|
|
if (OperatingSystem.IsWindows())
|
|
{
|
|
var hWnd = (HWND) GLFW.GetWin32Window(window);
|
|
WsiShared.SetWindowStyleNoTitleOptionsWindows(hWnd);
|
|
}
|
|
else if (OperatingSystem.IsLinux())
|
|
{
|
|
try
|
|
{
|
|
var x11Window = (X11Window)GLFW.GetX11Window(window);
|
|
var x11Display = (Display*) GLFW.GetX11Display(window);
|
|
WsiShared.SetWindowStyleNoTitleOptionsX11(x11Display, x11Window);
|
|
}
|
|
catch (EntryPointNotFoundException)
|
|
{
|
|
_sawmill.Warning("OSWindowStyles.NoTitleOptions not implemented on this windowing manager");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_sawmill.Warning("OSWindowStyles.NoTitleOptions not implemented on this platform");
|
|
}
|
|
}
|
|
|
|
if (ownerWindow != null)
|
|
{
|
|
if (OperatingSystem.IsWindows())
|
|
{
|
|
var hWnd = (HWND) GLFW.GetWin32Window(window);
|
|
var ownerHWnd = (HWND) GLFW.GetWin32Window(ownerWindow);
|
|
DebugTools.Assert(hWnd != HWND.NULL);
|
|
|
|
Windows.SetWindowLongPtrW(
|
|
hWnd,
|
|
GWLP.GWLP_HWNDPARENT,
|
|
ownerHWnd);
|
|
}
|
|
else if (OperatingSystem.IsLinux())
|
|
{
|
|
try
|
|
{
|
|
var x11Display = (Display*) GLFW.GetX11Display(window);
|
|
var thisWindow = (X11Window)GLFW.GetX11Window(window);
|
|
var parentWindow = (X11Window)GLFW.GetX11Window(ownerWindow);
|
|
DebugTools.Assert(thisWindow != X11Window.NULL);
|
|
DebugTools.Assert(parentWindow != X11Window.NULL);
|
|
|
|
#pragma warning disable CA1806
|
|
Xlib.XSetTransientForHint(x11Display, thisWindow, parentWindow);
|
|
#pragma warning restore CA1806
|
|
}
|
|
catch (EntryPointNotFoundException)
|
|
{
|
|
_sawmill.Warning("owner windows not implemented on this windowing manager");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_sawmill.Warning("owner windows not implemented on this platform");
|
|
}
|
|
|
|
|
|
if (parameters.StartupLocation == WindowStartupLocation.CenterOwner)
|
|
{
|
|
// TODO: Maybe include window frames in size calculations here?
|
|
// Figure out frame sizes of both windows.
|
|
GLFW.GetWindowPos(ownerWindow, out var ownerX, out var ownerY);
|
|
GLFW.GetWindowSize(ownerWindow, out var ownerW, out var ownerH);
|
|
|
|
// Re-fetch this in case DPI scaling is changing it I guess.
|
|
GLFW.GetWindowSize(window, out var thisW, out var thisH);
|
|
|
|
GLFW.SetWindowPos(window, ownerX + (ownerW - thisW) / 2, ownerY + (ownerH - thisH) / 2);
|
|
}
|
|
}
|
|
|
|
if (OperatingSystem.IsWindows())
|
|
WsiShared.WindowsSharedWindowCreate((HWND) GLFW.GetWin32Window(window), _cfg);
|
|
|
|
if (parameters.Visible)
|
|
{
|
|
GLFW.ShowWindow(window);
|
|
}
|
|
|
|
return window;
|
|
}
|
|
|
|
private GlfwWindowReg WinThreadSetupWindow(Window* window)
|
|
{
|
|
var reg = new GlfwWindowReg
|
|
{
|
|
GlfwWindow = window,
|
|
Id = new WindowId(_nextWindowId++)
|
|
};
|
|
var handle = new WindowHandle(_clyde, reg);
|
|
reg.Handle = handle;
|
|
|
|
LoadWindowIcon(window);
|
|
|
|
GLFW.SetCharCallback(window, _charCallback);
|
|
GLFW.SetKeyCallback(window, _keyCallback);
|
|
GLFW.SetWindowCloseCallback(window, _windowCloseCallback);
|
|
GLFW.SetCursorPosCallback(window, _cursorPosCallback);
|
|
GLFW.SetCursorEnterCallback(window, _cursorEnterCallback);
|
|
GLFW.SetWindowSizeCallback(window, _windowSizeCallback);
|
|
GLFW.SetWindowPosCallback(window, _windowPosCallback);
|
|
GLFW.SetScrollCallback(window, _scrollCallback);
|
|
GLFW.SetMouseButtonCallback(window, _mouseButtonCallback);
|
|
GLFW.SetWindowContentScaleCallback(window, _windowContentScaleCallback);
|
|
GLFW.SetWindowIconifyCallback(window, _windowIconifyCallback);
|
|
GLFW.SetWindowFocusCallback(window, _windowFocusCallback);
|
|
|
|
GLFW.GetFramebufferSize(window, out var fbW, out var fbH);
|
|
reg.FramebufferSize = (fbW, fbH);
|
|
|
|
GLFW.GetWindowContentScale(window, out var scaleX, out var scaleY);
|
|
reg.WindowScale = new Vector2(scaleX, scaleY);
|
|
|
|
GLFW.GetWindowSize(window, out var w, out var h);
|
|
reg.PrevWindowSize = reg.WindowSize = (w, h);
|
|
|
|
GLFW.GetWindowPos(window, out var x, out var y);
|
|
reg.PrevWindowPos = (x, y);
|
|
|
|
reg.PixelRatio = reg.FramebufferSize / (Vector2) reg.WindowSize;
|
|
|
|
return reg;
|
|
}
|
|
|
|
private GlfwWindowReg? FindWindow(nint window) => FindWindow((Window*) window);
|
|
|
|
private GlfwWindowReg? FindWindow(Window* window)
|
|
{
|
|
foreach (var windowReg in _clyde._windows)
|
|
{
|
|
var glfwReg = (GlfwWindowReg) windowReg;
|
|
if (glfwReg.GlfwWindow == window)
|
|
{
|
|
return glfwReg;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public Task<string> ClipboardGetText(WindowReg mainWindow)
|
|
{
|
|
var tcs = new TaskCompletionSource<string>();
|
|
SendCmd(new CmdGetClipboard((nint) ((GlfwWindowReg) mainWindow).GlfwWindow, tcs));
|
|
return tcs.Task;
|
|
}
|
|
|
|
private static void WinThreadGetClipboard(CmdGetClipboard cmd)
|
|
{
|
|
var clipboard = GLFW.GetClipboardString((Window*) cmd.Window) ?? "";
|
|
// Don't have to care about synchronization I don't think so just fire this immediately.
|
|
cmd.Tcs.TrySetResult(clipboard);
|
|
}
|
|
|
|
public void ClipboardSetText(WindowReg mainWindow, string text)
|
|
{
|
|
SendCmd(new CmdSetClipboard((nint) ((GlfwWindowReg) mainWindow).GlfwWindow, text));
|
|
}
|
|
|
|
private static void WinThreadSetClipboard(CmdSetClipboard cmd)
|
|
{
|
|
GLFW.SetClipboardString((Window*) cmd.Window, cmd.Text);
|
|
}
|
|
|
|
public void LoadWindowIcon(Window* window)
|
|
{
|
|
var icons = _clyde.LoadWindowIcons().ToArray();
|
|
|
|
// Done if no icon (e.g., macOS)
|
|
if (icons.Length == 0)
|
|
return;
|
|
|
|
// Turn each image into a byte[] so we can actually pin their contents.
|
|
// Wish I knew a clean way to do this without allocations.
|
|
var images = icons
|
|
.Select(i => (MemoryMarshal.Cast<Rgba32, byte>(i.GetPixelSpan()).ToArray(), i.Width, i.Height))
|
|
.ToList();
|
|
|
|
// ReSharper disable once SuggestVarOrType_Elsewhere
|
|
Span<GCHandle> handles = stackalloc GCHandle[images.Count];
|
|
Span<GlfwImage> glfwImages = stackalloc GlfwImage[images.Count];
|
|
|
|
for (var i = 0; i < images.Count; i++)
|
|
{
|
|
var image = images[i];
|
|
handles[i] = GCHandle.Alloc(image.Item1, GCHandleType.Pinned);
|
|
var addrOfPinnedObject = (byte*) handles[i].AddrOfPinnedObject();
|
|
glfwImages[i] = new GlfwImage(image.Width, image.Height, addrOfPinnedObject);
|
|
}
|
|
|
|
GLFW.SetWindowIcon(window, glfwImages);
|
|
|
|
foreach (var handle in handles)
|
|
{
|
|
handle.Free();
|
|
}
|
|
}
|
|
|
|
public void GLMakeContextCurrent(WindowReg? window)
|
|
{
|
|
if (window != null)
|
|
{
|
|
CheckWindowDisposed(window);
|
|
|
|
var reg = (GlfwWindowReg)window;
|
|
|
|
GLFW.MakeContextCurrent(reg.GlfwWindow);
|
|
}
|
|
else
|
|
{
|
|
GLFW.MakeContextCurrent(null);
|
|
}
|
|
}
|
|
|
|
public void GLSwapInterval(WindowReg reg, int interval)
|
|
{
|
|
GLFW.SwapInterval(interval);
|
|
}
|
|
|
|
public void* GLGetProcAddress(string procName)
|
|
{
|
|
return (void*) GLFW.GetProcAddress(procName);
|
|
}
|
|
|
|
public void TextInputSetRect(WindowReg reg, UIBox2i rect, int cursor)
|
|
{
|
|
// Not supported on GLFW.
|
|
}
|
|
|
|
public void TextInputStart(WindowReg reg)
|
|
{
|
|
// Not properly supported on GLFW.
|
|
|
|
((GlfwWindowReg)reg).TextInputActive = true;
|
|
}
|
|
|
|
public void TextInputStop(WindowReg reg)
|
|
{
|
|
// Not properly supported on GLFW.
|
|
|
|
((GlfwWindowReg)reg).TextInputActive = false;
|
|
}
|
|
|
|
private void CheckWindowDisposed(WindowReg reg)
|
|
{
|
|
if (reg.IsDisposed)
|
|
throw new ObjectDisposedException("Window disposed");
|
|
}
|
|
|
|
private sealed class GlfwWindowReg : WindowReg
|
|
{
|
|
public Window* GlfwWindow;
|
|
|
|
// Kept around to avoid it being GCd.
|
|
public CursorImpl? Cursor;
|
|
|
|
// While GLFW does not provide proper IME APIs, we can at least emulate SDL3's StartTextInput() system.
|
|
// This will ensure some level of consistency between the backends.
|
|
public bool TextInputActive;
|
|
}
|
|
}
|
|
}
|
|
}
|