mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-13 12:14:16 +01:00
- Further ImRaii safety in UiDebug2 - Set some mistakenly internal methods in ImGuiComponents to public - Added SpanFullWidth flag to trees in Util.ShowStruct
317 lines
11 KiB
C#
317 lines
11 KiB
C#
using System.Numerics;
|
|
using System.Runtime.InteropServices;
|
|
|
|
using Dalamud.Interface.Utility.Raii;
|
|
|
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
using ImGuiNET;
|
|
|
|
using static Dalamud.Interface.ColorHelpers;
|
|
using static Dalamud.Interface.Internal.UiDebug2.Utility.Gui;
|
|
using static Dalamud.Utility.Util;
|
|
using static FFXIVClientStructs.FFXIV.Component.GUI.TextureType;
|
|
using static ImGuiNET.ImGuiTableColumnFlags;
|
|
using static ImGuiNET.ImGuiTableFlags;
|
|
using static ImGuiNET.ImGuiTreeNodeFlags;
|
|
|
|
namespace Dalamud.Interface.Internal.UiDebug2.Browsing;
|
|
|
|
/// <summary>
|
|
/// A tree for an <see cref="AtkImageNode"/> that can be printed and browsed via ImGui.
|
|
/// </summary>
|
|
internal unsafe partial class ImageNodeTree : ResNodeTree
|
|
{
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ImageNodeTree"/> class.
|
|
/// </summary>
|
|
/// <param name="node">The node to create a tree for.</param>
|
|
/// <param name="addonTree">The tree representing the containing addon.</param>
|
|
internal ImageNodeTree(AtkResNode* node, AddonTree addonTree)
|
|
: base(node, addonTree)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the part ID that this node uses.
|
|
/// </summary>
|
|
private protected virtual uint PartId => this.ImgNode->PartId;
|
|
|
|
/// <summary>
|
|
/// Gets the parts list that this node uses.
|
|
/// </summary>
|
|
private protected virtual AtkUldPartsList* PartsList => this.ImgNode->PartsList;
|
|
|
|
/// <summary>
|
|
/// Gets or sets a summary of pertinent data about this <see cref="AtkImageNode"/>'s texture. Updated each time <see cref="DrawTextureAndParts"/> is called.
|
|
/// </summary>
|
|
private protected TextureData TexData { get; set; }
|
|
|
|
private AtkImageNode* ImgNode => (AtkImageNode*)this.Node;
|
|
|
|
/// <summary>
|
|
/// Draws the texture inside the window, in either of two styles.<br/><br/>
|
|
/// <term>Full Image (0)</term>presents the texture in full as a spritesheet.<br/>
|
|
/// <term>Parts List (1)</term>presents the individual parts as rows in a table.
|
|
/// </summary>
|
|
private protected void DrawTextureAndParts()
|
|
{
|
|
this.TexData = new TextureData(this.PartsList, this.PartId);
|
|
|
|
if (this.TexData.Texture == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var tree = ImRaii.TreeNode($"Texture##texture{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", SpanFullWidth);
|
|
|
|
if (tree.Success)
|
|
{
|
|
PrintFieldValuePairs(
|
|
("Texture Type", $"{this.TexData.TexType}"),
|
|
("Part ID", $"{this.TexData.PartId}"),
|
|
("Part Count", $"{this.TexData.PartCount}"));
|
|
|
|
if (this.TexData.Path != null)
|
|
{
|
|
PrintFieldValuePairs(("Texture Path", this.TexData.Path));
|
|
}
|
|
|
|
if (ImGui.RadioButton("Full Image##textureDisplayStyle0", TexDisplayStyle == 0))
|
|
{
|
|
TexDisplayStyle = 0;
|
|
}
|
|
|
|
ImGui.SameLine();
|
|
if (ImGui.RadioButton("Parts List##textureDisplayStyle1", TexDisplayStyle == 1))
|
|
{
|
|
TexDisplayStyle = 1;
|
|
}
|
|
|
|
ImGui.NewLine();
|
|
|
|
if (TexDisplayStyle == 1)
|
|
{
|
|
this.PrintPartsTable();
|
|
}
|
|
else
|
|
{
|
|
this.DrawFullTexture();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws an outline of a given part within the texture.
|
|
/// </summary>
|
|
/// <param name="partId">The part ID.</param>
|
|
/// <param name="cursorScreenPos">The absolute position of the cursor onscreen.</param>
|
|
/// <param name="cursorLocalPos">The relative position of the cursor within the window.</param>
|
|
/// <param name="col">The color of the outline.</param>
|
|
/// <param name="reqHover">Whether this outline requires the user to mouse over it.</param>
|
|
private protected virtual void DrawPartOutline(uint partId, Vector2 cursorScreenPos, Vector2 cursorLocalPos, Vector4 col, bool reqHover = false)
|
|
{
|
|
var part = this.TexData.PartsList->Parts[partId];
|
|
|
|
var hrFactor = this.TexData.HiRes ? 2f : 1f;
|
|
|
|
var uv = new Vector2(part.U, part.V) * hrFactor;
|
|
var wh = new Vector2(part.Width, part.Height) * hrFactor;
|
|
|
|
var partBegin = cursorScreenPos + uv;
|
|
var partEnd = partBegin + wh;
|
|
|
|
if (reqHover && !ImGui.IsMouseHoveringRect(partBegin, partEnd))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var savePos = ImGui.GetCursorPos();
|
|
|
|
ImGui.GetWindowDrawList().AddRect(partBegin, partEnd, RgbaVector4ToUint(col));
|
|
|
|
ImGui.SetCursorPos(cursorLocalPos + uv + new Vector2(0, -20));
|
|
ImGui.TextColored(col, $"[#{partId}]\t{part.U}, {part.V}\t{part.Width}x{part.Height}");
|
|
ImGui.SetCursorPos(savePos);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
private protected override void PrintNodeObject() => ShowStruct(this.ImgNode);
|
|
|
|
/// <inheritdoc/>
|
|
private protected override void PrintFieldsForNodeType(bool isEditorOpen = false)
|
|
{
|
|
PrintFieldValuePairs(
|
|
("Wrap", $"{this.ImgNode->WrapMode}"),
|
|
("Image Flags", $"0x{this.ImgNode->Flags:X}"));
|
|
this.DrawTextureAndParts();
|
|
}
|
|
|
|
private static void PrintPartCoords(float u, float v, float w, float h, bool asFloat = false, bool lineBreak = false)
|
|
{
|
|
ImGui.TextDisabled($"{u}, {v},{(lineBreak ? "\n" : " ")}{w}, {h}");
|
|
|
|
if (ImGui.IsItemHovered())
|
|
{
|
|
ImGui.SetTooltip("Click to copy as Vector2\nShift-click to copy as Vector4");
|
|
}
|
|
|
|
var suffix = asFloat ? "f" : string.Empty;
|
|
|
|
if (ImGui.IsItemClicked())
|
|
{
|
|
ImGui.SetClipboardText(
|
|
ImGui.IsKeyDown(ImGuiKey.ModShift)
|
|
? $"new Vector4({u}{suffix}, {v}{suffix}, {w}{suffix}, {h}{suffix})"
|
|
: $"new Vector2({u}{suffix}, {v}{suffix});\nnew Vector2({w}{suffix}, {h}{suffix})");
|
|
}
|
|
}
|
|
|
|
private void DrawFullTexture()
|
|
{
|
|
var cursorScreenPos = ImGui.GetCursorScreenPos();
|
|
var cursorLocalPos = ImGui.GetCursorPos();
|
|
|
|
ImGui.Image(new(this.TexData.Texture->D3D11ShaderResourceView), new(this.TexData.Texture->ActualWidth, this.TexData.Texture->ActualHeight));
|
|
|
|
for (uint p = 0; p < this.TexData.PartsList->PartCount; p++)
|
|
{
|
|
if (p == this.TexData.PartId)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
this.DrawPartOutline(p, cursorScreenPos, cursorLocalPos, new(0.6f, 0.6f, 0.6f, 1), true);
|
|
}
|
|
|
|
this.DrawPartOutline(this.TexData.PartId, cursorScreenPos, cursorLocalPos, new(0, 0.85F, 1, 1));
|
|
}
|
|
|
|
private void PrintPartsTable()
|
|
{
|
|
using var tbl = ImRaii.Table($"partsTable##{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", 3, Borders | RowBg | Reorderable);
|
|
if (tbl.Success)
|
|
{
|
|
ImGui.TableSetupColumn("Part ID", WidthFixed);
|
|
ImGui.TableSetupColumn("Part Texture", WidthFixed);
|
|
ImGui.TableSetupColumn("Coordinates", WidthFixed);
|
|
|
|
ImGui.TableHeadersRow();
|
|
|
|
var tWidth = this.TexData.Texture->ActualWidth;
|
|
var tHeight = this.TexData.Texture->ActualHeight;
|
|
var textureSize = new Vector2(tWidth, tHeight);
|
|
|
|
for (ushort i = 0; i < this.TexData.PartCount; i++)
|
|
{
|
|
ImGui.TableNextColumn();
|
|
|
|
var col = i == this.TexData.PartId ? new Vector4(0, 0.85F, 1, 1) : new(1);
|
|
ImGui.TextColored(col, $"#{i.ToString().PadLeft(this.TexData.PartCount.ToString().Length, '0')}");
|
|
|
|
ImGui.TableNextColumn();
|
|
|
|
var part = this.TexData.PartsList->Parts[i];
|
|
var hiRes = this.TexData.HiRes;
|
|
|
|
var u = hiRes ? part.U * 2f : part.U;
|
|
var v = hiRes ? part.V * 2f : part.V;
|
|
var width = hiRes ? part.Width * 2f : part.Width;
|
|
var height = hiRes ? part.Height * 2f : part.Height;
|
|
|
|
ImGui.Image(
|
|
new(this.TexData.Texture->D3D11ShaderResourceView),
|
|
new(width, height),
|
|
new Vector2(u, v) / textureSize,
|
|
new Vector2(u + width, v + height) / textureSize);
|
|
|
|
ImGui.TableNextColumn();
|
|
|
|
ImGui.TextColored(!hiRes ? new(1) : new(0.6f, 0.6f, 0.6f, 1), "Standard:\t");
|
|
ImGui.SameLine();
|
|
var cursX = ImGui.GetCursorPosX();
|
|
|
|
PrintPartCoords(u / 2f, v / 2f, width / 2f, height / 2f);
|
|
|
|
ImGui.TextColored(hiRes ? new(1) : new(0.6f, 0.6f, 0.6f, 1), "Hi-Res:\t");
|
|
ImGui.SameLine();
|
|
ImGui.SetCursorPosX(cursX);
|
|
|
|
PrintPartCoords(u, v, width, height);
|
|
|
|
ImGui.Text("UV:\t");
|
|
ImGui.SameLine();
|
|
ImGui.SetCursorPosX(cursX);
|
|
|
|
PrintPartCoords(u / tWidth, v / tWidth, (u + width) / tWidth, (v + height) / tHeight, true, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A summary of pertinent data about a node's texture.
|
|
/// </summary>
|
|
protected struct TextureData
|
|
{
|
|
/// <summary>The texture's partslist.</summary>
|
|
public AtkUldPartsList* PartsList;
|
|
|
|
/// <summary>The number of parts in the texture.</summary>
|
|
public uint PartCount;
|
|
|
|
/// <summary>The part ID the node is using.</summary>
|
|
public uint PartId;
|
|
|
|
/// <summary>The texture itself.</summary>
|
|
public Texture* Texture = null;
|
|
|
|
/// <summary>The type of texture.</summary>
|
|
public TextureType TexType = 0;
|
|
|
|
/// <summary>The texture's file path (if <see cref="TextureType.Resource"/>, otherwise this value is null).</summary>
|
|
public string? Path = null;
|
|
|
|
/// <summary>Whether this is a high-resolution texture.</summary>
|
|
public bool HiRes = false;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="TextureData"/> struct.
|
|
/// </summary>
|
|
/// <param name="partsList">The texture's parts list.</param>
|
|
/// <param name="partId">The part ID being used by the node.</param>
|
|
public TextureData(AtkUldPartsList* partsList, uint partId)
|
|
{
|
|
this.PartsList = partsList;
|
|
this.PartCount = this.PartsList->PartCount;
|
|
this.PartId = partId >= this.PartCount ? 0 : partId;
|
|
|
|
if (this.PartsList == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var asset = this.PartsList->Parts[this.PartId].UldAsset;
|
|
|
|
if (asset == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.TexType = asset->AtkTexture.TextureType;
|
|
|
|
if (this.TexType == Resource)
|
|
{
|
|
var resource = asset->AtkTexture.Resource;
|
|
this.Texture = resource->KernelTextureObject;
|
|
this.Path = Marshal.PtrToStringAnsi(new(resource->TexFileResourceHandle->ResourceHandle.FileName.BufferPtr));
|
|
}
|
|
else
|
|
{
|
|
this.Texture = this.TexType == KernelTexture ? asset->AtkTexture.KernelTexture : null;
|
|
this.Path = null;
|
|
}
|
|
|
|
this.HiRes = this.Path?.Contains("_hr1") ?? false;
|
|
}
|
|
}
|
|
}
|