Update Filter things.

This commit is contained in:
Ottermandias 2026-01-16 21:03:03 +01:00
parent 27e059658d
commit 3b4cab2a1a
22 changed files with 809 additions and 402 deletions

View file

@ -22,6 +22,9 @@ public class Configuration : IPluginConfiguration, ISavable, IService
[JsonIgnore]
public readonly EphemeralConfig Ephemeral;
[JsonIgnore]
public readonly FilterConfig Filters;
[JsonIgnore]
public readonly UiConfig Ui;
@ -122,11 +125,12 @@ public class Configuration : IPluginConfiguration, ISavable, IService
/// Includes adding new colors and migrating from old versions.
/// </summary>
public Configuration(CharacterUtility utility, ConfigMigrationService migrator, SaveService saveService, EphemeralConfig ephemeral,
UiConfig ui)
UiConfig ui, FilterConfig filters)
{
_saveService = saveService;
Ephemeral = ephemeral;
Ui = ui;
Filters = filters;
Load(utility, migrator);
}

View file

@ -0,0 +1,60 @@
using Dalamud.Interface.ImGuiNotification;
using Luna;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.Services;
namespace Penumbra;
public abstract class ConfigurationFile(SaveService saveService, TimeSpan? saveDelay = null) : ISavable, IService
{
public abstract int CurrentVersion { get; }
[JsonIgnore]
protected readonly SaveService SaveService = saveService;
public virtual void Save()
=> SaveService.DelaySave(this, SaveDelay);
protected TimeSpan SaveDelay { get; set; } = saveDelay ?? TimeSpan.FromMinutes(1);
public virtual void Save(StreamWriter writer)
{
using var j = new JsonTextWriter(writer);
j.Formatting = Formatting.Indented;
j.WriteStartObject();
j.WritePropertyName("Version");
j.WriteValue(CurrentVersion);
AddData(j);
j.WriteEndObject();
}
protected abstract void AddData(JsonTextWriter j);
protected abstract void LoadData(JObject j);
public abstract string ToFilePath(FilenameService fileNames);
protected virtual void Load()
{
var fileName = ToFilePath(SaveService.FileNames);
var logName = ((ISavable)this).LogName(fileName);
if (!File.Exists(fileName))
return;
try
{
Penumbra.Log.Debug($"Reading {logName}...");
var text = File.ReadAllText(fileName);
var jObj = JObject.Parse(text);
if (jObj["Version"]?.Value<int>() != CurrentVersion)
throw new Exception("Unsupported version.");
LoadData(jObj);
}
catch (Exception ex)
{
Penumbra.Messager.NotificationMessage(ex, $"Error reading {logName}, reverting to default.",
$"Error reading {logName}", NotificationType.Error);
}
}
}

View file

@ -30,7 +30,6 @@ public class EphemeralConfig : ISavable, IService
public RecordType ResourceWatcherRecordTypes { get; set; } = ResourceWatcher.AllRecords;
public CollectionPanelMode CollectionPanel { get; set; } = CollectionPanelMode.SimpleAssignment;
public TabType SelectedTab { get; set; } = TabType.Settings;
public ChangedItemIconFlag ChangedItemFilter { get; set; } = ChangedItemFlagExtensions.DefaultFlags;
public bool FixMainWindow { get; set; } = false;
public HashSet<string> AdvancedEditingOpenForModPaths { get; set; } = [];
public bool ForceRedrawOnFileChange { get; set; } = false;

View file

@ -0,0 +1,467 @@
using Luna.Generators;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.Enums;
using Penumbra.Services;
using Penumbra.UI.Classes;
using Penumbra.UI.ModsTab.Selector;
using Penumbra.UI.ResourceWatcher;
namespace Penumbra;
public sealed partial class FilterConfig : ConfigurationFile
{
public override int CurrentVersion
=> 1;
public FilterConfig(SaveService saveService)
: base(saveService)
{
Load();
}
protected override void AddData(JsonTextWriter j)
{
WriteModsTab(j);
WriteCollectionsTab(j);
WriteChangedItemsTab(j);
WriteEffectiveChangesTab(j);
WriteOnScreenTab(j);
WriteResourceManagerTab(j);
WriteResourceWatcherTab(j);
}
protected override void LoadData(JObject j)
{
LoadModsTab(j);
LoadCollectionsTab(j);
LoadChangedItemsTab(j);
LoadEffectiveChangesTab(j);
LoadOnScreenTab(j);
LoadResourceManagerTab(j);
LoadResourceWatcherTab(j);
}
public override string ToFilePath(FilenameService fileNames)
=> fileNames.FilterFile;
#region Mods Tab
[ConfigProperty]
private ModTypeFilter _modTypeFilter = ModTypeFilterExtensions.UnfilteredStateMods;
[ConfigProperty]
private string _modFilter = string.Empty;
[ConfigProperty]
private ChangedItemIconFlag _modChangedItemTypeFilter = ChangedItemFlagExtensions.DefaultFlags;
private void WriteModsTab(JsonTextWriter j)
{
if (ModTypeFilter is ModTypeFilterExtensions.UnfilteredStateMods
&& ModFilter.Length is 0
&& ModChangedItemTypeFilter is ChangedItemFlagExtensions.DefaultFlags)
return;
j.WritePropertyName("Mods");
j.WriteStartObject();
if (ModTypeFilter is not ModTypeFilterExtensions.UnfilteredStateMods)
{
j.WritePropertyName("TypeFilter");
j.WriteValue((uint)ModTypeFilter);
}
if (ModFilter.Length > 0)
{
j.WritePropertyName("ModFilter");
j.WriteValue(ModFilter);
}
if (ModChangedItemTypeFilter is not ChangedItemFlagExtensions.DefaultFlags)
{
j.WritePropertyName("ChangedItemTypeFilter");
j.WriteValue((uint)ModChangedItemTypeFilter);
}
j.WriteEndObject();
}
private void LoadModsTab(JObject j)
{
if (j["Mods"] is not JObject mods)
return;
_modTypeFilter = mods["TypeFilter"]?.Value<uint>() is { } modTypeFilter
? (ModTypeFilter)modTypeFilter
: ModTypeFilterExtensions.UnfilteredStateMods;
_modFilter = mods["ModFilter"]?.Value<string>() ?? string.Empty;
_modChangedItemTypeFilter = mods["ChangedItemTypeFilter"]?.Value<uint>() is { } changedItemFilter
? (ChangedItemIconFlag)changedItemFilter
: ChangedItemFlagExtensions.DefaultFlags;
}
#endregion
#region Collections Tab
[ConfigProperty]
private string _collectionFilter = string.Empty;
private void WriteCollectionsTab(JsonTextWriter j)
{
if (CollectionFilter.Length is 0)
return;
j.WritePropertyName("Collections");
j.WriteStartObject();
if (CollectionFilter.Length > 0)
{
j.WritePropertyName("CollectionFilter");
j.WriteValue(CollectionFilter);
}
j.WriteEndObject();
}
private void LoadCollectionsTab(JObject j)
{
if (j["Collections"] is JObject collections)
_collectionFilter = collections["CollectionFilter"]?.Value<string>() ?? string.Empty;
}
#endregion
#region Changed Items Tab
// Changed Items tab
[ConfigProperty]
private string _changedItemItemFilter = string.Empty;
[ConfigProperty]
private string _changedItemModFilter = string.Empty;
[ConfigProperty]
private ChangedItemIconFlag _changedItemTypeFilter = ChangedItemFlagExtensions.DefaultFlags;
private void WriteChangedItemsTab(JsonTextWriter j)
{
if (ChangedItemItemFilter.Length is 0
&& ChangedItemModFilter.Length is 0
&& ChangedItemTypeFilter is ChangedItemFlagExtensions.DefaultFlags)
return;
j.WritePropertyName("ChangedItems");
j.WriteStartObject();
if (ChangedItemItemFilter.Length > 0)
{
j.WritePropertyName("ItemFilter");
j.WriteValue(ChangedItemItemFilter);
}
if (ChangedItemModFilter.Length > 0)
{
j.WritePropertyName("ModFilter");
j.WriteValue(ChangedItemModFilter);
}
if (ChangedItemTypeFilter is not ChangedItemFlagExtensions.DefaultFlags)
{
j.WritePropertyName("TypeFilter");
j.WriteValue((uint)ChangedItemTypeFilter);
}
j.WriteEndObject();
}
private void LoadChangedItemsTab(JObject j)
{
if (j["ChangedItems"] is not JObject changedItems)
return;
_changedItemItemFilter = changedItems["ItemFilter"]?.Value<string>() ?? string.Empty;
_changedItemModFilter = changedItems["ModFilter"]?.Value<string>() ?? string.Empty;
_changedItemTypeFilter = changedItems["TypeFilter"]?.Value<uint>() is { } typeFilter
? (ChangedItemIconFlag)typeFilter
: ChangedItemFlagExtensions.DefaultFlags;
}
#endregion
#region Effective Changes tab
[ConfigProperty]
private string _effectiveChangesGamePathFilter = string.Empty;
[ConfigProperty]
private string _effectiveChangesFilePathFilter = string.Empty;
private void WriteEffectiveChangesTab(JsonTextWriter j)
{
if (EffectiveChangesGamePathFilter.Length is 0 && EffectiveChangesFilePathFilter.Length is 0)
return;
j.WritePropertyName("EffectiveChanges");
j.WriteStartObject();
if (EffectiveChangesGamePathFilter.Length > 0)
{
j.WritePropertyName("GamePathFilter");
j.WriteValue(EffectiveChangesGamePathFilter);
}
if (EffectiveChangesFilePathFilter.Length > 0)
{
j.WritePropertyName("FilePathFilter");
j.WriteValue(EffectiveChangesFilePathFilter);
}
j.WriteEndObject();
}
private void LoadEffectiveChangesTab(JObject j)
{
if (j["EffectiveChanges"] is not JObject effectiveChanges)
return;
_effectiveChangesGamePathFilter = effectiveChanges["GamePathFilter"]?.Value<string>() ?? string.Empty;
_effectiveChangesFilePathFilter = effectiveChanges["FilePathFilter"]?.Value<string>() ?? string.Empty;
}
#endregion
#region On-Screen tab
[ConfigProperty]
private string _onScreenCharacterFilter = string.Empty;
[ConfigProperty]
private string _onScreenItemFilter = string.Empty;
[ConfigProperty]
private ChangedItemIconFlag _onScreenTypeFilter = ChangedItemFlagExtensions.DefaultFlags;
private void WriteOnScreenTab(JsonTextWriter j)
{
if (OnScreenCharacterFilter.Length is 0
&& OnScreenItemFilter.Length is 0
&& OnScreenTypeFilter is ChangedItemFlagExtensions.DefaultFlags)
return;
j.WritePropertyName("OnScreen");
j.WriteStartObject();
if (OnScreenCharacterFilter.Length > 0)
{
j.WritePropertyName("CharacterFilter");
j.WriteValue(OnScreenCharacterFilter);
}
if (OnScreenItemFilter.Length > 0)
{
j.WritePropertyName("ItemFilter");
j.WriteValue(OnScreenItemFilter);
}
if (OnScreenTypeFilter is not ChangedItemFlagExtensions.DefaultFlags)
{
j.WritePropertyName("TypeFilter");
j.WriteValue((uint)OnScreenTypeFilter);
}
j.WriteEndObject();
}
private void LoadOnScreenTab(JObject j)
{
if (j["OnScreen"] is not JObject onScreen)
return;
_onScreenCharacterFilter = onScreen["CharacterFilter"]?.Value<string>() ?? string.Empty;
_onScreenItemFilter = onScreen["ItemFilter"]?.Value<string>() ?? string.Empty;
_onScreenTypeFilter = onScreen["TypeFilter"]?.Value<uint>() is { } typeFilter
? (ChangedItemIconFlag)typeFilter
: ChangedItemFlagExtensions.DefaultFlags;
}
#endregion
#region Resource Manager tab
[ConfigProperty]
private string _resourceManagerFilter = string.Empty;
private void WriteResourceManagerTab(JsonTextWriter j)
{
if (ResourceManagerFilter.Length is 0)
return;
j.WritePropertyName("ResourceManager");
j.WriteStartObject();
if (ResourceManagerFilter.Length > 0)
{
j.WritePropertyName("PathFilter");
j.WriteValue(ResourceManagerFilter);
}
j.WriteEndObject();
}
private void LoadResourceManagerTab(JObject j)
{
if (j["ResourceManager"] is JObject resourceManager)
_resourceManagerFilter = resourceManager["PathFilter"]?.Value<string>() ?? string.Empty;
}
#endregion
#region
[ConfigProperty]
private bool _resourceLoggerEnabled = false;
[ConfigProperty]
private int _resourceLoggerMaxEntries = 500;
[ConfigProperty]
private bool _resourceLoggerStoreOnlyMatching = true;
[ConfigProperty]
private bool _resourceLoggerWriteToLog = false;
[ConfigProperty]
private string _resourceLoggerLogFilter = string.Empty;
[ConfigProperty]
private string _resourceLoggerPathFilter = string.Empty;
[ConfigProperty]
private string _resourceLoggerCollectionFilter = string.Empty;
[ConfigProperty]
private string _resourceLoggerObjectFilter = string.Empty;
[ConfigProperty]
private string _resourceLoggerOriginalPathFilter = string.Empty;
[ConfigProperty]
private string _resourceLoggerResourceFilter = string.Empty;
[ConfigProperty]
private string _resourceLoggerCrcFilter = string.Empty;
[ConfigProperty]
private string _resourceLoggerRefFilter = string.Empty;
[ConfigProperty]
private string _resourceLoggerThreadFilter = string.Empty;
[ConfigProperty]
private RecordType _resourceLoggerRecordFilter = RecordTypeExtensions.All;
[ConfigProperty]
private BoolEnum _resourceLoggerCustomFilter = BoolEnumExtensions.All;
[ConfigProperty]
private BoolEnum _resourceLoggerSyncFilter = BoolEnumExtensions.All;
[ConfigProperty]
private ResourceCategoryFlag _resourceLoggerCategoryFilter = ResourceExtensions.AllResourceCategories;
[ConfigProperty]
private ResourceTypeFlag _resourceLoggerTypeFilter = ResourceExtensions.AllResourceTypes;
[ConfigProperty]
private LoadStateFlag _resourceLoggerLoadStateFilter = LoadStateExtensions.All;
private void WriteResourceWatcherTab(JsonTextWriter j)
{
var jObj = new JObject();
if (ResourceLoggerEnabled)
jObj["Enabled"] = true;
if (ResourceLoggerWriteToLog)
jObj["WriteToLog"] = true;
if (ResourceLoggerMaxEntries is not 500)
jObj["MaxEntries"] = ResourceLoggerMaxEntries;
if (!ResourceLoggerStoreOnlyMatching)
jObj["StoreOnlyMatching"] = false;
if (ResourceLoggerLogFilter.Length > 0)
jObj["LogFilter"] = ResourceLoggerLogFilter;
if (ResourceLoggerPathFilter.Length > 0)
jObj["PathFilter"] = ResourceLoggerPathFilter;
if (ResourceLoggerCollectionFilter.Length > 0)
jObj["CollectionFilter"] = ResourceLoggerCollectionFilter;
if (ResourceLoggerObjectFilter.Length > 0)
jObj["ObjectFilter"] = ResourceLoggerObjectFilter;
if (ResourceLoggerOriginalPathFilter.Length > 0)
jObj["OriginalPathFilter"] = ResourceLoggerOriginalPathFilter;
if (ResourceLoggerResourceFilter.Length > 0)
jObj["ResourceFilter"] = ResourceLoggerResourceFilter;
if (ResourceLoggerCrcFilter.Length > 0)
jObj["CrcFilter"] = ResourceLoggerCrcFilter;
if (ResourceLoggerRefFilter.Length > 0)
jObj["RefFilter"] = ResourceLoggerRefFilter;
if (ResourceLoggerThreadFilter.Length > 0)
jObj["ThreadFilter"] = ResourceLoggerThreadFilter;
if (ResourceLoggerRecordFilter is not RecordTypeExtensions.All)
jObj["RecordFilter"] = (uint)ResourceLoggerRecordFilter;
if (ResourceLoggerCustomFilter is not BoolEnumExtensions.All)
jObj["CustomFilter"] = (uint)ResourceLoggerCustomFilter;
if (ResourceLoggerSyncFilter is not BoolEnumExtensions.All)
jObj["SyncFilter"] = (uint)ResourceLoggerSyncFilter;
if (ResourceLoggerCategoryFilter != ResourceExtensions.AllResourceCategories)
jObj["CategoryFilter"] = (uint)ResourceLoggerCategoryFilter;
if (ResourceLoggerTypeFilter != ResourceExtensions.AllResourceTypes)
jObj["TypeFilter"] = (uint)ResourceLoggerTypeFilter;
if (ResourceLoggerLoadStateFilter is not LoadStateExtensions.All)
jObj["LoadStateFilter"] = (uint)ResourceLoggerLoadStateFilter;
if (jObj.Count is not 0)
{
j.WritePropertyName("ResourceWatcher");
jObj.WriteTo(j);
}
}
private void LoadResourceWatcherTab(JObject j)
{
if (j["ResourceWatcher"] is not JObject resourceWatcher)
return;
_resourceLoggerEnabled = resourceWatcher["Enabled"]?.Value<bool>() ?? false;
_resourceLoggerMaxEntries = resourceWatcher["MaxEntries"]?.Value<int>() ?? 500;
_resourceLoggerStoreOnlyMatching = resourceWatcher["StoreOnlyMatching"]?.Value<bool>() ?? true;
_resourceLoggerWriteToLog = resourceWatcher["WriteToLog"]?.Value<bool>() ?? false;
_resourceLoggerLogFilter = resourceWatcher["LogFilter"]?.Value<string>() ?? string.Empty;
_resourceLoggerPathFilter = resourceWatcher["PathFilter"]?.Value<string>() ?? string.Empty;
_resourceLoggerCollectionFilter = resourceWatcher["CollectionFilter"]?.Value<string>() ?? string.Empty;
_resourceLoggerObjectFilter = resourceWatcher["ObjectFilter"]?.Value<string>() ?? string.Empty;
_resourceLoggerOriginalPathFilter = resourceWatcher["OriginalPathFilter"]?.Value<string>() ?? string.Empty;
_resourceLoggerResourceFilter = resourceWatcher["ResourceFilter"]?.Value<string>() ?? string.Empty;
_resourceLoggerCrcFilter = resourceWatcher["CrcFilter"]?.Value<string>() ?? string.Empty;
_resourceLoggerRefFilter = resourceWatcher["RefFilter"]?.Value<string>() ?? string.Empty;
_resourceLoggerThreadFilter = resourceWatcher["ThreadFilter"]?.Value<string>() ?? string.Empty;
_resourceLoggerRecordFilter = resourceWatcher["RecordFilter"]?.Value<uint>() is { } recordFilter
? (RecordType)recordFilter
: RecordTypeExtensions.All;
_resourceLoggerCustomFilter = resourceWatcher["CustomFilter"]?.Value<uint>() is { } customFilter
? (BoolEnum)customFilter
: BoolEnumExtensions.All;
_resourceLoggerSyncFilter = resourceWatcher["SyncFilter"]?.Value<uint>() is { } syncFilter
? (BoolEnum)syncFilter
: BoolEnumExtensions.All;
_resourceLoggerCategoryFilter = resourceWatcher["CategoryFilter"]?.Value<uint>() is { } categoryFilter
? (ResourceCategoryFlag)categoryFilter
: ResourceExtensions.AllResourceCategories;
_resourceLoggerTypeFilter = resourceWatcher["TypeFilter"]?.Value<uint>() is { } typeFilter
? (ResourceTypeFlag)typeFilter
: ResourceExtensions.AllResourceTypes;
_resourceLoggerLoadStateFilter = resourceWatcher["LoadStateFilter"]?.Value<uint>() is { } loadStateFilter
? (LoadStateFlag)loadStateFilter
: LoadStateExtensions.All;
}
#endregion
}

View file

@ -1,73 +1,27 @@
using Dalamud.Interface.ImGuiNotification;
using Luna;
using Luna.Generators;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Penumbra.Services;
namespace Penumbra;
public class UiConfig : ISavable, IService
public sealed partial class UiConfig : ConfigurationFile
{
public const int CurrentVersion = 1;
[JsonIgnore]
private readonly SaveService _saveService;
public UiConfig(SaveService saveService)
: base(saveService, TimeSpan.FromMinutes(5))
{
_saveService = saveService;
Load();
}
private TwoPanelWidth _collectionsTabScale = new(0.25f, ScalingMode.Percentage);
public TwoPanelWidth CollectionTabScale
protected override void AddData(JsonTextWriter j)
{
get => _collectionsTabScale;
set
{
if (value == _collectionsTabScale)
return;
_collectionsTabScale = value;
Save();
}
}
private TwoPanelWidth _modTabScale = new(0.3f, ScalingMode.Percentage);
public TwoPanelWidth ModTabScale
{
get => _modTabScale;
set
{
if (value == _modTabScale)
return;
_modTabScale = value;
Save();
}
}
public string ToFilePath(FilenameService fileNames)
=> fileNames.UiConfigFile;
public void Save()
=> _saveService.DelaySave(this);
public void Save(StreamWriter writer)
{
using var j = new JsonTextWriter(writer);
j.Formatting = Formatting.Indented;
j.WriteStartObject();
j.WritePropertyName("Version");
j.WriteValue(CurrentVersion);
j.WritePropertyName("CollectionsTab");
j.WriteStartObject();
j.WritePropertyName("Mode");
j.WriteValue(CollectionTabScale.Mode.ToString());
j.WriteValue(CollectionsTabScale.Mode.ToString());
j.WritePropertyName("Width");
j.WriteValue(CollectionTabScale.Width);
j.WriteValue(CollectionsTabScale.Width);
j.WriteEndObject();
j.WritePropertyName("ModsTab");
j.WriteStartObject();
@ -76,33 +30,27 @@ public class UiConfig : ISavable, IService
j.WritePropertyName("Width");
j.WriteValue(ModTabScale.Width);
j.WriteEndObject();
j.WriteEndObject();
}
private void Load()
protected override void LoadData(JObject j)
{
if (!File.Exists(_saveService.FileNames.UiConfigFile))
return;
if (j["CollectionsTab"] is JObject collections)
_collectionsTabScale = new TwoPanelWidth(collections["Width"].ValueOr(float.NaN),
collections["Mode"].TextEnum(ScalingMode.Percentage));
try
{
var text = File.ReadAllText(_saveService.FileNames.UiConfigFile);
var jObj = JObject.Parse(text);
if (jObj["Version"]?.Value<int>() is not CurrentVersion)
throw new Exception("Unsupported version.");
if (jObj["CollectionsTab"] is JObject collections)
_collectionsTabScale = new TwoPanelWidth(collections["Width"].ValueOr(float.NaN),
collections["Mode"].TextEnum(ScalingMode.Percentage));
if (jObj["ModsTab"] is JObject mods)
_modTabScale = new TwoPanelWidth(mods["Width"].ValueOr(float.NaN), mods["Mode"].TextEnum(ScalingMode.Percentage));
}
catch (Exception ex)
{
Penumbra.Messager.NotificationMessage(ex,
"Error reading UI Configuration, reverting to default.",
"Error reading UI Configuration", NotificationType.Error);
}
if (j["ModsTab"] is JObject mods)
_modTabScale = new TwoPanelWidth(mods["Width"].ValueOr(float.NaN), mods["Mode"].TextEnum(ScalingMode.Percentage));
}
public override int CurrentVersion
=> 1;
public override string ToFilePath(FilenameService fileNames)
=> fileNames.UiConfigFile;
[ConfigProperty]
private TwoPanelWidth _collectionsTabScale = new(0.25f, ScalingMode.Percentage);
[ConfigProperty]
private TwoPanelWidth _modTabScale = new(0.3f, ScalingMode.Percentage);
}

View file

@ -8,7 +8,6 @@ using Penumbra.Interop.Services;
using Penumbra.Mods.Editor;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Settings;
using Penumbra.UI;
using Penumbra.UI.Classes;
using Penumbra.UI.ResourceWatcher;
using Penumbra.UI.Tabs;
@ -126,8 +125,8 @@ public class ConfigMigrationService(SaveService saveService, BackupService backu
_data["ResourceWatcherRecordTypes"]?.ToObject<RecordType>() ?? _config.Ephemeral.ResourceWatcherRecordTypes;
_config.Ephemeral.CollectionPanel = _data["CollectionPanel"]?.ToObject<CollectionPanelMode>() ?? _config.Ephemeral.CollectionPanel;
_config.Ephemeral.SelectedTab = _data["SelectedTab"]?.ToObject<TabType>() ?? _config.Ephemeral.SelectedTab;
_config.Ephemeral.ChangedItemFilter = _data["ChangedItemFilter"]?.ToObject<ChangedItemIconFlag>()
?? _config.Ephemeral.ChangedItemFilter;
_config.Filters.ChangedItemTypeFilter = _data["ChangedItemFilter"]?.ToObject<ChangedItemIconFlag>()
?? _config.Filters.ChangedItemTypeFilter;
_config.Ephemeral.FixMainWindow = _data["FixMainWindow"]?.ToObject<bool>() ?? _config.Ephemeral.FixMainWindow;
_config.Ephemeral.Save();
}

View file

@ -11,6 +11,7 @@ public sealed class FilenameService(IDalamudPluginInterface pi) : BaseFilePathPr
public readonly string LocalDataDirectory = Path.Combine(pi.ConfigDirectory.FullName, "mod_data");
public readonly string EphemeralConfigFile = Path.Combine(pi.ConfigDirectory.FullName, "ephemeral_config.json");
public readonly string UiConfigFile = Path.Combine(pi.ConfigDirectory.FullName, "ui_config.json");
public readonly string FilterFile = Path.Combine(pi.ConfigDirectory.FullName, "filters.json");
public readonly string OldFilesystemFile = Path.Combine(pi.ConfigDirectory.FullName, "sort_order.json");
public readonly string ActiveCollectionsFile = Path.Combine(pi.ConfigDirectory.FullName, "active_collections.json");
public readonly string PredefinedTagFile = Path.Combine(pi.ConfigDirectory.FullName, "predefined_tags.json");

View file

@ -39,11 +39,9 @@ public class ResourceTreeViewer(
private readonly Dictionary<nint, NodeVisibility> _filterCache = [];
private readonly Dictionary<FullPath, IWritable?> _writableCache = [];
private TreeCategory _categoryFilter = AllCategories;
private ChangedItemIconFlag _typeFilter = ChangedItemFlagExtensions.AllFlags;
private string _nameFilter = string.Empty;
private string _nodeFilter = string.Empty;
private string _note = string.Empty;
private TreeCategory _categoryFilter = AllCategories;
private string _note = string.Empty;
private Task<ResourceTree[]>? _task;
@ -73,7 +71,7 @@ public class ResourceTreeViewer(
foreach (var (index, tree) in _task.Result.Index())
{
var category = Classify(tree);
if (!_categoryFilter.HasFlag(category) || !tree.Name.Contains(_nameFilter, StringComparison.OrdinalIgnoreCase))
if (!_categoryFilter.HasFlag(category) || !tree.Name.Contains(config.Filters.OnScreenCharacterFilter, StringComparison.OrdinalIgnoreCase))
continue;
using (ImGuiColor.Text.Push(CategoryColor(category).Value()))
@ -198,15 +196,31 @@ public class ResourceTreeViewer(
Im.Cursor.Y -= yOffset;
using (Im.Child.Begin("##typeFilter"u8, new Vector2(Im.ContentRegion.Available.X, ChangedItemDrawer.TypeFilterIconSize.Y)))
{
filterChanged |= changedItemDrawer.DrawTypeFilter(ref _typeFilter);
if (changedItemDrawer.DrawTypeFilter(config.Filters.OnScreenTypeFilter, out var newTypeFilter))
{
filterChanged = true;
config.Filters.OnScreenTypeFilter = newTypeFilter;
}
}
var fieldWidth = (Im.ContentRegion.Available.X - checkSpacing * 2.0f - Im.Style.FrameHeightWithSpacing) / 2.0f;
Im.Item.SetNextWidth(fieldWidth);
filterChanged |= Im.Input.Text("##TreeNameFilter"u8, ref _nameFilter, "Filter by Character/Entity Name..."u8);
var filter = config.Filters.OnScreenCharacterFilter;
if (Im.Input.Text("##TreeNameFilter"u8, ref filter, "Filter by Character/Entity Name..."u8))
{
filterChanged = true;
config.Filters.OnScreenCharacterFilter = filter;
}
Im.Line.Same(0, checkSpacing);
Im.Item.SetNextWidth(fieldWidth);
filterChanged |= Im.Input.Text("##NodeFilter"u8, ref _nodeFilter, "Filter by Item/Part Name or Path..."u8);
filter = config.Filters.OnScreenItemFilter;
if (Im.Input.Text("##NodeFilter"u8, ref filter, "Filter by Item/Part Name or Path..."u8))
{
filterChanged = true;
config.Filters.OnScreenItemFilter = filter;
}
Im.Line.Same(0, checkSpacing);
incognito.DrawToggle(Im.Style.FrameHeightWithSpacing);
@ -353,7 +367,9 @@ public class ResourceTreeViewer(
if (hasMod && Im.Item.RightClicked() && Im.Io.KeyControl)
communicator.SelectTab.Invoke(new SelectTab.Arguments(TabType.Mods, mod));
Im.Tooltip.OnHover(default, $"{resourceNode.FullPath.ToPath()}\n\nClick to copy to clipboard.{(hasMod ? "\nControl + Right-Click to jump to mod." : string.Empty)}{GetAdditionalDataSuffix(resourceNode.AdditionalData)}", true);
Im.Tooltip.OnHover(default,
$"{resourceNode.FullPath.ToPath()}\n\nClick to copy to clipboard.{(hasMod ? "\nControl + Right-Click to jump to mod." : string.Empty)}{GetAdditionalDataSuffix(resourceNode.AdditionalData)}",
true);
}
else
{
@ -411,16 +427,17 @@ public class ResourceTreeViewer(
bool MatchesFilter(ResourceNode node, ChangedItemIconFlag filterIcon)
{
if (!_typeFilter.HasFlag(filterIcon))
if (!config.Filters.OnScreenTypeFilter.HasFlag(filterIcon))
return false;
if (_nodeFilter.Length == 0)
if (config.Filters.OnScreenItemFilter.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));
return node.Name != null && node.Name.Contains(config.Filters.OnScreenItemFilter, StringComparison.OrdinalIgnoreCase)
|| node.FullPath.FullName.Contains(config.Filters.OnScreenItemFilter, StringComparison.OrdinalIgnoreCase)
|| node.FullPath.InternalName.ToString().Contains(config.Filters.OnScreenItemFilter, StringComparison.OrdinalIgnoreCase)
|| Array.Exists(node.PossibleGamePaths,
path => path.Path.ToString().Contains(config.Filters.OnScreenItemFilter, StringComparison.OrdinalIgnoreCase));
}
void DrawActions(ResourceNode resourceNode, Vector2 buttonSize)
@ -489,7 +506,9 @@ public class ResourceTreeViewer(
private static void HeaderInteraction(ResourceTree tree)
{
Im.Tooltip.OnHover(default, $"Object Index: {tree.GameObjectIndex}\nObject Address: 0x{tree.GameObjectAddress:X16}\nDraw Object Address: 0x{tree.DrawObjectAddress:X16}", true, Im.Font.Mono);
Im.Tooltip.OnHover(default,
$"Object Index: {tree.GameObjectIndex}\nObject Address: 0x{tree.GameObjectAddress:X16}\nDraw Object Address: 0x{tree.DrawObjectAddress:X16}",
true, Im.Font.Mono);
if (tree.GameObjectAddress == nint.Zero)
return;
@ -513,7 +532,8 @@ public class ResourceTreeViewer(
private static void ResourceInteraction(ResourceNode node)
{
Im.Tooltip.OnHover(default, $"Resource Type: {node.Type}\nObject Address: 0x{node.ObjectAddress:X16}\nResource Handle: 0x{node.ResourceHandle:X16}\nLength: 0x{node.Length:X16}",
Im.Tooltip.OnHover(default,
$"Resource Type: {node.Type}\nObject Address: 0x{node.ObjectAddress:X16}\nResource Handle: 0x{node.ResourceHandle:X16}\nLength: 0x{node.Length:X16}",
true, Im.Font.Mono);
if (node.ResourceHandle == nint.Zero)

View file

@ -15,7 +15,8 @@ namespace Penumbra.UI.Classes;
public class ChangedItemDrawer : IDisposable, IUiService
{
private static readonly string[] LowerNames = ChangedItemFlagExtensions.Order.Select(f => f.ToDescription().ToString().ToLowerInvariant()).ToArray();
private static readonly string[] LowerNames =
ChangedItemFlagExtensions.Order.Select(f => f.ToDescription().ToString().ToLowerInvariant()).ToArray();
public static bool TryParseIndex(ReadOnlySpan<char> input, out ChangedItemIconFlag slot)
{
@ -83,9 +84,15 @@ public class ChangedItemDrawer : IDisposable, IUiService
}
/// <summary> Check if a changed item should be drawn based on its category. </summary>
public bool FilterChangedItem(string name, IIdentifiedObjectData data, string filter)
=> (_config.Ephemeral.ChangedItemFilter == ChangedItemFlagExtensions.AllFlags
|| _config.Ephemeral.ChangedItemFilter.HasFlag(data.GetIcon().ToFlag()))
public bool FilterChangedItemGlobal(string name, IIdentifiedObjectData data, string filter)
=> (_config.Filters.ChangedItemTypeFilter == ChangedItemFlagExtensions.AllFlags
|| _config.Filters.ChangedItemTypeFilter.HasFlag(data.GetIcon().ToFlag()))
&& (filter.Length is 0 || !data.IsFilteredOut(name, filter));
/// <summary> Check if a changed item should be drawn based on its category. </summary>
public bool FilterChangedItemMod(string name, IIdentifiedObjectData data, string filter)
=> (_config.Filters.ModChangedItemTypeFilter == ChangedItemFlagExtensions.AllFlags
|| _config.Filters.ModChangedItemTypeFilter.HasFlag(data.GetIcon().ToFlag()))
&& (filter.Length is 0 || !data.IsFilteredOut(name, filter));
/// <summary> Draw the icon corresponding to the category of a changed item. </summary>
@ -160,31 +167,33 @@ public class ChangedItemDrawer : IDisposable, IUiService
}
/// <summary> Draw a header line with the different icon types to filter them. </summary>
public void DrawTypeFilter()
public void DrawTypeFilter(bool global)
{
if (_config.HideChangedItemFilters)
return;
var typeFilter = _config.Ephemeral.ChangedItemFilter;
if (DrawTypeFilter(ref typeFilter))
var typeFilter = global ? _config.Filters.ChangedItemTypeFilter : _config.Filters.ModChangedItemTypeFilter;
if (DrawTypeFilter(typeFilter, out typeFilter))
{
_config.Ephemeral.ChangedItemFilter = typeFilter;
_config.Ephemeral.Save();
if (global)
_config.Filters.ChangedItemTypeFilter = typeFilter;
else
_config.Filters.ModChangedItemTypeFilter = typeFilter;
}
}
/// <summary> Draw a header line with the different icon types to filter them. </summary>
public bool DrawTypeFilter(ref ChangedItemIconFlag typeFilter)
public bool DrawTypeFilter(ChangedItemIconFlag typeFilter, out ChangedItemIconFlag newTypeFilter)
{
var ret = false;
using var _ = Im.Id.Push("ChangedItemIconFilter"u8);
var size = TypeFilterIconSize;
using var style = ImStyleDouble.ItemSpacing.Push(Vector2.Zero);
newTypeFilter = typeFilter;
foreach (var iconType in ChangedItemFlagExtensions.Order)
{
ret |= DrawIcon(iconType, ref typeFilter);
ret |= DrawIcon(iconType, ref newTypeFilter);
Im.Line.Same();
}
@ -198,30 +207,30 @@ public class ChangedItemDrawer : IDisposable, IUiService
});
if (Im.Item.Clicked())
{
typeFilter = typeFilter is ChangedItemFlagExtensions.AllFlags ? 0 : ChangedItemFlagExtensions.AllFlags;
ret = true;
newTypeFilter = typeFilter is ChangedItemFlagExtensions.AllFlags ? 0 : ChangedItemFlagExtensions.AllFlags;
ret = true;
}
return ret;
bool DrawIcon(ChangedItemIconFlag type, ref ChangedItemIconFlag typeFilter)
bool DrawIcon(ChangedItemIconFlag type, ref ChangedItemIconFlag filter)
{
var localRet = false;
var icon = _icons[type];
var flag = typeFilter.HasFlag(type);
var flag = filter.HasFlag(type);
Im.Image.Draw(icon.Id(), size, Vector2.Zero, Vector2.One, flag ? Vector4.One : new Vector4(0.6f, 0.3f, 0.3f, 1f));
if (Im.Item.Clicked())
{
typeFilter = flag ? typeFilter & ~type : typeFilter | type;
localRet = true;
filter = flag ? filter & ~type : filter | type;
localRet = true;
}
using var popup = Im.Popup.BeginContextItem($"{type}");
if (popup)
if (Im.Menu.Item("Enable Only This"u8))
{
typeFilter = type;
localRet = true;
filter = type;
localRet = true;
Im.Popup.CloseCurrent();
}

View file

@ -50,7 +50,7 @@ public static class ChangedItemFlagExtensions
public const ChangedItemIconFlag AllFlags = (ChangedItemIconFlag)0x01FFFF;
public static readonly int NumCategories = Order.Count;
public const ChangedItemIconFlag DefaultFlags = AllFlags & ~ChangedItemIconFlag.Offhand;
public const ChangedItemIconFlag DefaultFlags = AllFlags;
public static ReadOnlySpan<byte> ToDescription(this ChangedItemIconFlag iconFlag)
=> iconFlag switch

View file

@ -5,6 +5,12 @@ namespace Penumbra.UI.CollectionTab;
public sealed class CollectionFilter : TextFilterBase<CollectionSelector.Entry>, IUiService
{
public CollectionFilter(FilterConfig filterConfig)
{
Set(filterConfig.CollectionFilter);
FilterChanged += () => filterConfig.CollectionFilter = Text;
}
public override bool WouldBeVisible(in CollectionSelector.Entry item, int globalIndex)
=> base.WouldBeVisible(in item, globalIndex) || WouldBeVisible(item.AnonymousName.Utf16);

View file

@ -11,24 +11,12 @@ using Penumbra.UI.Classes;
namespace Penumbra.UI.MainWindow;
public class UiState : ISavable, IService
{
public string ChangedItemTabNameFilter = string.Empty;
public string ChangedItemTabModFilter = string.Empty;
public ChangedItemIconFlag ChangedItemTabCategoryFilter = ChangedItemFlagExtensions.DefaultFlags;
public string ToFilePath(FilenameService fileNames)
=> "uiState";
public void Save(StreamWriter writer)
{ }
}
public sealed class ChangedItemsTab(
CollectionManager collectionManager,
CollectionSelectHeader collectionHeader,
ChangedItemDrawer drawer,
CommunicatorService communicator)
CommunicatorService communicator,
FilterConfig filterConfig)
: ITab<TabType>
{
public ReadOnlySpan<byte> Label
@ -39,14 +27,14 @@ public sealed class ChangedItemsTab(
private Vector2 _buttonSize;
private readonly ChangedItemFilter _filter = new(drawer, new UiState());
private readonly ChangedItemFilter _filter = new(drawer, filterConfig);
private sealed class ChangedItemFilter(ChangedItemDrawer drawer, UiState uiState) : IFilter<Item>
private sealed class ChangedItemFilter(ChangedItemDrawer drawer, FilterConfig filterConfig) : IFilter<Item>
{
public bool WouldBeVisible(in Item item, int globalIndex)
=> drawer.FilterChangedItem(item.Name, item.Data, uiState.ChangedItemTabNameFilter)
&& (uiState.ChangedItemTabModFilter.Length is 0
|| item.Mods.Any(m => m.Name.Contains(uiState.ChangedItemTabModFilter, StringComparison.OrdinalIgnoreCase)));
=> drawer.FilterChangedItemGlobal(item.Name, item.Data, filterConfig.ChangedItemItemFilter)
&& (filterConfig.ChangedItemModFilter.Length is 0
|| item.Mods.Any(m => m.Name.Contains(filterConfig.ChangedItemModFilter, StringComparison.OrdinalIgnoreCase)));
public event Action? FilterChanged;
@ -56,29 +44,36 @@ public sealed class ChangedItemsTab(
- 450 * Im.Style.GlobalScale
- Im.Style.ItemSpacing.X;
Im.Item.SetNextWidth(450 * Im.Style.GlobalScale);
var ret = Im.Input.Text("##changedItemsFilter"u8, ref uiState.ChangedItemTabNameFilter, "Filter Item..."u8);
var filter = filterConfig.ChangedItemItemFilter;
var ret = Im.Input.Text("##changedItemsFilter"u8, ref filter, "Filter Item..."u8);
if (ret)
filterConfig.ChangedItemItemFilter = filter;
Im.Line.Same();
Im.Item.SetNextWidth(varWidth);
ret |= Im.Input.Text("##changedItemsModFilter"u8, ref uiState.ChangedItemTabModFilter, "Filter Mods..."u8);
if (!ret)
return false;
filter = filterConfig.ChangedItemModFilter;
if (Im.Input.Text("##changedItemsModFilter"u8, ref filter, "Filter Mods..."u8))
{
ret = true;
filterConfig.ChangedItemModFilter = filter;
}
FilterChanged?.Invoke();
return true;
if (ret)
FilterChanged?.Invoke();
return ret;
}
public void Clear()
{
uiState.ChangedItemTabModFilter = string.Empty;
uiState.ChangedItemTabNameFilter = string.Empty;
uiState.ChangedItemTabCategoryFilter = ChangedItemFlagExtensions.DefaultFlags;
filterConfig.ChangedItemModFilter = string.Empty;
filterConfig.ChangedItemItemFilter = string.Empty;
filterConfig.ChangedItemTypeFilter = ChangedItemFlagExtensions.DefaultFlags;
FilterChanged?.Invoke();
}
public bool IsEmpty
=> uiState.ChangedItemTabModFilter.Length is 0
&& uiState.ChangedItemTabNameFilter.Length is 0
&& uiState.ChangedItemTabCategoryFilter is ChangedItemFlagExtensions.DefaultFlags;
=> filterConfig.ChangedItemModFilter.Length is 0
&& filterConfig.ChangedItemItemFilter.Length is 0
&& filterConfig.ChangedItemTypeFilter is ChangedItemFlagExtensions.DefaultFlags;
}
private readonly record struct Item(string Label, IIdentifiedObjectData Data, SingleArray<IMod> Mods)
@ -124,7 +119,7 @@ public sealed class ChangedItemsTab(
public void DrawContent()
{
collectionHeader.Draw(true);
drawer.DrawTypeFilter();
drawer.DrawTypeFilter(true);
_filter.DrawFilter("##Filter"u8, Im.ContentRegion.Available);
using var child = Im.Child.Begin("##changedItemsChild"u8, Im.ContentRegion.Available);
if (!child)

View file

@ -15,7 +15,8 @@ namespace Penumbra.UI.MainWindow;
public sealed class EffectiveTab(
CollectionManager collectionManager,
CollectionSelectHeader collectionHeader,
CommunicatorService communicatorService)
CommunicatorService communicatorService,
FilterConfig filterConfig)
: ITab<TabType>
{
public ReadOnlySpan<byte> Label
@ -31,7 +32,7 @@ public sealed class EffectiveTab(
public TabType Identifier
=> TabType.EffectiveChanges;
private readonly PairFilter<Item> _filter = new(new GamePathFilter(), new FullPathFilter());
private readonly PairFilter<Item> _filter = new(new GamePathFilter(filterConfig), new FullPathFilter(filterConfig));
private sealed class Cache : BasicFilterCache<Item>, IPanel
{
@ -111,7 +112,6 @@ public sealed class EffectiveTab(
Filter.Filter1.DrawFilter("Filter game path..."u8, new Vector2(_gamePathSize + Im.Style.CellPadding.X, Im.Style.FrameHeight));
Im.Line.Same(0, _arrowSize + 2 * Im.Style.CellPadding.X);
Filter.Filter2.DrawFilter("Filter file path..."u8, Im.ContentRegion.Available with { Y = Im.Style.FrameHeight });
}
private void DrawTable()
@ -143,12 +143,24 @@ public sealed class EffectiveTab(
private sealed class GamePathFilter : RegexFilterBase<Item>
{
public GamePathFilter(FilterConfig config)
{
Set(config.EffectiveChangesGamePathFilter);
FilterChanged += () => config.EffectiveChangesGamePathFilter = Text;
}
protected override string ToFilterString(in Item item, int globalIndex)
=> item.GamePath.Utf16;
}
private sealed class FullPathFilter : RegexFilterBase<Item>
{
public FullPathFilter(FilterConfig config)
{
Set(config.EffectiveChangesFilePathFilter);
FilterChanged += () => config.EffectiveChangesFilePathFilter = Text;
}
protected override string ToFilterString(in Item item, int globalIndex)
=> item.FilePath.FullName;
}

View file

@ -110,7 +110,7 @@ public class ModPanelChangedItemsTab(
{
foreach (var (s, i) in _lastSelected.ChangedItems)
{
if (drawer.FilterChangedItem(s, i, string.Empty))
if (drawer.FilterChangedItemMod(s, i, string.Empty))
Data.Add(Container.Single(s, i));
}
@ -124,7 +124,7 @@ public class ModPanelChangedItemsTab(
if (i is not IdentifiedItem item)
continue;
if (!drawer.FilterChangedItem(s, item, string.Empty))
if (!drawer.FilterChangedItemMod(s, item, string.Empty))
continue;
if (tmp.TryGetValue((item.Item.PrimaryId, item.Item.Type), out var p))
@ -160,7 +160,7 @@ public class ModPanelChangedItemsTab(
if (i is IdentifiedItem)
continue;
if (!drawer.FilterChangedItem(s, i, string.Empty))
if (!drawer.FilterChangedItemMod(s, i, string.Empty))
continue;
while (sortedTmpIdx < sortedTmp.Length
@ -221,9 +221,9 @@ public class ModPanelChangedItemsTab(
_id = Im.Id.Current;
_mod = fileSystem.Selection.Selection!.GetValue<Mod>()!;
var cache = CacheManager.Instance.GetOrCreateCache(_id, () => new ChangedItemsCache());
drawer.DrawTypeFilter();
drawer.DrawTypeFilter(false);
cache.Update(_mod, drawer, config.Ephemeral.ChangedItemFilter, config.ChangedItemDisplay);
cache.Update(_mod, drawer, config.Filters.ModChangedItemTypeFilter, config.ChangedItemDisplay);
Im.Separator();
_buttonSize = new Vector2(Im.Style.ItemSpacing.Y + Im.Style.FrameHeight);
using var style = ImStyleDouble.CellPadding.Push(Vector2.Zero)

View file

@ -9,11 +9,25 @@ using static ImSharp.Im;
namespace Penumbra.UI.ModsTab.Selector;
public sealed class ModFilter(ModManager modManager, ActiveCollections collections)
: TokenizedFilter<ModFilterTokenType, ModFileSystemCache.ModData, ModFilterToken>,
IFileSystemFilter<ModFileSystemCache.ModData>
public sealed class ModFilter : TokenizedFilter<ModFilterTokenType, ModFileSystemCache.ModData, ModFilterToken>,
IFileSystemFilter<ModFileSystemCache.ModData>
{
private ModTypeFilter _stateFilter = ModTypeFilterExtensions.UnfilteredStateMods;
private ModTypeFilter _stateFilter;
private readonly ModManager _modManager;
private readonly ActiveCollections _collections;
public ModFilter(ModManager modManager, ActiveCollections collections, FilterConfig filterConfig)
{
_modManager = modManager;
_collections = collections;
_stateFilter = filterConfig.ModTypeFilter;
Set(filterConfig.ModFilter);
FilterChanged += () =>
{
filterConfig.ModFilter = Text;
filterConfig.ModTypeFilter = StateFilter;
};
}
public ModTypeFilter StateFilter
=> _stateFilter;
@ -156,8 +170,8 @@ public sealed class ModFilter(ModManager modManager, ActiveCollections collectio
private bool CheckStateFilters(Mod mod)
{
var (settings, collection) = collections.Current.GetActualSettings(mod.Index);
var isNew = modManager.IsNew(mod);
var (settings, collection) = _collections.Current.GetActualSettings(mod.Index);
var isNew = _modManager.IsNew(mod);
// Handle mod details.
if (CheckFlags(mod.TotalFileCount, ModTypeFilter.HasNoFiles, ModTypeFilter.HasFiles)
|| CheckFlags(mod.TotalSwapCount, ModTypeFilter.HasNoFileSwaps, ModTypeFilter.HasFileSwaps)
@ -182,7 +196,7 @@ public sealed class ModFilter(ModManager modManager, ActiveCollections collectio
}
// Handle Inheritance
if (collection == collections.Current)
if (collection == _collections.Current)
{
if (!_stateFilter.HasFlag(ModTypeFilter.Uninherited))
return false;
@ -213,7 +227,7 @@ public sealed class ModFilter(ModManager modManager, ActiveCollections collectio
return false;
// Conflicts can only be relevant if the mod is enabled.
var conflicts = collections.Current.Conflicts(mod);
var conflicts = _collections.Current.Conflicts(mod);
if (conflicts.Count > 0)
{
if (conflicts.Any(c => !c.Solved))

View file

@ -17,12 +17,10 @@ public sealed class ModFileSystemDrawer : FileSystemDrawer<ModFileSystemCache.Mo
public readonly FileDialogService FileService;
public readonly TutorialService Tutorial;
public readonly CommunicatorService Communicator;
public readonly GlobalModImporter GlobalModImporter;
public ModFileSystemDrawer(ModFileSystem fileSystem, ModManager modManager, CollectionManager collectionManager, Configuration config,
ModImportManager modImport, FileDialogService fileService, TutorialService tutorial, CommunicatorService communicator,
GlobalModImporter globalModImporter)
: base(fileSystem, new ModFilter(modManager, collectionManager.Active))
ModImportManager modImport, FileDialogService fileService, TutorialService tutorial, CommunicatorService communicator)
: base(fileSystem, new ModFilter(modManager, collectionManager.Active, config.Filters))
{
ModManager = modManager;
CollectionManager = collectionManager;
@ -31,7 +29,6 @@ public sealed class ModFileSystemDrawer : FileSystemDrawer<ModFileSystemCache.Mo
FileService = fileService;
Tutorial = tutorial;
Communicator = communicator;
GlobalModImporter = globalModImporter;
SortMode = Config.SortMode;
MainContext.AddButton(new ClearTemporarySettingsButton(this), 105);

View file

@ -30,6 +30,15 @@ public enum RecordType : byte
ResourceComplete = 0x10,
}
public static partial class RecordTypeExtensions
{
public const RecordType All = RecordType.Request
| RecordType.ResourceLoad
| RecordType.FileLoad
| RecordType.Destruction
| RecordType.ResourceComplete;
}
internal unsafe struct Record()
{
public DateTime Time;

View file

@ -43,7 +43,7 @@ public sealed class ResourceWatcher : IDisposable, ITab<TabType>
_resources = resources;
_destructor = destructor;
_loader = loader;
_table = new ResourceWatcherTable(new ResourceWatcherConfig(), _records);
_table = new ResourceWatcherTable(config.Filters, _records);
_resources.ResourceRequested += OnResourceRequested;
_destructor.Subscribe(OnResourceDestroyed, ResourceHandleDestructor.Priority.ResourceWatcher);
_loader.ResourceLoaded += OnResourceLoaded;

View file

@ -9,33 +9,6 @@ using Penumbra.UI.Classes;
namespace Penumbra.UI.ResourceWatcher;
public class ResourceWatcherConfig
{
public int Version = 1;
public bool Enabled = false;
public int MaxEntries = 500;
public bool StoreOnlyMatching = true;
public bool WriteToLog = false;
public string LogFilter = string.Empty;
public string PathFilter = string.Empty;
public string CollectionFilter = string.Empty;
public string ObjectFilter = string.Empty;
public string OriginalPathFilter = string.Empty;
public string ResourceFilter = string.Empty;
public string CrcFilter = string.Empty;
public string RefFilter = string.Empty;
public string ThreadFilter = string.Empty;
public RecordType RecordFilter = Enum.GetValues<RecordType>().Or();
public BoolEnum CustomFilter = BoolEnum.True | BoolEnum.False | BoolEnum.Unknown;
public BoolEnum SyncFilter = BoolEnum.True | BoolEnum.False | BoolEnum.Unknown;
public ResourceCategoryFlag CategoryFilter = ResourceExtensions.AllResourceCategories;
public ResourceTypeFlag TypeFilter = ResourceExtensions.AllResourceTypes;
public LoadStateFlag LoadStateFilter = Enum.GetValues<LoadStateFlag>().Or();
public void Save()
{ }
}
[Flags]
public enum BoolEnum : byte
{
@ -44,6 +17,11 @@ public enum BoolEnum : byte
Unknown = 0x04,
}
public static class BoolEnumExtensions
{
public const BoolEnum All = BoolEnum.True | BoolEnum.False | BoolEnum.Unknown;
}
[Flags]
public enum LoadStateFlag : byte
{
@ -55,6 +33,16 @@ public enum LoadStateFlag : byte
None = 0xFF,
}
public static class LoadStateExtensions
{
public const LoadStateFlag All = LoadStateFlag.Success
| LoadStateFlag.Async
| LoadStateFlag.Failed
| LoadStateFlag.FailedSub
| LoadStateFlag.Unknown
| LoadStateFlag.None;
}
internal sealed unsafe class CachedRecord(Record record)
{
public readonly Record Record = record;
@ -84,23 +72,23 @@ internal sealed class ResourceWatcherTable : TableBase<CachedRecord, TableCache<
public bool WouldBeVisible(Record record)
=> Columns.OfType<ICheckRecord>().All(column => column.WouldBeVisible(record));
public ResourceWatcherTable(ResourceWatcherConfig config, IReadOnlyList<Record> records)
public ResourceWatcherTable(FilterConfig filterConfig, IReadOnlyList<Record> records)
: base(new StringU8("##records"u8),
new PathColumn(config) { Label = new StringU8("Path"u8) },
new RecordTypeColumn(config) { Label = new StringU8("Record"u8) },
new CollectionColumn(config) { Label = new StringU8("Collection"u8) },
new ObjectColumn(config) { Label = new StringU8("Game Object"u8) },
new CustomLoadColumn(config) { Label = new StringU8("Custom"u8) },
new SynchronousLoadColumn(config) { Label = new StringU8("Sync"u8) },
new OriginalPathColumn(config) { Label = new StringU8("Original Path"u8) },
new ResourceCategoryColumn(config) { Label = new StringU8("Category"u8) },
new ResourceTypeColumn(config) { Label = new StringU8("Type"u8) },
new HandleColumn(config) { Label = new StringU8("Resource"u8) },
new LoadStateColumn(config) { Label = new StringU8("State"u8) },
new RefCountColumn(config) { Label = new StringU8("#Ref"u8) },
new DateColumn { Label = new StringU8("Time"u8) },
new Crc64Column(config) { Label = new StringU8("Crc64"u8) },
new OsThreadColumn(config) { Label = new StringU8("TID"u8) }
new PathColumn(filterConfig) { Label = new StringU8("Path"u8) },
new RecordTypeColumn(filterConfig) { Label = new StringU8("Record"u8) },
new CollectionColumn(filterConfig) { Label = new StringU8("Collection"u8) },
new ObjectColumn(filterConfig) { Label = new StringU8("Game Object"u8) },
new CustomLoadColumn(filterConfig) { Label = new StringU8("Custom"u8) },
new SynchronousLoadColumn(filterConfig) { Label = new StringU8("Sync"u8) },
new OriginalPathColumn(filterConfig) { Label = new StringU8("Original Path"u8) },
new ResourceCategoryColumn(filterConfig) { Label = new StringU8("Category"u8) },
new ResourceTypeColumn(filterConfig) { Label = new StringU8("Type"u8) },
new HandleColumn(filterConfig) { Label = new StringU8("Resource"u8) },
new LoadStateColumn(filterConfig) { Label = new StringU8("State"u8) },
new RefCountColumn(filterConfig) { Label = new StringU8("#Ref"u8) },
new DateColumn { Label = new StringU8("Time"u8) },
new Crc64Column(filterConfig) { Label = new StringU8("Crc64"u8) },
new OsThreadColumn(filterConfig) { Label = new StringU8("TID"u8) }
)
{
_records = records;
@ -149,26 +137,15 @@ internal sealed class ResourceWatcherTable : TableBase<CachedRecord, TableCache<
private sealed class PathColumn : TextColumn<CachedRecord>, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
public PathColumn(ResourceWatcherConfig config)
public PathColumn(FilterConfig config)
{
_config = config;
UnscaledWidth = 300;
Filter.Set(config.PathFilter);
Filter.FilterChanged += OnFilterChanged;
}
private void OnFilterChanged()
{
_config.PathFilter = Filter.Text;
_config.Save();
Filter.Set(config.ResourceLoggerPathFilter);
Filter.FilterChanged += () => config.ResourceLoggerPathFilter = Filter.Text;
}
public override void DrawColumn(in CachedRecord item, int globalIndex)
{
DrawByteString(item.Record.Path, 290 * Im.Style.GlobalScale);
}
=> DrawByteString(item.Record.Path, 290 * Im.Style.GlobalScale);
protected override string ComparisonText(in CachedRecord item, int globalIndex)
=> item.PathU16;
@ -182,20 +159,11 @@ internal sealed class ResourceWatcherTable : TableBase<CachedRecord, TableCache<
private sealed class RecordTypeColumn : FlagColumn<RecordType, CachedRecord>, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
public RecordTypeColumn(ResourceWatcherConfig config)
public RecordTypeColumn(FilterConfig config)
{
_config = config;
UnscaledWidth = 80;
Filter.LoadValue(config.RecordFilter);
Filter.FilterChanged += OnFilterChanged;
}
private void OnFilterChanged()
{
_config.RecordFilter = Filter.FilterValue;
_config.Save();
Filter.LoadValue(config.ResourceLoggerRecordFilter);
Filter.FilterChanged += () => config.ResourceLoggerRecordFilter = Filter.FilterValue;
}
protected override StringU8 DisplayString(in CachedRecord item, int globalIndex)
@ -225,20 +193,11 @@ internal sealed class ResourceWatcherTable : TableBase<CachedRecord, TableCache<
private sealed class Crc64Column : TextColumn<CachedRecord>, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
public Crc64Column(ResourceWatcherConfig config)
public Crc64Column(FilterConfig config)
{
_config = config;
UnscaledWidth = 17 * 8;
Filter.Set(config.CrcFilter);
Filter.FilterChanged += OnFilterChanged;
}
private void OnFilterChanged()
{
_config.CrcFilter = Filter.Text;
_config.Save();
Filter.Set(config.ResourceLoggerCrcFilter);
Filter.FilterChanged += () => config.ResourceLoggerCrcFilter = Filter.Text;
}
public override int Compare(in CachedRecord lhs, int lhsGlobalIndex, in CachedRecord rhs, int rhsGlobalIndex)
@ -266,20 +225,11 @@ internal sealed class ResourceWatcherTable : TableBase<CachedRecord, TableCache<
private sealed class CollectionColumn : TextColumn<CachedRecord>, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
public CollectionColumn(ResourceWatcherConfig config)
public CollectionColumn(FilterConfig config)
{
_config = config;
UnscaledWidth = 80;
Filter.Set(config.CollectionFilter);
Filter.FilterChanged += OnFilterChanged;
}
private void OnFilterChanged()
{
_config.CollectionFilter = Filter.Text;
_config.Save();
Filter.Set(config.ResourceLoggerCollectionFilter);
Filter.FilterChanged += () => config.ResourceLoggerCollectionFilter = Filter.Text;
}
protected override string ComparisonText(in CachedRecord item, int globalIndex)
@ -294,20 +244,11 @@ internal sealed class ResourceWatcherTable : TableBase<CachedRecord, TableCache<
private sealed class ObjectColumn : TextColumn<CachedRecord>, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
public ObjectColumn(ResourceWatcherConfig config)
public ObjectColumn(FilterConfig config)
{
_config = config;
UnscaledWidth = 150;
Filter.Set(config.ObjectFilter);
Filter.FilterChanged += OnFilterChanged;
}
private void OnFilterChanged()
{
_config.ObjectFilter = Filter.Text;
_config.Save();
Filter.Set(config.ResourceLoggerObjectFilter);
Filter.FilterChanged += () => config.ResourceLoggerObjectFilter = Filter.Text;
}
protected override string ComparisonText(in CachedRecord item, int globalIndex)
@ -322,20 +263,11 @@ internal sealed class ResourceWatcherTable : TableBase<CachedRecord, TableCache<
private sealed class OriginalPathColumn : TextColumn<CachedRecord>, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
public OriginalPathColumn(ResourceWatcherConfig config)
public OriginalPathColumn(FilterConfig config)
{
_config = config;
UnscaledWidth = 200;
Filter.Set(config.OriginalPathFilter);
Filter.FilterChanged += OnFilterChanged;
}
private void OnFilterChanged()
{
_config.OriginalPathFilter = Filter.Text;
_config.Save();
Filter.Set(config.ResourceLoggerOriginalPathFilter);
Filter.FilterChanged += () => config.ResourceLoggerOriginalPathFilter = Filter.Text;
}
public override void DrawColumn(in CachedRecord item, int globalIndex)
@ -355,20 +287,11 @@ internal sealed class ResourceWatcherTable : TableBase<CachedRecord, TableCache<
private sealed class ResourceCategoryColumn : FlagColumn<ResourceCategoryFlag, CachedRecord>, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
public ResourceCategoryColumn(ResourceWatcherConfig config)
public ResourceCategoryColumn(FilterConfig config)
{
_config = config;
UnscaledWidth = 80;
Filter.LoadValue(config.CategoryFilter);
Filter.FilterChanged += OnFilterChanged;
}
private void OnFilterChanged()
{
_config.CategoryFilter = Filter.FilterValue;
_config.Save();
Filter.LoadValue(config.ResourceLoggerCategoryFilter);
Filter.FilterChanged += () => config.ResourceLoggerCategoryFilter = Filter.FilterValue;
}
protected override StringU8 DisplayString(in CachedRecord item, int globalIndex)
@ -386,20 +309,11 @@ internal sealed class ResourceWatcherTable : TableBase<CachedRecord, TableCache<
private sealed class ResourceTypeColumn : FlagColumn<ResourceTypeFlag, CachedRecord>, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
public ResourceTypeColumn(ResourceWatcherConfig config)
public ResourceTypeColumn(FilterConfig config)
{
_config = config;
UnscaledWidth = 50;
Filter.LoadValue(config.TypeFilter);
Filter.FilterChanged += OnFilterChanged;
}
private void OnFilterChanged()
{
_config.TypeFilter = Filter.FilterValue;
_config.Save();
Filter.LoadValue(config.ResourceLoggerTypeFilter);
Filter.FilterChanged += () => config.ResourceLoggerTypeFilter = Filter.FilterValue;
}
protected override IReadOnlyList<(ResourceTypeFlag Value, StringU8 Name)> EnumData { get; } =
@ -417,20 +331,11 @@ internal sealed class ResourceWatcherTable : TableBase<CachedRecord, TableCache<
private sealed class LoadStateColumn : FlagColumn<LoadStateFlag, CachedRecord>, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
public LoadStateColumn(ResourceWatcherConfig config)
public LoadStateColumn(FilterConfig config)
{
_config = config;
UnscaledWidth = 50;
Filter.LoadValue(config.LoadStateFilter);
Filter.FilterChanged += OnFilterChanged;
}
private void OnFilterChanged()
{
_config.LoadStateFilter = Filter.FilterValue;
_config.Save();
Filter.LoadValue(config.ResourceLoggerLoadStateFilter);
Filter.FilterChanged += () => config.ResourceLoggerLoadStateFilter = Filter.FilterValue;
}
public override void DrawColumn(in CachedRecord item, int globalIndex)
@ -474,39 +379,30 @@ internal sealed class ResourceWatcherTable : TableBase<CachedRecord, TableCache<
protected override LoadStateFlag GetValue(in CachedRecord item, int globalIndex)
=> GetValue(item.Record.LoadState);
public bool WouldBeVisible(in Record record)
=> Filter.FilterValue.HasFlag(GetValue(record.LoadState));
private static LoadStateFlag GetValue(LoadState value)
=> value switch
{
LoadState.None => LoadStateFlag.None,
LoadState.Success => LoadStateFlag.Success,
LoadState.FailedSubResource => LoadStateFlag.FailedSub,
<= LoadState.Constructed => LoadStateFlag.Unknown,
< LoadState.Success => LoadStateFlag.Async,
> LoadState.Success => LoadStateFlag.Failed,
};
=> value switch
{
LoadState.None => LoadStateFlag.None,
LoadState.Success => LoadStateFlag.Success,
LoadState.FailedSubResource => LoadStateFlag.FailedSub,
<= LoadState.Constructed => LoadStateFlag.Unknown,
< LoadState.Success => LoadStateFlag.Async,
> LoadState.Success => LoadStateFlag.Failed,
};
}
private sealed class HandleColumn : TextColumn<CachedRecord>, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
public HandleColumn(ResourceWatcherConfig config)
public HandleColumn(FilterConfig config)
{
_config = config;
UnscaledWidth = 120;
Filter.Set(config.ResourceFilter);
Filter.FilterChanged += OnFilterChanged;
}
private void OnFilterChanged()
{
_config.ResourceFilter = Filter.Text;
_config.Save();
Filter.Set(config.ResourceLoggerResourceFilter);
Filter.FilterChanged += () => config.ResourceLoggerResourceFilter = Filter.Text;
}
public override unsafe void DrawColumn(in CachedRecord item, int globalIndex)
@ -569,20 +465,11 @@ internal sealed class ResourceWatcherTable : TableBase<CachedRecord, TableCache<
private sealed class CustomLoadColumn : OptBoolColumn, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
public CustomLoadColumn(ResourceWatcherConfig config)
public CustomLoadColumn(FilterConfig config)
: base(60f)
{
_config = config;
Filter.LoadValue(config.CustomFilter);
Filter.FilterChanged += OnFilterChanged;
}
private void OnFilterChanged()
{
_config.CustomFilter = Filter.FilterValue;
_config.Save();
Filter.LoadValue(config.ResourceLoggerCustomFilter);
Filter.FilterChanged += () => config.ResourceLoggerCustomFilter = Filter.FilterValue;
}
protected override BoolEnum GetValue(in CachedRecord item, int globalIndex)
@ -594,20 +481,11 @@ internal sealed class ResourceWatcherTable : TableBase<CachedRecord, TableCache<
private sealed class SynchronousLoadColumn : OptBoolColumn, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
public SynchronousLoadColumn(ResourceWatcherConfig config)
public SynchronousLoadColumn(FilterConfig config)
: base(45)
{
_config = config;
Filter.LoadValue(config.SyncFilter);
Filter.FilterChanged += OnFilterChanged;
}
private void OnFilterChanged()
{
_config.SyncFilter = Filter.FilterValue;
_config.Save();
Filter.LoadValue(config.ResourceLoggerSyncFilter);
Filter.FilterChanged += () => config.ResourceLoggerSyncFilter = Filter.FilterValue;
}
protected override BoolEnum GetValue(in CachedRecord item, int globalIndex)
@ -619,20 +497,11 @@ internal sealed class ResourceWatcherTable : TableBase<CachedRecord, TableCache<
private sealed class RefCountColumn : NumberColumn<uint, CachedRecord>, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
public RefCountColumn(ResourceWatcherConfig config)
public RefCountColumn(FilterConfig config)
{
_config = config;
UnscaledWidth = 60;
Filter.Set(config.RefFilter);
Filter.FilterChanged += OnFilterChanged;
}
private void OnFilterChanged()
{
_config.RefFilter = Filter.Text;
_config.Save();
Filter.Set(config.ResourceLoggerRefFilter);
Filter.FilterChanged += () => config.ResourceLoggerRefFilter = Filter.Text;
}
public override uint ToValue(in CachedRecord item, int globalIndex)
@ -650,20 +519,11 @@ internal sealed class ResourceWatcherTable : TableBase<CachedRecord, TableCache<
private sealed class OsThreadColumn : NumberColumn<uint, CachedRecord>, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
public OsThreadColumn(ResourceWatcherConfig config)
public OsThreadColumn(FilterConfig config)
{
_config = config;
UnscaledWidth = 60;
Filter.Set(config.ThreadFilter);
Filter.FilterChanged += OnFilterChanged;
}
private void OnFilterChanged()
{
_config.ThreadFilter = Filter.Text;
_config.Save();
Filter.Set(config.ResourceLoggerThreadFilter);
Filter.FilterChanged += () => config.ResourceLoggerThreadFilter = Filter.Text;
}
public override uint ToValue(in CachedRecord item, int globalIndex)

View file

@ -43,10 +43,10 @@ public sealed class CollectionsTab : TwoPanelLayout, ITab<TabType>
}
public void DrawContent()
=> Draw(_config.CollectionTabScale);
=> Draw(_config.CollectionsTabScale);
protected override void SetWidth(float width, ScalingMode mode)
=> _config.CollectionTabScale = new TwoPanelWidth(width, mode);
=> _config.CollectionsTabScale = new TwoPanelWidth(width, mode);
public void PostTabButton()
=> _tutorial.OpenTutorial(BasicTutorialSteps.Collections);

View file

@ -20,10 +20,16 @@ public sealed class ResourceTab(Configuration config, ResourceManagerService res
public bool IsVisible
=> config.DebugMode;
public readonly ResourceFilter Filter = new();
public readonly ResourceFilter Filter = new(config.Filters);
public sealed class ResourceFilter : Utf8FilterBase<ResourceHandle>
{
public ResourceFilter(FilterConfig filterConfig)
{
Set(new StringU8(filterConfig.ResourceManagerFilter));
FilterChanged += () => filterConfig.ResourceManagerFilter = Text.ToString();
}
protected override ReadOnlySpan<byte> ToFilterString(in ResourceHandle item, int globalIndex)
=> item.FileName.AsSpan();
}

View file

@ -432,7 +432,8 @@ public sealed class SettingsTab : ITab<TabType>
_config.HideChangedItemFilters = v;
if (v)
{
_config.Ephemeral.ChangedItemFilter = ChangedItemFlagExtensions.AllFlags;
_config.Filters.ModChangedItemTypeFilter = ChangedItemFlagExtensions.AllFlags;
_config.Filters.ChangedItemTypeFilter = ChangedItemFlagExtensions.AllFlags;
_config.Ephemeral.Save();
}
});