Merge branch 'restree-blurb'

This commit is contained in:
Ottermandias 2024-06-02 01:04:08 +02:00
commit 3b81dd89c8
21 changed files with 210 additions and 121 deletions

@ -1 +1 @@
Subproject commit 0b5afffda19d3e16aec9e8682d18c8f11f67f1c6
Subproject commit becacbca4f35595d16ff40dc9639cfa24be3461f

@ -1 +1 @@
Subproject commit 29b71cf7b3cc68995d38f0954fa38c4b9500a81d
Subproject commit fed687b536b7c709484db251b690b8821c5ef403

View file

@ -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));
}
}

View file

@ -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;
}

View file

@ -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)

View file

@ -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;

View file

@ -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)

View file

@ -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
{

View file

@ -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 };
}

View file

@ -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);
}
}

View 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);
}

View file

@ -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));

View file

@ -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)
{

View file

@ -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)
{

View file

@ -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;
}

View 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);
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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()

View file

@ -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