mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Merge branch 'restree-blurb'
This commit is contained in:
commit
3b81dd89c8
21 changed files with 210 additions and 121 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit 0b5afffda19d3e16aec9e8682d18c8f11f67f1c6
|
||||
Subproject commit becacbca4f35595d16ff40dc9639cfa24be3461f
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 29b71cf7b3cc68995d38f0954fa38c4b9500a81d
|
||||
Subproject commit fed687b536b7c709484db251b690b8821c5ef403
|
||||
|
|
@ -3,8 +3,9 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
|||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.GameData.Interop;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.ResourceTree;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
using Penumbra.String;
|
||||
using static Penumbra.Interop.Structs.StructExtensions;
|
||||
using Model = Penumbra.GameData.Interop.Model;
|
||||
|
||||
namespace Penumbra.Interop.MaterialPreview;
|
||||
|
|
@ -78,8 +79,12 @@ public readonly record struct MaterialInfo(ObjectIndex ObjectIndex, DrawObjectTy
|
|||
continue;
|
||||
|
||||
var mtrlHandle = material->MaterialResourceHandle;
|
||||
var path = ResolveContext.GetResourceHandlePath(&mtrlHandle->ResourceHandle);
|
||||
if (path == needle)
|
||||
if (mtrlHandle == null)
|
||||
continue;
|
||||
|
||||
PathDataHandler.Split(mtrlHandle->ResourceHandle.FileName.AsSpan(), out var path, out _);
|
||||
var fileName = ByteString.FromSpanUnsafe(path, true);
|
||||
if (fileName == needle)
|
||||
result.Add(new MaterialInfo(index, type, i, j));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ internal partial record ResolveContext
|
|||
var imcFileData = imc->GetDataSpan();
|
||||
if (imcFileData.IsEmpty)
|
||||
{
|
||||
Penumbra.Log.Warning($"IMC resource handle with path {GetResourceHandlePath(imc, false)} doesn't have a valid data span");
|
||||
Penumbra.Log.Warning($"IMC resource handle with path {imc->FileName.AsByteString()} doesn't have a valid data span");
|
||||
return variant.Id;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -111,12 +111,18 @@ internal unsafe partial record ResolveContext(
|
|||
if (resourceHandle == null)
|
||||
throw new ArgumentNullException(nameof(resourceHandle));
|
||||
|
||||
var fullPath = Utf8GamePath.FromByteString(GetResourceHandlePath(resourceHandle), out var p) ? new FullPath(p) : FullPath.Empty;
|
||||
var fileName = resourceHandle->FileName.AsSpan();
|
||||
var additionalData = ByteString.Empty;
|
||||
if (PathDataHandler.Split(fileName, out fileName, out var data))
|
||||
additionalData = ByteString.FromSpanUnsafe(data, false).Clone();
|
||||
|
||||
var fullPath = Utf8GamePath.FromSpan(fileName, out var p) ? new FullPath(p.Clone()) : FullPath.Empty;
|
||||
|
||||
var node = new ResourceNode(type, objectAddress, (nint)resourceHandle, GetResourceHandleLength(resourceHandle), this)
|
||||
{
|
||||
GamePath = gamePath,
|
||||
FullPath = fullPath,
|
||||
GamePath = gamePath,
|
||||
FullPath = fullPath,
|
||||
AdditionalData = additionalData,
|
||||
};
|
||||
if (autoAdd)
|
||||
Global.Nodes.Add((gamePath, (nint)resourceHandle), node);
|
||||
|
|
@ -365,21 +371,6 @@ internal unsafe partial record ResolveContext(
|
|||
return i >= 0 && i < array.Length ? array[i] : null;
|
||||
}
|
||||
|
||||
internal static ByteString GetResourceHandlePath(ResourceHandle* handle, bool stripPrefix = true)
|
||||
{
|
||||
if (handle == null)
|
||||
return ByteString.Empty;
|
||||
|
||||
var name = handle->FileName.AsByteString();
|
||||
if (name.IsEmpty)
|
||||
return ByteString.Empty;
|
||||
|
||||
if (stripPrefix && PathDataHandler.Split(name.Span, out var path, out _))
|
||||
name = ByteString.FromSpanUnsafe(path, name.IsNullTerminated, name.IsAsciiLowerCase, name.IsAscii);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private static ulong GetResourceHandleLength(ResourceHandle* handle)
|
||||
{
|
||||
if (handle == null)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Penumbra.Api.Enums;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
using ChangedItemIcon = Penumbra.UI.ChangedItemDrawer.ChangedItemIcon;
|
||||
|
||||
|
|
@ -9,12 +10,12 @@ public class ResourceNode : ICloneable
|
|||
public string? Name;
|
||||
public string? FallbackName;
|
||||
public ChangedItemIcon Icon;
|
||||
public ChangedItemIcon DescendentIcons;
|
||||
public readonly ResourceType Type;
|
||||
public readonly nint ObjectAddress;
|
||||
public readonly nint ResourceHandle;
|
||||
public Utf8GamePath[] PossibleGamePaths;
|
||||
public FullPath FullPath;
|
||||
public ByteString AdditionalData;
|
||||
public readonly ulong Length;
|
||||
public readonly List<ResourceNode> Children;
|
||||
internal ResolveContext? ResolveContext;
|
||||
|
|
@ -40,6 +41,7 @@ public class ResourceNode : ICloneable
|
|||
ObjectAddress = objectAddress;
|
||||
ResourceHandle = resourceHandle;
|
||||
PossibleGamePaths = Array.Empty<Utf8GamePath>();
|
||||
AdditionalData = ByteString.Empty;
|
||||
Length = length;
|
||||
Children = new List<ResourceNode>();
|
||||
ResolveContext = resolveContext;
|
||||
|
|
@ -50,12 +52,12 @@ public class ResourceNode : ICloneable
|
|||
Name = other.Name;
|
||||
FallbackName = other.FallbackName;
|
||||
Icon = other.Icon;
|
||||
DescendentIcons = other.DescendentIcons;
|
||||
Type = other.Type;
|
||||
ObjectAddress = other.ObjectAddress;
|
||||
ResourceHandle = other.ResourceHandle;
|
||||
PossibleGamePaths = other.PossibleGamePaths;
|
||||
FullPath = other.FullPath;
|
||||
AdditionalData = other.AdditionalData;
|
||||
Length = other.Length;
|
||||
Children = other.Children;
|
||||
ResolveContext = other.ResolveContext;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace Penumbra.Interop.ResourceTree;
|
|||
public class ResourceTree
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly string AnonymizedName;
|
||||
public readonly int GameObjectIndex;
|
||||
public readonly nint GameObjectAddress;
|
||||
public readonly nint DrawObjectAddress;
|
||||
|
|
@ -22,6 +23,7 @@ public class ResourceTree
|
|||
public readonly bool PlayerRelated;
|
||||
public readonly bool Networked;
|
||||
public readonly string CollectionName;
|
||||
public readonly string AnonymizedCollectionName;
|
||||
public readonly List<ResourceNode> Nodes;
|
||||
public readonly HashSet<ResourceNode> FlatNodes;
|
||||
|
||||
|
|
@ -29,18 +31,20 @@ public class ResourceTree
|
|||
public CustomizeData CustomizeData;
|
||||
public GenderRace RaceCode;
|
||||
|
||||
public ResourceTree(string name, int gameObjectIndex, nint gameObjectAddress, nint drawObjectAddress, bool localPlayerRelated, bool playerRelated, bool networked, string collectionName)
|
||||
public ResourceTree(string name, string anonymizedName, int gameObjectIndex, nint gameObjectAddress, nint drawObjectAddress, bool localPlayerRelated, bool playerRelated, bool networked, string collectionName, string anonymizedCollectionName)
|
||||
{
|
||||
Name = name;
|
||||
GameObjectIndex = gameObjectIndex;
|
||||
GameObjectAddress = gameObjectAddress;
|
||||
DrawObjectAddress = drawObjectAddress;
|
||||
LocalPlayerRelated = localPlayerRelated;
|
||||
Networked = networked;
|
||||
PlayerRelated = playerRelated;
|
||||
CollectionName = collectionName;
|
||||
Nodes = new List<ResourceNode>();
|
||||
FlatNodes = new HashSet<ResourceNode>();
|
||||
Name = name;
|
||||
AnonymizedName = anonymizedName;
|
||||
GameObjectIndex = gameObjectIndex;
|
||||
GameObjectAddress = gameObjectAddress;
|
||||
DrawObjectAddress = drawObjectAddress;
|
||||
LocalPlayerRelated = localPlayerRelated;
|
||||
Networked = networked;
|
||||
PlayerRelated = playerRelated;
|
||||
CollectionName = collectionName;
|
||||
AnonymizedCollectionName = anonymizedCollectionName;
|
||||
Nodes = new List<ResourceNode>();
|
||||
FlatNodes = new HashSet<ResourceNode>();
|
||||
}
|
||||
|
||||
public void ProcessPostfix(Action<ResourceNode, ResourceNode?> action)
|
||||
|
|
|
|||
|
|
@ -72,10 +72,10 @@ public class ResourceTreeFactory(
|
|||
return null;
|
||||
|
||||
var localPlayerRelated = cache.IsLocalPlayerRelated(character);
|
||||
var (name, related) = GetCharacterName(character, cache);
|
||||
var (name, anonymizedName, related) = GetCharacterName(character);
|
||||
var networked = character.ObjectId != Dalamud.Game.ClientState.Objects.Types.GameObject.InvalidGameObjectId;
|
||||
var tree = new ResourceTree(name, character.ObjectIndex, (nint)gameObjStruct, (nint)drawObjStruct, localPlayerRelated, related,
|
||||
networked, collectionResolveData.ModCollection.Name);
|
||||
var tree = new ResourceTree(name, anonymizedName, character.ObjectIndex, (nint)gameObjStruct, (nint)drawObjStruct, localPlayerRelated, related,
|
||||
networked, collectionResolveData.ModCollection.Name, collectionResolveData.ModCollection.AnonymizedName);
|
||||
var globalContext = new GlobalResolveContext(identifier, collectionResolveData.ModCollection,
|
||||
cache, (flags & Flags.WithUiData) != 0);
|
||||
using (var _ = pathState.EnterInternalResolve())
|
||||
|
|
@ -116,9 +116,6 @@ public class ResourceTreeFactory(
|
|||
{
|
||||
if (node.Name == parent?.Name)
|
||||
node.Name = null;
|
||||
|
||||
if (parent != null)
|
||||
parent.DescendentIcons |= node.Icon | node.DescendentIcons;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -157,27 +154,30 @@ public class ResourceTreeFactory(
|
|||
}
|
||||
}
|
||||
|
||||
private unsafe (string Name, bool PlayerRelated) GetCharacterName(Dalamud.Game.ClientState.Objects.Types.Character character,
|
||||
TreeBuildCache cache)
|
||||
private unsafe (string Name, string AnonymizedName, bool PlayerRelated) GetCharacterName(Dalamud.Game.ClientState.Objects.Types.Character character)
|
||||
{
|
||||
var identifier = actors.FromObject((GameObject*)character.Address, out var owner, true, false, false);
|
||||
switch (identifier.Type)
|
||||
{
|
||||
case IdentifierType.Player: return (identifier.PlayerName.ToString(), true);
|
||||
case IdentifierType.Owned:
|
||||
var ownerChara = objects.Objects.CreateObjectReference(owner) as Dalamud.Game.ClientState.Objects.Types.Character;
|
||||
if (ownerChara != null)
|
||||
{
|
||||
var ownerName = GetCharacterName(ownerChara, cache);
|
||||
return ($"[{ownerName.Name}] {character.Name} ({identifier.Kind.ToName()})", ownerName.PlayerRelated);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return ($"{character.Name} ({identifier.Kind.ToName()})", false);
|
||||
var identifierStr = identifier.ToString();
|
||||
return (identifierStr, identifier.Incognito(identifierStr), IsPlayerRelated(identifier, owner));
|
||||
}
|
||||
|
||||
private unsafe bool IsPlayerRelated(Dalamud.Game.ClientState.Objects.Types.Character? character)
|
||||
{
|
||||
if (character == null)
|
||||
return false;
|
||||
|
||||
var identifier = actors.FromObject((GameObject*)character.Address, out var owner, true, false, false);
|
||||
return IsPlayerRelated(identifier, owner);
|
||||
}
|
||||
|
||||
private bool IsPlayerRelated(ActorIdentifier identifier, Actor owner)
|
||||
=> identifier.Type switch
|
||||
{
|
||||
IdentifierType.Player => true,
|
||||
IdentifierType.Owned => IsPlayerRelated(objects.Objects.CreateObjectReference(owner) as Dalamud.Game.ClientState.Objects.Types.Character),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
[Flags]
|
||||
public enum Flags
|
||||
{
|
||||
|
|
|
|||
|
|
@ -585,7 +585,8 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, MetaFileManager metaFileManager,
|
||||
StainService stainService, ActiveCollections activeCollections, ModMergeTab modMergeTab,
|
||||
CommunicatorService communicator, TextureManager textures, ModelManager models, IDragDropManager dragDropManager,
|
||||
ChangedItemDrawer changedItemDrawer, ObjectManager objects, IFramework framework, CharacterBaseDestructor characterBaseDestructor)
|
||||
ResourceTreeViewerFactory resourceTreeViewerFactory, ObjectManager objects, IFramework framework,
|
||||
CharacterBaseDestructor characterBaseDestructor)
|
||||
: base(WindowBaseLabel)
|
||||
{
|
||||
_performance = performance;
|
||||
|
|
@ -618,8 +619,7 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
_center = new CombinedTexture(_left, _right);
|
||||
_textureSelectCombo = new TextureDrawer.PathSelectCombo(textures, editor, () => GetPlayerResourcesOfType(ResourceType.Tex));
|
||||
_resourceTreeFactory = resourceTreeFactory;
|
||||
_quickImportViewer =
|
||||
new ResourceTreeViewer(_config, resourceTreeFactory, changedItemDrawer, 2, OnQuickImportRefresh, DrawQuickImportActions);
|
||||
_quickImportViewer = resourceTreeViewerFactory.Create(2, OnQuickImportRefresh, DrawQuickImportActions);
|
||||
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModEditWindow);
|
||||
IsOpen = _config is { OpenWindowAtStart: true, Ephemeral.AdvancedEditingOpen: true };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ using OtterGui.Raii;
|
|||
using OtterGui;
|
||||
using Penumbra.Interop.ResourceTree;
|
||||
using Penumbra.UI.Classes;
|
||||
using Penumbra.String;
|
||||
using Penumbra.UI.Tabs;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow;
|
||||
|
||||
|
|
@ -16,31 +18,39 @@ public class ResourceTreeViewer
|
|||
private readonly Configuration _config;
|
||||
private readonly ResourceTreeFactory _treeFactory;
|
||||
private readonly ChangedItemDrawer _changedItemDrawer;
|
||||
private readonly IncognitoService _incognito;
|
||||
private readonly int _actionCapacity;
|
||||
private readonly Action _onRefresh;
|
||||
private readonly Action<ResourceNode, Vector2> _drawActions;
|
||||
private readonly HashSet<nint> _unfolded;
|
||||
|
||||
private readonly Dictionary<nint, NodeVisibility> _filterCache;
|
||||
|
||||
private TreeCategory _categoryFilter;
|
||||
private ChangedItemDrawer.ChangedItemIcon _typeFilter;
|
||||
private string _nameFilter;
|
||||
private string _nodeFilter;
|
||||
|
||||
private Task<ResourceTree[]>? _task;
|
||||
|
||||
public ResourceTreeViewer(Configuration config, ResourceTreeFactory treeFactory, ChangedItemDrawer changedItemDrawer,
|
||||
int actionCapacity, Action onRefresh, Action<ResourceNode, Vector2> drawActions)
|
||||
IncognitoService incognito, int actionCapacity, Action onRefresh, Action<ResourceNode, Vector2> drawActions)
|
||||
{
|
||||
_config = config;
|
||||
_treeFactory = treeFactory;
|
||||
_changedItemDrawer = changedItemDrawer;
|
||||
_incognito = incognito;
|
||||
_actionCapacity = actionCapacity;
|
||||
_onRefresh = onRefresh;
|
||||
_drawActions = drawActions;
|
||||
_unfolded = new HashSet<nint>();
|
||||
_unfolded = [];
|
||||
|
||||
_filterCache = [];
|
||||
|
||||
_categoryFilter = AllCategories;
|
||||
_typeFilter = ChangedItemDrawer.AllFlags;
|
||||
_nameFilter = string.Empty;
|
||||
_nodeFilter = string.Empty;
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
|
|
@ -74,7 +84,7 @@ public class ResourceTreeViewer
|
|||
|
||||
using (var c = ImRaii.PushColor(ImGuiCol.Text, CategoryColor(category).Value()))
|
||||
{
|
||||
var isOpen = ImGui.CollapsingHeader($"{tree.Name}##{index}", index == 0 ? ImGuiTreeNodeFlags.DefaultOpen : 0);
|
||||
var isOpen = ImGui.CollapsingHeader($"{(_incognito.IncognitoMode ? tree.AnonymizedName : tree.Name)}###{index}", index == 0 ? ImGuiTreeNodeFlags.DefaultOpen : 0);
|
||||
if (debugMode)
|
||||
{
|
||||
using var _ = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
|
|
@ -88,7 +98,7 @@ public class ResourceTreeViewer
|
|||
|
||||
using var id = ImRaii.PushId(index);
|
||||
|
||||
ImGui.TextUnformatted($"Collection: {tree.CollectionName}");
|
||||
ImGui.TextUnformatted($"Collection: {(_incognito.IncognitoMode ? tree.AnonymizedCollectionName : tree.CollectionName)}");
|
||||
|
||||
using var table = ImRaii.Table("##ResourceTree", _actionCapacity > 0 ? 4 : 3,
|
||||
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
|
||||
|
|
@ -103,7 +113,7 @@ public class ResourceTreeViewer
|
|||
(_actionCapacity - 1) * 3 * ImGuiHelpers.GlobalScale + _actionCapacity * ImGui.GetFrameHeight());
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
DrawNodes(tree.Nodes, 0, unchecked(tree.DrawObjectAddress * 31));
|
||||
DrawNodes(tree.Nodes, 0, unchecked(tree.DrawObjectAddress * 31), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -136,10 +146,22 @@ public class ResourceTreeViewer
|
|||
|
||||
ImGui.SameLine(0, checkPadding);
|
||||
|
||||
_changedItemDrawer.DrawTypeFilter(ref _typeFilter, -yOffset);
|
||||
var filterChanged = false;
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - yOffset);
|
||||
using (ImRaii.Child("##typeFilter", new Vector2(ImGui.GetContentRegionAvail().X, ChangedItemDrawer.TypeFilterIconSize.Y)))
|
||||
filterChanged |= _changedItemDrawer.DrawTypeFilter(ref _typeFilter);
|
||||
|
||||
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
ImGui.InputTextWithHint("##TreeNameFilter", "Filter by Character/Entity Name...", ref _nameFilter, 128);
|
||||
var fieldWidth = (ImGui.GetContentRegionAvail().X - checkSpacing * 2.0f - ImGui.GetFrameHeightWithSpacing()) / 2.0f;
|
||||
ImGui.SetNextItemWidth(fieldWidth);
|
||||
filterChanged |= ImGui.InputTextWithHint("##TreeNameFilter", "Filter by Character/Entity Name...", ref _nameFilter, 128);
|
||||
ImGui.SameLine(0, checkSpacing);
|
||||
ImGui.SetNextItemWidth(fieldWidth);
|
||||
filterChanged |= ImGui.InputTextWithHint("##NodeFilter", "Filter by Item/Part Name or Path...", ref _nodeFilter, 128);
|
||||
ImGui.SameLine(0, checkSpacing);
|
||||
_incognito.DrawToggle(ImGui.GetFrameHeightWithSpacing());
|
||||
|
||||
if (filterChanged)
|
||||
_filterCache.Clear();
|
||||
}
|
||||
|
||||
private Task<ResourceTree[]> RefreshCharacterList()
|
||||
|
|
@ -153,33 +175,68 @@ public class ResourceTreeViewer
|
|||
}
|
||||
finally
|
||||
{
|
||||
_filterCache.Clear();
|
||||
_unfolded.Clear();
|
||||
_onRefresh();
|
||||
}
|
||||
});
|
||||
|
||||
private void DrawNodes(IEnumerable<ResourceNode> resourceNodes, int level, nint pathHash)
|
||||
private void DrawNodes(IEnumerable<ResourceNode> resourceNodes, int level, nint pathHash, ChangedItemDrawer.ChangedItemIcon parentFilterIcon)
|
||||
{
|
||||
var debugMode = _config.DebugMode;
|
||||
var frameHeight = ImGui.GetFrameHeight();
|
||||
var cellHeight = _actionCapacity > 0 ? frameHeight : 0.0f;
|
||||
|
||||
NodeVisibility GetNodeVisibility(ResourceNode node)
|
||||
bool MatchesFilter(ResourceNode node, ChangedItemDrawer.ChangedItemIcon filterIcon)
|
||||
{
|
||||
if (!_typeFilter.HasFlag(filterIcon))
|
||||
return false;
|
||||
|
||||
if (_nodeFilter.Length == 0)
|
||||
return true;
|
||||
|
||||
return node.Name != null && node.Name.Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase)
|
||||
|| node.FullPath.FullName.Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase)
|
||||
|| node.FullPath.InternalName.ToString().Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase)
|
||||
|| Array.Exists(node.PossibleGamePaths, path => path.Path.ToString().Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
NodeVisibility CalculateNodeVisibility(nint nodePathHash, ResourceNode node, ChangedItemDrawer.ChangedItemIcon parentFilterIcon)
|
||||
{
|
||||
if (node.Internal && !debugMode)
|
||||
return NodeVisibility.Hidden;
|
||||
|
||||
if (_typeFilter.HasFlag(node.Icon))
|
||||
var filterIcon = node.Icon != 0 ? node.Icon : parentFilterIcon;
|
||||
if (MatchesFilter(node, filterIcon))
|
||||
return NodeVisibility.Visible;
|
||||
if ((_typeFilter & node.DescendentIcons) != 0)
|
||||
return NodeVisibility.DescendentsOnly;
|
||||
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
if (GetNodeVisibility(unchecked(nodePathHash * 31 + child.ResourceHandle), child, filterIcon) != NodeVisibility.Hidden)
|
||||
return NodeVisibility.DescendentsOnly;
|
||||
}
|
||||
|
||||
return NodeVisibility.Hidden;
|
||||
}
|
||||
|
||||
NodeVisibility GetNodeVisibility(nint nodePathHash, ResourceNode node, ChangedItemDrawer.ChangedItemIcon parentFilterIcon)
|
||||
{
|
||||
if (!_filterCache.TryGetValue(nodePathHash, out var visibility))
|
||||
{
|
||||
visibility = CalculateNodeVisibility(nodePathHash, node, parentFilterIcon);
|
||||
_filterCache.Add(nodePathHash, visibility);
|
||||
}
|
||||
return visibility;
|
||||
}
|
||||
|
||||
string GetAdditionalDataSuffix(ByteString data)
|
||||
=> !debugMode || data.IsEmpty ? string.Empty : $"\n\nAdditional Data: {data}";
|
||||
|
||||
foreach (var (resourceNode, index) in resourceNodes.WithIndex())
|
||||
{
|
||||
var visibility = GetNodeVisibility(resourceNode);
|
||||
var nodePathHash = unchecked(pathHash + resourceNode.ResourceHandle);
|
||||
|
||||
var visibility = GetNodeVisibility(nodePathHash, resourceNode, parentFilterIcon);
|
||||
if (visibility == NodeVisibility.Hidden)
|
||||
continue;
|
||||
|
||||
|
|
@ -188,14 +245,14 @@ public class ResourceTreeViewer
|
|||
|
||||
using var mutedColor = ImRaii.PushColor(ImGuiCol.Text, textColorInternal, resourceNode.Internal);
|
||||
|
||||
var nodePathHash = unchecked(pathHash + resourceNode.ResourceHandle);
|
||||
var filterIcon = resourceNode.Icon != 0 ? resourceNode.Icon : parentFilterIcon;
|
||||
|
||||
using var id = ImRaii.PushId(index);
|
||||
ImGui.TableNextColumn();
|
||||
var unfolded = _unfolded.Contains(nodePathHash);
|
||||
using (var indent = ImRaii.PushIndent(level))
|
||||
{
|
||||
var hasVisibleChildren = resourceNode.Children.Any(child => GetNodeVisibility(child) != NodeVisibility.Hidden);
|
||||
var hasVisibleChildren = resourceNode.Children.Any(child => GetNodeVisibility(unchecked(nodePathHash * 31 + child.ResourceHandle), child, filterIcon) != NodeVisibility.Hidden);
|
||||
var unfoldable = hasVisibleChildren && visibility != NodeVisibility.DescendentsOnly;
|
||||
if (unfoldable)
|
||||
{
|
||||
|
|
@ -260,13 +317,13 @@ public class ResourceTreeViewer
|
|||
ImGui.Selectable(resourceNode.FullPath.ToPath(), false, 0, new Vector2(ImGui.GetContentRegionAvail().X, cellHeight));
|
||||
if (ImGui.IsItemClicked())
|
||||
ImGui.SetClipboardText(resourceNode.FullPath.ToPath());
|
||||
ImGuiUtil.HoverTooltip($"{resourceNode.FullPath.ToPath()}\n\nClick to copy to clipboard.");
|
||||
ImGuiUtil.HoverTooltip($"{resourceNode.FullPath.ToPath()}\n\nClick to copy to clipboard.{GetAdditionalDataSuffix(resourceNode.AdditionalData)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Selectable("(unavailable)", false, ImGuiSelectableFlags.Disabled,
|
||||
new Vector2(ImGui.GetContentRegionAvail().X, cellHeight));
|
||||
ImGuiUtil.HoverTooltip("The actual path to this file is unavailable.\nIt may be managed by another plug-in.");
|
||||
ImGuiUtil.HoverTooltip($"The actual path to this file is unavailable.\nIt may be managed by another plug-in.{GetAdditionalDataSuffix(resourceNode.AdditionalData)}");
|
||||
}
|
||||
|
||||
mutedColor.Dispose();
|
||||
|
|
@ -280,7 +337,7 @@ public class ResourceTreeViewer
|
|||
}
|
||||
|
||||
if (unfolded)
|
||||
DrawNodes(resourceNode.Children, level + 1, unchecked(nodePathHash * 31));
|
||||
DrawNodes(resourceNode.Children, level + 1, unchecked(nodePathHash * 31), filterIcon);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
14
Penumbra/UI/AdvancedWindow/ResourceTreeViewerFactory.cs
Normal file
14
Penumbra/UI/AdvancedWindow/ResourceTreeViewerFactory.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using OtterGui.Services;
|
||||
using Penumbra.Interop.ResourceTree;
|
||||
|
||||
namespace Penumbra.UI.AdvancedWindow;
|
||||
|
||||
public class ResourceTreeViewerFactory(
|
||||
Configuration config,
|
||||
ResourceTreeFactory treeFactory,
|
||||
ChangedItemDrawer changedItemDrawer,
|
||||
IncognitoService incognito) : IService
|
||||
{
|
||||
public ResourceTreeViewer Create(int actionCapacity, Action onRefresh, Action<ResourceNode, Vector2> drawActions)
|
||||
=> new(config, treeFactory, changedItemDrawer, incognito, actionCapacity, onRefresh, drawActions);
|
||||
}
|
||||
|
|
@ -212,7 +212,7 @@ public class ChangedItemDrawer : IDisposable
|
|||
return;
|
||||
|
||||
var typeFilter = _config.Ephemeral.ChangedItemFilter;
|
||||
if (DrawTypeFilter(ref typeFilter, 0.0f))
|
||||
if (DrawTypeFilter(ref typeFilter))
|
||||
{
|
||||
_config.Ephemeral.ChangedItemFilter = typeFilter;
|
||||
_config.Ephemeral.Save();
|
||||
|
|
@ -220,7 +220,7 @@ public class ChangedItemDrawer : IDisposable
|
|||
}
|
||||
|
||||
/// <summary> Draw a header line with the different icon types to filter them. </summary>
|
||||
public bool DrawTypeFilter(ref ChangedItemIcon typeFilter, float yOffset)
|
||||
public bool DrawTypeFilter(ref ChangedItemIcon typeFilter)
|
||||
{
|
||||
var ret = false;
|
||||
using var _ = ImRaii.PushId("ChangedItemIconFilter");
|
||||
|
|
@ -233,7 +233,6 @@ public class ChangedItemDrawer : IDisposable
|
|||
var ret = false;
|
||||
var icon = _icons[type];
|
||||
var flag = typeFilter.HasFlag(type);
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + yOffset);
|
||||
ImGui.Image(icon.ImGuiHandle, size, Vector2.Zero, Vector2.One, flag ? Vector4.One : new Vector4(0.6f, 0.3f, 0.3f, 1f));
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||
{
|
||||
|
|
@ -267,7 +266,7 @@ public class ChangedItemDrawer : IDisposable
|
|||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
ImGui.SetCursorPos(new Vector2(ImGui.GetContentRegionMax().X - size.X, ImGui.GetCursorPosY() + yOffset));
|
||||
ImGui.SetCursorPosX(ImGui.GetContentRegionMax().X - size.X);
|
||||
ImGui.Image(_icons[AllFlags].ImGuiHandle, size, Vector2.Zero, Vector2.One,
|
||||
typeFilter == 0 ? new Vector4(0.6f, 0.3f, 0.3f, 1f) :
|
||||
typeFilter == AllFlags ? new Vector4(0.75f, 0.75f, 0.75f, 1f) : new Vector4(0.5f, 0.5f, 1f, 1f));
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ public sealed class CollectionPanel : IDisposable
|
|||
private readonly InheritanceUi _inheritanceUi;
|
||||
private readonly ModStorage _mods;
|
||||
private readonly FilenameService _fileNames;
|
||||
private readonly IncognitoService _incognito;
|
||||
private readonly IFontHandle _nameFont;
|
||||
|
||||
private static readonly IReadOnlyDictionary<CollectionType, (string Name, uint Border)> Buttons = CreateButtons();
|
||||
|
|
@ -41,7 +42,8 @@ public sealed class CollectionPanel : IDisposable
|
|||
private int _draggedIndividualAssignment = -1;
|
||||
|
||||
public CollectionPanel(DalamudPluginInterface pi, CommunicatorService communicator, CollectionManager manager,
|
||||
CollectionSelector selector, ActorManager actors, ITargetManager targets, ModStorage mods, FilenameService fileNames)
|
||||
CollectionSelector selector, ActorManager actors, ITargetManager targets, ModStorage mods, FilenameService fileNames,
|
||||
IncognitoService incognito)
|
||||
{
|
||||
_collections = manager.Storage;
|
||||
_active = manager.Active;
|
||||
|
|
@ -50,8 +52,9 @@ public sealed class CollectionPanel : IDisposable
|
|||
_targets = targets;
|
||||
_mods = mods;
|
||||
_fileNames = fileNames;
|
||||
_incognito = incognito;
|
||||
_individualAssignmentUi = new IndividualAssignmentUi(communicator, actors, manager);
|
||||
_inheritanceUi = new InheritanceUi(manager, _selector);
|
||||
_inheritanceUi = new InheritanceUi(manager, incognito);
|
||||
_nameFont = pi.UiBuilder.FontAtlas.NewGameFontHandle(new GameFontStyle(GameFontFamilyAndSize.Jupiter23));
|
||||
}
|
||||
|
||||
|
|
@ -415,7 +418,7 @@ public sealed class CollectionPanel : IDisposable
|
|||
|
||||
/// <summary> Respect incognito mode for names of identifiers. </summary>
|
||||
private string Name(ActorIdentifier id, string? name)
|
||||
=> _selector.IncognitoMode && id.Type is IdentifierType.Player or IdentifierType.Owned
|
||||
=> _incognito.IncognitoMode && id.Type is IdentifierType.Player or IdentifierType.Owned
|
||||
? id.Incognito(name)
|
||||
: name ?? id.ToString();
|
||||
|
||||
|
|
@ -423,7 +426,7 @@ public sealed class CollectionPanel : IDisposable
|
|||
private string Name(ModCollection? collection)
|
||||
=> collection == null ? "Unassigned" :
|
||||
collection == ModCollection.Empty ? "Use No Mods" :
|
||||
_selector.IncognitoMode ? collection.AnonymizedName : collection.Name;
|
||||
_incognito.IncognitoMode ? collection.AnonymizedName : collection.Name;
|
||||
|
||||
private void DrawIndividualButton(string intro, Vector2 width, string tooltip, char suffix, params ActorIdentifier[] identifiers)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,13 +17,12 @@ public sealed class CollectionSelector : ItemSelector<ModCollection>, IDisposabl
|
|||
private readonly CollectionStorage _storage;
|
||||
private readonly ActiveCollections _active;
|
||||
private readonly TutorialService _tutorial;
|
||||
private readonly IncognitoService _incognito;
|
||||
|
||||
private ModCollection? _dragging;
|
||||
|
||||
public bool IncognitoMode;
|
||||
|
||||
public CollectionSelector(Configuration config, CommunicatorService communicator, CollectionStorage storage, ActiveCollections active,
|
||||
TutorialService tutorial)
|
||||
TutorialService tutorial, IncognitoService incognito)
|
||||
: base([], Flags.Delete | Flags.Add | Flags.Duplicate | Flags.Filter)
|
||||
{
|
||||
_config = config;
|
||||
|
|
@ -31,6 +30,7 @@ public sealed class CollectionSelector : ItemSelector<ModCollection>, IDisposabl
|
|||
_storage = storage;
|
||||
_active = active;
|
||||
_tutorial = tutorial;
|
||||
_incognito = incognito;
|
||||
|
||||
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.CollectionSelector);
|
||||
// Set items.
|
||||
|
|
@ -109,7 +109,7 @@ public sealed class CollectionSelector : ItemSelector<ModCollection>, IDisposabl
|
|||
}
|
||||
|
||||
private string Name(ModCollection collection)
|
||||
=> IncognitoMode || collection.Name.Length == 0 ? collection.AnonymizedName : collection.Name;
|
||||
=> _incognito.IncognitoMode || collection.Name.Length == 0 ? collection.AnonymizedName : collection.Name;
|
||||
|
||||
private void OnCollectionChange(CollectionType type, ModCollection? old, ModCollection? @new, string _3)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ using Penumbra.UI.Classes;
|
|||
|
||||
namespace Penumbra.UI.CollectionTab;
|
||||
|
||||
public class InheritanceUi(CollectionManager collectionManager, CollectionSelector selector) : IUiService
|
||||
public class InheritanceUi(CollectionManager collectionManager, IncognitoService incognito) : IUiService
|
||||
{
|
||||
private const int InheritedCollectionHeight = 9;
|
||||
private const string InheritanceDragDropLabel = "##InheritanceMove";
|
||||
|
|
@ -312,5 +312,5 @@ public class InheritanceUi(CollectionManager collectionManager, CollectionSelect
|
|||
}
|
||||
|
||||
private string Name(ModCollection collection)
|
||||
=> selector.IncognitoMode ? collection.AnonymizedName : collection.Name;
|
||||
=> incognito.IncognitoMode ? collection.AnonymizedName : collection.Name;
|
||||
}
|
||||
|
|
|
|||
26
Penumbra/UI/IncognitoService.cs
Normal file
26
Penumbra/UI/IncognitoService.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using Dalamud.Interface;
|
||||
using Penumbra.UI.Classes;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Text;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public class IncognitoService(TutorialService tutorial) : IService
|
||||
{
|
||||
public bool IncognitoMode;
|
||||
|
||||
public void DrawToggle(float width)
|
||||
{
|
||||
var color = ColorId.FolderExpanded.Value();
|
||||
using (ImRaii.PushFrameBorder(ImUtf8.GlobalScale, color))
|
||||
{
|
||||
var tt = IncognitoMode ? "Toggle incognito mode off."u8 : "Toggle incognito mode on."u8;
|
||||
var icon = IncognitoMode ? FontAwesomeIcon.EyeSlash : FontAwesomeIcon.Eye;
|
||||
if (ImUtf8.IconButton(icon, tt, new Vector2(width, ImUtf8.FrameHeight), false, color))
|
||||
IncognitoMode = !IncognitoMode;
|
||||
}
|
||||
|
||||
tutorial.OpenTutorial(BasicTutorialSteps.Incognito);
|
||||
}
|
||||
}
|
||||
|
|
@ -118,7 +118,7 @@ public readonly struct ImcModGroupEditDrawer(ModGroupEditDrawer editor, ImcModGr
|
|||
: validName
|
||||
? "Add a new option to this group."u8
|
||||
: "Please enter a name for the new option."u8;
|
||||
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, !validName || dis))
|
||||
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, default, !validName || dis))
|
||||
{
|
||||
editor.ModManager.OptionEditor.ImcEditor.AddOption(group, cache, name);
|
||||
editor.NewOptionName = null;
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public readonly struct MultiModGroupEditDrawer(ModGroupEditDrawer editor, MultiM
|
|||
var validName = name.Length > 0;
|
||||
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, validName
|
||||
? "Add a new option to this group."u8
|
||||
: "Please enter a name for the new option."u8, !validName))
|
||||
: "Please enter a name for the new option."u8, default, !validName))
|
||||
{
|
||||
editor.ModManager.OptionEditor.MultiEditor.AddOption(group, name);
|
||||
editor.NewOptionName = null;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public readonly struct SingleModGroupEditDrawer(ModGroupEditDrawer editor, Singl
|
|||
var validName = name.Length > 0;
|
||||
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, validName
|
||||
? "Add a new option to this group."u8
|
||||
: "Please enter a name for the new option."u8, !validName))
|
||||
: "Please enter a name for the new option."u8, default, !validName))
|
||||
{
|
||||
editor.ModManager.OptionEditor.SingleEditor.AddOption(group, name);
|
||||
editor.NewOptionName = null;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ public sealed class CollectionsTab : IDisposable, ITab
|
|||
private readonly CollectionSelector _selector;
|
||||
private readonly CollectionPanel _panel;
|
||||
private readonly TutorialService _tutorial;
|
||||
private readonly IncognitoService _incognito;
|
||||
|
||||
public enum PanelMode
|
||||
{
|
||||
|
|
@ -40,13 +41,14 @@ public sealed class CollectionsTab : IDisposable, ITab
|
|||
}
|
||||
}
|
||||
|
||||
public CollectionsTab(DalamudPluginInterface pi, Configuration configuration, CommunicatorService communicator,
|
||||
public CollectionsTab(DalamudPluginInterface pi, Configuration configuration, CommunicatorService communicator, IncognitoService incognito,
|
||||
CollectionManager collectionManager, ModStorage modStorage, ActorManager actors, ITargetManager targets, TutorialService tutorial, FilenameService fileNames)
|
||||
{
|
||||
_config = configuration.Ephemeral;
|
||||
_tutorial = tutorial;
|
||||
_selector = new CollectionSelector(configuration, communicator, collectionManager.Storage, collectionManager.Active, _tutorial);
|
||||
_panel = new CollectionPanel(pi, communicator, collectionManager, _selector, actors, targets, modStorage, fileNames);
|
||||
_config = configuration.Ephemeral;
|
||||
_tutorial = tutorial;
|
||||
_incognito = incognito;
|
||||
_selector = new CollectionSelector(configuration, communicator, collectionManager.Storage, collectionManager.Active, _tutorial, incognito);
|
||||
_panel = new CollectionPanel(pi, communicator, collectionManager, _selector, actors, targets, modStorage, fileNames, incognito);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -116,18 +118,7 @@ public sealed class CollectionsTab : IDisposable, ITab
|
|||
_tutorial.OpenTutorial(BasicTutorialSteps.CollectionDetails);
|
||||
ImGui.SameLine();
|
||||
|
||||
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||
color.Push(ImGuiCol.Text, ColorId.FolderExpanded.Value())
|
||||
.Push(ImGuiCol.Border, ColorId.FolderExpanded.Value());
|
||||
if (ImGuiUtil.DrawDisabledButton(
|
||||
$"{(_selector.IncognitoMode ? FontAwesomeIcon.Eye : FontAwesomeIcon.EyeSlash).ToIconString()}###IncognitoMode",
|
||||
buttonSize with { X = withSpacing }, string.Empty, false, true))
|
||||
_selector.IncognitoMode = !_selector.IncognitoMode;
|
||||
var hovered = ImGui.IsItemHovered();
|
||||
_tutorial.OpenTutorial(BasicTutorialSteps.Incognito);
|
||||
color.Pop(2);
|
||||
if (hovered)
|
||||
ImGui.SetTooltip(_selector.IncognitoMode ? "Toggle incognito mode off." : "Toggle incognito mode on.");
|
||||
_incognito.DrawToggle(withSpacing);
|
||||
}
|
||||
|
||||
private void DrawPanel()
|
||||
|
|
|
|||
|
|
@ -1,18 +1,15 @@
|
|||
using OtterGui.Widgets;
|
||||
using Penumbra.Interop.ResourceTree;
|
||||
using Penumbra.UI.AdvancedWindow;
|
||||
|
||||
namespace Penumbra.UI.Tabs;
|
||||
|
||||
public class OnScreenTab : ITab
|
||||
{
|
||||
private readonly Configuration _config;
|
||||
private readonly ResourceTreeViewer _viewer;
|
||||
|
||||
public OnScreenTab(Configuration config, ResourceTreeFactory treeFactory, ChangedItemDrawer changedItemDrawer)
|
||||
public OnScreenTab(ResourceTreeViewerFactory resourceTreeViewerFactory)
|
||||
{
|
||||
_config = config;
|
||||
_viewer = new ResourceTreeViewer(_config, treeFactory, changedItemDrawer, 0, delegate { }, delegate { });
|
||||
_viewer = resourceTreeViewerFactory.Create(0, delegate { }, delegate { });
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> Label
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue