TexWidget: add test/examples for new APIs

This commit is contained in:
Soreepeong 2024-02-22 03:04:42 +09:00
parent 3fe2920e92
commit eb4d9aba7e

View file

@ -1,12 +1,16 @@
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Threading.Tasks;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.Internal.SharableTextures;
using Dalamud.Interface.Utility;
using Dalamud.Plugin.Services;
using Dalamud.Storage.Assets;
using Dalamud.Utility;
using ImGuiNET;
using ImGuiScene;
using Serilog;
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
@ -15,23 +19,24 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
/// </summary>
internal class TexWidget : IDataWindowWidget
{
private readonly List<IDalamudTextureWrap> addedTextures = new();
private readonly List<TextureEntry> addedTextures = new();
private string iconId = "18";
private bool hiRes = true;
private bool hq = false;
private bool keepAlive = false;
private string inputTexPath = string.Empty;
private string inputFilePath = string.Empty;
private Vector2 inputTexUv0 = Vector2.Zero;
private Vector2 inputTexUv1 = Vector2.One;
private Vector4 inputTintCol = Vector4.One;
private Vector2 inputTexScale = Vector2.Zero;
private TextureManager textureManager = null!;
/// <inheritdoc/>
public string[]? CommandShortcuts { get; init; } = { "tex", "texture" };
/// <inheritdoc/>
public string DisplayName { get; init; } = "Tex";
public string DisplayName { get; init; } = "Tex";
/// <inheritdoc/>
public bool Ready { get; set; }
@ -39,91 +44,81 @@ internal class TexWidget : IDataWindowWidget
/// <inheritdoc/>
public void Load()
{
this.addedTextures.AggregateToDisposable().Dispose();
this.addedTextures.Clear();
this.inputTexPath = "ui/loadingimage/-nowloading_base25_hr1.tex";
this.inputFilePath = Path.Join(
Service<Dalamud>.Get().StartInfo.AssetDirectory!,
DalamudAsset.Logo.GetAttribute<DalamudAssetPathAttribute>()!.FileName);
this.Ready = true;
}
/// <inheritdoc/>
public void Draw()
{
var texManager = Service<TextureManager>.Get();
this.textureManager = Service<TextureManager>.Get();
ImGui.InputText("Icon ID", ref this.iconId, 32);
ImGui.Checkbox("HQ Item", ref this.hq);
ImGui.Checkbox("Hi-Res", ref this.hiRes);
ImGui.Checkbox("Keep alive", ref this.keepAlive);
if (ImGui.Button("Load Icon"))
if (ImGui.Button("GC"))
GC.Collect();
ImGui.PushID("loadedGameTextures");
if (ImGui.CollapsingHeader($"Loaded Game Textures: {this.textureManager.GamePathTextures.Count}###header"))
this.DrawLoadedTextures(this.textureManager.GamePathTextures);
ImGui.PopID();
ImGui.PushID("loadedFileTextures");
if (ImGui.CollapsingHeader($"Loaded File Textures: {this.textureManager.FileSystemTextures.Count}###header"))
this.DrawLoadedTextures(this.textureManager.FileSystemTextures);
ImGui.PopID();
if (ImGui.CollapsingHeader("Load Game File by Icon ID", ImGuiTreeNodeFlags.DefaultOpen))
this.DrawIconInput();
if (ImGui.CollapsingHeader("Load Game File by Path", ImGuiTreeNodeFlags.DefaultOpen))
this.DrawGamePathInput();
if (ImGui.CollapsingHeader("Load File", ImGuiTreeNodeFlags.DefaultOpen))
this.DrawFileInput();
if (ImGui.CollapsingHeader("UV"))
this.DrawUvInput();
TextureEntry? toRemove = null;
TextureEntry? toCopy = null;
foreach (var t in this.addedTextures)
{
try
ImGui.PushID(t.Id);
if (ImGui.CollapsingHeader($"Tex #{t.Id} {t}###header", ImGuiTreeNodeFlags.DefaultOpen))
{
var flags = ITextureProvider.IconFlags.None;
if (this.hq)
flags |= ITextureProvider.IconFlags.ItemHighQuality;
if (this.hiRes)
flags |= ITextureProvider.IconFlags.HiRes;
this.addedTextures.Add(texManager.GetIcon(uint.Parse(this.iconId), flags, keepAlive: this.keepAlive));
}
catch (Exception ex)
{
Log.Error(ex, "Could not load tex");
}
}
ImGui.Separator();
ImGui.InputText("Tex Path", ref this.inputTexPath, 255);
if (ImGui.Button("Load Tex"))
{
try
{
this.addedTextures.Add(texManager.GetTextureFromGame(this.inputTexPath, this.keepAlive));
}
catch (Exception ex)
{
Log.Error(ex, "Could not load tex");
}
}
if (ImGui.Button("Load File"))
{
try
{
this.addedTextures.Add(texManager.GetTextureFromFile(new FileInfo(this.inputTexPath), this.keepAlive));
}
catch (Exception ex)
{
Log.Error(ex, "Could not load tex");
}
}
ImGui.Separator();
ImGui.InputFloat2("UV0", ref this.inputTexUv0);
ImGui.InputFloat2("UV1", ref this.inputTexUv1);
ImGui.InputFloat4("Tint", ref this.inputTintCol);
ImGui.InputFloat2("Scale", ref this.inputTexScale);
ImGuiHelpers.ScaledDummy(10);
IDalamudTextureWrap? toRemove = null;
for (var i = 0; i < this.addedTextures.Count; i++)
{
if (ImGui.CollapsingHeader($"Tex #{i}"))
{
var tex = this.addedTextures[i];
var scale = new Vector2(tex.Width, tex.Height);
if (this.inputTexScale != Vector2.Zero)
scale = this.inputTexScale;
ImGui.Image(tex.ImGuiHandle, scale, this.inputTexUv0, this.inputTexUv1, this.inputTintCol);
if (ImGui.Button($"X##{i}"))
toRemove = tex;
if (ImGui.Button("X"))
toRemove = t;
ImGui.SameLine();
if (ImGui.Button($"Clone##{i}"))
this.addedTextures.Add(tex.CreateWrapSharingLowLevelResource());
if (ImGui.Button("Copy"))
toCopy = t;
try
{
if (t.GetTexture(this.textureManager) is { } tex)
{
var scale = new Vector2(tex.Width, tex.Height);
if (this.inputTexScale != Vector2.Zero)
scale = this.inputTexScale;
ImGui.Image(tex.ImGuiHandle, scale, this.inputTexUv0, this.inputTexUv1, this.inputTintCol);
}
else
{
ImGui.TextUnformatted(t.DescribeError());
}
}
catch (Exception e)
{
ImGui.TextUnformatted(e.ToString());
}
}
ImGui.PopID();
}
if (toRemove != null)
@ -131,5 +126,291 @@ internal class TexWidget : IDataWindowWidget
toRemove.Dispose();
this.addedTextures.Remove(toRemove);
}
if (toCopy != null)
{
this.addedTextures.Add(toCopy.CreateFromSharedLowLevelResource(this.textureManager));
}
}
private unsafe void DrawLoadedTextures(IReadOnlyDictionary<string, SharableTexture> textures)
{
if (!ImGui.BeginTable("##table", 6))
return;
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableSetupColumn("ID", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("000000").X);
ImGui.TableSetupColumn("Preview", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Preview_").X);
ImGui.TableSetupColumn("Source", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("RefCount", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("RefCount__").X);
ImGui.TableSetupColumn("SelfRef", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("00.000___").X);
ImGui.TableSetupColumn("CanRevive", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("CanRevive__").X);
ImGui.TableHeadersRow();
var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
clipper.Begin(textures.Count);
using (var enu = textures.GetEnumerator())
{
var row = 0;
while (clipper.Step())
{
var valid = true;
for (; row < clipper.DisplayStart && valid; row++)
valid = enu.MoveNext();
if (!valid)
break;
for (; row < clipper.DisplayEnd; row++)
{
valid = enu.MoveNext();
if (!valid)
break;
var (key, texture) = enu.Current;
ImGui.TableNextRow();
ImGui.TableNextColumn();
this.TextRightAlign($"{texture.InstanceIdForDebug:n0}");
ImGui.TableNextColumn();
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero);
ImGui.Button("Hover");
ImGui.PopStyleColor(3);
ImGui.PopStyleVar();
if (ImGui.IsItemHovered() && texture.GetImmediate() is { } immediate)
{
ImGui.BeginTooltip();
ImGui.Image(immediate.ImGuiHandle, immediate.Size);
ImGui.EndTooltip();
}
ImGui.TableNextColumn();
this.TextCopiable(key);
ImGui.TableNextColumn();
this.TextRightAlign($"{texture.RefCountForDebug:n0}");
ImGui.TableNextColumn();
var remain = texture.SelfReferenceExpiresInForDebug;
this.TextRightAlign(remain <= 0 ? "-" : $"{remain:00.000}");
ImGui.TableNextColumn();
ImGui.TextUnformatted(texture.RevivalPossibility?.TryGetTarget(out _) is true ? "Yes" : "No");
}
if (!valid)
break;
}
}
clipper.Destroy();
ImGui.EndTable();
ImGuiHelpers.ScaledDummy(10);
}
private void DrawIconInput()
{
ImGui.InputText("Icon ID", ref this.iconId, 32);
ImGui.Checkbox("HQ Item", ref this.hq);
ImGui.Checkbox("Hi-Res", ref this.hiRes);
#pragma warning disable CS0618 // Type or member is obsolete
if (ImGui.Button("Load Icon (API9)"))
{
var flags = ITextureProvider.IconFlags.None;
if (this.hq)
flags |= ITextureProvider.IconFlags.ItemHighQuality;
if (this.hiRes)
flags |= ITextureProvider.IconFlags.HiRes;
this.addedTextures.Add(new(Api9: this.textureManager.GetIcon(uint.Parse(this.iconId), flags)));
}
#pragma warning restore CS0618 // Type or member is obsolete
ImGui.SameLine();
if (ImGui.Button("Load Icon (Async)"))
{
this.addedTextures.Add(
new(
Api10: this.textureManager.GetFromGameIconAsync(
new(uint.Parse(this.iconId), this.hq, this.hiRes))));
}
ImGui.SameLine();
if (ImGui.Button("Load Icon (Immediate)"))
this.addedTextures.Add(new(Api10ImmGameIcon: new(uint.Parse(this.iconId), this.hq, this.hiRes)));
ImGuiHelpers.ScaledDummy(10);
}
private void DrawGamePathInput()
{
ImGui.InputText("Tex Path", ref this.inputTexPath, 255);
#pragma warning disable CS0618 // Type or member is obsolete
if (ImGui.Button("Load Tex (API9)"))
this.addedTextures.Add(new(Api9: this.textureManager.GetTextureFromGame(this.inputTexPath)));
#pragma warning restore CS0618 // Type or member is obsolete
ImGui.SameLine();
if (ImGui.Button("Load Tex (Async)"))
this.addedTextures.Add(new(Api10: this.textureManager.GetFromGameAsync(this.inputTexPath)));
ImGui.SameLine();
if (ImGui.Button("Load Tex (Immediate)"))
this.addedTextures.Add(new(Api10ImmGamePath: this.inputTexPath));
ImGuiHelpers.ScaledDummy(10);
}
private void DrawFileInput()
{
ImGui.InputText("File Path", ref this.inputFilePath, 255);
#pragma warning disable CS0618 // Type or member is obsolete
if (ImGui.Button("Load File (API9)"))
this.addedTextures.Add(new(Api9: this.textureManager.GetTextureFromFile(new(this.inputFilePath))));
#pragma warning restore CS0618 // Type or member is obsolete
ImGui.SameLine();
if (ImGui.Button("Load File (Async)"))
this.addedTextures.Add(new(Api10: this.textureManager.GetFromFileAsync(this.inputFilePath)));
ImGui.SameLine();
if (ImGui.Button("Load File (Immediate)"))
this.addedTextures.Add(new(Api10ImmFile: this.inputFilePath));
ImGuiHelpers.ScaledDummy(10);
}
private void DrawUvInput()
{
ImGui.InputFloat2("UV0", ref this.inputTexUv0);
ImGui.InputFloat2("UV1", ref this.inputTexUv1);
ImGui.InputFloat4("Tint", ref this.inputTintCol);
ImGui.InputFloat2("Scale", ref this.inputTexScale);
ImGuiHelpers.ScaledDummy(10);
}
private void TextRightAlign(string s)
{
var width = ImGui.CalcTextSize(s).X;
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetColumnWidth() - width);
ImGui.TextUnformatted(s);
}
private void TextCopiable(string s, bool framepad = false)
{
var offset = ImGui.GetCursorScreenPos() + new Vector2(0, framepad ? ImGui.GetStyle().FramePadding.Y : 0);
if (framepad)
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(s);
if (ImGui.IsItemHovered())
{
ImGui.SetNextWindowPos(offset - ImGui.GetStyle().WindowPadding);
var vp = ImGui.GetWindowViewport();
var wrx = (vp.WorkPos.X + vp.WorkSize.X) - offset.X;
ImGui.SetNextWindowSizeConstraints(Vector2.One, new(wrx, float.MaxValue));
ImGui.BeginTooltip();
ImGui.PushTextWrapPos(wrx);
ImGui.TextWrapped(s.Replace("%", "%%"));
ImGui.PopTextWrapPos();
ImGui.EndTooltip();
}
if (ImGui.IsItemClicked())
{
ImGui.SetClipboardText(s);
Service<NotificationManager>.Get().AddNotification(
$"Copied {ImGui.TableGetColumnName()} to clipboard.",
this.DisplayName,
NotificationType.Success);
}
}
private record TextureEntry(
IDalamudTextureWrap? SharedResource = null,
IDalamudTextureWrap? Api9 = null,
Task<IDalamudTextureWrap>? Api10 = null,
GameIconLookup? Api10ImmGameIcon = null,
string? Api10ImmGamePath = null,
string? Api10ImmFile = null) : IDisposable
{
private static int idCounter;
public int Id { get; } = idCounter++;
public void Dispose()
{
this.SharedResource?.Dispose();
this.Api9?.Dispose();
_ = this.Api10?.ToContentDisposedTask();
}
public string DescribeError()
{
if (this.SharedResource is not null)
return "Unknown error";
if (this.Api9 is not null)
return "Unknown error";
if (this.Api10 is not null)
{
return !this.Api10.IsCompleted
? "Loading"
: this.Api10.Exception?.ToString() ?? (this.Api10.IsCanceled ? "Canceled" : "Unknown error");
}
if (this.Api10ImmGameIcon is not null)
return "Must not happen";
if (this.Api10ImmGamePath is not null)
return "Must not happen";
if (this.Api10ImmFile is not null)
return "Must not happen";
return "Not implemented";
}
public IDalamudTextureWrap? GetTexture(ITextureProvider tp)
{
if (this.SharedResource is not null)
return this.SharedResource;
if (this.Api9 is not null)
return this.Api9;
if (this.Api10 is not null)
return this.Api10.IsCompletedSuccessfully ? this.Api10.Result : null;
if (this.Api10ImmGameIcon is not null)
return tp.ImmediateGetFromGameIcon(this.Api10ImmGameIcon.Value);
if (this.Api10ImmGamePath is not null)
return tp.ImmediateGetFromGame(this.Api10ImmGamePath);
if (this.Api10ImmFile is not null)
return tp.ImmediateGetFromFile(this.Api10ImmFile);
return null;
}
public TextureEntry CreateFromSharedLowLevelResource(ITextureProvider tp) =>
new() { SharedResource = this.GetTexture(tp)?.CreateWrapSharingLowLevelResource() };
public override string ToString()
{
if (this.SharedResource is not null)
return $"{nameof(this.SharedResource)}: {this.SharedResource}";
if (this.Api9 is not null)
return $"{nameof(this.Api9)}: {this.Api9}";
if (this.Api10 is { IsCompletedSuccessfully: true })
return $"{nameof(this.Api10)}: {this.Api10.Result}";
if (this.Api10 is not null)
return $"{nameof(this.Api10)}: {this.Api10}";
if (this.Api10ImmGameIcon is not null)
return $"{nameof(this.Api10ImmGameIcon)}: {this.Api10ImmGameIcon}";
if (this.Api10ImmGamePath is not null)
return $"{nameof(this.Api10ImmGamePath)}: {this.Api10ImmGamePath}";
if (this.Api10ImmFile is not null)
return $"{nameof(this.Api10ImmFile)}: {this.Api10ImmFile}";
return "Not implemented";
}
}
}