mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Resource Tree: Add ChangedItem-like icons, make UI prettier
This commit is contained in:
parent
db521dd21c
commit
30c622c085
8 changed files with 139 additions and 95 deletions
|
|
@ -11,20 +11,21 @@ using Penumbra.GameData.Structs;
|
|||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
using static Penumbra.GameData.Files.ShpkFile;
|
||||
using Penumbra.UI;
|
||||
|
||||
namespace Penumbra.Interop.ResourceTree;
|
||||
|
||||
internal record class GlobalResolveContext(Configuration Config, IObjectIdentifier Identifier, TreeBuildCache TreeBuildCache, ModCollection Collection, int Skeleton, bool WithNames)
|
||||
internal record class GlobalResolveContext(Configuration Config, IObjectIdentifier Identifier, TreeBuildCache TreeBuildCache, ModCollection Collection, int Skeleton, bool WithUIData,
|
||||
bool RedactExternalPaths)
|
||||
{
|
||||
public readonly Dictionary<nint, ResourceNode> Nodes = new(128);
|
||||
|
||||
public ResolveContext CreateContext(EquipSlot slot, CharacterArmor equipment)
|
||||
=> new(Config, Identifier, TreeBuildCache, Collection, Skeleton, WithNames, Nodes, slot, equipment);
|
||||
=> new(Config, Identifier, TreeBuildCache, Collection, Skeleton, WithUIData, RedactExternalPaths, Nodes, slot, equipment);
|
||||
}
|
||||
|
||||
internal record class ResolveContext(Configuration Config, IObjectIdentifier Identifier, TreeBuildCache TreeBuildCache, ModCollection Collection, int Skeleton, bool WithNames,
|
||||
Dictionary<nint, ResourceNode> Nodes, EquipSlot Slot, CharacterArmor Equipment)
|
||||
internal record class ResolveContext(Configuration Config, IObjectIdentifier Identifier, TreeBuildCache TreeBuildCache, ModCollection Collection, int Skeleton, bool WithUIData,
|
||||
bool RedactExternalPaths, Dictionary<nint, ResourceNode> Nodes, EquipSlot Slot, CharacterArmor Equipment)
|
||||
{
|
||||
private static readonly ByteString ShpkPrefix = ByteString.FromSpanUnsafe("shader/sm5/shpk"u8, true, true, true);
|
||||
private unsafe ResourceNode? CreateNodeFromShpk(ShaderPackageResourceHandle* resourceHandle, ByteString gamePath, bool @internal)
|
||||
|
|
@ -78,7 +79,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
if (fullPath.InternalName.IsEmpty)
|
||||
fullPath = Collection.ResolvePath(gamePath) ?? new FullPath(gamePath);
|
||||
|
||||
var node = new ResourceNode(null, type, objectAddress, (nint)resourceHandle, gamePath, FilterFullPath(fullPath), GetResourceHandleLength(resourceHandle), @internal);
|
||||
var node = new ResourceNode(default, type, objectAddress, (nint)resourceHandle, gamePath, FilterFullPath(fullPath), GetResourceHandleLength(resourceHandle), @internal);
|
||||
if (resourceHandle != null)
|
||||
Nodes.Add((nint)resourceHandle, node);
|
||||
|
||||
|
|
@ -99,14 +100,14 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
gamePaths = Filter(gamePaths);
|
||||
|
||||
if (gamePaths.Count == 1)
|
||||
return new ResourceNode(withName ? GuessNameFromPath(gamePaths[0]) : null, type, objectAddress, (nint)handle, gamePaths[0], fullPath,
|
||||
return new ResourceNode(withName ? GuessUIDataFromPath(gamePaths[0]) : default, type, objectAddress, (nint)handle, gamePaths[0], fullPath,
|
||||
GetResourceHandleLength(handle), @internal);
|
||||
|
||||
Penumbra.Log.Information($"Found {gamePaths.Count} game paths while reverse-resolving {fullPath} in {Collection.Name}:");
|
||||
foreach (var gamePath in gamePaths)
|
||||
Penumbra.Log.Information($"Game path: {gamePath}");
|
||||
|
||||
return new ResourceNode(null, type, objectAddress, (nint)handle, gamePaths.ToArray(), fullPath, GetResourceHandleLength(handle), @internal);
|
||||
return new ResourceNode(default, type, objectAddress, (nint)handle, gamePaths.ToArray(), fullPath, GetResourceHandleLength(handle), @internal);
|
||||
}
|
||||
|
||||
public unsafe ResourceNode? CreateHumanSkeletonNode(GenderRace gr)
|
||||
|
|
@ -129,10 +130,10 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
if (node == null)
|
||||
return null;
|
||||
|
||||
if (WithNames)
|
||||
if (WithUIData)
|
||||
{
|
||||
var name = GuessModelName(node.GamePath);
|
||||
node = node.WithName(name != null ? $"IMC: {name}" : null);
|
||||
var uiData = GuessModelUIData(node.GamePath);
|
||||
node = node.WithUIData(uiData.PrependName("IMC: "));
|
||||
}
|
||||
|
||||
Nodes.Add((nint)imc, node);
|
||||
|
|
@ -145,7 +146,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
if (Nodes.TryGetValue((nint)tex, out var cached))
|
||||
return cached;
|
||||
|
||||
var node = CreateNodeFromResourceHandle(ResourceType.Tex, (nint)tex->KernelTexture, &tex->Handle, false, WithNames);
|
||||
var node = CreateNodeFromResourceHandle(ResourceType.Tex, (nint)tex->KernelTexture, &tex->Handle, false, WithUIData);
|
||||
if (node != null)
|
||||
Nodes.Add((nint)tex, node);
|
||||
|
||||
|
|
@ -164,8 +165,8 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
if (node == null)
|
||||
return null;
|
||||
|
||||
if (WithNames)
|
||||
node = node.WithName(GuessModelName(node.GamePath));
|
||||
if (WithUIData)
|
||||
node = node.WithUIData(GuessModelUIData(node.GamePath));
|
||||
|
||||
for (var i = 0; i < mdl->MaterialCount; i++)
|
||||
{
|
||||
|
|
@ -173,9 +174,9 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
var mtrlNode = CreateNodeFromMaterial(mtrl);
|
||||
if (mtrlNode != null)
|
||||
// Don't keep the material's name if it's redundant with the model's name.
|
||||
node.Children.Add(WithNames
|
||||
? mtrlNode.WithName((string.Equals(mtrlNode.Name, node.Name, StringComparison.Ordinal) ? null : mtrlNode.Name)
|
||||
?? $"Material #{i}")
|
||||
node.Children.Add(WithUIData
|
||||
? mtrlNode.WithUIData((string.Equals(mtrlNode.Name, node.Name, StringComparison.Ordinal) ? null : mtrlNode.Name)
|
||||
?? $"Material #{i}", mtrlNode.Icon)
|
||||
: mtrlNode);
|
||||
}
|
||||
|
||||
|
|
@ -225,15 +226,15 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
if (Nodes.TryGetValue((nint)resource, out var cached))
|
||||
return cached;
|
||||
|
||||
var node = CreateNodeFromResourceHandle(ResourceType.Mtrl, (nint) mtrl, &resource->Handle, false, WithNames);
|
||||
var node = CreateNodeFromResourceHandle(ResourceType.Mtrl, (nint) mtrl, &resource->Handle, false, WithUIData);
|
||||
if (node == null)
|
||||
return null;
|
||||
|
||||
var shpkNode = CreateNodeFromShpk(resource->ShpkResourceHandle, new ByteString(resource->ShpkString), false);
|
||||
if (shpkNode != null)
|
||||
node.Children.Add(WithNames ? shpkNode.WithName("Shader Package") : shpkNode);
|
||||
var shpkFile = WithNames && shpkNode != null ? TreeBuildCache.ReadShaderPackage(shpkNode.FullPath) : null;
|
||||
var shpk = WithNames && shpkNode != null ? (ShaderPackage*)shpkNode.ObjectAddress : null;
|
||||
node.Children.Add(WithUIData ? shpkNode.WithUIData("Shader Package", 0) : shpkNode);
|
||||
var shpkFile = WithUIData && shpkNode != null ? TreeBuildCache.ReadShaderPackage(shpkNode.FullPath) : null;
|
||||
var shpk = WithUIData && shpkNode != null ? (ShaderPackage*)shpkNode.ObjectAddress : null;
|
||||
|
||||
for (var i = 0; i < resource->NumTex; i++)
|
||||
{
|
||||
|
|
@ -241,7 +242,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
if (texNode == null)
|
||||
continue;
|
||||
|
||||
if (WithNames)
|
||||
if (WithUIData)
|
||||
{
|
||||
string? name = null;
|
||||
if (shpk != null)
|
||||
|
|
@ -259,7 +260,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
name = shpkFile?.GetSamplerById(samplerCrc.Value)?.Name ?? $"Texture 0x{samplerCrc.Value:X8}";
|
||||
}
|
||||
}
|
||||
node.Children.Add(texNode.WithName(name ?? $"Texture #{i}"));
|
||||
node.Children.Add(texNode.WithUIData(name ?? $"Texture #{i}", 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -280,7 +281,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
if (Nodes.TryGetValue((nint)sklb->SkeletonResourceHandle, out var cached))
|
||||
return cached;
|
||||
|
||||
var node = CreateNodeFromResourceHandle(ResourceType.Sklb, (nint)sklb, (ResourceHandle*)sklb->SkeletonResourceHandle, false, WithNames);
|
||||
var node = CreateNodeFromResourceHandle(ResourceType.Sklb, (nint)sklb, (ResourceHandle*)sklb->SkeletonResourceHandle, false, WithUIData);
|
||||
if (node != null)
|
||||
Nodes.Add((nint)sklb->SkeletonResourceHandle, node);
|
||||
|
||||
|
|
@ -295,7 +296,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
if (Nodes.TryGetValue((nint)sklb->SkeletonParameterResourceHandle, out var cached))
|
||||
return cached;
|
||||
|
||||
var node = CreateNodeFromResourceHandle(ResourceType.Skp, (nint)sklb, (ResourceHandle*)sklb->SkeletonParameterResourceHandle, true, WithNames);
|
||||
var node = CreateNodeFromResourceHandle(ResourceType.Skp, (nint)sklb, (ResourceHandle*)sklb->SkeletonParameterResourceHandle, true, WithUIData);
|
||||
if (node != null)
|
||||
Nodes.Add((nint)sklb->SkeletonParameterResourceHandle, node);
|
||||
|
||||
|
|
@ -308,7 +309,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
return fullPath;
|
||||
|
||||
var relPath = Path.GetRelativePath(Config.ModDirectory, fullPath.FullName);
|
||||
if (relPath == "." || !relPath.StartsWith('.') && !Path.IsPathRooted(relPath))
|
||||
if (!RedactExternalPaths || relPath == "." || !relPath.StartsWith('.') && !Path.IsPathRooted(relPath))
|
||||
return fullPath.Exists ? fullPath : FullPath.Empty;
|
||||
|
||||
return FullPath.Empty;
|
||||
|
|
@ -351,7 +352,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
}
|
||||
: false;
|
||||
|
||||
private string? GuessModelName(Utf8GamePath gamePath)
|
||||
private ResourceNode.UIData GuessModelUIData(Utf8GamePath gamePath)
|
||||
{
|
||||
var path = gamePath.ToString().Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
// Weapons intentionally left out.
|
||||
|
|
@ -359,23 +360,24 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
if (isEquipment)
|
||||
foreach (var item in Identifier.Identify(Equipment.Set, Equipment.Variant, Slot.ToSlot()))
|
||||
{
|
||||
return Slot switch
|
||||
var name = Slot switch
|
||||
{
|
||||
EquipSlot.RFinger => "R: ",
|
||||
EquipSlot.LFinger => "L: ",
|
||||
_ => string.Empty,
|
||||
}
|
||||
+ item.Name.ToString();
|
||||
return new(name, ChangedItemDrawer.GetCategoryIcon(item.Name, item));
|
||||
}
|
||||
|
||||
var nameFromPath = GuessNameFromPath(gamePath);
|
||||
if (nameFromPath != null)
|
||||
return nameFromPath;
|
||||
var dataFromPath = GuessUIDataFromPath(gamePath);
|
||||
if (dataFromPath.Name != null)
|
||||
return dataFromPath;
|
||||
|
||||
return isEquipment ? Slot.ToName() : null;
|
||||
return isEquipment ? new(Slot.ToName(), ChangedItemDrawer.GetCategoryIcon(Slot.ToSlot())) : new(null, ChangedItemDrawer.ChangedItemIcon.Unknown);
|
||||
}
|
||||
|
||||
private string? GuessNameFromPath(Utf8GamePath gamePath)
|
||||
private ResourceNode.UIData GuessUIDataFromPath(Utf8GamePath gamePath)
|
||||
{
|
||||
foreach (var obj in Identifier.Identify(gamePath.ToString()))
|
||||
{
|
||||
|
|
@ -383,10 +385,10 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
if (name.StartsWith("Customization:"))
|
||||
name = name[14..].Trim();
|
||||
if (name != "Unknown")
|
||||
return name;
|
||||
return new(name, ChangedItemDrawer.GetCategoryIcon(obj.Key, obj.Value));
|
||||
}
|
||||
|
||||
return null;
|
||||
return new(null, ChangedItemDrawer.ChangedItemIcon.Unknown);
|
||||
}
|
||||
|
||||
private static string? SafeGet(ReadOnlySpan<string> array, Index index)
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.String.Classes;
|
||||
using ChangedItemIcon = Penumbra.UI.ChangedItemDrawer.ChangedItemIcon;
|
||||
|
||||
namespace Penumbra.Interop.ResourceTree;
|
||||
|
||||
public class ResourceNode
|
||||
{
|
||||
public readonly string? Name;
|
||||
public readonly ChangedItemIcon Icon;
|
||||
public readonly ResourceType Type;
|
||||
public readonly nint ObjectAddress;
|
||||
public readonly nint ResourceHandle;
|
||||
|
|
@ -18,9 +20,11 @@ public class ResourceNode
|
|||
public readonly bool Internal;
|
||||
public readonly List<ResourceNode> Children;
|
||||
|
||||
public ResourceNode(string? name, ResourceType type, nint objectAddress, nint resourceHandle, Utf8GamePath gamePath, FullPath fullPath, ulong length, bool @internal)
|
||||
public ResourceNode(UIData uiData, ResourceType type, nint objectAddress, nint resourceHandle, Utf8GamePath gamePath, FullPath fullPath,
|
||||
ulong length, bool @internal)
|
||||
{
|
||||
Name = name;
|
||||
Name = uiData.Name;
|
||||
Icon = uiData.Icon;
|
||||
Type = type;
|
||||
ObjectAddress = objectAddress;
|
||||
ResourceHandle = resourceHandle;
|
||||
|
|
@ -35,10 +39,11 @@ public class ResourceNode
|
|||
Children = new List<ResourceNode>();
|
||||
}
|
||||
|
||||
public ResourceNode(string? name, ResourceType type, nint objectAddress, nint resourceHandle, Utf8GamePath[] possibleGamePaths, FullPath fullPath,
|
||||
public ResourceNode(UIData uiData, ResourceType type, nint objectAddress, nint resourceHandle, Utf8GamePath[] possibleGamePaths, FullPath fullPath,
|
||||
ulong length, bool @internal)
|
||||
{
|
||||
Name = name;
|
||||
Name = uiData.Name;
|
||||
Icon = uiData.Icon;
|
||||
Type = type;
|
||||
ObjectAddress = objectAddress;
|
||||
ResourceHandle = resourceHandle;
|
||||
|
|
@ -50,9 +55,10 @@ public class ResourceNode
|
|||
Children = new List<ResourceNode>();
|
||||
}
|
||||
|
||||
private ResourceNode(string? name, ResourceNode originalResourceNode)
|
||||
private ResourceNode(UIData uiData, ResourceNode originalResourceNode)
|
||||
{
|
||||
Name = name;
|
||||
Name = uiData.Name;
|
||||
Icon = uiData.Icon;
|
||||
Type = originalResourceNode.Type;
|
||||
ObjectAddress = originalResourceNode.ObjectAddress;
|
||||
ResourceHandle = originalResourceNode.ResourceHandle;
|
||||
|
|
@ -64,6 +70,15 @@ public class ResourceNode
|
|||
Children = originalResourceNode.Children;
|
||||
}
|
||||
|
||||
public ResourceNode WithName(string? name)
|
||||
=> string.Equals(Name, name, StringComparison.Ordinal) ? this : new ResourceNode(name, this);
|
||||
public ResourceNode WithUIData(string? name, ChangedItemIcon icon)
|
||||
=> string.Equals(Name, name, StringComparison.Ordinal) && Icon == icon ? this : new ResourceNode(new(name, icon), this);
|
||||
|
||||
public ResourceNode WithUIData(UIData uiData)
|
||||
=> string.Equals(Name, uiData.Name, StringComparison.Ordinal) && Icon == uiData.Icon ? this : new ResourceNode(uiData, this);
|
||||
|
||||
public readonly record struct UIData(string? Name, ChangedItemIcon Icon)
|
||||
{
|
||||
public readonly UIData PrependName(string prefix)
|
||||
=> Name == null ? this : new(prefix + Name, Icon);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,12 +56,12 @@ public class ResourceTree
|
|||
var imc = (ResourceHandle*)model->IMCArray[i];
|
||||
var imcNode = context.CreateNodeFromImc(imc);
|
||||
if (imcNode != null)
|
||||
Nodes.Add(globalContext.WithNames ? imcNode.WithName(imcNode.Name ?? $"IMC #{i}") : imcNode);
|
||||
Nodes.Add(globalContext.WithUIData ? imcNode.WithUIData(imcNode.Name ?? $"IMC #{i}", imcNode.Icon) : imcNode);
|
||||
|
||||
var mdl = (RenderModel*)model->Models[i];
|
||||
var mdlNode = context.CreateNodeFromRenderModel(mdl);
|
||||
if (mdlNode != null)
|
||||
Nodes.Add(globalContext.WithNames ? mdlNode.WithName(mdlNode.Name ?? $"Model #{i}") : mdlNode);
|
||||
Nodes.Add(globalContext.WithUIData ? mdlNode.WithUIData(mdlNode.Name ?? $"Model #{i}", mdlNode.Icon) : mdlNode);
|
||||
}
|
||||
|
||||
AddSkeleton(Nodes, globalContext.CreateContext(EquipSlot.Unknown, default), model->Skeleton);
|
||||
|
|
@ -92,15 +92,15 @@ public class ResourceTree
|
|||
var imc = (ResourceHandle*)subObject->IMCArray[i];
|
||||
var imcNode = subObjectContext.CreateNodeFromImc(imc);
|
||||
if (imcNode != null)
|
||||
subObjectNodes.Add(globalContext.WithNames
|
||||
? imcNode.WithName(imcNode.Name ?? $"{subObjectNamePrefix} #{subObjectIndex}, IMC #{i}")
|
||||
subObjectNodes.Add(globalContext.WithUIData
|
||||
? imcNode.WithUIData(imcNode.Name ?? $"{subObjectNamePrefix} #{subObjectIndex}, IMC #{i}", imcNode.Icon)
|
||||
: imcNode);
|
||||
|
||||
var mdl = (RenderModel*)subObject->Models[i];
|
||||
var mdlNode = subObjectContext.CreateNodeFromRenderModel(mdl);
|
||||
if (mdlNode != null)
|
||||
subObjectNodes.Add(globalContext.WithNames
|
||||
? mdlNode.WithName(mdlNode.Name ?? $"{subObjectNamePrefix} #{subObjectIndex}, Model #{i}")
|
||||
subObjectNodes.Add(globalContext.WithUIData
|
||||
? mdlNode.WithUIData(mdlNode.Name ?? $"{subObjectNamePrefix} #{subObjectIndex}, Model #{i}", mdlNode.Icon)
|
||||
: mdlNode);
|
||||
}
|
||||
|
||||
|
|
@ -117,11 +117,11 @@ public class ResourceTree
|
|||
|
||||
var decalNode = context.CreateNodeFromTex((TextureResourceHandle*)human->Decal);
|
||||
if (decalNode != null)
|
||||
Nodes.Add(globalContext.WithNames ? decalNode.WithName(decalNode.Name ?? "Face Decal") : decalNode);
|
||||
Nodes.Add(globalContext.WithUIData ? decalNode.WithUIData(decalNode.Name ?? "Face Decal", decalNode.Icon) : decalNode);
|
||||
|
||||
var legacyDecalNode = context.CreateNodeFromTex((TextureResourceHandle*)human->LegacyBodyDecal);
|
||||
if (legacyDecalNode != null)
|
||||
Nodes.Add(globalContext.WithNames ? legacyDecalNode.WithName(legacyDecalNode.Name ?? "Legacy Body Decal") : legacyDecalNode);
|
||||
Nodes.Add(globalContext.WithUIData ? legacyDecalNode.WithUIData(legacyDecalNode.Name ?? "Legacy Body Decal", legacyDecalNode.Icon) : legacyDecalNode);
|
||||
}
|
||||
|
||||
private unsafe void AddSkeleton(List<ResourceNode> nodes, ResolveContext context, Skeleton* skeleton, string prefix = "")
|
||||
|
|
@ -133,11 +133,11 @@ public class ResourceTree
|
|||
{
|
||||
var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]);
|
||||
if (sklbNode != null)
|
||||
nodes.Add(context.WithNames ? sklbNode.WithName($"{prefix}Skeleton #{i}") : sklbNode);
|
||||
nodes.Add(context.WithUIData ? sklbNode.WithUIData($"{prefix}Skeleton #{i}", sklbNode.Icon) : sklbNode);
|
||||
|
||||
var skpNode = context.CreateParameterNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]);
|
||||
if (skpNode != null)
|
||||
nodes.Add(context.WithNames ? skpNode.WithName($"{prefix}Skeleton #{i} Parameters") : skpNode);
|
||||
nodes.Add(context.WithUIData ? skpNode.WithUIData($"{prefix}Skeleton #{i} Parameters", skpNode.Icon) : skpNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,34 +29,35 @@ public class ResourceTreeFactory
|
|||
_actors = actors;
|
||||
}
|
||||
|
||||
public ResourceTree[] FromObjectTable(bool withNames = true)
|
||||
public ResourceTree[] FromObjectTable(bool withNames = true, bool redactExternalPaths = true)
|
||||
{
|
||||
var cache = new TreeBuildCache(_objects, _gameData);
|
||||
|
||||
return cache.Characters
|
||||
.Select(c => FromCharacter(c, cache, withNames))
|
||||
.Select(c => FromCharacter(c, cache, withNames, redactExternalPaths))
|
||||
.OfType<ResourceTree>()
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public IEnumerable<(Dalamud.Game.ClientState.Objects.Types.Character Character, ResourceTree ResourceTree)> FromCharacters(
|
||||
IEnumerable<Dalamud.Game.ClientState.Objects.Types.Character> characters,
|
||||
bool withNames = true)
|
||||
bool withUIData = true, bool redactExternalPaths = true)
|
||||
{
|
||||
var cache = new TreeBuildCache(_objects, _gameData);
|
||||
foreach (var character in characters)
|
||||
{
|
||||
var tree = FromCharacter(character, cache, withNames);
|
||||
var tree = FromCharacter(character, cache, withUIData, redactExternalPaths);
|
||||
if (tree != null)
|
||||
yield return (character, tree);
|
||||
}
|
||||
}
|
||||
|
||||
public ResourceTree? FromCharacter(Dalamud.Game.ClientState.Objects.Types.Character character, bool withNames = true)
|
||||
=> FromCharacter(character, new TreeBuildCache(_objects, _gameData), withNames);
|
||||
public ResourceTree? FromCharacter(Dalamud.Game.ClientState.Objects.Types.Character character, bool withUIData = true,
|
||||
bool redactExternalPaths = true)
|
||||
=> FromCharacter(character, new TreeBuildCache(_objects, _gameData), withUIData, redactExternalPaths);
|
||||
|
||||
private unsafe ResourceTree? FromCharacter(Dalamud.Game.ClientState.Objects.Types.Character character, TreeBuildCache cache,
|
||||
bool withNames = true)
|
||||
bool withUIData = true, bool redactExternalPaths = true)
|
||||
{
|
||||
if (!character.IsValid())
|
||||
return null;
|
||||
|
|
@ -73,7 +74,7 @@ public class ResourceTreeFactory
|
|||
var (name, related) = GetCharacterName(character, cache);
|
||||
var tree = new ResourceTree(name, (nint)gameObjStruct, (nint)drawObjStruct, related, collectionResolveData.ModCollection.Name);
|
||||
var globalContext = new GlobalResolveContext(_config, _identifier.AwaitedService, cache, collectionResolveData.ModCollection,
|
||||
((Character*)gameObjStruct)->CharacterData.ModelCharaId, withNames);
|
||||
((Character*)gameObjStruct)->CharacterData.ModelCharaId, withUIData, redactExternalPaths);
|
||||
tree.LoadResources(globalContext);
|
||||
tree.FlatNodes.UnionWith(globalContext.Nodes.Values);
|
||||
return tree;
|
||||
|
|
|
|||
|
|
@ -554,7 +554,8 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
public ModEditWindow(PerformanceTracker performance, FileDialogService fileDialog, ItemSwapTab itemSwapTab, IDataManager gameData,
|
||||
Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, MetaFileManager metaFileManager,
|
||||
StainService stainService, ActiveCollections activeCollections, DalamudServices dalamud, ModMergeTab modMergeTab,
|
||||
CommunicatorService communicator, TextureManager textures, IDragDropManager dragDropManager, GameEventManager gameEvents)
|
||||
CommunicatorService communicator, TextureManager textures, IDragDropManager dragDropManager, GameEventManager gameEvents,
|
||||
ChangedItemDrawer changedItemDrawer)
|
||||
: base(WindowBaseLabel)
|
||||
{
|
||||
_performance = performance;
|
||||
|
|
@ -581,7 +582,7 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
(bytes, _, _) => new ShpkTab(_fileDialog, bytes));
|
||||
_center = new CombinedTexture(_left, _right);
|
||||
_textureSelectCombo = new TextureDrawer.PathSelectCombo(textures, editor);
|
||||
_quickImportViewer = new ResourceTreeViewer(_config, resourceTreeFactory, 2, OnQuickImportRefresh, DrawQuickImportActions);
|
||||
_quickImportViewer = new ResourceTreeViewer(_config, resourceTreeFactory, changedItemDrawer, 2, OnQuickImportRefresh, DrawQuickImportActions);
|
||||
_communicator.ModPathChanged.Subscribe(OnModPathChanged, ModPathChanged.Priority.ModEditWindow);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ public class ResourceTreeViewer
|
|||
{
|
||||
private readonly Configuration _config;
|
||||
private readonly ResourceTreeFactory _treeFactory;
|
||||
private readonly ChangedItemDrawer _changedItemDrawer;
|
||||
private readonly int _actionCapacity;
|
||||
private readonly Action _onRefresh;
|
||||
private readonly Action<ResourceNode, Vector2> _drawActions;
|
||||
|
|
@ -22,15 +23,16 @@ public class ResourceTreeViewer
|
|||
|
||||
private Task<ResourceTree[]>? _task;
|
||||
|
||||
public ResourceTreeViewer(Configuration config, ResourceTreeFactory treeFactory, int actionCapacity, Action onRefresh,
|
||||
Action<ResourceNode, Vector2> drawActions)
|
||||
public ResourceTreeViewer(Configuration config, ResourceTreeFactory treeFactory, ChangedItemDrawer changedItemDrawer,
|
||||
int actionCapacity, Action onRefresh, Action<ResourceNode, Vector2> drawActions)
|
||||
{
|
||||
_config = config;
|
||||
_treeFactory = treeFactory;
|
||||
_actionCapacity = actionCapacity;
|
||||
_onRefresh = onRefresh;
|
||||
_drawActions = drawActions;
|
||||
_unfolded = new HashSet<nint>();
|
||||
_config = config;
|
||||
_treeFactory = treeFactory;
|
||||
_changedItemDrawer = changedItemDrawer;
|
||||
_actionCapacity = actionCapacity;
|
||||
_onRefresh = onRefresh;
|
||||
_drawActions = drawActions;
|
||||
_unfolded = new HashSet<nint>();
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
|
|
@ -123,7 +125,23 @@ public class ResourceTreeViewer
|
|||
var unfolded = _unfolded.Contains(nodePathHash);
|
||||
using (var indent = ImRaii.PushIndent(level))
|
||||
{
|
||||
ImGui.TableHeader((resourceNode.Children.Count > 0 ? unfolded ? "[-] " : "[+] " : string.Empty) + resourceNode.Name);
|
||||
if (resourceNode.Children.Count > 0)
|
||||
{
|
||||
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||
var icon = (unfolded ? FontAwesomeIcon.CaretDown : FontAwesomeIcon.CaretRight).ToIconString();
|
||||
var offset = (ImGui.GetFrameHeight() - ImGui.CalcTextSize(icon).X) / 2;
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + offset);
|
||||
ImGui.TextUnformatted(icon);
|
||||
ImGui.SameLine(0f, offset + ImGui.GetStyle().ItemInnerSpacing.X);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Dummy(new Vector2(ImGui.GetFrameHeight()));
|
||||
ImGui.SameLine(0f, ImGui.GetStyle().ItemInnerSpacing.X);
|
||||
}
|
||||
_changedItemDrawer.DrawCategoryIcon(resourceNode.Icon);
|
||||
ImGui.SameLine(0f, ImGui.GetStyle().ItemInnerSpacing.X);
|
||||
ImGui.TableHeader(resourceNode.Name);
|
||||
if (ImGui.IsItemClicked() && resourceNode.Children.Count > 0)
|
||||
{
|
||||
if (unfolded)
|
||||
|
|
@ -172,6 +190,8 @@ public class ResourceTreeViewer
|
|||
ImGuiUtil.HoverTooltip("The actual path to this file is unavailable.\nIt may be managed by another plug-in.");
|
||||
}
|
||||
|
||||
mutedColor.Dispose();
|
||||
|
||||
if (_actionCapacity > 0)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
|
|
|
|||
|
|
@ -76,9 +76,11 @@ public class ChangedItemDrawer : IDisposable
|
|||
|
||||
/// <summary> Draw the icon corresponding to the category of a changed item. </summary>
|
||||
public void DrawCategoryIcon(string name, object? data)
|
||||
=> DrawCategoryIcon(GetCategoryIcon(name, data));
|
||||
|
||||
public void DrawCategoryIcon(ChangedItemIcon iconType)
|
||||
{
|
||||
var height = ImGui.GetFrameHeight();
|
||||
var iconType = GetCategoryIcon(name, data);
|
||||
var height = ImGui.GetFrameHeight();
|
||||
if (!_icons.TryGetValue(iconType, out var icon))
|
||||
{
|
||||
ImGui.Dummy(new Vector2(height));
|
||||
|
|
@ -216,27 +218,13 @@ public class ChangedItemDrawer : IDisposable
|
|||
}
|
||||
|
||||
/// <summary> Obtain the icon category corresponding to a changed item. </summary>
|
||||
private static ChangedItemIcon GetCategoryIcon(string name, object? obj)
|
||||
internal static ChangedItemIcon GetCategoryIcon(string name, object? obj)
|
||||
{
|
||||
var iconType = ChangedItemIcon.Unknown;
|
||||
switch (obj)
|
||||
{
|
||||
case EquipItem it:
|
||||
iconType = it.Type.ToSlot() switch
|
||||
{
|
||||
EquipSlot.MainHand => ChangedItemIcon.Mainhand,
|
||||
EquipSlot.OffHand => ChangedItemIcon.Offhand,
|
||||
EquipSlot.Head => ChangedItemIcon.Head,
|
||||
EquipSlot.Body => ChangedItemIcon.Body,
|
||||
EquipSlot.Hands => ChangedItemIcon.Hands,
|
||||
EquipSlot.Legs => ChangedItemIcon.Legs,
|
||||
EquipSlot.Feet => ChangedItemIcon.Feet,
|
||||
EquipSlot.Ears => ChangedItemIcon.Ears,
|
||||
EquipSlot.Neck => ChangedItemIcon.Neck,
|
||||
EquipSlot.Wrists => ChangedItemIcon.Wrists,
|
||||
EquipSlot.RFinger => ChangedItemIcon.Finger,
|
||||
_ => ChangedItemIcon.Unknown,
|
||||
};
|
||||
iconType = GetCategoryIcon(it.Type.ToSlot());
|
||||
break;
|
||||
case ModelChara m:
|
||||
iconType = (CharacterBase.ModelType)m.Type switch
|
||||
|
|
@ -259,6 +247,23 @@ public class ChangedItemDrawer : IDisposable
|
|||
return iconType;
|
||||
}
|
||||
|
||||
internal static ChangedItemIcon GetCategoryIcon(EquipSlot slot)
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.MainHand => ChangedItemIcon.Mainhand,
|
||||
EquipSlot.OffHand => ChangedItemIcon.Offhand,
|
||||
EquipSlot.Head => ChangedItemIcon.Head,
|
||||
EquipSlot.Body => ChangedItemIcon.Body,
|
||||
EquipSlot.Hands => ChangedItemIcon.Hands,
|
||||
EquipSlot.Legs => ChangedItemIcon.Legs,
|
||||
EquipSlot.Feet => ChangedItemIcon.Feet,
|
||||
EquipSlot.Ears => ChangedItemIcon.Ears,
|
||||
EquipSlot.Neck => ChangedItemIcon.Neck,
|
||||
EquipSlot.Wrists => ChangedItemIcon.Wrists,
|
||||
EquipSlot.RFinger => ChangedItemIcon.Finger,
|
||||
_ => ChangedItemIcon.Unknown,
|
||||
};
|
||||
|
||||
/// <summary> Return more detailed object information in text, if it exists. </summary>
|
||||
private static bool GetChangedItemObject(object? obj, out string text)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ public class OnScreenTab : ITab
|
|||
private readonly Configuration _config;
|
||||
private ResourceTreeViewer _viewer;
|
||||
|
||||
public OnScreenTab(Configuration config, ResourceTreeFactory treeFactory)
|
||||
public OnScreenTab(Configuration config, ResourceTreeFactory treeFactory, ChangedItemDrawer changedItemDrawer)
|
||||
{
|
||||
_config = config;
|
||||
_viewer = new ResourceTreeViewer(_config, treeFactory, 0, delegate { }, delegate { });
|
||||
_viewer = new ResourceTreeViewer(_config, treeFactory, changedItemDrawer, 0, delegate { }, delegate { });
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> Label
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue