Make Resource Trees honor Incognito Mode

This commit is contained in:
Exter-N 2024-05-30 23:09:26 +02:00
parent a6661f15e8
commit f4bdbcac53
13 changed files with 123 additions and 75 deletions

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())
@ -157,27 +157,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

@ -149,6 +149,7 @@ public static class StaticServiceManager
private static ServiceManager AddInterface(this ServiceManager services)
=> services.AddSingleton<FileDialogService>()
.AddSingleton<TutorialService>()
.AddSingleton<IncognitoService>()
.AddSingleton<PenumbraChangelog>()
.AddSingleton<LaunchButton>()
.AddSingleton<ConfigWindow>()
@ -181,6 +182,7 @@ public static class StaticServiceManager
.AddSingleton<ItemSwapTab>()
.AddSingleton<ModMergeTab>()
.AddSingleton<ChangedItemDrawer>()
.AddSingleton<ResourceTreeViewerFactory>()
.AddSingleton(p => new Diagnostics(p));
private static ServiceManager AddModEditor(this ServiceManager services)

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

@ -6,6 +6,7 @@ using OtterGui;
using Penumbra.Interop.ResourceTree;
using Penumbra.UI.Classes;
using Penumbra.String;
using Penumbra.UI.Tabs;
namespace Penumbra.UI.AdvancedWindow;
@ -17,6 +18,7 @@ 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;
@ -29,11 +31,12 @@ public class ResourceTreeViewer
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;
@ -75,7 +78,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);
@ -89,7 +92,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);
@ -137,10 +140,14 @@ public class ResourceTreeViewer
ImGui.SameLine(0, checkPadding);
_changedItemDrawer.DrawTypeFilter(ref _typeFilter, -yOffset);
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - yOffset);
using (ImRaii.Child("##typeFilter", new Vector2(ImGui.GetContentRegionAvail().X, ChangedItemDrawer.TypeFilterIconSize.Y)))
_changedItemDrawer.DrawTypeFilter(ref _typeFilter);
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - checkSpacing - ImGui.GetFrameHeightWithSpacing());
ImGui.InputTextWithHint("##TreeNameFilter", "Filter by Character/Entity Name...", ref _nameFilter, 128);
ImGui.SameLine(0, checkSpacing);
_incognito.DrawToggle();
}
private Task<ResourceTree[]> RefreshCharacterList()

View file

@ -0,0 +1,13 @@
using Penumbra.Interop.ResourceTree;
namespace Penumbra.UI.AdvancedWindow;
public class ResourceTreeViewerFactory(
Configuration config,
ResourceTreeFactory treeFactory,
ChangedItemDrawer changedItemDrawer,
IncognitoService incognito)
{
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.AnonymizedName : collection.Name;
=> _incognito.IncognitoMode ? 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,29 @@
using Dalamud.Interface.Utility;
using Dalamud.Interface;
using ImGuiNET;
using OtterGui;
using Penumbra.UI.Classes;
using OtterGui.Raii;
namespace Penumbra.UI;
public class IncognitoService(TutorialService tutorial)
{
public bool IncognitoMode;
public void DrawToggle(float? buttonWidth = null)
{
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value())
.Push(ImGuiCol.Border, ColorId.FolderExpanded.Value());
if (ImGuiUtil.DrawDisabledButton(
$"{(IncognitoMode ? FontAwesomeIcon.Eye : FontAwesomeIcon.EyeSlash).ToIconString()}###IncognitoMode",
new Vector2(buttonWidth ?? ImGui.GetFrameHeightWithSpacing(), ImGui.GetFrameHeight()), string.Empty, false, true))
IncognitoMode = !IncognitoMode;
var hovered = ImGui.IsItemHovered();
tutorial.OpenTutorial(BasicTutorialSteps.Incognito);
color.Pop(2);
if (hovered)
ImGui.SetTooltip(IncognitoMode ? "Toggle incognito mode off." : "Toggle incognito mode on.");
}
}

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