mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-01-03 14:23:40 +01:00
Merge pull request #1567 from Soreepeong/fix/imgui-clipboard
Fix ImGui clipboard copy/paste normalization
This commit is contained in:
commit
3611785357
6 changed files with 279 additions and 101 deletions
|
|
@ -104,13 +104,14 @@ resharper_can_use_global_alias = false
|
|||
resharper_csharp_align_multiline_parameter = true
|
||||
resharper_csharp_align_multiple_declaration = true
|
||||
resharper_csharp_empty_block_style = multiline
|
||||
resharper_csharp_int_align_comments = true
|
||||
resharper_csharp_int_align_comments = false
|
||||
resharper_csharp_new_line_before_while = true
|
||||
resharper_csharp_wrap_after_declaration_lpar = true
|
||||
resharper_csharp_wrap_after_invocation_lpar = true
|
||||
resharper_csharp_wrap_arguments_style = chop_if_long
|
||||
resharper_enforce_line_ending_style = true
|
||||
resharper_instance_members_qualify_declared_in = this_class, base_class
|
||||
resharper_int_align = false
|
||||
resharper_member_can_be_private_global_highlighting = none
|
||||
resharper_member_can_be_private_local_highlighting = none
|
||||
resharper_new_line_before_finally = true
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@
|
|||
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="7.0.0" />
|
||||
<PackageReference Include="System.Resources.Extensions" Version="7.0.0" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.22621.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Dalamud.Common\Dalamud.Common.csproj" />
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Configures the ImGui clipboard behaviour to work nicely with XIV.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// XIV uses '\r' for line endings and will truncate all text after a '\n' character.
|
||||
/// This means that copy/pasting multi-line text from ImGui to XIV will only copy the first line.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// ImGui uses '\n' for line endings and will ignore '\r' entirely.
|
||||
/// This means that copy/pasting multi-line text from XIV to ImGui will copy all the text
|
||||
/// without line breaks.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// To fix this we normalize all clipboard line endings entering/exiting ImGui to '\r\n' which
|
||||
/// works for both ImGui and XIV.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal static class ImGuiClipboardConfig
|
||||
{
|
||||
private delegate void SetClipboardTextDelegate(IntPtr userData, string text);
|
||||
private delegate string GetClipboardTextDelegate();
|
||||
|
||||
private static SetClipboardTextDelegate? _setTextOriginal = null;
|
||||
private static GetClipboardTextDelegate? _getTextOriginal = null;
|
||||
|
||||
// These must exist as variables to prevent them from being GC'd
|
||||
private static SetClipboardTextDelegate? _setText = null;
|
||||
private static GetClipboardTextDelegate? _getText = null;
|
||||
|
||||
public static void Apply()
|
||||
{
|
||||
var io = ImGui.GetIO();
|
||||
if (_setTextOriginal == null)
|
||||
{
|
||||
_setTextOriginal =
|
||||
Marshal.GetDelegateForFunctionPointer<SetClipboardTextDelegate>(io.SetClipboardTextFn);
|
||||
}
|
||||
|
||||
if (_getTextOriginal == null)
|
||||
{
|
||||
_getTextOriginal =
|
||||
Marshal.GetDelegateForFunctionPointer<GetClipboardTextDelegate>(io.GetClipboardTextFn);
|
||||
}
|
||||
|
||||
_setText = new SetClipboardTextDelegate(SetClipboardText);
|
||||
_getText = new GetClipboardTextDelegate(GetClipboardText);
|
||||
|
||||
io.SetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(_setText);
|
||||
io.GetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(_getText);
|
||||
}
|
||||
|
||||
public static void Unapply()
|
||||
{
|
||||
var io = ImGui.GetIO();
|
||||
if (_setTextOriginal != null)
|
||||
{
|
||||
io.SetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(_setTextOriginal);
|
||||
}
|
||||
if (_getTextOriginal != null)
|
||||
{
|
||||
io.GetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(_getTextOriginal);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetClipboardText(IntPtr userData, string text)
|
||||
{
|
||||
_setTextOriginal!(userData, text.ReplaceLineEndings("\r\n"));
|
||||
}
|
||||
|
||||
private static string GetClipboardText()
|
||||
{
|
||||
return _getTextOriginal!().ReplaceLineEndings("\r\n");
|
||||
}
|
||||
}
|
||||
199
Dalamud/Interface/Internal/ImGuiClipboardFunctionProvider.cs
Normal file
199
Dalamud/Interface/Internal/ImGuiClipboardFunctionProvider.cs
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using CheapLoc;
|
||||
|
||||
using Dalamud.Game.Gui.Toast;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
using static TerraFX.Interop.Windows.Windows;
|
||||
|
||||
namespace Dalamud.Interface.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Configures the ImGui clipboard behaviour to work nicely with XIV.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// XIV uses '\r' for line endings and will truncate all text after a '\n' character.
|
||||
/// This means that copy/pasting multi-line text from ImGui to XIV will only copy the first line.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// ImGui uses '\n' for line endings and will ignore '\r' entirely.
|
||||
/// This means that copy/pasting multi-line text from XIV to ImGui will copy all the text
|
||||
/// without line breaks.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// To fix this we normalize all clipboard line endings entering/exiting ImGui to '\r\n' which
|
||||
/// works for both ImGui and XIV.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed unsafe class ImGuiClipboardFunctionProvider : IServiceType, IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new(nameof(ImGuiClipboardFunctionProvider));
|
||||
private readonly nint clipboardUserDataOriginal;
|
||||
private readonly nint setTextOriginal;
|
||||
private readonly nint getTextOriginal;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ToastGui toastGui = Service<ToastGui>.Get();
|
||||
|
||||
private ImVectorWrapper<byte> clipboardData;
|
||||
private GCHandle clipboardUserData;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private ImGuiClipboardFunctionProvider(InterfaceManager.InterfaceManagerWithScene imws)
|
||||
{
|
||||
// Effectively waiting for ImGui to become available.
|
||||
_ = imws;
|
||||
Debug.Assert(ImGuiHelpers.IsImGuiInitialized, "IMWS initialized but IsImGuiInitialized is false?");
|
||||
|
||||
var io = ImGui.GetIO();
|
||||
this.clipboardUserDataOriginal = io.ClipboardUserData;
|
||||
this.setTextOriginal = io.SetClipboardTextFn;
|
||||
this.getTextOriginal = io.GetClipboardTextFn;
|
||||
io.ClipboardUserData = GCHandle.ToIntPtr(this.clipboardUserData = GCHandle.Alloc(this));
|
||||
io.SetClipboardTextFn = (nint)(delegate* unmanaged<nint, byte*, void>)&StaticSetClipboardTextImpl;
|
||||
io.GetClipboardTextFn = (nint)(delegate* unmanaged<nint, byte*>)&StaticGetClipboardTextImpl;
|
||||
|
||||
this.clipboardData = new(0);
|
||||
return;
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
static void StaticSetClipboardTextImpl(nint userData, byte* text) =>
|
||||
((ImGuiClipboardFunctionProvider)GCHandle.FromIntPtr(userData).Target)!.SetClipboardTextImpl(text);
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
static byte* StaticGetClipboardTextImpl(nint userData) =>
|
||||
((ImGuiClipboardFunctionProvider)GCHandle.FromIntPtr(userData).Target)!.GetClipboardTextImpl();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!this.clipboardUserData.IsAllocated)
|
||||
return;
|
||||
|
||||
var io = ImGui.GetIO();
|
||||
io.SetClipboardTextFn = this.setTextOriginal;
|
||||
io.GetClipboardTextFn = this.getTextOriginal;
|
||||
io.ClipboardUserData = this.clipboardUserDataOriginal;
|
||||
|
||||
this.clipboardUserData.Free();
|
||||
this.clipboardData.Dispose();
|
||||
}
|
||||
|
||||
private bool OpenClipboardOrShowError()
|
||||
{
|
||||
if (!OpenClipboard(default))
|
||||
{
|
||||
this.toastGui.ShowError(
|
||||
Loc.Localize(
|
||||
"ImGuiClipboardFunctionProviderClipboardInUse",
|
||||
"Some other application is using the clipboard. Try again later."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetClipboardTextImpl(byte* text)
|
||||
{
|
||||
if (!this.OpenClipboardOrShowError())
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var len = 0;
|
||||
while (text[len] != 0)
|
||||
len++;
|
||||
var str = Encoding.UTF8.GetString(text, len);
|
||||
str = str.ReplaceLineEndings("\r\n");
|
||||
var hMem = GlobalAlloc(GMEM.GMEM_MOVEABLE, (nuint)((str.Length + 1) * 2));
|
||||
if (hMem == 0)
|
||||
throw new OutOfMemoryException();
|
||||
|
||||
var ptr = (char*)GlobalLock(hMem);
|
||||
if (ptr == null)
|
||||
{
|
||||
throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error())
|
||||
?? throw new InvalidOperationException($"{nameof(GlobalLock)} failed.");
|
||||
}
|
||||
|
||||
str.AsSpan().CopyTo(new(ptr, str.Length));
|
||||
ptr[str.Length] = default;
|
||||
GlobalUnlock(hMem);
|
||||
|
||||
SetClipboardData(CF.CF_UNICODETEXT, hMem);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Error in {nameof(this.SetClipboardTextImpl)}");
|
||||
this.toastGui.ShowError(
|
||||
Loc.Localize(
|
||||
"ImGuiClipboardFunctionProviderErrorCopy",
|
||||
"Failed to copy. See logs for details."));
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseClipboard();
|
||||
}
|
||||
}
|
||||
|
||||
private byte* GetClipboardTextImpl()
|
||||
{
|
||||
this.clipboardData.Clear();
|
||||
|
||||
var formats = stackalloc uint[] { CF.CF_UNICODETEXT, CF.CF_TEXT };
|
||||
if (GetPriorityClipboardFormat(formats, 2) < 1 || !this.OpenClipboardOrShowError())
|
||||
{
|
||||
this.clipboardData.Add(0);
|
||||
return this.clipboardData.Data;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var hMem = (HGLOBAL)GetClipboardData(CF.CF_UNICODETEXT);
|
||||
if (hMem != default)
|
||||
{
|
||||
var ptr = (char*)GlobalLock(hMem);
|
||||
if (ptr == null)
|
||||
{
|
||||
throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error())
|
||||
?? throw new InvalidOperationException($"{nameof(GlobalLock)} failed.");
|
||||
}
|
||||
|
||||
var str = new string(ptr);
|
||||
str = str.ReplaceLineEndings("\r\n");
|
||||
this.clipboardData.Resize(Encoding.UTF8.GetByteCount(str) + 1);
|
||||
Encoding.UTF8.GetBytes(str, this.clipboardData.DataSpan);
|
||||
this.clipboardData[^1] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.clipboardData.Add(0);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Error in {nameof(this.GetClipboardTextImpl)}");
|
||||
this.toastGui.ShowError(
|
||||
Loc.Localize(
|
||||
"ImGuiClipboardFunctionProviderErrorPaste",
|
||||
"Failed to paste. See logs for details."));
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseClipboard();
|
||||
}
|
||||
|
||||
return this.clipboardData.Data;
|
||||
}
|
||||
}
|
||||
|
|
@ -240,7 +240,6 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
this.processMessageHook?.Dispose();
|
||||
}).Wait();
|
||||
|
||||
ImGuiClipboardConfig.Unapply();
|
||||
this.scene?.Dispose();
|
||||
}
|
||||
|
||||
|
|
@ -629,7 +628,6 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale;
|
||||
|
||||
this.SetupFonts();
|
||||
ImGuiClipboardConfig.Apply();
|
||||
|
||||
if (!configuration.IsDocking)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ public unsafe struct ImVectorWrapper<T> : IList<T>, IList, IReadOnlyList<T>, IDi
|
|||
/// </summary>
|
||||
/// <param name="initialCapacity">The initial capacity.</param>
|
||||
/// <param name="destroyer">The destroyer function to call on item removal.</param>
|
||||
public ImVectorWrapper(int initialCapacity = 0, ImGuiNativeDestroyDelegate? destroyer = null)
|
||||
public ImVectorWrapper(int initialCapacity, ImGuiNativeDestroyDelegate? destroyer = null)
|
||||
{
|
||||
if (initialCapacity < 0)
|
||||
{
|
||||
|
|
@ -394,7 +394,7 @@ public unsafe struct ImVectorWrapper<T> : IList<T>, IList, IReadOnlyList<T>, IDi
|
|||
}
|
||||
|
||||
/// <inheritdoc cref="List{T}.AddRange"/>
|
||||
public void AddRange(Span<T> items)
|
||||
public void AddRange(ReadOnlySpan<T> items)
|
||||
{
|
||||
this.EnsureCapacityExponential(this.LengthUnsafe + items.Length);
|
||||
foreach (var item in items)
|
||||
|
|
@ -466,7 +466,7 @@ public unsafe struct ImVectorWrapper<T> : IList<T>, IList, IReadOnlyList<T>, IDi
|
|||
/// <param name="capacity">The minimum capacity to ensure.</param>
|
||||
/// <returns>Whether the capacity has been changed.</returns>
|
||||
public bool EnsureCapacityExponential(int capacity)
|
||||
=> this.EnsureCapacity(1 << ((sizeof(int) * 8) - BitOperations.LeadingZeroCount((uint)this.LengthUnsafe)));
|
||||
=> this.EnsureCapacity(1 << ((sizeof(int) * 8) - BitOperations.LeadingZeroCount((uint)capacity)));
|
||||
|
||||
/// <summary>
|
||||
/// Resizes the underlying array and fills with zeroes if grown.
|
||||
|
|
@ -519,10 +519,11 @@ public unsafe struct ImVectorWrapper<T> : IList<T>, IList, IReadOnlyList<T>, IDi
|
|||
if (index < 0 || index > this.LengthUnsafe)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
this.EnsureCapacityExponential(this.CapacityUnsafe + 1);
|
||||
this.EnsureCapacityExponential(this.LengthUnsafe + 1);
|
||||
var num = this.LengthUnsafe - index;
|
||||
Buffer.MemoryCopy(this.DataUnsafe + index, this.DataUnsafe + index + 1, num * sizeof(T), num * sizeof(T));
|
||||
this.DataUnsafe[index] = item;
|
||||
this.LengthUnsafe += 1;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="List{T}.InsertRange"/>
|
||||
|
|
@ -535,6 +536,7 @@ public unsafe struct ImVectorWrapper<T> : IList<T>, IList, IReadOnlyList<T>, IDi
|
|||
Buffer.MemoryCopy(this.DataUnsafe + index, this.DataUnsafe + index + count, num * sizeof(T), num * sizeof(T));
|
||||
foreach (var item in items)
|
||||
this.DataUnsafe[index++] = item;
|
||||
this.LengthUnsafe += count;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -543,14 +545,15 @@ public unsafe struct ImVectorWrapper<T> : IList<T>, IList, IReadOnlyList<T>, IDi
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="List{T}.AddRange"/>
|
||||
public void InsertRange(int index, Span<T> items)
|
||||
/// <inheritdoc cref="List{T}.InsertRange"/>
|
||||
public void InsertRange(int index, ReadOnlySpan<T> items)
|
||||
{
|
||||
this.EnsureCapacityExponential(this.LengthUnsafe + items.Length);
|
||||
var num = this.LengthUnsafe - index;
|
||||
Buffer.MemoryCopy(this.DataUnsafe + index, this.DataUnsafe + index + items.Length, num * sizeof(T), num * sizeof(T));
|
||||
foreach (var item in items)
|
||||
this.DataUnsafe[index++] = item;
|
||||
this.LengthUnsafe += items.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -558,15 +561,7 @@ public unsafe struct ImVectorWrapper<T> : IList<T>, IList, IReadOnlyList<T>, IDi
|
|||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <param name="skipDestroyer">Whether to skip calling the destroyer function.</param>
|
||||
public void RemoveAt(int index, bool skipDestroyer = false)
|
||||
{
|
||||
this.EnsureIndex(index);
|
||||
var num = this.LengthUnsafe - index - 1;
|
||||
if (!skipDestroyer)
|
||||
this.destroyer?.Invoke(&this.DataUnsafe[index]);
|
||||
|
||||
Buffer.MemoryCopy(this.DataUnsafe + index + 1, this.DataUnsafe + index, num * sizeof(T), num * sizeof(T));
|
||||
}
|
||||
public void RemoveAt(int index, bool skipDestroyer = false) => this.RemoveRange(index, 1, skipDestroyer);
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IList<T>.RemoveAt(int index) => this.RemoveAt(index);
|
||||
|
|
@ -574,6 +569,73 @@ public unsafe struct ImVectorWrapper<T> : IList<T>, IList, IReadOnlyList<T>, IDi
|
|||
/// <inheritdoc/>
|
||||
void IList.RemoveAt(int index) => this.RemoveAt(index);
|
||||
|
||||
/// <summary>
|
||||
/// Removes <paramref name="count"/> elements at the given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the first item to remove.</param>
|
||||
/// <param name="count">Number of items to remove.</param>
|
||||
/// <param name="skipDestroyer">Whether to skip calling the destroyer function.</param>
|
||||
public void RemoveRange(int index, int count, bool skipDestroyer = false)
|
||||
{
|
||||
this.EnsureIndex(index);
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(count), count, "Must be positive.");
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
if (!skipDestroyer && this.destroyer is { } d)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
d(this.DataUnsafe + index + i);
|
||||
}
|
||||
|
||||
var numItemsToMove = this.LengthUnsafe - index - count;
|
||||
var numBytesToMove = numItemsToMove * sizeof(T);
|
||||
Buffer.MemoryCopy(this.DataUnsafe + index + count, this.DataUnsafe + index, numBytesToMove, numBytesToMove);
|
||||
this.LengthUnsafe -= count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces a sequence at given offset <paramref name="index"/> of <paramref name="count"/> items with
|
||||
/// <paramref name="replacement"/>.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the first item to be replaced.</param>
|
||||
/// <param name="count">The number of items to be replaced.</param>
|
||||
/// <param name="replacement">The replacement.</param>
|
||||
/// <param name="skipDestroyer">Whether to skip calling the destroyer function.</param>
|
||||
public void ReplaceRange(int index, int count, ReadOnlySpan<T> replacement, bool skipDestroyer = false)
|
||||
{
|
||||
this.EnsureIndex(index);
|
||||
if (count < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(count), count, "Must be positive.");
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
// Ensure the capacity first, so that we can safely destroy the items first.
|
||||
this.EnsureCapacityExponential((this.LengthUnsafe + replacement.Length) - count);
|
||||
|
||||
if (!skipDestroyer && this.destroyer is { } d)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
d(this.DataUnsafe + index + i);
|
||||
}
|
||||
|
||||
if (count == replacement.Length)
|
||||
{
|
||||
replacement.CopyTo(this.DataSpan[index..]);
|
||||
}
|
||||
else if (count > replacement.Length)
|
||||
{
|
||||
replacement.CopyTo(this.DataSpan[index..]);
|
||||
this.RemoveRange(index + replacement.Length, count - replacement.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
replacement[..count].CopyTo(this.DataSpan[index..]);
|
||||
this.InsertRange(index + count, replacement[count..]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the capacity exactly as requested.
|
||||
/// </summary>
|
||||
|
|
@ -611,9 +673,6 @@ public unsafe struct ImVectorWrapper<T> : IList<T>, IList, IReadOnlyList<T>, IDi
|
|||
|
||||
if (!oldSpan.IsEmpty && !newSpan.IsEmpty)
|
||||
oldSpan[..this.LengthUnsafe].CopyTo(newSpan);
|
||||
// #if DEBUG
|
||||
// new Span<byte>(newAlloc + this.LengthUnsafe, sizeof(T) * (capacity - this.LengthUnsafe)).Fill(0xCC);
|
||||
// #endif
|
||||
|
||||
if (oldAlloc != null)
|
||||
ImGuiNative.igMemFree(oldAlloc);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue