Merge remote-tracking branch 'Exter-N/rt-filtering'

This commit is contained in:
Ottermandias 2023-11-29 17:47:58 +01:00
commit e0749bb791
6 changed files with 173 additions and 28 deletions

View file

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

View file

@ -171,6 +171,9 @@ public class ResourceTreeFactory
{
if (node.Name == parent?.Name)
node.Name = null;
if (parent != null)
parent.DescendentIcons |= node.Icon | node.DescendentIcons;
});
}

View file

@ -48,6 +48,8 @@ public partial class ModEditWindow
return;
}
if (DrawOptionSelectHeader())
_quickImportActions.Clear();
_quickImportViewer.Draw();
}

View file

@ -381,18 +381,25 @@ public partial class ModEditWindow : Window, IDisposable
}
}
private void DrawOptionSelectHeader()
private bool DrawOptionSelectHeader()
{
const string defaultOption = "Default Option";
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero).Push(ImGuiStyleVar.FrameRounding, 0);
var width = new Vector2(ImGui.GetContentRegionAvail().X / 3, 0);
var ret = false;
if (ImGuiUtil.DrawDisabledButton(defaultOption, width, "Switch to the default option for the mod.\nThis resets unsaved changes.",
_editor.Option!.IsDefault))
{
_editor.LoadOption(-1, 0);
ret = true;
}
ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton("Refresh Data", width, "Refresh data for the current option.\nThis resets unsaved changes.", false))
{
_editor.LoadMod(_editor.Mod!, _editor.GroupIdx, _editor.OptionIdx);
ret = true;
}
ImGui.SameLine();
ImGui.SetNextItemWidth(width.X);
@ -400,14 +407,19 @@ public partial class ModEditWindow : Window, IDisposable
using var color = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value());
using var combo = ImRaii.Combo("##optionSelector", _editor.Option.FullName);
if (!combo)
return;
return ret;
foreach (var (option, idx) in _mod!.AllSubMods.WithIndex())
{
using var id = ImRaii.PushId(idx);
if (ImGui.Selectable(option.FullName, option == _editor.Option))
{
_editor.LoadOption(option.GroupIdx, option.OptionIdx);
ret = true;
}
}
return ret;
}
private string _newSwapKey = string.Empty;

View file

@ -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,12 +39,16 @@ public class ResourceTreeViewer
_onRefresh = onRefresh;
_drawActions = drawActions;
_unfolded = new HashSet<nint>();
_categoryFilter = AllCategories;
_typeFilter = ChangedItemDrawer.AllFlags;
_nameFilter = string.Empty;
}
public void Draw()
{
if (ImGui.Button("Refresh Character List") || _task == null)
_task = RefreshCharacterList();
DrawControls();
_task ??= RefreshCharacterList();
using var child = ImRaii.Child("##Data");
if (!child)
@ -62,12 +70,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, StringComparison.OrdinalIgnoreCase))
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 +109,40 @@ public class ResourceTreeViewer
}
}
private void DrawControls()
{
var yOffset = (ChangedItemDrawer.TypeFilterIconSize.Y - ImGui.GetFrameHeight()) / 2f;
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + yOffset);
if (ImGui.Button("Refresh Character List"))
_task = RefreshCharacterList();
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, -yOffset);
ImGui.InputTextWithHint("##TreeNameFilter", "Filter by Character/Entity Name...", ref _nameFilter, 128);
}
private Task<ResourceTree[]> RefreshCharacterList()
=> Task.Run(() =>
{
@ -120,12 +161,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 +196,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 +209,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 +260,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 +283,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,
}
}

View file

@ -52,6 +52,9 @@ public class ChangedItemDrawer : IDisposable
private readonly Dictionary<ChangedItemIcon, IDalamudTextureWrap> _icons = new(16);
private float _smallestIconWidth;
public static Vector2 TypeFilterIconSize
=> new(2 * ImGui.GetTextLineHeight());
public ChangedItemDrawer(UiBuilder uiBuilder, IDataManager gameData, ITextureProvider textureProvider, CommunicatorService communicator,
Configuration config)
{
@ -145,8 +148,20 @@ public class ChangedItemDrawer : IDisposable
if (_config.HideChangedItemFilters)
return;
var typeFilter = _config.Ephemeral.ChangedItemFilter;
if (DrawTypeFilter(ref typeFilter, 0.0f))
{
_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, float yOffset)
{
var ret = false;
using var _ = ImRaii.PushId("ChangedItemIconFilter");
var size = new Vector2(2 * ImGui.GetTextLineHeight());
var size = TypeFilterIconSize;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
var order = new[]
{
@ -169,23 +184,25 @@ 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.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))
{
_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 +213,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.SetCursorPos(new(ImGui.GetContentRegionMax().X - size.X, ImGui.GetCursorPosY() + yOffset));
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>