diff --git a/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs b/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs index 596df4c67..62e254a1a 100644 --- a/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs +++ b/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs @@ -34,11 +34,12 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler private readonly HCURSOR[] cursors; private readonly WndProcDelegate wndProcDelegate; - private readonly bool[] imguiMouseIsDown; private readonly nint platformNamePtr; private ViewportHandler viewportHandler; + private int mouseButtonsDown; + private bool mouseTracked; private long lastTime; private nint iniPathPtr; @@ -64,7 +65,8 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors | ImGuiBackendFlags.HasSetMousePos | ImGuiBackendFlags.RendererHasViewports | - ImGuiBackendFlags.PlatformHasViewports; + ImGuiBackendFlags.PlatformHasViewports | + ImGuiBackendFlags.HasMouseHoveredViewport; this.platformNamePtr = Marshal.StringToHGlobalAnsi("imgui_impl_win32_c#"); io.Handle->BackendPlatformName = (byte*)this.platformNamePtr; @@ -74,8 +76,6 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable)) this.viewportHandler = new(this); - this.imguiMouseIsDown = new bool[5]; - this.cursors = new HCURSOR[9]; this.cursors[(int)ImGuiMouseCursor.Arrow] = LoadCursorW(default, IDC.IDC_ARROW); this.cursors[(int)ImGuiMouseCursor.TextInput] = LoadCursorW(default, IDC.IDC_IBEAM); @@ -95,8 +95,6 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler private delegate LRESULT WndProcDelegate(HWND hWnd, uint uMsg, WPARAM wparam, LPARAM lparam); - private delegate BOOL MonitorEnumProcDelegate(HMONITOR monitor, HDC hdc, RECT* rect, LPARAM lparam); - /// public bool UpdateCursor { get; set; } = true; @@ -155,6 +153,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler public void NewFrame(int targetWidth, int targetHeight) { var io = ImGui.GetIO(); + var focusedWindow = GetForegroundWindow(); io.DisplaySize.X = targetWidth; io.DisplaySize.Y = targetHeight; @@ -168,9 +167,9 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler this.viewportHandler.UpdateMonitors(); - this.UpdateMousePos(); + this.UpdateMouseData(focusedWindow); - this.ProcessKeyEventsWorkarounds(); + this.ProcessKeyEventsWorkarounds(focusedWindow); // 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 @@ -224,6 +223,40 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler 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_LBUTTONDBLCLK: case WM.WM_RBUTTONDOWN: @@ -236,11 +269,10 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler var button = GetButton(msg, wParam); if (io.WantCaptureMouse) { - if (!ImGui.IsAnyMouseDown() && GetCapture() == nint.Zero) + if (this.mouseButtonsDown == 0 && GetCapture() == nint.Zero) SetCapture(hWndCurrent); - - io.MouseDown[button] = true; - this.imguiMouseIsDown[button] = true; + this.mouseButtonsDown |= 1 << button; + io.AddMouseButtonEvent(button, true); return default(LRESULT); } @@ -256,13 +288,12 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler case WM.WM_XBUTTONUP: { 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(); - - io.MouseDown[button] = false; - this.imguiMouseIsDown[button] = false; + io.AddMouseButtonEvent(button, false); return default(LRESULT); } @@ -272,7 +303,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler case WM.WM_MOUSEWHEEL: 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); } @@ -280,7 +311,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler case WM.WM_MOUSEHWHEEL: 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); } @@ -374,68 +405,86 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler this.viewportHandler.UpdateMonitors(); break; - case WM.WM_KILLFOCUS when hWndCurrent == this.hWnd: - if (!ImGui.IsAnyMouseDown() && GetCapture() == hWndCurrent) - ReleaseCapture(); + case WM.WM_SETFOCUS when hWndCurrent == this.hWnd: + io.AddFocusEvent(true); + break; - ImGui.GetIO().WantCaptureMouse = false; - ImGui.ClearWindowFocus(); + case WM.WM_KILLFOCUS when hWndCurrent == this.hWnd: + io.AddFocusEvent(false); + // if (!ImGui.IsAnyMouseDown() && GetCapture() == hWndCurrent) + // ReleaseCapture(); + // + // ImGui.GetIO().WantCaptureMouse = false; + // ImGui.ClearWindowFocus(); break; } return null; } - private void UpdateMousePos() + private void UpdateMouseData(HWND focusedWindow) { var io = ImGui.GetIO(); - var pt = default(POINT); - // Depending on if Viewports are enabled, we have to change how we process - // the cursor position. If viewports are enabled, we pass the absolute cursor - // position to ImGui. Otherwise, we use the old method of passing client-local - // mouse position to ImGui. - if (io.ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable)) + var mouseScreenPos = default(POINT); + var hasMouseScreenPos = GetCursorPos(&mouseScreenPos) != 0; + + var isAppFocused = + 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) { - 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; - io.MousePos.Y = pt.y; - } - else - { - io.MousePos.X = float.MinValue; - io.MousePos.Y = float.MinValue; + // 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) + // (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) + // (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; + if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) == 0) + ClientToScreen(focusedWindow, &mousePos); + io.AddMousePosEvent(mousePos.x, mousePos.y); } } + + // (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) + { + var viewport = this.ViewportFromPoint(mouseScreenPos); + io.AddMouseViewportEvent(!viewport.IsNull ? viewport.ID : 0u); + } 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)) - { - io.MousePos.X = pt.x; - io.MousePos.Y = pt.y; - } - else - { - io.MousePos.X = float.MinValue; - io.MousePos.Y = float.MinValue; - } + io.AddMouseViewportEvent(0); } } + private ImGuiViewportPtr ViewportFromPoint(POINT mouseScreenPos) + { + var hoveredHwnd = WindowFromPoint(mouseScreenPos); + return hoveredHwnd != default ? ImGui.FindViewportByPlatformHandle(hoveredHwnd) : default; + } + private bool UpdateMouseCursor() { var io = ImGui.GetIO(); @@ -451,7 +500,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler 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. if (ImGui.IsKeyDown(ImGuiKey.LeftShift) && !IsVkDown(VK.VK_LSHIFT)) @@ -480,7 +529,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler { // See: https://github.com/goatcorp/ImGuiScene/pull/13 // > 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++) { // Skip raising modifier keys if the game is focused. @@ -646,14 +695,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler return; var pio = ImGui.GetPlatformIO(); - - 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; - } + ImGui.GetPlatformIO().Handle->Monitors.Free(); fixed (char* windowClassNamePtr = WindowClassName) { @@ -693,59 +735,50 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler // 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 var pio = ImGui.GetPlatformIO(); - var numMonitors = GetSystemMetrics(SM.SM_CMONITORS); - var data = Marshal.AllocHGlobal(Marshal.SizeOf() * numMonitors); - if (pio.Handle->Monitors.Data != null) - Marshal.FreeHGlobal(new IntPtr(pio.Handle->Monitors.Data)); - pio.Handle->Monitors = new(numMonitors, numMonitors, (ImGuiPlatformMonitor*)data.ToPointer()); + pio.Handle->Monitors.Resize(0); - // ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO(); - // Marshal.FreeHGlobal(platformIO.Handle->Monitors.Data); - // int numMonitors = GetSystemMetrics(SystemMetric.SM_CMONITORS); - // nint data = Marshal.AllocHGlobal(Marshal.SizeOf() * numMonitors); - // platformIO.Handle->Monitors = new ImVector(numMonitors, numMonitors, data); - - var monitorIndex = -1; - var enumfn = new MonitorEnumProcDelegate( - (hMonitor, _, _, _) => - { - monitorIndex++; - var info = new MONITORINFO { cbSize = (uint)sizeof(MONITORINFO) }; - if (!GetMonitorInfoW(hMonitor, &info)) - return true; - - var monitorLt = new Vector2(info.rcMonitor.left, info.rcMonitor.top); - var monitorRb = new Vector2(info.rcMonitor.right, info.rcMonitor.bottom); - var workLt = new Vector2(info.rcWork.left, info.rcWork.top); - var workRb = new Vector2(info.rcWork.right, info.rcWork.bottom); - // Give ImGui the info for this display - - 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)Marshal.GetFunctionPointerForDelegate(enumfn), - default); + EnumDisplayMonitors(default, null, &EnumDisplayMonitorsCallback, default); Log.Information("Monitors set up!"); - for (var i = 0; i < numMonitors; i++) + foreach (ref var monitor in pio.Handle->Monitors) { - var monitor = pio.Handle->Monitors[i]; Log.Information( - "Monitor {Index}: {MainPos} {MainSize} {WorkPos} {WorkSize}", - i, + "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) + { + var info = new MONITORINFO { cbSize = (uint)sizeof(MONITORINFO) }; + if (!GetMonitorInfoW(hMonitor, &info)) + return true; + + var monitorLt = new Vector2(info.rcMonitor.left, info.rcMonitor.top); + var monitorRb = new Vector2(info.rcMonitor.right, info.rcMonitor.bottom); + var workLt = new Vector2(info.rcWork.left, info.rcWork.top); + var workRb = new Vector2(info.rcWork.right, info.rcWork.bottom); + + // Give ImGui the info for this display + var imMonitor = new ImGuiPlatformMonitor + { + MainPos = monitorLt, + MainSize = monitorRb - monitorLt, + WorkPos = workLt, + WorkSize = workRb - workLt, + DpiScale = 1f, + }; + if ((info.dwFlags & MONITORINFOF_PRIMARY) != 0) + ImGui.GetPlatformIO().Monitors.PushFront(imMonitor); + else + ImGui.GetPlatformIO().Monitors.PushBack(imMonitor); + return true; + } } [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index e3eb22a04..69cdc4d28 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -86,7 +86,8 @@ internal class TitleScreenMenuWindow : Window, IDisposable : base( "TitleScreenMenuOverlay", 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); diff --git a/imgui/Dalamud.Bindings.ImGui/ImVector.cs b/imgui/Dalamud.Bindings.ImGui/ImVector.cs index 9a10c1d6b..67e450193 100644 --- a/imgui/Dalamud.Bindings.ImGui/ImVector.cs +++ b/imgui/Dalamud.Bindings.ImGui/ImVector.cs @@ -1,7 +1,12 @@ -using System.Runtime.CompilerServices; +using System.Collections; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace Dalamud.Bindings.ImGui; +/// +/// A structure representing a dynamic array for unmanaged types. +/// public unsafe struct ImVector { public readonly int Size; @@ -15,23 +20,23 @@ public unsafe struct ImVector Data = data; } - public ref T Ref(int index) - { - return ref Unsafe.AsRef((byte*)Data + index * Unsafe.SizeOf()); - } + public readonly ref T Ref(int index) => ref Unsafe.AsRef((byte*)this.Data + (index * Unsafe.SizeOf())); - public IntPtr Address(int index) - { - return (IntPtr)((byte*)Data + index * Unsafe.SizeOf()); - } + public readonly nint Address(int index) => (nint)((byte*)this.Data + (index * Unsafe.SizeOf())); } /// /// A structure representing a dynamic array for unmanaged types. /// /// The type of elements in the vector, must be unmanaged. -public unsafe struct ImVector where T : unmanaged +[StructLayout(LayoutKind.Sequential)] +public unsafe struct ImVector : IEnumerable + where T : unmanaged { + private int size; + private int capacity; + private T* data; + /// /// Initializes a new instance of the struct with the specified size, capacity, and data pointer. /// @@ -45,11 +50,6 @@ public unsafe struct ImVector where T : unmanaged this.data = data; } - private int size; - private int capacity; - private unsafe T* data; - - /// /// Gets or sets the element at the specified index. /// @@ -58,80 +58,72 @@ public unsafe struct ImVector where T : unmanaged /// Thrown when the index is out of range. public T this[int index] { - get + readonly get { - if (index < 0 || index >= size) - { + if (index < 0 || index >= this.size) throw new IndexOutOfRangeException(); - } - return data[index]; + return this.data[index]; } set { - if (index < 0 || index >= size) - { + if (index < 0 || index >= this.size) throw new IndexOutOfRangeException(); - } - data[index] = value; + this.data[index] = value; } } /// /// Gets a pointer to the first element of the vector. /// - public readonly T* Data => data; + public readonly T* Data => this.data; /// /// Gets a pointer to the first element of the vector. /// - public readonly T* Front => data; + public readonly T* Front => this.data; /// /// Gets a pointer to the last element of the vector. /// - public readonly T* Back => size > 0 ? data + size - 1 : null; + public readonly T* Back => this.size > 0 ? this.data + this.size - 1 : null; /// /// Gets or sets the capacity of the vector. /// public int Capacity { - readonly get => capacity; + readonly get => this.capacity; set { - if (capacity == value) - { + ArgumentOutOfRangeException.ThrowIfLessThan(value, this.size, nameof(Capacity)); + if (this.capacity == value) 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 { - int newSize = Math.Min(size, value); - T* newData = (T*)ImGui.MemAlloc((nuint)(value * sizeof(T))); - Buffer.MemoryCopy(data, newData, (nuint)(value * sizeof(T)), (nuint)(newSize * sizeof(T))); - ImGui.MemFree(data); - data = newData; - size = newSize; + var newSize = Math.Min(this.size, value); + var newData = (T*)ImGui.MemAlloc((nuint)(value * sizeof(T))); + Buffer.MemoryCopy(this.data, newData, (nuint)(value * sizeof(T)), (nuint)(newSize * sizeof(T))); + ImGui.MemFree(this.data); + this.data = newData; + this.size = newSize; } - capacity = value; + this.capacity = value; // Clear the rest of the data - for (int i = size; i < capacity; i++) - { - data[i] = default; - } + new Span(this.data + this.size, this.capacity - this.size).Clear(); } } /// /// Gets the number of elements in the vector. /// - public readonly int Size => size; + public readonly int Size => this.size; /// /// Grows the capacity of the vector to at least the specified value. @@ -139,10 +131,8 @@ public unsafe struct ImVector where T : unmanaged /// The new capacity. public void Grow(int newCapacity) { - if (newCapacity > capacity) - { - Capacity = newCapacity * 2; - } + var newCapacity2 = this.capacity > 0 ? this.capacity + (this.capacity / 2) : 8; + this.Capacity = newCapacity2 > newCapacity ? newCapacity2 : newCapacity; } /// @@ -151,10 +141,8 @@ public unsafe struct ImVector where T : unmanaged /// The minimum capacity required. public void EnsureCapacity(int size) { - if (size > capacity) - { + if (size > this.capacity) Grow(size); - } } /// @@ -164,25 +152,46 @@ public unsafe struct ImVector where T : unmanaged public void Resize(int newSize) { EnsureCapacity(newSize); - size = newSize; + this.size = newSize; } /// /// Clears all elements from the vector. /// - public void Clear() + public void Clear() => this.size = 0; + + /// + /// Adds an element to the end of the vector. + /// + /// The value to add. + [OverloadResolutionPriority(1)] + public void PushBack(T value) { - size = 0; + this.EnsureCapacity(this.size + 1); + this.data[this.size++] = value; } /// /// Adds an element to the end of the vector. /// /// The value to add. - public void PushBack(T value) + [OverloadResolutionPriority(2)] + public void PushBack(in T value) { - EnsureCapacity(size + 1); - data[size++] = value; + EnsureCapacity(this.size + 1); + this.data[this.size++] = value; + } + + /// + /// Adds an element to the front of the vector. + /// + /// The value to add. + public void PushFront(in T value) + { + if (this.size == 0) + this.PushBack(value); + else + this.Insert(0, value); } /// @@ -190,48 +199,126 @@ public unsafe struct ImVector where T : unmanaged /// 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 InsertRange(int index, ReadOnlySpan 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(this.data + index, v.Length); + v.CopyTo(new(this.data + index, v.Length)); + this.size += v.Length; + return dstSpan; + } + /// /// Frees the memory allocated for the vector. /// public void Free() { - if (data != null) + if (this.data != null) { - ImGui.MemFree(data); - data = null; - size = 0; - capacity = 0; + ImGui.MemFree(this.data); + this.data = null; + this.size = 0; + this.capacity = 0; } } - public ref T Ref(int index) + public readonly ref T Ref(int index) { - return ref Unsafe.AsRef((byte*)Data + index * Unsafe.SizeOf()); + return ref Unsafe.AsRef((byte*)Data + (index * Unsafe.SizeOf())); } - public ref TCast Ref(int index) + public readonly ref TCast Ref(int index) { - return ref Unsafe.AsRef((byte*)Data + index * Unsafe.SizeOf()); + return ref Unsafe.AsRef((byte*)Data + (index * Unsafe.SizeOf())); } - public void* Address(int index) + public readonly void* Address(int index) { - return (byte*)Data + index * Unsafe.SizeOf(); + return (byte*)Data + (index * Unsafe.SizeOf()); } - public void* Address(int index) + public readonly void* Address(int index) { - return (byte*)Data + index * Unsafe.SizeOf(); + return (byte*)Data + (index * Unsafe.SizeOf()); } - 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 AsSpan() => new(this.data, this.size); + + public readonly Enumerator GetEnumerator() => new(this.data, this.data + this.size); + + readonly IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + readonly IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + public struct Enumerator(T* begin, T* end) : IEnumerator, IEnumerable + { + private T* current = null; + + public readonly ref T Current => ref *this.current; + + readonly T IEnumerator.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 IEnumerable.GetEnumerator() => this.GetEnumerator(); + + readonly IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); } }