mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-14 20:54:16 +01:00
ResourceTree: Add filtering to the UI
This commit is contained in:
parent
43c6b52d0b
commit
5e76ab3b84
4 changed files with 145 additions and 22 deletions
|
|
@ -9,6 +9,7 @@ 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;
|
||||
|
|
@ -49,6 +50,7 @@ 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;
|
||||
|
|
|
|||
|
|
@ -171,6 +171,9 @@ public class ResourceTreeFactory
|
|||
{
|
||||
if (node.Name == parent?.Name)
|
||||
node.Name = null;
|
||||
|
||||
if (parent != null)
|
||||
parent.DescendentIcons |= node.Icon | node.DescendentIcons;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ public class ResourceTreeViewer
|
|||
private readonly Action<ResourceNode, Vector2> _drawActions;
|
||||
private readonly HashSet<nint> _unfolded;
|
||||
|
||||
private TreeCategory _categoryFilter;
|
||||
private ChangedItemDrawer.ChangedItemIcon _typeFilter;
|
||||
private string _nameFilter;
|
||||
|
||||
private Task<ResourceTree[]>? _task;
|
||||
|
||||
public ResourceTreeViewer(Configuration config, ResourceTreeFactory treeFactory, ChangedItemDrawer changedItemDrawer,
|
||||
|
|
@ -35,6 +39,10 @@ public class ResourceTreeViewer
|
|||
_onRefresh = onRefresh;
|
||||
_drawActions = drawActions;
|
||||
_unfolded = new HashSet<nint>();
|
||||
|
||||
_categoryFilter = AllCategories;
|
||||
_typeFilter = ChangedItemDrawer.AllFlags;
|
||||
_nameFilter = string.Empty;
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
|
|
@ -42,6 +50,8 @@ public class ResourceTreeViewer
|
|||
if (ImGui.Button("Refresh Character List") || _task == null)
|
||||
_task = RefreshCharacterList();
|
||||
|
||||
DrawFilters();
|
||||
|
||||
using var child = ImRaii.Child("##Data");
|
||||
if (!child)
|
||||
return;
|
||||
|
|
@ -62,12 +72,11 @@ public class ResourceTreeViewer
|
|||
var debugMode = _config.DebugMode;
|
||||
foreach (var (tree, index) in _task.Result.WithIndex())
|
||||
{
|
||||
var headerColorId =
|
||||
tree.LocalPlayerRelated ? ColorId.ResTreeLocalPlayer :
|
||||
tree.PlayerRelated ? ColorId.ResTreePlayer :
|
||||
tree.Networked ? ColorId.ResTreeNetworked :
|
||||
ColorId.ResTreeNonNetworked;
|
||||
using (var c = ImRaii.PushColor(ImGuiCol.Text, headerColorId.Value()))
|
||||
var category = Classify(tree);
|
||||
if (!_categoryFilter.HasFlag(category) || !tree.Name.Contains(_nameFilter))
|
||||
continue;
|
||||
|
||||
using (var c = ImRaii.PushColor(ImGuiCol.Text, CategoryColor(category).Value()))
|
||||
{
|
||||
var isOpen = ImGui.CollapsingHeader($"{tree.Name}##{index}", index == 0 ? ImGuiTreeNodeFlags.DefaultOpen : 0);
|
||||
if (debugMode)
|
||||
|
|
@ -102,6 +111,34 @@ public class ResourceTreeViewer
|
|||
}
|
||||
}
|
||||
|
||||
private void DrawFilters()
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.Dummy(ImGuiHelpers.ScaledVector2(20, 0));
|
||||
|
||||
using (var id = ImRaii.PushId("TreeCategoryFilter"))
|
||||
{
|
||||
var spacing = ImGui.GetStyle().ItemInnerSpacing.X;
|
||||
var categoryFilter = (uint)_categoryFilter;
|
||||
foreach (var category in Enum.GetValues<TreeCategory>())
|
||||
{
|
||||
ImGui.SameLine(0.0f, spacing);
|
||||
using var c = ImRaii.PushColor(ImGuiCol.CheckMark, CategoryColor(category).Value());
|
||||
ImGui.CheckboxFlags($"##{category}", ref categoryFilter, (uint)category);
|
||||
ImGuiUtil.HoverTooltip(CategoryFilterDescription(category));
|
||||
}
|
||||
_categoryFilter = (TreeCategory)categoryFilter;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.Dummy(ImGuiHelpers.ScaledVector2(20, 0));
|
||||
|
||||
ImGui.SameLine();
|
||||
_changedItemDrawer.DrawTypeFilter(ref _typeFilter);
|
||||
|
||||
ImGui.InputTextWithHint("##TreeNameFilter", "Filter by Character/Entity Name...", ref _nameFilter, 128);
|
||||
}
|
||||
|
||||
private Task<ResourceTree[]> RefreshCharacterList()
|
||||
=> Task.Run(() =>
|
||||
{
|
||||
|
|
@ -120,12 +157,27 @@ public class ResourceTreeViewer
|
|||
|
||||
private void DrawNodes(IEnumerable<ResourceNode> resourceNodes, int level, nint pathHash)
|
||||
{
|
||||
|
||||
var debugMode = _config.DebugMode;
|
||||
var frameHeight = ImGui.GetFrameHeight();
|
||||
var cellHeight = _actionCapacity > 0 ? frameHeight : 0.0f;
|
||||
|
||||
NodeVisibility GetNodeVisibility(ResourceNode node)
|
||||
{
|
||||
if (node.Internal && !debugMode)
|
||||
return NodeVisibility.Hidden;
|
||||
|
||||
if (_typeFilter.HasFlag(node.Icon))
|
||||
return NodeVisibility.Visible;
|
||||
if ((_typeFilter & node.DescendentIcons) != 0)
|
||||
return NodeVisibility.DescendentsOnly;
|
||||
return NodeVisibility.Hidden;
|
||||
}
|
||||
|
||||
foreach (var (resourceNode, index) in resourceNodes.WithIndex())
|
||||
{
|
||||
if (resourceNode.Internal && !debugMode)
|
||||
var visibility = GetNodeVisibility(resourceNode);
|
||||
if (visibility == NodeVisibility.Hidden)
|
||||
continue;
|
||||
|
||||
var textColor = ImGui.GetColorU32(ImGuiCol.Text);
|
||||
|
|
@ -140,9 +192,8 @@ public class ResourceTreeViewer
|
|||
var unfolded = _unfolded.Contains(nodePathHash);
|
||||
using (var indent = ImRaii.PushIndent(level))
|
||||
{
|
||||
var unfoldable = debugMode
|
||||
? resourceNode.Children.Count > 0
|
||||
: resourceNode.Children.Any(child => !child.Internal);
|
||||
var hasVisibleChildren = resourceNode.Children.Any(child => GetNodeVisibility(child) != NodeVisibility.Hidden);
|
||||
var unfoldable = hasVisibleChildren && visibility != NodeVisibility.DescendentsOnly;
|
||||
if (unfoldable)
|
||||
{
|
||||
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||
|
|
@ -154,6 +205,11 @@ public class ResourceTreeViewer
|
|||
}
|
||||
else
|
||||
{
|
||||
if (hasVisibleChildren && !unfolded)
|
||||
{
|
||||
_unfolded.Add(nodePathHash);
|
||||
unfolded = true;
|
||||
}
|
||||
ImGui.Dummy(new Vector2(ImGui.GetFrameHeight()));
|
||||
ImGui.SameLine(0f, ImGui.GetStyle().ItemInnerSpacing.X);
|
||||
}
|
||||
|
|
@ -200,7 +256,7 @@ 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}\n\nClick to copy to clipboard.");
|
||||
ImGuiUtil.HoverTooltip($"{resourceNode.FullPath.ToPath()}\n\nClick to copy to clipboard.");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -223,4 +279,49 @@ public class ResourceTreeViewer
|
|||
DrawNodes(resourceNode.Children, level + 1, unchecked(nodePathHash * 31));
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum TreeCategory : uint
|
||||
{
|
||||
LocalPlayer = 1,
|
||||
Player = 2,
|
||||
Networked = 4,
|
||||
NonNetworked = 8,
|
||||
}
|
||||
|
||||
private const TreeCategory AllCategories = (TreeCategory)(((uint)TreeCategory.NonNetworked << 1) - 1);
|
||||
|
||||
private static TreeCategory Classify(ResourceTree tree)
|
||||
=> tree.LocalPlayerRelated ? TreeCategory.LocalPlayer :
|
||||
tree.PlayerRelated ? TreeCategory.Player :
|
||||
tree.Networked ? TreeCategory.Networked :
|
||||
TreeCategory.NonNetworked;
|
||||
|
||||
private static ColorId CategoryColor(TreeCategory category)
|
||||
=> category switch
|
||||
{
|
||||
TreeCategory.LocalPlayer => ColorId.ResTreeLocalPlayer,
|
||||
TreeCategory.Player => ColorId.ResTreePlayer,
|
||||
TreeCategory.Networked => ColorId.ResTreeNetworked,
|
||||
TreeCategory.NonNetworked => ColorId.ResTreeNonNetworked,
|
||||
_ => throw new ArgumentException(),
|
||||
};
|
||||
|
||||
private static string CategoryFilterDescription(TreeCategory category)
|
||||
=> category switch
|
||||
{
|
||||
TreeCategory.LocalPlayer => "Show you and what you own (mount, minion, accessory, pets and so on).",
|
||||
TreeCategory.Player => "Show other players and what they own.",
|
||||
TreeCategory.Networked => "Show non-player entities handled by the game server.",
|
||||
TreeCategory.NonNetworked => "Show non-player entities handled locally.",
|
||||
_ => throw new ArgumentException(),
|
||||
};
|
||||
|
||||
[Flags]
|
||||
private enum NodeVisibility : uint
|
||||
{
|
||||
Hidden = 0,
|
||||
Visible = 1,
|
||||
DescendentsOnly = 2,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,6 +145,18 @@ public class ChangedItemDrawer : IDisposable
|
|||
if (_config.HideChangedItemFilters)
|
||||
return;
|
||||
|
||||
var typeFilter = _config.Ephemeral.ChangedItemFilter;
|
||||
if (DrawTypeFilter(ref typeFilter))
|
||||
{
|
||||
_config.Ephemeral.ChangedItemFilter = typeFilter;
|
||||
_config.Ephemeral.Save();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Draw a header line with the different icon types to filter them. </summary>
|
||||
public bool DrawTypeFilter(ref ChangedItemIcon typeFilter)
|
||||
{
|
||||
var ret = false;
|
||||
using var _ = ImRaii.PushId("ChangedItemIconFilter");
|
||||
var size = new Vector2(2 * ImGui.GetTextLineHeight());
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||
|
|
@ -169,23 +181,24 @@ public class ChangedItemDrawer : IDisposable
|
|||
ChangedItemIcon.Unknown,
|
||||
};
|
||||
|
||||
void DrawIcon(ChangedItemIcon type)
|
||||
bool DrawIcon(ChangedItemIcon type, ref ChangedItemIcon typeFilter)
|
||||
{
|
||||
var ret = false;
|
||||
var icon = _icons[type];
|
||||
var flag = _config.Ephemeral.ChangedItemFilter.HasFlag(type);
|
||||
var flag = typeFilter.HasFlag(type);
|
||||
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))
|
||||
{
|
||||
_config.Ephemeral.ChangedItemFilter = flag ? _config.Ephemeral.ChangedItemFilter & ~type : _config.Ephemeral.ChangedItemFilter | type;
|
||||
_config.Ephemeral.Save();
|
||||
typeFilter = flag ? typeFilter & ~type : typeFilter | type;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
using var popup = ImRaii.ContextPopupItem(type.ToString());
|
||||
if (popup)
|
||||
if (ImGui.MenuItem("Enable Only This"))
|
||||
{
|
||||
_config.Ephemeral.ChangedItemFilter = type;
|
||||
_config.Ephemeral.Save();
|
||||
typeFilter = type;
|
||||
ret = true;
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
|
|
@ -196,23 +209,27 @@ public class ChangedItemDrawer : IDisposable
|
|||
ImGui.SameLine();
|
||||
ImGuiUtil.DrawTextButton(ToDescription(type), new Vector2(0, _smallestIconWidth), 0);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
foreach (var iconType in order)
|
||||
{
|
||||
DrawIcon(iconType);
|
||||
ret |= DrawIcon(iconType, ref typeFilter);
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
ImGui.SetCursorPosX(ImGui.GetContentRegionMax().X - size.X);
|
||||
ImGui.Image(_icons[AllFlags].ImGuiHandle, size, Vector2.Zero, Vector2.One,
|
||||
_config.Ephemeral.ChangedItemFilter == 0 ? new Vector4(0.6f, 0.3f, 0.3f, 1f) :
|
||||
_config.Ephemeral.ChangedItemFilter == AllFlags ? new Vector4(0.75f, 0.75f, 0.75f, 1f) : new Vector4(0.5f, 0.5f, 1f, 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));
|
||||
if (ImGui.IsItemClicked())
|
||||
{
|
||||
_config.Ephemeral.ChangedItemFilter = _config.Ephemeral.ChangedItemFilter == AllFlags ? 0 : AllFlags;
|
||||
_config.Ephemeral.Save();
|
||||
typeFilter = typeFilter == AllFlags ? 0 : AllFlags;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary> Obtain the icon category corresponding to a changed item. </summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue