mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
Merge branch 'Soreepeong-feature/enable-viewport-alpha'
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 4s
Tag Build / Tag Build (push) Successful in 2s
Some checks failed
Build Dalamud / Build on Windows (push) Waiting to run
Build Dalamud / Check API Compatibility (push) Blocked by required conditions
Build Dalamud / Deploy dalamud-distrib staging (push) Blocked by required conditions
Rollup changes to next version / check (api14) (push) Failing after 4s
Tag Build / Tag Build (push) Successful in 2s
This commit is contained in:
commit
8dcbd52c22
5 changed files with 377 additions and 271 deletions
|
|
@ -299,11 +299,12 @@ internal sealed partial class Win32InputHandler
|
||||||
|
|
||||||
private static void ViewportFlagsToWin32Styles(ImGuiViewportFlags flags, out int style, out int exStyle)
|
private static void ViewportFlagsToWin32Styles(ImGuiViewportFlags flags, out int style, out int exStyle)
|
||||||
{
|
{
|
||||||
style = (int)(flags.HasFlag(ImGuiViewportFlags.NoDecoration) ? WS.WS_POPUP : WS.WS_OVERLAPPEDWINDOW);
|
style = (flags & ImGuiViewportFlags.NoDecoration) != 0 ? unchecked((int)WS.WS_POPUP) : WS.WS_OVERLAPPEDWINDOW;
|
||||||
exStyle =
|
exStyle = (flags & ImGuiViewportFlags.NoTaskBarIcon) != 0 ? WS.WS_EX_TOOLWINDOW : WS.WS_EX_APPWINDOW;
|
||||||
(int)(flags.HasFlag(ImGuiViewportFlags.NoTaskBarIcon) ? WS.WS_EX_TOOLWINDOW : (uint)WS.WS_EX_APPWINDOW);
|
|
||||||
exStyle |= WS.WS_EX_NOREDIRECTIONBITMAP;
|
exStyle |= WS.WS_EX_NOREDIRECTIONBITMAP;
|
||||||
if (flags.HasFlag(ImGuiViewportFlags.TopMost))
|
if ((flags & ImGuiViewportFlags.TopMost) != 0)
|
||||||
exStyle |= WS.WS_EX_TOPMOST;
|
exStyle |= WS.WS_EX_TOPMOST;
|
||||||
|
if ((flags & ImGuiViewportFlags.NoInputs) != 0)
|
||||||
|
exStyle |= WS.WS_EX_TRANSPARENT | WS.WS_EX_LAYERED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System.Text;
|
||||||
|
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
|
|
@ -34,11 +35,12 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
private readonly HCURSOR[] cursors;
|
private readonly HCURSOR[] cursors;
|
||||||
|
|
||||||
private readonly WndProcDelegate wndProcDelegate;
|
private readonly WndProcDelegate wndProcDelegate;
|
||||||
private readonly bool[] imguiMouseIsDown;
|
|
||||||
private readonly nint platformNamePtr;
|
private readonly nint platformNamePtr;
|
||||||
|
|
||||||
private ViewportHandler viewportHandler;
|
private ViewportHandler viewportHandler;
|
||||||
|
|
||||||
|
private int mouseButtonsDown;
|
||||||
|
private bool mouseTracked;
|
||||||
private long lastTime;
|
private long lastTime;
|
||||||
|
|
||||||
private nint iniPathPtr;
|
private nint iniPathPtr;
|
||||||
|
|
@ -64,7 +66,8 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors |
|
io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors |
|
||||||
ImGuiBackendFlags.HasSetMousePos |
|
ImGuiBackendFlags.HasSetMousePos |
|
||||||
ImGuiBackendFlags.RendererHasViewports |
|
ImGuiBackendFlags.RendererHasViewports |
|
||||||
ImGuiBackendFlags.PlatformHasViewports;
|
ImGuiBackendFlags.PlatformHasViewports |
|
||||||
|
ImGuiBackendFlags.HasMouseHoveredViewport;
|
||||||
|
|
||||||
this.platformNamePtr = Marshal.StringToHGlobalAnsi("imgui_impl_win32_c#");
|
this.platformNamePtr = Marshal.StringToHGlobalAnsi("imgui_impl_win32_c#");
|
||||||
io.Handle->BackendPlatformName = (byte*)this.platformNamePtr;
|
io.Handle->BackendPlatformName = (byte*)this.platformNamePtr;
|
||||||
|
|
@ -74,8 +77,6 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable))
|
if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable))
|
||||||
this.viewportHandler = new(this);
|
this.viewportHandler = new(this);
|
||||||
|
|
||||||
this.imguiMouseIsDown = new bool[5];
|
|
||||||
|
|
||||||
this.cursors = new HCURSOR[9];
|
this.cursors = new HCURSOR[9];
|
||||||
this.cursors[(int)ImGuiMouseCursor.Arrow] = LoadCursorW(default, IDC.IDC_ARROW);
|
this.cursors[(int)ImGuiMouseCursor.Arrow] = LoadCursorW(default, IDC.IDC_ARROW);
|
||||||
this.cursors[(int)ImGuiMouseCursor.TextInput] = LoadCursorW(default, IDC.IDC_IBEAM);
|
this.cursors[(int)ImGuiMouseCursor.TextInput] = LoadCursorW(default, IDC.IDC_IBEAM);
|
||||||
|
|
@ -95,8 +96,6 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
|
|
||||||
private delegate LRESULT WndProcDelegate(HWND hWnd, uint uMsg, WPARAM wparam, LPARAM lparam);
|
private delegate LRESULT WndProcDelegate(HWND hWnd, uint uMsg, WPARAM wparam, LPARAM lparam);
|
||||||
|
|
||||||
private delegate BOOL MonitorEnumProcDelegate(HMONITOR monitor, HDC hdc, RECT* rect, LPARAM lparam);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool UpdateCursor { get; set; } = true;
|
public bool UpdateCursor { get; set; } = true;
|
||||||
|
|
||||||
|
|
@ -155,6 +154,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
public void NewFrame(int targetWidth, int targetHeight)
|
public void NewFrame(int targetWidth, int targetHeight)
|
||||||
{
|
{
|
||||||
var io = ImGui.GetIO();
|
var io = ImGui.GetIO();
|
||||||
|
var focusedWindow = GetForegroundWindow();
|
||||||
|
|
||||||
io.DisplaySize.X = targetWidth;
|
io.DisplaySize.X = targetWidth;
|
||||||
io.DisplaySize.Y = targetHeight;
|
io.DisplaySize.Y = targetHeight;
|
||||||
|
|
@ -168,9 +168,9 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
|
|
||||||
this.viewportHandler.UpdateMonitors();
|
this.viewportHandler.UpdateMonitors();
|
||||||
|
|
||||||
this.UpdateMousePos();
|
this.UpdateMouseData(focusedWindow);
|
||||||
|
|
||||||
this.ProcessKeyEventsWorkarounds();
|
this.ProcessKeyEventsWorkarounds(focusedWindow);
|
||||||
|
|
||||||
// TODO: need to figure out some way to unify all this
|
// TODO: need to figure out some way to unify all this
|
||||||
// The bottom case works(?) if the caller hooks SetCursor, but otherwise causes fps issues
|
// The bottom case works(?) if the caller hooks SetCursor, but otherwise causes fps issues
|
||||||
|
|
@ -224,6 +224,40 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
|
|
||||||
switch (msg)
|
switch (msg)
|
||||||
{
|
{
|
||||||
|
case WM.WM_MOUSEMOVE:
|
||||||
|
{
|
||||||
|
if (!this.mouseTracked)
|
||||||
|
{
|
||||||
|
var tme = new TRACKMOUSEEVENT
|
||||||
|
{
|
||||||
|
cbSize = (uint)sizeof(TRACKMOUSEEVENT),
|
||||||
|
dwFlags = TME.TME_LEAVE,
|
||||||
|
hwndTrack = hWndCurrent,
|
||||||
|
};
|
||||||
|
this.mouseTracked = TrackMouseEvent(&tme);
|
||||||
|
}
|
||||||
|
|
||||||
|
var mousePos = new POINT(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
||||||
|
if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0)
|
||||||
|
ClientToScreen(hWndCurrent, &mousePos);
|
||||||
|
io.AddMousePosEvent(mousePos.x, mousePos.y);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WM.WM_MOUSELEAVE:
|
||||||
|
{
|
||||||
|
this.mouseTracked = false;
|
||||||
|
var mouseScreenPos = new POINT(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
||||||
|
ClientToScreen(hWndCurrent, &mouseScreenPos);
|
||||||
|
if (this.ViewportFromPoint(mouseScreenPos).IsNull)
|
||||||
|
{
|
||||||
|
var fltMax = ImGuiNative.GETFLTMAX();
|
||||||
|
io.AddMousePosEvent(-fltMax, -fltMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case WM.WM_LBUTTONDOWN:
|
case WM.WM_LBUTTONDOWN:
|
||||||
case WM.WM_LBUTTONDBLCLK:
|
case WM.WM_LBUTTONDBLCLK:
|
||||||
case WM.WM_RBUTTONDOWN:
|
case WM.WM_RBUTTONDOWN:
|
||||||
|
|
@ -236,11 +270,10 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
var button = GetButton(msg, wParam);
|
var button = GetButton(msg, wParam);
|
||||||
if (io.WantCaptureMouse)
|
if (io.WantCaptureMouse)
|
||||||
{
|
{
|
||||||
if (!ImGui.IsAnyMouseDown() && GetCapture() == nint.Zero)
|
if (this.mouseButtonsDown == 0 && GetCapture() == nint.Zero)
|
||||||
SetCapture(hWndCurrent);
|
SetCapture(hWndCurrent);
|
||||||
|
this.mouseButtonsDown |= 1 << button;
|
||||||
io.MouseDown[button] = true;
|
io.AddMouseButtonEvent(button, true);
|
||||||
this.imguiMouseIsDown[button] = true;
|
|
||||||
return default(LRESULT);
|
return default(LRESULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,13 +289,12 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
case WM.WM_XBUTTONUP:
|
case WM.WM_XBUTTONUP:
|
||||||
{
|
{
|
||||||
var button = GetButton(msg, wParam);
|
var button = GetButton(msg, wParam);
|
||||||
if (io.WantCaptureMouse && this.imguiMouseIsDown[button])
|
if (io.WantCaptureMouse)
|
||||||
{
|
{
|
||||||
if (!ImGui.IsAnyMouseDown() && GetCapture() == hWndCurrent)
|
this.mouseButtonsDown &= ~(1 << button);
|
||||||
|
if (this.mouseButtonsDown == 0 && GetCapture() == hWndCurrent)
|
||||||
ReleaseCapture();
|
ReleaseCapture();
|
||||||
|
io.AddMouseButtonEvent(button, false);
|
||||||
io.MouseDown[button] = false;
|
|
||||||
this.imguiMouseIsDown[button] = false;
|
|
||||||
return default(LRESULT);
|
return default(LRESULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,7 +304,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
case WM.WM_MOUSEWHEEL:
|
case WM.WM_MOUSEWHEEL:
|
||||||
if (io.WantCaptureMouse)
|
if (io.WantCaptureMouse)
|
||||||
{
|
{
|
||||||
io.MouseWheel += GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
|
io.AddMouseWheelEvent(0, GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA);
|
||||||
return default(LRESULT);
|
return default(LRESULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -280,7 +312,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
case WM.WM_MOUSEHWHEEL:
|
case WM.WM_MOUSEHWHEEL:
|
||||||
if (io.WantCaptureMouse)
|
if (io.WantCaptureMouse)
|
||||||
{
|
{
|
||||||
io.MouseWheelH += GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
|
io.AddMouseWheelEvent(GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA, 0);
|
||||||
return default(LRESULT);
|
return default(LRESULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -374,66 +406,84 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
this.viewportHandler.UpdateMonitors();
|
this.viewportHandler.UpdateMonitors();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WM.WM_KILLFOCUS when hWndCurrent == this.hWnd:
|
case WM.WM_SETFOCUS when hWndCurrent == this.hWnd:
|
||||||
if (!ImGui.IsAnyMouseDown() && GetCapture() == hWndCurrent)
|
io.AddFocusEvent(true);
|
||||||
ReleaseCapture();
|
break;
|
||||||
|
|
||||||
ImGui.GetIO().WantCaptureMouse = false;
|
case WM.WM_KILLFOCUS when hWndCurrent == this.hWnd:
|
||||||
ImGui.ClearWindowFocus();
|
io.AddFocusEvent(false);
|
||||||
|
// if (!ImGui.IsAnyMouseDown() && GetCapture() == hWndCurrent)
|
||||||
|
// ReleaseCapture();
|
||||||
|
//
|
||||||
|
// ImGui.GetIO().WantCaptureMouse = false;
|
||||||
|
// ImGui.ClearWindowFocus();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateMousePos()
|
private void UpdateMouseData(HWND focusedWindow)
|
||||||
{
|
{
|
||||||
var io = ImGui.GetIO();
|
var io = ImGui.GetIO();
|
||||||
var pt = default(POINT);
|
|
||||||
|
|
||||||
// Depending on if Viewports are enabled, we have to change how we process
|
var mouseScreenPos = default(POINT);
|
||||||
// the cursor position. If viewports are enabled, we pass the absolute cursor
|
var hasMouseScreenPos = GetCursorPos(&mouseScreenPos) != 0;
|
||||||
// position to ImGui. Otherwise, we use the old method of passing client-local
|
|
||||||
// mouse position to ImGui.
|
var isAppFocused =
|
||||||
if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable))
|
focusedWindow != default
|
||||||
|
&& (focusedWindow == this.hWnd
|
||||||
|
|| IsChild(focusedWindow, this.hWnd)
|
||||||
|
|| !ImGui.FindViewportByPlatformHandle(focusedWindow).IsNull);
|
||||||
|
|
||||||
|
if (isAppFocused)
|
||||||
{
|
{
|
||||||
|
// (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
|
||||||
|
// When multi-viewports are enabled, all Dear ImGui positions are same as OS positions.
|
||||||
if (io.WantSetMousePos)
|
if (io.WantSetMousePos)
|
||||||
{
|
{
|
||||||
SetCursorPos((int)io.MousePos.X, (int)io.MousePos.Y);
|
var pos = new POINT((int)io.MousePos.X, (int)io.MousePos.Y);
|
||||||
|
if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0)
|
||||||
|
ClientToScreen(this.hWnd, &pos);
|
||||||
|
SetCursorPos(pos.x, pos.y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetCursorPos(&pt))
|
// (Optional) Fallback to provide mouse position when focused (WM_MOUSEMOVE already provides this when hovered or captured)
|
||||||
|
if (!io.WantSetMousePos && !this.mouseTracked && hasMouseScreenPos)
|
||||||
{
|
{
|
||||||
io.MousePos.X = pt.x;
|
// Single viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window)
|
||||||
io.MousePos.Y = pt.y;
|
// (This is the position you can get with ::GetCursorPos() + ::ScreenToClient() or WM_MOUSEMOVE.)
|
||||||
}
|
// Multi-viewport mode: mouse position in OS absolute coordinates (io.MousePos is (0,0) when the mouse is on the upper-left of the primary monitor)
|
||||||
else
|
// (This is the position you can get with ::GetCursorPos() or WM_MOUSEMOVE + ::ClientToScreen(). In theory adding viewport->Pos to a client position would also be the same.)
|
||||||
{
|
var mousePos = mouseScreenPos;
|
||||||
io.MousePos.X = float.MinValue;
|
if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) == 0)
|
||||||
io.MousePos.Y = float.MinValue;
|
ClientToScreen(focusedWindow, &mousePos);
|
||||||
}
|
io.AddMousePosEvent(mousePos.x, mousePos.y);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (io.WantSetMousePos)
|
|
||||||
{
|
|
||||||
pt.x = (int)io.MousePos.X;
|
|
||||||
pt.y = (int)io.MousePos.Y;
|
|
||||||
ClientToScreen(this.hWnd, &pt);
|
|
||||||
SetCursorPos(pt.x, pt.y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetCursorPos(&pt) && ScreenToClient(this.hWnd, &pt))
|
// (Optional) When using multiple viewports: call io.AddMouseViewportEvent() with the viewport the OS mouse cursor is hovering.
|
||||||
|
// If ImGuiBackendFlags_HasMouseHoveredViewport is not set by the backend, Dear imGui will ignore this field and infer the information using its flawed heuristic.
|
||||||
|
// - [X] Win32 backend correctly ignore viewports with the _NoInputs flag (here using ::WindowFromPoint with WM_NCHITTEST + HTTRANSPARENT in WndProc does that)
|
||||||
|
// Some backend are not able to handle that correctly. If a backend report an hovered viewport that has the _NoInputs flag (e.g. when dragging a window
|
||||||
|
// for docking, the viewport has the _NoInputs flag in order to allow us to find the viewport under), then Dear ImGui is forced to ignore the value reported
|
||||||
|
// by the backend, and use its flawed heuristic to guess the viewport behind.
|
||||||
|
// - [X] Win32 backend correctly reports this regardless of another viewport behind focused and dragged from (we need this to find a useful drag and drop target).
|
||||||
|
if (hasMouseScreenPos)
|
||||||
{
|
{
|
||||||
io.MousePos.X = pt.x;
|
var viewport = this.ViewportFromPoint(mouseScreenPos);
|
||||||
io.MousePos.Y = pt.y;
|
io.AddMouseViewportEvent(!viewport.IsNull ? viewport.ID : 0u);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
io.MousePos.X = float.MinValue;
|
io.AddMouseViewportEvent(0);
|
||||||
io.MousePos.Y = float.MinValue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ImGuiViewportPtr ViewportFromPoint(POINT mouseScreenPos)
|
||||||
|
{
|
||||||
|
var hoveredHwnd = WindowFromPoint(mouseScreenPos);
|
||||||
|
return hoveredHwnd != default ? ImGui.FindViewportByPlatformHandle(hoveredHwnd) : default;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool UpdateMouseCursor()
|
private bool UpdateMouseCursor()
|
||||||
|
|
@ -451,7 +501,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessKeyEventsWorkarounds()
|
private void ProcessKeyEventsWorkarounds(HWND focusedWindow)
|
||||||
{
|
{
|
||||||
// Left & right Shift keys: when both are pressed together, Windows tend to not generate the WM_KEYUP event for the first released one.
|
// Left & right Shift keys: when both are pressed together, Windows tend to not generate the WM_KEYUP event for the first released one.
|
||||||
if (ImGui.IsKeyDown(ImGuiKey.LeftShift) && !IsVkDown(VK.VK_LSHIFT))
|
if (ImGui.IsKeyDown(ImGuiKey.LeftShift) && !IsVkDown(VK.VK_LSHIFT))
|
||||||
|
|
@ -480,7 +530,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
{
|
{
|
||||||
// See: https://github.com/goatcorp/ImGuiScene/pull/13
|
// See: https://github.com/goatcorp/ImGuiScene/pull/13
|
||||||
// > GetForegroundWindow from winuser.h is a surprisingly expensive function.
|
// > GetForegroundWindow from winuser.h is a surprisingly expensive function.
|
||||||
var isForeground = GetForegroundWindow() == this.hWnd;
|
var isForeground = focusedWindow == this.hWnd;
|
||||||
for (var i = (int)ImGuiKey.NamedKeyBegin; i < (int)ImGuiKey.NamedKeyEnd; i++)
|
for (var i = (int)ImGuiKey.NamedKeyBegin; i < (int)ImGuiKey.NamedKeyEnd; i++)
|
||||||
{
|
{
|
||||||
// Skip raising modifier keys if the game is focused.
|
// Skip raising modifier keys if the game is focused.
|
||||||
|
|
@ -646,14 +696,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var pio = ImGui.GetPlatformIO();
|
var pio = ImGui.GetPlatformIO();
|
||||||
|
ImGui.GetPlatformIO().Handle->Monitors.Free();
|
||||||
if (ImGui.GetPlatformIO().Handle->Monitors.Data != null)
|
|
||||||
{
|
|
||||||
// We allocated the platform monitor data in OnUpdateMonitors ourselves,
|
|
||||||
// so we have to free it ourselves to ImGui doesn't try to, or else it will crash
|
|
||||||
Marshal.FreeHGlobal(new IntPtr(ImGui.GetPlatformIO().Handle->Monitors.Data));
|
|
||||||
ImGui.GetPlatformIO().Handle->Monitors = default;
|
|
||||||
}
|
|
||||||
|
|
||||||
fixed (char* windowClassNamePtr = WindowClassName)
|
fixed (char* windowClassNamePtr = WindowClassName)
|
||||||
{
|
{
|
||||||
|
|
@ -693,23 +736,26 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
// Here we use a manual ImVector overload, free the existing monitor data,
|
// Here we use a manual ImVector overload, free the existing monitor data,
|
||||||
// and allocate our own, as we are responsible for telling ImGui about monitors
|
// and allocate our own, as we are responsible for telling ImGui about monitors
|
||||||
var pio = ImGui.GetPlatformIO();
|
var pio = ImGui.GetPlatformIO();
|
||||||
var numMonitors = GetSystemMetrics(SM.SM_CMONITORS);
|
pio.Handle->Monitors.Resize(0);
|
||||||
var data = Marshal.AllocHGlobal(Marshal.SizeOf<ImGuiPlatformMonitor>() * numMonitors);
|
|
||||||
if (pio.Handle->Monitors.Data != null)
|
|
||||||
Marshal.FreeHGlobal(new IntPtr(pio.Handle->Monitors.Data));
|
|
||||||
pio.Handle->Monitors = new(numMonitors, numMonitors, (ImGuiPlatformMonitor*)data.ToPointer());
|
|
||||||
|
|
||||||
// ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO();
|
EnumDisplayMonitors(default, null, &EnumDisplayMonitorsCallback, default);
|
||||||
// Marshal.FreeHGlobal(platformIO.Handle->Monitors.Data);
|
|
||||||
// int numMonitors = GetSystemMetrics(SystemMetric.SM_CMONITORS);
|
|
||||||
// nint data = Marshal.AllocHGlobal(Marshal.SizeOf<ImGuiPlatformMonitor>() * numMonitors);
|
|
||||||
// platformIO.Handle->Monitors = new ImVector(numMonitors, numMonitors, data);
|
|
||||||
|
|
||||||
var monitorIndex = -1;
|
Log.Information("Monitors set up!");
|
||||||
var enumfn = new MonitorEnumProcDelegate(
|
foreach (ref var monitor in pio.Handle->Monitors)
|
||||||
(hMonitor, _, _, _) =>
|
{
|
||||||
|
Log.Information(
|
||||||
|
"Monitor: {MainPos} {MainSize} {WorkPos} {WorkSize}",
|
||||||
|
monitor.MainPos,
|
||||||
|
monitor.MainSize,
|
||||||
|
monitor.WorkPos,
|
||||||
|
monitor.WorkSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly]
|
||||||
|
static BOOL EnumDisplayMonitorsCallback(HMONITOR hMonitor, HDC hdc, RECT* rect, LPARAM lParam)
|
||||||
{
|
{
|
||||||
monitorIndex++;
|
|
||||||
var info = new MONITORINFO { cbSize = (uint)sizeof(MONITORINFO) };
|
var info = new MONITORINFO { cbSize = (uint)sizeof(MONITORINFO) };
|
||||||
if (!GetMonitorInfoW(hMonitor, &info))
|
if (!GetMonitorInfoW(hMonitor, &info))
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -718,33 +764,21 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
var monitorRb = new Vector2(info.rcMonitor.right, info.rcMonitor.bottom);
|
var monitorRb = new Vector2(info.rcMonitor.right, info.rcMonitor.bottom);
|
||||||
var workLt = new Vector2(info.rcWork.left, info.rcWork.top);
|
var workLt = new Vector2(info.rcWork.left, info.rcWork.top);
|
||||||
var workRb = new Vector2(info.rcWork.right, info.rcWork.bottom);
|
var workRb = new Vector2(info.rcWork.right, info.rcWork.bottom);
|
||||||
|
|
||||||
// Give ImGui the info for this display
|
// Give ImGui the info for this display
|
||||||
|
var imMonitor = new ImGuiPlatformMonitor
|
||||||
ref var imMonitor = ref ImGui.GetPlatformIO().Monitors.Ref(monitorIndex);
|
|
||||||
imMonitor.MainPos = monitorLt;
|
|
||||||
imMonitor.MainSize = monitorRb - monitorLt;
|
|
||||||
imMonitor.WorkPos = workLt;
|
|
||||||
imMonitor.WorkSize = workRb - workLt;
|
|
||||||
imMonitor.DpiScale = 1f;
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
EnumDisplayMonitors(
|
|
||||||
default,
|
|
||||||
null,
|
|
||||||
(delegate* unmanaged<HMONITOR, HDC, RECT*, LPARAM, BOOL>)Marshal.GetFunctionPointerForDelegate(enumfn),
|
|
||||||
default);
|
|
||||||
|
|
||||||
Log.Information("Monitors set up!");
|
|
||||||
for (var i = 0; i < numMonitors; i++)
|
|
||||||
{
|
{
|
||||||
var monitor = pio.Handle->Monitors[i];
|
MainPos = monitorLt,
|
||||||
Log.Information(
|
MainSize = monitorRb - monitorLt,
|
||||||
"Monitor {Index}: {MainPos} {MainSize} {WorkPos} {WorkSize}",
|
WorkPos = workLt,
|
||||||
i,
|
WorkSize = workRb - workLt,
|
||||||
monitor.MainPos,
|
DpiScale = 1f,
|
||||||
monitor.MainSize,
|
};
|
||||||
monitor.WorkPos,
|
if ((info.dwFlags & MONITORINFOF_PRIMARY) != 0)
|
||||||
monitor.WorkSize);
|
ImGui.GetPlatformIO().Monitors.PushFront(imMonitor);
|
||||||
|
else
|
||||||
|
ImGui.GetPlatformIO().Monitors.PushBack(imMonitor);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -794,6 +828,9 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data->Hwnd == 0)
|
||||||
|
Util.Fatal($"CreateWindowExW failed: {GetLastError()}", "ImGui Viewport error");
|
||||||
|
|
||||||
data->HwndOwned = true;
|
data->HwndOwned = true;
|
||||||
viewport.PlatformRequestResize = false;
|
viewport.PlatformRequestResize = false;
|
||||||
viewport.PlatformHandle = viewport.PlatformHandleRaw = data->Hwnd;
|
viewport.PlatformHandle = viewport.PlatformHandleRaw = data->Hwnd;
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,8 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
: base(
|
: base(
|
||||||
"TitleScreenMenuOverlay",
|
"TitleScreenMenuOverlay",
|
||||||
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar |
|
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar |
|
||||||
ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus)
|
ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus |
|
||||||
|
ImGuiWindowFlags.NoDocking)
|
||||||
{
|
{
|
||||||
this.showTsm = consoleManager.AddVariable("dalamud.show_tsm", "Show the Title Screen Menu", true);
|
this.showTsm = consoleManager.AddVariable("dalamud.show_tsm", "Show the Title Screen Menu", true);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using CheapLoc;
|
using CheapLoc;
|
||||||
|
|
@ -19,10 +16,13 @@ using Dalamud.Interface.Utility.Internal;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Interface.Windowing.Persistence;
|
using Dalamud.Interface.Windowing.Persistence;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Utility;
|
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
|
using static TerraFX.Interop.Windows.Windows;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Windowing;
|
namespace Dalamud.Interface.Windowing;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -31,11 +31,15 @@ namespace Dalamud.Interface.Windowing;
|
||||||
public abstract class Window
|
public abstract class Window
|
||||||
{
|
{
|
||||||
private const float FadeInOutTime = 0.072f;
|
private const float FadeInOutTime = 0.072f;
|
||||||
|
private const string AdditionsPopupName = "WindowSystemContextActions";
|
||||||
|
|
||||||
private static readonly ModuleLog Log = new("WindowSystem");
|
private static readonly ModuleLog Log = new("WindowSystem");
|
||||||
|
|
||||||
private static bool wasEscPressedLastFrame = false;
|
private static bool wasEscPressedLastFrame = false;
|
||||||
|
|
||||||
|
private readonly TitleBarButton additionsButton;
|
||||||
|
private readonly List<TitleBarButton> allButtons = [];
|
||||||
|
|
||||||
private bool internalLastIsOpen = false;
|
private bool internalLastIsOpen = false;
|
||||||
private bool internalIsOpen = false;
|
private bool internalIsOpen = false;
|
||||||
private bool internalIsPinned = false;
|
private bool internalIsPinned = false;
|
||||||
|
|
@ -72,6 +76,20 @@ public abstract class Window
|
||||||
this.WindowName = name;
|
this.WindowName = name;
|
||||||
this.Flags = flags;
|
this.Flags = flags;
|
||||||
this.ForceMainWindow = forceMainWindow;
|
this.ForceMainWindow = forceMainWindow;
|
||||||
|
|
||||||
|
this.additionsButton = new()
|
||||||
|
{
|
||||||
|
Icon = FontAwesomeIcon.Bars,
|
||||||
|
IconOffset = new Vector2(2.5f, 1),
|
||||||
|
Click = _ =>
|
||||||
|
{
|
||||||
|
this.internalIsClickthrough = false;
|
||||||
|
this.presetDirty = false;
|
||||||
|
ImGui.OpenPopup(AdditionsPopupName);
|
||||||
|
},
|
||||||
|
Priority = int.MinValue,
|
||||||
|
AvailableClickthrough = true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -482,14 +500,12 @@ public abstract class Window
|
||||||
ImGuiP.GetCurrentWindow().InheritNoInputs = this.internalIsClickthrough;
|
ImGuiP.GetCurrentWindow().InheritNoInputs = this.internalIsClickthrough;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not supported yet on non-main viewports
|
if (ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID)
|
||||||
if ((this.internalIsPinned || this.internalIsClickthrough || this.internalAlpha.HasValue) &&
|
|
||||||
ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID)
|
|
||||||
{
|
{
|
||||||
this.internalAlpha = null;
|
if ((flags & ImGuiWindowFlags.NoInputs) == ImGuiWindowFlags.NoInputs)
|
||||||
this.internalIsPinned = false;
|
ImGui.GetWindowViewport().Flags |= ImGuiViewportFlags.NoInputs;
|
||||||
this.internalIsClickthrough = false;
|
else
|
||||||
this.presetDirty = true;
|
ImGui.GetWindowViewport().Flags &= ~ImGuiViewportFlags.NoInputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasError)
|
if (this.hasError)
|
||||||
|
|
@ -513,7 +529,6 @@ public abstract class Window
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const string additionsPopupName = "WindowSystemContextActions";
|
|
||||||
var flagsApplicableForTitleBarIcons = !flags.HasFlag(ImGuiWindowFlags.NoDecoration) &&
|
var flagsApplicableForTitleBarIcons = !flags.HasFlag(ImGuiWindowFlags.NoDecoration) &&
|
||||||
!flags.HasFlag(ImGuiWindowFlags.NoTitleBar);
|
!flags.HasFlag(ImGuiWindowFlags.NoTitleBar);
|
||||||
var showAdditions = (this.AllowPinning || this.AllowClickthrough) &&
|
var showAdditions = (this.AllowPinning || this.AllowClickthrough) &&
|
||||||
|
|
@ -524,13 +539,8 @@ public abstract class Window
|
||||||
{
|
{
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1f);
|
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 1f);
|
||||||
|
|
||||||
if (ImGui.BeginPopup(additionsPopupName, ImGuiWindowFlags.NoMove))
|
if (ImGui.BeginPopup(AdditionsPopupName, ImGuiWindowFlags.NoMove))
|
||||||
{
|
{
|
||||||
var isAvailable = ImGuiHelpers.CheckIsWindowOnMainViewport();
|
|
||||||
|
|
||||||
if (!isAvailable)
|
|
||||||
ImGui.BeginDisabled();
|
|
||||||
|
|
||||||
if (this.internalIsClickthrough)
|
if (this.internalIsClickthrough)
|
||||||
ImGui.BeginDisabled();
|
ImGui.BeginDisabled();
|
||||||
|
|
||||||
|
|
@ -578,21 +588,11 @@ public abstract class Window
|
||||||
this.presetDirty = true;
|
this.presetDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAvailable)
|
ImGui.TextColored(
|
||||||
{
|
ImGuiColors.DalamudGrey,
|
||||||
ImGui.TextColored(ImGuiColors.DalamudGrey,
|
Loc.Localize(
|
||||||
Loc.Localize("WindowSystemContextActionClickthroughDisclaimer",
|
"WindowSystemContextActionClickthroughDisclaimer",
|
||||||
"Open this menu again by clicking the three dashes to disable clickthrough."));
|
"Open this menu again by clicking the three dashes to disable clickthrough."));
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.TextColored(ImGuiColors.DalamudGrey,
|
|
||||||
Loc.Localize("WindowSystemContextActionViewportDisclaimer",
|
|
||||||
"These features are only available if this window is inside the game window."));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isAvailable)
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
|
|
||||||
if (ImGui.Button(Loc.Localize("WindowSystemContextActionPrintWindow", "Print window")))
|
if (ImGui.Button(Loc.Localize("WindowSystemContextActionPrintWindow", "Print window")))
|
||||||
printWindow = true;
|
printWindow = true;
|
||||||
|
|
@ -603,34 +603,15 @@ public abstract class Window
|
||||||
ImGui.PopStyleVar();
|
ImGui.PopStyleVar();
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
var window = ImGuiP.GetCurrentWindow();
|
|
||||||
|
|
||||||
ImRect outRect;
|
|
||||||
ImGuiP.TitleBarRect(&outRect, window);
|
|
||||||
|
|
||||||
var additionsButton = new TitleBarButton
|
|
||||||
{
|
|
||||||
Icon = FontAwesomeIcon.Bars,
|
|
||||||
IconOffset = new Vector2(2.5f, 1),
|
|
||||||
Click = _ =>
|
|
||||||
{
|
|
||||||
this.internalIsClickthrough = false;
|
|
||||||
this.presetDirty = false;
|
|
||||||
ImGui.OpenPopup(additionsPopupName);
|
|
||||||
},
|
|
||||||
Priority = int.MinValue,
|
|
||||||
AvailableClickthrough = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (flagsApplicableForTitleBarIcons)
|
if (flagsApplicableForTitleBarIcons)
|
||||||
{
|
{
|
||||||
this.DrawTitleBarButtons(window, flags, outRect,
|
this.allButtons.Clear();
|
||||||
showAdditions
|
this.allButtons.EnsureCapacity(this.TitleBarButtons.Count + 1);
|
||||||
? this.TitleBarButtons.Append(additionsButton)
|
this.allButtons.AddRange(this.TitleBarButtons);
|
||||||
: this.TitleBarButtons);
|
if (showAdditions)
|
||||||
}
|
this.allButtons.Add(this.additionsButton);
|
||||||
|
this.allButtons.Sort(static (a, b) => b.Priority - a.Priority);
|
||||||
|
this.DrawTitleBarButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wasFocused)
|
if (wasFocused)
|
||||||
|
|
@ -797,8 +778,11 @@ public abstract class Window
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void DrawTitleBarButtons(ImGuiWindowPtr window, ImGuiWindowFlags flags, ImRect titleBarRect, IEnumerable<TitleBarButton> buttons)
|
private unsafe void DrawTitleBarButtons()
|
||||||
{
|
{
|
||||||
|
var window = ImGuiP.GetCurrentWindow();
|
||||||
|
var flags = window.Flags;
|
||||||
|
var titleBarRect = window.TitleBarRect();
|
||||||
ImGui.PushClipRect(ImGui.GetWindowPos(), ImGui.GetWindowPos() + ImGui.GetWindowSize(), false);
|
ImGui.PushClipRect(ImGui.GetWindowPos(), ImGui.GetWindowPos() + ImGui.GetWindowSize(), false);
|
||||||
|
|
||||||
var style = ImGui.GetStyle();
|
var style = ImGui.GetStyle();
|
||||||
|
|
@ -833,26 +817,22 @@ public abstract class Window
|
||||||
var max = pos + new Vector2(fontSize, fontSize);
|
var max = pos + new Vector2(fontSize, fontSize);
|
||||||
ImRect bb = new(pos, max);
|
ImRect bb = new(pos, max);
|
||||||
var isClipped = !ImGuiP.ItemAdd(bb, id, null, 0);
|
var isClipped = !ImGuiP.ItemAdd(bb, id, null, 0);
|
||||||
bool hovered, held;
|
bool hovered, held, pressed;
|
||||||
var pressed = false;
|
|
||||||
|
|
||||||
if (this.internalIsClickthrough)
|
if (this.internalIsClickthrough)
|
||||||
{
|
{
|
||||||
hovered = false;
|
|
||||||
held = false;
|
|
||||||
|
|
||||||
// ButtonBehavior does not function if the window is clickthrough, so we have to do it ourselves
|
// ButtonBehavior does not function if the window is clickthrough, so we have to do it ourselves
|
||||||
if (ImGui.IsMouseHoveringRect(pos, max))
|
var pad = ImGui.GetStyle().TouchExtraPadding;
|
||||||
{
|
var rect = new ImRect(pos - pad, max + pad);
|
||||||
hovered = true;
|
hovered = rect.Contains(ImGui.GetMousePos());
|
||||||
|
|
||||||
|
// Temporarily enable inputs
|
||||||
|
// This will be reset on next frame, and then enabled again if it is still being hovered
|
||||||
|
if (hovered && ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID)
|
||||||
|
ImGui.GetWindowViewport().Flags &= ~ImGuiViewportFlags.NoInputs;
|
||||||
|
|
||||||
// We can't use ImGui native functions here, because they don't work with clickthrough
|
// We can't use ImGui native functions here, because they don't work with clickthrough
|
||||||
if ((Windows.Win32.PInvoke.GetKeyState((int)VirtualKey.LBUTTON) & 0x8000) != 0)
|
pressed = held = hovered && (GetKeyState(VK.VK_LBUTTON) & 0x8000) != 0;
|
||||||
{
|
|
||||||
held = true;
|
|
||||||
pressed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -881,7 +861,7 @@ public abstract class Window
|
||||||
return pressed;
|
return pressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var button in buttons.OrderBy(x => x.Priority))
|
foreach (var button in this.allButtons)
|
||||||
{
|
{
|
||||||
if (this.internalIsClickthrough && !button.AvailableClickthrough)
|
if (this.internalIsClickthrough && !button.AvailableClickthrough)
|
||||||
return;
|
return;
|
||||||
|
|
@ -1035,7 +1015,7 @@ public abstract class Window
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets an action that is called when the button is clicked.
|
/// Gets or sets an action that is called when the button is clicked.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<ImGuiMouseButton> Click { get; set; }
|
public Action<ImGuiMouseButton>? Click { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the priority the button shall be shown in.
|
/// Gets or sets the priority the button shall be shown in.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
using System.Runtime.CompilerServices;
|
using System.Collections;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Dalamud.Bindings.ImGui;
|
namespace Dalamud.Bindings.ImGui;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A structure representing a dynamic array for unmanaged types.
|
||||||
|
/// </summary>
|
||||||
public unsafe struct ImVector
|
public unsafe struct ImVector
|
||||||
{
|
{
|
||||||
public readonly int Size;
|
public readonly int Size;
|
||||||
|
|
@ -15,23 +20,23 @@ public unsafe struct ImVector
|
||||||
Data = data;
|
Data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref T Ref<T>(int index)
|
public readonly ref T Ref<T>(int index) => ref Unsafe.AsRef<T>((byte*)this.Data + (index * Unsafe.SizeOf<T>()));
|
||||||
{
|
|
||||||
return ref Unsafe.AsRef<T>((byte*)Data + index * Unsafe.SizeOf<T>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public IntPtr Address<T>(int index)
|
public readonly nint Address<T>(int index) => (nint)((byte*)this.Data + (index * Unsafe.SizeOf<T>()));
|
||||||
{
|
|
||||||
return (IntPtr)((byte*)Data + index * Unsafe.SizeOf<T>());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A structure representing a dynamic array for unmanaged types.
|
/// A structure representing a dynamic array for unmanaged types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type of elements in the vector, must be unmanaged.</typeparam>
|
/// <typeparam name="T">The type of elements in the vector, must be unmanaged.</typeparam>
|
||||||
public unsafe struct ImVector<T> where T : unmanaged
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public unsafe struct ImVector<T> : IEnumerable<T>
|
||||||
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
|
private int size;
|
||||||
|
private int capacity;
|
||||||
|
private T* data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ImVector{T}"/> struct with the specified size, capacity, and data pointer.
|
/// Initializes a new instance of the <see cref="ImVector{T}"/> struct with the specified size, capacity, and data pointer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -45,11 +50,6 @@ public unsafe struct ImVector<T> where T : unmanaged
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int size;
|
|
||||||
private int capacity;
|
|
||||||
private unsafe T* data;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the element at the specified index.
|
/// Gets or sets the element at the specified index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -58,80 +58,72 @@ public unsafe struct ImVector<T> where T : unmanaged
|
||||||
/// <exception cref="IndexOutOfRangeException">Thrown when the index is out of range.</exception>
|
/// <exception cref="IndexOutOfRangeException">Thrown when the index is out of range.</exception>
|
||||||
public T this[int index]
|
public T this[int index]
|
||||||
{
|
{
|
||||||
get
|
readonly get
|
||||||
{
|
|
||||||
if (index < 0 || index >= size)
|
|
||||||
{
|
{
|
||||||
|
if (index < 0 || index >= this.size)
|
||||||
throw new IndexOutOfRangeException();
|
throw new IndexOutOfRangeException();
|
||||||
}
|
return this.data[index];
|
||||||
return data[index];
|
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= size)
|
if (index < 0 || index >= this.size)
|
||||||
{
|
|
||||||
throw new IndexOutOfRangeException();
|
throw new IndexOutOfRangeException();
|
||||||
}
|
this.data[index] = value;
|
||||||
data[index] = value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a pointer to the first element of the vector.
|
/// Gets a pointer to the first element of the vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly T* Data => data;
|
public readonly T* Data => this.data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a pointer to the first element of the vector.
|
/// Gets a pointer to the first element of the vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly T* Front => data;
|
public readonly T* Front => this.data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a pointer to the last element of the vector.
|
/// Gets a pointer to the last element of the vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly T* Back => size > 0 ? data + size - 1 : null;
|
public readonly T* Back => this.size > 0 ? this.data + this.size - 1 : null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the capacity of the vector.
|
/// Gets or sets the capacity of the vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Capacity
|
public int Capacity
|
||||||
{
|
{
|
||||||
readonly get => capacity;
|
readonly get => this.capacity;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (capacity == value)
|
ArgumentOutOfRangeException.ThrowIfLessThan(value, this.size, nameof(Capacity));
|
||||||
{
|
if (this.capacity == value)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (data == null)
|
if (this.data == null)
|
||||||
{
|
{
|
||||||
data = (T*)ImGui.MemAlloc((nuint)(value * sizeof(T)));
|
this.data = (T*)ImGui.MemAlloc((nuint)(value * sizeof(T)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int newSize = Math.Min(size, value);
|
var newSize = Math.Min(this.size, value);
|
||||||
T* newData = (T*)ImGui.MemAlloc((nuint)(value * sizeof(T)));
|
var newData = (T*)ImGui.MemAlloc((nuint)(value * sizeof(T)));
|
||||||
Buffer.MemoryCopy(data, newData, (nuint)(value * sizeof(T)), (nuint)(newSize * sizeof(T)));
|
Buffer.MemoryCopy(this.data, newData, (nuint)(value * sizeof(T)), (nuint)(newSize * sizeof(T)));
|
||||||
ImGui.MemFree(data);
|
ImGui.MemFree(this.data);
|
||||||
data = newData;
|
this.data = newData;
|
||||||
size = newSize;
|
this.size = newSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
capacity = value;
|
this.capacity = value;
|
||||||
|
|
||||||
// Clear the rest of the data
|
// Clear the rest of the data
|
||||||
for (int i = size; i < capacity; i++)
|
new Span<T>(this.data + this.size, this.capacity - this.size).Clear();
|
||||||
{
|
|
||||||
data[i] = default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the number of elements in the vector.
|
/// Gets the number of elements in the vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly int Size => size;
|
public readonly int Size => this.size;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Grows the capacity of the vector to at least the specified value.
|
/// Grows the capacity of the vector to at least the specified value.
|
||||||
|
|
@ -139,10 +131,8 @@ public unsafe struct ImVector<T> where T : unmanaged
|
||||||
/// <param name="newCapacity">The new capacity.</param>
|
/// <param name="newCapacity">The new capacity.</param>
|
||||||
public void Grow(int newCapacity)
|
public void Grow(int newCapacity)
|
||||||
{
|
{
|
||||||
if (newCapacity > capacity)
|
var newCapacity2 = this.capacity > 0 ? this.capacity + (this.capacity / 2) : 8;
|
||||||
{
|
this.Capacity = newCapacity2 > newCapacity ? newCapacity2 : newCapacity;
|
||||||
Capacity = newCapacity * 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -151,11 +141,9 @@ public unsafe struct ImVector<T> where T : unmanaged
|
||||||
/// <param name="size">The minimum capacity required.</param>
|
/// <param name="size">The minimum capacity required.</param>
|
||||||
public void EnsureCapacity(int size)
|
public void EnsureCapacity(int size)
|
||||||
{
|
{
|
||||||
if (size > capacity)
|
if (size > this.capacity)
|
||||||
{
|
|
||||||
Grow(size);
|
Grow(size);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resizes the vector to the specified size.
|
/// Resizes the vector to the specified size.
|
||||||
|
|
@ -164,25 +152,46 @@ public unsafe struct ImVector<T> where T : unmanaged
|
||||||
public void Resize(int newSize)
|
public void Resize(int newSize)
|
||||||
{
|
{
|
||||||
EnsureCapacity(newSize);
|
EnsureCapacity(newSize);
|
||||||
size = newSize;
|
this.size = newSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears all elements from the vector.
|
/// Clears all elements from the vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Clear()
|
public void Clear() => this.size = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an element to the end of the vector.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to add.</param>
|
||||||
|
[OverloadResolutionPriority(1)]
|
||||||
|
public void PushBack(T value)
|
||||||
{
|
{
|
||||||
size = 0;
|
this.EnsureCapacity(this.size + 1);
|
||||||
|
this.data[this.size++] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds an element to the end of the vector.
|
/// Adds an element to the end of the vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">The value to add.</param>
|
/// <param name="value">The value to add.</param>
|
||||||
public void PushBack(T value)
|
[OverloadResolutionPriority(2)]
|
||||||
|
public void PushBack(in T value)
|
||||||
{
|
{
|
||||||
EnsureCapacity(size + 1);
|
EnsureCapacity(this.size + 1);
|
||||||
data[size++] = value;
|
this.data[this.size++] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an element to the front of the vector.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to add.</param>
|
||||||
|
public void PushFront(in T value)
|
||||||
|
{
|
||||||
|
if (this.size == 0)
|
||||||
|
this.PushBack(value);
|
||||||
|
else
|
||||||
|
this.Insert(0, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -190,48 +199,126 @@ public unsafe struct ImVector<T> where T : unmanaged
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void PopBack()
|
public void PopBack()
|
||||||
{
|
{
|
||||||
if (size > 0)
|
if (this.size > 0)
|
||||||
{
|
{
|
||||||
size--;
|
this.size--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ref T Insert(int index, in T v) {
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(index, nameof(index));
|
||||||
|
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, this.size, nameof(index));
|
||||||
|
this.EnsureCapacity(this.size + 1);
|
||||||
|
if (index < this.size)
|
||||||
|
{
|
||||||
|
Buffer.MemoryCopy(
|
||||||
|
this.data + index,
|
||||||
|
this.data + index + 1,
|
||||||
|
(this.size - index) * sizeof(T),
|
||||||
|
(this.size - index) * sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data[index] = v;
|
||||||
|
this.size++;
|
||||||
|
return ref this.data[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public Span<T> InsertRange(int index, ReadOnlySpan<T> v)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(index, nameof(index));
|
||||||
|
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, this.size, nameof(index));
|
||||||
|
this.EnsureCapacity(this.size + v.Length);
|
||||||
|
if (index < this.size)
|
||||||
|
{
|
||||||
|
Buffer.MemoryCopy(
|
||||||
|
this.data + index,
|
||||||
|
this.data + index + v.Length,
|
||||||
|
(this.size - index) * sizeof(T),
|
||||||
|
(this.size - index) * sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
var dstSpan = new Span<T>(this.data + index, v.Length);
|
||||||
|
v.CopyTo(new(this.data + index, v.Length));
|
||||||
|
this.size += v.Length;
|
||||||
|
return dstSpan;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Frees the memory allocated for the vector.
|
/// Frees the memory allocated for the vector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Free()
|
public void Free()
|
||||||
{
|
{
|
||||||
if (data != null)
|
if (this.data != null)
|
||||||
{
|
{
|
||||||
ImGui.MemFree(data);
|
ImGui.MemFree(this.data);
|
||||||
data = null;
|
this.data = null;
|
||||||
size = 0;
|
this.size = 0;
|
||||||
capacity = 0;
|
this.capacity = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref T Ref(int index)
|
public readonly ref T Ref(int index)
|
||||||
{
|
{
|
||||||
return ref Unsafe.AsRef<T>((byte*)Data + index * Unsafe.SizeOf<T>());
|
return ref Unsafe.AsRef<T>((byte*)Data + (index * Unsafe.SizeOf<T>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref TCast Ref<TCast>(int index)
|
public readonly ref TCast Ref<TCast>(int index)
|
||||||
{
|
{
|
||||||
return ref Unsafe.AsRef<TCast>((byte*)Data + index * Unsafe.SizeOf<TCast>());
|
return ref Unsafe.AsRef<TCast>((byte*)Data + (index * Unsafe.SizeOf<TCast>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void* Address(int index)
|
public readonly void* Address(int index)
|
||||||
{
|
{
|
||||||
return (byte*)Data + index * Unsafe.SizeOf<T>();
|
return (byte*)Data + (index * Unsafe.SizeOf<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void* Address<TCast>(int index)
|
public readonly void* Address<TCast>(int index)
|
||||||
{
|
{
|
||||||
return (byte*)Data + index * Unsafe.SizeOf<TCast>();
|
return (byte*)Data + (index * Unsafe.SizeOf<TCast>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImVector* ToUntyped()
|
public readonly ImVector* ToUntyped()
|
||||||
{
|
{
|
||||||
return (ImVector*)Unsafe.AsPointer(ref this);
|
return (ImVector*)Unsafe.AsPointer(ref Unsafe.AsRef(in this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly Span<T> AsSpan() => new(this.data, this.size);
|
||||||
|
|
||||||
|
public readonly Enumerator GetEnumerator() => new(this.data, this.data + this.size);
|
||||||
|
|
||||||
|
readonly IEnumerator<T> IEnumerable<T>.GetEnumerator() => this.GetEnumerator();
|
||||||
|
|
||||||
|
readonly IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
|
|
||||||
|
public struct Enumerator(T* begin, T* end) : IEnumerator<T>, IEnumerable<T>
|
||||||
|
{
|
||||||
|
private T* current = null;
|
||||||
|
|
||||||
|
public readonly ref T Current => ref *this.current;
|
||||||
|
|
||||||
|
readonly T IEnumerator<T>.Current => this.Current;
|
||||||
|
|
||||||
|
readonly object IEnumerator.Current => this.Current;
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
var next = this.current == null ? begin : this.current + 1;
|
||||||
|
if (next == end)
|
||||||
|
return false;
|
||||||
|
this.current = next;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset() => this.current = null;
|
||||||
|
|
||||||
|
public readonly Enumerator GetEnumerator() => new(begin, end);
|
||||||
|
|
||||||
|
readonly void IDisposable.Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly IEnumerator<T> IEnumerable<T>.GetEnumerator() => this.GetEnumerator();
|
||||||
|
|
||||||
|
readonly IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue