mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-18 06:34:19 +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 FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using Penumbra.GameData.Interop;
|
using Penumbra.GameData.Interop;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Interop.ResourceTree;
|
using Penumbra.Interop.PathResolving;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
|
using static Penumbra.Interop.Structs.StructExtensions;
|
||||||
using Model = Penumbra.GameData.Interop.Model;
|
using Model = Penumbra.GameData.Interop.Model;
|
||||||
|
|
||||||
namespace Penumbra.Interop.MaterialPreview;
|
namespace Penumbra.Interop.MaterialPreview;
|
||||||
|
|
@ -78,8 +79,12 @@ public readonly record struct MaterialInfo(ObjectIndex ObjectIndex, DrawObjectTy
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var mtrlHandle = material->MaterialResourceHandle;
|
var mtrlHandle = material->MaterialResourceHandle;
|
||||||
var path = ResolveContext.GetResourceHandlePath(&mtrlHandle->ResourceHandle);
|
if (mtrlHandle == null)
|
||||||
if (path == needle)
|
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));
|
result.Add(new MaterialInfo(index, type, i, j));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ internal partial record ResolveContext
|
||||||
var imcFileData = imc->GetDataSpan();
|
var imcFileData = imc->GetDataSpan();
|
||||||
if (imcFileData.IsEmpty)
|
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;
|
return variant.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111,12 +111,18 @@ internal unsafe partial record ResolveContext(
|
||||||
if (resourceHandle == null)
|
if (resourceHandle == null)
|
||||||
throw new ArgumentNullException(nameof(resourceHandle));
|
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)
|
var node = new ResourceNode(type, objectAddress, (nint)resourceHandle, GetResourceHandleLength(resourceHandle), this)
|
||||||
{
|
{
|
||||||
GamePath = gamePath,
|
GamePath = gamePath,
|
||||||
FullPath = fullPath,
|
FullPath = fullPath,
|
||||||
|
AdditionalData = additionalData,
|
||||||
};
|
};
|
||||||
if (autoAdd)
|
if (autoAdd)
|
||||||
Global.Nodes.Add((gamePath, (nint)resourceHandle), node);
|
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;
|
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)
|
private static ulong GetResourceHandleLength(ResourceHandle* handle)
|
||||||
{
|
{
|
||||||
if (handle == null)
|
if (handle == null)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
|
using Penumbra.String;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using ChangedItemIcon = Penumbra.UI.ChangedItemDrawer.ChangedItemIcon;
|
using ChangedItemIcon = Penumbra.UI.ChangedItemDrawer.ChangedItemIcon;
|
||||||
|
|
||||||
|
|
@ -9,12 +10,12 @@ public class ResourceNode : ICloneable
|
||||||
public string? Name;
|
public string? Name;
|
||||||
public string? FallbackName;
|
public string? FallbackName;
|
||||||
public ChangedItemIcon Icon;
|
public ChangedItemIcon Icon;
|
||||||
public ChangedItemIcon DescendentIcons;
|
|
||||||
public readonly ResourceType Type;
|
public readonly ResourceType Type;
|
||||||
public readonly nint ObjectAddress;
|
public readonly nint ObjectAddress;
|
||||||
public readonly nint ResourceHandle;
|
public readonly nint ResourceHandle;
|
||||||
public Utf8GamePath[] PossibleGamePaths;
|
public Utf8GamePath[] PossibleGamePaths;
|
||||||
public FullPath FullPath;
|
public FullPath FullPath;
|
||||||
|
public ByteString AdditionalData;
|
||||||
public readonly ulong Length;
|
public readonly ulong Length;
|
||||||
public readonly List<ResourceNode> Children;
|
public readonly List<ResourceNode> Children;
|
||||||
internal ResolveContext? ResolveContext;
|
internal ResolveContext? ResolveContext;
|
||||||
|
|
@ -40,6 +41,7 @@ public class ResourceNode : ICloneable
|
||||||
ObjectAddress = objectAddress;
|
ObjectAddress = objectAddress;
|
||||||
ResourceHandle = resourceHandle;
|
ResourceHandle = resourceHandle;
|
||||||
PossibleGamePaths = Array.Empty<Utf8GamePath>();
|
PossibleGamePaths = Array.Empty<Utf8GamePath>();
|
||||||
|
AdditionalData = ByteString.Empty;
|
||||||
Length = length;
|
Length = length;
|
||||||
Children = new List<ResourceNode>();
|
Children = new List<ResourceNode>();
|
||||||
ResolveContext = resolveContext;
|
ResolveContext = resolveContext;
|
||||||
|
|
@ -50,12 +52,12 @@ public class ResourceNode : ICloneable
|
||||||
Name = other.Name;
|
Name = other.Name;
|
||||||
FallbackName = other.FallbackName;
|
FallbackName = other.FallbackName;
|
||||||
Icon = other.Icon;
|
Icon = other.Icon;
|
||||||
DescendentIcons = other.DescendentIcons;
|
|
||||||
Type = other.Type;
|
Type = other.Type;
|
||||||
ObjectAddress = other.ObjectAddress;
|
ObjectAddress = other.ObjectAddress;
|
||||||
ResourceHandle = other.ResourceHandle;
|
ResourceHandle = other.ResourceHandle;
|
||||||
PossibleGamePaths = other.PossibleGamePaths;
|
PossibleGamePaths = other.PossibleGamePaths;
|
||||||
FullPath = other.FullPath;
|
FullPath = other.FullPath;
|
||||||
|
AdditionalData = other.AdditionalData;
|
||||||
Length = other.Length;
|
Length = other.Length;
|
||||||
Children = other.Children;
|
Children = other.Children;
|
||||||
ResolveContext = other.ResolveContext;
|
ResolveContext = other.ResolveContext;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ namespace Penumbra.Interop.ResourceTree;
|
||||||
public class ResourceTree
|
public class ResourceTree
|
||||||
{
|
{
|
||||||
public readonly string Name;
|
public readonly string Name;
|
||||||
|
public readonly string AnonymizedName;
|
||||||
public readonly int GameObjectIndex;
|
public readonly int GameObjectIndex;
|
||||||
public readonly nint GameObjectAddress;
|
public readonly nint GameObjectAddress;
|
||||||
public readonly nint DrawObjectAddress;
|
public readonly nint DrawObjectAddress;
|
||||||
|
|
@ -22,6 +23,7 @@ public class ResourceTree
|
||||||
public readonly bool PlayerRelated;
|
public readonly bool PlayerRelated;
|
||||||
public readonly bool Networked;
|
public readonly bool Networked;
|
||||||
public readonly string CollectionName;
|
public readonly string CollectionName;
|
||||||
|
public readonly string AnonymizedCollectionName;
|
||||||
public readonly List<ResourceNode> Nodes;
|
public readonly List<ResourceNode> Nodes;
|
||||||
public readonly HashSet<ResourceNode> FlatNodes;
|
public readonly HashSet<ResourceNode> FlatNodes;
|
||||||
|
|
||||||
|
|
@ -29,18 +31,20 @@ public class ResourceTree
|
||||||
public CustomizeData CustomizeData;
|
public CustomizeData CustomizeData;
|
||||||
public GenderRace RaceCode;
|
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;
|
Name = name;
|
||||||
GameObjectIndex = gameObjectIndex;
|
AnonymizedName = anonymizedName;
|
||||||
GameObjectAddress = gameObjectAddress;
|
GameObjectIndex = gameObjectIndex;
|
||||||
DrawObjectAddress = drawObjectAddress;
|
GameObjectAddress = gameObjectAddress;
|
||||||
LocalPlayerRelated = localPlayerRelated;
|
DrawObjectAddress = drawObjectAddress;
|
||||||
Networked = networked;
|
LocalPlayerRelated = localPlayerRelated;
|
||||||
PlayerRelated = playerRelated;
|
Networked = networked;
|
||||||
CollectionName = collectionName;
|
PlayerRelated = playerRelated;
|
||||||
Nodes = new List<ResourceNode>();
|
CollectionName = collectionName;
|
||||||
FlatNodes = new HashSet<ResourceNode>();
|
AnonymizedCollectionName = anonymizedCollectionName;
|
||||||
|
Nodes = new List<ResourceNode>();
|
||||||
|
FlatNodes = new HashSet<ResourceNode>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ProcessPostfix(Action<ResourceNode, ResourceNode?> action)
|
public void ProcessPostfix(Action<ResourceNode, ResourceNode?> action)
|
||||||
|
|
|
||||||
|
|
@ -72,10 +72,10 @@ public class ResourceTreeFactory(
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var localPlayerRelated = cache.IsLocalPlayerRelated(character);
|
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 networked = character.ObjectId != Dalamud.Game.ClientState.Objects.Types.GameObject.InvalidGameObjectId;
|
||||||
var tree = new ResourceTree(name, character.ObjectIndex, (nint)gameObjStruct, (nint)drawObjStruct, localPlayerRelated, related,
|
var tree = new ResourceTree(name, anonymizedName, character.ObjectIndex, (nint)gameObjStruct, (nint)drawObjStruct, localPlayerRelated, related,
|
||||||
networked, collectionResolveData.ModCollection.Name);
|
networked, collectionResolveData.ModCollection.Name, collectionResolveData.ModCollection.AnonymizedName);
|
||||||
var globalContext = new GlobalResolveContext(identifier, collectionResolveData.ModCollection,
|
var globalContext = new GlobalResolveContext(identifier, collectionResolveData.ModCollection,
|
||||||
cache, (flags & Flags.WithUiData) != 0);
|
cache, (flags & Flags.WithUiData) != 0);
|
||||||
using (var _ = pathState.EnterInternalResolve())
|
using (var _ = pathState.EnterInternalResolve())
|
||||||
|
|
@ -116,9 +116,6 @@ public class ResourceTreeFactory(
|
||||||
{
|
{
|
||||||
if (node.Name == parent?.Name)
|
if (node.Name == parent?.Name)
|
||||||
node.Name = null;
|
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,
|
private unsafe (string Name, string AnonymizedName, bool PlayerRelated) GetCharacterName(Dalamud.Game.ClientState.Objects.Types.Character character)
|
||||||
TreeBuildCache cache)
|
|
||||||
{
|
{
|
||||||
var identifier = actors.FromObject((GameObject*)character.Address, out var owner, true, false, false);
|
var identifier = actors.FromObject((GameObject*)character.Address, out var owner, true, false, false);
|
||||||
switch (identifier.Type)
|
var identifierStr = identifier.ToString();
|
||||||
{
|
return (identifierStr, identifier.Incognito(identifierStr), IsPlayerRelated(identifier, owner));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
[Flags]
|
||||||
public enum Flags
|
public enum Flags
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -585,7 +585,8 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, MetaFileManager metaFileManager,
|
Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, MetaFileManager metaFileManager,
|
||||||
StainService stainService, ActiveCollections activeCollections, ModMergeTab modMergeTab,
|
StainService stainService, ActiveCollections activeCollections, ModMergeTab modMergeTab,
|
||||||
CommunicatorService communicator, TextureManager textures, ModelManager models, IDragDropManager dragDropManager,
|
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)
|
: base(WindowBaseLabel)
|
||||||
{
|
{
|
||||||
_performance = performance;
|
_performance = performance;
|
||||||
|
|
@ -618,8 +619,7 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
_center = new CombinedTexture(_left, _right);
|
_center = new CombinedTexture(_left, _right);
|
||||||
_textureSelectCombo = new TextureDrawer.PathSelectCombo(textures, editor, () => GetPlayerResourcesOfType(ResourceType.Tex));
|
_textureSelectCombo = new TextureDrawer.PathSelectCombo(textures, editor, () => GetPlayerResourcesOfType(ResourceType.Tex));
|
||||||
_resourceTreeFactory = resourceTreeFactory;
|
_resourceTreeFactory = resourceTreeFactory;
|
||||||
_quickImportViewer =
|
_quickImportViewer = resourceTreeViewerFactory.Create(2, OnQuickImportRefresh, DrawQuickImportActions);
|
||||||
new ResourceTreeViewer(_config, resourceTreeFactory, changedItemDrawer, 2, OnQuickImportRefresh, DrawQuickImportActions);
|
|
||||||
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModEditWindow);
|
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModEditWindow);
|
||||||
IsOpen = _config is { OpenWindowAtStart: true, Ephemeral.AdvancedEditingOpen: true };
|
IsOpen = _config is { OpenWindowAtStart: true, Ephemeral.AdvancedEditingOpen: true };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ using OtterGui.Raii;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using Penumbra.Interop.ResourceTree;
|
using Penumbra.Interop.ResourceTree;
|
||||||
using Penumbra.UI.Classes;
|
using Penumbra.UI.Classes;
|
||||||
|
using Penumbra.String;
|
||||||
|
using Penumbra.UI.Tabs;
|
||||||
|
|
||||||
namespace Penumbra.UI.AdvancedWindow;
|
namespace Penumbra.UI.AdvancedWindow;
|
||||||
|
|
||||||
|
|
@ -16,31 +18,39 @@ public class ResourceTreeViewer
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private readonly ResourceTreeFactory _treeFactory;
|
private readonly ResourceTreeFactory _treeFactory;
|
||||||
private readonly ChangedItemDrawer _changedItemDrawer;
|
private readonly ChangedItemDrawer _changedItemDrawer;
|
||||||
|
private readonly IncognitoService _incognito;
|
||||||
private readonly int _actionCapacity;
|
private readonly int _actionCapacity;
|
||||||
private readonly Action _onRefresh;
|
private readonly Action _onRefresh;
|
||||||
private readonly Action<ResourceNode, Vector2> _drawActions;
|
private readonly Action<ResourceNode, Vector2> _drawActions;
|
||||||
private readonly HashSet<nint> _unfolded;
|
private readonly HashSet<nint> _unfolded;
|
||||||
|
|
||||||
|
private readonly Dictionary<nint, NodeVisibility> _filterCache;
|
||||||
|
|
||||||
private TreeCategory _categoryFilter;
|
private TreeCategory _categoryFilter;
|
||||||
private ChangedItemDrawer.ChangedItemIcon _typeFilter;
|
private ChangedItemDrawer.ChangedItemIcon _typeFilter;
|
||||||
private string _nameFilter;
|
private string _nameFilter;
|
||||||
|
private string _nodeFilter;
|
||||||
|
|
||||||
private Task<ResourceTree[]>? _task;
|
private Task<ResourceTree[]>? _task;
|
||||||
|
|
||||||
public ResourceTreeViewer(Configuration config, ResourceTreeFactory treeFactory, ChangedItemDrawer changedItemDrawer,
|
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;
|
_config = config;
|
||||||
_treeFactory = treeFactory;
|
_treeFactory = treeFactory;
|
||||||
_changedItemDrawer = changedItemDrawer;
|
_changedItemDrawer = changedItemDrawer;
|
||||||
|
_incognito = incognito;
|
||||||
_actionCapacity = actionCapacity;
|
_actionCapacity = actionCapacity;
|
||||||
_onRefresh = onRefresh;
|
_onRefresh = onRefresh;
|
||||||
_drawActions = drawActions;
|
_drawActions = drawActions;
|
||||||
_unfolded = new HashSet<nint>();
|
_unfolded = [];
|
||||||
|
|
||||||
|
_filterCache = [];
|
||||||
|
|
||||||
_categoryFilter = AllCategories;
|
_categoryFilter = AllCategories;
|
||||||
_typeFilter = ChangedItemDrawer.AllFlags;
|
_typeFilter = ChangedItemDrawer.AllFlags;
|
||||||
_nameFilter = string.Empty;
|
_nameFilter = string.Empty;
|
||||||
|
_nodeFilter = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
|
|
@ -74,7 +84,7 @@ public class ResourceTreeViewer
|
||||||
|
|
||||||
using (var c = ImRaii.PushColor(ImGuiCol.Text, CategoryColor(category).Value()))
|
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)
|
if (debugMode)
|
||||||
{
|
{
|
||||||
using var _ = ImRaii.PushFont(UiBuilder.MonoFont);
|
using var _ = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||||
|
|
@ -88,7 +98,7 @@ public class ResourceTreeViewer
|
||||||
|
|
||||||
using var id = ImRaii.PushId(index);
|
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,
|
using var table = ImRaii.Table("##ResourceTree", _actionCapacity > 0 ? 4 : 3,
|
||||||
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
|
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
|
||||||
|
|
@ -103,7 +113,7 @@ public class ResourceTreeViewer
|
||||||
(_actionCapacity - 1) * 3 * ImGuiHelpers.GlobalScale + _actionCapacity * ImGui.GetFrameHeight());
|
(_actionCapacity - 1) * 3 * ImGuiHelpers.GlobalScale + _actionCapacity * ImGui.GetFrameHeight());
|
||||||
ImGui.TableHeadersRow();
|
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);
|
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);
|
var fieldWidth = (ImGui.GetContentRegionAvail().X - checkSpacing * 2.0f - ImGui.GetFrameHeightWithSpacing()) / 2.0f;
|
||||||
ImGui.InputTextWithHint("##TreeNameFilter", "Filter by Character/Entity Name...", ref _nameFilter, 128);
|
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()
|
private Task<ResourceTree[]> RefreshCharacterList()
|
||||||
|
|
@ -153,33 +175,68 @@ public class ResourceTreeViewer
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
_filterCache.Clear();
|
||||||
_unfolded.Clear();
|
_unfolded.Clear();
|
||||||
_onRefresh();
|
_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 debugMode = _config.DebugMode;
|
||||||
var frameHeight = ImGui.GetFrameHeight();
|
var frameHeight = ImGui.GetFrameHeight();
|
||||||
var cellHeight = _actionCapacity > 0 ? frameHeight : 0.0f;
|
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)
|
if (node.Internal && !debugMode)
|
||||||
return NodeVisibility.Hidden;
|
return NodeVisibility.Hidden;
|
||||||
|
|
||||||
if (_typeFilter.HasFlag(node.Icon))
|
var filterIcon = node.Icon != 0 ? node.Icon : parentFilterIcon;
|
||||||
|
if (MatchesFilter(node, filterIcon))
|
||||||
return NodeVisibility.Visible;
|
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;
|
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())
|
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)
|
if (visibility == NodeVisibility.Hidden)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
@ -188,14 +245,14 @@ public class ResourceTreeViewer
|
||||||
|
|
||||||
using var mutedColor = ImRaii.PushColor(ImGuiCol.Text, textColorInternal, resourceNode.Internal);
|
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);
|
using var id = ImRaii.PushId(index);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
var unfolded = _unfolded.Contains(nodePathHash);
|
var unfolded = _unfolded.Contains(nodePathHash);
|
||||||
using (var indent = ImRaii.PushIndent(level))
|
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;
|
var unfoldable = hasVisibleChildren && visibility != NodeVisibility.DescendentsOnly;
|
||||||
if (unfoldable)
|
if (unfoldable)
|
||||||
{
|
{
|
||||||
|
|
@ -260,13 +317,13 @@ public class ResourceTreeViewer
|
||||||
ImGui.Selectable(resourceNode.FullPath.ToPath(), false, 0, new Vector2(ImGui.GetContentRegionAvail().X, cellHeight));
|
ImGui.Selectable(resourceNode.FullPath.ToPath(), false, 0, new Vector2(ImGui.GetContentRegionAvail().X, cellHeight));
|
||||||
if (ImGui.IsItemClicked())
|
if (ImGui.IsItemClicked())
|
||||||
ImGui.SetClipboardText(resourceNode.FullPath.ToPath());
|
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
|
else
|
||||||
{
|
{
|
||||||
ImGui.Selectable("(unavailable)", false, ImGuiSelectableFlags.Disabled,
|
ImGui.Selectable("(unavailable)", false, ImGuiSelectableFlags.Disabled,
|
||||||
new Vector2(ImGui.GetContentRegionAvail().X, cellHeight));
|
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();
|
mutedColor.Dispose();
|
||||||
|
|
@ -280,7 +337,7 @@ public class ResourceTreeViewer
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unfolded)
|
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;
|
return;
|
||||||
|
|
||||||
var typeFilter = _config.Ephemeral.ChangedItemFilter;
|
var typeFilter = _config.Ephemeral.ChangedItemFilter;
|
||||||
if (DrawTypeFilter(ref typeFilter, 0.0f))
|
if (DrawTypeFilter(ref typeFilter))
|
||||||
{
|
{
|
||||||
_config.Ephemeral.ChangedItemFilter = typeFilter;
|
_config.Ephemeral.ChangedItemFilter = typeFilter;
|
||||||
_config.Ephemeral.Save();
|
_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>
|
/// <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;
|
var ret = false;
|
||||||
using var _ = ImRaii.PushId("ChangedItemIconFilter");
|
using var _ = ImRaii.PushId("ChangedItemIconFilter");
|
||||||
|
|
@ -233,7 +233,6 @@ public class ChangedItemDrawer : IDisposable
|
||||||
var ret = false;
|
var ret = false;
|
||||||
var icon = _icons[type];
|
var icon = _icons[type];
|
||||||
var flag = typeFilter.HasFlag(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));
|
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))
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||||
{
|
{
|
||||||
|
|
@ -267,7 +266,7 @@ public class ChangedItemDrawer : IDisposable
|
||||||
ImGui.SameLine();
|
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,
|
ImGui.Image(_icons[AllFlags].ImGuiHandle, size, Vector2.Zero, Vector2.One,
|
||||||
typeFilter == 0 ? new Vector4(0.6f, 0.3f, 0.3f, 1f) :
|
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));
|
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 InheritanceUi _inheritanceUi;
|
||||||
private readonly ModStorage _mods;
|
private readonly ModStorage _mods;
|
||||||
private readonly FilenameService _fileNames;
|
private readonly FilenameService _fileNames;
|
||||||
|
private readonly IncognitoService _incognito;
|
||||||
private readonly IFontHandle _nameFont;
|
private readonly IFontHandle _nameFont;
|
||||||
|
|
||||||
private static readonly IReadOnlyDictionary<CollectionType, (string Name, uint Border)> Buttons = CreateButtons();
|
private static readonly IReadOnlyDictionary<CollectionType, (string Name, uint Border)> Buttons = CreateButtons();
|
||||||
|
|
@ -41,7 +42,8 @@ public sealed class CollectionPanel : IDisposable
|
||||||
private int _draggedIndividualAssignment = -1;
|
private int _draggedIndividualAssignment = -1;
|
||||||
|
|
||||||
public CollectionPanel(DalamudPluginInterface pi, CommunicatorService communicator, CollectionManager manager,
|
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;
|
_collections = manager.Storage;
|
||||||
_active = manager.Active;
|
_active = manager.Active;
|
||||||
|
|
@ -50,8 +52,9 @@ public sealed class CollectionPanel : IDisposable
|
||||||
_targets = targets;
|
_targets = targets;
|
||||||
_mods = mods;
|
_mods = mods;
|
||||||
_fileNames = fileNames;
|
_fileNames = fileNames;
|
||||||
|
_incognito = incognito;
|
||||||
_individualAssignmentUi = new IndividualAssignmentUi(communicator, actors, manager);
|
_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));
|
_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>
|
/// <summary> Respect incognito mode for names of identifiers. </summary>
|
||||||
private string Name(ActorIdentifier id, string? name)
|
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)
|
? id.Incognito(name)
|
||||||
: name ?? id.ToString();
|
: name ?? id.ToString();
|
||||||
|
|
||||||
|
|
@ -423,7 +426,7 @@ public sealed class CollectionPanel : IDisposable
|
||||||
private string Name(ModCollection? collection)
|
private string Name(ModCollection? collection)
|
||||||
=> collection == null ? "Unassigned" :
|
=> collection == null ? "Unassigned" :
|
||||||
collection == ModCollection.Empty ? "Use No Mods" :
|
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)
|
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 CollectionStorage _storage;
|
||||||
private readonly ActiveCollections _active;
|
private readonly ActiveCollections _active;
|
||||||
private readonly TutorialService _tutorial;
|
private readonly TutorialService _tutorial;
|
||||||
|
private readonly IncognitoService _incognito;
|
||||||
|
|
||||||
private ModCollection? _dragging;
|
private ModCollection? _dragging;
|
||||||
|
|
||||||
public bool IncognitoMode;
|
|
||||||
|
|
||||||
public CollectionSelector(Configuration config, CommunicatorService communicator, CollectionStorage storage, ActiveCollections active,
|
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)
|
: base([], Flags.Delete | Flags.Add | Flags.Duplicate | Flags.Filter)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
|
|
@ -31,6 +30,7 @@ public sealed class CollectionSelector : ItemSelector<ModCollection>, IDisposabl
|
||||||
_storage = storage;
|
_storage = storage;
|
||||||
_active = active;
|
_active = active;
|
||||||
_tutorial = tutorial;
|
_tutorial = tutorial;
|
||||||
|
_incognito = incognito;
|
||||||
|
|
||||||
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.CollectionSelector);
|
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.CollectionSelector);
|
||||||
// Set items.
|
// Set items.
|
||||||
|
|
@ -109,7 +109,7 @@ public sealed class CollectionSelector : ItemSelector<ModCollection>, IDisposabl
|
||||||
}
|
}
|
||||||
|
|
||||||
private string Name(ModCollection collection)
|
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)
|
private void OnCollectionChange(CollectionType type, ModCollection? old, ModCollection? @new, string _3)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ using Penumbra.UI.Classes;
|
||||||
|
|
||||||
namespace Penumbra.UI.CollectionTab;
|
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 int InheritedCollectionHeight = 9;
|
||||||
private const string InheritanceDragDropLabel = "##InheritanceMove";
|
private const string InheritanceDragDropLabel = "##InheritanceMove";
|
||||||
|
|
@ -312,5 +312,5 @@ public class InheritanceUi(CollectionManager collectionManager, CollectionSelect
|
||||||
}
|
}
|
||||||
|
|
||||||
private string Name(ModCollection collection)
|
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
|
: validName
|
||||||
? "Add a new option to this group."u8
|
? "Add a new option to this group."u8
|
||||||
: "Please enter a name for the new option."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.ModManager.OptionEditor.ImcEditor.AddOption(group, cache, name);
|
||||||
editor.NewOptionName = null;
|
editor.NewOptionName = null;
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ public readonly struct MultiModGroupEditDrawer(ModGroupEditDrawer editor, MultiM
|
||||||
var validName = name.Length > 0;
|
var validName = name.Length > 0;
|
||||||
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, validName
|
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, validName
|
||||||
? "Add a new option to this group."u8
|
? "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.ModManager.OptionEditor.MultiEditor.AddOption(group, name);
|
||||||
editor.NewOptionName = null;
|
editor.NewOptionName = null;
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ public readonly struct SingleModGroupEditDrawer(ModGroupEditDrawer editor, Singl
|
||||||
var validName = name.Length > 0;
|
var validName = name.Length > 0;
|
||||||
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, validName
|
if (ImUtf8.IconButton(FontAwesomeIcon.Plus, validName
|
||||||
? "Add a new option to this group."u8
|
? "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.ModManager.OptionEditor.SingleEditor.AddOption(group, name);
|
||||||
editor.NewOptionName = null;
|
editor.NewOptionName = null;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ public sealed class CollectionsTab : IDisposable, ITab
|
||||||
private readonly CollectionSelector _selector;
|
private readonly CollectionSelector _selector;
|
||||||
private readonly CollectionPanel _panel;
|
private readonly CollectionPanel _panel;
|
||||||
private readonly TutorialService _tutorial;
|
private readonly TutorialService _tutorial;
|
||||||
|
private readonly IncognitoService _incognito;
|
||||||
|
|
||||||
public enum PanelMode
|
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)
|
CollectionManager collectionManager, ModStorage modStorage, ActorManager actors, ITargetManager targets, TutorialService tutorial, FilenameService fileNames)
|
||||||
{
|
{
|
||||||
_config = configuration.Ephemeral;
|
_config = configuration.Ephemeral;
|
||||||
_tutorial = tutorial;
|
_tutorial = tutorial;
|
||||||
_selector = new CollectionSelector(configuration, communicator, collectionManager.Storage, collectionManager.Active, _tutorial);
|
_incognito = incognito;
|
||||||
_panel = new CollectionPanel(pi, communicator, collectionManager, _selector, actors, targets, modStorage, fileNames);
|
_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()
|
public void Dispose()
|
||||||
|
|
@ -116,18 +118,7 @@ public sealed class CollectionsTab : IDisposable, ITab
|
||||||
_tutorial.OpenTutorial(BasicTutorialSteps.CollectionDetails);
|
_tutorial.OpenTutorial(BasicTutorialSteps.CollectionDetails);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
_incognito.DrawToggle(withSpacing);
|
||||||
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.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawPanel()
|
private void DrawPanel()
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,15 @@
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using Penumbra.Interop.ResourceTree;
|
|
||||||
using Penumbra.UI.AdvancedWindow;
|
using Penumbra.UI.AdvancedWindow;
|
||||||
|
|
||||||
namespace Penumbra.UI.Tabs;
|
namespace Penumbra.UI.Tabs;
|
||||||
|
|
||||||
public class OnScreenTab : ITab
|
public class OnScreenTab : ITab
|
||||||
{
|
{
|
||||||
private readonly Configuration _config;
|
|
||||||
private readonly ResourceTreeViewer _viewer;
|
private readonly ResourceTreeViewer _viewer;
|
||||||
|
|
||||||
public OnScreenTab(Configuration config, ResourceTreeFactory treeFactory, ChangedItemDrawer changedItemDrawer)
|
public OnScreenTab(ResourceTreeViewerFactory resourceTreeViewerFactory)
|
||||||
{
|
{
|
||||||
_config = config;
|
_viewer = resourceTreeViewerFactory.Create(0, delegate { }, delegate { });
|
||||||
_viewer = new ResourceTreeViewer(_config, treeFactory, changedItemDrawer, 0, delegate { }, delegate { });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Label
|
public ReadOnlySpan<byte> Label
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue