mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-10 10:04:37 +01:00
Update Addon Inspector
This updates the Addon Inspector with lots of new features and functionality. - Features from Caraxi's fork of UiDebug have been incorporated, such as the Element Selector UI and address search. - Any addon or node can now pop out into its own window. - Revised the visual style of node field/property information. - Color values are now visually displayed. - Any nodes or components that are referenced by fields within the addon will now show that field name in the inspector. - Added editors for nodes, allowing complete control over most of their properties. - Improved texture display for Image nodes (and Image node variant types). The active part of the texture is now highlighted, and the boundaries of other parts can be shown via mouseover. - Highlighting of node bounds onscreen is now more accurate, factoring in rotation (including when using the Element Selector). - Display of animation timelines has been revamped, showing a table of keyframes for each animation. A standalone SamplePlugin-based version is available here: https://github.com/ItsBexy/UiDebug2
This commit is contained in:
parent
0fb7585973
commit
bf8690fc60
23 changed files with 4039 additions and 3 deletions
317
Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs
Normal file
317
Dalamud/Interface/Internal/UiDebug2/Browsing/NodeTree.Image.cs
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
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;
|
||||
|
||||
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</term>presents the texture in full as a spritesheet.<br/>
|
||||
/// <term>Parts List</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;
|
||||
}
|
||||
|
||||
if (NestedTreePush($"Texture##texture{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", out _))
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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->Width, this.TexData.Texture->Height));
|
||||
|
||||
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()
|
||||
{
|
||||
ImGui.BeginTable($"partsTable##{(nint)this.TexData.Texture->D3D11ShaderResourceView:X}", 3, Borders | RowBg | Reorderable);
|
||||
ImGui.TableSetupColumn("Part ID", WidthFixed);
|
||||
ImGui.TableSetupColumn("Part Texture", WidthFixed);
|
||||
ImGui.TableSetupColumn("Coordinates", WidthFixed);
|
||||
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
var tWidth = this.TexData.Texture->Width;
|
||||
var tHeight = this.TexData.Texture->Height;
|
||||
var textureSize = new Vector2(tWidth, tHeight);
|
||||
|
||||
for (ushort i = 0; i < this.TexData.PartCount; i++)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
if (i == this.TexData.PartId)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 0.85F, 1, 1));
|
||||
}
|
||||
|
||||
ImGui.Text($"#{i.ToString().PadLeft(this.TexData.PartCount.ToString().Length, '0')}");
|
||||
|
||||
if (i == this.TexData.PartId)
|
||||
{
|
||||
ImGui.PopStyleColor(1);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue