diff --git a/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.StaticLookupFunctions.cs b/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.StaticLookupFunctions.cs index a7b70ce35..5710a5991 100644 --- a/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.StaticLookupFunctions.cs +++ b/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.StaticLookupFunctions.cs @@ -299,12 +299,11 @@ internal sealed partial class Win32InputHandler private static void ViewportFlagsToWin32Styles(ImGuiViewportFlags flags, out int style, out int exStyle) { - style = (flags & ImGuiViewportFlags.NoDecoration) != 0 ? unchecked((int)WS.WS_POPUP) : WS.WS_OVERLAPPEDWINDOW; - exStyle = (flags & ImGuiViewportFlags.NoTaskBarIcon) != 0 ? WS.WS_EX_TOOLWINDOW : WS.WS_EX_APPWINDOW; + style = (int)(flags.HasFlag(ImGuiViewportFlags.NoDecoration) ? WS.WS_POPUP : WS.WS_OVERLAPPEDWINDOW); + exStyle = + (int)(flags.HasFlag(ImGuiViewportFlags.NoTaskBarIcon) ? WS.WS_EX_TOOLWINDOW : (uint)WS.WS_EX_APPWINDOW); exStyle |= WS.WS_EX_NOREDIRECTIONBITMAP; - if ((flags & ImGuiViewportFlags.TopMost) != 0) + if (flags.HasFlag(ImGuiViewportFlags.TopMost)) exStyle |= WS.WS_EX_TOPMOST; - if ((flags & ImGuiViewportFlags.NoInputs) != 0) - exStyle |= WS.WS_EX_TRANSPARENT | WS.WS_EX_LAYERED; } } diff --git a/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs b/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs index 0b2e27b57..596df4c67 100644 --- a/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs +++ b/Dalamud/Interface/ImGuiBackend/InputHandler/Win32InputHandler.cs @@ -8,7 +8,6 @@ using System.Text; using Dalamud.Bindings.ImGui; using Dalamud.Memory; -using Dalamud.Utility; using Serilog; @@ -35,12 +34,11 @@ 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; @@ -66,8 +64,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors | ImGuiBackendFlags.HasSetMousePos | ImGuiBackendFlags.RendererHasViewports | - ImGuiBackendFlags.PlatformHasViewports | - ImGuiBackendFlags.HasMouseHoveredViewport; + ImGuiBackendFlags.PlatformHasViewports; this.platformNamePtr = Marshal.StringToHGlobalAnsi("imgui_impl_win32_c#"); io.Handle->BackendPlatformName = (byte*)this.platformNamePtr; @@ -77,6 +74,8 @@ 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); @@ -96,6 +95,8 @@ 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; @@ -154,7 +155,6 @@ 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 +168,9 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler this.viewportHandler.UpdateMonitors(); - this.UpdateMouseData(focusedWindow); + this.UpdateMousePos(); - this.ProcessKeyEventsWorkarounds(focusedWindow); + this.ProcessKeyEventsWorkarounds(); // 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,40 +224,6 @@ 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: @@ -270,10 +236,11 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler var button = GetButton(msg, wParam); if (io.WantCaptureMouse) { - if (this.mouseButtonsDown == 0 && GetCapture() == nint.Zero) + if (!ImGui.IsAnyMouseDown() && GetCapture() == nint.Zero) SetCapture(hWndCurrent); - this.mouseButtonsDown |= 1 << button; - io.AddMouseButtonEvent(button, true); + + io.MouseDown[button] = true; + this.imguiMouseIsDown[button] = true; return default(LRESULT); } @@ -289,12 +256,13 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler case WM.WM_XBUTTONUP: { var button = GetButton(msg, wParam); - if (io.WantCaptureMouse) + if (io.WantCaptureMouse && this.imguiMouseIsDown[button]) { - this.mouseButtonsDown &= ~(1 << button); - if (this.mouseButtonsDown == 0 && GetCapture() == hWndCurrent) + if (!ImGui.IsAnyMouseDown() && GetCapture() == hWndCurrent) ReleaseCapture(); - io.AddMouseButtonEvent(button, false); + + io.MouseDown[button] = false; + this.imguiMouseIsDown[button] = false; return default(LRESULT); } @@ -304,7 +272,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler case WM.WM_MOUSEWHEEL: if (io.WantCaptureMouse) { - io.AddMouseWheelEvent(0, GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA); + io.MouseWheel += GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA; return default(LRESULT); } @@ -312,7 +280,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler case WM.WM_MOUSEHWHEEL: if (io.WantCaptureMouse) { - io.AddMouseWheelEvent(GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA, 0); + io.MouseWheelH += GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA; return default(LRESULT); } @@ -406,84 +374,66 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler this.viewportHandler.UpdateMonitors(); break; - case WM.WM_SETFOCUS when hWndCurrent == this.hWnd: - io.AddFocusEvent(true); - break; - case WM.WM_KILLFOCUS when hWndCurrent == this.hWnd: - io.AddFocusEvent(false); - // if (!ImGui.IsAnyMouseDown() && GetCapture() == hWndCurrent) - // ReleaseCapture(); - // - // ImGui.GetIO().WantCaptureMouse = false; - // ImGui.ClearWindowFocus(); + if (!ImGui.IsAnyMouseDown() && GetCapture() == hWndCurrent) + ReleaseCapture(); + + ImGui.GetIO().WantCaptureMouse = false; + ImGui.ClearWindowFocus(); break; } return null; } - private void UpdateMouseData(HWND focusedWindow) + private void UpdateMousePos() { var io = ImGui.GetIO(); + var pt = default(POINT); - 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) + // 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)) { - // (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) { - 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); + SetCursorPos((int)io.MousePos.X, (int)io.MousePos.Y); } - } - // (Optional) Fallback to provide mouse position when focused (WM_MOUSEMOVE already provides this when hovered or captured) - if (!io.WantSetMousePos && !this.mouseTracked && hasMouseScreenPos) - { - // 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); + if (GetCursorPos(&pt)) + { + io.MousePos.X = pt.x; + io.MousePos.Y = pt.y; + } + else + { + io.MousePos.X = float.MinValue; + io.MousePos.Y = float.MinValue; + } } else { - io.AddMouseViewportEvent(0); - } - } + if (io.WantSetMousePos) + { + pt.x = (int)io.MousePos.X; + pt.y = (int)io.MousePos.Y; + ClientToScreen(this.hWnd, &pt); + SetCursorPos(pt.x, pt.y); + } - private ImGuiViewportPtr ViewportFromPoint(POINT mouseScreenPos) - { - var hoveredHwnd = WindowFromPoint(mouseScreenPos); - return hoveredHwnd != default ? ImGui.FindViewportByPlatformHandle(hoveredHwnd) : default; + 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; + } + } } private bool UpdateMouseCursor() @@ -501,7 +451,7 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler return true; } - private void ProcessKeyEventsWorkarounds(HWND focusedWindow) + private void ProcessKeyEventsWorkarounds() { // 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)) @@ -530,7 +480,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 = focusedWindow == this.hWnd; + var isForeground = GetForegroundWindow() == this.hWnd; for (var i = (int)ImGuiKey.NamedKeyBegin; i < (int)ImGuiKey.NamedKeyEnd; i++) { // Skip raising modifier keys if the game is focused. @@ -696,7 +646,14 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler return; 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) { @@ -736,50 +693,59 @@ 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(); - pio.Handle->Monitors.Resize(0); + 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()); - EnumDisplayMonitors(default, null, &EnumDisplayMonitorsCallback, default); + // 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); Log.Information("Monitors set up!"); - foreach (ref var monitor in pio.Handle->Monitors) + for (var i = 0; i < numMonitors; i++) { + var monitor = pio.Handle->Monitors[i]; Log.Information( - "Monitor: {MainPos} {MainSize} {WorkPos} {WorkSize}", + "Monitor {Index}: {MainPos} {MainSize} {WorkPos} {WorkSize}", + i, 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)])] @@ -828,9 +794,6 @@ internal sealed unsafe partial class Win32InputHandler : IImGuiInputHandler null); } - if (data->Hwnd == 0) - Util.Fatal($"CreateWindowExW failed: {GetLastError()}", "ImGui Viewport error"); - data->HwndOwned = true; viewport.PlatformRequestResize = false; viewport.PlatformHandle = viewport.PlatformHandleRaw = data->Hwnd; diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs index 85ab2e441..ad60d405e 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringColorStackSet.cs @@ -15,6 +15,10 @@ namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal; /// Color stacks to use while evaluating a SeString. internal sealed class SeStringColorStackSet { + /// Parsed , containing colors to use with and + /// . + private readonly uint[,] colorTypes; + /// Foreground color stack while evaluating a SeString for rendering. /// Touched only from the main thread. private readonly List colorStack = []; @@ -35,38 +39,30 @@ internal sealed class SeStringColorStackSet foreach (var row in uiColor) maxId = (int)Math.Max(row.RowId, maxId); - this.ColorTypes = new uint[maxId + 1, 4]; + this.colorTypes = new uint[maxId + 1, 4]; foreach (var row in uiColor) { // Contains ABGR. - this.ColorTypes[row.RowId, 0] = row.Dark; - this.ColorTypes[row.RowId, 1] = row.Light; - this.ColorTypes[row.RowId, 2] = row.ClassicFF; - this.ColorTypes[row.RowId, 3] = row.ClearBlue; + this.colorTypes[row.RowId, 0] = row.Dark; + this.colorTypes[row.RowId, 1] = row.Light; + this.colorTypes[row.RowId, 2] = row.ClassicFF; + this.colorTypes[row.RowId, 3] = row.ClearBlue; } if (BitConverter.IsLittleEndian) { // ImGui wants RGBA in LE. - fixed (uint* p = this.ColorTypes) + fixed (uint* p = this.colorTypes) { - foreach (ref var r in new Span(p, this.ColorTypes.GetLength(0) * this.ColorTypes.GetLength(1))) + foreach (ref var r in new Span(p, this.colorTypes.GetLength(0) * this.colorTypes.GetLength(1))) r = BinaryPrimitives.ReverseEndianness(r); } } } - /// Initializes a new instance of the class. - /// Color types. - public SeStringColorStackSet(uint[,] colorTypes) => this.ColorTypes = colorTypes; - /// Gets a value indicating whether at least one color has been pushed to the edge color stack. public bool HasAdditionalEdgeColor { get; private set; } - /// Gets the parsed containing colors to use with - /// and . - public uint[,] ColorTypes { get; } - /// Resets the colors in the stack. /// Draw state. internal void Initialize(scoped ref SeStringDrawState drawState) @@ -195,9 +191,9 @@ internal sealed class SeStringColorStackSet } // Opacity component is ignored. - var color = themeIndex >= 0 && themeIndex < this.ColorTypes.GetLength(1) && - colorTypeIndex < this.ColorTypes.GetLength(0) - ? this.ColorTypes[colorTypeIndex, themeIndex] + var color = themeIndex >= 0 && themeIndex < this.colorTypes.GetLength(1) && + colorTypeIndex < this.colorTypes.GetLength(0) + ? this.colorTypes[colorTypeIndex, themeIndex] : 0u; rgbaStack.Add(color | 0xFF000000u); diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs index 87df2da2c..d0c40cd9f 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Numerics; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -26,7 +25,7 @@ namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal; /// Draws SeString. [ServiceManager.EarlyLoadedService] -internal class SeStringRenderer : IServiceType +internal unsafe class SeStringRenderer : IInternalDisposableService { private const int ImGuiContextCurrentWindowOffset = 0x3FF0; private const int ImGuiWindowDcOffset = 0x118; @@ -48,19 +47,28 @@ internal class SeStringRenderer : IServiceType /// Parsed text fragments from a SeString. /// Touched only from the main thread. - private readonly List fragmentsMainThread = []; + private readonly List fragments = []; /// Color stacks to use while evaluating a SeString for rendering. /// Touched only from the main thread. - private readonly SeStringColorStackSet colorStackSetMainThread; + private readonly SeStringColorStackSet colorStackSet; + + /// Splits a draw list so that different layers of a single glyph can be drawn out of order. + private ImDrawListSplitter* splitter = ImGui.ImDrawListSplitter(); [ServiceManager.ServiceConstructor] private SeStringRenderer(DataManager dm, TargetSigScanner sigScanner) { - this.colorStackSetMainThread = new(dm.Excel.GetSheet()); + this.colorStackSet = new(dm.Excel.GetSheet()); this.gfd = dm.GetFile("common/font/gfdata.gfd")!; } + /// Finalizes an instance of the class. + ~SeStringRenderer() => this.ReleaseUnmanagedResources(); + + /// + void IInternalDisposableService.DisposeService() => this.ReleaseUnmanagedResources(); + /// Compiles and caches a SeString from a text macro representation. /// SeString text macro representation. /// Newline characters will be normalized to newline payloads. @@ -72,44 +80,6 @@ internal class SeStringRenderer : IServiceType text.ReplaceLineEndings("
"), new() { ExceptionMode = MacroStringParseExceptionMode.EmbedError })); - /// Creates a draw data that will draw the given SeString onto it. - /// SeString to render. - /// Parameters for drawing. - /// A new self-contained draw data. - public unsafe BufferBackedImDrawData CreateDrawData( - ReadOnlySeStringSpan sss, - scoped in SeStringDrawParams drawParams = default) - { - if (drawParams.TargetDrawList is not null) - { - throw new ArgumentException( - $"{nameof(SeStringDrawParams.TargetDrawList)} may not be specified.", - nameof(drawParams)); - } - - var dd = BufferBackedImDrawData.Create(); - - try - { - var size = this.Draw(sss, drawParams with { TargetDrawList = dd.ListPtr }).Size; - - var offset = drawParams.ScreenOffset ?? Vector2.Zero; - foreach (var vtx in new Span(dd.ListPtr.VtxBuffer.Data, dd.ListPtr.VtxBuffer.Size)) - offset = Vector2.Min(offset, vtx.Pos); - - dd.Data.DisplayPos = offset; - dd.Data.DisplaySize = size - offset; - dd.Data.Valid = 1; - dd.UpdateDrawDataStatistics(); - return dd; - } - catch - { - dd.Dispose(); - throw; - } - } - /// Compiles and caches a SeString from a text macro representation, and then draws it. /// SeString text macro representation. /// Newline characters will be normalized to newline payloads. @@ -143,42 +113,28 @@ internal class SeStringRenderer : IServiceType /// ImGui ID, if link functionality is desired. /// Button flags to use on link interaction. /// Interaction result of the rendered text. - public unsafe SeStringDrawResult Draw( + public SeStringDrawResult Draw( ReadOnlySeStringSpan sss, scoped in SeStringDrawParams drawParams = default, ImGuiId imGuiId = default, ImGuiButtonFlags buttonFlags = ImGuiButtonFlags.MouseButtonDefault) { - // Interactivity is supported only from the main thread. - if (!imGuiId.IsEmpty()) - ThreadSafety.AssertMainThread(); + // Drawing is only valid if done from the main thread anyway, especially with interactivity. + ThreadSafety.AssertMainThread(); if (drawParams.TargetDrawList is not null && imGuiId) throw new ArgumentException("ImGuiId cannot be set if TargetDrawList is manually set.", nameof(imGuiId)); - using var cleanup = new DisposeSafety.ScopedFinalizer(); - - ImFont* font = null; - if (drawParams.Font.HasValue) - font = drawParams.Font.Value; - if (ThreadSafety.IsMainThread && drawParams.TargetDrawList is null && font is null) - font = ImGui.GetFont(); - if (font is null) - throw new ArgumentException("Specified font is empty."); - // This also does argument validation for drawParams. Do it here. - // `using var` makes a struct read-only, but we do want to modify it. - var stateStorage = new SeStringDrawState( - sss, - drawParams, - ThreadSafety.IsMainThread ? this.colorStackSetMainThread : new(this.colorStackSetMainThread.ColorTypes), - ThreadSafety.IsMainThread ? this.fragmentsMainThread : [], - font); - ref var state = ref Unsafe.AsRef(in stateStorage); + var state = new SeStringDrawState(sss, drawParams, this.colorStackSet, this.splitter); + + // Reset and initialize the state. + this.fragments.Clear(); + this.colorStackSet.Initialize(ref state); // Analyze the provided SeString and break it up to text fragments. this.CreateTextFragments(ref state); - var fragmentSpan = CollectionsMarshal.AsSpan(state.Fragments); + var fragmentSpan = CollectionsMarshal.AsSpan(this.fragments); // Calculate size. var size = Vector2.Zero; @@ -191,19 +147,26 @@ internal class SeStringRenderer : IServiceType state.SplitDrawList(); - var itemSize = size; - if (drawParams.TargetDrawList is null) + // Handle cases where ImGui.AlignTextToFramePadding has been called. + var context = ImGui.GetCurrentContext(); + var currLineTextBaseOffset = 0f; + if (!context.IsNull) { - // Handle cases where ImGui.AlignTextToFramePadding has been called. - var currLineTextBaseOffset = ImGui.GetCurrentContext().CurrentWindow.DC.CurrLineTextBaseOffset; - if (currLineTextBaseOffset != 0f) + var currentWindow = context.CurrentWindow; + if (!currentWindow.IsNull) { - itemSize.Y += 2 * currLineTextBaseOffset; - foreach (ref var f in fragmentSpan) - f.Offset += new Vector2(0, currLineTextBaseOffset); + currLineTextBaseOffset = currentWindow.DC.CurrLineTextBaseOffset; } } + var itemSize = size; + if (currLineTextBaseOffset != 0f) + { + itemSize.Y += 2 * currLineTextBaseOffset; + foreach (ref var f in fragmentSpan) + f.Offset += new Vector2(0, currLineTextBaseOffset); + } + // Draw all text fragments. var lastRune = default(Rune); foreach (ref var f in fragmentSpan) @@ -317,6 +280,15 @@ internal class SeStringRenderer : IServiceType return displayRune.Value != 0; } + private void ReleaseUnmanagedResources() + { + if (this.splitter is not null) + { + this.splitter->Destroy(); + this.splitter = null; + } + } + /// Creates text fragment, taking line and word breaking into account. /// Draw state. private void CreateTextFragments(ref SeStringDrawState state) @@ -419,7 +391,7 @@ internal class SeStringRenderer : IServiceType var overflows = Math.Max(w, xy.X + fragment.VisibleWidth) > state.WrapWidth; // Test if the fragment does not fit into the current line and the current line is not empty. - if (xy.X != 0 && state.Fragments.Count > 0 && !state.Fragments[^1].BreakAfter && overflows) + if (xy.X != 0 && this.fragments.Count > 0 && !this.fragments[^1].BreakAfter && overflows) { // Introduce break if this is the first time testing the current break unit or the current fragment // is an entity. @@ -429,7 +401,7 @@ internal class SeStringRenderer : IServiceType xy.X = 0; xy.Y += state.LineHeight; w = 0; - CollectionsMarshal.AsSpan(state.Fragments)[^1].BreakAfter = true; + CollectionsMarshal.AsSpan(this.fragments)[^1].BreakAfter = true; fragment.Offset = xy; // Now that the fragment is given its own line, test if it overflows again. @@ -447,16 +419,16 @@ internal class SeStringRenderer : IServiceType fragment = this.CreateFragment(state, prev, curr, true, xy, link, entity, remainingWidth); } } - else if (state.Fragments.Count > 0 && xy.X != 0) + else if (this.fragments.Count > 0 && xy.X != 0) { // New fragment fits into the current line, and it has a previous fragment in the same line. // If the previous fragment ends with a soft hyphen, adjust its width so that the width of its // trailing soft hyphens are not considered. - if (state.Fragments[^1].EndsWithSoftHyphen) - xy.X += state.Fragments[^1].AdvanceWidthWithoutSoftHyphen - state.Fragments[^1].AdvanceWidth; + if (this.fragments[^1].EndsWithSoftHyphen) + xy.X += this.fragments[^1].AdvanceWidthWithoutSoftHyphen - this.fragments[^1].AdvanceWidth; // Adjust this fragment's offset from kerning distance. - xy.X += state.CalculateScaledDistance(state.Fragments[^1].LastRune, fragment.FirstRune); + xy.X += state.CalculateScaledDistance(this.fragments[^1].LastRune, fragment.FirstRune); fragment.Offset = xy; } @@ -467,7 +439,7 @@ internal class SeStringRenderer : IServiceType w = Math.Max(w, xy.X + fragment.VisibleWidth); xy.X += fragment.AdvanceWidth; prev = fragment.To; - state.Fragments.Add(fragment); + this.fragments.Add(fragment); if (fragment.BreakAfter) { @@ -519,7 +491,7 @@ internal class SeStringRenderer : IServiceType if (gfdTextureSrv != 0) { state.Draw( - new(gfdTextureSrv), + new ImTextureID(gfdTextureSrv), offset + new Vector2(x, MathF.Round((state.LineHeight - size.Y) / 2)), size, useHq ? gfdEntry.HqUv0 : gfdEntry.Uv0, @@ -556,7 +528,7 @@ internal class SeStringRenderer : IServiceType return; - static unsafe nint GetGfdTextureSrv() + static nint GetGfdTextureSrv() { var uim = UIModule.Instance(); if (uim is null) @@ -581,7 +553,7 @@ internal class SeStringRenderer : IServiceType /// Determines a bitmap icon to display for the given SeString payload. /// Byte span that should include a SeString payload. /// Icon to display, or if it should not be displayed as an icon. - private unsafe BitmapFontIcon GetBitmapFontIconFor(ReadOnlySpan sss) + private BitmapFontIcon GetBitmapFontIconFor(ReadOnlySpan sss) { var e = new ReadOnlySeStringSpan(sss).GetEnumerator(); if (!e.MoveNext() || e.Current.MacroCode is not MacroCode.Icon and not MacroCode.Icon2) @@ -738,4 +710,38 @@ internal class SeStringRenderer : IServiceType firstDisplayRune ?? default, lastNonSoftHyphenRune); } + + /// Represents a text fragment in a SeString span. + /// Starting byte offset (inclusive) in a SeString. + /// Ending byte offset (exclusive) in a SeString. + /// Byte offset of the link that decorates this text fragment, or -1 if none. + /// Offset in pixels w.r.t. . + /// Replacement entity, if any. + /// Visible width of this text fragment. This is the width required to draw everything + /// without clipping. + /// Advance width of this text fragment. This is the width required to add to the cursor + /// to position the next fragment correctly. + /// Same with , but trimming all the + /// trailing soft hyphens. + /// Whether to insert a line break after this text fragment. + /// Whether this text fragment ends with one or more soft hyphens. + /// First rune in this text fragment. + /// Last rune in this text fragment, for the purpose of calculating kerning distance with + /// the following text fragment in the same line, if any. + private record struct TextFragment( + int From, + int To, + int Link, + Vector2 Offset, + SeStringReplacementEntity Entity, + float VisibleWidth, + float AdvanceWidth, + float AdvanceWidthWithoutSoftHyphen, + bool BreakAfter, + bool EndsWithSoftHyphen, + Rune FirstRune, + Rune LastRune) + { + public bool IsSoftHyphenVisible => this.EndsWithSoftHyphen && this.BreakAfter; + } } diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextFragment.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextFragment.cs deleted file mode 100644 index a64c32109..000000000 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextFragment.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Numerics; -using System.Text; - -namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal; - -/// Represents a text fragment in a SeString span. -/// Starting byte offset (inclusive) in a SeString. -/// Ending byte offset (exclusive) in a SeString. -/// Byte offset of the link that decorates this text fragment, or -1 if none. -/// Offset in pixels w.r.t. . -/// Replacement entity, if any. -/// Visible width of this text fragment. This is the width required to draw everything -/// without clipping. -/// Advance width of this text fragment. This is the width required to add to the cursor -/// to position the next fragment correctly. -/// Same with , but trimming all the -/// trailing soft hyphens. -/// Whether to insert a line break after this text fragment. -/// Whether this text fragment ends with one or more soft hyphens. -/// First rune in this text fragment. -/// Last rune in this text fragment, for the purpose of calculating kerning distance with -/// the following text fragment in the same line, if any. -internal record struct TextFragment( - int From, - int To, - int Link, - Vector2 Offset, - SeStringReplacementEntity Entity, - float VisibleWidth, - float AdvanceWidth, - float AdvanceWidthWithoutSoftHyphen, - bool BreakAfter, - bool EndsWithSoftHyphen, - Rune FirstRune, - Rune LastRune) -{ - /// Gets a value indicating whether the fragment ends with a visible soft hyphen. - public bool IsSoftHyphenVisible => this.EndsWithSoftHyphen && this.BreakAfter; -} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs index 11c1120b4..64a7f3db3 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -7,8 +6,6 @@ using System.Text; using Dalamud.Bindings.ImGui; using Dalamud.Interface.ImGuiSeStringRenderer.Internal; using Dalamud.Interface.Utility; -using Dalamud.Utility; - using FFXIVClientStructs.FFXIV.Component.GUI; using Lumina.Text.Payloads; using Lumina.Text.ReadOnly; @@ -22,75 +19,46 @@ public unsafe ref struct SeStringDrawState private static readonly int ChannelCount = Enum.GetValues().Length; private readonly ImDrawList* drawList; - - private ImDrawListSplitter splitter; + private readonly SeStringColorStackSet colorStackSet; + private readonly ImDrawListSplitter* splitter; /// Initializes a new instance of the struct. /// Raw SeString byte span. /// Instance of to initialize from. /// Instance of to use. - /// Fragments. - /// Font to use. + /// Instance of ImGui Splitter to use. internal SeStringDrawState( ReadOnlySpan span, scoped in SeStringDrawParams ssdp, SeStringColorStackSet colorStackSet, - List fragments, - ImFont* font) + ImDrawListSplitter* splitter) { + this.colorStackSet = colorStackSet; + this.splitter = splitter; + this.drawList = ssdp.TargetDrawList ?? ImGui.GetWindowDrawList(); this.Span = span; - this.ColorStackSet = colorStackSet; - this.Fragments = fragments; - this.Font = font; - - if (ssdp.TargetDrawList is null) - { - if (!ThreadSafety.IsMainThread) - { - throw new ArgumentException( - $"{nameof(ssdp.TargetDrawList)} must be set to render outside the main thread."); - } - - this.drawList = ssdp.TargetDrawList ?? ImGui.GetWindowDrawList(); - this.ScreenOffset = ssdp.ScreenOffset ?? ImGui.GetCursorScreenPos(); - this.FontSize = ssdp.FontSize ?? ImGui.GetFontSize(); - this.WrapWidth = ssdp.WrapWidth ?? ImGui.GetContentRegionAvail().X; - this.Color = ssdp.Color ?? ImGui.GetColorU32(ImGuiCol.Text); - this.LinkHoverBackColor = ssdp.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered); - this.LinkActiveBackColor = ssdp.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive); - this.ThemeIndex = ssdp.ThemeIndex ?? AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType; - } - else - { - this.drawList = ssdp.TargetDrawList.Value; - this.ScreenOffset = Vector2.Zero; - this.FontSize = ssdp.FontSize ?? throw new ArgumentException( - $"{nameof(ssdp.FontSize)} must be set to render outside the main thread."); - this.WrapWidth = ssdp.WrapWidth ?? float.MaxValue; - this.Color = ssdp.Color ?? uint.MaxValue; - this.LinkHoverBackColor = 0; // Interactivity is unused outside the main thread. - this.LinkActiveBackColor = 0; // Interactivity is unused outside the main thread. - this.ThemeIndex = ssdp.ThemeIndex ?? 0; - } - - this.splitter = default; this.GetEntity = ssdp.GetEntity; + this.ScreenOffset = ssdp.ScreenOffset ?? ImGui.GetCursorScreenPos(); this.ScreenOffset = new(MathF.Round(this.ScreenOffset.X), MathF.Round(this.ScreenOffset.Y)); + this.Font = ssdp.EffectiveFont; + this.FontSize = ssdp.FontSize ?? ImGui.GetFontSize(); this.FontSizeScale = this.FontSize / this.Font->FontSize; this.LineHeight = MathF.Round(ssdp.EffectiveLineHeight); + this.WrapWidth = ssdp.WrapWidth ?? ImGui.GetContentRegionAvail().X; this.LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f; this.Opacity = ssdp.EffectiveOpacity; this.EdgeOpacity = (ssdp.EdgeStrength ?? 0.25f) * ssdp.EffectiveOpacity; + this.Color = ssdp.Color ?? ImGui.GetColorU32(ImGuiCol.Text); this.EdgeColor = ssdp.EdgeColor ?? 0xFF000000; this.ShadowColor = ssdp.ShadowColor ?? 0xFF000000; + this.LinkHoverBackColor = ssdp.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered); + this.LinkActiveBackColor = ssdp.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive); this.ForceEdgeColor = ssdp.ForceEdgeColor; + this.ThemeIndex = ssdp.ThemeIndex ?? AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType; this.Bold = ssdp.Bold; this.Italic = ssdp.Italic; this.Edge = ssdp.Edge; this.Shadow = ssdp.Shadow; - - this.ColorStackSet.Initialize(ref this); - fragments.Clear(); } /// @@ -167,7 +135,7 @@ public unsafe ref struct SeStringDrawState /// Gets a value indicating whether the edge should be drawn. public readonly bool ShouldDrawEdge => - (this.Edge || this.ColorStackSet.HasAdditionalEdgeColor) && this.EdgeColor >= 0x1000000; + (this.Edge || this.colorStackSet.HasAdditionalEdgeColor) && this.EdgeColor >= 0x1000000; /// Gets a value indicating whether the edge should be drawn. public readonly bool ShouldDrawShadow => this is { Shadow: true, ShadowColor: >= 0x1000000 }; @@ -175,17 +143,11 @@ public unsafe ref struct SeStringDrawState /// Gets a value indicating whether the edge should be drawn. public readonly bool ShouldDrawForeground => this is { Color: >= 0x1000000 }; - /// Gets the color stacks. - internal SeStringColorStackSet ColorStackSet { get; } - - /// Gets the text fragments. - internal List Fragments { get; } - /// Sets the current channel in the ImGui draw list splitter. /// Channel to switch to. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetCurrentChannel(SeStringDrawChannel channelIndex) => - this.splitter.SetCurrentChannel(this.drawList, (int)channelIndex); + public readonly void SetCurrentChannel(SeStringDrawChannel channelIndex) => + this.splitter->SetCurrentChannel(this.drawList, (int)channelIndex); /// Draws a single texture. /// ImGui texture ID to draw from. @@ -254,7 +216,7 @@ public unsafe ref struct SeStringDrawState /// Draws a single glyph using current styling configurations. /// Glyph to draw. /// Offset of the glyph in pixels w.r.t. . - internal void DrawGlyph(scoped in ImGuiHelpers.ImFontGlyphReal g, Vector2 offset) + internal readonly void DrawGlyph(scoped in ImGuiHelpers.ImFontGlyphReal g, Vector2 offset) { var texId = this.Font->ContainerAtlas->Textures.Ref(g.TextureIndex).TexID; var xy0 = new Vector2( @@ -306,7 +268,7 @@ public unsafe ref struct SeStringDrawState /// Offset of the glyph in pixels w.r.t. /// . /// Advance width of the glyph. - internal void DrawLinkUnderline(Vector2 offset, float advanceWidth) + internal readonly void DrawLinkUnderline(Vector2 offset, float advanceWidth) { if (this.LinkUnderlineThickness < 1f) return; @@ -388,15 +350,15 @@ public unsafe ref struct SeStringDrawState switch (payload.MacroCode) { case MacroCode.Color: - this.ColorStackSet.HandleColorPayload(ref this, payload); + this.colorStackSet.HandleColorPayload(ref this, payload); return true; case MacroCode.EdgeColor: - this.ColorStackSet.HandleEdgeColorPayload(ref this, payload); + this.colorStackSet.HandleEdgeColorPayload(ref this, payload); return true; case MacroCode.ShadowColor: - this.ColorStackSet.HandleShadowColorPayload(ref this, payload); + this.colorStackSet.HandleShadowColorPayload(ref this, payload); return true; case MacroCode.Bold when payload.TryGetExpression(out var e) && e.TryGetUInt(out var u): @@ -417,11 +379,11 @@ public unsafe ref struct SeStringDrawState return true; case MacroCode.ColorType: - this.ColorStackSet.HandleColorTypePayload(ref this, payload); + this.colorStackSet.HandleColorTypePayload(ref this, payload); return true; case MacroCode.EdgeColorType: - this.ColorStackSet.HandleEdgeColorTypePayload(ref this, payload); + this.colorStackSet.HandleEdgeColorTypePayload(ref this, payload); return true; default: @@ -431,9 +393,10 @@ public unsafe ref struct SeStringDrawState /// Splits the draw list. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SplitDrawList() => this.splitter.Split(this.drawList, ChannelCount); + internal readonly void SplitDrawList() => + this.splitter->Split(this.drawList, ChannelCount); /// Merges the draw list. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void MergeDrawList() => this.splitter.Merge(this.drawList); + internal readonly void MergeDrawList() => this.splitter->Merge(this.drawList); } diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index af78c5b0c..64e1acaa4 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -533,13 +533,6 @@ internal class DalamudInterface : IInternalDisposableService this.creditsDarkeningAnimation.Restart(); } - /// - public T GetDataWindowWidget() where T : IDataWindowWidget => this.dataWindow.GetWidget(); - - /// Sets the data window current widget. - /// Widget to set current. - public void SetDataWindowWidget(IDataWindowWidget widget) => this.dataWindow.CurrentWidget = widget; - private void OnDraw() { this.FrameCount++; diff --git a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs index eb0589d59..ae86958dd 100644 --- a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs @@ -68,7 +68,7 @@ internal class DataWindow : Window, IDisposable private bool isExcept; private bool selectionCollapsed; - + private IDataWindowWidget currentWidget; private bool isLoaded; /// @@ -82,12 +82,9 @@ internal class DataWindow : Window, IDisposable this.RespectCloseHotkey = false; this.orderedModules = this.modules.OrderBy(module => module.DisplayName); - this.CurrentWidget = this.orderedModules.First(); + this.currentWidget = this.orderedModules.First(); } - /// Gets or sets the current widget. - public IDataWindowWidget CurrentWidget { get; set; } - /// public void Dispose() => this.modules.OfType().AggregateToDisposable().Dispose(); @@ -102,20 +99,6 @@ internal class DataWindow : Window, IDisposable { } - /// Gets the data window widget of the specified type. - /// Type of the data window widget to find. - /// Found widget. - public T GetWidget() where T : IDataWindowWidget - { - foreach (var m in this.modules) - { - if (m is T w) - return w; - } - - throw new ArgumentException($"No widget of type {typeof(T).FullName} found."); - } - /// /// Set the DataKind dropdown menu. /// @@ -127,7 +110,7 @@ internal class DataWindow : Window, IDisposable if (this.modules.FirstOrDefault(module => module.IsWidgetCommand(dataKind)) is { } targetModule) { - this.CurrentWidget = targetModule; + this.currentWidget = targetModule; } else { @@ -170,9 +153,9 @@ internal class DataWindow : Window, IDisposable { foreach (var widget in this.orderedModules) { - if (ImGui.Selectable(widget.DisplayName, this.CurrentWidget == widget)) + if (ImGui.Selectable(widget.DisplayName, this.currentWidget == widget)) { - this.CurrentWidget = widget; + this.currentWidget = widget; } } @@ -223,9 +206,9 @@ internal class DataWindow : Window, IDisposable try { - if (this.CurrentWidget is { Ready: true }) + if (this.currentWidget is { Ready: true }) { - this.CurrentWidget.Draw(); + this.currentWidget.Draw(); } else { diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/FontAwesomeTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/FontAwesomeTestWidget.cs index 4f5540daf..91f1af98e 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/FontAwesomeTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/FontAwesomeTestWidget.cs @@ -1,15 +1,9 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Numerics; -using System.Threading.Tasks; using Dalamud.Bindings.ImGui; -using Dalamud.Interface.Components; -using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Utility; -using Dalamud.Interface.Utility.Internal; - -using Lumina.Text.ReadOnly; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; @@ -93,30 +87,12 @@ internal class FontAwesomeTestWidget : IDataWindowWidget ImGuiHelpers.ScaledDummy(10f); for (var i = 0; i < this.icons?.Count; i++) { - if (this.icons[i] == FontAwesomeIcon.None) - continue; - - ImGui.AlignTextToFramePadding(); ImGui.Text($"0x{(int)this.icons[i].ToIconChar():X}"); ImGuiHelpers.ScaledRelativeSameLine(50f); ImGui.Text($"{this.iconNames?[i]}"); ImGuiHelpers.ScaledRelativeSameLine(280f); ImGui.PushFont(this.useFixedWidth ? InterfaceManager.IconFontFixedWidth : InterfaceManager.IconFont); ImGui.Text(this.icons[i].ToIconString()); - ImGuiHelpers.ScaledRelativeSameLine(320f); - if (this.useFixedWidth - ? ImGui.Button($"{(char)this.icons[i]}##FontAwesomeIconButton{i}") - : ImGuiComponents.IconButton($"##FontAwesomeIconButton{i}", this.icons[i])) - { - _ = Service.Get().ShowTextureSaveMenuAsync( - this.DisplayName, - this.icons[i].ToString(), - Task.FromResult( - Service.Get().CreateTextureFromSeString( - ReadOnlySeString.FromText(this.icons[i].ToIconString()), - new() { Font = ImGui.GetFont(), FontSize = ImGui.GetFontSize() }))); - } - ImGui.PopFont(); ImGuiHelpers.ScaledDummy(2f); } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs index 0f51e0322..7ff5a63be 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs @@ -1,6 +1,5 @@ using System.Numerics; using System.Text; -using System.Threading.Tasks; using Dalamud.Bindings.ImGui; using Dalamud.Data; @@ -10,13 +9,11 @@ using Dalamud.Interface.ImGuiSeStringRenderer; using Dalamud.Interface.ImGuiSeStringRenderer.Internal; using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Utility; -using Dalamud.Interface.Utility.Internal; using Dalamud.Storage.Assets; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Component.GUI; using Lumina.Excel.Sheets; using Lumina.Text; -using Lumina.Text.Parse; using Lumina.Text.Payloads; using Lumina.Text.ReadOnly; @@ -59,11 +56,11 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget /// public void Draw() { - var t2 = ImGui.ColorConvertU32ToFloat4(this.style.Color ??= ImGui.GetColorU32(ImGuiCol.Text)); + var t2 = ImGui.ColorConvertU32ToFloat4(this.style.Color ?? ImGui.GetColorU32(ImGuiCol.Text)); if (ImGui.ColorEdit4("Color", ref t2)) this.style.Color = ImGui.ColorConvertFloat4ToU32(t2); - t2 = ImGui.ColorConvertU32ToFloat4(this.style.EdgeColor ??= 0xFF000000u); + t2 = ImGui.ColorConvertU32ToFloat4(this.style.EdgeColor ?? 0xFF000000u); if (ImGui.ColorEdit4("Edge Color", ref t2)) this.style.EdgeColor = ImGui.ColorConvertFloat4ToU32(t2); @@ -72,27 +69,27 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget if (ImGui.Checkbox("Forced"u8, ref t)) this.style.ForceEdgeColor = t; - t2 = ImGui.ColorConvertU32ToFloat4(this.style.ShadowColor ??= 0xFF000000u); - if (ImGui.ColorEdit4("Shadow Color"u8, ref t2)) + t2 = ImGui.ColorConvertU32ToFloat4(this.style.ShadowColor ?? 0xFF000000u); + if (ImGui.ColorEdit4("Shadow Color", ref t2)) this.style.ShadowColor = ImGui.ColorConvertFloat4ToU32(t2); - t2 = ImGui.ColorConvertU32ToFloat4(this.style.LinkHoverBackColor ??= ImGui.GetColorU32(ImGuiCol.ButtonHovered)); - if (ImGui.ColorEdit4("Link Hover Color"u8, ref t2)) + t2 = ImGui.ColorConvertU32ToFloat4(this.style.LinkHoverBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonHovered)); + if (ImGui.ColorEdit4("Link Hover Color", ref t2)) this.style.LinkHoverBackColor = ImGui.ColorConvertFloat4ToU32(t2); - t2 = ImGui.ColorConvertU32ToFloat4(this.style.LinkActiveBackColor ??= ImGui.GetColorU32(ImGuiCol.ButtonActive)); - if (ImGui.ColorEdit4("Link Active Color"u8, ref t2)) + t2 = ImGui.ColorConvertU32ToFloat4(this.style.LinkActiveBackColor ?? ImGui.GetColorU32(ImGuiCol.ButtonActive)); + if (ImGui.ColorEdit4("Link Active Color", ref t2)) this.style.LinkActiveBackColor = ImGui.ColorConvertFloat4ToU32(t2); - var t3 = this.style.LineHeight ??= 1f; + var t3 = this.style.LineHeight ?? 1f; if (ImGui.DragFloat("Line Height"u8, ref t3, 0.01f, 0.4f, 3f, "%.02f")) this.style.LineHeight = t3; - t3 = this.style.Opacity ??= 1f; + t3 = this.style.Opacity ?? ImGui.GetStyle().Alpha; if (ImGui.DragFloat("Opacity"u8, ref t3, 0.005f, 0f, 1f, "%.02f")) this.style.Opacity = t3; - t3 = this.style.EdgeStrength ??= 0.25f; + t3 = this.style.EdgeStrength ?? 0.25f; if (ImGui.DragFloat("Edge Strength"u8, ref t3, 0.005f, 0f, 1f, "%.02f")) this.style.EdgeStrength = t3; @@ -243,27 +240,6 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget Service.Get().CompileAndCache(this.testString).Data.Span)); } - ImGui.SameLine(); - - if (ImGui.Button("Copy as Image")) - { - _ = Service.Get().ShowTextureSaveMenuAsync( - this.DisplayName, - $"From {nameof(SeStringRendererTestWidget)}", - Task.FromResult( - Service.Get().CreateTextureFromSeString( - ReadOnlySeString.FromMacroString( - this.testString, - new(ExceptionMode: MacroStringParseExceptionMode.EmbedError)), - this.style with - { - Font = ImGui.GetFont(), - FontSize = ImGui.GetFontSize(), - WrapWidth = ImGui.GetContentRegionAvail().X, - ThemeIndex = AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType, - }))); - } - ImGuiHelpers.ScaledDummy(3); ImGuiHelpers.CompileSeStringWrapped( "Optional features implemented for the following test input:
" + diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs index 3416a2506..52fa0e822 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; @@ -306,12 +306,12 @@ internal class TexWidget : IDataWindowWidget pres->Release(); ImGui.Text($"RC: Resource({rcres})/View({rcsrv})"); - ImGui.Text($"{source.Width} x {source.Height} | {source}"); + ImGui.Text(source.ToString()); } else { - ImGui.Text("RC: -"); - ImGui.Text(string.Empty); + ImGui.Text("RC: -"u8); + ImGui.Text(" "u8); } } @@ -342,10 +342,6 @@ internal class TexWidget : IDataWindowWidget runLater?.Invoke(); } - /// Adds a texture wrap for debug display purposes. - /// Task returning a texture. - public void AddTexture(Task textureTask) => this.addedTextures.Add(new(Api10: textureTask)); - private unsafe void DrawBlame(List allBlames) { var im = Service.Get(); diff --git a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs index 69cdc4d28..e3eb22a04 100644 --- a/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs +++ b/Dalamud/Interface/Internal/Windows/TitleScreenMenuWindow.cs @@ -86,8 +86,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable : base( "TitleScreenMenuOverlay", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar | - ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus | - ImGuiWindowFlags.NoDocking) + ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus) { this.showTsm = consoleManager.AddVariable("dalamud.show_tsm", "Show the Title Screen Menu", true); diff --git a/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs b/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs index be2f5a742..2853aa4d2 100644 --- a/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs +++ b/Dalamud/Interface/ManagedFontAtlas/IFontHandle.cs @@ -1,5 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; +using System.Threading.Tasks; using Dalamud.Bindings.ImGui; @@ -34,22 +33,10 @@ public interface IFontHandle : IDisposable ///
/// /// Use directly if you want to keep the current ImGui font if the font is not ready.
- /// Alternatively, use to wait for this property to become true. + /// Alternatively, use to wait for this property to become true. ///
bool Available { get; } - /// - /// Attempts to lock the fully constructed instance of corresponding to the this - /// , for use in any thread.
- /// Modification of the font will exhibit undefined behavior if some other thread also uses the font. - ///
- /// The error message, if any. - /// - /// An instance of that must be disposed after use on success; - /// null with populated on failure. - /// - ILockedImFont? TryLock(out string? errorMessage); - /// /// Locks the fully constructed instance of corresponding to the this /// , for use in any thread.
@@ -105,11 +92,4 @@ public interface IFontHandle : IDisposable ///
/// A task containing this . Task WaitAsync(); - - /// - /// Waits for to become true. - /// - /// The cancellation token. - /// A task containing this . - Task WaitAsync(CancellationToken cancellationToken); } diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontHandle.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontHandle.cs index 98a823deb..1fdaf4596 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontHandle.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontHandle.cs @@ -238,17 +238,12 @@ internal abstract class FontHandle : IFontHandle } /// - public Task WaitAsync() => this.WaitAsync(CancellationToken.None); - - /// - public Task WaitAsync(CancellationToken cancellationToken) + public Task WaitAsync() { if (this.Available) return Task.FromResult(this); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - cancellationToken.Register(() => tcs.TrySetCanceled()); - this.ImFontChanged += OnImFontChanged; this.Disposed += OnDisposed; if (this.Available) diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.FromSeString.cs b/Dalamud/Interface/Textures/Internal/TextureManager.FromSeString.cs deleted file mode 100644 index 3e90ae3ea..000000000 --- a/Dalamud/Interface/Textures/Internal/TextureManager.FromSeString.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Dalamud.Interface.ImGuiSeStringRenderer; -using Dalamud.Interface.ImGuiSeStringRenderer.Internal; -using Dalamud.Interface.Textures.TextureWraps; -using Dalamud.Utility; - -namespace Dalamud.Interface.Textures.Internal; - -/// Service responsible for loading and disposing ImGui texture wraps. -internal sealed partial class TextureManager -{ - [ServiceManager.ServiceDependency] - private readonly SeStringRenderer seStringRenderer = Service.Get(); - - /// - public IDalamudTextureWrap CreateTextureFromSeString( - ReadOnlySpan text, - scoped in SeStringDrawParams drawParams = default, - string? debugName = null) - { - ThreadSafety.AssertMainThread(); - using var dd = this.seStringRenderer.CreateDrawData(text, drawParams); - var texture = this.CreateDrawListTexture(debugName ?? nameof(this.CreateTextureFromSeString)); - try - { - texture.Size = dd.Data.DisplaySize; - texture.Draw(dd.DataPtr); - return texture; - } - catch - { - texture.Dispose(); - throw; - } - } -} diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.cs b/Dalamud/Interface/Textures/Internal/TextureManager.cs index d0f0d8c07..059c716ce 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using Dalamud.Configuration.Internal; using Dalamud.Data; using Dalamud.Game; -using Dalamud.Interface.ImGuiSeStringRenderer.Internal; using Dalamud.Interface.Internal; using Dalamud.Interface.Textures.Internal.SharedImmediateTextures; using Dalamud.Interface.Textures.TextureWraps; @@ -249,7 +248,7 @@ internal sealed partial class TextureManager usage = D3D11_USAGE.D3D11_USAGE_DYNAMIC; else usage = D3D11_USAGE.D3D11_USAGE_DEFAULT; - + using var texture = this.device.CreateTexture2D( new() { diff --git a/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs b/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs index ac6de7dd7..9b0fa0943 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManagerPluginScoped.cs @@ -6,7 +6,6 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Dalamud.Interface.ImGuiSeStringRenderer; using Dalamud.Interface.Internal; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.IoC; @@ -284,18 +283,6 @@ internal sealed class TextureManagerPluginScoped return textureWrap; } - /// - public IDalamudTextureWrap CreateTextureFromSeString( - ReadOnlySpan text, - scoped in SeStringDrawParams drawParams = default, - string? debugName = null) - { - var manager = this.ManagerOrThrow; - var textureWrap = manager.CreateTextureFromSeString(text, drawParams, debugName); - manager.Blame(textureWrap, this.plugin); - return textureWrap; - } - /// public IEnumerable GetSupportedImageDecoderInfos() => this.ManagerOrThrow.Wic.GetSupportedDecoderInfos(); diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 1ea0d9f2f..e38537018 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Diagnostics; -using System.Threading; using System.Threading.Tasks; using Dalamud.Bindings.ImGui; @@ -692,14 +691,13 @@ public sealed class UiBuilder : IDisposable, IUiBuilder FontAtlasAutoRebuildMode autoRebuildMode, bool isGlobalScaled = true, string? debugName = null) => - this.scopedFinalizer.Add( - Service - .Get() - .CreateFontAtlas( - this.namespaceName + ":" + (debugName ?? "custom"), - autoRebuildMode, - isGlobalScaled, - this.plugin)); + this.scopedFinalizer.Add(Service + .Get() + .CreateFontAtlas( + this.namespaceName + ":" + (debugName ?? "custom"), + autoRebuildMode, + isGlobalScaled, + this.plugin)); /// /// Unregister the UiBuilder. Do not call this in plugin code. @@ -870,15 +868,6 @@ public sealed class UiBuilder : IDisposable, IUiBuilder // Note: do not dispose w; we do not own it } - public ILockedImFont? TryLock(out string? errorMessage) - { - if (this.wrapped is { } w) - return w.TryLock(out errorMessage); - - errorMessage = nameof(ObjectDisposedException); - return null; - } - public ILockedImFont Lock() => this.wrapped?.Lock() ?? throw new ObjectDisposedException(nameof(FontHandleWrapper)); @@ -887,13 +876,7 @@ public sealed class UiBuilder : IDisposable, IUiBuilder public void Pop() => this.WrappedNotDisposed.Pop(); public Task WaitAsync() => - this.wrapped?.WaitAsync().ContinueWith(_ => (IFontHandle)this) - ?? Task.FromException(new ObjectDisposedException(nameof(FontHandleWrapper))); - - public Task WaitAsync(CancellationToken cancellationToken) => - this.wrapped?.WaitAsync(cancellationToken) - .ContinueWith(_ => (IFontHandle)this, cancellationToken) - ?? Task.FromException(new ObjectDisposedException(nameof(FontHandleWrapper))); + this.WrappedNotDisposed.WaitAsync().ContinueWith(_ => (IFontHandle)this); public override string ToString() => $"{nameof(FontHandleWrapper)}({this.wrapped?.ToString() ?? "disposed"})"; diff --git a/Dalamud/Interface/Utility/BufferBackedImDrawData.cs b/Dalamud/Interface/Utility/BufferBackedImDrawData.cs deleted file mode 100644 index e6128992a..000000000 --- a/Dalamud/Interface/Utility/BufferBackedImDrawData.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -using Dalamud.Bindings.ImGui; - -namespace Dalamud.Interface.Utility; - -/// Wrapper aroundx containing one . -public unsafe struct BufferBackedImDrawData : IDisposable -{ - private nint buffer; - - /// Initializes a new instance of the struct. - /// Address of buffer to use. - private BufferBackedImDrawData(nint buffer) => this.buffer = buffer; - - /// Gets the stored in this buffer. - public readonly ref ImDrawData Data => ref ((DataStruct*)this.buffer)->Data; - - /// Gets the stored in this buffer. - public readonly ImDrawDataPtr DataPtr => new((ImDrawData*)Unsafe.AsPointer(ref this.Data)); - - /// Gets the stored in this buffer. - public readonly ref ImDrawList List => ref ((DataStruct*)this.buffer)->List; - - /// Gets the stored in this buffer. - public readonly ImDrawListPtr ListPtr => new((ImDrawList*)Unsafe.AsPointer(ref this.List)); - - /// Creates a new instance of . - /// A new instance of . - public static BufferBackedImDrawData Create() - { - if (ImGui.GetCurrentContext().IsNull || ImGui.GetIO().FontDefault.Handle is null) - throw new("ImGui is not ready"); - - var res = new BufferBackedImDrawData(Marshal.AllocHGlobal(sizeof(DataStruct))); - var ds = (DataStruct*)res.buffer; - *ds = default; - - var atlas = ImGui.GetIO().Fonts; - ds->SharedData = *ImGui.GetDrawListSharedData().Handle; - ds->SharedData.TexIdCommon = atlas.Textures[atlas.TextureIndexCommon].TexID; - ds->SharedData.TexUvWhitePixel = atlas.TexUvWhitePixel; - ds->SharedData.TexUvLines = (Vector4*)Unsafe.AsPointer(ref atlas.TexUvLines[0]); - ds->SharedData.Font = ImGui.GetIO().FontDefault; - ds->SharedData.FontSize = ds->SharedData.Font->FontSize; - ds->SharedData.ClipRectFullscreen = new( - float.NegativeInfinity, - float.NegativeInfinity, - float.PositiveInfinity, - float.PositiveInfinity); - - ds->List.Data = &ds->SharedData; - ds->ListPtr = &ds->List; - ds->Data.CmdLists = &ds->ListPtr; - ds->Data.CmdListsCount = 1; - ds->Data.FramebufferScale = Vector2.One; - - res.ListPtr._ResetForNewFrame(); - res.ListPtr.PushClipRectFullScreen(); - res.ListPtr.PushTextureID(new(atlas.TextureIndexCommon)); - return res; - } - - /// Updates the statistics information stored in from . - public readonly void UpdateDrawDataStatistics() - { - this.Data.TotalIdxCount = this.List.IdxBuffer.Size; - this.Data.TotalVtxCount = this.List.VtxBuffer.Size; - } - - /// - public void Dispose() - { - if (this.buffer != 0) - { - this.ListPtr._ClearFreeMemory(); - Marshal.FreeHGlobal(this.buffer); - this.buffer = 0; - } - } - - [StructLayout(LayoutKind.Sequential)] - private struct DataStruct - { - public ImDrawData Data; - public ImDrawList* ListPtr; - public ImDrawList List; - public ImDrawListSharedData SharedData; - } -} diff --git a/Dalamud/Interface/Utility/ImGuiHelpers.cs b/Dalamud/Interface/Utility/ImGuiHelpers.cs index ee2840d3d..87f258250 100644 --- a/Dalamud/Interface/Utility/ImGuiHelpers.cs +++ b/Dalamud/Interface/Utility/ImGuiHelpers.cs @@ -575,15 +575,6 @@ public static partial class ImGuiHelpers public static unsafe ImFontPtr OrElse(this ImFontPtr self, ImFontPtr other) => self.IsNull ? other : self; - /// Creates a draw data that will draw the given SeString onto it. - /// SeString to render. - /// Initial rendering style. - /// A new self-contained draw data. - internal static BufferBackedImDrawData CreateDrawData( - ReadOnlySpan sss, - scoped in SeStringDrawParams style = default) => - Service.Get().CreateDrawData(sss, style); - /// /// Mark 4K page as used, after adding a codepoint to a font. /// diff --git a/Dalamud/Interface/Utility/Internal/DevTextureSaveMenu.cs b/Dalamud/Interface/Utility/Internal/DevTextureSaveMenu.cs index 4a0137c88..86435e8c1 100644 --- a/Dalamud/Interface/Utility/Internal/DevTextureSaveMenu.cs +++ b/Dalamud/Interface/Utility/Internal/DevTextureSaveMenu.cs @@ -6,12 +6,10 @@ using System.Text; using System.Threading.Tasks; using Dalamud.Bindings.ImGui; -using Dalamud.Game; using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification.Internal; using Dalamud.Interface.Internal; -using Dalamud.Interface.Internal.Windows.Data.Widgets; using Dalamud.Interface.Textures.Internal; using Dalamud.Interface.Textures.TextureWraps; using Serilog; @@ -35,14 +33,6 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService this.interfaceManager.Draw += this.InterfaceManagerOnDraw; } - private enum ContextMenuActionType - { - None, - SaveAsFile, - CopyToClipboard, - SendToTexWidget, - } - /// void IInternalDisposableService.DisposeService() => this.interfaceManager.Draw -= this.InterfaceManagerOnDraw; @@ -76,16 +66,15 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService var textureManager = await Service.GetAsync(); var popupName = $"{nameof(this.ShowTextureSaveMenuAsync)}_{textureWrap.Handle.Handle:X}"; - ContextMenuActionType action; BitmapCodecInfo? encoder; { var first = true; var encoders = textureManager.Wic.GetSupportedEncoderInfos().ToList(); - var tcs = new TaskCompletionSource<(ContextMenuActionType Action, BitmapCodecInfo? Codec)>( + var tcs = new TaskCompletionSource( TaskCreationOptions.RunContinuationsAsynchronously); Service.Get().Draw += DrawChoices; - (action, encoder) = await tcs.Task; + encoder = await tcs.Task; [SuppressMessage("ReSharper", "AccessToDisposedClosure", Justification = "This shall not escape")] void DrawChoices() @@ -109,20 +98,13 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService } if (ImGui.Selectable("Copy"u8)) - tcs.TrySetResult((ContextMenuActionType.CopyToClipboard, null)); - if (ImGui.Selectable("Send to TexWidget"u8)) - tcs.TrySetResult((ContextMenuActionType.SendToTexWidget, null)); - - ImGui.Separator(); - + tcs.TrySetResult(null); foreach (var encoder2 in encoders) { if (ImGui.Selectable(encoder2.Name)) - tcs.TrySetResult((ContextMenuActionType.SaveAsFile, encoder2)); + tcs.TrySetResult(encoder2); } - ImGui.Separator(); - const float previewImageWidth = 320; var size = textureWrap.Size; if (size.X > previewImageWidth) @@ -138,68 +120,50 @@ internal sealed class DevTextureSaveMenu : IInternalDisposableService } } - switch (action) + if (encoder is null) { - case ContextMenuActionType.CopyToClipboard: - isCopy = true; - await textureManager.CopyToClipboardAsync(textureWrap, name, true); - break; + isCopy = true; + await textureManager.CopyToClipboardAsync(textureWrap, name, true); + } + else + { + var props = new Dictionary(); + if (encoder.ContainerGuid == GUID.GUID_ContainerFormatTiff) + props["CompressionQuality"] = 1.0f; + else if (encoder.ContainerGuid == GUID.GUID_ContainerFormatJpeg || + encoder.ContainerGuid == GUID.GUID_ContainerFormatHeif || + encoder.ContainerGuid == GUID.GUID_ContainerFormatWmp) + props["ImageQuality"] = 1.0f; - case ContextMenuActionType.SendToTexWidget: - { - var framework = await Service.GetAsync(); - var dalamudInterface = await Service.GetAsync(); - await framework.RunOnFrameworkThread( - () => - { - var texWidget = dalamudInterface.GetDataWindowWidget(); - dalamudInterface.SetDataWindowWidget(texWidget); - texWidget.AddTexture(Task.FromResult(textureWrap.CreateWrapSharingLowLevelResource())); - }); - break; - } - - case ContextMenuActionType.SaveAsFile when encoder is not null: - { - var props = new Dictionary(); - if (encoder.ContainerGuid == GUID.GUID_ContainerFormatTiff) - props["CompressionQuality"] = 1.0f; - else if (encoder.ContainerGuid == GUID.GUID_ContainerFormatJpeg || - encoder.ContainerGuid == GUID.GUID_ContainerFormatHeif || - encoder.ContainerGuid == GUID.GUID_ContainerFormatWmp) - props["ImageQuality"] = 1.0f; - - var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - this.fileDialogManager.SaveFileDialog( - "Save texture...", - $"{encoder.Name.Replace(',', '.')}{{{string.Join(',', encoder.Extensions)}}}", - name + encoder.Extensions.First(), - encoder.Extensions.First(), - (ok, path2) => - { - if (!ok) - tcs.SetCanceled(); - else - tcs.SetResult(path2); - }); - var path = await tcs.Task.ConfigureAwait(false); - - await textureManager.SaveToFileAsync(textureWrap, encoder.ContainerGuid, path, props: props); - - var notif = Service.Get().AddNotification( - new() - { - Content = $"File saved to: {path}", - Title = initiatorName, - Type = NotificationType.Success, - }); - notif.Click += n => + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + this.fileDialogManager.SaveFileDialog( + "Save texture...", + $"{encoder.Name.Replace(',', '.')}{{{string.Join(',', encoder.Extensions)}}}", + name + encoder.Extensions.First(), + encoder.Extensions.First(), + (ok, path2) => { - Process.Start(new ProcessStartInfo(path) { UseShellExecute = true }); - n.Notification.DismissNow(); - }; - break; - } + if (!ok) + tcs.SetCanceled(); + else + tcs.SetResult(path2); + }); + var path = await tcs.Task.ConfigureAwait(false); + + await textureManager.SaveToFileAsync(textureWrap, encoder.ContainerGuid, path, props: props); + + var notif = Service.Get().AddNotification( + new() + { + Content = $"File saved to: {path}", + Title = initiatorName, + Type = NotificationType.Success, + }); + notif.Click += n => + { + Process.Start(new ProcessStartInfo(path) { UseShellExecute = true }); + n.Notification.DismissNow(); + }; } } catch (Exception e) diff --git a/Dalamud/Interface/Windowing/Window.cs b/Dalamud/Interface/Windowing/Window.cs index b0786fbb5..e90e38119 100644 --- a/Dalamud/Interface/Windowing/Window.cs +++ b/Dalamud/Interface/Windowing/Window.cs @@ -1,6 +1,9 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Numerics; +using System.Runtime.InteropServices; using System.Threading.Tasks; using CheapLoc; @@ -16,13 +19,10 @@ using Dalamud.Interface.Utility.Internal; using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing.Persistence; using Dalamud.Logging.Internal; +using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.UI; -using TerraFX.Interop.Windows; - -using static TerraFX.Interop.Windows.Windows; - namespace Dalamud.Interface.Windowing; /// @@ -31,15 +31,11 @@ namespace Dalamud.Interface.Windowing; public abstract class Window { private const float FadeInOutTime = 0.072f; - private const string AdditionsPopupName = "WindowSystemContextActions"; private static readonly ModuleLog Log = new("WindowSystem"); private static bool wasEscPressedLastFrame = false; - private readonly TitleBarButton additionsButton; - private readonly List allButtons = []; - private bool internalLastIsOpen = false; private bool internalIsOpen = false; private bool internalIsPinned = false; @@ -76,20 +72,6 @@ public abstract class Window this.WindowName = name; this.Flags = flags; 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, - }; } /// @@ -500,12 +482,14 @@ public abstract class Window ImGuiP.GetCurrentWindow().InheritNoInputs = this.internalIsClickthrough; } - if (ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID) + // Not supported yet on non-main viewports + if ((this.internalIsPinned || this.internalIsClickthrough || this.internalAlpha.HasValue) && + ImGui.GetWindowViewport().ID != ImGui.GetMainViewport().ID) { - if ((flags & ImGuiWindowFlags.NoInputs) == ImGuiWindowFlags.NoInputs) - ImGui.GetWindowViewport().Flags |= ImGuiViewportFlags.NoInputs; - else - ImGui.GetWindowViewport().Flags &= ~ImGuiViewportFlags.NoInputs; + this.internalAlpha = null; + this.internalIsPinned = false; + this.internalIsClickthrough = false; + this.presetDirty = true; } if (this.hasError) @@ -529,6 +513,7 @@ public abstract class Window } } + const string additionsPopupName = "WindowSystemContextActions"; var flagsApplicableForTitleBarIcons = !flags.HasFlag(ImGuiWindowFlags.NoDecoration) && !flags.HasFlag(ImGuiWindowFlags.NoTitleBar); var showAdditions = (this.AllowPinning || this.AllowClickthrough) && @@ -539,8 +524,13 @@ public abstract class Window { 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) ImGui.BeginDisabled(); @@ -588,11 +578,21 @@ public abstract class Window this.presetDirty = true; } - ImGui.TextColored( - ImGuiColors.DalamudGrey, - Loc.Localize( - "WindowSystemContextActionClickthroughDisclaimer", - "Open this menu again by clicking the three dashes to disable clickthrough.")); + if (isAvailable) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, + Loc.Localize("WindowSystemContextActionClickthroughDisclaimer", + "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"))) printWindow = true; @@ -603,15 +603,34 @@ public abstract class Window ImGui.PopStyleVar(); } - if (flagsApplicableForTitleBarIcons) + unsafe { - this.allButtons.Clear(); - this.allButtons.EnsureCapacity(this.TitleBarButtons.Count + 1); - this.allButtons.AddRange(this.TitleBarButtons); - if (showAdditions) - this.allButtons.Add(this.additionsButton); - this.allButtons.Sort(static (a, b) => b.Priority - a.Priority); - this.DrawTitleBarButtons(); + 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) + { + this.DrawTitleBarButtons(window, flags, outRect, + showAdditions + ? this.TitleBarButtons.Append(additionsButton) + : this.TitleBarButtons); + } } if (wasFocused) @@ -778,11 +797,8 @@ public abstract class Window } } - private unsafe void DrawTitleBarButtons() + private unsafe void DrawTitleBarButtons(ImGuiWindowPtr window, ImGuiWindowFlags flags, ImRect titleBarRect, IEnumerable buttons) { - var window = ImGuiP.GetCurrentWindow(); - var flags = window.Flags; - var titleBarRect = window.TitleBarRect(); ImGui.PushClipRect(ImGui.GetWindowPos(), ImGui.GetWindowPos() + ImGui.GetWindowSize(), false); var style = ImGui.GetStyle(); @@ -817,22 +833,26 @@ public abstract class Window var max = pos + new Vector2(fontSize, fontSize); ImRect bb = new(pos, max); var isClipped = !ImGuiP.ItemAdd(bb, id, null, 0); - bool hovered, held, pressed; + bool hovered, held; + var pressed = false; if (this.internalIsClickthrough) { + hovered = false; + held = false; + // ButtonBehavior does not function if the window is clickthrough, so we have to do it ourselves - var pad = ImGui.GetStyle().TouchExtraPadding; - var rect = new ImRect(pos - pad, max + pad); - hovered = rect.Contains(ImGui.GetMousePos()); + if (ImGui.IsMouseHoveringRect(pos, max)) + { + hovered = true; - // 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 - pressed = held = hovered && (GetKeyState(VK.VK_LBUTTON) & 0x8000) != 0; + // 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) + { + held = true; + pressed = true; + } + } } else { @@ -861,7 +881,7 @@ public abstract class Window return pressed; } - foreach (var button in this.allButtons) + foreach (var button in buttons.OrderBy(x => x.Priority)) { if (this.internalIsClickthrough && !button.AvailableClickthrough) return; @@ -1015,7 +1035,7 @@ public abstract class Window /// /// Gets or sets an action that is called when the button is clicked. /// - public Action? Click { get; set; } + public Action Click { get; set; } /// /// Gets or sets the priority the button shall be shown in. diff --git a/Dalamud/Plugin/Services/ITextureProvider.cs b/Dalamud/Plugin/Services/ITextureProvider.cs index dc0522aa8..7cd1b7c86 100644 --- a/Dalamud/Plugin/Services/ITextureProvider.cs +++ b/Dalamud/Plugin/Services/ITextureProvider.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using Dalamud.Bindings.ImGui; -using Dalamud.Interface.ImGuiSeStringRenderer; using Dalamud.Interface.Internal.Windows.Data.Widgets; using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.TextureWraps; @@ -187,17 +186,6 @@ public interface ITextureProvider : IDalamudService string? debugName = null, CancellationToken cancellationToken = default); - /// Creates a texture by drawing a SeString onto it. - /// SeString to render. - /// Parameters for drawing. - /// Name for debug display purposes. - /// The new texture. - /// Can be only be used from the main thread. - public IDalamudTextureWrap CreateTextureFromSeString( - ReadOnlySpan text, - scoped in SeStringDrawParams drawParams = default, - string? debugName = null); - /// Gets the supported bitmap decoders. /// The supported bitmap decoders. /// diff --git a/imgui/Dalamud.Bindings.ImGui/ImVector.cs b/imgui/Dalamud.Bindings.ImGui/ImVector.cs index 67e450193..9a10c1d6b 100644 --- a/imgui/Dalamud.Bindings.ImGui/ImVector.cs +++ b/imgui/Dalamud.Bindings.ImGui/ImVector.cs @@ -1,12 +1,7 @@ -using System.Collections; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; namespace Dalamud.Bindings.ImGui; -/// -/// A structure representing a dynamic array for unmanaged types. -/// public unsafe struct ImVector { public readonly int Size; @@ -20,23 +15,23 @@ public unsafe struct ImVector Data = data; } - public readonly ref T Ref(int index) => ref Unsafe.AsRef((byte*)this.Data + (index * Unsafe.SizeOf())); + public ref T Ref(int index) + { + return ref Unsafe.AsRef((byte*)Data + index * Unsafe.SizeOf()); + } - public readonly nint Address(int index) => (nint)((byte*)this.Data + (index * Unsafe.SizeOf())); + public IntPtr Address(int index) + { + return (IntPtr)((byte*)Data + index * Unsafe.SizeOf()); + } } /// /// A structure representing a dynamic array for unmanaged types. /// /// The type of elements in the vector, must be unmanaged. -[StructLayout(LayoutKind.Sequential)] -public unsafe struct ImVector : IEnumerable - where T : unmanaged +public unsafe struct ImVector 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. /// @@ -50,6 +45,11 @@ public unsafe struct ImVector : IEnumerable this.data = data; } + private int size; + private int capacity; + private unsafe T* data; + + /// /// Gets or sets the element at the specified index. /// @@ -58,72 +58,80 @@ public unsafe struct ImVector : IEnumerable /// Thrown when the index is out of range. public T this[int index] { - readonly get + get { - if (index < 0 || index >= this.size) + if (index < 0 || index >= size) + { throw new IndexOutOfRangeException(); - return this.data[index]; + } + return data[index]; } set { - if (index < 0 || index >= this.size) + if (index < 0 || index >= size) + { throw new IndexOutOfRangeException(); - this.data[index] = value; + } + data[index] = value; } } /// /// Gets a pointer to the first element of the vector. /// - public readonly T* Data => this.data; + public readonly T* Data => data; /// /// Gets a pointer to the first element of the vector. /// - public readonly T* Front => this.data; + public readonly T* Front => data; /// /// Gets a pointer to the last element of the vector. /// - public readonly T* Back => this.size > 0 ? this.data + this.size - 1 : null; + public readonly T* Back => size > 0 ? data + size - 1 : null; /// /// Gets or sets the capacity of the vector. /// public int Capacity { - readonly get => this.capacity; + readonly get => capacity; set { - ArgumentOutOfRangeException.ThrowIfLessThan(value, this.size, nameof(Capacity)); - if (this.capacity == value) - return; - - if (this.data == null) + if (capacity == value) { - this.data = (T*)ImGui.MemAlloc((nuint)(value * sizeof(T))); + return; + } + + if (data == null) + { + data = (T*)ImGui.MemAlloc((nuint)(value * sizeof(T))); } else { - 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; + 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; } - this.capacity = value; + capacity = value; // Clear the rest of the data - new Span(this.data + this.size, this.capacity - this.size).Clear(); + for (int i = size; i < capacity; i++) + { + data[i] = default; + } } } /// /// Gets the number of elements in the vector. /// - public readonly int Size => this.size; + public readonly int Size => size; /// /// Grows the capacity of the vector to at least the specified value. @@ -131,8 +139,10 @@ public unsafe struct ImVector : IEnumerable /// The new capacity. public void Grow(int newCapacity) { - var newCapacity2 = this.capacity > 0 ? this.capacity + (this.capacity / 2) : 8; - this.Capacity = newCapacity2 > newCapacity ? newCapacity2 : newCapacity; + if (newCapacity > capacity) + { + Capacity = newCapacity * 2; + } } /// @@ -141,8 +151,10 @@ public unsafe struct ImVector : IEnumerable /// The minimum capacity required. public void EnsureCapacity(int size) { - if (size > this.capacity) + if (size > capacity) + { Grow(size); + } } /// @@ -152,46 +164,25 @@ public unsafe struct ImVector : IEnumerable public void Resize(int newSize) { EnsureCapacity(newSize); - this.size = newSize; + size = newSize; } /// /// Clears all elements from the vector. /// - public void Clear() => this.size = 0; + public void Clear() + { + size = 0; + } /// /// Adds an element to the end of the vector. /// /// The value to add. - [OverloadResolutionPriority(1)] public void PushBack(T value) { - this.EnsureCapacity(this.size + 1); - this.data[this.size++] = value; - } - - /// - /// Adds an element to the end of the vector. - /// - /// The value to add. - [OverloadResolutionPriority(2)] - public void PushBack(in T 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); + EnsureCapacity(size + 1); + data[size++] = value; } /// @@ -199,126 +190,48 @@ public unsafe struct ImVector : IEnumerable /// public void PopBack() { - if (this.size > 0) + if (size > 0) { - this.size--; + 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 (this.data != null) + if (data != null) { - ImGui.MemFree(this.data); - this.data = null; - this.size = 0; - this.capacity = 0; + ImGui.MemFree(data); + data = null; + size = 0; + capacity = 0; } } - public readonly ref T Ref(int index) + public ref T Ref(int index) { - return ref Unsafe.AsRef((byte*)Data + (index * Unsafe.SizeOf())); + return ref Unsafe.AsRef((byte*)Data + index * Unsafe.SizeOf()); } - public readonly ref TCast Ref(int index) + public ref TCast Ref(int index) { - return ref Unsafe.AsRef((byte*)Data + (index * Unsafe.SizeOf())); + return ref Unsafe.AsRef((byte*)Data + index * Unsafe.SizeOf()); } - public readonly void* Address(int index) + public void* Address(int index) { - return (byte*)Data + (index * Unsafe.SizeOf()); + return (byte*)Data + index * Unsafe.SizeOf(); } - public readonly void* Address(int index) + public void* Address(int index) { - return (byte*)Data + (index * Unsafe.SizeOf()); + return (byte*)Data + index * Unsafe.SizeOf(); } - public readonly ImVector* ToUntyped() + public ImVector* ToUntyped() { - 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(); + return (ImVector*)Unsafe.AsPointer(ref this); } } diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index e5dedba42..e5f586630 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit e5dedba42a3fea8f050ea54ac583a5874bf51c6f +Subproject commit e5f586630ef06fa48d5dc0d8c0fa679323093c77 diff --git a/lib/cimgui b/lib/cimgui index bc3272967..27c8565f6 160000 --- a/lib/cimgui +++ b/lib/cimgui @@ -1 +1 @@ -Subproject commit bc327296758d57d3bdc963cb6ce71dd5b0c7e54c +Subproject commit 27c8565f631b004c3266373890e41ecc627f775b