mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-02-06 07:54:36 +01:00
Improve filter stuff, add start of management tab.
This commit is contained in:
parent
3b4cab2a1a
commit
0bf7278bb8
30 changed files with 613 additions and 258 deletions
2
Luna
2
Luna
|
|
@ -1 +1 @@
|
|||
Subproject commit 1153628fae18b5e720841b73c5bff9a56652ab7b
|
||||
Subproject commit 06094555dc93eb302d7e823a84edab5926450db9
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 52a3216a525592205198303df2844435e382cf87
|
||||
Subproject commit 247b173d2fdee2d0c18666972114e61f77aef6b6
|
||||
|
|
@ -7,7 +7,7 @@ using Penumbra.Services;
|
|||
|
||||
namespace Penumbra.Api.Api;
|
||||
|
||||
public class ModsApi : IPenumbraApiMods, IApiService, IDisposable
|
||||
public sealed partial class ModsApi : IPenumbraApiMods, IApiService, IDisposable
|
||||
{
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly ModManager _modManager;
|
||||
|
|
@ -15,10 +15,11 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable
|
|||
private readonly Configuration _config;
|
||||
private readonly ModFileSystem _modFileSystem;
|
||||
private readonly MigrationManager _migrationManager;
|
||||
private readonly ModConfigUpdater _modConfigUpdater;
|
||||
private readonly Logger _log;
|
||||
|
||||
public ModsApi(ModManager modManager, ModImportManager modImportManager, Configuration config, ModFileSystem modFileSystem,
|
||||
CommunicatorService communicator, MigrationManager migrationManager, Logger log)
|
||||
CommunicatorService communicator, MigrationManager migrationManager, Logger log, ModConfigUpdater modConfigUpdater)
|
||||
{
|
||||
_modManager = modManager;
|
||||
_modImportManager = modImportManager;
|
||||
|
|
@ -27,6 +28,7 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable
|
|||
_communicator = communicator;
|
||||
_migrationManager = migrationManager;
|
||||
_log = log;
|
||||
_modConfigUpdater = modConfigUpdater;
|
||||
_communicator.ModPathChanged.Subscribe(OnModPathChanged, ModPathChanged.Priority.ApiMods);
|
||||
_communicator.PcpCreation.Subscribe(OnPcpCreation, PcpCreation.Priority.ApiMods);
|
||||
_communicator.PcpParsing.Subscribe(OnPcpParsing, PcpParsing.Priority.ApiMods);
|
||||
|
|
@ -120,6 +122,12 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable
|
|||
public event Action<JObject, ushort, string>? CreatingPcp;
|
||||
public event Action<JObject, string, Guid>? ParsingPcp;
|
||||
|
||||
public event Action<string, string, Dictionary<Assembly, (bool MarkUsed, string Note)>>? ModUsageQueried
|
||||
{
|
||||
add => _modConfigUpdater.ModUsageQueried += value;
|
||||
remove => _modConfigUpdater.ModUsageQueried -= value;
|
||||
}
|
||||
|
||||
public (PenumbraApiEc, string, bool, bool) GetModPath(string modDirectory, string modName)
|
||||
{
|
||||
if (!_modManager.TryGetMod(modDirectory, modName, out var mod) || mod.Node is not { } node)
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ public sealed class IpcProviders : IDisposable, IApiService, IRequiredService
|
|||
IpcSubscribers.ModMoved.Provider(pi, api.Mods),
|
||||
IpcSubscribers.CreatingPcp.Provider(pi, api.Mods),
|
||||
IpcSubscribers.ParsingPcp.Provider(pi, api.Mods),
|
||||
IpcSubscribers.ModUsageQueried.Provider(pi, api.Mods),
|
||||
IpcSubscribers.GetModPath.Provider(pi, api.Mods),
|
||||
IpcSubscribers.SetModPath.Provider(pi, api.Mods),
|
||||
IpcSubscribers.GetChangedItems.Provider(pi, api.Mods),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Dalamud.Configuration;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Luna;
|
||||
using Luna.Generators;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.Import.Structs;
|
||||
using Penumbra.Interop.Services;
|
||||
|
|
@ -8,13 +9,12 @@ using Penumbra.Services;
|
|||
using Penumbra.UI.Classes;
|
||||
using Penumbra.UI.ModsTab;
|
||||
using Penumbra.UI.ModsTab.Selector;
|
||||
using Penumbra.UI.ResourceWatcher;
|
||||
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
|
||||
|
||||
namespace Penumbra;
|
||||
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration, ISavable, IService
|
||||
public partial class Configuration : IPluginConfiguration, ISavable, IService
|
||||
{
|
||||
[JsonIgnore]
|
||||
private readonly SaveService _saveService;
|
||||
|
|
@ -56,24 +56,45 @@ public class Configuration : IPluginConfiguration, ISavable, IService
|
|||
|
||||
public bool AutoSelectCollection { get; set; } = false;
|
||||
|
||||
public bool ShowModsInLobby { get; set; } = true;
|
||||
public bool UseCharacterCollectionInMainWindow { get; set; } = true;
|
||||
public bool UseCharacterCollectionsInCards { get; set; } = true;
|
||||
public bool UseCharacterCollectionInInspect { get; set; } = true;
|
||||
public bool UseCharacterCollectionInTryOn { get; set; } = true;
|
||||
public bool UseOwnerNameForCharacterCollection { get; set; } = true;
|
||||
public bool UseNoModsInInspect { get; set; } = false;
|
||||
public bool HideChangedItemFilters { get; set; } = false;
|
||||
public bool ReplaceNonAsciiOnImport { get; set; } = false;
|
||||
public bool HidePrioritiesInSelector { get; set; } = false;
|
||||
public bool HideRedrawBar { get; set; } = false;
|
||||
public bool HideMachinistOffhandFromChangedItems { get; set; } = true;
|
||||
public bool DefaultTemporaryMode { get; set; } = false;
|
||||
public bool EnableDirectoryWatch { get; set; } = false;
|
||||
public bool EnableAutomaticModImport { get; set; } = false;
|
||||
public bool EnableCustomShapes { get; set; } = true;
|
||||
public PcpSettings PcpSettings = new();
|
||||
public RenameField ShowRename { get; set; } = RenameField.BothDataPrio;
|
||||
public bool ShowModsInLobby { get; set; } = true;
|
||||
public bool UseCharacterCollectionInMainWindow { get; set; } = true;
|
||||
public bool UseCharacterCollectionsInCards { get; set; } = true;
|
||||
public bool UseCharacterCollectionInInspect { get; set; } = true;
|
||||
public bool UseCharacterCollectionInTryOn { get; set; } = true;
|
||||
public bool UseOwnerNameForCharacterCollection { get; set; } = true;
|
||||
public bool UseNoModsInInspect { get; set; } = false;
|
||||
public bool HideChangedItemFilters { get; set; } = false;
|
||||
public bool ReplaceNonAsciiOnImport { get; set; } = false;
|
||||
public bool HidePrioritiesInSelector { get; set; } = false;
|
||||
public bool HideRedrawBar { get; set; } = false;
|
||||
public bool HideMachinistOffhandFromChangedItems { get; set; } = true;
|
||||
public bool DefaultTemporaryMode { get; set; } = false;
|
||||
public bool EnableDirectoryWatch { get; set; } = false;
|
||||
public bool EnableAutomaticModImport { get; set; } = false;
|
||||
public bool EnableCustomShapes { get; set; } = true;
|
||||
public PcpSettings PcpSettings = new();
|
||||
|
||||
[ConfigProperty]
|
||||
private bool _rememberModFilters = true;
|
||||
|
||||
[ConfigProperty]
|
||||
private bool _rememberCollectionFilters = true;
|
||||
|
||||
[ConfigProperty]
|
||||
private bool _rememberOnScreenFilters = true;
|
||||
|
||||
[ConfigProperty]
|
||||
private bool _rememberChangedItemFilters = true;
|
||||
|
||||
[ConfigProperty]
|
||||
private bool _rememberEffectiveChangesFilters = true;
|
||||
|
||||
[ConfigProperty]
|
||||
private bool _rememberResourceManagerFilters = true;
|
||||
|
||||
[ConfigProperty(EventName = "ShowRenameChanged")]
|
||||
private RenameField _showRename = RenameField.BothDataPrio;
|
||||
|
||||
public ChangedItemMode ChangedItemDisplay { get; set; } = ChangedItemMode.GroupedCollapsed;
|
||||
public int OptionGroupCollapsibleMin { get; set; } = 5;
|
||||
|
||||
|
|
@ -84,7 +105,6 @@ public class Configuration : IPluginConfiguration, ISavable, IService
|
|||
#else
|
||||
public bool DebugMode { get; set; } = false;
|
||||
#endif
|
||||
public int MaxResourceWatcherRecords { get; set; } = ResourceWatcher.DefaultMaxEntries;
|
||||
|
||||
[JsonConverter(typeof(SortModeConverter))]
|
||||
[JsonProperty(Order = int.MaxValue)]
|
||||
|
|
|
|||
|
|
@ -1,39 +1,39 @@
|
|||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Luna;
|
||||
using Luna.Generators;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.Enums;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.UI;
|
||||
using Penumbra.UI.Classes;
|
||||
using Penumbra.UI.ResourceWatcher;
|
||||
using Penumbra.UI.ManagementTab;
|
||||
using Penumbra.UI.ModsTab;
|
||||
using Penumbra.UI.Tabs;
|
||||
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
|
||||
using TabType = Penumbra.Api.Enums.TabType;
|
||||
|
||||
namespace Penumbra;
|
||||
|
||||
public class EphemeralConfig : ISavable, IService
|
||||
public sealed partial class EphemeralConfig : ISavable, IService
|
||||
{
|
||||
[JsonIgnore]
|
||||
private readonly SaveService _saveService;
|
||||
|
||||
public int Version { get; set; } = Configuration.Constants.CurrentVersion;
|
||||
public int LastSeenVersion { get; set; } = PenumbraChangelog.LastChangelogVersion;
|
||||
public bool DebugSeparateWindow { get; set; } = false;
|
||||
public int TutorialStep { get; set; } = 0;
|
||||
public bool EnableResourceLogging { get; set; } = false;
|
||||
public string ResourceLoggingFilter { get; set; } = string.Empty;
|
||||
public bool EnableResourceWatcher { get; set; } = false;
|
||||
public bool OnlyAddMatchingResources { get; set; } = true;
|
||||
public ResourceTypeFlag ResourceWatcherResourceTypes { get; set; } = ResourceExtensions.AllResourceTypes;
|
||||
public ResourceCategoryFlag ResourceWatcherResourceCategories { get; set; } = ResourceExtensions.AllResourceCategories;
|
||||
public RecordType ResourceWatcherRecordTypes { get; set; } = ResourceWatcher.AllRecords;
|
||||
public CollectionPanelMode CollectionPanel { get; set; } = CollectionPanelMode.SimpleAssignment;
|
||||
public TabType SelectedTab { get; set; } = TabType.Settings;
|
||||
public bool FixMainWindow { get; set; } = false;
|
||||
public HashSet<string> AdvancedEditingOpenForModPaths { get; set; } = [];
|
||||
public bool ForceRedrawOnFileChange { get; set; } = false;
|
||||
public bool IncognitoMode { get; set; } = false;
|
||||
public int Version { get; set; } = Configuration.Constants.CurrentVersion;
|
||||
public int LastSeenVersion { get; set; } = PenumbraChangelog.LastChangelogVersion;
|
||||
public bool DebugSeparateWindow { get; set; } = false;
|
||||
public int TutorialStep { get; set; } = 0;
|
||||
public CollectionPanelMode CollectionPanel { get; set; } = CollectionPanelMode.SimpleAssignment;
|
||||
public TabType SelectedTab { get; set; } = TabType.Settings;
|
||||
|
||||
[ConfigProperty]
|
||||
private ManagementTabType _selectedManagementTab = ManagementTabType.UnusedMods;
|
||||
|
||||
[ConfigProperty]
|
||||
private ModPanelTab _selectedModPanelTab = ModPanelTab.Settings;
|
||||
|
||||
public bool FixMainWindow { get; set; } = false;
|
||||
public HashSet<string> AdvancedEditingOpenForModPaths { get; set; } = [];
|
||||
public bool ForceRedrawOnFileChange { get; set; } = false;
|
||||
public bool IncognitoMode { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Load the current configuration.
|
||||
|
|
@ -41,7 +41,7 @@ public class EphemeralConfig : ISavable, IService
|
|||
/// </summary>
|
||||
public EphemeralConfig(SaveService saveService)
|
||||
{
|
||||
_saveService = saveService;
|
||||
_saveService = saveService;
|
||||
Load();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -240,6 +240,13 @@ public sealed partial class FilterConfig : ConfigurationFile
|
|||
[ConfigProperty]
|
||||
private ChangedItemIconFlag _onScreenTypeFilter = ChangedItemFlagExtensions.DefaultFlags;
|
||||
|
||||
public void ClearOnScreenFilters()
|
||||
{
|
||||
_onScreenCharacterFilter = string.Empty;
|
||||
_onScreenItemFilter = string.Empty;
|
||||
_onScreenTypeFilter = ChangedItemFlagExtensions.DefaultFlags;
|
||||
}
|
||||
|
||||
private void WriteOnScreenTab(JsonTextWriter j)
|
||||
{
|
||||
if (OnScreenCharacterFilter.Length is 0
|
||||
|
|
|
|||
|
|
@ -169,6 +169,8 @@ public unsafe class ResourceLoader : IDisposable, Luna.IService
|
|||
return;
|
||||
|
||||
CompareHash(ComputeHash(path.Path, parameters), hash, path);
|
||||
if (PathResolver.ForbiddenFiles.Contains((uint)hash))
|
||||
return;
|
||||
|
||||
// If no replacements are being made, we still want to be able to trigger the event.
|
||||
var resolvedData = _resolvedData.Value;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Collections.Frozen;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
|
|
@ -21,6 +22,19 @@ public class PathResolver : IDisposable, Luna.IService
|
|||
private readonly CollectionResolver _collectionResolver;
|
||||
private readonly GamePathPreProcessService _preprocessor;
|
||||
|
||||
public static FrozenSet<uint> ForbiddenFiles = ((uint[])
|
||||
[
|
||||
0x90E4EE2F, // common/graphics/texture/dummy.tex
|
||||
0x84815A1A, // chara/common/texture/white.tex
|
||||
0x749091FB, // chara/common/texture/black.tex
|
||||
0x5CB9681A, // chara/common/texture/id_16.tex
|
||||
0x7E78D000, // chara/common/texture/red.tex
|
||||
0xBDC0BFD3, // chara/common/texture/green.tex
|
||||
0xC410E850, // chara/common/texture/blue.tex
|
||||
0xD5CFA221, // chara/common/texture/null_normal.tex
|
||||
0xBE48CA67, // chara/common/texture/skin_mask.tex
|
||||
]).ToFrozenSet();
|
||||
|
||||
public PathResolver(Configuration config, CollectionManager collectionManager, ResourceLoader loader,
|
||||
SubfileHelper subfileHelper, PathState pathState, MetaState metaState, CollectionResolver collectionResolver, GameState gameState,
|
||||
GamePathPreProcessService preprocessor)
|
||||
|
|
|
|||
|
|
@ -23,9 +23,12 @@ public class ModConfigUpdater : IDisposable, IRequiredService
|
|||
_communicator.ModSettingChanged.Subscribe(OnModSettingChanged, ModSettingChanged.Priority.ModConfigUpdater);
|
||||
}
|
||||
|
||||
public IEnumerable<Mod> ListUnusedMods(TimeSpan age)
|
||||
public event Action<string, string, Dictionary<Assembly, (bool MarkUsed, string Note)>>? ModUsageQueried;
|
||||
|
||||
public IEnumerable<(Mod, (string Plugin, string Notes)[])> ListUnusedMods(TimeSpan age)
|
||||
{
|
||||
var cutoff = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - (int)age.TotalMilliseconds;
|
||||
var cutoff = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - (int)age.TotalMilliseconds;
|
||||
var noteDictionary = new Dictionary<Assembly, (bool InUse, string Notes)>();
|
||||
foreach (var mod in _mods)
|
||||
{
|
||||
// Skip actively ignored mods.
|
||||
|
|
@ -40,7 +43,17 @@ public class ModConfigUpdater : IDisposable, IRequiredService
|
|||
if (_collections.Any(c => c.GetOwnSettings(mod.Index)?.Enabled is true || c.GetTempSettings(mod.Index) is not null))
|
||||
continue;
|
||||
|
||||
yield return mod;
|
||||
// Check whether other plugins mark this mod as in use.
|
||||
noteDictionary.Clear();
|
||||
ModUsageQueried?.Invoke(mod.Name, mod.Identifier, noteDictionary);
|
||||
if (noteDictionary.Values.Any(n => n.InUse))
|
||||
continue;
|
||||
|
||||
// Collect other plugin's notes for this mod.
|
||||
var notes = noteDictionary.Where(t => !string.IsNullOrWhiteSpace(t.Value.Notes))
|
||||
.Select(t => (t.Key.GetName().Name ?? "Unknown Plugin", t.Value.Notes)).ToArray();
|
||||
|
||||
yield return (mod, notes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ public readonly struct ModLocalData(Mod mod) : ISavable
|
|||
var json = JObject.Parse(text);
|
||||
|
||||
importDate = json[nameof(Mod.ImportDate)]?.Value<long>() ?? importDate;
|
||||
lastConfigEdit = json[nameof(Mod.LastConfigEdit)]?.Value<long>() ?? lastConfigEdit;
|
||||
lastConfigEdit = json[nameof(Mod.LastConfigEdit)]?.Value<long>() ?? now;
|
||||
favorite = json[nameof(Mod.Favorite)]?.Value<bool>() ?? favorite;
|
||||
note = json[nameof(Mod.Note)]?.Value<string>() ?? note;
|
||||
localTags = (json[nameof(Mod.LocalTags)] as JArray)?.Values<string>().OfType<string>() ?? localTags;
|
||||
|
|
@ -81,6 +81,9 @@ public readonly struct ModLocalData(Mod mod) : ISavable
|
|||
if (importDate == 0)
|
||||
importDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
||||
if (lastConfigEdit == now)
|
||||
save = true;
|
||||
|
||||
ModDataChangeType changes = 0;
|
||||
if (mod.ImportDate != importDate)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ public class Penumbra : IDalamudPlugin
|
|||
sb.Append(
|
||||
$"> **`Synchronous Load (Dalamud): `** {(_services.GetService<DalamudConfigService>().GetDalamudConfig(DalamudConfigService.WaitingForPluginsOption, out bool v) ? v.ToString() : "Unknown")} (first Start: {hdrEnabler.FirstLaunchWaitForPluginsState?.ToString() ?? "Unknown"})\n");
|
||||
sb.Append(
|
||||
$"> **`Logging: `** Log: {_config.Ephemeral.EnableResourceLogging}, Watcher: {_config.Ephemeral.EnableResourceWatcher} ({_config.MaxResourceWatcherRecords})\n");
|
||||
$"> **`Logging: `** Log: {_config.Filters.ResourceLoggerWriteToLog}, Watcher: {_config.Filters.ResourceLoggerEnabled} ({_config.Filters.ResourceLoggerMaxEntries})\n");
|
||||
sb.Append($"> **`Use Ownership: `** {_config.UseOwnerNameForCharacterCollection}\n");
|
||||
GatherRelevantPlugins(sb);
|
||||
sb.AppendLine("**Mods**");
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
<PropertyGroup>
|
||||
<DefineConstants>PROFILING;</DefineConstants>
|
||||
<Use_DalamudPackager>false</Use_DalamudPackager>
|
||||
<Use_Dalamud_ImGui >false</Use_Dalamud_ImGui>
|
||||
<Use_Dalamud_ImGui>false</Use_Dalamud_ImGui>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -109,20 +109,20 @@ public class ConfigMigrationService(SaveService saveService, BackupService backu
|
|||
_config.Version = 8;
|
||||
_config.Ephemeral.Version = 8;
|
||||
|
||||
_config.Ephemeral.LastSeenVersion = _data["LastSeenVersion"]?.ToObject<int>() ?? _config.Ephemeral.LastSeenVersion;
|
||||
_config.Ephemeral.DebugSeparateWindow = _data["DebugSeparateWindow"]?.ToObject<bool>() ?? _config.Ephemeral.DebugSeparateWindow;
|
||||
_config.Ephemeral.TutorialStep = _data["TutorialStep"]?.ToObject<int>() ?? _config.Ephemeral.TutorialStep;
|
||||
_config.Ephemeral.EnableResourceLogging = _data["EnableResourceLogging"]?.ToObject<bool>() ?? _config.Ephemeral.EnableResourceLogging;
|
||||
_config.Ephemeral.ResourceLoggingFilter = _data["ResourceLoggingFilter"]?.ToObject<string>() ?? _config.Ephemeral.ResourceLoggingFilter;
|
||||
_config.Ephemeral.EnableResourceWatcher = _data["EnableResourceWatcher"]?.ToObject<bool>() ?? _config.Ephemeral.EnableResourceWatcher;
|
||||
_config.Ephemeral.OnlyAddMatchingResources =
|
||||
_data["OnlyAddMatchingResources"]?.ToObject<bool>() ?? _config.Ephemeral.OnlyAddMatchingResources;
|
||||
_config.Ephemeral.ResourceWatcherResourceTypes = _data["ResourceWatcherResourceTypes"]?.ToObject<ResourceTypeFlag>()
|
||||
?? _config.Ephemeral.ResourceWatcherResourceTypes;
|
||||
_config.Ephemeral.ResourceWatcherResourceCategories = _data["ResourceWatcherResourceCategories"]?.ToObject<ResourceCategoryFlag>()
|
||||
?? _config.Ephemeral.ResourceWatcherResourceCategories;
|
||||
_config.Ephemeral.ResourceWatcherRecordTypes =
|
||||
_data["ResourceWatcherRecordTypes"]?.ToObject<RecordType>() ?? _config.Ephemeral.ResourceWatcherRecordTypes;
|
||||
_config.Ephemeral.LastSeenVersion = _data["LastSeenVersion"]?.ToObject<int>() ?? _config.Ephemeral.LastSeenVersion;
|
||||
_config.Ephemeral.DebugSeparateWindow = _data["DebugSeparateWindow"]?.ToObject<bool>() ?? _config.Ephemeral.DebugSeparateWindow;
|
||||
_config.Ephemeral.TutorialStep = _data["TutorialStep"]?.ToObject<int>() ?? _config.Ephemeral.TutorialStep;
|
||||
_config.Filters.ResourceLoggerWriteToLog = _data["EnableResourceLogging"]?.ToObject<bool>() ?? _config.Filters.ResourceLoggerWriteToLog;
|
||||
_config.Filters.ResourceLoggerLogFilter = _data["ResourceLoggingFilter"]?.ToObject<string>() ?? _config.Filters.ResourceLoggerLogFilter;
|
||||
_config.Filters.ResourceLoggerEnabled = _data["EnableResourceWatcher"]?.ToObject<bool>() ?? _config.Filters.ResourceLoggerEnabled;
|
||||
_config.Filters.ResourceLoggerStoreOnlyMatching =
|
||||
_data["OnlyAddMatchingResources"]?.ToObject<bool>() ?? _config.Filters.ResourceLoggerStoreOnlyMatching;
|
||||
_config.Filters.ResourceLoggerTypeFilter = _data["ResourceWatcherResourceTypes"]?.ToObject<ResourceTypeFlag>()
|
||||
?? _config.Filters.ResourceLoggerTypeFilter;
|
||||
_config.Filters.ResourceLoggerCategoryFilter = _data["ResourceWatcherResourceCategories"]?.ToObject<ResourceCategoryFlag>()
|
||||
?? _config.Filters.ResourceLoggerCategoryFilter;
|
||||
_config.Filters.ResourceLoggerRecordFilter =
|
||||
_data["ResourceWatcherRecordTypes"]?.ToObject<RecordType>() ?? _config.Filters.ResourceLoggerRecordFilter;
|
||||
_config.Ephemeral.CollectionPanel = _data["CollectionPanel"]?.ToObject<CollectionPanelMode>() ?? _config.Ephemeral.CollectionPanel;
|
||||
_config.Ephemeral.SelectedTab = _data["SelectedTab"]?.ToObject<TabType>() ?? _config.Ephemeral.SelectedTab;
|
||||
_config.Filters.ChangedItemTypeFilter = _data["ChangedItemFilter"]?.ToObject<ChangedItemIconFlag>()
|
||||
|
|
|
|||
|
|
@ -203,22 +203,25 @@ public class ResourceTreeViewer(
|
|||
}
|
||||
}
|
||||
|
||||
var fieldWidth = (Im.ContentRegion.Available.X - checkSpacing * 2.0f - Im.Style.FrameHeightWithSpacing) / 2.0f;
|
||||
Im.Item.SetNextWidth(fieldWidth);
|
||||
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);
|
||||
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;
|
||||
using (ImStyleSingle.FrameRounding.Push(0))
|
||||
{
|
||||
var fieldWidth = (Im.ContentRegion.Available.X - checkSpacing * 2.0f - Im.Style.FrameHeightWithSpacing) / 2.0f;
|
||||
Im.Item.SetNextWidth(fieldWidth);
|
||||
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);
|
||||
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);
|
||||
|
|
@ -412,7 +415,7 @@ public class ResourceTreeViewer(
|
|||
if (node.Internal && !debugMode)
|
||||
return NodeVisibility.Hidden;
|
||||
|
||||
var filterIcon = node.IconFlag != 0 ? node.IconFlag : parentFilterIcon;
|
||||
var filterIcon = node.IconFlag is not 0 ? node.IconFlag : parentFilterIcon;
|
||||
if (MatchesFilter(node, filterIcon))
|
||||
return NodeVisibility.Visible;
|
||||
|
||||
|
|
@ -430,7 +433,7 @@ public class ResourceTreeViewer(
|
|||
if (!config.Filters.OnScreenTypeFilter.HasFlag(filterIcon))
|
||||
return false;
|
||||
|
||||
if (config.Filters.OnScreenItemFilter.Length == 0)
|
||||
if (config.Filters.OnScreenItemFilter.Length is 0)
|
||||
return true;
|
||||
|
||||
return node.Name != null && node.Name.Contains(config.Filters.OnScreenItemFilter, StringComparison.OrdinalIgnoreCase)
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ namespace Penumbra.UI.CollectionTab;
|
|||
|
||||
public sealed class CollectionFilter : TextFilterBase<CollectionSelector.Entry>, IUiService
|
||||
{
|
||||
public CollectionFilter(FilterConfig filterConfig)
|
||||
public CollectionFilter(Configuration config)
|
||||
{
|
||||
Set(filterConfig.CollectionFilter);
|
||||
FilterChanged += () => filterConfig.CollectionFilter = Text;
|
||||
if (config.RememberCollectionFilters)
|
||||
Set(config.Filters.CollectionFilter);
|
||||
FilterChanged += () => config.Filters.CollectionFilter = Text;
|
||||
}
|
||||
|
||||
public override bool WouldBeVisible(in CollectionSelector.Entry item, int globalIndex)
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ public sealed class CollectionPanel(
|
|||
DrawSimpleCollectionButton(CollectionType.MaleNonPlayerCharacter, buttonWidth);
|
||||
DrawSimpleCollectionButton(CollectionType.FemaleNonPlayerCharacter, buttonWidth);
|
||||
|
||||
ImEx.TextMultiColored("Individual"u8, ColorId.NewMod.Value())
|
||||
ImEx.TextMultiColored("Individual "u8, ColorId.NewMod.Value())
|
||||
.Then("Assignments take precedence before anything else and only apply to one specific character or monster."u8)
|
||||
.End();
|
||||
Im.Dummy(1);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public sealed class ChangedItemsTab(
|
|||
CollectionSelectHeader collectionHeader,
|
||||
ChangedItemDrawer drawer,
|
||||
CommunicatorService communicator,
|
||||
FilterConfig filterConfig)
|
||||
Configuration config)
|
||||
: ITab<TabType>
|
||||
{
|
||||
public ReadOnlySpan<byte> Label
|
||||
|
|
@ -27,34 +27,46 @@ public sealed class ChangedItemsTab(
|
|||
|
||||
private Vector2 _buttonSize;
|
||||
|
||||
private readonly ChangedItemFilter _filter = new(drawer, filterConfig);
|
||||
private readonly ChangedItemFilter _filter = new(drawer, config);
|
||||
|
||||
private sealed class ChangedItemFilter(ChangedItemDrawer drawer, FilterConfig filterConfig) : IFilter<Item>
|
||||
private sealed class ChangedItemFilter : IFilter<Item>
|
||||
{
|
||||
private readonly ChangedItemDrawer _drawer;
|
||||
private readonly FilterConfig _filterConfig;
|
||||
|
||||
public ChangedItemFilter(ChangedItemDrawer drawer, Configuration config)
|
||||
{
|
||||
_drawer = drawer;
|
||||
_filterConfig = config.Filters;
|
||||
if (!config.RememberChangedItemFilters)
|
||||
Clear();
|
||||
}
|
||||
|
||||
public bool WouldBeVisible(in Item item, int globalIndex)
|
||||
=> drawer.FilterChangedItemGlobal(item.Name, item.Data, filterConfig.ChangedItemItemFilter)
|
||||
&& (filterConfig.ChangedItemModFilter.Length is 0
|
||||
|| item.Mods.Any(m => m.Name.Contains(filterConfig.ChangedItemModFilter, 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;
|
||||
|
||||
public bool DrawFilter(ReadOnlySpan<byte> label, Vector2 availableRegion)
|
||||
{
|
||||
using var style = ImStyleSingle.FrameRounding.Push(0);
|
||||
var varWidth = Im.ContentRegion.Available.X
|
||||
- 450 * Im.Style.GlobalScale
|
||||
- Im.Style.ItemSpacing.X;
|
||||
Im.Item.SetNextWidth(450 * Im.Style.GlobalScale);
|
||||
var filter = filterConfig.ChangedItemItemFilter;
|
||||
var filter = _filterConfig.ChangedItemItemFilter;
|
||||
var ret = Im.Input.Text("##changedItemsFilter"u8, ref filter, "Filter Item..."u8);
|
||||
if (ret)
|
||||
filterConfig.ChangedItemItemFilter = filter;
|
||||
_filterConfig.ChangedItemItemFilter = filter;
|
||||
Im.Line.Same();
|
||||
Im.Item.SetNextWidth(varWidth);
|
||||
filter = filterConfig.ChangedItemModFilter;
|
||||
filter = _filterConfig.ChangedItemModFilter;
|
||||
if (Im.Input.Text("##changedItemsModFilter"u8, ref filter, "Filter Mods..."u8))
|
||||
{
|
||||
ret = true;
|
||||
filterConfig.ChangedItemModFilter = filter;
|
||||
ret = true;
|
||||
_filterConfig.ChangedItemModFilter = filter;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
|
|
@ -64,16 +76,16 @@ public sealed class ChangedItemsTab(
|
|||
|
||||
public void Clear()
|
||||
{
|
||||
filterConfig.ChangedItemModFilter = string.Empty;
|
||||
filterConfig.ChangedItemItemFilter = string.Empty;
|
||||
filterConfig.ChangedItemTypeFilter = ChangedItemFlagExtensions.DefaultFlags;
|
||||
_filterConfig.ChangedItemModFilter = string.Empty;
|
||||
_filterConfig.ChangedItemItemFilter = string.Empty;
|
||||
_filterConfig.ChangedItemTypeFilter = ChangedItemFlagExtensions.DefaultFlags;
|
||||
FilterChanged?.Invoke();
|
||||
}
|
||||
|
||||
public bool IsEmpty
|
||||
=> filterConfig.ChangedItemModFilter.Length is 0
|
||||
&& filterConfig.ChangedItemItemFilter.Length is 0
|
||||
&& filterConfig.ChangedItemTypeFilter 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)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public sealed class EffectiveTab(
|
|||
CollectionManager collectionManager,
|
||||
CollectionSelectHeader collectionHeader,
|
||||
CommunicatorService communicatorService,
|
||||
FilterConfig filterConfig)
|
||||
Configuration config)
|
||||
: ITab<TabType>
|
||||
{
|
||||
public ReadOnlySpan<byte> Label
|
||||
|
|
@ -32,7 +32,7 @@ public sealed class EffectiveTab(
|
|||
public TabType Identifier
|
||||
=> TabType.EffectiveChanges;
|
||||
|
||||
private readonly PairFilter<Item> _filter = new(new GamePathFilter(filterConfig), new FullPathFilter(filterConfig));
|
||||
private readonly PairFilter<Item> _filter = new(new GamePathFilter(config), new FullPathFilter(config));
|
||||
|
||||
private sealed class Cache : BasicFilterCache<Item>, IPanel
|
||||
{
|
||||
|
|
@ -143,10 +143,11 @@ public sealed class EffectiveTab(
|
|||
|
||||
private sealed class GamePathFilter : RegexFilterBase<Item>
|
||||
{
|
||||
public GamePathFilter(FilterConfig config)
|
||||
public GamePathFilter(Configuration config)
|
||||
{
|
||||
Set(config.EffectiveChangesGamePathFilter);
|
||||
FilterChanged += () => config.EffectiveChangesGamePathFilter = Text;
|
||||
if (config.RememberEffectiveChangesFilters)
|
||||
Set(config.Filters.EffectiveChangesGamePathFilter);
|
||||
FilterChanged += () => config.Filters.EffectiveChangesGamePathFilter = Text;
|
||||
}
|
||||
|
||||
protected override string ToFilterString(in Item item, int globalIndex)
|
||||
|
|
@ -155,10 +156,11 @@ public sealed class EffectiveTab(
|
|||
|
||||
private sealed class FullPathFilter : RegexFilterBase<Item>
|
||||
{
|
||||
public FullPathFilter(FilterConfig config)
|
||||
public FullPathFilter(Configuration config)
|
||||
{
|
||||
Set(config.EffectiveChangesFilePathFilter);
|
||||
FilterChanged += () => config.EffectiveChangesFilePathFilter = Text;
|
||||
if (config.RememberEffectiveChangesFilters)
|
||||
Set(config.Filters.EffectiveChangesFilePathFilter);
|
||||
FilterChanged += () => config.Filters.EffectiveChangesFilePathFilter = Text;
|
||||
}
|
||||
|
||||
protected override string ToFilterString(in Item item, int globalIndex)
|
||||
|
|
|
|||
|
|
@ -26,9 +26,13 @@ public sealed class MainTabBar : TabBar<TabType>, IDisposable
|
|||
ResourceTab resources,
|
||||
Watcher watcher,
|
||||
OnScreenTab onScreen,
|
||||
MessagesTab messages, EphemeralConfig config, CommunicatorService communicator, ModFileSystem modFileSystem)
|
||||
MessagesTab messages,
|
||||
ManagementTab.ManagementTab management,
|
||||
EphemeralConfig config,
|
||||
CommunicatorService communicator,
|
||||
ModFileSystem modFileSystem)
|
||||
: base(nameof(MainTabBar), log, settings, collections, mods, changedItems, effectiveChanges, onScreen,
|
||||
resources, watcher, debug, messages)
|
||||
resources, watcher, debug, messages, management)
|
||||
{
|
||||
_config = config;
|
||||
_modFileSystem = modFileSystem;
|
||||
|
|
|
|||
|
|
@ -4,9 +4,18 @@ using Penumbra.UI.AdvancedWindow;
|
|||
|
||||
namespace Penumbra.UI.MainWindow;
|
||||
|
||||
public sealed class OnScreenTab(ResourceTreeViewerFactory resourceTreeViewerFactory) : ITab<TabType>
|
||||
public sealed class OnScreenTab : ITab<TabType>
|
||||
{
|
||||
private readonly ResourceTreeViewer _viewer = resourceTreeViewerFactory.Create(0, delegate { }, delegate { });
|
||||
private readonly ResourceTreeViewer _viewer;
|
||||
|
||||
public OnScreenTab(Configuration config, ResourceTreeViewerFactory resourceTreeViewerFactory)
|
||||
{
|
||||
// Hack to handle config settings because no specific filters have been made yet.
|
||||
if (!config.RememberOnScreenFilters)
|
||||
config.Filters.ClearOnScreenFilters();
|
||||
|
||||
_viewer = resourceTreeViewerFactory.Create(0, delegate { }, delegate { });
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> Label
|
||||
=> "On-Screen"u8;
|
||||
|
|
|
|||
196
Penumbra/UI/ManagementTab/ManagementTab.cs
Normal file
196
Penumbra/UI/ManagementTab/ManagementTab.cs
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
using ImSharp;
|
||||
using Luna;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Manager;
|
||||
|
||||
namespace Penumbra.UI.ManagementTab;
|
||||
|
||||
public enum ManagementTabType
|
||||
{
|
||||
UnusedMods,
|
||||
DuplicateMods,
|
||||
Cleanup,
|
||||
}
|
||||
|
||||
public sealed class DuplicateModsTab(ModManager mods, CollectionStorage collections) : ITab<ManagementTabType>
|
||||
{
|
||||
public ReadOnlySpan<byte> Label
|
||||
=> "Duplicate Mods"u8;
|
||||
|
||||
public ManagementTabType Identifier
|
||||
=> ManagementTabType.DuplicateMods;
|
||||
|
||||
public void DrawContent()
|
||||
{
|
||||
using var table = Im.Table.Begin("duplicates"u8, 4, TableFlags.RowBackground);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
var cache = CacheManager.Instance.GetOrCreateCache(Im.Id.Current, () => new Cache(mods, collections));
|
||||
foreach (var item in cache.Items)
|
||||
{
|
||||
table.DrawFrameColumn(item.Name);
|
||||
foreach (var (mod, date, path, enabledCollections) in item.Data)
|
||||
{
|
||||
table.GoToColumn(0);
|
||||
table.DrawFrameColumn(path);
|
||||
table.DrawFrameColumn(date);
|
||||
table.DrawFrameColumn($"{enabledCollections.Length}");
|
||||
if (Im.Item.Hovered())
|
||||
{
|
||||
using var tt = Im.Tooltip.Begin();
|
||||
foreach (var collection in enabledCollections)
|
||||
Im.Text(collection.Identity.Name);
|
||||
}
|
||||
|
||||
table.NextRow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Cache(ModManager mods, CollectionStorage collections) : BasicCache
|
||||
{
|
||||
public readonly record struct CacheItem(
|
||||
StringU8 Name,
|
||||
(Mod Mod, StringU8 CreationDate, StringU8 Path, ModCollection[] Collections)[] Data);
|
||||
|
||||
public List<CacheItem> Items = [];
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (!Dirty.HasFlag(IManagedCache.DirtyFlags.Custom))
|
||||
return;
|
||||
|
||||
Items.Clear();
|
||||
Items.AddRange(mods.GroupBy(m => m.Name)
|
||||
.Select(kvp => new CacheItem(new StringU8(kvp.Key),
|
||||
kvp.Select(m => (m, new StringU8($"{DateTimeOffset.FromUnixTimeMilliseconds(m.ImportDate)}"),
|
||||
new StringU8(m.Path.CurrentPath),
|
||||
collections.Where(c => c.GetActualSettings(m.Index).Settings?.Enabled is true).ToArray()))
|
||||
.OrderByDescending(t => t.Item4.Length).ThenBy(t => t.m.ImportDate).ToArray())).Where(p => p.Data.Length > 1));
|
||||
|
||||
Dirty = IManagedCache.DirtyFlags.Clean;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class UnusedModsTab(ModConfigUpdater modConfigUpdater) : ITab<ManagementTabType>
|
||||
{
|
||||
public ReadOnlySpan<byte> Label
|
||||
=> "Unused Mods"u8;
|
||||
|
||||
public ManagementTabType Identifier
|
||||
=> ManagementTabType.UnusedMods;
|
||||
|
||||
public void DrawContent()
|
||||
{
|
||||
using var table = Im.Table.Begin("unused"u8, 5, TableFlags.RowBackground | TableFlags.SizingFixedFit);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
var cache = CacheManager.Instance.GetOrCreateCache(Im.Id.Current, () => new Cache(modConfigUpdater));
|
||||
using var clipper = new Im.ListClipper(cache.Data.Count, Im.Style.FrameHeightWithSpacing);
|
||||
foreach (var item in clipper.Iterate(cache.Data))
|
||||
{
|
||||
table.DrawFrameColumn(item.ModName);
|
||||
table.DrawFrameColumn(item.ModPath);
|
||||
table.DrawFrameColumn(item.Duration.Utf8);
|
||||
table.DrawFrameColumn(item.ModSizeString.Utf8);
|
||||
table.NextColumn();
|
||||
if (item.Notes.Length > 0)
|
||||
{
|
||||
ImEx.Icon.DrawAligned(LunaStyle.InfoIcon, LunaStyle.FavoriteColor);
|
||||
var hovered = Im.Item.Hovered();
|
||||
Im.Line.SameInner();
|
||||
Im.Text("Notes"u8);
|
||||
if (hovered || Im.Item.Hovered())
|
||||
{
|
||||
using var tt = Im.Tooltip.Begin();
|
||||
using (Im.Group())
|
||||
{
|
||||
foreach (var (plugin, _) in item.Notes)
|
||||
Im.Text(plugin.Utf8);
|
||||
}
|
||||
|
||||
using (Im.Group())
|
||||
{
|
||||
foreach (var (_, node) in item.Notes)
|
||||
Im.Text(node.Utf8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private sealed class Cache(ModConfigUpdater modConfigUpdater) : BasicCache
|
||||
{
|
||||
public readonly record struct CacheItem(
|
||||
Mod Mod,
|
||||
StringU8 ModName,
|
||||
StringU8 ModPath,
|
||||
long ModSize,
|
||||
StringPair ModSizeString,
|
||||
StringPair Duration,
|
||||
(StringPair, StringPair)[] Notes)
|
||||
{
|
||||
public CacheItem(Mod mod, (string, string)[] Notes, DateTime now)
|
||||
: this(mod, new StringU8(mod.Name), new StringU8(mod.Path.CurrentPath), 0, StringPair.Empty,
|
||||
new StringPair(FormattingFunctions.DurationString(mod.LastConfigEdit, now)),
|
||||
Notes.Select(n => (new StringPair(n.Item1), new StringPair(n.Item2))).ToArray())
|
||||
{ }
|
||||
}
|
||||
|
||||
public List<CacheItem> Data = [];
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (!Dirty.HasFlag(IManagedCache.DirtyFlags.Custom))
|
||||
return;
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
Data.Clear();
|
||||
foreach (var (mod, notes) in modConfigUpdater.ListUnusedMods(TimeSpan.Zero).OrderBy(mod => mod.Item1.LastConfigEdit))
|
||||
Data.Add(new CacheItem(mod, notes, now));
|
||||
Dirty = IManagedCache.DirtyFlags.Clean;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CleanupTab : ITab<ManagementTabType>
|
||||
{
|
||||
public ReadOnlySpan<byte> Label
|
||||
=> "General Cleanup"u8;
|
||||
|
||||
public ManagementTabType Identifier
|
||||
=> ManagementTabType.Cleanup;
|
||||
|
||||
public void DrawContent()
|
||||
{ }
|
||||
}
|
||||
|
||||
public sealed class ManagementTab : TabBar<ManagementTabType>, ITab<TabType>
|
||||
{
|
||||
public new ReadOnlySpan<byte> Label
|
||||
=> base.Label;
|
||||
|
||||
public TabType Identifier
|
||||
=> TabType.Management;
|
||||
|
||||
public ManagementTab(Logger log,
|
||||
EphemeralConfig config,
|
||||
UnusedModsTab unusedMods,
|
||||
DuplicateModsTab duplicateMods,
|
||||
CleanupTab cleanup)
|
||||
: base("Management", log, unusedMods, duplicateMods, cleanup)
|
||||
{
|
||||
NextTab = config.SelectedManagementTab;
|
||||
TabSelected.Subscribe((in tab) => config.SelectedManagementTab = tab, 0);
|
||||
}
|
||||
|
||||
public void DrawContent()
|
||||
=> Draw();
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ public class ModPanelTabBar : TabBar<ModPanelTab>
|
|||
|
||||
public ModPanelTabBar(ModEditWindowFactory modEditWindowFactory, ModPanelSettingsTab settings, ModPanelDescriptionTab description,
|
||||
ModPanelConflictsTab conflicts, ModPanelChangedItemsTab changedItems, ModPanelEditTab edit, ModManager modManager,
|
||||
TutorialService tutorial, ModPanelCollectionsTab collections, Logger log)
|
||||
TutorialService tutorial, ModPanelCollectionsTab collections, Logger log, EphemeralConfig config)
|
||||
: base(nameof(ModPanelTabBar), log, settings, description, conflicts, changedItems, collections, edit)
|
||||
{
|
||||
Flags = TabBarFlags.NoTooltip | TabBarFlags.FittingPolicyScroll;
|
||||
|
|
@ -37,7 +37,9 @@ public class ModPanelTabBar : TabBar<ModPanelTab>
|
|||
Edit = edit;
|
||||
_modManager = modManager;
|
||||
_tutorial = tutorial;
|
||||
NextTab = config.SelectedModPanelTab;
|
||||
Buttons.AddButton(new AdvancedEditingButton(this, modEditWindowFactory), 0);
|
||||
TabSelected.Subscribe((in v) => config.SelectedModPanelTab = v, 0);
|
||||
}
|
||||
|
||||
private sealed class AdvancedEditingButton(ModPanelTabBar parent, ModEditWindowFactory editFactory) : BaseButton
|
||||
|
|
|
|||
34
Penumbra/UI/ModsTab/Selector/Buttons/MoveModInput.cs
Normal file
34
Penumbra/UI/ModsTab/Selector/Buttons/MoveModInput.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using ImSharp;
|
||||
using Luna;
|
||||
|
||||
namespace Penumbra.UI.ModsTab.Selector;
|
||||
|
||||
public sealed class MoveModInput(ModFileSystemDrawer fileSystem) : BaseButton<IFileSystemData>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override ReadOnlySpan<byte> Label(in IFileSystemData _)
|
||||
=> "##Move"u8;
|
||||
|
||||
/// <summary> Replaces the normal menu item handling for a text input, so the other fields are not used. </summary>
|
||||
/// <inheritdoc/>
|
||||
public override bool DrawMenuItem(in IFileSystemData data)
|
||||
{
|
||||
var currentPath = data.FullPath;
|
||||
using var style = Im.Style.PushDefault(ImStyleDouble.FramePadding);
|
||||
MenuSeparator.DrawSeparator();
|
||||
Im.Text("Move Mod:"u8);
|
||||
if (Im.Window.Appearing)
|
||||
Im.Keyboard.SetFocusHere();
|
||||
var ret = Im.Input.Text(Label(data), ref currentPath, flags: InputTextFlags.EnterReturnsTrue);
|
||||
Im.Tooltip.OnHover(
|
||||
"Enter a full path here to move the mod or change its search path. Creates all required parent directories, if possible."u8);
|
||||
if (!ret)
|
||||
return false;
|
||||
|
||||
fileSystem.FileSystem.RenameAndMove(data, currentPath);
|
||||
fileSystem.FileSystem.ExpandAllAncestors(data);
|
||||
Im.Popup.CloseCurrent();
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
34
Penumbra/UI/ModsTab/Selector/Buttons/RenameModInput.cs
Normal file
34
Penumbra/UI/ModsTab/Selector/Buttons/RenameModInput.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using ImSharp;
|
||||
using Luna;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.UI.ModsTab.Selector;
|
||||
|
||||
public sealed class RenameModInput(ModFileSystemDrawer fileSystem) : BaseButton<IFileSystemData>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override ReadOnlySpan<byte> Label(in IFileSystemData _)
|
||||
=> "##Rename"u8;
|
||||
|
||||
/// <summary> Replaces the normal menu item handling for a text input, so the other fields are not used. </summary>
|
||||
/// <inheritdoc/>
|
||||
public override bool DrawMenuItem(in IFileSystemData data)
|
||||
{
|
||||
var mod = (Mod)data.Value;
|
||||
var currentName = mod.Name;
|
||||
using var style = Im.Style.PushDefault(ImStyleDouble.FramePadding);
|
||||
MenuSeparator.DrawSeparator();
|
||||
Im.Text("Rename Mod:"u8);
|
||||
if (Im.Window.Appearing)
|
||||
Im.Keyboard.SetFocusHere();
|
||||
var ret = Im.Input.Text(Label(data), ref currentName, flags: InputTextFlags.EnterReturnsTrue);
|
||||
Im.Tooltip.OnHover("Enter a new name here to rename the changed mod."u8);
|
||||
if (!ret)
|
||||
return false;
|
||||
|
||||
fileSystem.ModManager.DataEditor.ChangeModName(mod, currentName);
|
||||
Im.Popup.CloseCurrent();
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,16 +16,20 @@ public sealed class ModFilter : TokenizedFilter<ModFilterTokenType, ModFileSyste
|
|||
private readonly ModManager _modManager;
|
||||
private readonly ActiveCollections _collections;
|
||||
|
||||
public ModFilter(ModManager modManager, ActiveCollections collections, FilterConfig filterConfig)
|
||||
public ModFilter(ModManager modManager, ActiveCollections collections, Configuration config)
|
||||
{
|
||||
_modManager = modManager;
|
||||
_collections = collections;
|
||||
_stateFilter = filterConfig.ModTypeFilter;
|
||||
Set(filterConfig.ModFilter);
|
||||
if (config.RememberModFilters)
|
||||
{
|
||||
_stateFilter = config.Filters.ModTypeFilter;
|
||||
Set(config.Filters.ModFilter);
|
||||
}
|
||||
|
||||
FilterChanged += () =>
|
||||
{
|
||||
filterConfig.ModFilter = Text;
|
||||
filterConfig.ModTypeFilter = StateFilter;
|
||||
config.Filters.ModFilter = Text;
|
||||
config.Filters.ModTypeFilter = StateFilter;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using ImSharp;
|
||||
using Luna;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Mods;
|
||||
|
|
@ -8,7 +7,7 @@ using Penumbra.UI.Classes;
|
|||
|
||||
namespace Penumbra.UI.ModsTab.Selector;
|
||||
|
||||
public sealed class ModFileSystemDrawer : FileSystemDrawer<ModFileSystemCache.ModData>
|
||||
public sealed class ModFileSystemDrawer : FileSystemDrawer<ModFileSystemCache.ModData>, IDisposable
|
||||
{
|
||||
public readonly ModManager ModManager;
|
||||
public readonly CollectionManager CollectionManager;
|
||||
|
|
@ -20,7 +19,7 @@ public sealed class ModFileSystemDrawer : FileSystemDrawer<ModFileSystemCache.Mo
|
|||
|
||||
public ModFileSystemDrawer(ModFileSystem fileSystem, ModManager modManager, CollectionManager collectionManager, Configuration config,
|
||||
ModImportManager modImport, FileDialogService fileService, TutorialService tutorial, CommunicatorService communicator)
|
||||
: base(fileSystem, new ModFilter(modManager, collectionManager.Active, config.Filters))
|
||||
: base(fileSystem, new ModFilter(modManager, collectionManager.Active, config))
|
||||
{
|
||||
ModManager = modManager;
|
||||
CollectionManager = collectionManager;
|
||||
|
|
@ -31,6 +30,8 @@ public sealed class ModFileSystemDrawer : FileSystemDrawer<ModFileSystemCache.Mo
|
|||
Communicator = communicator;
|
||||
SortMode = Config.SortMode;
|
||||
|
||||
Config.ShowRenameChanged += SetRenameFields;
|
||||
|
||||
MainContext.AddButton(new ClearTemporarySettingsButton(this), 105);
|
||||
MainContext.AddButton(new ClearDefaultImportFolderButton(this), -10);
|
||||
MainContext.AddButton(new ClearQuickMoveFoldersButtons(this), -20);
|
||||
|
|
@ -45,6 +46,7 @@ public sealed class ModFileSystemDrawer : FileSystemDrawer<ModFileSystemCache.Mo
|
|||
DataContext.AddButton(new ToggleFavoriteButton(this), 10);
|
||||
DataContext.AddButton(new TemporaryButtons(this), 20);
|
||||
DataContext.AddButton(new MoveToQuickMoveFoldersButtons(this), -100);
|
||||
SetRenameFields(Config.ShowRename, default);
|
||||
|
||||
Footer.Buttons.AddButton(new AddNewModButton(this), 1000);
|
||||
Footer.Buttons.AddButton(new ImportModButton(this), 900);
|
||||
|
|
@ -72,4 +74,26 @@ public sealed class ModFileSystemDrawer : FileSystemDrawer<ModFileSystemCache.Mo
|
|||
else
|
||||
CollectionManager.Editor.SetMultipleModStates(CollectionManager.Active.Current, mods, enabled);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> Config.ShowRenameChanged -= SetRenameFields;
|
||||
|
||||
private void SetRenameFields(RenameField newField, RenameField _)
|
||||
{
|
||||
DataContext.RemoveButtons<MoveModInput>();
|
||||
DataContext.RemoveButtons<RenameModInput>();
|
||||
switch (newField)
|
||||
{
|
||||
case RenameField.RenameSearchPath: DataContext.AddButton(new RenameModInput(this), -1000); break;
|
||||
case RenameField.RenameData: DataContext.AddButton(new MoveModInput(this), -1000); break;
|
||||
case RenameField.BothSearchPathPrio:
|
||||
DataContext.AddButton(new RenameModInput(this), -1000);
|
||||
DataContext.AddButton(new MoveModInput(this), -1001);
|
||||
break;
|
||||
case RenameField.BothDataPrio:
|
||||
DataContext.AddButton(new RenameModInput(this), -1001);
|
||||
DataContext.AddButton(new MoveModInput(this), -1000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,17 +12,14 @@ using Penumbra.Interop.Hooks.Resources;
|
|||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.UI.Classes;
|
||||
|
||||
namespace Penumbra.UI.ResourceWatcher;
|
||||
|
||||
public sealed class ResourceWatcher : IDisposable, ITab<TabType>
|
||||
{
|
||||
public const int DefaultMaxEntries = 1024;
|
||||
public const RecordType AllRecords = RecordType.Request | RecordType.ResourceLoad | RecordType.FileLoad | RecordType.Destruction;
|
||||
public const int DefaultMaxEntries = 500;
|
||||
|
||||
private readonly Configuration _config;
|
||||
private readonly EphemeralConfig _ephemeral;
|
||||
private readonly FilterConfig _config;
|
||||
private readonly ResourceService _resources;
|
||||
private readonly ResourceLoader _loader;
|
||||
private readonly ResourceHandleDestructor _destructor;
|
||||
|
|
@ -30,50 +27,54 @@ public sealed class ResourceWatcher : IDisposable, ITab<TabType>
|
|||
private readonly ObservableList<Record> _records = [];
|
||||
private readonly ConcurrentQueue<Record> _newRecords = [];
|
||||
private readonly ResourceWatcherTable _table;
|
||||
private string _logFilter = string.Empty;
|
||||
private Regex? _logRegex;
|
||||
private int _newMaxEntries;
|
||||
private readonly RegexFilter _filter = new();
|
||||
|
||||
public unsafe ResourceWatcher(ActorManager actors, Configuration config, ResourceService resources, ResourceLoader loader,
|
||||
public unsafe ResourceWatcher(ActorManager actors, FilterConfig config, ResourceService resources, ResourceLoader loader,
|
||||
ResourceHandleDestructor destructor)
|
||||
{
|
||||
_actors = actors;
|
||||
_config = config;
|
||||
_ephemeral = config.Ephemeral;
|
||||
_actors = actors;
|
||||
_resources = resources;
|
||||
_destructor = destructor;
|
||||
_loader = loader;
|
||||
_table = new ResourceWatcherTable(config.Filters, _records);
|
||||
_table = new ResourceWatcherTable(_config, _records);
|
||||
_resources.ResourceRequested += OnResourceRequested;
|
||||
_destructor.Subscribe(OnResourceDestroyed, ResourceHandleDestructor.Priority.ResourceWatcher);
|
||||
_loader.ResourceLoaded += OnResourceLoaded;
|
||||
_loader.ResourceComplete += OnResourceComplete;
|
||||
_loader.FileLoaded += OnFileLoaded;
|
||||
_loader.PapRequested += OnPapRequested;
|
||||
UpdateFilter(_ephemeral.ResourceLoggingFilter, false);
|
||||
_newMaxEntries = _config.MaxResourceWatcherRecords;
|
||||
_filter.Set(_config.ResourceLoggerLogFilter);
|
||||
_filter.FilterChanged += () => _config.ResourceLoggerLogFilter = _filter.Text;
|
||||
}
|
||||
|
||||
private void OnPapRequested(Utf8GamePath original, FullPath? _1, ResolveData _2)
|
||||
{
|
||||
if (_ephemeral.EnableResourceLogging && FilterMatch(original.Path, out var match))
|
||||
if (_config.ResourceLoggerWriteToLog && Filter(original.Path, out var path))
|
||||
{
|
||||
Penumbra.Log.Information($"[ResourceLoader] [REQ] {match} was requested asynchronously.");
|
||||
Penumbra.Log.Information($"[ResourceLoader] [REQ] {path} was requested asynchronously.");
|
||||
if (_1.HasValue)
|
||||
Penumbra.Log.Information(
|
||||
$"[ResourceLoader] [LOAD] Resolved {_1.Value.FullName} for {match} from collection {_2.ModCollection} for object 0x{_2.AssociatedGameObject:X}.");
|
||||
$"[ResourceLoader] [LOAD] Resolved {_1.Value.FullName} for {path} from collection {_2.ModCollection} for object 0x{_2.AssociatedGameObject:X}.");
|
||||
}
|
||||
|
||||
if (!_ephemeral.EnableResourceWatcher)
|
||||
if (!_config.ResourceLoggerEnabled)
|
||||
return;
|
||||
|
||||
var record = _1.HasValue
|
||||
? Record.CreateRequest(original.Path, false, _1.Value, _2)
|
||||
: Record.CreateRequest(original.Path, false);
|
||||
if (!_ephemeral.OnlyAddMatchingResources || _table.WouldBeVisible(record))
|
||||
if (!_config.ResourceLoggerStoreOnlyMatching || _table.WouldBeVisible(record))
|
||||
Enqueue(record);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool Filter(CiByteString path, out string ret)
|
||||
{
|
||||
ret = path.ToString();
|
||||
return _filter.WouldBeVisible(ret);
|
||||
}
|
||||
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
Clear();
|
||||
|
|
@ -103,12 +104,8 @@ public sealed class ResourceWatcher : IDisposable, ITab<TabType>
|
|||
UpdateRecords();
|
||||
|
||||
Im.Cursor.Y += Im.Style.TextHeightWithSpacing / 2;
|
||||
var isEnabled = _ephemeral.EnableResourceWatcher;
|
||||
if (Im.Checkbox("Enable"u8, ref isEnabled))
|
||||
{
|
||||
_ephemeral.EnableResourceWatcher = isEnabled;
|
||||
_ephemeral.Save();
|
||||
}
|
||||
if (Im.Checkbox("Enable"u8, _config.ResourceLoggerEnabled))
|
||||
_config.ResourceLoggerEnabled ^= true;
|
||||
|
||||
Im.Line.Same();
|
||||
DrawMaxEntries();
|
||||
|
|
@ -117,20 +114,12 @@ public sealed class ResourceWatcher : IDisposable, ITab<TabType>
|
|||
Clear();
|
||||
|
||||
Im.Line.Same();
|
||||
var onlyMatching = _ephemeral.OnlyAddMatchingResources;
|
||||
if (Im.Checkbox("Store Only Matching"u8, ref onlyMatching))
|
||||
{
|
||||
_ephemeral.OnlyAddMatchingResources = onlyMatching;
|
||||
_ephemeral.Save();
|
||||
}
|
||||
if (Im.Checkbox("Store Only Matching"u8, _config.ResourceLoggerStoreOnlyMatching))
|
||||
_config.ResourceLoggerStoreOnlyMatching ^= true;
|
||||
|
||||
Im.Line.Same();
|
||||
var writeToLog = _ephemeral.EnableResourceLogging;
|
||||
if (Im.Checkbox("Write to Log"u8, ref writeToLog))
|
||||
{
|
||||
_ephemeral.EnableResourceLogging = writeToLog;
|
||||
_ephemeral.Save();
|
||||
}
|
||||
if (Im.Checkbox("Write to Log"u8, _config.ResourceLoggerWriteToLog))
|
||||
_config.ResourceLoggerWriteToLog ^= true;
|
||||
|
||||
Im.Line.Same();
|
||||
DrawFilterInput();
|
||||
|
|
@ -141,70 +130,22 @@ public sealed class ResourceWatcher : IDisposable, ITab<TabType>
|
|||
}
|
||||
|
||||
private void DrawFilterInput()
|
||||
{
|
||||
Im.Item.SetNextWidth(Im.ContentRegion.Available.X);
|
||||
var tmp = _logFilter;
|
||||
var invalidRegex = _logRegex is null && _logFilter.Length > 0;
|
||||
using var color = ImStyleBorder.Frame.Push(Colors.RegexWarningBorder, Im.Style.GlobalScale, invalidRegex);
|
||||
if (Im.Input.Text("##logFilter"u8, ref tmp, "If path matches this Regex..."u8))
|
||||
UpdateFilter(tmp, true);
|
||||
}
|
||||
|
||||
private void UpdateFilter(string newString, bool config)
|
||||
{
|
||||
if (newString == _logFilter)
|
||||
return;
|
||||
|
||||
_logFilter = newString;
|
||||
try
|
||||
{
|
||||
_logRegex = new Regex(_logFilter, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_logRegex = null;
|
||||
}
|
||||
|
||||
if (config)
|
||||
{
|
||||
_ephemeral.ResourceLoggingFilter = newString;
|
||||
_ephemeral.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private bool FilterMatch(CiByteString path, out string match)
|
||||
{
|
||||
match = path.ToString();
|
||||
return _logFilter.Length == 0 || (_logRegex?.IsMatch(match) ?? false) || match.Contains(_logFilter, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
=> _filter.DrawFilter("If path matches this Regex..."u8, Im.ContentRegion.Available);
|
||||
|
||||
private void DrawMaxEntries()
|
||||
{
|
||||
Im.Item.SetNextWidthScaled(80);
|
||||
Im.Input.Scalar("Max. Entries"u8, ref _newMaxEntries);
|
||||
var change = Im.Item.DeactivatedAfterEdit;
|
||||
if (ImEx.InputOnDeactivation.Scalar("Max. Entries"u8, _config.ResourceLoggerMaxEntries, out var newValue))
|
||||
_config.ResourceLoggerMaxEntries = Math.Max(16, newValue);
|
||||
|
||||
if (Im.Item.RightClicked() && Im.Io.KeyControl)
|
||||
{
|
||||
change = true;
|
||||
_newMaxEntries = DefaultMaxEntries;
|
||||
}
|
||||
_config.ResourceLoggerMaxEntries = DefaultMaxEntries;
|
||||
|
||||
var maxEntries = _config.MaxResourceWatcherRecords;
|
||||
if (maxEntries != DefaultMaxEntries && Im.Item.Hovered())
|
||||
Im.Tooltip.Set($"CTRL + Right-Click to reset to default {DefaultMaxEntries}.");
|
||||
if (_config.ResourceLoggerMaxEntries is not DefaultMaxEntries && Im.Item.Hovered())
|
||||
Im.Tooltip.Set("Control + Right-Click to reset to default 500."u8);
|
||||
|
||||
if (!change)
|
||||
return;
|
||||
|
||||
_newMaxEntries = Math.Max(16, _newMaxEntries);
|
||||
if (_newMaxEntries == maxEntries)
|
||||
return;
|
||||
|
||||
_config.MaxResourceWatcherRecords = _newMaxEntries;
|
||||
_config.Save();
|
||||
if (_newMaxEntries > _records.Count)
|
||||
_records.RemoveRange(0, _records.Count - _newMaxEntries);
|
||||
if (_records.Count > _config.ResourceLoggerMaxEntries)
|
||||
_records.RemoveRange(0, _records.Count - _config.ResourceLoggerMaxEntries);
|
||||
}
|
||||
|
||||
private void UpdateRecords()
|
||||
|
|
@ -216,49 +157,49 @@ public sealed class ResourceWatcher : IDisposable, ITab<TabType>
|
|||
while (_newRecords.TryDequeue(out var rec) && count-- > 0)
|
||||
_records.Add(rec);
|
||||
|
||||
if (_records.Count > _config.MaxResourceWatcherRecords)
|
||||
_records.RemoveRange(0, _records.Count - _config.MaxResourceWatcherRecords);
|
||||
if (_records.Count > _config.ResourceLoggerMaxEntries)
|
||||
_records.RemoveRange(0, _records.Count - _config.ResourceLoggerMaxEntries);
|
||||
}
|
||||
|
||||
|
||||
private unsafe void OnResourceRequested(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path,
|
||||
Utf8GamePath original, GetResourceParameters* parameters, ref bool sync, ref ResourceHandle* returnValue)
|
||||
{
|
||||
if (_ephemeral.EnableResourceLogging && FilterMatch(original.Path, out var match))
|
||||
if (_config.ResourceLoggerWriteToLog && Filter(original.Path, out var match))
|
||||
Penumbra.Log.Information($"[ResourceLoader] [REQ] {match} was requested {(sync ? "synchronously." : "asynchronously.")}");
|
||||
|
||||
if (!_ephemeral.EnableResourceWatcher)
|
||||
if (!_config.ResourceLoggerEnabled)
|
||||
return;
|
||||
|
||||
var record = Record.CreateRequest(original.Path, sync);
|
||||
if (!_ephemeral.OnlyAddMatchingResources || _table.WouldBeVisible(record))
|
||||
if (!_config.ResourceLoggerStoreOnlyMatching || _table.WouldBeVisible(record))
|
||||
Enqueue(record);
|
||||
}
|
||||
|
||||
private unsafe void OnResourceLoaded(ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, ResolveData data)
|
||||
{
|
||||
if (_ephemeral.EnableResourceLogging)
|
||||
if (_config.ResourceLoggerWriteToLog)
|
||||
{
|
||||
var log = FilterMatch(path.Path, out var name);
|
||||
var log = Filter(path.Path, out var name);
|
||||
var name2 = string.Empty;
|
||||
if (manipulatedPath != null)
|
||||
log |= FilterMatch(manipulatedPath.Value.InternalName, out name2);
|
||||
if (manipulatedPath is not null)
|
||||
log |= Filter(manipulatedPath.Value.InternalName, out name2);
|
||||
|
||||
if (log)
|
||||
{
|
||||
var pathString = manipulatedPath != null ? $"custom file {name2} instead of {name}" : name;
|
||||
var pathString = manipulatedPath is not null ? $"custom file {name2} instead of {name}" : name;
|
||||
Penumbra.Log.Information(
|
||||
$"[ResourceLoader] [LOAD] [{handle->FileType}] Loaded {pathString} to 0x{(ulong)handle:X} using collection {data.ModCollection.Identity.AnonymizedName} for {Name(data, "no associated object.")} (Refcount {handle->RefCount}) ");
|
||||
}
|
||||
}
|
||||
|
||||
if (!_ephemeral.EnableResourceWatcher)
|
||||
if (!_config.ResourceLoggerEnabled)
|
||||
return;
|
||||
|
||||
var record = manipulatedPath == null
|
||||
? Record.CreateDefaultLoad(path.Path, handle, data.ModCollection, Name(data))
|
||||
: Record.CreateLoad(manipulatedPath.Value, path.Path, handle, data.ModCollection, Name(data));
|
||||
if (!_ephemeral.OnlyAddMatchingResources || _table.WouldBeVisible(record))
|
||||
if (!_config.ResourceLoggerStoreOnlyMatching || _table.WouldBeVisible(record))
|
||||
Enqueue(record);
|
||||
}
|
||||
|
||||
|
|
@ -268,43 +209,43 @@ public sealed class ResourceWatcher : IDisposable, ITab<TabType>
|
|||
if (!isAsync)
|
||||
return;
|
||||
|
||||
if (_ephemeral.EnableResourceLogging && FilterMatch(path, out var match))
|
||||
if (_config.ResourceLoggerWriteToLog && Filter(path, out var match))
|
||||
Penumbra.Log.Information(
|
||||
$"[ResourceLoader] [DONE] [{resource->FileType}] Finished loading {match} into 0x{(ulong)resource:X}, state {resource->LoadState}.");
|
||||
|
||||
if (!_ephemeral.EnableResourceWatcher)
|
||||
if (!_config.ResourceLoggerEnabled)
|
||||
return;
|
||||
|
||||
var record = Record.CreateResourceComplete(path, resource, original, additionalData);
|
||||
if (!_ephemeral.OnlyAddMatchingResources || _table.WouldBeVisible(record))
|
||||
if (!_config.ResourceLoggerStoreOnlyMatching || _table.WouldBeVisible(record))
|
||||
Enqueue(record);
|
||||
}
|
||||
|
||||
private unsafe void OnFileLoaded(ResourceHandle* resource, CiByteString path, bool success, bool custom, ReadOnlySpan<byte> _)
|
||||
{
|
||||
if (_ephemeral.EnableResourceLogging && FilterMatch(path, out var match))
|
||||
if (_config.ResourceLoggerWriteToLog && Filter(path, out var match))
|
||||
Penumbra.Log.Information(
|
||||
$"[ResourceLoader] [FILE] [{resource->FileType}] Loading {match} from {(custom ? "local files" : "SqPack")} into 0x{(ulong)resource:X} returned {success}.");
|
||||
|
||||
if (!_ephemeral.EnableResourceWatcher)
|
||||
if (!_config.ResourceLoggerEnabled)
|
||||
return;
|
||||
|
||||
var record = Record.CreateFileLoad(path, resource, success, custom);
|
||||
if (!_ephemeral.OnlyAddMatchingResources || _table.WouldBeVisible(record))
|
||||
if (!_config.ResourceLoggerStoreOnlyMatching || _table.WouldBeVisible(record))
|
||||
Enqueue(record);
|
||||
}
|
||||
|
||||
private unsafe void OnResourceDestroyed(in ResourceHandleDestructor.Arguments arguments)
|
||||
{
|
||||
if (_ephemeral.EnableResourceLogging && FilterMatch(arguments.ResourceHandle->FileName(), out var match))
|
||||
if (_config.ResourceLoggerWriteToLog && Filter(arguments.ResourceHandle->FileName(), out var match))
|
||||
Penumbra.Log.Information(
|
||||
$"[ResourceLoader] [DEST] [{arguments.ResourceHandle->FileType}] Destroyed {match} at 0x{(ulong)arguments.ResourceHandle:X}.");
|
||||
|
||||
if (!_ephemeral.EnableResourceWatcher)
|
||||
if (!_config.ResourceLoggerEnabled)
|
||||
return;
|
||||
|
||||
var record = Record.CreateDestruction(arguments.ResourceHandle);
|
||||
if (!_ephemeral.OnlyAddMatchingResources || _table.WouldBeVisible(record))
|
||||
if (!_config.ResourceLoggerStoreOnlyMatching || _table.WouldBeVisible(record))
|
||||
Enqueue(record);
|
||||
}
|
||||
|
||||
|
|
@ -337,7 +278,7 @@ public sealed class ResourceWatcher : IDisposable, ITab<TabType>
|
|||
private void Enqueue(Record record)
|
||||
{
|
||||
// Discard entries that exceed the number of records.
|
||||
while (_newRecords.Count >= _config.MaxResourceWatcherRecords)
|
||||
while (_newRecords.Count >= _config.ResourceLoggerMaxEntries)
|
||||
_newRecords.TryDequeue(out _);
|
||||
_newRecords.Enqueue(record);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,14 +20,15 @@ public sealed class ResourceTab(Configuration config, ResourceManagerService res
|
|||
public bool IsVisible
|
||||
=> config.DebugMode;
|
||||
|
||||
public readonly ResourceFilter Filter = new(config.Filters);
|
||||
public readonly ResourceFilter Filter = new(config);
|
||||
|
||||
public sealed class ResourceFilter : Utf8FilterBase<ResourceHandle>
|
||||
{
|
||||
public ResourceFilter(FilterConfig filterConfig)
|
||||
public ResourceFilter(Configuration config)
|
||||
{
|
||||
Set(new StringU8(filterConfig.ResourceManagerFilter));
|
||||
FilterChanged += () => filterConfig.ResourceManagerFilter = Text.ToString();
|
||||
if (config.RememberResourceManagerFilters)
|
||||
Set(new StringU8(config.Filters.ResourceManagerFilter));
|
||||
FilterChanged += () => config.Filters.ResourceManagerFilter = Text.ToString();
|
||||
}
|
||||
|
||||
protected override ReadOnlySpan<byte> ToFilterString(in ResourceHandle item, int globalIndex)
|
||||
|
|
@ -58,9 +59,9 @@ public sealed class ResourceTab(Configuration config, ResourceManagerService res
|
|||
Penumbra.Dynamis.DrawPointer(resourceManager.ResourceManager);
|
||||
}
|
||||
|
||||
private float _hashColumnWidth;
|
||||
private float _pathColumnWidth;
|
||||
private float _refsColumnWidth;
|
||||
private float _hashColumnWidth;
|
||||
private float _pathColumnWidth;
|
||||
private float _refsColumnWidth;
|
||||
|
||||
/// <summary> Draw a single resource map. </summary>
|
||||
private unsafe void DrawResourceMap(ResourceCategory category, uint ext,
|
||||
|
|
@ -91,19 +92,23 @@ public sealed class ResourceTab(Configuration config, ResourceManagerService res
|
|||
return;
|
||||
|
||||
Im.Table.DrawColumn($"0x{hash:X8}");
|
||||
if (Im.Item.Clicked())
|
||||
Im.Clipboard.Set($"0x{hash:X8}");
|
||||
Im.Table.NextColumn();
|
||||
Penumbra.Dynamis.DrawPointer(r);
|
||||
var resource = (Interop.Structs.ResourceHandle*)r;
|
||||
Im.Table.DrawColumn(resource->FileName().Span);
|
||||
if (Im.Item.Clicked())
|
||||
{
|
||||
Im.Clipboard.Set(resource->FileName().Span);
|
||||
}
|
||||
else if (Im.Item.RightClicked())
|
||||
{
|
||||
var data = resource->CsHandle.GetData();
|
||||
if (data != null)
|
||||
{
|
||||
var length = (int)resource->CsHandle.GetLength();
|
||||
Im.Clipboard.Set(StringU8.Join((byte) ' ',
|
||||
Im.Clipboard.Set(StringU8.Join((byte)' ',
|
||||
new ReadOnlySpan<byte>(data, length).ToArray().Select(b => b.ToString("X2"))));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -413,6 +413,27 @@ public sealed class SettingsTab : ITab<TabType>
|
|||
_config.HideUiInGPose = v;
|
||||
_pluginInterface.UiBuilder.DisableGposeUiHide = !v;
|
||||
});
|
||||
|
||||
Im.Separator();
|
||||
Checkbox("Remember Mod Filters Across Sessions"u8,
|
||||
"Whether filters in the Mods tab should remember their input and start with their respective lists filtered identically to the last session."u8,
|
||||
_config.RememberModFilters, v => _config.RememberModFilters = v);
|
||||
Checkbox("Remember Collection Filters Across Sessions"u8,
|
||||
"Whether filters in the Collections tab should remember their input and start with their respective lists filtered identically to the last session."u8,
|
||||
_config.RememberCollectionFilters, v => _config.RememberCollectionFilters = v);
|
||||
Checkbox("Remember Changed Items Filters Across Sessions"u8,
|
||||
"Whether filters in the Changed Items tab should remember their input and start with their respective lists filtered identically to the last session."u8,
|
||||
_config.RememberChangedItemFilters, v => _config.RememberChangedItemFilters= v);
|
||||
Checkbox("Remember Effective Changes Filters Across Sessions"u8,
|
||||
"Whether filters in the Effective Changes tab should remember their input and start with their respective lists filtered identically to the last session."u8,
|
||||
_config.RememberEffectiveChangesFilters, v => _config.RememberEffectiveChangesFilters = v);
|
||||
Checkbox("Remember On-Screen Filters Across Sessions"u8,
|
||||
"Whether filters in the On-Screen tab should remember their input and start with their respective lists filtered identically to the last session."u8,
|
||||
_config.RememberOnScreenFilters, v => _config.RememberOnScreenFilters = v);
|
||||
Checkbox("Remember Resource Manager Filters Across Sessions"u8,
|
||||
"Whether filters in the Resource Manager tab should remember their input and start with their respective lists filtered identically to the last session."u8,
|
||||
_config.RememberResourceManagerFilters, v => _config.RememberResourceManagerFilters = v);
|
||||
|
||||
}
|
||||
|
||||
/// <summary> Draw all settings that do not fit into other categories. </summary>
|
||||
|
|
@ -433,7 +454,7 @@ public sealed class SettingsTab : ITab<TabType>
|
|||
if (v)
|
||||
{
|
||||
_config.Filters.ModChangedItemTypeFilter = ChangedItemFlagExtensions.AllFlags;
|
||||
_config.Filters.ChangedItemTypeFilter = ChangedItemFlagExtensions.AllFlags;
|
||||
_config.Filters.ChangedItemTypeFilter = ChangedItemFlagExtensions.AllFlags;
|
||||
_config.Ephemeral.Save();
|
||||
}
|
||||
});
|
||||
|
|
@ -520,15 +541,10 @@ public sealed class SettingsTab : ITab<TabType>
|
|||
using (var combo = Im.Combo.Begin("##renameSettings"u8, _config.ShowRename.ToNameU8()))
|
||||
{
|
||||
if (combo)
|
||||
foreach (var value in Enum.GetValues<RenameField>())
|
||||
foreach (var value in RenameField.Values)
|
||||
{
|
||||
if (Im.Selectable(value.ToNameU8(), _config.ShowRename == value))
|
||||
{
|
||||
_config.ShowRename = value;
|
||||
// TODO
|
||||
// _selector.SetRenameSearchPath(value);
|
||||
_config.Save();
|
||||
}
|
||||
|
||||
Im.Tooltip.OnHover(value.Tooltip());
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue