From d7c48ae776d1155edf70aa930b76dbba52e9e328 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 3 Jan 2026 23:25:44 +0100 Subject: [PATCH] OtterGui and Dalamud ImGui gone. --- Luna | 2 +- Penumbra.GameData | 2 +- Penumbra.sln | 10 +- Penumbra/Api/Api/ModsApi.cs | 14 +- Penumbra/Communication/CollectionChange.cs | 2 +- .../CollectionInheritanceChanged.cs | 2 +- Penumbra/Communication/ModDataChanged.cs | 2 +- Penumbra/Communication/ModSettingChanged.cs | 2 +- Penumbra/{ => Config}/Configuration.cs | 20 +- Penumbra/{ => Config}/EphemeralConfig.cs | 30 +- Penumbra/Config/PcpSettings.cs | 11 + Penumbra/Config/UiConfig.cs | 108 +++ Penumbra/Import/Textures/TexFileParser.cs | 6 +- Penumbra/Interop/GameState.cs | 5 +- .../Animation/ApricotListenerSoundPlay.cs | 16 +- .../Animation/CreateApricotDocumentHook.cs | 42 + Penumbra/Interop/Hooks/DebugHook.cs | 26 +- Penumbra/Interop/Hooks/HookSettings.cs | 1 + .../Processing/AvfxPathPreProcessor.cs | 7 +- Penumbra/Mods/Manager/ModFileSystem.cs | 201 +--- Penumbra/Mods/Manager/ModManager.cs | 13 +- Penumbra/Mods/ModSelection.cs | 7 - Penumbra/Penumbra.cs | 7 +- Penumbra/Penumbra.csproj | 2 +- Penumbra/Penumbra.csproj.DotSettings | 1 + Penumbra/Services/FilenameService.cs | 1 + Penumbra/Services/PcpService.cs | 4 +- Penumbra/Services/StainService.cs | 10 +- Penumbra/Services/StaticServiceManager.cs | 7 - Penumbra/UI/AdvancedWindow/ItemSelector.cs | 7 +- Penumbra/UI/AdvancedWindow/ItemSwapTab.cs | 23 +- .../UI/AdvancedWindow/ItemSwapTabFactory.cs | 14 +- .../Materials/MtrlTab.ColorTable.cs | 22 +- .../Materials/MtrlTab.CommonColorTable.cs | 2 +- Penumbra/UI/Classes/FileDialogService.cs | 6 +- Penumbra/UI/MainWindow/ChangedItemsTab.cs | 6 +- Penumbra/UI/MainWindow/MainTabBar.cs | 23 +- .../ModsTab/Groups/ImcModGroupEditDrawer.cs | 19 +- Penumbra/UI/ModsTab/ModFileSystemSelector.cs | 856 ------------------ .../UI/ModsTab/ModPanelChangedItemsTab.cs | 57 +- Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs | 14 +- Penumbra/UI/ModsTab/ModPanelConflictsTab.cs | 21 +- Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs | 18 +- Penumbra/UI/ModsTab/ModPanelEditTab.cs | 14 +- .../UI/ModsTab/ModSearchStringSplitter.cs | 138 --- Penumbra/UI/ModsTab/ModTab.cs | 42 + Penumbra/UI/ModsTab/MultiModPanel.cs | 308 ++++--- .../Selector/Buttons/AddNewModButton.cs | 66 +- .../Buttons/ClearQuickMoveFoldersButtons.cs | 50 + .../Selector/Buttons/DeleteSelectionButton.cs | 53 ++ .../UI/ModsTab/Selector/Buttons/HelpButton.cs | 91 ++ .../Selector/Buttons/ImportModButton.cs | 45 + .../Buttons/MoveToQuickMoveFoldersButtons.cs | 84 ++ .../Selector/Buttons/SetQuickMoveButtons.cs | 35 + .../Selector/Buttons/TemporaryButtons.cs | 42 + .../UI/ModsTab/Selector/ModFileSystemCache.cs | 77 +- .../ModsTab/Selector/ModFileSystemDrawer.cs | 34 +- .../UI/ResourceWatcher/ResourceWatcher.cs | 11 +- .../ResourceWatcher/ResourceWatcherTable.cs | 102 ++- Penumbra/UI/Tabs/CollectionButtonFooter.cs | 3 + Penumbra/UI/Tabs/CollectionsTab.cs | 19 +- Penumbra/UI/Tabs/Debug/DebugTab.cs | 7 +- Penumbra/UI/Tabs/Debug/ShapeInspector.cs | 13 +- Penumbra/UI/Tabs/ModsTab.cs | 181 ---- Penumbra/UI/Tabs/SettingsTab.cs | 50 +- 65 files changed, 1206 insertions(+), 1908 deletions(-) rename Penumbra/{ => Config}/Configuration.cs (95%) rename Penumbra/{ => Config}/EphemeralConfig.cs (77%) create mode 100644 Penumbra/Config/PcpSettings.cs create mode 100644 Penumbra/Config/UiConfig.cs create mode 100644 Penumbra/Interop/Hooks/Animation/CreateApricotDocumentHook.cs delete mode 100644 Penumbra/UI/ModsTab/ModFileSystemSelector.cs delete mode 100644 Penumbra/UI/ModsTab/ModSearchStringSplitter.cs create mode 100644 Penumbra/UI/ModsTab/ModTab.cs create mode 100644 Penumbra/UI/ModsTab/Selector/Buttons/ClearQuickMoveFoldersButtons.cs create mode 100644 Penumbra/UI/ModsTab/Selector/Buttons/DeleteSelectionButton.cs create mode 100644 Penumbra/UI/ModsTab/Selector/Buttons/HelpButton.cs create mode 100644 Penumbra/UI/ModsTab/Selector/Buttons/ImportModButton.cs create mode 100644 Penumbra/UI/ModsTab/Selector/Buttons/MoveToQuickMoveFoldersButtons.cs create mode 100644 Penumbra/UI/ModsTab/Selector/Buttons/SetQuickMoveButtons.cs create mode 100644 Penumbra/UI/ModsTab/Selector/Buttons/TemporaryButtons.cs delete mode 100644 Penumbra/UI/Tabs/ModsTab.cs diff --git a/Luna b/Luna index d81c7881..91a282ca 160000 --- a/Luna +++ b/Luna @@ -1 +1 @@ -Subproject commit d81c788133b8b557febbad0bf74baee9588215eb +Subproject commit 91a282ca7aa716f1774975a7e286c4b603bcd6aa diff --git a/Penumbra.GameData b/Penumbra.GameData index a6b170cb..bab75501 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit a6b170cbd5ef2936466a8c85ba4a4ad495d4f3c9 +Subproject commit bab75501cd499de7d1c28883fe87dde98c772a3c diff --git a/Penumbra.sln b/Penumbra.sln index 2acdf570..be81b9d2 100644 --- a/Penumbra.sln +++ b/Penumbra.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.2.32210.308 +# Visual Studio Version 18 +VisualStudioVersion = 18.3.11312.210 d18.3 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra", "Penumbra\Penumbra.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}" EndProject @@ -17,8 +17,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "Penumbra.GameData\Penumbra.GameData.csproj", "{EE551E87-FDB3-4612-B500-DC870C07C605}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "OtterGui\OtterGui.csproj", "{87750518-1A20-40B4-9FC1-22F906EFB290}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.Api", "Penumbra.Api\Penumbra.Api.csproj", "{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra.String\Penumbra.String.csproj", "{5549BAFD-6357-4B1A-800C-75AC36E5B76D}" @@ -73,10 +71,6 @@ Global {EE551E87-FDB3-4612-B500-DC870C07C605}.Debug|x64.Build.0 = Debug|x64 {EE551E87-FDB3-4612-B500-DC870C07C605}.Release|x64.ActiveCfg = Release|x64 {EE551E87-FDB3-4612-B500-DC870C07C605}.Release|x64.Build.0 = Release|x64 - {87750518-1A20-40B4-9FC1-22F906EFB290}.Debug|x64.ActiveCfg = Debug|x64 - {87750518-1A20-40B4-9FC1-22F906EFB290}.Debug|x64.Build.0 = Debug|x64 - {87750518-1A20-40B4-9FC1-22F906EFB290}.Release|x64.ActiveCfg = Release|x64 - {87750518-1A20-40B4-9FC1-22F906EFB290}.Release|x64.Build.0 = Release|x64 {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Debug|x64.ActiveCfg = Debug|x64 {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Debug|x64.Build.0 = Debug|x64 {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Release|x64.ActiveCfg = Release|x64 diff --git a/Penumbra/Api/Api/ModsApi.cs b/Penumbra/Api/Api/ModsApi.cs index 348dc250..5d4e2392 100644 --- a/Penumbra/Api/Api/ModsApi.cs +++ b/Penumbra/Api/Api/ModsApi.cs @@ -122,13 +122,12 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable public (PenumbraApiEc, string, bool, bool) GetModPath(string modDirectory, string modName) { - if (!_modManager.TryGetMod(modDirectory, modName, out var mod) - || !_modFileSystem.TryGetValue(mod, out var leaf)) + if (!_modManager.TryGetMod(modDirectory, modName, out var mod) || mod.Node is not { } node) return (PenumbraApiEc.ModMissing, string.Empty, false, false); - var fullPath = leaf.FullName(); - var isDefault = ModFileSystem.ModHasDefaultPath(mod, fullPath); - var isNameDefault = isDefault || ModFileSystem.ModHasDefaultPath(mod, leaf.Name); + var fullPath = node.FullPath; + var isDefault = mod.Path.IsDefault; + var isNameDefault = mod.Path.SortName is null; return (PenumbraApiEc.Success, fullPath, !isDefault, !isNameDefault); } @@ -137,13 +136,12 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable if (newPath.Length == 0) return PenumbraApiEc.InvalidArgument; - if (!_modManager.TryGetMod(modDirectory, modName, out var mod) - || !_modFileSystem.TryGetValue(mod, out var leaf)) + if (!_modManager.TryGetMod(modDirectory, modName, out var mod) || mod.Node is not { } node) return PenumbraApiEc.ModMissing; try { - _modFileSystem.RenameAndMove(leaf, newPath); + _modFileSystem.RenameAndMove(node, newPath); return PenumbraApiEc.Success; } catch diff --git a/Penumbra/Communication/CollectionChange.cs b/Penumbra/Communication/CollectionChange.cs index e6aa8ac2..8ed978f6 100644 --- a/Penumbra/Communication/CollectionChange.cs +++ b/Penumbra/Communication/CollectionChange.cs @@ -46,7 +46,7 @@ public sealed class CollectionChange(Logger log) IndividualAssignmentUi = 0, /// - ModFileSystemSelector = 0, + ModFileSystemCache = 0, /// ModSelection = 10, diff --git a/Penumbra/Communication/CollectionInheritanceChanged.cs b/Penumbra/Communication/CollectionInheritanceChanged.cs index c917875d..5de5414f 100644 --- a/Penumbra/Communication/CollectionInheritanceChanged.cs +++ b/Penumbra/Communication/CollectionInheritanceChanged.cs @@ -22,7 +22,7 @@ public sealed class CollectionInheritanceChanged(Logger log) ItemSwapTab = 0, /// - ModFileSystemSelector = 0, + ModFileSystemCache = 0, /// ModSelection = 10, diff --git a/Penumbra/Communication/ModDataChanged.cs b/Penumbra/Communication/ModDataChanged.cs index 325247f5..af870870 100644 --- a/Penumbra/Communication/ModDataChanged.cs +++ b/Penumbra/Communication/ModDataChanged.cs @@ -10,7 +10,7 @@ public sealed class ModDataChanged(Logger log) : EventBase - ModFileSystemSelector = -10, + ModFileSystemCache = -10, /// ModCacheManager = 0, diff --git a/Penumbra/Communication/ModSettingChanged.cs b/Penumbra/Communication/ModSettingChanged.cs index a41658a9..7cf13db5 100644 --- a/Penumbra/Communication/ModSettingChanged.cs +++ b/Penumbra/Communication/ModSettingChanged.cs @@ -27,7 +27,7 @@ public sealed class ModSettingChanged(Logger log) ItemSwapTab = 0, /// - ModFileSystemSelector = 0, + ModFileSystemCache = 0, /// ModSelection = 10, diff --git a/Penumbra/Configuration.cs b/Penumbra/Config/Configuration.cs similarity index 95% rename from Penumbra/Configuration.cs rename to Penumbra/Config/Configuration.cs index 828d03bb..16dde140 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Config/Configuration.cs @@ -1,6 +1,5 @@ using Dalamud.Configuration; using Dalamud.Interface.ImGuiNotification; -using ImSharp; using Luna; using Newtonsoft.Json; using Penumbra.Import.Structs; @@ -14,16 +13,6 @@ using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; namespace Penumbra; -public record PcpSettings -{ - public bool CreateCollection { get; set; } = true; - public bool AssignCollection { get; set; } = true; - public bool AllowIpc { get; set; } = true; - public bool DisableHandling { get; set; } = false; - public string FolderName { get; set; } = "PCP"; - public string PcpExtension { get; set; } = ".pcp"; -} - [Serializable] public class Configuration : IPluginConfiguration, ISavable, IService { @@ -33,6 +22,9 @@ public class Configuration : IPluginConfiguration, ISavable, IService [JsonIgnore] public readonly EphemeralConfig Ephemeral; + [JsonIgnore] + public readonly UiConfig Ui; + public int Version { get; set; } = Constants.CurrentVersion; public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New; @@ -120,7 +112,7 @@ public class Configuration : IPluginConfiguration, ISavable, IService public bool EditRawTileTransforms { get; set; } = false; public bool WholePairSelectorAlwaysHighlights { get; set; } = false; - public bool HdrRenderTargets { get; set; } = true; + public bool HdrRenderTargets { get; set; } = true; public Dictionary Colors { get; set; } = Enum.GetValues().ToDictionary(c => c, c => c.Data().DefaultColor); @@ -129,10 +121,12 @@ public class Configuration : IPluginConfiguration, ISavable, IService /// Load the current configuration. /// Includes adding new colors and migrating from old versions. /// - public Configuration(CharacterUtility utility, ConfigMigrationService migrator, SaveService saveService, EphemeralConfig ephemeral) + public Configuration(CharacterUtility utility, ConfigMigrationService migrator, SaveService saveService, EphemeralConfig ephemeral, + UiConfig ui) { _saveService = saveService; Ephemeral = ephemeral; + Ui = ui; Load(utility, migrator); } diff --git a/Penumbra/EphemeralConfig.cs b/Penumbra/Config/EphemeralConfig.cs similarity index 77% rename from Penumbra/EphemeralConfig.cs rename to Penumbra/Config/EphemeralConfig.cs index 4cd2516e..2eb8d038 100644 --- a/Penumbra/EphemeralConfig.cs +++ b/Penumbra/Config/EphemeralConfig.cs @@ -1,9 +1,7 @@ using Dalamud.Interface.ImGuiNotification; using Luna; using Newtonsoft.Json; -using Penumbra.Communication; using Penumbra.Enums; -using Penumbra.Mods.Manager; using Penumbra.Services; using Penumbra.UI; using Penumbra.UI.Classes; @@ -14,18 +12,11 @@ using TabType = Penumbra.Api.Enums.TabType; namespace Penumbra; -public class EphemeralConfig : ISavable, IDisposable, IService +public class EphemeralConfig : ISavable, IService { [JsonIgnore] private readonly SaveService _saveService; - [JsonIgnore] - private readonly ModPathChanged _modPathChanged; - - public float CurrentModSelectorWidth { get; set; } = 200f; - public float ModSelectorMinimumScale { get; set; } = 0.1f; - public float ModSelectorMaximumScale { get; set; } = 0.5f; - public int Version { get; set; } = Configuration.Constants.CurrentVersion; public int LastSeenVersion { get; set; } = PenumbraChangelog.LastChangelogVersion; public bool DebugSeparateWindow { get; set; } = false; @@ -41,7 +32,6 @@ public class EphemeralConfig : ISavable, IDisposable, IService public TabType SelectedTab { get; set; } = TabType.Settings; public ChangedItemIconFlag ChangedItemFilter { get; set; } = ChangedItemFlagExtensions.DefaultFlags; public bool FixMainWindow { get; set; } = false; - public string LastModPath { get; set; } = string.Empty; public HashSet AdvancedEditingOpenForModPaths { get; set; } = []; public bool ForceRedrawOnFileChange { get; set; } = false; public bool IncognitoMode { get; set; } = false; @@ -50,17 +40,12 @@ public class EphemeralConfig : ISavable, IDisposable, IService /// Load the current configuration. /// Includes adding new colors and migrating from old versions. /// - public EphemeralConfig(SaveService saveService, ModPathChanged modPathChanged) + public EphemeralConfig(SaveService saveService) { _saveService = saveService; - _modPathChanged = modPathChanged; Load(); - _modPathChanged.Subscribe(OnModPathChanged, ModPathChanged.Priority.EphemeralConfig); } - public void Dispose() - => _modPathChanged.Unsubscribe(OnModPathChanged); - private void Load() { static void HandleDeserializationError(object? sender, ErrorEventArgs errorArgs) @@ -104,15 +89,4 @@ public class EphemeralConfig : ISavable, IDisposable, IService var serializer = new JsonSerializer { Formatting = Formatting.Indented }; serializer.Serialize(jWriter, this); } - - /// Overwrite the last saved mod path if it changes. - private void OnModPathChanged(in ModPathChanged.Arguments arguments) - { - if (arguments.Type is not ModPathChangeType.Moved - || !string.Equals(arguments.OldDirectory?.Name, LastModPath, StringComparison.OrdinalIgnoreCase)) - return; - - LastModPath = arguments.Mod.Identifier; - Save(); - } } diff --git a/Penumbra/Config/PcpSettings.cs b/Penumbra/Config/PcpSettings.cs new file mode 100644 index 00000000..8b4ee9d9 --- /dev/null +++ b/Penumbra/Config/PcpSettings.cs @@ -0,0 +1,11 @@ +namespace Penumbra; + +public record PcpSettings +{ + public bool CreateCollection { get; set; } = true; + public bool AssignCollection { get; set; } = true; + public bool AllowIpc { get; set; } = true; + public bool DisableHandling { get; set; } = false; + public string FolderName { get; set; } = "PCP"; + public string PcpExtension { get; set; } = ".pcp"; +} diff --git a/Penumbra/Config/UiConfig.cs b/Penumbra/Config/UiConfig.cs new file mode 100644 index 00000000..7233b1ab --- /dev/null +++ b/Penumbra/Config/UiConfig.cs @@ -0,0 +1,108 @@ +using Dalamud.Interface.ImGuiNotification; +using Luna; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Penumbra.Services; + +namespace Penumbra; + +public class UiConfig : ISavable, IService +{ + public const int CurrentVersion = 1; + + [JsonIgnore] + private readonly SaveService _saveService; + + public UiConfig(SaveService saveService) + { + _saveService = saveService; + Load(); + } + + private TwoPanelWidth _collectionsTabScale = new(0.25f, ScalingMode.Percentage); + + public TwoPanelWidth CollectionTabScale + { + 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.WritePropertyName("Width"); + j.WriteValue(CollectionTabScale.Width); + j.WriteEndObject(); + j.WritePropertyName("ModsTab"); + j.WriteStartObject(); + j.WritePropertyName("Mode"); + j.WriteValue(ModTabScale.Mode.ToString()); + j.WritePropertyName("Width"); + j.WriteValue(ModTabScale.Width); + j.WriteEndObject(); + j.WriteEndObject(); + } + + private void Load() + { + if (!File.Exists(_saveService.FileNames.UiConfigFile)) + return; + + try + { + var text = File.ReadAllText(_saveService.FileNames.UiConfigFile); + var jObj = JObject.Parse(text); + if (jObj["Version"]?.Value() 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); + } + } +} diff --git a/Penumbra/Import/Textures/TexFileParser.cs b/Penumbra/Import/Textures/TexFileParser.cs index 04bbf5d8..ca061472 100644 --- a/Penumbra/Import/Textures/TexFileParser.cs +++ b/Penumbra/Import/Textures/TexFileParser.cs @@ -44,8 +44,8 @@ public static class TexFileParser if (offset == 0) return i; - var Size = width * height * bits / 8; - if (offset + Size > data.Length) + var size = width * height * bits / 8; + if (offset + size > data.Length) return i; var diff = offset - lastOffset; @@ -55,7 +55,7 @@ public static class TexFileParser width = Math.Max(width / 2, minSize); height = Math.Max(height / 2, minSize); lastOffset = offset; - lastSize = Size; + lastSize = size; } return 13; diff --git a/Penumbra/Interop/GameState.cs b/Penumbra/Interop/GameState.cs index 409dc363..4b766fef 100644 --- a/Penumbra/Interop/GameState.cs +++ b/Penumbra/Interop/GameState.cs @@ -74,8 +74,9 @@ public class GameState : Luna.IService #region Subfiles - public readonly ThreadLocal MtrlData = new(() => ResolveData.Invalid); - public readonly ThreadLocal AvfxData = new(() => ResolveData.Invalid); + public readonly ThreadLocal MtrlData = new(() => ResolveData.Invalid); + public readonly ThreadLocal AvfxData = new(() => ResolveData.Invalid); + public ResolveData ApricotDocumentAvfx = ResolveData.Invalid; public readonly ConcurrentDictionary SubFileCollection = new(); diff --git a/Penumbra/Interop/Hooks/Animation/ApricotListenerSoundPlay.cs b/Penumbra/Interop/Hooks/Animation/ApricotListenerSoundPlay.cs index de76b6d9..0e909cda 100644 --- a/Penumbra/Interop/Hooks/Animation/ApricotListenerSoundPlay.cs +++ b/Penumbra/Interop/Hooks/Animation/ApricotListenerSoundPlay.cs @@ -16,13 +16,15 @@ public sealed unsafe class ApricotListenerSoundPlayCaller : FastHook("Apricot Listener Sound Play Caller", Sigs.ApricotListenerSoundPlayCaller, Detour, !HookOverrides.Instance.Animation.ApricotListenerSoundPlayCaller); } @@ -52,8 +54,10 @@ public sealed unsafe class ApricotListenerSoundPlayCaller : FastHook**)apricotIInstanceListenner)[VolatileOffsets.ApricotListenerSoundPlayCaller.CasterVFunc](apricotIInstanceListenner); + var newData = ResolveData.Invalid; + var gameObject = + (*(delegate* unmanaged**)apricotIInstanceListenner)[VolatileOffsets.ApricotListenerSoundPlayCaller.CasterVFunc]( + apricotIInstanceListenner); if (gameObject != null) { newData = _collectionResolver.IdentifyCollection(gameObject, true); @@ -64,8 +68,10 @@ public sealed unsafe class ApricotListenerSoundPlayCaller : FastHook +{ + public CreateApricotDocumentHook(CollectionManager collections, GameState state, HookManager hooks) + : this(collections, state) + { + Task = hooks.CreateHook("Create Apricot Document", Sigs.CreateApricotDocument, Detour, + !HookOverrides.Instance.Animation.CreateApricotDocument); + } + + public delegate nint Delegate(nint singleton, nint vfxOwner, byte* avfxPath, byte* avfxData, uint avfxSize, ApricotResourceHandle* resource, + nint document); + + private nint Detour(nint singleton, nint vfxOwner, byte* avfxPath, byte* avfxData, uint avfxSize, ApricotResourceHandle* resource, + nint document) + { + var fullPath = new CiByteString(avfxPath); + state.ApricotDocumentAvfx = state.AvfxData.Value.Valid + ? state.AvfxData.Value + : new ResolveData(avfxPath is not null + && PathDataHandler.Split(fullPath.Span, out var path, out var additionalData) + && PathDataHandler.Read(additionalData, out var data) + ? collections.Storage.ByLocalId(data.Collection) + : collections.Active.Default, nint.Zero); + var ret = Task!.Result.Original(singleton, vfxOwner, avfxPath, avfxData, avfxSize, resource, document); + Penumbra.Log.Excessive( + $"[CreateApricotDocument] Results with 0x{singleton:X}, 0x{vfxOwner:X}, 0x{(nint)avfxPath:X} ({fullPath}, {state.ApricotDocumentAvfx.ModCollection}) 0x{(nint)avfxData:X} ({avfxSize} bytes) 0x{(long)resource:X} 0x{document:X} -> 0x{ret:X}."); + return ret; + } +} +#endif diff --git a/Penumbra/Interop/Hooks/DebugHook.cs b/Penumbra/Interop/Hooks/DebugHook.cs index cf3264c6..c4840591 100644 --- a/Penumbra/Interop/Hooks/DebugHook.cs +++ b/Penumbra/Interop/Hooks/DebugHook.cs @@ -1,15 +1,20 @@ using Dalamud.Hooking; using Luna; -using Penumbra.Interop.Structs; +using Penumbra.Collections; +using Penumbra.Collections.Manager; +using Penumbra.Interop.PathResolving; +using Penumbra.String; namespace Penumbra.Interop.Hooks; #if DEBUG -public sealed unsafe class DebugHook : Luna.IHookService + +public sealed unsafe class DebugHook(CollectionStorage collections) : Luna.IHookService { public const string Signature = ""; - public DebugHook(HookManager hooks) + public DebugHook(CollectionStorage collections, HookManager hooks) + : this(collections) { if (Signature.Length > 0) _task = hooks.CreateHook("Debug Hook", Signature, Detour, true); @@ -32,12 +37,19 @@ public sealed unsafe class DebugHook : Luna.IHookService public bool Finished => _task?.IsCompletedSuccessfully ?? true; - private delegate nint Delegate(ResourceHandle* a, int b, int c); + private delegate nint Delegate(nint a, nint b, nint c, nint d, uint e, nint f, nint g); - private nint Detour(ResourceHandle* a, int b, int c) + private nint Detour(nint a, nint b, nint c, nint d, uint e, nint f, nint g) { - var ret = _task!.Result.Original(a, b, c); - Penumbra.Log.Information($"[Debug Hook] Results with 0x{(nint)a:X}, {b}, {c} -> 0x{ret:X}."); + var path = new CiByteString((byte*)c); + var collection = PathDataHandler.Split(path.Span, out _, out var additionalData) && PathDataHandler.Read(additionalData, out var data) + ? collections.ByLocalId(data.Collection) + : ModCollection.Empty; + + var ret = _task!.Result.Original(a, b, c, d, e, f, g); + + + Penumbra.Log.Information($"[Debug Hook] Results with 0x{a:X}, 0x{b:X}, 0x{c:X} ({path}, {collection}) 0x{d:X} {e} 0x{f:X} {g} -> 0x{ret:X}."); return ret; } } diff --git a/Penumbra/Interop/Hooks/HookSettings.cs b/Penumbra/Interop/Hooks/HookSettings.cs index bcff25d2..afef00f8 100644 --- a/Penumbra/Interop/Hooks/HookSettings.cs +++ b/Penumbra/Interop/Hooks/HookSettings.cs @@ -31,6 +31,7 @@ public class HookOverrides public struct AnimationHooks { public bool ApricotListenerSoundPlayCaller; + public bool CreateApricotDocument; public bool CharacterBaseLoadAnimation; public bool Dismount; public bool LoadAreaVfx; diff --git a/Penumbra/Interop/Processing/AvfxPathPreProcessor.cs b/Penumbra/Interop/Processing/AvfxPathPreProcessor.cs index 2194354a..1beeecc5 100644 --- a/Penumbra/Interop/Processing/AvfxPathPreProcessor.cs +++ b/Penumbra/Interop/Processing/AvfxPathPreProcessor.cs @@ -6,11 +6,14 @@ using Penumbra.String.Classes; namespace Penumbra.Interop.Processing; -public sealed class AvfxPathPreProcessor : IPathPreProcessor +public sealed class AvfxPathPreProcessor(GameState state) : IPathPreProcessor { public ResourceType Type => ResourceType.Avfx; public FullPath? PreProcess(ResolveData resolveData, CiByteString path, Utf8GamePath _, bool nonDefault, FullPath? resolved) - => nonDefault ? PathDataHandler.CreateAvfx(path, resolveData.ModCollection) : resolved; + { + state.ApricotDocumentAvfx = resolveData; + return nonDefault ? PathDataHandler.CreateAvfx(path, resolveData.ModCollection) : resolved; + } } diff --git a/Penumbra/Mods/Manager/ModFileSystem.cs b/Penumbra/Mods/Manager/ModFileSystem.cs index 8be970ae..810bc731 100644 --- a/Penumbra/Mods/Manager/ModFileSystem.cs +++ b/Penumbra/Mods/Manager/ModFileSystem.cs @@ -1,69 +1,36 @@ using Dalamud.Interface.ImGuiNotification; -using ImSharp; using Luna; -using OtterGui.Filesystem; -using Penumbra.Api.Enums; using Penumbra.Communication; using Penumbra.Services; -using Penumbra.UI.Classes; -using Penumbra.UI.ModsTab; -using Penumbra.UI.ModsTab.Selector; -using FileSystemChangeType = OtterGui.Filesystem.FileSystemChangeType; namespace Penumbra.Mods.Manager; -public sealed class ModTab : TwoPanelLayout, ITab -{ - public override ReadOnlySpan Label - => "Mods2"u8; - - public ModTab(ModFileSystemDrawer drawer, ModPanel panel, CollectionSelectHeader collectionHeader, RedrawFooter redrawFooter) - { - LeftHeader = drawer.Header; - LeftFooter = drawer.Footer; - LeftPanel = drawer; - RightPanel = panel; - RightHeader = collectionHeader; - RightFooter = redrawFooter; - } - - public void DrawContent() - => Draw(); - - public TabType Identifier - => TabType.Mods; - - protected override void SetSize(Vector2 newSize) - { - base.SetSize(newSize); - ((ModFileSystemDrawer)LeftPanel).Config.Ephemeral.CurrentModSelectorWidth = newSize.X / Im.Style.GlobalScale; - } - - protected override float MinimumWidth - => ((ModFileSystemDrawer)LeftPanel).Footer.Buttons.Count * Im.Style.FrameHeight; - - protected override float MaximumWidth - => Im.Window.Width - 500 * Im.Style.GlobalScale; -} - -public sealed class ModFileSystem2 : BaseFileSystem, IDisposable, IRequiredService +public sealed class ModFileSystem : BaseFileSystem, IDisposable, IRequiredService { private readonly Configuration _config; private readonly CommunicatorService _communicator; private readonly ModFileSystemSaver _saver; + private readonly ModSelection _selection; - public ModFileSystem2(Configuration config, CommunicatorService communicator, SaveService saveService, Logger log, ModStorage modStorage) + public ModFileSystem(Configuration config, CommunicatorService communicator, SaveService saveService, Logger log, ModStorage modStorage, + ModSelection selection) : base("ModFileSystem", log, true) { _config = config; _communicator = communicator; + _selection = selection; _saver = new ModFileSystemSaver(log, this, saveService, modStorage); _communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModFileSystem); _communicator.ModDiscoveryFinished.Subscribe(_saver.Load, ModDiscoveryFinished.Priority.ModFileSystem); _communicator.ModDataChanged.Subscribe(OnModDataChange, ModDataChanged.Priority.ModFileSystem); _saver.Load(); + Selection.Changed += OnSelectionChanged; + _selection.SelectMod(Selection.Selection?.GetValue()); } + private void OnSelectionChanged() + => _selection.SelectMod(Selection.Selection?.GetValue()); + public void Dispose() { _communicator.ModPathChanged.Unsubscribe(OnModPathChange); @@ -113,151 +80,3 @@ public sealed class ModFileSystem2 : BaseFileSystem, IDisposable, IRequiredServi } } } - -public sealed class ModFileSystem : FileSystem, IDisposable, ISavable, IService -{ - private readonly ModManager _modManager; - private readonly CommunicatorService _communicator; - private readonly SaveService _saveService; - private readonly Configuration _config; - - // Create a new ModFileSystem from the currently loaded mods and the current sort order file. - public ModFileSystem(ModManager modManager, CommunicatorService communicator, SaveService saveService, Configuration config) - { - _modManager = modManager; - _communicator = communicator; - _saveService = saveService; - _config = config; - Reload(); - Changed += OnChange; - _communicator.ModDiscoveryFinished.Subscribe(Reload, ModDiscoveryFinished.Priority.ModFileSystem); - _communicator.ModDataChanged.Subscribe(OnModDataChange, ModDataChanged.Priority.ModFileSystem); - _communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModFileSystem); - } - - public void Dispose() - { - _communicator.ModPathChanged.Unsubscribe(OnModPathChange); - _communicator.ModDiscoveryFinished.Unsubscribe(Reload); - _communicator.ModDataChanged.Unsubscribe(OnModDataChange); - } - - public struct ImportDate : ISortMode - { - public ReadOnlySpan Name - => "Import Date (Older First)"u8; - - public ReadOnlySpan Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their import date."u8; - - public IEnumerable GetChildren(Folder f) - => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderBy(l => l.Value.ImportDate)); - } - - public struct InverseImportDate : ISortMode - { - public ReadOnlySpan Name - => "Import Date (Newer First)"u8; - - public ReadOnlySpan Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse import date."u8; - - public IEnumerable GetChildren(Folder f) - => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderByDescending(l => l.Value.ImportDate)); - } - - // Reload the whole filesystem from currently loaded mods and the current sort order file. - // Used on construction and on mod rediscoveries. - private void Reload() - { - var jObj = BackupService.GetJObjectForFile(_saveService.FileNames, _saveService.FileNames.OldFilesystemFile); - if (Load(jObj, _modManager, ModToIdentifier, ModToName)) - _saveService.ImmediateSave(this); - - Penumbra.Log.Debug("Reloaded mod filesystem."); - } - - // Save the filesystem on every filesystem change except full reloading. - private void OnChange(FileSystemChangeType type, IPath _1, IPath? _2, IPath? _3) - { - if (type != FileSystemChangeType.Reload) - _saveService.DelaySave(this); - } - - // Update sort order when defaulted mod names change. - private void OnModDataChange(in ModDataChanged.Arguments arguments) - { - if (!arguments.Type.HasFlag(ModDataChangeType.Name) || arguments.OldName == null || !TryGetValue(arguments.Mod, out var leaf)) - return; - - var old = Extensions.FixName(arguments.OldName); - if (old == leaf.Name || Extensions.IsDuplicateName(leaf.Name, out var baseName, out _) && baseName == old) - RenameWithDuplicates(leaf, arguments.Mod.Name); - } - - // Update the filesystem if a mod has been added or removed. - // Save it, if the mod directory has been moved, since this will change the save format. - private void OnModPathChange(in ModPathChanged.Arguments arguments) - { - switch (arguments.Type) - { - case ModPathChangeType.Added: - var parent = Root; - if (_config.DefaultImportFolder.Length != 0) - try - { - parent = FindOrCreateAllFolders(_config.DefaultImportFolder); - } - catch (Exception e) - { - Penumbra.Messager.NotificationMessage(e, - $"Could not move newly imported mod {arguments.Mod.Name} to default import folder {_config.DefaultImportFolder}.", - NotificationType.Warning); - } - - CreateDuplicateLeaf(parent, arguments.Mod.Name, arguments.Mod); - break; - case ModPathChangeType.Deleted: - if (TryGetValue(arguments.Mod, out var leaf)) - Delete(leaf); - - break; - case ModPathChangeType.Moved: _saveService.DelaySave(this); break; - case ModPathChangeType.Reloaded: - // Nothing - break; - } - } - - // Used for saving and loading. - private static string ModToIdentifier(Mod mod) - => mod.ModPath.Name; - - private static string ModToName(Mod mod) - => Extensions.FixName(mod.Name); - - // Return whether a mod has a custom path or is just a numbered default path. - public static bool ModHasDefaultPath(Mod mod, string fullPath) - { - var regex = new Regex($@"^{Regex.Escape(ModToName(mod))}( \(\d+\))?$"); - return regex.IsMatch(fullPath); - } - - private static (string, bool) SaveMod(Mod mod, string fullPath) - // Only save pairs with non-default paths. - => ModHasDefaultPath(mod, fullPath) - ? (string.Empty, false) - : (ModToIdentifier(mod), true); - - public string ToFilePath(FilenameService fileNames) - => fileNames.OldFilesystemFile; - - public void Save(StreamWriter writer) - => SaveToFile(writer, SaveMod, true); - - public string TypeName - => "Mod File System"; - - public string LogName(string _) - => "to file"; -} diff --git a/Penumbra/Mods/Manager/ModManager.cs b/Penumbra/Mods/Manager/ModManager.cs index 58e9fe12..6cac3c79 100644 --- a/Penumbra/Mods/Manager/ModManager.cs +++ b/Penumbra/Mods/Manager/ModManager.cs @@ -332,9 +332,16 @@ public sealed class ModManager : ModStorage, IDisposable, Luna.IService var queue = new ConcurrentQueue(); Parallel.ForEach(BasePath.EnumerateDirectories(), options, dir => { - var mod = Creator.LoadMod(dir, false, false); - if (mod != null) - queue.Enqueue(mod); + try + { + var mod = Creator.LoadMod(dir, false, false); + if (mod != null) + queue.Enqueue(mod); + } + catch (Exception ex) + { + Penumbra.Log.Warning($"Failed to load mod at {dir}:\n{ex}"); + } }); foreach (var mod in queue) diff --git a/Penumbra/Mods/ModSelection.cs b/Penumbra/Mods/ModSelection.cs index 294d8244..9c535731 100644 --- a/Penumbra/Mods/ModSelection.cs +++ b/Penumbra/Mods/ModSelection.cs @@ -18,7 +18,6 @@ namespace Penumbra.Mods; public class ModSelection : EventBase { private readonly ActiveCollections _collections; - private readonly EphemeralConfig _config; private readonly CommunicatorService _communicator; public ModSelection(Logger log, CommunicatorService communicator, ModManager mods, ActiveCollections collections, EphemeralConfig config) @@ -26,10 +25,6 @@ public class ModSelection : EventBase 0 && mods.TryGetMod(config.LastModPath, out var mod)) - SelectMod(mod); - _communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.ModSelection); _communicator.CollectionInheritanceChanged.Subscribe(OnInheritanceChange, CollectionInheritanceChanged.Priority.ModSelection); _communicator.ModSettingChanged.Subscribe(OnSettingChange, ModSettingChanged.Priority.ModSelection); @@ -50,8 +45,6 @@ public class ModSelection : EventBase(); Messager = _services.GetService(); Dynamis = _services.GetService(); _validityChecker = _services.GetService(); _services.EnsureRequiredServices(); - _services.EnsureRequiredServices(); var startup = _services.GetService() .GetDalamudConfig(DalamudConfigService.WaitingForPluginsOption, out bool s) diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index e0f9de69..47fb58dc 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -13,6 +13,7 @@ PROFILING; false + false @@ -58,7 +59,6 @@ - diff --git a/Penumbra/Penumbra.csproj.DotSettings b/Penumbra/Penumbra.csproj.DotSettings index 21db684f..a0b39c16 100644 --- a/Penumbra/Penumbra.csproj.DotSettings +++ b/Penumbra/Penumbra.csproj.DotSettings @@ -1,4 +1,5 @@  + True True True True \ No newline at end of file diff --git a/Penumbra/Services/FilenameService.cs b/Penumbra/Services/FilenameService.cs index ba3ef826..54b7e5b5 100644 --- a/Penumbra/Services/FilenameService.cs +++ b/Penumbra/Services/FilenameService.cs @@ -10,6 +10,7 @@ public sealed class FilenameService(IDalamudPluginInterface pi) : BaseFilePathPr public readonly string CollectionDirectory = Path.Combine(pi.ConfigDirectory.FullName, "collections"); 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 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"); diff --git a/Penumbra/Services/PcpService.cs b/Penumbra/Services/PcpService.cs index d0aa4630..74401065 100644 --- a/Penumbra/Services/PcpService.cs +++ b/Penumbra/Services/PcpService.cs @@ -135,12 +135,12 @@ public class PcpService : IApiService, IDisposable } // Move to folder. - if (_fileSystem.TryGetValue(arguments.Mod, out var leaf)) + if (arguments.Mod.Node is {} node) { try { var folder = _fileSystem.FindOrCreateAllFolders(_config.PcpSettings.FolderName); - _fileSystem.Move(leaf, folder); + _fileSystem.Move(node, folder); } catch { diff --git a/Penumbra/Services/StainService.cs b/Penumbra/Services/StainService.cs index 51a84619..2e9b4ec5 100644 --- a/Penumbra/Services/StainService.cs +++ b/Penumbra/Services/StainService.cs @@ -1,6 +1,6 @@ using Dalamud.Plugin.Services; using ImSharp; -using OtterGui.Widgets; +using Luna; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Files; using Penumbra.GameData.Files.StainMapStructs; @@ -24,7 +24,7 @@ public sealed class TemplateFilter : TextFilterBase => item.Id; } -public sealed class StainTemplateCombo : ImSharp.FilterComboBase +public sealed class StainTemplateCombo : FilterComboBase where TDyePack : unmanaged, IDyePack { private readonly StainService.StainCombo[] _stainCombos; @@ -70,7 +70,7 @@ public sealed class StainTemplateCombo : ImSharp.FilterComboBase : ImSharp.FilterComboBase(dataManager); } - public sealed class StainCombo(DictStain stainData) : Luna.FilterComboColors + public sealed class StainCombo(DictStain stainData) : FilterComboColors { protected override IEnumerable GetItems() => stainData.Value.Select(t => new Item(new StringPair(t.Value.Name), t.Value.Dye, t.Key, t.Value.Gloss)).Prepend(None); diff --git a/Penumbra/Services/StaticServiceManager.cs b/Penumbra/Services/StaticServiceManager.cs index e6b8d703..4511882a 100644 --- a/Penumbra/Services/StaticServiceManager.cs +++ b/Penumbra/Services/StaticServiceManager.cs @@ -1,11 +1,8 @@ -using Dalamud.Game; -using Dalamud.Game.ClientState.Objects; using Dalamud.Interface.DragDrop; using Dalamud.Plugin; using Dalamud.Plugin.Services; using Luna; using Microsoft.Extensions.DependencyInjection; -using OtterGui; using Penumbra.Api.Api; using Penumbra.GameData.Actors; using Penumbra.GameData.Structs; @@ -26,12 +23,8 @@ public static class StaticServiceManager .AddDalamudServices(pi) .AddExistingService(log) .AddExistingService(penumbra); - // TODO Remove this when migration done - services.AddExistingService(Penumbra.Log); services.AddIServices(typeof(EquipItem).Assembly); services.AddIServices(typeof(Penumbra).Assembly); - services.AddIServices(typeof(Penumbra).Assembly); - services.AddIServices(typeof(ImGuiUtil).Assembly); services.AddIServices(typeof(IService).Assembly); services.AddSingleton(p => diff --git a/Penumbra/UI/AdvancedWindow/ItemSelector.cs b/Penumbra/UI/AdvancedWindow/ItemSelector.cs index d659fc1b..358837f0 100644 --- a/Penumbra/UI/AdvancedWindow/ItemSelector.cs +++ b/Penumbra/UI/AdvancedWindow/ItemSelector.cs @@ -6,11 +6,10 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Mods; using Penumbra.UI.Classes; -using Penumbra.UI.ModsTab; namespace Penumbra.UI.AdvancedWindow; -public sealed class ItemSelector(ActiveCollections collections, ItemData data, ModFileSystemSelector? selector, FullEquipType type) +public sealed class ItemSelector(ActiveCollections collections, ItemData data, ModSelection? selection, FullEquipType type) : FilterComboBase(new ItemFilter()) { public EquipItem CurrentSelection = new(string.Empty, default, default, default, default, default, FullEquipType.Unknown, default, default, @@ -59,11 +58,11 @@ public sealed class ItemSelector(ActiveCollections collections, ItemData data, M protected override IEnumerable GetItems() { var list = data.ByType[type]; - if (selector?.Selected is { } currentMod && currentMod.ChangedItems.Values.Any(c => c is IdentifiedItem i && i.Item.Type == type)) + if (selection?.Mod is { } currentMod && currentMod.ChangedItems.Values.Any(c => c is IdentifiedItem i && i.Item.Type == type)) return list.Select(item => new CacheItem(item, currentMod, collections.Current)).OrderByDescending(i => i.InCurrentMod) .ThenByDescending(i => i.CollectionMods.Length); - if (selector is null) + if (selection is null) return list.Select(item => new CacheItem(item, collections.Current)).OrderBy(i => i.CollectionMods.Length); return list.Select(item => new CacheItem(item, collections.Current)).OrderByDescending(i => i.CollectionMods.Length); diff --git a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs index db94ac30..ea1f9dba 100644 --- a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs +++ b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs @@ -19,7 +19,6 @@ using Penumbra.Mods.Settings; using Penumbra.Mods.SubMods; using Penumbra.Services; using Penumbra.UI.Classes; -using Penumbra.UI.ModsTab; namespace Penumbra.UI.AdvancedWindow; @@ -69,7 +68,7 @@ public class ItemSwapTab : IDisposable, ITab private readonly MetaFileManager _metaFileManager; public ItemSwapTab(CommunicatorService communicator, ItemData itemService, CollectionManager collectionManager, - ModManager modManager, ModFileSystemSelector selector, ObjectIdentification identifier, MetaFileManager metaFileManager, + ModManager modManager, ModSelection selection, ObjectIdentification identifier, MetaFileManager metaFileManager, Configuration config) { _communicator = communicator; @@ -83,16 +82,16 @@ public class ItemSwapTab : IDisposable, ITab _selectors = new Dictionary { // @formatter:off - [SwapType.Hat] = (new ItemSelector(a, itemService, selector, FullEquipType.Head), new ItemSelector(a, itemService, null, FullEquipType.Head), new StringU8("Take this Hat"u8), new StringU8("and put it on this one"u8) ), - [SwapType.Top] = (new ItemSelector(a, itemService, selector, FullEquipType.Body), new ItemSelector(a, itemService, null, FullEquipType.Body), new StringU8("Take this Top"u8), new StringU8("and put it on this one"u8) ), - [SwapType.Gloves] = (new ItemSelector(a, itemService, selector, FullEquipType.Hands), new ItemSelector(a, itemService, null, FullEquipType.Hands), new StringU8("Take these Gloves"u8), new StringU8("and put them on these"u8) ), - [SwapType.Pants] = (new ItemSelector(a, itemService, selector, FullEquipType.Legs), new ItemSelector(a, itemService, null, FullEquipType.Legs), new StringU8("Take these Pants"u8), new StringU8("and put them on these"u8) ), - [SwapType.Shoes] = (new ItemSelector(a, itemService, selector, FullEquipType.Feet), new ItemSelector(a, itemService, null, FullEquipType.Feet), new StringU8("Take these Shoes"u8), new StringU8("and put them on these"u8) ), - [SwapType.Earrings] = (new ItemSelector(a, itemService, selector, FullEquipType.Ears), new ItemSelector(a, itemService, null, FullEquipType.Ears), new StringU8("Take these Earrings"u8), new StringU8("and put them on these"u8) ), - [SwapType.Necklace] = (new ItemSelector(a, itemService, selector, FullEquipType.Neck), new ItemSelector(a, itemService, null, FullEquipType.Neck), new StringU8("Take this Necklace"u8), new StringU8("and put it on this one"u8) ), - [SwapType.Bracelet] = (new ItemSelector(a, itemService, selector, FullEquipType.Wrists), new ItemSelector(a, itemService, null, FullEquipType.Wrists), new StringU8("Take these Bracelets"u8), new StringU8("and put them on these"u8) ), - [SwapType.Ring] = (new ItemSelector(a, itemService, selector, FullEquipType.Finger), new ItemSelector(a, itemService, null, FullEquipType.Finger), new StringU8("Take this Ring"u8), new StringU8("and put it on this one"u8) ), - [SwapType.Glasses] = (new ItemSelector(a, itemService, selector, FullEquipType.Glasses), new ItemSelector(a, itemService, null, FullEquipType.Glasses), new StringU8("Take these Glasses"u8), new StringU8("and put them on these"u8) ), + [SwapType.Hat] = (new ItemSelector(a, itemService, selection, FullEquipType.Head), new ItemSelector(a, itemService, null, FullEquipType.Head), new StringU8("Take this Hat"u8), new StringU8("and put it on this one"u8) ), + [SwapType.Top] = (new ItemSelector(a, itemService, selection, FullEquipType.Body), new ItemSelector(a, itemService, null, FullEquipType.Body), new StringU8("Take this Top"u8), new StringU8("and put it on this one"u8) ), + [SwapType.Gloves] = (new ItemSelector(a, itemService, selection, FullEquipType.Hands), new ItemSelector(a, itemService, null, FullEquipType.Hands), new StringU8("Take these Gloves"u8), new StringU8("and put them on these"u8) ), + [SwapType.Pants] = (new ItemSelector(a, itemService, selection, FullEquipType.Legs), new ItemSelector(a, itemService, null, FullEquipType.Legs), new StringU8("Take these Pants"u8), new StringU8("and put them on these"u8) ), + [SwapType.Shoes] = (new ItemSelector(a, itemService, selection, FullEquipType.Feet), new ItemSelector(a, itemService, null, FullEquipType.Feet), new StringU8("Take these Shoes"u8), new StringU8("and put them on these"u8) ), + [SwapType.Earrings] = (new ItemSelector(a, itemService, selection, FullEquipType.Ears), new ItemSelector(a, itemService, null, FullEquipType.Ears), new StringU8("Take these Earrings"u8), new StringU8("and put them on these"u8) ), + [SwapType.Necklace] = (new ItemSelector(a, itemService, selection, FullEquipType.Neck), new ItemSelector(a, itemService, null, FullEquipType.Neck), new StringU8("Take this Necklace"u8), new StringU8("and put it on this one"u8) ), + [SwapType.Bracelet] = (new ItemSelector(a, itemService, selection, FullEquipType.Wrists), new ItemSelector(a, itemService, null, FullEquipType.Wrists), new StringU8("Take these Bracelets"u8), new StringU8("and put them on these"u8) ), + [SwapType.Ring] = (new ItemSelector(a, itemService, selection, FullEquipType.Finger), new ItemSelector(a, itemService, null, FullEquipType.Finger), new StringU8("Take this Ring"u8), new StringU8("and put it on this one"u8) ), + [SwapType.Glasses] = (new ItemSelector(a, itemService, selection, FullEquipType.Glasses), new ItemSelector(a, itemService, null, FullEquipType.Glasses), new StringU8("Take these Glasses"u8), new StringU8("and put them on these"u8) ), // @formatter:on }; diff --git a/Penumbra/UI/AdvancedWindow/ItemSwapTabFactory.cs b/Penumbra/UI/AdvancedWindow/ItemSwapTabFactory.cs index ba06807d..3b9087c5 100644 --- a/Penumbra/UI/AdvancedWindow/ItemSwapTabFactory.cs +++ b/Penumbra/UI/AdvancedWindow/ItemSwapTabFactory.cs @@ -2,16 +2,22 @@ using Luna; using Penumbra.Collections.Manager; using Penumbra.GameData.Data; using Penumbra.Meta; +using Penumbra.Mods; using Penumbra.Mods.Manager; using Penumbra.Services; -using Penumbra.UI.ModsTab; namespace Penumbra.UI.AdvancedWindow; -public class ItemSwapTabFactory(CommunicatorService communicator, ItemData itemService, CollectionManager collectionManager, - ModManager modManager, ModFileSystemSelector selector, ObjectIdentification identifier, MetaFileManager metaFileManager, +public class ItemSwapTabFactory( + CommunicatorService communicator, + ItemData itemService, + CollectionManager collectionManager, + ModManager modManager, + ModSelection selection, + ObjectIdentification identifier, + MetaFileManager metaFileManager, Configuration config) : IUiService { public ItemSwapTab Create() - => new(communicator, itemService, collectionManager, modManager, selector, identifier, metaFileManager, config); + => new(communicator, itemService, collectionManager, modManager, selection, identifier, metaFileManager, config); } diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ColorTable.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ColorTable.cs index d9ca832c..7fbccaa9 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ColorTable.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ColorTable.cs @@ -27,22 +27,18 @@ public partial class MtrlTab var frameHeight = Im.Style.FrameHeight; var highlighterSize = ImEx.Icon.CalculateSize(LunaStyle.OnHoverIcon) + framePadding * 2.0f; - using var font = Im.Font.PushMono(); using var alignment = ImStyleDouble.ButtonTextAlign.Push(new Vector2(0, 0.5f)); - // This depends on the font being pushed for "proper" alignment of the pair indices in the buttons. - var spaceWidth = Im.Font.Mono.GetCharacterAdvance(' '); - var spacePadding = (int)MathF.Ceiling((highlighterSize.X + framePadding.X + itemInnerSpacing) / spaceWidth); - + var buttonSize = new Vector2(buttonWidth, Im.Style.FrameHeightWithSpacing + frameHeight); for (var i = 0; i < ColorTable.NumRows >> 1; i += 8) { for (var j = 0; j < 8; ++j) { - var pairIndex = i + j; + var pairIndex = i + j; + using var id = Im.Id.Push(pairIndex); using (ImGuiColor.Button.Push(Im.Style[ImGuiColor.ButtonActive], pairIndex == _colorTableSelectedPair)) { - if (Im.Button($"#{pairIndex + 1}".PadLeft(3 + spacePadding), - new Vector2(buttonWidth, Im.Style.FrameHeightWithSpacing + frameHeight))) + if (Im.Button(StringU8.Empty, buttonSize)) _colorTableSelectedPair = pairIndex; } @@ -68,11 +64,13 @@ public partial class MtrlTab if (j < 7) Im.Line.Same(); - var cursor = Im.Cursor.ScreenPosition; - Im.Cursor.ScreenPosition = rcMin with { Y = float.Lerp(rcMin.Y, rcMax.Y, 0.5f) - highlighterSize.Y * 0.5f }; - font.Pop(); + var cursor = Im.Cursor.ScreenPosition; + var buttonPos = rcMin with { Y = float.Lerp(rcMin.Y, rcMax.Y, 0.5f) - highlighterSize.Y * 0.5f }; + Im.Cursor.ScreenPosition = buttonPos; ColorTablePairHighlightButton(pairIndex, disabled); - font.Push(Im.Font.Mono); + Im.Cursor.ScreenPosition = buttonPos + new Vector2(Im.Style.FrameHeight + Im.Style.ItemInnerSpacing.X, Im.Style.FramePadding.Y); + using var font = Im.Font.PushMono(); + Im.Text($"#{pairIndex + 1:D2}"); Im.Cursor.ScreenPosition = cursor; } } diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.CommonColorTable.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.CommonColorTable.cs index a271426c..f60fa376 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.CommonColorTable.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.CommonColorTable.cs @@ -316,7 +316,7 @@ public partial class MtrlTab topColor, topColor, bottomColor, bottomColor); drawList.RectangleFilled( rcMin with { Y = MathF.Ceiling(float.Lerp(rcMin.Y, rcMax.Y, 2.0f / 3)) }, rcMax, - bottomColor, frameRounding, ImDrawFlagsRectangle.RoundCornersTop); + bottomColor, frameRounding, ImDrawFlagsRectangle.RoundCornersBottom); } drawList.Rectangle(rcMin, rcMax, borderColor.Color, frameRounding, default, frameThickness); diff --git a/Penumbra/UI/Classes/FileDialogService.cs b/Penumbra/UI/Classes/FileDialogService.cs index 0a6cb6d9..14fd0045 100644 --- a/Penumbra/UI/Classes/FileDialogService.cs +++ b/Penumbra/UI/Classes/FileDialogService.cs @@ -118,11 +118,7 @@ public class FileDialogService : IDisposable, IUiService /// Set up the file selector with the right flags and custom side bar items. private static FileDialogManager SetupFileManager(string modDirectory) { - var fileManager = new FileDialogManager - { - AddedWindowFlags = (WindowFlags.NoCollapse | WindowFlags.NoDocking).ToDalamudWindowFlags(), - }; - + var fileManager = DalamudExtensions.CreateFileDialog(WindowFlags.NoCollapse | WindowFlags.NoDocking); if (WindowsFunctions.GetDownloadsFolder(out var downloadsFolder)) fileManager.CustomSideBarItems.Add(("Downloads", downloadsFolder, FontAwesomeIcon.Download, -1)); diff --git a/Penumbra/UI/MainWindow/ChangedItemsTab.cs b/Penumbra/UI/MainWindow/ChangedItemsTab.cs index 71499ced..f226f134 100644 --- a/Penumbra/UI/MainWindow/ChangedItemsTab.cs +++ b/Penumbra/UI/MainWindow/ChangedItemsTab.cs @@ -74,6 +74,11 @@ public sealed class ChangedItemsTab( uiState.ChangedItemTabCategoryFilter = ChangedItemFlagExtensions.DefaultFlags; FilterChanged?.Invoke(); } + + public bool IsEmpty + => uiState.ChangedItemTabModFilter.Length is 0 + && uiState.ChangedItemTabNameFilter.Length is 0 + && uiState.ChangedItemTabCategoryFilter is ChangedItemFlagExtensions.DefaultFlags; } private readonly record struct Item(string Label, IIdentifiedObjectData Data, SingleArray Mods) @@ -102,7 +107,6 @@ public sealed class ChangedItemsTab( _collectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.ChangedItemsTabCache); } - private void OnCollectionChange(in CollectionChange.Arguments arguments) => FilterDirty = true; diff --git a/Penumbra/UI/MainWindow/MainTabBar.cs b/Penumbra/UI/MainWindow/MainTabBar.cs index 1bf73669..139c8945 100644 --- a/Penumbra/UI/MainWindow/MainTabBar.cs +++ b/Penumbra/UI/MainWindow/MainTabBar.cs @@ -3,6 +3,7 @@ using Penumbra.Api.Enums; using Penumbra.Communication; using Penumbra.Mods.Manager; using Penumbra.Services; +using Penumbra.UI.ModsTab; using Penumbra.UI.Tabs; using Penumbra.UI.Tabs.Debug; using Watcher = Penumbra.UI.ResourceWatcher.ResourceWatcher; @@ -11,14 +12,13 @@ namespace Penumbra.UI.MainWindow; public sealed class MainTabBar : TabBar, IDisposable { - public readonly Tabs.ModsTab Mods; + private readonly ModFileSystem _modFileSystem; private readonly EphemeralConfig _config; private readonly SelectTab _selectTab; public MainTabBar(Logger log, SettingsTab settings, - Tabs.ModsTab mods, - ModTab mods2, + ModTab mods, CollectionsTab collections, ChangedItemsTab changedItems, EffectiveTab effectiveChanges, @@ -26,13 +26,13 @@ public sealed class MainTabBar : TabBar, IDisposable ResourceTab resources, Watcher watcher, OnScreenTab onScreen, - MessagesTab messages, EphemeralConfig config, CommunicatorService communicator) - : base(nameof(MainTabBar), log, settings, collections, mods, mods2, changedItems, effectiveChanges, onScreen, + MessagesTab messages, EphemeralConfig config, CommunicatorService communicator, ModFileSystem modFileSystem) + : base(nameof(MainTabBar), log, settings, collections, mods, changedItems, effectiveChanges, onScreen, resources, watcher, debug, messages) { - Mods = mods; - _config = config; - _selectTab = communicator.SelectTab; + _config = config; + _modFileSystem = modFileSystem; + _selectTab = communicator.SelectTab; _selectTab.Subscribe(OnSelectTab, SelectTab.Priority.MainTabBar); TabSelected.Subscribe(OnTabSelected, 0); @@ -41,8 +41,8 @@ public sealed class MainTabBar : TabBar, IDisposable private void OnSelectTab(in SelectTab.Arguments arguments) { NextTab = arguments.Tab; - if (arguments.Mod is not null) - Mods.SelectMod = arguments.Mod; + if (arguments.Mod?.Node is { } node) + _modFileSystem.Selection.Select(node); } public void Dispose() @@ -52,6 +52,9 @@ public sealed class MainTabBar : TabBar, IDisposable private void OnTabSelected(in TabType type) { + if (_config.SelectedTab == type) + return; + _config.SelectedTab = type; _config.Save(); } diff --git a/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs index d53410b1..ba99af9e 100644 --- a/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs +++ b/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs @@ -155,7 +155,7 @@ public readonly struct ImcModGroupEditDrawer(ModGroupEditDrawer editor, ImcModGr var inDefault = defaultMask.HasValue && (defaultMask & flag) is not 0; using (Im.Disabled(defaultMask is not null && !cache.CanChange(i))) { - if (inDefault ? NegativeCheckbox.Instance.Draw(""u8, ref value) : Im.Checkbox(""u8, ref value)) + if (inDefault ? ImEx.XCheckbox(""u8, ref value) : Im.Checkbox(""u8, ref value)) { if (data is ImcModGroup g) editor.ChangeDefaultAttribute(g, cache, i, value); @@ -169,21 +169,4 @@ public readonly struct ImcModGroupEditDrawer(ModGroupEditDrawer editor, ImcModGr Im.Line.SameInner(); } } - - private sealed class NegativeCheckbox : OtterGui.Text.Widget.MultiStateCheckbox - { - public static readonly NegativeCheckbox Instance = new(); - - protected override void RenderSymbol(bool value, Vector2 position, float size) - { - if (value) - Im.Render.Cross(Im.Window.DrawList, position, ImGuiColor.CheckMark.Get(), size); - } - - protected override bool NextValue(bool value) - => !value; - - protected override bool PreviousValue(bool value) - => !value; - } } diff --git a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs deleted file mode 100644 index 41727c73..00000000 --- a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs +++ /dev/null @@ -1,856 +0,0 @@ -using Dalamud.Interface; -using Dalamud.Interface.DragDrop; -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Plugin.Services; -using Dalamud.Bindings.ImGui; -using ImSharp; -using Luna; -using OtterGui; -using OtterGui.Filesystem; -using OtterGui.FileSystem.Selector; -using OtterGui.Raii; -using OtterGui.Text; -using OtterGui.Text.Widget; -using Penumbra.Collections; -using Penumbra.Collections.Manager; -using Penumbra.Communication; -using Penumbra.Mods; -using Penumbra.Mods.Manager; -using Penumbra.Mods.Settings; -using Penumbra.Services; -using Penumbra.UI.Classes; -using Penumbra.UI.ModsTab.Selector; -using MessageService = Penumbra.Services.MessageService; - -namespace Penumbra.UI.ModsTab; - -public sealed class ModFileSystemSelector : FileSystemSelector, IUiService -{ - private readonly CommunicatorService _communicator; - private readonly Configuration _config; - private readonly FileDialogService _fileDialog; - private readonly ModManager _modManager; - private readonly CollectionManager _collectionManager; - private readonly TutorialService _tutorial; - private readonly ModImportManager _modImportManager; - private readonly IDragDropManager _dragDrop; - private readonly ModSearchStringSplitter _filter = new(); - private readonly ModSelection _selection; - - public ModFileSystemSelector(IKeyState keyState, CommunicatorService communicator, ModFileSystem fileSystem, ModManager modManager, - CollectionManager collectionManager, Configuration config, TutorialService tutorial, FileDialogService fileDialog, - MessageService messager, ModImportManager modImportManager, IDragDropManager dragDrop, ModSelection selection) - : base(fileSystem, keyState, Penumbra.Log, HandleException, allowMultipleSelection: true) - { - _communicator = communicator; - _modManager = modManager; - _collectionManager = collectionManager; - _config = config; - _tutorial = tutorial; - _fileDialog = fileDialog; - _modImportManager = modImportManager; - _dragDrop = dragDrop; - _selection = selection; - - // @formatter:off - SubscribeRightClickFolder(EnableDescendants, 10); - SubscribeRightClickFolder(DisableDescendants, 10); - SubscribeRightClickFolder(InheritDescendants, 15); - SubscribeRightClickFolder(OwnDescendants, 15); - SubscribeRightClickFolder(SetDefaultImportFolder, 100); - SubscribeRightClickFolder(f => SetQuickMove(f, 0, _config.QuickMoveFolder1, s => { _config.QuickMoveFolder1 = s; _config.Save(); }), 110); - SubscribeRightClickFolder(f => SetQuickMove(f, 1, _config.QuickMoveFolder2, s => { _config.QuickMoveFolder2 = s; _config.Save(); }), 120); - SubscribeRightClickFolder(f => SetQuickMove(f, 2, _config.QuickMoveFolder3, s => { _config.QuickMoveFolder3 = s; _config.Save(); }), 130); - SubscribeRightClickLeaf(ToggleLeafFavorite); - SubscribeRightClickLeaf(DrawTemporaryOptions); - SubscribeRightClickLeaf(l => QuickMove(l, _config.QuickMoveFolder1, _config.QuickMoveFolder2, _config.QuickMoveFolder3)); - SubscribeRightClickMain(ClearTemporarySettings, 105); - SubscribeRightClickMain(ClearDefaultImportFolder, 100); - SubscribeRightClickMain(() => ClearQuickMove(0, _config.QuickMoveFolder1, () => {_config.QuickMoveFolder1 = string.Empty; _config.Save();}), 110); - SubscribeRightClickMain(() => ClearQuickMove(1, _config.QuickMoveFolder2, () => {_config.QuickMoveFolder2 = string.Empty; _config.Save();}), 120); - SubscribeRightClickMain(() => ClearQuickMove(2, _config.QuickMoveFolder3, () => {_config.QuickMoveFolder3 = string.Empty; _config.Save();}), 130); - UnsubscribeRightClickLeaf(RenameLeaf); - SetRenameSearchPath(_config.ShowRename); - AddButton(AddNewModButton, 0); - AddButton(AddImportModButton, 1); - AddButton(AddHelpButton, 2); - AddButton(DeleteModButton, 1000); - // @formatter:on - SetFilterTooltip(); - - if (_selection.Mod != null) - SelectByValue(_selection.Mod); - _communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.ModFileSystemSelector); - _communicator.ModSettingChanged.Subscribe(OnSettingChange, ModSettingChanged.Priority.ModFileSystemSelector); - _communicator.CollectionInheritanceChanged.Subscribe(OnInheritanceChange, CollectionInheritanceChanged.Priority.ModFileSystemSelector); - _communicator.ModDataChanged.Subscribe(OnModDataChange, ModDataChanged.Priority.ModFileSystemSelector); - _communicator.ModDiscoveryStarted.Subscribe(StoreCurrentSelection, ModDiscoveryStarted.Priority.ModFileSystemSelector); - _communicator.ModDiscoveryFinished.Subscribe(RestoreLastSelection, ModDiscoveryFinished.Priority.ModFileSystemSelector); - SetFilterDirty(); - SelectionChanged += OnSelectionChanged; - } - - public void SetRenameSearchPath(RenameField value) - { - switch (value) - { - case RenameField.RenameSearchPath: - SubscribeRightClickLeaf(RenameLeafMod, 1000); - UnsubscribeRightClickLeaf(RenameMod); - break; - case RenameField.RenameData: - UnsubscribeRightClickLeaf(RenameLeafMod); - SubscribeRightClickLeaf(RenameMod, 1000); - break; - case RenameField.BothSearchPathPrio: - UnsubscribeRightClickLeaf(RenameLeafMod); - UnsubscribeRightClickLeaf(RenameMod); - SubscribeRightClickLeaf(RenameLeafMod, 1001); - SubscribeRightClickLeaf(RenameMod, 1000); - break; - case RenameField.BothDataPrio: - UnsubscribeRightClickLeaf(RenameLeafMod); - UnsubscribeRightClickLeaf(RenameMod); - SubscribeRightClickLeaf(RenameLeafMod, 1000); - SubscribeRightClickLeaf(RenameMod, 1001); - break; - default: - UnsubscribeRightClickLeaf(RenameLeafMod); - UnsubscribeRightClickLeaf(RenameMod); - break; - } - } - - private static readonly string[] ValidModExtensions = - [ - ".ttmp", - ".ttmp2", - ".pmp", - ".pcp", - ".zip", - ".rar", - ".7z", - ]; - - public new void Draw() - { - _dragDrop.CreateImGuiSource("ModDragDrop", m => m.Extensions.Any(e => ValidModExtensions.Contains(e.ToLowerInvariant())), m => - { - ImUtf8.Text($"Dragging mods for import:\n\t{string.Join("\n\t", m.Files.Select(Path.GetFileName))}"); - return true; - }); - base.Draw(); - if (_dragDrop.CreateImGuiTarget("ModDragDrop", out var files, out _)) - _modImportManager.AddUnpack(files.Where(f => ValidModExtensions.Contains(Path.GetExtension(f.ToLowerInvariant())))); - } - - protected override float CurrentWidth - => _config.Ephemeral.CurrentModSelectorWidth * Im.Style.GlobalScale; - - protected override float MinimumAbsoluteRemainder - => 550 * Im.Style.GlobalScale; - - protected override float MinimumScaling - => _config.Ephemeral.ModSelectorMinimumScale; - - protected override float MaximumScaling - => _config.Ephemeral.ModSelectorMaximumScale; - - protected override void SetSize(Vector2 size) - { - base.SetSize(size); - var adaptedSize = MathF.Round(size.X / Im.Style.GlobalScale); - if (adaptedSize == _config.Ephemeral.CurrentModSelectorWidth) - return; - - _config.Ephemeral.CurrentModSelectorWidth = adaptedSize; - _config.Ephemeral.Save(); - } - - public override void Dispose() - { - base.Dispose(); - _communicator.ModDiscoveryStarted.Unsubscribe(StoreCurrentSelection); - _communicator.ModDiscoveryFinished.Unsubscribe(RestoreLastSelection); - _communicator.ModDataChanged.Unsubscribe(OnModDataChange); - _communicator.ModSettingChanged.Unsubscribe(OnSettingChange); - _communicator.CollectionInheritanceChanged.Unsubscribe(OnInheritanceChange); - _communicator.CollectionChange.Unsubscribe(OnCollectionChange); - } - - public new ModFileSystem.Leaf? SelectedLeaf - => base.SelectedLeaf; - - #region Interface - - // Customization points. - public override ISortMode SortMode - => ISortMode.FoldersFirst; - - protected override uint ExpandedFolderColor - => ColorId.FolderExpanded.Value().Color; - - protected override uint CollapsedFolderColor - => ColorId.FolderCollapsed.Value().Color; - - protected override uint FolderLineColor - => ColorId.FolderLine.Value().Color; - - protected override bool FoldersDefaultOpen - => _config.OpenFoldersByDefault; - - protected override void DrawPopups() - { - DrawHelpPopup(); - - if (ImGuiUtil.OpenNameField("Create New Mod", ref _newModName)) - { - var newDir = _modManager.Creator.CreateEmptyMod(_modManager.BasePath, _newModName); - if (newDir != null) - { - _modManager.AddMod(newDir, false); - _newModName = string.Empty; - } - } - - while (_modImportManager.AddUnpackedMod(out var mod)) - SelectByValue(mod); - } - - protected override void DrawLeafName(FileSystem.Leaf leaf, in ModState state, bool selected) - { - var flags = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags; - using var c = ImGuiColor.Text.Push(state.Color.Value().Tinted(state.Tint.Value())) - .Push(ImGuiColor.HeaderHovered, 0x4000FFFF, leaf.Value.Favorite); - using var id = ImUtf8.PushId(leaf.Value.Index); - ImUtf8.TreeNode(leaf.Value.Name, flags).Dispose(); - if (Im.Item.MiddleClicked()) - { - _modManager.SetKnown(leaf.Value); - var (setting, collection) = _collectionManager.Active.Current.GetActualSettings(leaf.Value.Index); - if (_config.DeleteModModifier.ForcedModifier(new DoubleModifier(ModifierHotkey.Control, ModifierHotkey.Shift)).IsActive()) - { - // Delete temporary settings if they exist, regardless of mode, or set to inheriting if none exist. - if (_collectionManager.Active.Current.GetTempSettings(leaf.Value.Index) is not null) - _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, leaf.Value, null); - else - _collectionManager.Editor.SetModInheritance(_collectionManager.Active.Current, leaf.Value, true); - } - else - { - if (_config.DefaultTemporaryMode) - { - var settings = new TemporaryModSettings(leaf.Value, setting) { ForceInherit = false }; - settings.Enabled = !settings.Enabled; - _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, leaf.Value, settings); - } - else - { - var inherited = collection != _collectionManager.Active.Current; - if (inherited) - _collectionManager.Editor.SetModInheritance(_collectionManager.Active.Current, leaf.Value, false); - _collectionManager.Editor.SetModState(_collectionManager.Active.Current, leaf.Value, setting is not { Enabled: true }); - } - } - } - - if (!state.Priority.IsDefault && !_config.HidePrioritiesInSelector) - { - var line = ImGui.GetItemRectMin().Y; - var itemPos = ImGui.GetItemRectMax().X; - var maxWidth = ImGui.GetWindowPos().X + Im.Window.MaximumContentRegion.X; - var priorityString = $"[{state.Priority}]"; - var size = ImGui.CalcTextSize(priorityString).X; - var remainingSpace = maxWidth - itemPos; - var offset = remainingSpace - size; - if (ImGui.GetScrollMaxY() == 0) - offset -= Im.Style.ItemInnerSpacing.X; - - if (offset > Im.Style.ItemSpacing.X) - ImGui.GetWindowDrawList().AddText(new Vector2(itemPos + offset, line), ColorId.SelectorPriority.Value().Color, priorityString); - } - } - - - // Add custom context menu items. - private void EnableDescendants(ModFileSystem.Folder folder) - { - if (ImUtf8.MenuItem("Enable Descendants"u8)) - SetDescendants(folder, true); - } - - private void ClearTemporarySettings() - { - if (ImUtf8.MenuItem("Clear Temporary Settings"u8)) - _collectionManager.Editor.ClearTemporarySettings(_collectionManager.Active.Current); - } - - private void DisableDescendants(ModFileSystem.Folder folder) - { - if (ImUtf8.MenuItem("Disable Descendants"u8)) - SetDescendants(folder, false); - } - - private void InheritDescendants(ModFileSystem.Folder folder) - { - if (ImUtf8.MenuItem("Inherit Descendants"u8)) - SetDescendants(folder, true, true); - } - - private void OwnDescendants(ModFileSystem.Folder folder) - { - if (ImUtf8.MenuItem("Stop Inheriting Descendants"u8)) - SetDescendants(folder, false, true); - } - - private void ToggleLeafFavorite(FileSystem.Leaf mod) - { - if (ImUtf8.MenuItem(mod.Value.Favorite ? "Remove Favorite"u8 : "Mark as Favorite"u8)) - _modManager.DataEditor.ChangeModFavorite(mod.Value, !mod.Value.Favorite); - } - - private void DrawTemporaryOptions(FileSystem.Leaf mod) - { - var tempSettings = _collectionManager.Active.Current.GetTempSettings(mod.Value.Index); - if (tempSettings is { Lock: > 0 }) - return; - - if (tempSettings is { Lock: <= 0 } && ImUtf8.MenuItem("Remove Temporary Settings"u8)) - _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, mod.Value, null); - var actual = _collectionManager.Active.Current.GetActualSettings(mod.Value.Index).Settings; - if (actual?.Enabled is true && ImUtf8.MenuItem("Disable Temporarily"u8)) - _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, mod.Value, - new TemporaryModSettings(mod.Value, actual) { Enabled = false }); - - if (actual is not { Enabled: true } && ImUtf8.MenuItem("Enable Temporarily"u8)) - { - var newSettings = actual is null - ? TemporaryModSettings.DefaultSettings(mod.Value, TemporaryModSettings.OwnSource, true) - : new TemporaryModSettings(mod.Value, actual) { Enabled = true }; - _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, mod.Value, newSettings); - } - - if (tempSettings is null && ImUtf8.MenuItem("Turn Temporary"u8)) - _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, mod.Value, - new TemporaryModSettings(mod.Value, actual)); - } - - private void SetDefaultImportFolder(ModFileSystem.Folder folder) - { - if (!ImUtf8.MenuItem("Set As Default Import Folder"u8)) - return; - - var newName = folder.FullName(); - if (newName == _config.DefaultImportFolder) - return; - - _config.DefaultImportFolder = newName; - _config.Save(); - } - - private void ClearDefaultImportFolder() - { - if (!ImUtf8.MenuItem("Clear Default Import Folder"u8) || _config.DefaultImportFolder.Length <= 0) - return; - - _config.DefaultImportFolder = string.Empty; - _config.Save(); - } - - private string _newModName = string.Empty; - - private void AddNewModButton(Vector2 size) - { - if (ImUtf8.IconButton(FontAwesomeIcon.Plus, "Create a new, empty mod of a given name."u8, size, !_modManager.Valid)) - ImUtf8.OpenPopup("Create New Mod"u8); - } - - /// Add an import mods button that opens a file selector. - private void AddImportModButton(Vector2 size) - { - var button = ImUtf8.IconButton(FontAwesomeIcon.FileImport, - "Import one or multiple mods from Tex Tools Mod Pack Files or Penumbra Mod Pack Files."u8, size, !_modManager.Valid); - _tutorial.OpenTutorial(BasicTutorialSteps.ModImport); - if (!button) - return; - - var modPath = _config.DefaultModImportPath.Length > 0 - ? _config.DefaultModImportPath - : _config.ModDirectory.Length > 0 - ? _config.ModDirectory - : null; - - _fileDialog.OpenFilePicker("Import Mod Pack", - "Mod Packs{.ttmp,.ttmp2,.pmp,.pcp},TexTools Mod Packs{.ttmp,.ttmp2},Penumbra Mod Packs{.pmp,.pcp},Archives{.zip,.7z,.rar},Penumbra Character Packs{.pcp}", - (s, f) => - { - if (!s) - return; - - _modImportManager.AddUnpack(f); - }, 0, modPath, _config.AlwaysOpenDefaultImport); - } - - private void RenameLeafMod(ModFileSystem.Leaf leaf) - { - Im.Separator(); - RenameLeaf(leaf); - } - - private void RenameMod(ModFileSystem.Leaf leaf) - { - Im.Separator(); - var currentName = leaf.Value.Name; - if (ImGui.IsWindowAppearing()) - ImGui.SetKeyboardFocusHere(0); - ImUtf8.Text("Rename Mod:"u8); - if (ImUtf8.InputText("##RenameMod"u8, ref currentName, flags: ImGuiInputTextFlags.EnterReturnsTrue)) - { - _modManager.DataEditor.ChangeModName(leaf.Value, currentName); - Im.Popup.CloseCurrent(); - } - - Im.Tooltip.OnHover("Enter a new name here to rename the changed mod."u8); - } - - private void DeleteModButton(Vector2 size) - => DeleteSelectionButton(size, Unsafe.BitCast(_config.DeleteModModifier), "mod", - "mods", _modManager.DeleteMod); - - private void AddHelpButton(Vector2 size) - { - if (ImUtf8.IconButton(FontAwesomeIcon.QuestionCircle, "Open extended help."u8, size, false)) - ImUtf8.OpenPopup("ExtendedHelp"u8); - - _tutorial.OpenTutorial(BasicTutorialSteps.AdvancedHelp); - } - - private void SetDescendants(ModFileSystem.Folder folder, bool enabled, bool inherit = false) - { - var mods = folder.GetAllDescendants(ISortMode.Lexicographical).OfType().Select(l => - { - // Any mod handled here should not stay new. - _modManager.SetKnown(l.Value); - return l.Value; - }); - - if (inherit) - _collectionManager.Editor.SetMultipleModInheritances(_collectionManager.Active.Current, mods, enabled); - else - _collectionManager.Editor.SetMultipleModStates(_collectionManager.Active.Current, mods, enabled); - } - - private void DrawHelpPopup() - { - ImGuiUtil.HelpPopup("ExtendedHelp", new Vector2(1000 * Im.Style.GlobalScale, 38.5f * Im.Style.TextHeightWithSpacing), () => - { - ImGui.Dummy(Vector2.UnitY * Im.Style.TextHeight); - ImUtf8.Text("Mod Management"u8); - ImUtf8.BulletText("You can create empty mods or import mods with the buttons in this row."u8); - using var indent = ImRaii.PushIndent(); - ImUtf8.BulletText("Supported formats for import are: .ttmp, .ttmp2, .pmp, .pcp."u8); - ImUtf8.BulletText( - "You can also support .zip, .7z or .rar archives, but only if they already contain Penumbra-styled mods with appropriate metadata."u8); - indent.Pop(1); - ImUtf8.BulletText("You can also create empty mod folders and delete mods."u8); - ImUtf8.BulletText( - "For further editing of mods, select them and use the Edit Mod tab in the panel or the Advanced Editing popup."u8); - ImGui.Dummy(Vector2.UnitY * Im.Style.TextHeight); - ImUtf8.Text("Mod Selector"u8); - ImUtf8.BulletText("Select a mod to obtain more information or change settings."u8); - ImUtf8.BulletText("Names are colored according to your config and their current state in the collection:"u8); - indent.Push(); - ImUtf8.BulletTextColored(ColorId.EnabledMod.Value().Color, "enabled in the current collection."u8); - ImUtf8.BulletTextColored(ColorId.DisabledMod.Value().Color, "disabled in the current collection."u8); - ImUtf8.BulletTextColored(ColorId.InheritedMod.Value().Color, "enabled due to inheritance from another collection."u8); - ImUtf8.BulletTextColored(ColorId.InheritedDisabledMod.Value().Color, "disabled due to inheritance from another collection."u8); - ImUtf8.BulletTextColored(ColorId.UndefinedMod.Value().Color, "unconfigured in all inherited collections."u8); - ImUtf8.BulletTextColored(ColorId.HandledConflictMod.Value().Color, - "enabled and conflicting with another enabled Mod, but on different priorities (i.e. the conflict is solved)."u8); - ImUtf8.BulletTextColored(ColorId.ConflictingMod.Value().Color, - "enabled and conflicting with another enabled Mod on the same priority."u8); - ImUtf8.BulletTextColored(ColorId.FolderExpanded.Value().Color, "expanded mod folder."u8); - ImUtf8.BulletTextColored(ColorId.FolderCollapsed.Value().Color, "collapsed mod folder"u8); - indent.Pop(1); - ImUtf8.BulletText("Middle-click a mod to disable it if it is enabled or enable it if it is disabled."u8); - indent.Push(); - ImUtf8.BulletText( - $"Holding {_config.DeleteModModifier.ForcedModifier(new DoubleModifier(ModifierHotkey.Control, ModifierHotkey.Shift))} while middle-clicking lets it inherit, discarding settings."); - indent.Pop(1); - ImUtf8.BulletText("Right-click a mod to enter its sort order, which is its name by default, possibly with a duplicate number."u8); - indent.Push(); - ImUtf8.BulletText("A sort order differing from the mods name will not be displayed, it will just be used for ordering."u8); - ImUtf8.BulletText( - "If the sort order string contains Forward-Slashes ('/'), the preceding substring will be turned into folders automatically."u8); - indent.Pop(1); - ImUtf8.BulletText( - "You can drag and drop mods and subfolders into existing folders. Dropping them onto mods is the same as dropping them onto the parent of the mod."u8); - indent.Push(); - ImUtf8.BulletText( - "You can select multiple mods and folders by holding Control while clicking them, and then drag all of them at once."u8); - ImUtf8.BulletText( - "Selected mods inside an also selected folder will be ignored when dragging and move inside their folder instead of directly into the target."u8); - indent.Pop(1); - ImUtf8.BulletText("Right-clicking a folder opens a context menu."u8); - ImUtf8.BulletText("Right-clicking empty space allows you to expand or collapse all folders at once."u8); - ImUtf8.BulletText("Use the Filter Mods... input at the top to filter the list for mods whose name or path contain the text."u8); - indent.Push(); - ImUtf8.BulletText("You can enter n:[string] to filter only for names, without path."u8); - ImUtf8.BulletText("You can enter c:[string] to filter for Changed Items instead."u8); - ImUtf8.BulletText("You can enter a:[string] to filter for Mod Authors instead."u8); - indent.Pop(1); - ImUtf8.BulletText("Use the expandable menu beside the input to filter for mods fulfilling specific criteria."u8); - }); - } - - private static void HandleException(Exception e) - => Penumbra.Messager.NotificationMessage(e, e.Message, NotificationType.Warning); - - #endregion - - #region Automatic cache update functions. - - private void OnSettingChange(in ModSettingChanged.Arguments arguments) - { - if (arguments.Collection == _collectionManager.Active.Current) - SetFilterDirty(); - } - - private void OnModDataChange(in ModDataChanged.Arguments arguments) - { - const ModDataChangeType relevantFlags = - ModDataChangeType.Name - | ModDataChangeType.Author - | ModDataChangeType.ModTags - | ModDataChangeType.LocalTags - | ModDataChangeType.Favorite - | ModDataChangeType.ImportDate; - if ((arguments.Type & relevantFlags) is not 0) - SetFilterDirty(); - } - - private void OnInheritanceChange(in CollectionInheritanceChanged.Arguments arguments) - { - if (arguments.Collection == _collectionManager.Active.Current) - SetFilterDirty(); - } - - private void OnCollectionChange(in CollectionChange.Arguments arguments) - { - if (arguments.Type is CollectionType.Current && arguments.OldCollection != arguments.NewCollection) - SetFilterDirty(); - } - - // Keep selections across rediscoveries if possible. - private string _lastSelectedDirectory = string.Empty; - - private void StoreCurrentSelection() - { - _lastSelectedDirectory = Selected?.ModPath.FullName ?? string.Empty; - ClearSelection(); - } - - private void RestoreLastSelection() - { - if (_lastSelectedDirectory.Length <= 0) - return; - - var leaf = (ModFileSystem.Leaf?)FileSystem.Root.GetAllDescendants(ISortMode.Lexicographical) - .FirstOrDefault(l => l is ModFileSystem.Leaf m && m.Value.ModPath.FullName == _lastSelectedDirectory); - Select(leaf, AllowMultipleSelection); - _lastSelectedDirectory = string.Empty; - } - - private void OnSelectionChanged(Mod? oldSelection, Mod? newSelection, in ModState state) - => _selection.SelectMod(newSelection); - - #endregion - - #region Filters - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct ModState - { - public ColorId Color; - public ColorId Tint; - public ModPriority Priority; - } - - private ModTypeFilter _stateTypeFilter = ModTypeFilterExtensions.UnfilteredStateMods; - - private void SetFilterTooltip() - { - FilterTooltip = "Filter mods for those where their full paths or names contain the given strings, split by spaces.\n" - + "Enter c:[string] to filter for mods changing specific items.\n" - + "Enter t:[string] to filter for mods set to specific tags.\n" - + "Enter n:[string] to filter only for mod names and no paths.\n" - + "Enter a:[string] to filter for mods by specific authors.\n" - + $"Enter s:[string] to filter for mods by the categories of the items they change (1-{ChangedItemFlagExtensions.NumCategories + 1} or partial category name).\n\n" - + "Use None as a placeholder value that only matches empty lists or names.\n" - + "Regularly, a mod has to match all supplied criteria separately.\n" - + "Put a - in front of a search token to search only for mods not matching the criterion.\n" - + "Put a ? in front of a search token to search for mods matching at least one of the '?'-criteria.\n" - + "Wrap spaces in \"[string with space]\" to match this exact combination of words.\n\n" - + "Example: 't:Tag1 t:\"Tag 2\" -t:Tag3 -a:None s:Body -c:Hempen ?c:Camise ?n:Top' will match any mod that\n" - + " - contains the tags 'tag1' and 'tag 2'\n" - + " - does not contain the tag 'tag3'\n" - + " - has any author set (negating None means Any)\n" - + " - changes an item of the 'Body' category\n" - + " - and either contains a changed item with 'camise' in it's name, or has 'top' in the mod's name."; - } - - /// Appropriately identify and set the string filter and its type. - protected override bool ChangeFilter(string filterValue) - { - _filter.Parse(filterValue); - return true; - } - - /// - /// Check the state filter for a specific pair of has/has-not flags. - /// Uses count == 0 to check for has-not and count != 0 for has. - /// Returns true if it should be filtered and false if not. - /// - private bool CheckFlags(int count, ModTypeFilter hasNoFlag, ModTypeFilter hasFlag) - => count switch - { - 0 when _stateTypeFilter.HasFlag(hasNoFlag) => false, - 0 => true, - _ when _stateTypeFilter.HasFlag(hasFlag) => false, - _ => true, - }; - - /// - /// The overwritten filter method also computes the state. - /// Folders have default state and are filtered out on the direct string instead of the other options. - /// If any filter is set, they should be hidden by default unless their children are visible, - /// or they contain the path search string. - /// - protected override bool ApplyFiltersAndState(FileSystem.IPath path, out ModState state) - { - if (path is ModFileSystem.Folder f) - { - state = default; - return ModTypeFilterExtensions.UnfilteredStateMods != _stateTypeFilter - || !_filter.IsVisible(f); - } - - return ApplyFiltersAndState((ModFileSystem.Leaf)path, out state); - } - - /// Apply the string filters. - private bool ApplyStringFilters(ModFileSystem.Leaf leaf, Mod _) - => !_filter.IsVisible(leaf); - - /// Only get the text color for a mod if no filters are set. - private (ColorId Color, ColorId Tint) GetTextColor(Mod mod, ModSettings? settings, ModCollection collection) - { - var tint = settings.IsTemporary() - ? ColorId.TemporaryModSettingsTint - : _modManager.IsNew(mod) - ? ColorId.NewModTint - : ColorId.NoTint; - if (settings.IsTemporary()) - tint = ColorId.TemporaryModSettingsTint; - - if (settings == null) - return (ColorId.UndefinedMod, tint); - - if (!settings.Enabled) - return (collection != _collectionManager.Active.Current - ? ColorId.InheritedDisabledMod - : ColorId.DisabledMod, tint); - - var conflicts = _collectionManager.Active.Current.Conflicts(mod); - if (conflicts.Count == 0) - return (collection != _collectionManager.Active.Current ? ColorId.InheritedMod : ColorId.EnabledMod, tint); - - return (conflicts.Any(c => !c.Solved) - ? ColorId.ConflictingMod - : ColorId.HandledConflictMod, tint); - } - - private bool CheckStateFilters(Mod mod, ModSettings? settings, ModCollection collection, ref ModState state) - { - var isNew = _modManager.IsNew(mod); - // Handle mod details. - if (CheckFlags(mod.TotalFileCount, ModTypeFilter.HasNoFiles, ModTypeFilter.HasFiles) - || CheckFlags(mod.TotalSwapCount, ModTypeFilter.HasNoFileSwaps, ModTypeFilter.HasFileSwaps) - || CheckFlags(mod.TotalManipulations, ModTypeFilter.HasNoMetaManipulations, ModTypeFilter.HasMetaManipulations) - || CheckFlags(mod.HasOptions ? 1 : 0, ModTypeFilter.HasNoConfig, ModTypeFilter.HasConfig) - || CheckFlags(isNew ? 1 : 0, ModTypeFilter.NotNew, ModTypeFilter.IsNew)) - return true; - - // Handle Favoritism - if (!_stateTypeFilter.HasFlag(ModTypeFilter.Favorite) && mod.Favorite - || !_stateTypeFilter.HasFlag(ModTypeFilter.NotFavorite) && !mod.Favorite) - return true; - - // Handle Temporary - if (!_stateTypeFilter.HasFlag(ModTypeFilter.Temporary) || !_stateTypeFilter.HasFlag(ModTypeFilter.NotTemporary)) - { - if (settings == null && _stateTypeFilter.HasFlag(ModTypeFilter.Temporary)) - return true; - - if (settings != null && settings.IsTemporary() != _stateTypeFilter.HasFlag(ModTypeFilter.Temporary)) - return true; - } - - // Handle Inheritance - if (collection == _collectionManager.Active.Current) - { - if (!_stateTypeFilter.HasFlag(ModTypeFilter.Uninherited)) - return true; - } - else - { - state.Color = ColorId.InheritedMod; - if (!_stateTypeFilter.HasFlag(ModTypeFilter.Inherited)) - return true; - } - - // isNew color takes precedence before other colors. - if (settings.IsTemporary()) - state.Tint = ColorId.TemporaryModSettingsTint; - else if (isNew) - state.Tint = ColorId.NewModTint; - else - state.Tint = ColorId.NoTint; - - - // Handle settings. - if (settings == null) - { - state.Color = ColorId.UndefinedMod; - if (!_stateTypeFilter.HasFlag(ModTypeFilter.Undefined) - || !_stateTypeFilter.HasFlag(ModTypeFilter.Disabled) - || !_stateTypeFilter.HasFlag(ModTypeFilter.NoConflict)) - return true; - } - else if (!settings.Enabled) - { - state.Color = collection != _collectionManager.Active.Current - ? ColorId.InheritedDisabledMod - : ColorId.DisabledMod; - if (!_stateTypeFilter.HasFlag(ModTypeFilter.Disabled) - || !_stateTypeFilter.HasFlag(ModTypeFilter.NoConflict)) - return true; - } - else - { - if (!_stateTypeFilter.HasFlag(ModTypeFilter.Enabled)) - return true; - - // Conflicts can only be relevant if the mod is enabled. - var conflicts = _collectionManager.Active.Current.Conflicts(mod); - if (conflicts.Count > 0) - { - if (conflicts.Any(c => !c.Solved)) - { - if (!_stateTypeFilter.HasFlag(ModTypeFilter.UnsolvedConflict)) - return true; - - state.Color = ColorId.ConflictingMod; - } - else - { - if (!_stateTypeFilter.HasFlag(ModTypeFilter.SolvedConflict)) - return true; - - state.Color = ColorId.HandledConflictMod; - } - } - else if (!_stateTypeFilter.HasFlag(ModTypeFilter.NoConflict)) - { - return true; - } - } - - - return false; - } - - /// Combined wrapper for handling all filters and setting state. - private bool ApplyFiltersAndState(ModFileSystem.Leaf leaf, out ModState state) - { - var mod = leaf.Value; - var (settings, collection) = _collectionManager.Active.Current.GetActualSettings(mod.Index); - - state = new ModState - { - Color = ColorId.EnabledMod, - Priority = settings?.Priority ?? ModPriority.Default, - }; - if (ApplyStringFilters(leaf, mod)) - return true; - - if (_stateTypeFilter != ModTypeFilterExtensions.UnfilteredStateMods) - return CheckStateFilters(mod, settings, collection, ref state); - - (state.Color, state.Tint) = GetTextColor(mod, settings, collection); - return false; - } - - private bool DrawFilterCombo(ref bool everything) - { - using var combo = ImUtf8.Combo("##filterCombo"u8, ""u8, - ImGuiComboFlags.NoPreview | ImGuiComboFlags.PopupAlignLeft | ImGuiComboFlags.HeightLargest); - var ret = Im.Item.RightClicked(); - if (!combo) - return ret; - - using var style = ImStyleDouble.ItemSpacing.PushY(3 * Im.Style.GlobalScale); - - if (ImUtf8.Checkbox("Everything"u8, ref everything)) - { - _stateTypeFilter = everything ? ModTypeFilterExtensions.UnfilteredStateMods : 0; - SetFilterDirty(); - } - - ImGui.Dummy(new Vector2(0, 5 * Im.Style.GlobalScale)); - foreach (var (onFlag, offFlag, name) in ModTypeFilterExtensions.TriStatePairs) - { - if (TriStateCheckbox.Instance.Draw(name, ref _stateTypeFilter, onFlag, offFlag)) - SetFilterDirty(); - } - - foreach (var group in ModTypeFilterExtensions.Groups) - { - Im.Separator(); - foreach (var (flag, name) in group) - { - if (ImUtf8.Checkbox(name, ref _stateTypeFilter, flag)) - SetFilterDirty(); - } - } - - return ret; - } - - /// Add the state filter combo-button to the right of the filter box. - protected override (float, bool) CustomFilters(float width) - { - var pos = ImGui.GetCursorPos(); - var remainingWidth = width - Im.Style.FrameHeight; - var comboPos = new Vector2(pos.X + remainingWidth, pos.Y); - - var everything = _stateTypeFilter == ModTypeFilterExtensions.UnfilteredStateMods; - - ImGui.SetCursorPos(comboPos); - // Draw combo button - using var color = ImGuiColor.Button.Push(Colors.FilterActive, !everything); - var rightClick = DrawFilterCombo(ref everything); - _tutorial.OpenTutorial(BasicTutorialSteps.ModFilters); - if (rightClick) - { - _stateTypeFilter = ModTypeFilterExtensions.UnfilteredStateMods; - SetFilterDirty(); - } - - Im.Tooltip.OnHover("Filter mods for their activation status.\nRight-Click to clear all filters."u8); - ImGui.SetCursorPos(pos); - return (remainingWidth, rightClick); - } - - #endregion -} diff --git a/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs b/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs index 0d5b2016..201caf44 100644 --- a/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs @@ -1,7 +1,5 @@ using ImSharp; using Luna; -using OtterGui; -using OtterGui.Services; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -9,20 +7,18 @@ using Penumbra.Mods; using Penumbra.Mods.Manager; using Penumbra.String; using Penumbra.UI.Classes; +using ImGuiId = ImSharp.ImGuiId; namespace Penumbra.UI.ModsTab; public class ModPanelChangedItemsTab( - ModFileSystemSelector selector, + ModFileSystem fileSystem, ChangedItemDrawer drawer, - ImGuiCacheService cacheService, Configuration config, ModDataEditor dataEditor) : ITab { - private readonly ImGuiCacheService.CacheId _cacheId = cacheService.GetNewId(); - - private class ChangedItemsCache + private class ChangedItemsCache : BasicCache { private Mod? _lastSelected; private ushort _lastUpdate; @@ -199,6 +195,10 @@ public class ModPanelChangedItemsTab( } } } + + /// Handled differently. + public override void Update() + { } } public ReadOnlySpan Label @@ -208,19 +208,22 @@ public class ModPanelChangedItemsTab( => ModPanelTab.ChangedItems; public bool IsVisible - => selector.Selected!.ChangedItems.Count > 0; + => fileSystem.Selection.Selection?.GetValue()?.ChangedItems.Count > 0; + + private Mod _mod = null!; private Vector2 _buttonSize; private Rgba32 _starColor; + private ImGuiId _id; public void DrawContent() { - if (cacheService.Cache(_cacheId, () => (new ChangedItemsCache(), "ModPanelChangedItemsCache")) is not { } cache) - return; - + _id = Im.Id.Current; + _mod = fileSystem.Selection.Selection!.GetValue()!; + var cache = CacheManager.Instance.GetOrCreateCache(_id, () => new ChangedItemsCache()); drawer.DrawTypeFilter(); - cache.Update(selector.Selected, drawer, config.Ephemeral.ChangedItemFilter, config.ChangedItemDisplay); + cache.Update(_mod, drawer, config.Ephemeral.ChangedItemFilter, config.ChangedItemDisplay); Im.Separator(); _buttonSize = new Vector2(Im.Style.ItemSpacing.Y + Im.Style.FrameHeight); using var style = ImStyleDouble.CellPadding.Push(Vector2.Zero) @@ -232,7 +235,7 @@ public class ModPanelChangedItemsTab( .Push(ImGuiColor.ButtonHovered, Rgba32.Transparent); using var table = Im.Table.Begin("##changedItems"u8, cache.AnyExpandable ? 2 : 1, TableFlags.RowBackground | TableFlags.ScrollY, - new Vector2(Im.ContentRegion.Available.X, -1)); + Im.ContentRegion.Available); if (!table) return; @@ -241,11 +244,11 @@ public class ModPanelChangedItemsTab( { table.SetupColumn("##exp"u8, TableColumnFlags.WidthFixed, _buttonSize.Y); table.SetupColumn("##text"u8, TableColumnFlags.WidthStretch); - ImGuiClip.ClippedDraw(cache.Data, DrawContainerExpandable, _buttonSize.Y); + Im.ListClipper.Draw(cache.Data, DrawContainerExpandable, _buttonSize.Y); } else { - ImGuiClip.ClippedDraw(cache.Data, DrawContainer, _buttonSize.Y); + Im.ListClipper.Draw(cache.Data, DrawContainer, _buttonSize.Y); } } @@ -262,7 +265,7 @@ public class ModPanelChangedItemsTab( _buttonSize)) { Im.State.Storage.SetBool(obj.Id, !obj.Expanded); - if (cacheService.TryGetCache(_cacheId, out var cache)) + if (CacheManager.Instance.TryGetCache(_id, out var cache)) cache.Reset(); } } @@ -293,12 +296,12 @@ public class ModPanelChangedItemsTab( if (ImEx.Icon.Button(LunaStyle.FavoriteIcon, "Prefer displaying this item instead of the current primary item.\n\nRight-click for more options."u8, textColor: textColor, size: _buttonSize)) - dataEditor.AddPreferredItem(selector.Selected!, item.Item.Id, false, true); + dataEditor.AddPreferredItem(_mod, item.Item.Id, false, true); using var context = Im.Popup.BeginContextItem("StarContext"u8); if (!context) return; - if (cacheService.TryGetCache(_cacheId, out var cache)) + if (CacheManager.Instance.TryGetCache(_id, out var cache)) for (--idx; idx >= 0; --idx) { if (!cache.Data[idx].Expanded) @@ -306,34 +309,34 @@ public class ModPanelChangedItemsTab( if (cache.Data[idx].Data is IdentifiedItem it) { - if (selector.Selected!.PreferredChangedItems.Contains(it.Item.Id) + if (_mod.PreferredChangedItems.Contains(it.Item.Id) && Im.Menu.Item("Remove Parent from Local Preferred Items"u8)) - dataEditor.RemovePreferredItem(selector.Selected!, it.Item.Id, false); - if (selector.Selected!.DefaultPreferredItems.Contains(it.Item.Id) + dataEditor.RemovePreferredItem(_mod, it.Item.Id, false); + if (_mod.DefaultPreferredItems.Contains(it.Item.Id) && Im.Menu.Item("Remove Parent from Default Preferred Items"u8)) - dataEditor.RemovePreferredItem(selector.Selected!, it.Item.Id, true); + dataEditor.RemovePreferredItem(_mod, it.Item.Id, true); } break; } - var enabled = !selector.Selected!.DefaultPreferredItems.Contains(item.Item.Id); + var enabled = !_mod.DefaultPreferredItems.Contains(item.Item.Id); if (enabled) { if (Im.Menu.Item("Add to Local and Default Preferred Changed Items"u8)) - dataEditor.AddPreferredItem(selector.Selected!, item.Item.Id, true, true); + dataEditor.AddPreferredItem(_mod, item.Item.Id, true, true); } else { if (Im.Menu.Item("Remove from Default Preferred Changed Items"u8)) - dataEditor.RemovePreferredItem(selector.Selected!, item.Item.Id, true); + dataEditor.RemovePreferredItem(_mod, item.Item.Id, true); } if (Im.Menu.Item("Reset Local Preferred Items to Default"u8)) - dataEditor.ResetPreferredItems(selector.Selected!); + dataEditor.ResetPreferredItems(_mod); if (Im.Menu.Item("Clear Local and Default Preferred Items not Changed by the Mod"u8)) - dataEditor.ClearInvalidPreferredItems(selector.Selected!); + dataEditor.ClearInvalidPreferredItems(_mod); } diff --git a/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs b/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs index fcb5b956..41faed8a 100644 --- a/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs @@ -7,7 +7,7 @@ using Penumbra.UI.Classes; namespace Penumbra.UI.ModsTab; -public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSelector selector) : ITab +public class ModPanelCollectionsTab(CollectionManager manager, ModSelection selection) : ITab { private enum ModState { @@ -26,7 +26,7 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele public void DrawContent() { - var (direct, inherited) = CountUsage(selector.Selected!); + var (direct, inherited) = CountUsage(selection.Mod!); Im.Line.New(); switch (direct) { @@ -70,8 +70,8 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele if (Im.Menu.Item("Enable"u8)) { if (parent != collection) - manager.Editor.SetModInheritance(collection, selector.Selected!, false); - manager.Editor.SetModState(collection, selector.Selected!, true); + manager.Editor.SetModInheritance(collection, selection.Mod!, false); + manager.Editor.SetModState(collection, selection.Mod!, true); } } @@ -80,15 +80,15 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele if (Im.Menu.Item("Disable"u8)) { if (parent != collection) - manager.Editor.SetModInheritance(collection, selector.Selected!, false); - manager.Editor.SetModState(collection, selector.Selected!, false); + manager.Editor.SetModInheritance(collection, selection.Mod!, false); + manager.Editor.SetModState(collection, selection.Mod!, false); } } using (Im.Disabled(parent != collection)) { if (Im.Menu.Item("Inherit"u8)) - manager.Editor.SetModInheritance(collection, selector.Selected!, true); + manager.Editor.SetModInheritance(collection, selection.Mod!, true); } } } diff --git a/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs b/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs index c477aae7..ce7b750d 100644 --- a/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs @@ -12,7 +12,7 @@ using Penumbra.UI.Classes; namespace Penumbra.UI.ModsTab; -public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSystemSelector selector) : ITab +public class ModPanelConflictsTab(CollectionManager collectionManager, ModSelection selection) : ITab { public ReadOnlySpan Label => "Conflicts"u8; @@ -21,7 +21,7 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy => ModPanelTab.Conflicts; public bool IsVisible - => collectionManager.Active.Current.Conflicts(selector.Selected!).Any(c => !GetPriority(c).IsHidden); + => collectionManager.Active.Current.Conflicts(selection.Mod!).Any(c => !GetPriority(c).IsHidden); private readonly ConditionalWeakTable _expandedMods = []; @@ -53,7 +53,7 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy DrawCurrentRow(table, priorityWidth); // Can not be null because otherwise the tab bar is never drawn. - var mod = selector.Selected!; + var mod = selection.Mod!; foreach (var (index, conflict) in collectionManager.Active.Current.Conflicts(mod).Where(c => !c.Mod2.Priority.IsHidden) .OrderByDescending(GetPriority) .ThenBy(c => c.Mod2.Name, StringComparer.OrdinalIgnoreCase).Index()) @@ -66,16 +66,16 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy private void DrawCurrentRow(in Im.TableDisposable table, float priorityWidth) { using var c = ImGuiColor.Text.Push(ColorId.FolderLine.Value()); - table.DrawFrameColumn(selector.Selected!.Name); + table.DrawFrameColumn(selection.Mod!.Name); table.NextColumn(); - var actualSettings = collectionManager.Active.Current.GetActualSettings(selector.Selected!.Index).Settings!; + var actualSettings = collectionManager.Active.Current.GetActualSettings(selection.Mod!.Index).Settings!; var priority = actualSettings.Priority.Value; // TODO using (Im.Disabled(actualSettings is TemporaryModSettings)) { if (ImEx.InputOnDeactivation.Scalar("##priority"u8, ref priority)) if (priority != actualSettings.Priority.Value) - collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selector.Selected!, + collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selection.Mod!, new ModPriority(priority)); } @@ -86,7 +86,7 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy { Im.Cursor.FrameAlign(); if (Im.Selectable(conflict.Mod2.Name) && conflict.Mod2 is Mod otherMod) - selector.SelectByValue(otherMod); + selection.SelectMod(otherMod); var hovered = Im.Item.Hovered(); var rightClicked = Im.Item.RightClicked(); if (conflict.Mod2 is Mod otherMod2) @@ -125,7 +125,7 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy table.NextColumn(); var conflictPriority = DrawPriorityInput(conflict, priorityWidth); Im.Line.Same(); - var selectedPriority = collectionManager.Active.Current.GetActualSettings(selector.Selected!.Index).Settings!.Priority.Value; + var selectedPriority = collectionManager.Active.Current.GetActualSettings(selection.Mod!.Index).Settings!.Priority.Value; DrawPriorityButtons(conflict.Mod2 as Mod, conflictPriority, selectedPriority, buttonSize); table.NextColumn(); DrawExpandButton(conflict.Mod2, expanded, buttonSize); @@ -164,10 +164,9 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy if (ImEx.Icon.Button(FontAwesomeIcon.SortNumericUpAlt.Icon(), $"Set the priority of the currently selected mod to this mods priority plus one. ({selectedPriority} -> {conflictPriority + 1})", selectedPriority > conflictPriority, buttonSize)) - collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selector.Selected!, - new ModPriority(conflictPriority + 1)); + collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selection.Mod!, new ModPriority(conflictPriority + 1)); Im.Line.Same(); - if (ImEx.Icon.Button(FontAwesomeIcon.SortNumericDownAlt.Icon(), + if (ImEx.Icon.Button(FontAwesomeIcon.SortNumericDownAlt.Icon(), $"Set the priority of this mod to the currently selected mods priority minus one. ({conflictPriority} -> {selectedPriority - 1})", selectedPriority > conflictPriority || conflict == null, buttonSize)) collectionManager.Editor.SetModPriority(collectionManager.Active.Current, conflict!, new ModPriority(selectedPriority - 1)); diff --git a/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs b/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs index 9166b2ac..be705941 100644 --- a/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs @@ -1,18 +1,18 @@ using ImSharp; using Luna; +using Penumbra.Mods; using Penumbra.Mods.Manager; using Penumbra.UI.Classes; namespace Penumbra.UI.ModsTab; public class ModPanelDescriptionTab( - ModFileSystemSelector selector, + ModSelection selection, TutorialService tutorial, ModManager modManager, PredefinedTagManager predefinedTagsConfig) : ITab { - public ReadOnlySpan Label => "Description"u8; @@ -32,24 +32,22 @@ public class ModPanelDescriptionTab( : (false, 0); var tagIdx = TagButtons.Draw("Local Tags: "u8, "Custom tags you can set personally that will not be exported to the mod data but only set for you.\n"u8 - + "If the mod already contains a local tag in its own tags, the local tag will be ignored."u8, selector.Selected!.LocalTags, + + "If the mod already contains a local tag in its own tags, the local tag will be ignored."u8, selection.Mod!.LocalTags, out var editedTag, rightEndOffset: predefinedTagButtonOffset); tutorial.OpenTutorial(BasicTutorialSteps.Tags); if (tagIdx >= 0) - modManager.DataEditor.ChangeLocalTag(selector.Selected!, tagIdx, editedTag); + modManager.DataEditor.ChangeLocalTag(selection.Mod!, tagIdx, editedTag); if (predefinedTagsEnabled) - predefinedTagsConfig.DrawAddFromSharedTagsAndUpdateTags(selector.Selected!.LocalTags, selector.Selected!.ModTags, true, - selector.Selected!); + predefinedTagsConfig.DrawAddFromSharedTagsAndUpdateTags(selection.Mod!.LocalTags, selection.Mod!.ModTags, true, selection.Mod!); - if (selector.Selected!.ModTags.Count > 0) + if (selection.Mod!.ModTags.Count > 0) TagButtons.Draw("Mod Tags: "u8, "Tags assigned by the mod creator and saved with the mod data. To edit these, look at Edit Mod."u8, - selector.Selected!.ModTags, out _, false, - Im.Font.CalculateSize("Local "u8).X - Im.Font.CalculateSize("Mod "u8).X); + selection.Mod!.ModTags, out _, false, Im.Font.CalculateSize("Local "u8).X - Im.Font.CalculateSize("Mod "u8).X); Im.ScaledDummy(2, 2); Im.Separator(); - Im.TextWrapped(selector.Selected!.Description); + Im.TextWrapped(selection.Mod!.Description); } } diff --git a/Penumbra/UI/ModsTab/ModPanelEditTab.cs b/Penumbra/UI/ModsTab/ModPanelEditTab.cs index ca833aa0..1917e81d 100644 --- a/Penumbra/UI/ModsTab/ModPanelEditTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelEditTab.cs @@ -12,7 +12,6 @@ namespace Penumbra.UI.ModsTab; public class ModPanelEditTab( ModManager modManager, - ModFileSystemSelector selector, ModFileSystem fileSystem, Services.MessageService messager, FilenameService filenames, @@ -24,8 +23,8 @@ public class ModPanelEditTab( AddGroupDrawer addGroupDrawer) : ITab { - private ModFileSystem.Leaf _leaf = null!; - private Mod _mod = null!; + private IFileSystemData _leaf = null!; + private Mod _mod = null!; public ReadOnlySpan Label => "Edit Mod"u8; @@ -39,8 +38,8 @@ public class ModPanelEditTab( if (!child) return; - _leaf = selector.SelectedLeaf!; - _mod = selector.Selected!; + _leaf = (IFileSystemData)fileSystem.Selection.Selection!; + _mod = _leaf.Value; EditButtons(); EditRegularMeta(); @@ -48,7 +47,7 @@ public class ModPanelEditTab( EditLocalData(); UiHelpers.DefaultLineSpace(); - if (Input.Text("Mod Path"u8, Input.Path, Input.None, _leaf.FullName(), out var newPath, UiHelpers.InputTextWidth.X)) + if (Input.Text("Mod Path"u8, Input.Path, Input.None, _leaf.FullPath, out var newPath, UiHelpers.InputTextWidth.X)) try { fileSystem.RenameAndMove(_leaf, newPath); @@ -71,8 +70,7 @@ public class ModPanelEditTab( modManager.DataEditor.ChangeModTag(_mod, tagIdx, editedTag); if (sharedTagsEnabled) - predefinedTagManager.DrawAddFromSharedTagsAndUpdateTags(selector.Selected!.LocalTags, selector.Selected!.ModTags, false, - selector.Selected!); + predefinedTagManager.DrawAddFromSharedTagsAndUpdateTags(_mod.LocalTags, _mod.ModTags, false, _mod); UiHelpers.DefaultLineSpace(); diff --git a/Penumbra/UI/ModsTab/ModSearchStringSplitter.cs b/Penumbra/UI/ModsTab/ModSearchStringSplitter.cs deleted file mode 100644 index d122122f..00000000 --- a/Penumbra/UI/ModsTab/ModSearchStringSplitter.cs +++ /dev/null @@ -1,138 +0,0 @@ -using OtterGui.Filesystem; -using OtterGui.Filesystem.Selector; -using Penumbra.Mods; -using Penumbra.Mods.Manager; -using Penumbra.UI.Classes; - -namespace Penumbra.UI.ModsTab; - -public enum ModSearchType : byte -{ - Default = 0, - ChangedItem, - Tag, - Name, - Author, - Category, -} - -public sealed class ModSearchStringSplitter : SearchStringSplitter.Leaf, ModSearchStringSplitter.Entry> -{ - public readonly struct Entry : ISplitterEntry - { - public string Needle { get; init; } - public ModSearchType Type { get; init; } - public ChangedItemIconFlag IconFlagFilter { get; init; } - - public bool Contains(Entry other) - { - if (Type != other.Type) - return false; - if (Type is ModSearchType.Category) - return IconFlagFilter == other.IconFlagFilter; - - return Needle.Contains(other.Needle); - } - } - - protected override bool ConvertToken(char token, out ModSearchType val) - { - val = token switch - { - 'c' or 'C' => ModSearchType.ChangedItem, - 't' or 'T' => ModSearchType.Tag, - 'n' or 'N' => ModSearchType.Name, - 'a' or 'A' => ModSearchType.Author, - 's' or 'S' => ModSearchType.Category, - _ => ModSearchType.Default, - }; - return val is not ModSearchType.Default; - } - - protected override bool AllowsNone(ModSearchType val) - => val switch - { - ModSearchType.Author => true, - ModSearchType.ChangedItem => true, - ModSearchType.Tag => true, - ModSearchType.Category => true, - _ => false, - }; - - protected override void PostProcessing() - { - base.PostProcessing(); - HandleList(General); - HandleList(Forced); - HandleList(Negated); - return; - - static void HandleList(List list) - { - for (var i = 0; i < list.Count; ++i) - { - var entry = list[i]; - if (entry.Type is not ModSearchType.Category) - continue; - - if (ChangedItemDrawer.TryParsePartial(entry.Needle, out var icon)) - list[i] = entry with - { - IconFlagFilter = icon, - Needle = string.Empty, - }; - else - list.RemoveAt(i--); - } - } - } - - public bool IsVisible(ModFileSystem.Folder folder) - { - switch (State) - { - case FilterState.NoFilters: return true; - case FilterState.NoMatches: return false; - } - - var fullName = folder.FullName(); - return Forced.All(i => MatchesName(i, folder.Name, fullName, false)) - && !Negated.Any(i => MatchesName(i, folder.Name, fullName, true)) - && (General.Count is 0 || General.Any(i => MatchesName(i, folder.Name, fullName, false))); - } - - protected override bool Matches(Entry entry, ModFileSystem.Leaf leaf) - => entry.Type switch - { - ModSearchType.Default => leaf.FullName().AsSpan().Contains(entry.Needle, StringComparison.OrdinalIgnoreCase) - || leaf.Value.Name.AsSpan().Contains(entry.Needle, StringComparison.OrdinalIgnoreCase), - ModSearchType.ChangedItem => leaf.Value.LowerChangedItemsString.AsSpan().Contains(entry.Needle, StringComparison.Ordinal), - ModSearchType.Tag => leaf.Value.AllTagsLower.AsSpan().Contains(entry.Needle, StringComparison.Ordinal), - ModSearchType.Name => leaf.Value.Name.AsSpan().Contains(entry.Needle, StringComparison.OrdinalIgnoreCase), - ModSearchType.Author => leaf.Value.Author.AsSpan().Contains(entry.Needle, StringComparison.OrdinalIgnoreCase), - ModSearchType.Category => leaf.Value.ChangedItems.Any(p => (p.Value.Icon.ToFlag() & entry.IconFlagFilter) is not 0), - _ => true, - }; - - protected override bool MatchesNone(ModSearchType type, bool negated, ModFileSystem.Leaf haystack) - => type switch - { - ModSearchType.Author when negated => haystack.Value.Author.Length > 0, - ModSearchType.Author => haystack.Value.Author.Length is 0, - ModSearchType.ChangedItem when negated => haystack.Value.LowerChangedItemsString.Length > 0, - ModSearchType.ChangedItem => haystack.Value.LowerChangedItemsString.Length is 0, - ModSearchType.Tag when negated => haystack.Value.AllTagsLower.Length > 0, - ModSearchType.Tag => haystack.Value.AllTagsLower.Length is 0, - ModSearchType.Category when negated => haystack.Value.ChangedItems.Count > 0, - ModSearchType.Category => haystack.Value.ChangedItems.Count is 0, - _ => true, - }; - - private static bool MatchesName(Entry entry, ReadOnlySpan name, ReadOnlySpan fullName, bool defaultValue) - => entry.Type switch - { - ModSearchType.Default => fullName.Contains(entry.Needle, StringComparison.OrdinalIgnoreCase), - ModSearchType.Name => name.Contains(entry.Needle, StringComparison.OrdinalIgnoreCase), - _ => defaultValue, - }; -} diff --git a/Penumbra/UI/ModsTab/ModTab.cs b/Penumbra/UI/ModsTab/ModTab.cs new file mode 100644 index 00000000..7750e628 --- /dev/null +++ b/Penumbra/UI/ModsTab/ModTab.cs @@ -0,0 +1,42 @@ +using ImSharp; +using Luna; +using Penumbra.Api.Enums; +using Penumbra.UI.Classes; +using Penumbra.UI.ModsTab.Selector; + +namespace Penumbra.UI.ModsTab; + +public sealed class ModTab : TwoPanelLayout, ITab +{ + private readonly UiConfig _uiConfig; + + public override ReadOnlySpan Label + => "Mods"u8; + + public ModTab(ModFileSystemDrawer drawer, ModPanel panel, CollectionSelectHeader collectionHeader, RedrawFooter redrawFooter, + UiConfig uiConfig) + { + _uiConfig = uiConfig; + LeftHeader = drawer.Header; + LeftFooter = drawer.Footer; + LeftPanel = drawer; + RightPanel = panel; + RightHeader = collectionHeader; + RightFooter = redrawFooter; + } + + public void DrawContent() + => Draw(_uiConfig.ModTabScale); + + public TabType Identifier + => TabType.Mods; + + protected override void SetWidth(float width, ScalingMode mode) + => _uiConfig.ModTabScale = new TwoPanelWidth(width, mode); + + protected override float MinimumWidth + => LeftFooter.MinimumWidth; + + protected override float MaximumWidth + => Im.Window.Width - 500 * Im.Style.GlobalScale; +} diff --git a/Penumbra/UI/ModsTab/MultiModPanel.cs b/Penumbra/UI/ModsTab/MultiModPanel.cs index 52f1eef1..508c7cfa 100644 --- a/Penumbra/UI/ModsTab/MultiModPanel.cs +++ b/Penumbra/UI/ModsTab/MultiModPanel.cs @@ -1,157 +1,153 @@ -using Dalamud.Interface; -using ImSharp; -using Luna; -using Penumbra.Mods; -using Penumbra.Mods.Manager; - -namespace Penumbra.UI.ModsTab; - -public class MultiModPanel(ModFileSystemSelector selector, ModDataEditor editor, PredefinedTagManager tagManager) : IUiService -{ - public void Draw() - { - if (selector.SelectedPaths.Count == 0) - return; - - Im.Line.New(); - var treeNodePos = Im.Cursor.Position; - var numLeaves = DrawModList(); - DrawCounts(treeNodePos, numLeaves); - DrawMultiTagger(); - } - - private void DrawCounts(Vector2 treeNodePos, int numLeaves) - { - var startPos = Im.Cursor.Position; - var numFolders = selector.SelectedPaths.Count - numLeaves; - Utf8StringHandler text = (numLeaves, numFolders) switch - { - (0, 0) => StringU8.Empty, // should not happen - (> 0, 0) => $"{numLeaves} Mods", - (0, > 0) => $"{numFolders} Folders", - _ => $"{numLeaves} Mods, {numFolders} Folders", - }; - Im.Cursor.Position = treeNodePos; - ImEx.TextRightAligned(ref text); - Im.Cursor.Position = startPos; - } - - private int DrawModList() - { - using var tree = Im.Tree.Node("Currently Selected Objects###Selected"u8, - TreeNodeFlags.DefaultOpen | TreeNodeFlags.NoTreePushOnOpen); - Im.Separator(); - - - if (!tree) - return selector.SelectedPaths.Count(l => l is ModFileSystem.Leaf); - - var sizeType = new Vector2(Im.Style.FrameHeight); - var availableSizePercent = (Im.ContentRegion.Available.X - sizeType.X - 4 * Im.Style.CellPadding.X) / 100; - var sizeMods = availableSizePercent * 35; - var sizeFolders = availableSizePercent * 65; - - var leaves = 0; - using (var table = Im.Table.Begin("mods"u8, 3, TableFlags.RowBackground)) - { - if (!table) - return selector.SelectedPaths.Count(l => l is ModFileSystem.Leaf); - - table.SetupColumn("type"u8, TableColumnFlags.WidthFixed, sizeType.X); - table.SetupColumn("mod"u8, TableColumnFlags.WidthFixed, sizeMods); - table.SetupColumn("path"u8, TableColumnFlags.WidthFixed, sizeFolders); - - var i = 0; - foreach (var (fullName, path) in selector.SelectedPaths.Select(p => (p.FullName(), p)) - .OrderBy(p => p.Item1, StringComparer.OrdinalIgnoreCase)) - { - using var id = Im.Id.Push(i++); - var (icon, text) = path is ModFileSystem.Leaf l - ? (FontAwesomeIcon.FileCircleMinus.Icon(), l.Value.Name) - : (FontAwesomeIcon.FolderMinus.Icon(), string.Empty); - table.NextColumn(); - if (ImEx.Icon.Button(icon, "Remove from selection."u8, false, sizeType)) - selector.RemovePathFromMultiSelection(path); +using Dalamud.Interface; +using ImSharp; +using Luna; +using Penumbra.Mods; +using Penumbra.Mods.Manager; - table.DrawFrameColumn(text); - table.DrawFrameColumn(fullName); - if (path is ModFileSystem.Leaf) - ++leaves; - } - } - - Im.Separator(); - return leaves; - } - - private string _tag = string.Empty; - private readonly List _addMods = []; - private readonly List<(Mod, int)> _removeMods = []; - - private void DrawMultiTagger() - { - var width = ImEx.ScaledVector(150, 0); - ImEx.TextFrameAligned("Multi Tagger:"u8); - Im.Line.Same(); - - var predefinedTagsEnabled = tagManager.Enabled; - var inputWidth = predefinedTagsEnabled - ? Im.ContentRegion.Available.X - 2 * width.X - 3 * Im.Style.ItemInnerSpacing.X - Im.Style.FrameHeight - : Im.ContentRegion.Available.X - 2 * (width.X + Im.Style.ItemInnerSpacing.X); - Im.Item.SetNextWidth(inputWidth); - Im.Input.Text("##tag"u8, ref _tag, "Local Tag Name..."u8); - - UpdateTagCache(); - Utf8StringHandler label = _addMods.Count > 0 - ? $"Add to {_addMods.Count} Mods" - : "Add"; - Utf8StringHandler tooltip = _addMods.Count is 0 - ? _tag.Length is 0 - ? "No tag specified." - : $"All mods selected already contain the tag \"{_tag}\", either locally or as mod data." - : $"Add the tag \"{_tag}\" to {_addMods.Count} mods as a local tag:\n\n\t{string.Join("\n\t", _addMods.Select(m => m.Name))}"; - Im.Line.SameInner(); - if (ImEx.Button(label, width, tooltip, _addMods.Count is 0)) - foreach (var mod in _addMods) - editor.ChangeLocalTag(mod, mod.LocalTags.Count, _tag); - - label = _removeMods.Count > 0 - ? $"Remove from {_removeMods.Count} Mods" - : "Remove"; - tooltip = _removeMods.Count is 0 - ? _tag.Length is 0 - ? "No tag specified." - : $"No selected mod contains the tag \"{_tag}\" locally." - : $"Remove the local tag \"{_tag}\" from {_removeMods.Count} mods:\n\n\t{string.Join("\n\t", _removeMods.Select(m => m.Item1.Name))}"; - Im.Line.SameInner(); - if (ImEx.Button(label, width, tooltip, _removeMods.Count is 0)) - foreach (var (mod, index) in _removeMods) - editor.ChangeLocalTag(mod, index, string.Empty); - - if (predefinedTagsEnabled) - { - Im.Line.SameInner(); - tagManager.DrawToggleButton(); - tagManager.DrawListMulti(selector.SelectedPaths.OfType().Select(l => l.Value)); - } - - Im.Separator(); - } - - private void UpdateTagCache() - { - _addMods.Clear(); - _removeMods.Clear(); - if (_tag.Length == 0) - return; - - foreach (var leaf in selector.SelectedPaths.OfType()) - { - var index = leaf.Value.LocalTags.IndexOf(_tag); - if (index >= 0) - _removeMods.Add((leaf.Value, index)); - else if (!leaf.Value.ModTags.Contains(_tag)) - _addMods.Add(leaf.Value); - } - } -} +namespace Penumbra.UI.ModsTab; + +public class MultiModPanel(ModFileSystem fileSystem, ModDataEditor editor, PredefinedTagManager tagManager) : IUiService +{ + public void Draw() + { + if (fileSystem.Selection.OrderedNodes.Count is 0) + return; + + Im.Line.New(); + var treeNodePos = Im.Cursor.Position; + DrawModList(); + DrawCounts(treeNodePos); + DrawMultiTagger(); + } + + private void DrawCounts(Vector2 treeNodePos) + { + var startPos = Im.Cursor.Position; + var numLeaves = fileSystem.Selection.DataNodes.Count; + var numFolders = fileSystem.Selection.Folders.Count; + Utf8StringHandler text = (numLeaves, numFolders) switch + { + (0, 0) => StringU8.Empty, // should not happen + (> 0, 0) => $"{numLeaves} Mods", + (0, > 0) => $"{numFolders} Folders", + _ => $"{numLeaves} Mods, {numFolders} Folders", + }; + Im.Cursor.Position = treeNodePos; + ImEx.TextRightAligned(ref text); + Im.Cursor.Position = startPos; + } + + private void DrawModList() + { + using var tree = Im.Tree.Node("Currently Selected Objects###Selected"u8, + TreeNodeFlags.DefaultOpen | TreeNodeFlags.NoTreePushOnOpen); + Im.Separator(); + + if (!tree) + return; + + var sizeType = new Vector2(Im.Style.FrameHeight); + var availableSizePercent = (Im.ContentRegion.Available.X - sizeType.X - 4 * Im.Style.CellPadding.X) / 100; + var sizeMods = availableSizePercent * 35; + var sizeFolders = availableSizePercent * 65; + + using (var table = Im.Table.Begin("mods"u8, 3, TableFlags.RowBackground)) + { + if (!table) + return; + + table.SetupColumn("type"u8, TableColumnFlags.WidthFixed, sizeType.X); + table.SetupColumn("mod"u8, TableColumnFlags.WidthFixed, sizeMods); + table.SetupColumn("path"u8, TableColumnFlags.WidthFixed, sizeFolders); + + var i = 0; + foreach (var node in fileSystem.Selection.OrderedNodes.OrderBy(p => p.FullPath, StringComparer.OrdinalIgnoreCase)) + { + using var id = Im.Id.Push(i++); + var (icon, text) = node is IFileSystemData l + ? (FontAwesomeIcon.FileCircleMinus.Icon(), l.Value.Name) + : (FontAwesomeIcon.FolderMinus.Icon(), string.Empty); + table.NextColumn(); + if (ImEx.Icon.Button(icon, "Remove from selection."u8, false, sizeType)) + fileSystem.Selection.RemoveFromSelection(node); + + table.DrawFrameColumn(text); + table.DrawFrameColumn(node.FullPath); + } + } + + Im.Separator(); + } + + private string _tag = string.Empty; + private readonly List _addMods = []; + private readonly List<(Mod, int)> _removeMods = []; + + private void DrawMultiTagger() + { + var width = ImEx.ScaledVector(150, 0); + ImEx.TextFrameAligned("Multi Tagger:"u8); + Im.Line.Same(); + + var predefinedTagsEnabled = tagManager.Enabled; + var inputWidth = predefinedTagsEnabled + ? Im.ContentRegion.Available.X - 2 * width.X - 3 * Im.Style.ItemInnerSpacing.X - Im.Style.FrameHeight + : Im.ContentRegion.Available.X - 2 * (width.X + Im.Style.ItemInnerSpacing.X); + Im.Item.SetNextWidth(inputWidth); + Im.Input.Text("##tag"u8, ref _tag, "Local Tag Name..."u8); + + UpdateTagCache(); + Utf8StringHandler label = _addMods.Count > 0 + ? $"Add to {_addMods.Count} Mods" + : "Add"; + Utf8StringHandler tooltip = _addMods.Count is 0 + ? _tag.Length is 0 + ? "No tag specified." + : $"All mods selected already contain the tag \"{_tag}\", either locally or as mod data." + : $"Add the tag \"{_tag}\" to {_addMods.Count} mods as a local tag:\n\n\t{string.Join("\n\t", _addMods.Select(m => m.Name))}"; + Im.Line.SameInner(); + if (ImEx.Button(label, width, tooltip, _addMods.Count is 0)) + foreach (var mod in _addMods) + editor.ChangeLocalTag(mod, mod.LocalTags.Count, _tag); + + label = _removeMods.Count > 0 + ? $"Remove from {_removeMods.Count} Mods" + : "Remove"; + tooltip = _removeMods.Count is 0 + ? _tag.Length is 0 + ? "No tag specified." + : $"No selected mod contains the tag \"{_tag}\" locally." + : $"Remove the local tag \"{_tag}\" from {_removeMods.Count} mods:\n\n\t{string.Join("\n\t", _removeMods.Select(m => m.Item1.Name))}"; + Im.Line.SameInner(); + if (ImEx.Button(label, width, tooltip, _removeMods.Count is 0)) + foreach (var (mod, index) in _removeMods) + editor.ChangeLocalTag(mod, index, string.Empty); + + if (predefinedTagsEnabled) + { + Im.Line.SameInner(); + tagManager.DrawToggleButton(); + tagManager.DrawListMulti(fileSystem.Selection.DataNodes.Select(l => (Mod)l.Value)); + } + + Im.Separator(); + } + + private void UpdateTagCache() + { + _addMods.Clear(); + _removeMods.Clear(); + if (_tag.Length is 0) + return; + + foreach (var leaf in fileSystem.Selection.DataNodes) + { + var mod = (Mod)leaf.Value; + var index = mod.LocalTags.IndexOf(_tag); + if (index >= 0) + _removeMods.Add((mod, index)); + else if (!mod.ModTags.Contains(_tag)) + _addMods.Add(mod); + } + } +} diff --git a/Penumbra/UI/ModsTab/Selector/Buttons/AddNewModButton.cs b/Penumbra/UI/ModsTab/Selector/Buttons/AddNewModButton.cs index 1b6e8404..78e7cc3a 100644 --- a/Penumbra/UI/ModsTab/Selector/Buttons/AddNewModButton.cs +++ b/Penumbra/UI/ModsTab/Selector/Buttons/AddNewModButton.cs @@ -21,74 +21,12 @@ public sealed class AddNewModButton(ModFileSystemDrawer drawer) : BaseIconButton /// public override void OnClick() - => Im.Popup.Open("Create New Mod"u8); + => Im.Popup.Open("newMod"u8); /// protected override void PostDraw() { - if (!InputPopup.OpenName("Create New Mod"u8, out var newModName)) - return; - - if (drawer.ModManager.Creator.CreateEmptyMod(drawer.ModManager.BasePath, newModName) is { } directory) - drawer.ModManager.AddMod(directory, false); - } -} - -/// The button to import a mod. -/// The file system drawer. -public sealed class ImportModButton(ModFileSystemDrawer drawer) : BaseIconButton -{ - /// - public override AwesomeIcon Icon - => LunaStyle.AddObjectIcon; - - /// - public override bool HasTooltip - => true; - - /// - public override void DrawTooltip() - => Im.Text("Create a new, empty mod of a given name."u8); - - /// - public override void OnClick() - => Im.Popup.Open("Create New Mod"u8); - - /// - protected override void PostDraw() - { - if (!InputPopup.OpenName("Create New Mod"u8, out var newModName)) - return; - - if (drawer.ModManager.Creator.CreateEmptyMod(drawer.ModManager.BasePath, newModName) is { } directory) - drawer.ModManager.AddMod(directory, false); - } -} - -/// The button to import a mod. -/// The file system drawer. -public sealed class DeleteSelectionButton(ModFileSystemDrawer drawer) : BaseIconButton -{ - /// - public override AwesomeIcon Icon - => LunaStyle.AddObjectIcon; - - /// - public override bool HasTooltip - => true; - - /// - public override void DrawTooltip() - => Im.Text("Create a new, empty mod of a given name."u8); - - /// - public override void OnClick() - => Im.Popup.Open("Create New Mod"u8); - - /// - protected override void PostDraw() - { - if (!InputPopup.OpenName("Create New Mod"u8, out var newModName)) + if (!InputPopup.OpenName("newMod"u8, out var newModName)) return; if (drawer.ModManager.Creator.CreateEmptyMod(drawer.ModManager.BasePath, newModName) is { } directory) diff --git a/Penumbra/UI/ModsTab/Selector/Buttons/ClearQuickMoveFoldersButtons.cs b/Penumbra/UI/ModsTab/Selector/Buttons/ClearQuickMoveFoldersButtons.cs new file mode 100644 index 00000000..880eb76b --- /dev/null +++ b/Penumbra/UI/ModsTab/Selector/Buttons/ClearQuickMoveFoldersButtons.cs @@ -0,0 +1,50 @@ +using ImSharp; +using Luna; + +namespace Penumbra.UI.ModsTab.Selector; + +public sealed class ClearQuickMoveFoldersButtons(ModFileSystemDrawer drawer) : BaseButton +{ + public override ReadOnlySpan Label + => throw new NotImplementedException(); + + public override bool DrawMenuItem() + { + if (drawer.Config.QuickMoveFolder1.Length > 0) + { + if (Im.Menu.Item("Clear Quick Move Folder #1"u8)) + { + drawer.Config.QuickMoveFolder1 = string.Empty; + drawer.Config.Save(); + } + + Im.Tooltip.OnHover($"Clear the current quick move assignment of {drawer.Config.QuickMoveFolder1}."); + } + + + if (drawer.Config.QuickMoveFolder2.Length > 0) + { + if (Im.Menu.Item("Clear Quick Move Folder #2"u8)) + { + drawer.Config.QuickMoveFolder2 = string.Empty; + drawer.Config.Save(); + } + + Im.Tooltip.OnHover($"Clear the current quick move assignment of {drawer.Config.QuickMoveFolder2}."); + } + + + if (drawer.Config.QuickMoveFolder3.Length > 0) + { + if (Im.Menu.Item("Clear Quick Move Folder #3"u8)) + { + drawer.Config.QuickMoveFolder3 = string.Empty; + drawer.Config.Save(); + } + + Im.Tooltip.OnHover($"Clear the current quick move assignment of {drawer.Config.QuickMoveFolder3}."); + } + + return false; + } +} diff --git a/Penumbra/UI/ModsTab/Selector/Buttons/DeleteSelectionButton.cs b/Penumbra/UI/ModsTab/Selector/Buttons/DeleteSelectionButton.cs new file mode 100644 index 00000000..dfba8e61 --- /dev/null +++ b/Penumbra/UI/ModsTab/Selector/Buttons/DeleteSelectionButton.cs @@ -0,0 +1,53 @@ +using ImSharp; +using Luna; +using Penumbra.Mods; + +namespace Penumbra.UI.ModsTab.Selector; + +/// The button to import a mod. +/// The file system drawer. +public sealed class DeleteSelectionButton(ModFileSystemDrawer drawer) : BaseIconButton +{ + /// + public override AwesomeIcon Icon + => LunaStyle.DeleteIcon; + + /// + public override bool HasTooltip + => true; + + /// + public override void DrawTooltip() + { + var anySelected = drawer.FileSystem.Selection.DataNodes.Count > 0; + var modifier = Enabled; + + Im.Text(anySelected ? "Delete the currently selected mods entirely from your drive\nThis can not be undone."u8 : "No mods selected."u8); + if (!modifier) + Im.Text($"Hold {drawer.Config.DeleteModModifier} while clicking to delete the mods."); + } + + /// + public override bool Enabled + => drawer.Config.DeleteModModifier.IsActive() && drawer.FileSystem.Selection.DataNodes.Count > 0; + + /// + public override void OnClick() + { + foreach (var node in drawer.FileSystem.Selection.DataNodes.ToArray()) + { + if (node.GetValue() is { } mod) + drawer.ModManager.DeleteMod(mod); + } + } + + /// + protected override void PostDraw() + { + if (!InputPopup.OpenName("Create New Mod"u8, out var newModName)) + return; + + if (drawer.ModManager.Creator.CreateEmptyMod(drawer.ModManager.BasePath, newModName) is { } directory) + drawer.ModManager.AddMod(directory, false); + } +} diff --git a/Penumbra/UI/ModsTab/Selector/Buttons/HelpButton.cs b/Penumbra/UI/ModsTab/Selector/Buttons/HelpButton.cs new file mode 100644 index 00000000..db76721f --- /dev/null +++ b/Penumbra/UI/ModsTab/Selector/Buttons/HelpButton.cs @@ -0,0 +1,91 @@ +using ImSharp; +using Luna; +using Penumbra.UI.Classes; + +namespace Penumbra.UI.ModsTab.Selector; + +/// The button to open the help popup. +public sealed class HelpButton(ModFileSystemDrawer drawer) : BaseIconButton +{ + /// + public override AwesomeIcon Icon + => LunaStyle.InfoIcon; + + /// + public override bool HasTooltip + => true; + + /// + public override void DrawTooltip() + => Im.Text("Open extended help."u8); + + /// + public override void OnClick() + => Im.Popup.Open("ExHelp"u8); + + /// + protected override void PostDraw() + { + drawer.Tutorial.OpenTutorial(BasicTutorialSteps.AdvancedHelp); + ImEx.HelpPopup("ExtendedHelp"u8, new Vector2(1000 * Im.Style.GlobalScale, 38.5f * Im.Style.TextHeightWithSpacing), PopupContent); + } + + private void PopupContent() + { + Im.Line.New(); + Im.Text("Mod Management"u8); + Im.BulletText("You can create empty mods or import mods with the buttons in this row."u8); + using var indent = Im.Indent(); + Im.BulletText("Supported formats for import are: .ttmp, .ttmp2, .pmp, .pcp."u8); + Im.BulletText( + "You can also support .zip, .7z or .rar archives, but only if they already contain Penumbra-styled mods with appropriate metadata."u8); + indent.Unindent(); + Im.BulletText("You can also create empty mod folders and delete mods."u8); + Im.BulletText( + "For further editing of mods, select them and use the Edit Mod tab in the panel or the Advanced Editing popup."u8); + Im.Line.New(); + Im.Text("Mod Selector"u8); + Im.BulletText("Select a mod to obtain more information or change settings."u8); + Im.BulletText("Names are colored according to your config and their current state in the collection:"u8); + indent.Indent(); + Im.BulletText("enabled in the current collection."u8, ColorId.EnabledMod.Value()); + Im.BulletText("disabled in the current collection."u8, ColorId.DisabledMod.Value()); + Im.BulletText("enabled due to inheritance from another collection."u8, ColorId.InheritedMod.Value()); + Im.BulletText("disabled due to inheritance from another collection."u8, ColorId.InheritedDisabledMod.Value()); + Im.BulletText("unconfigured in all inherited collections."u8, ColorId.UndefinedMod.Value()); + Im.BulletText("enabled and conflicting with another enabled Mod, but on different priorities (i.e. the conflict is solved)."u8, + ColorId.HandledConflictMod.Value()); + Im.BulletText("enabled and conflicting with another enabled Mod on the same priority."u8, ColorId.ConflictingMod.Value()); + Im.BulletText("expanded mod folder."u8, ColorId.FolderExpanded.Value()); + Im.BulletText("collapsed mod folder"u8, ColorId.FolderCollapsed.Value()); + indent.Unindent(); + Im.BulletText("Middle-click a mod to disable it if it is enabled or enable it if it is disabled."u8); + indent.Indent(); + Im.BulletText( + $"Holding {drawer.Config.DeleteModModifier.ForcedModifier(new DoubleModifier(ModifierHotkey.Control, ModifierHotkey.Shift))} while middle-clicking lets it inherit, discarding settings."); + indent.Unindent(); + Im.BulletText("Right-click a mod to enter its sort order, which is its name by default, possibly with a duplicate number."u8); + indent.Indent(); + Im.BulletText("A sort order differing from the mods name will not be displayed, it will just be used for ordering."u8); + Im.BulletText( + "If the sort order string contains Forward-Slashes ('/'), the preceding substring will be turned into folders automatically."u8); + indent.Unindent(); + Im.BulletText( + "You can drag and drop mods and subfolders into existing folders. Dropping them onto mods is the same as dropping them onto the parent of the mod."u8); + indent.Indent(); + Im.BulletText( + "You can select multiple mods and folders by holding Control while clicking them, and then drag all of them at once."u8); + Im.BulletText( + "Selected mods inside an also selected folder will be ignored when dragging and move inside their folder instead of directly into the target."u8); + indent.Unindent(); + Im.BulletText("Right-clicking a folder opens a context menu."u8); + Im.BulletText("Right-clicking empty space allows you to expand or collapse all folders at once."u8); + Im.BulletText("Use the Filter Mods... input at the top to filter the list for mods whose name or path contain the text."u8); + indent.Indent(); + Im.BulletText("You can enter n:[string] to filter only for names, without path."u8); + Im.BulletText("You can enter c:[string] to filter for Changed Items instead."u8); + Im.BulletText("You can enter a:[string] to filter for Mod Authors instead."u8); + indent.Unindent(); + Im.BulletText("Use the expandable menu beside the input to filter for mods fulfilling specific criteria."u8); + } +} diff --git a/Penumbra/UI/ModsTab/Selector/Buttons/ImportModButton.cs b/Penumbra/UI/ModsTab/Selector/Buttons/ImportModButton.cs new file mode 100644 index 00000000..1ce9e5f4 --- /dev/null +++ b/Penumbra/UI/ModsTab/Selector/Buttons/ImportModButton.cs @@ -0,0 +1,45 @@ +using ImSharp; +using Luna; +using Penumbra.UI.Classes; + +namespace Penumbra.UI.ModsTab.Selector; + +/// The button to import a mod. +public sealed class ImportModButton(ModFileSystemDrawer drawer) : BaseIconButton +{ + /// + public override AwesomeIcon Icon + => LunaStyle.ImportIcon; + + /// + public override bool HasTooltip + => true; + + /// + public override void DrawTooltip() + => Im.Text("Import one or multiple mods from Tex Tools Mod Pack Files or Penumbra Mod Pack Files."u8); + + /// + public override void OnClick() + { + var modPath = drawer.Config.DefaultModImportPath.Length > 0 + ? drawer.Config.DefaultModImportPath + : drawer.Config.ModDirectory.Length > 0 + ? drawer.Config.ModDirectory + : null; + + drawer.FileService.OpenFilePicker("Import Mod Pack", + "Mod Packs{.ttmp,.ttmp2,.pmp,.pcp},TexTools Mod Packs{.ttmp,.ttmp2},Penumbra Mod Packs{.pmp,.pcp},Archives{.zip,.7z,.rar},Penumbra Character Packs{.pcp}", + (s, f) => + { + if (!s) + return; + + drawer.ModImport.AddUnpack(f); + }, 0, modPath, drawer.Config.AlwaysOpenDefaultImport); + } + + /// + protected override void PostDraw() + => drawer.Tutorial.OpenTutorial(BasicTutorialSteps.ModImport); +} diff --git a/Penumbra/UI/ModsTab/Selector/Buttons/MoveToQuickMoveFoldersButtons.cs b/Penumbra/UI/ModsTab/Selector/Buttons/MoveToQuickMoveFoldersButtons.cs new file mode 100644 index 00000000..edd6e004 --- /dev/null +++ b/Penumbra/UI/ModsTab/Selector/Buttons/MoveToQuickMoveFoldersButtons.cs @@ -0,0 +1,84 @@ +using ImSharp; +using Luna; + +namespace Penumbra.UI.ModsTab.Selector; + +public sealed class MoveToQuickMoveFoldersButtons(ModFileSystemDrawer drawer) : BaseButton +{ + public override ReadOnlySpan Label(in IFileSystemData data) + => throw new NotImplementedException(); + + public override bool DrawMenuItem(in IFileSystemData data) + { + var currentName = data.Name; + var currentPath = data.FullPath; + using var id = new Im.IdDisposable(); + if (drawer.Config.QuickMoveFolder1.Length > 0) + { + id.Push(0); + var targetPath = $"{drawer.Config.QuickMoveFolder1}/{currentName}"; + if (!drawer.FileSystem.Equal(currentPath, targetPath)) + { + if (Im.Menu.Item($"Move to {drawer.Config.QuickMoveFolder1}")) + { + foreach (var path in drawer.FileSystem.Selection.OrderedNodes) + { + if (path != data) + drawer.FileSystem.RenameAndMoveWithDuplicates(path, $"{drawer.Config.QuickMoveFolder1}/{path.Name}"); + } + + drawer.FileSystem.RenameAndMoveWithDuplicates(data, targetPath); + } + Im.Tooltip.OnHover("Move the selected objects to a previously set-up quick move location, if possible."u8); + } + + id.Pop(); + } + + if (drawer.Config.QuickMoveFolder2.Length > 0) + { + id.Push(1); + var targetPath = $"{drawer.Config.QuickMoveFolder2}/{currentName}"; + if (!drawer.FileSystem.Equal(currentPath, targetPath)) + { + if (Im.Menu.Item($"Move to {drawer.Config.QuickMoveFolder2}")) + { + foreach (var path in drawer.FileSystem.Selection.OrderedNodes) + { + if (path != data) + drawer.FileSystem.RenameAndMoveWithDuplicates(path, $"{drawer.Config.QuickMoveFolder2}/{path.Name}"); + } + + drawer.FileSystem.RenameAndMoveWithDuplicates(data, targetPath); + } + Im.Tooltip.OnHover("Move the selected objects to a previously set-up quick move location, if possible."u8); + } + + id.Pop(); + } + + if (drawer.Config.QuickMoveFolder3.Length > 0) + { + id.Push(2); + var targetPath = $"{drawer.Config.QuickMoveFolder3}/{currentName}"; + if (!drawer.FileSystem.Equal(currentPath, targetPath)) + { + if (Im.Menu.Item($"Move to {drawer.Config.QuickMoveFolder3}")) + { + foreach (var path in drawer.FileSystem.Selection.OrderedNodes) + { + if (path != data) + drawer.FileSystem.RenameAndMoveWithDuplicates(path, $"{drawer.Config.QuickMoveFolder3}/{path.Name}"); + } + + drawer.FileSystem.RenameAndMoveWithDuplicates(data, targetPath); + } + Im.Tooltip.OnHover("Move the selected objects to a previously set-up quick move location, if possible."u8); + } + + id.Pop(); + } + + return false; + } +} diff --git a/Penumbra/UI/ModsTab/Selector/Buttons/SetQuickMoveButtons.cs b/Penumbra/UI/ModsTab/Selector/Buttons/SetQuickMoveButtons.cs new file mode 100644 index 00000000..533dc670 --- /dev/null +++ b/Penumbra/UI/ModsTab/Selector/Buttons/SetQuickMoveButtons.cs @@ -0,0 +1,35 @@ +using ImSharp; +using Luna; + +namespace Penumbra.UI.ModsTab.Selector; + +public sealed class SetQuickMoveFoldersButtons(ModFileSystemDrawer drawer) : BaseButton +{ + public override ReadOnlySpan Label(in IFileSystemFolder data) + => throw new NotImplementedException(); + + public override bool DrawMenuItem(in IFileSystemFolder data) + { + if (Im.Menu.Item("Set as Quick Move Folder #1"u8)) + { + drawer.Config.QuickMoveFolder1 = data.FullPath; + drawer.Config.Save(); + } + Im.Tooltip.OnHover(drawer.Config.QuickMoveFolder1.Length is 0 ? "Set this folder as a quick move location."u8 : $"Set this folder as a quick move location instead of {drawer.Config.QuickMoveFolder1}."); + + if (Im.Menu.Item("Set as Quick Move Folder #2"u8)) + { + drawer.Config.QuickMoveFolder2 = data.FullPath; + drawer.Config.Save(); + } + Im.Tooltip.OnHover(drawer.Config.QuickMoveFolder2.Length is 0 ? "Set this folder as a quick move location."u8 : $"Set this folder as a quick move location instead of {drawer.Config.QuickMoveFolder2}."); + + if (Im.Menu.Item("Set as Quick Move Folder #3"u8)) + { + drawer.Config.QuickMoveFolder3 = data.FullPath; + drawer.Config.Save(); + } + Im.Tooltip.OnHover(drawer.Config.QuickMoveFolder3.Length is 0 ? "Set this folder as a quick move location."u8 : $"Set this folder as a quick move location instead of {drawer.Config.QuickMoveFolder3}."); + return false; + } +} diff --git a/Penumbra/UI/ModsTab/Selector/Buttons/TemporaryButtons.cs b/Penumbra/UI/ModsTab/Selector/Buttons/TemporaryButtons.cs new file mode 100644 index 00000000..98e2ec45 --- /dev/null +++ b/Penumbra/UI/ModsTab/Selector/Buttons/TemporaryButtons.cs @@ -0,0 +1,42 @@ +using ImSharp; +using Luna; +using Penumbra.Mods; +using Penumbra.Mods.Settings; + +namespace Penumbra.UI.ModsTab.Selector; + +public sealed class TemporaryButtons(ModFileSystemDrawer drawer) : BaseButton +{ + public override ReadOnlySpan Label(in IFileSystemData data) + => throw new NotImplementedException(); + + public override bool DrawMenuItem(in IFileSystemData data) + { + if (data.GetValue() is not { } mod) + return false; + + var current = drawer.CollectionManager.Active.Current; + var tempSettings = current.GetTempSettings(mod.Index); + if (tempSettings is { Lock: > 0 }) + return false; + + var editor = drawer.CollectionManager.Editor; + if (tempSettings is { Lock: <= 0 } && Im.Menu.Item("Remove Temporary Settings"u8)) + editor.SetTemporarySettings(current, mod, null); + var actual = current.GetActualSettings(mod.Index).Settings; + if (actual?.Enabled is true && Im.Menu.Item("Disable Temporarily"u8)) + editor.SetTemporarySettings(current, mod, new TemporaryModSettings(mod, actual) { Enabled = false }); + + if (actual is not { Enabled: true } && Im.Menu.Item("Enable Temporarily"u8)) + { + var newSettings = actual is null + ? TemporaryModSettings.DefaultSettings(mod, TemporaryModSettings.OwnSource, true) + : new TemporaryModSettings(mod, actual) { Enabled = true }; + editor.SetTemporarySettings(current, mod, newSettings); + } + + if (tempSettings is null && Im.Menu.Item("Turn Temporary"u8)) + editor.SetTemporarySettings(current, mod, new TemporaryModSettings(mod, actual)); + return false; + } +} diff --git a/Penumbra/UI/ModsTab/Selector/ModFileSystemCache.cs b/Penumbra/UI/ModsTab/Selector/ModFileSystemCache.cs index e9191076..ac25d5ed 100644 --- a/Penumbra/UI/ModsTab/Selector/ModFileSystemCache.cs +++ b/Penumbra/UI/ModsTab/Selector/ModFileSystemCache.cs @@ -1,15 +1,79 @@ using ImSharp; using Luna; using Penumbra.Collections; +using Penumbra.Collections.Manager; +using Penumbra.Communication; using Penumbra.Mods; +using Penumbra.Mods.Manager; using Penumbra.Mods.Settings; using Penumbra.UI.Classes; namespace Penumbra.UI.ModsTab.Selector; -public sealed class ModFileSystemCache(ModFileSystemDrawer parent) - : FileSystemCache(parent), IService +public sealed class ModFileSystemCache : FileSystemCache, IService { + private new ModFileSystemDrawer Parent + => (ModFileSystemDrawer)base.Parent; + + public ModFileSystemCache(ModFileSystemDrawer parent) + : base(parent) + { + Parent.Communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.ModFileSystemCache); + Parent.Communicator.CollectionInheritanceChanged.Subscribe(OnInheritanceChange, + CollectionInheritanceChanged.Priority.ModFileSystemCache); + Parent.Communicator.ModSettingChanged.Subscribe(OnSettingChange, ModSettingChanged.Priority.ModFileSystemCache); + Parent.Communicator.ModDataChanged.Subscribe(OnModDataChange, ModDataChanged.Priority.ModFileSystemCache); + } + + private void OnModDataChange(in ModDataChanged.Arguments arguments) + { + const ModDataChangeType relevantFlags = + ModDataChangeType.Name + | ModDataChangeType.Author + | ModDataChangeType.ModTags + | ModDataChangeType.LocalTags + | ModDataChangeType.Favorite + | ModDataChangeType.ImportDate; + if ((arguments.Type & relevantFlags) is not 0 && !Filter.IsEmpty) + VisibleDirty = true; + + if (arguments.Mod.Node is { } node && AllNodes.TryGetValue(node, out var cache)) + cache.Dirty = true; + } + + private void OnSettingChange(in ModSettingChanged.Arguments arguments) + { + if (!Filter.IsEmpty) + VisibleDirty = true; + + if (arguments.Mod?.Node is { } node && AllNodes.TryGetValue(node, out var cache)) + cache.Dirty = true; + } + + private void OnInheritanceChange(in CollectionInheritanceChanged.Arguments arguments) + { + if (arguments.Collection == Parent.CollectionManager.Active.Current) + { + if (!Filter.IsEmpty) + VisibleDirty = true; + + foreach (var node in AllNodes.Values) + node.Dirty = true; + } + } + + private void OnCollectionChange(in CollectionChange.Arguments arguments) + { + if (arguments.Type is CollectionType.Current && arguments.OldCollection != arguments.NewCollection) + { + if (!Filter.IsEmpty) + VisibleDirty = true; + + foreach (var node in AllNodes.Values) + node.Dirty = true; + } + } + public sealed class ModData(IFileSystemData node) : BaseFileSystemNodeCache { public readonly IFileSystemData Node = node; @@ -130,6 +194,15 @@ public sealed class ModFileSystemCache(ModFileSystemDrawer parent) } } + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + Parent.Communicator.CollectionChange.Unsubscribe(OnCollectionChange); + Parent.Communicator.CollectionInheritanceChanged.Unsubscribe(OnInheritanceChange); + Parent.Communicator.ModSettingChanged.Unsubscribe(OnSettingChange); + Parent.Communicator.ModDataChanged.Unsubscribe(OnModDataChange); + } + protected override ModData ConvertNode(in IFileSystemNode node) => new((IFileSystemData)node); } diff --git a/Penumbra/UI/ModsTab/Selector/ModFileSystemDrawer.cs b/Penumbra/UI/ModsTab/Selector/ModFileSystemDrawer.cs index 1c658e42..f66ef125 100644 --- a/Penumbra/UI/ModsTab/Selector/ModFileSystemDrawer.cs +++ b/Penumbra/UI/ModsTab/Selector/ModFileSystemDrawer.cs @@ -2,33 +2,52 @@ using Luna; using Penumbra.Collections.Manager; using Penumbra.Mods; using Penumbra.Mods.Manager; +using Penumbra.Services; +using Penumbra.UI.Classes; namespace Penumbra.UI.ModsTab.Selector; public sealed class ModFileSystemDrawer : FileSystemDrawer { - public readonly ModManager ModManager; - public readonly CollectionManager CollectionManager; - public readonly Configuration Config; + public readonly ModManager ModManager; + public readonly CollectionManager CollectionManager; + public readonly Configuration Config; + public readonly ModImportManager ModImport; + public readonly FileDialogService FileService; + public readonly TutorialService Tutorial; + public readonly CommunicatorService Communicator; - public ModFileSystemDrawer(ModFileSystem2 fileSystem, ModManager modManager, CollectionManager collectionManager, Configuration config) + 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)) { ModManager = modManager; CollectionManager = collectionManager; Config = config; + ModImport = modImport; + FileService = fileService; + Tutorial = tutorial; + Communicator = communicator; MainContext.AddButton(new ClearTemporarySettingsButton(this), 105); - MainContext.AddButton(new ClearDefaultImportFolderButton(this), 10); + MainContext.AddButton(new ClearDefaultImportFolderButton(this), -10); + MainContext.AddButton(new ClearQuickMoveFoldersButtons(this), -20); FolderContext.AddButton(new SetDescendantsButton(this, true), 11); FolderContext.AddButton(new SetDescendantsButton(this, false), 10); FolderContext.AddButton(new SetDescendantsButton(this, true, true), 6); FolderContext.AddButton(new SetDescendantsButton(this, false, true), 5); - FolderContext.AddButton(new SetDefaultImportFolderButton(this), -100); + FolderContext.AddButton(new SetDefaultImportFolderButton(this), -50); + FolderContext.AddButton(new SetQuickMoveFoldersButtons(this), -70); DataContext.AddButton(new ToggleFavoriteButton(this), 10); - Footer.Buttons.AddButton(new AddNewModButton(this), 1000); + DataContext.AddButton(new TemporaryButtons(this), 20); + DataContext.AddButton(new MoveToQuickMoveFoldersButtons(this), -100); + + Footer.Buttons.AddButton(new AddNewModButton(this), 1000); + Footer.Buttons.AddButton(new ImportModButton(this), 900); + Footer.Buttons.AddButton(new HelpButton(this), 500); + Footer.Buttons.AddButton(new DeleteSelectionButton(this), -100); } public override ReadOnlySpan Id @@ -37,7 +56,6 @@ public sealed class ModFileSystemDrawer : FileSystemDrawer CreateCache() => new ModFileSystemCache(this); - public void SetDescendants(IFileSystemFolder folder, bool enabled, bool inherit = false) { var mods = folder.GetDescendants().OfType>().Select(l => diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs index d931799c..c74b45b2 100644 --- a/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs +++ b/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs @@ -336,12 +336,9 @@ public sealed class ResourceWatcher : IDisposable, ITab private void Enqueue(Record record) { - lock (_newRecords) - { - // Discard entries that exceed the number of records. - while (_newRecords.Count >= _config.MaxResourceWatcherRecords) - _newRecords.TryDequeue(out _); - _newRecords.Enqueue(record); - } + // Discard entries that exceed the number of records. + while (_newRecords.Count >= _config.MaxResourceWatcherRecords) + _newRecords.TryDequeue(out _); + _newRecords.Enqueue(record); } } diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs index 628c0e36..6385b52a 100644 --- a/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs +++ b/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs @@ -76,12 +76,14 @@ internal sealed class ResourceWatcherTable : TableBase _records; - public bool WouldBeVisible(Record record) + internal interface ICheckRecord { - var cached = new CachedRecord(record); - return Columns.All(c => c.WouldBeVisible(cached, -1)); + public bool WouldBeVisible(in Record record); } + public bool WouldBeVisible(Record record) + => Columns.OfType().All(column => column.WouldBeVisible(record)); + public ResourceWatcherTable(ResourceWatcherConfig config, IReadOnlyList records) : base(new StringU8("##records"u8), new PathColumn(config) { Label = new StringU8("Path"u8) }, @@ -145,7 +147,7 @@ internal sealed class ResourceWatcherTable : TableBase + private sealed class PathColumn : TextColumn, ICheckRecord { private readonly ResourceWatcherConfig _config; @@ -173,9 +175,12 @@ internal sealed class ResourceWatcherTable : TableBase item.Record.Path; + + public bool WouldBeVisible(in Record record) + => Filter.Text.Length is 0 || Filter.WouldBeVisible(record.Path.ToString()); } - private sealed class RecordTypeColumn : FlagColumn + private sealed class RecordTypeColumn : FlagColumn, ICheckRecord { private readonly ResourceWatcherConfig _config; @@ -201,6 +206,9 @@ internal sealed class ResourceWatcherTable : TableBase item.Record.RecordType; + + public bool WouldBeVisible(in Record record) + => Filter.FilterValue.HasFlag(record.RecordType); } private sealed class DateColumn : BasicColumn @@ -215,7 +223,7 @@ internal sealed class ResourceWatcherTable : TableBase Im.Text(item.Time); } - private sealed class Crc64Column : TextColumn + private sealed class Crc64Column : TextColumn, ICheckRecord { private readonly ResourceWatcherConfig _config; @@ -250,10 +258,13 @@ internal sealed class ResourceWatcherTable : TableBase item.Crc64; + + public bool WouldBeVisible(in Record record) + => Filter.Text.Length is 0 || Filter.WouldBeVisible(record.Crc64.ToString("X16")); } - private sealed class CollectionColumn : TextColumn + private sealed class CollectionColumn : TextColumn, ICheckRecord { private readonly ResourceWatcherConfig _config; @@ -276,9 +287,12 @@ internal sealed class ResourceWatcherTable : TableBase item.Collection; + + public bool WouldBeVisible(in Record record) + => Filter.WouldBeVisible(record.Collection?.Identity.Name ?? string.Empty); } - private sealed class ObjectColumn : TextColumn + private sealed class ObjectColumn : TextColumn, ICheckRecord { private readonly ResourceWatcherConfig _config; @@ -301,9 +315,12 @@ internal sealed class ResourceWatcherTable : TableBase item.AssociatedGameObject; + + public bool WouldBeVisible(in Record record) + => Filter.WouldBeVisible(record.AssociatedGameObject); } - private sealed class OriginalPathColumn : TextColumn + private sealed class OriginalPathColumn : TextColumn, ICheckRecord { private readonly ResourceWatcherConfig _config; @@ -331,9 +348,12 @@ internal sealed class ResourceWatcherTable : TableBase item.Record.OriginalPath; + + public bool WouldBeVisible(in Record record) + => Filter.Text.Length is 0 || Filter.WouldBeVisible(record.OriginalPath.ToString()); } - private sealed class ResourceCategoryColumn : FlagColumn + private sealed class ResourceCategoryColumn : FlagColumn, ICheckRecord { private readonly ResourceWatcherConfig _config; @@ -359,9 +379,12 @@ internal sealed class ResourceWatcherTable : TableBase item.Record.Category; + + public bool WouldBeVisible(in Record record) + => Filter.FilterValue.HasFlag(record.Category); } - private sealed class ResourceTypeColumn : FlagColumn + private sealed class ResourceTypeColumn : FlagColumn, ICheckRecord { private readonly ResourceWatcherConfig _config; @@ -387,9 +410,12 @@ internal sealed class ResourceWatcherTable : TableBase item.Record.ResourceType; + + public bool WouldBeVisible(in Record record) + => Filter.FilterValue.HasFlag(record.ResourceType); } - private sealed class LoadStateColumn : FlagColumn + private sealed class LoadStateColumn : FlagColumn, ICheckRecord { private readonly ResourceWatcherConfig _config; @@ -447,18 +473,25 @@ internal sealed class ResourceWatcherTable : TableBase item.Record.LoadState 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, - }; + => 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, + }; } - private sealed class HandleColumn : TextColumn + private sealed class HandleColumn : TextColumn, ICheckRecord { private readonly ResourceWatcherConfig _config; @@ -489,6 +522,9 @@ internal sealed class ResourceWatcherTable : TableBase StringU8.Empty; + + public unsafe bool WouldBeVisible(in Record record) + => Filter.Text.Length is 0 || Filter.WouldBeVisible($"0x{(nint)record.Handle:X}"); } @@ -531,7 +567,7 @@ internal sealed class ResourceWatcherTable : TableBase ToValue(item.Record.CustomLoad); + + public bool WouldBeVisible(in Record record) + => Filter.FilterValue.HasFlag(ToValue(record.CustomLoad)); } - private sealed class SynchronousLoadColumn : OptBoolColumn + private sealed class SynchronousLoadColumn : OptBoolColumn, ICheckRecord { private readonly ResourceWatcherConfig _config; @@ -573,12 +612,15 @@ internal sealed class ResourceWatcherTable : TableBase ToValue(item.Record.Synchronously); + + public bool WouldBeVisible(in Record record) + => Filter.FilterValue.HasFlag(ToValue(record.Synchronously)); } - private sealed class RefCountColumn : NumberColumn + private sealed class RefCountColumn : NumberColumn, ICheckRecord { private readonly ResourceWatcherConfig _config; - + public RefCountColumn(ResourceWatcherConfig config) { _config = config; @@ -601,9 +643,12 @@ internal sealed class ResourceWatcherTable : TableBase item.RefCount; + + public bool WouldBeVisible(in Record record) + => Filter.WouldBeVisible(record.RefCount); } - private sealed class OsThreadColumn : NumberColumn + private sealed class OsThreadColumn : NumberColumn, ICheckRecord { private readonly ResourceWatcherConfig _config; @@ -629,6 +674,9 @@ internal sealed class ResourceWatcherTable : TableBase item.Thread; + + public bool WouldBeVisible(in Record record) + => Filter.WouldBeVisible(record.OsThreadId); } public override IEnumerable GetItems() diff --git a/Penumbra/UI/Tabs/CollectionButtonFooter.cs b/Penumbra/UI/Tabs/CollectionButtonFooter.cs index e4e0bb55..bb184573 100644 --- a/Penumbra/UI/Tabs/CollectionButtonFooter.cs +++ b/Penumbra/UI/Tabs/CollectionButtonFooter.cs @@ -16,6 +16,9 @@ public sealed class CollectionButtonFooter : ButtonFooter Buttons.AddButton(new DeleteButton(collectionManager.Storage, collectionManager.Active, configuration), 0); } + public int Count + => Buttons.Count; + public sealed class AddButton(CollectionStorage collections) : BaseIconButton { public override AwesomeIcon Icon diff --git a/Penumbra/UI/Tabs/CollectionsTab.cs b/Penumbra/UI/Tabs/CollectionsTab.cs index cc1eef02..ffcc5d8a 100644 --- a/Penumbra/UI/Tabs/CollectionsTab.cs +++ b/Penumbra/UI/Tabs/CollectionsTab.cs @@ -9,12 +9,13 @@ namespace Penumbra.UI.Tabs; public sealed class CollectionsTab : TwoPanelLayout, ITab { private readonly TutorialService _tutorial; + private readonly UiConfig _config; public TabType Identifier => TabType.Collections; public CollectionsTab(TutorialService tutorial, CollectionButtonFooter leftFooter, CollectionSelector leftPanel, CollectionFilter filter, - CollectionModeHeader rightHeader, CollectionPanel rightPanel) + CollectionModeHeader rightHeader, CollectionPanel rightPanel, UiConfig config) { LeftHeader = new FilterHeader(filter, new StringU8("Filter..."u8)); LeftPanel = leftPanel; @@ -23,19 +24,29 @@ public sealed class CollectionsTab : TwoPanelLayout, ITab RightPanel = rightPanel; RightFooter = NopHeaderFooter.Instance; _tutorial = tutorial; + _config = config; } + protected override float MinimumWidth + => LeftFooter.MinimumWidth; + + protected override float MaximumWidth + => Im.Window.Width - 690 * Im.Style.GlobalScale; + public override ReadOnlySpan Label => "Collections"u8; - protected override void DrawLeftGroup() + protected override void DrawLeftGroup(in TwoPanelWidth width) { - base.DrawLeftGroup(); + base.DrawLeftGroup(width); _tutorial.OpenTutorial(BasicTutorialSteps.EditingCollections); } public void DrawContent() - => Draw(); + => Draw(_config.CollectionTabScale); + + protected override void SetWidth(float width, ScalingMode mode) + => _config.CollectionTabScale = new TwoPanelWidth(width, mode); public void PostTabButton() => _tutorial.OpenTutorial(BasicTutorialSteps.Collections); diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs index cb4cb578..1f903123 100644 --- a/Penumbra/UI/Tabs/Debug/DebugTab.cs +++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs @@ -1,4 +1,3 @@ -using Dalamud.Interface; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Group; @@ -1049,8 +1048,8 @@ public sealed class DebugTab : Window, ITab if (!table) return; - table.SetupColumn("Hash"u8, TableColumnFlags.WidthFixed, 18 * UiBuilder.MonoFont.GetCharAdvance('0')); - table.SetupColumn("Type"u8, TableColumnFlags.WidthFixed, 5 * UiBuilder.MonoFont.GetCharAdvance('0')); + table.SetupColumn("Hash"u8, TableColumnFlags.WidthFixed, 18 * Im.Font.Mono.GetCharacterAdvance('0')); + table.SetupColumn("Type"u8, TableColumnFlags.WidthFixed, 5 * Im.Font.Mono.GetCharacterAdvance('0')); table.HeaderRow(); foreach (var (hash, type) in _rsfService.CustomCache) @@ -1173,7 +1172,7 @@ public sealed class DebugTab : Window, ITab _config.Ephemeral.Save(); } - public static unsafe void DrawCopyableAddress(ReadOnlySpan label, nint address) + public static void DrawCopyableAddress(ReadOnlySpan label, nint address) { Penumbra.Dynamis.DrawPointer(address); Im.Line.SameInner(); diff --git a/Penumbra/UI/Tabs/Debug/ShapeInspector.cs b/Penumbra/UI/Tabs/Debug/ShapeInspector.cs index b373b681..cbcf5a2b 100644 --- a/Penumbra/UI/Tabs/Debug/ShapeInspector.cs +++ b/Penumbra/UI/Tabs/Debug/ShapeInspector.cs @@ -1,4 +1,3 @@ -using Dalamud.Interface; using ImSharp; using Penumbra.Collections.Cache; using Penumbra.GameData.Enums; @@ -176,9 +175,9 @@ public class ShapeInspector(ObjectManager objects, CollectionResolver resolver) table.SetupColumn("#"u8, TableColumnFlags.WidthFixed, 25 * Im.Style.GlobalScale); table.SetupColumn("Slot"u8, TableColumnFlags.WidthFixed, 150 * Im.Style.GlobalScale); - table.SetupColumn("Address"u8, TableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 14); - table.SetupColumn("Mask"u8, TableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 8); - table.SetupColumn("ID"u8, TableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 4); + table.SetupColumn("Address"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 14); + table.SetupColumn("Mask"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 8); + table.SetupColumn("ID"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 4); table.SetupColumn("Count"u8, TableColumnFlags.WidthFixed, 30 * Im.Style.GlobalScale); table.SetupColumn("Shapes"u8, TableColumnFlags.WidthStretch); table.HeaderRow(); @@ -232,9 +231,9 @@ public class ShapeInspector(ObjectManager objects, CollectionResolver resolver) table.SetupColumn("#"u8, TableColumnFlags.WidthFixed, 25 * Im.Style.GlobalScale); table.SetupColumn("Slot"u8, TableColumnFlags.WidthFixed, 150 * Im.Style.GlobalScale); - table.SetupColumn("Address"u8, TableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 14); - table.SetupColumn("Mask"u8, TableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 8); - table.SetupColumn("ID"u8, TableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 4); + table.SetupColumn("Address"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 14); + table.SetupColumn("Mask"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 8); + table.SetupColumn("ID"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 4); table.SetupColumn("Count"u8, TableColumnFlags.WidthFixed, 30 * Im.Style.GlobalScale); table.SetupColumn("Attributes"u8, TableColumnFlags.WidthStretch); table.HeaderRow(); diff --git a/Penumbra/UI/Tabs/ModsTab.cs b/Penumbra/UI/Tabs/ModsTab.cs deleted file mode 100644 index 3abcadcd..00000000 --- a/Penumbra/UI/Tabs/ModsTab.cs +++ /dev/null @@ -1,181 +0,0 @@ -using Dalamud.Plugin.Services; -using Penumbra.UI.Classes; -using FFXIVClientStructs.FFXIV.Client.Game; -using ImSharp; -using Luna; -using Penumbra.Api.Enums; -using Penumbra.Interop.Services; -using Penumbra.Mods; -using Penumbra.Mods.Manager; -using Penumbra.UI.ModsTab; -using ModFileSystemSelector = Penumbra.UI.ModsTab.ModFileSystemSelector; -using Penumbra.Collections.Manager; -using Penumbra.GameData.Interop; - -namespace Penumbra.UI.Tabs; - -public sealed class ModsTab( - ModManager modManager, - CollectionManager collectionManager, - ModFileSystemSelector selector, - ModPanel panel, - TutorialService tutorial, - RedrawService redrawService, - Configuration config, - CollectionSelectHeader collectionHeader, - ITargetManager targets, - ObjectManager objects) - : ITab -{ - private readonly ActiveCollections _activeCollections = collectionManager.Active; - - public bool IsEnabled - => modManager.Valid; - - public ReadOnlySpan Label - => "Mods"u8; - - public TabType Identifier - => TabType.Mods; - - public void PostTabButton() - => tutorial.OpenTutorial(BasicTutorialSteps.Mods); - - public Mod SelectMod - { - set => selector.SelectByValue(value); - } - - public void DrawContent() - { - try - { - selector.Draw(); - Im.Line.Same(); - Im.Cursor.X = MathF.Round(Im.Cursor.X); - using var group = Im.Group(); - collectionHeader.Draw(false); - - using var style = ImStyleDouble.ItemSpacing.Push(Vector2.Zero); - using (var child = Im.Child.Begin("##ModsTabMod"u8, - Im.ContentRegion.Available with { Y = config.HideRedrawBar ? 0 : -Im.Style.FrameHeight }, - true, WindowFlags.HorizontalScrollbar)) - { - style.Pop(); - if (child) - panel.Draw(); - - style.Push(ImStyleDouble.ItemSpacing, Vector2.Zero); - } - - style.Push(ImStyleSingle.FrameRounding, 0); - DrawRedrawLine(); - } - catch (Exception e) - { - Penumbra.Log.Error($"Exception thrown during ModPanel Render:\n{e}"); - Penumbra.Log.Error($"{modManager.Count} Mods\n" - + $"{_activeCollections.Current.Identity.AnonymizedName} Current Collection\n" - + $"{_activeCollections.Current.Settings.Count} Settings\n" - + $"{selector.SortMode.Name} Sort Mode\n" - + $"{selector.SelectedLeaf?.Name ?? "NULL"} Selected Leaf\n" - + $"{selector.Selected?.Name ?? "NULL"} Selected Mod\n" - + $"{string.Join(", ", _activeCollections.Current.Inheritance.DirectlyInheritsFrom.Select(c => c.Identity.AnonymizedName))} Inheritances\n"); - } - } - - private void DrawRedrawLine() - { - if (config.HideRedrawBar) - { - tutorial.SkipTutorial(BasicTutorialSteps.Redrawing); - return; - } - - var frameHeight = new Vector2(0, Im.Style.FrameHeight); - var frameColor = Im.Style[ImGuiColor.FrameBackground]; - using (Im.Group()) - { - using (AwesomeIcon.Font.Push()) - { - ImEx.TextFramed(LunaStyle.HelpMarker.Span, frameHeight, frameColor); - } - - Im.Line.Same(); - ImEx.TextFramed("Redraw: "u8, frameHeight, frameColor); - } - - var hovered = Im.Item.Hovered(); - tutorial.OpenTutorial(BasicTutorialSteps.Redrawing); - if (hovered) - { - using var _ = Im.Tooltip.Begin(); - Im.Text("The supported modifiers for '/penumbra redraw' are:"u8); - Im.BulletText("nothing, to redraw all characters\n"u8); - Im.BulletText("'self' or '': your own character\n"u8); - Im.BulletText("'target' or '': your target\n"u8); - Im.BulletText("'focus' or ': your focus target\n"u8); - Im.BulletText("'mouseover' or '': the actor you are currently hovering over\n"u8); - Im.BulletText("'furniture': most indoor furniture, does not currently work outdoors\n"u8); - Im.BulletText("any specific actor name to redraw all actors of that exactly matching name."u8); - } - - using var id = Im.Id.Push("Redraw"u8); - using var disabled = Im.Disabled(!objects[0].Valid); - Im.Line.Same(); - var buttonWidth = frameHeight with { X = Im.ContentRegion.Available.X / 5 }; - var tt = !objects[0].Valid - ? "Can only be used when you are logged in and your character is available."u8 - : StringU8.Empty; - DrawButton(buttonWidth, "All"u8, string.Empty, tt); - Im.Line.Same(); - DrawButton(buttonWidth, "Self"u8, "self", tt); - Im.Line.Same(); - - tt = targets.Target is null && targets.GPoseTarget is null - ? "Can only be used when you have a target."u8 - : StringU8.Empty; - DrawButton(buttonWidth, "Target"u8, "target", tt); - Im.Line.Same(); - - tt = targets.FocusTarget is null - ? "Can only be used when you have a focus target."u8 - : StringU8.Empty; - DrawButton(buttonWidth, "Focus"u8, "focus", tt); - Im.Line.Same(); - - tt = !IsIndoors() - ? "Can currently only be used for indoor furniture."u8 - : StringU8.Empty; - DrawButton(frameHeight with { X = Im.ContentRegion.Available.X - 1 }, "Furniture"u8, "furniture", tt); - return; - - void DrawButton(Vector2 size, ReadOnlySpan label, string lower, ReadOnlySpan additionalTooltip) - { - using (Im.Disabled(additionalTooltip.Length > 0)) - { - if (Im.Button(label, size)) - { - if (lower.Length > 0) - redrawService.RedrawObject(lower, RedrawType.Redraw); - else - redrawService.RedrawAll(RedrawType.Redraw); - } - } - - if (!Im.Item.Hovered(HoveredFlags.AllowWhenDisabled)) - return; - - using var _ = Im.Tooltip.Begin(); - if (lower.Length > 0) - Im.Text($"Execute '/penumbra redraw {lower}'."); - else - Im.Text("Execute '/penumbra redraw'."u8); - if (additionalTooltip.Length > 0) - Im.Text(additionalTooltip); - } - } - - private static unsafe bool IsIndoors() - => HousingManager.Instance()->IsInside(); -} diff --git a/Penumbra/UI/Tabs/SettingsTab.cs b/Penumbra/UI/Tabs/SettingsTab.cs index c1669cbf..ee0dd249 100644 --- a/Penumbra/UI/Tabs/SettingsTab.cs +++ b/Penumbra/UI/Tabs/SettingsTab.cs @@ -36,7 +36,6 @@ public sealed class SettingsTab : ITab private readonly ModManager _modManager; private readonly FileWatcher _fileWatcher; private readonly ModExportManager _modExportManager; - private readonly ModFileSystemSelector _selector; private readonly CharacterUtility _characterUtility; private readonly ResidentResourceManager _residentResources; private readonly HttpApi _httpApi; @@ -53,18 +52,20 @@ public sealed class SettingsTab : ITab private readonly AttributeHook _attributeHook; private readonly PcpService _pcpService; private readonly IntegrationSettingsRegistry _integrationSettings; + private readonly ModFileSystemDrawer _modFileSystemDrawer; private string _lastCloudSyncTestedPath = string.Empty; private bool _lastCloudSyncTestResult; public SettingsTab(IDalamudPluginInterface pluginInterface, Configuration config, FontReloader fontReloader, TutorialService tutorial, - Penumbra penumbra, FileDialogService fileDialog, ModManager modManager, ModFileSystemSelector selector, - CharacterUtility characterUtility, ResidentResourceManager residentResources, ModExportManager modExportManager, + Penumbra penumbra, FileDialogService fileDialog, ModManager modManager, CharacterUtility characterUtility, + ResidentResourceManager residentResources, ModExportManager modExportManager, FileWatcher fileWatcher, HttpApi httpApi, DalamudSubstitutionProvider dalamudSubstitutionProvider, FileCompactor compactor, DalamudConfigService dalamudConfig, IDataManager gameData, PredefinedTagManager predefinedTagConfig, CrashHandlerService crashService, MigrationSectionDrawer migrationDrawer, CollectionAutoSelector autoSelector, CleanupService cleanupService, - AttributeHook attributeHook, PcpService pcpService, IntegrationSettingsRegistry integrationSettings) + AttributeHook attributeHook, PcpService pcpService, IntegrationSettingsRegistry integrationSettings, + ModFileSystemDrawer modFileSystemDrawer) { _pluginInterface = pluginInterface; _config = config; @@ -73,7 +74,6 @@ public sealed class SettingsTab : ITab _penumbra = penumbra; _fileDialog = fileDialog; _modManager = modManager; - _selector = selector; _characterUtility = characterUtility; _residentResources = residentResources; _modExportManager = modExportManager; @@ -93,6 +93,7 @@ public sealed class SettingsTab : ITab _attributeHook = attributeHook; _pcpService = pcpService; _integrationSettings = integrationSettings; + _modFileSystemDrawer = modFileSystemDrawer; } public void PostTabButton() @@ -164,9 +165,8 @@ public sealed class SettingsTab : ITab private bool DrawPressEnterWarning(string newName, string old, float width, bool saved, bool selected) { using var color = ImGuiColor.Button.Push(Colors.PressEnterWarningBg); - var w = new Vector2(width, 0); var (text, valid) = CheckRootDirectoryPath(newName, old, selected); - + var w = new Vector2(Math.Max(width, Im.Font.CalculateButtonSize(text).X), 0); return (Im.Button(text, w) || saved) && valid; } @@ -279,6 +279,7 @@ public sealed class SettingsTab : ITab + "It should also be placed near the root of a logical drive - the shorter the total path to this folder, the better.\n"u8 + "Definitely do not place it in your Dalamud directory or any sub-directory thereof."u8; + Im.Line.SameInner(); LunaStyle.DrawAlignedHelpMarker(tt); _tutorial.OpenTutorial(BasicTutorialSteps.GeneralTooltips); Im.Line.SameInner(); @@ -389,7 +390,7 @@ public sealed class SettingsTab : ITab private void DrawHidingSettings() { Checkbox("Open Config Window at Game Start"u8, "Whether the Penumbra main window should be open or closed after launching the game."u8, - _config.OpenWindowAtStart, v => _config.OpenWindowAtStart = v); + _config.OpenWindowAtStart, v => _config.OpenWindowAtStart = v); Checkbox("Hide Config Window when UI is Hidden"u8, "Hide the Penumbra main window when you manually hide the in-game user interface."u8, _config.HideUiWhenUiHidden, @@ -424,9 +425,9 @@ public sealed class SettingsTab : ITab "Chat Commands usually print messages on failure but also on success to confirm your action. You can disable this here."u8, _config.PrintSuccessfulCommandsToChat, v => _config.PrintSuccessfulCommandsToChat = v); Checkbox("Hide Redraw Bar in Mod Panel"u8, "Hides the lower redraw buttons in the mod panel in your Mods tab."u8, - _config.HideRedrawBar, v => _config.HideRedrawBar = v); + _config.HideRedrawBar, v => _config.HideRedrawBar = v); Checkbox("Hide Changed Item Filters"u8, "Hides the category filter line in the Changed Items tab and the Changed Items mod panel."u8, - _config.HideChangedItemFilters, v => + _config.HideChangedItemFilters, v => { _config.HideChangedItemFilters = v; if (v) @@ -477,7 +478,8 @@ public sealed class SettingsTab : ITab Checkbox("Use Assigned Collections in Try-On Window"u8, "Use the individual collection for your character's name in your try-on, dye preview or glamour plate window, if it is set."u8, _config.UseCharacterCollectionInTryOn, v => _config.UseCharacterCollectionInTryOn = v); - Checkbox("Use No Mods in Inspect Windows"u8, "Use the empty collection for characters you are inspecting, regardless of the character.\n"u8 + Checkbox("Use No Mods in Inspect Windows"u8, + "Use the empty collection for characters you are inspecting, regardless of the character.\n"u8 + "Takes precedence before the next option."u8, _config.UseNoModsInInspect, v => _config.UseNoModsInInspect = v); Checkbox("Use Assigned Collections in Inspect Windows"u8, "Use the appropriate individual collection for the character you are currently inspecting, based on their name."u8, @@ -499,8 +501,8 @@ public sealed class SettingsTab : ITab { if (Im.Selectable(val.Name, val.Equals(sortMode)) && !val.Equals(sortMode)) { - _config.SortMode = val; - _selector.SetFilterDirty(); + _config.SortMode = val; + _modFileSystemDrawer.SortMode = val; _config.Save(); } @@ -522,14 +524,17 @@ public sealed class SettingsTab : ITab if (Im.Selectable(value.ToNameU8(), _config.ShowRename == value)) { _config.ShowRename = value; - _selector.SetRenameSearchPath(value); + // TODO + // _selector.SetRenameSearchPath(value); _config.Save(); } + Im.Tooltip.OnHover(value.Tooltip()); } } - LunaStyle.DrawAlignedHelpMarkerLabel("Rename Fields in Mod Context Menu"u8, "Select which of the two renaming input fields are visible when opening the right-click context menu of a mod in the mod selector."u8); + LunaStyle.DrawAlignedHelpMarkerLabel("Rename Fields in Mod Context Menu"u8, + "Select which of the two renaming input fields are visible when opening the right-click context menu of a mod in the mod selector."u8); } /// Draw all settings pertaining to the mod selector. @@ -538,10 +543,11 @@ public sealed class SettingsTab : ITab DrawFolderSortType(); DrawRenameSettings(); Checkbox("Open Folders by Default"u8, "Whether to start with all folders collapsed or expanded in the mod selector."u8, - _config.OpenFoldersByDefault, v => + _config.OpenFoldersByDefault, v => { _config.OpenFoldersByDefault = v; - _selector.SetFilterDirty(); + // TODO + // SetFilterDirty }); KeySelector.DoubleModifier("Mod Deletion Modifier"u8, @@ -587,7 +593,8 @@ public sealed class SettingsTab : ITab Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteModModifier} while clicking."); Im.Line.Same(); - if (ImEx.Button("Delete all PCP Collections"u8, default, "Deletes all collections whose name starts with 'PCP/' from the collection list."u8, !active)) + if (ImEx.Button("Delete all PCP Collections"u8, default, + "Deletes all collections whose name starts with 'PCP/' from the collection list."u8, !active)) _pcpService.CleanPcpCollections(); if (!active) Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteModModifier} while clicking."); @@ -757,7 +764,6 @@ public sealed class SettingsTab : ITab LunaStyle.DrawAlignedHelpMarkerLabel("Default PCP Organizational Folder"u8, "The folder any penumbra character packs are moved to on import.\nLeave blank to import into Root."u8); - } private void DrawPcpExtension() @@ -1061,7 +1067,7 @@ public sealed class SettingsTab : ITab Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteModModifier} while clicking to delete files."); if (ImEx.Button("Clear All Unused Settings"u8, default, - "Remove all mod settings in all of your collections that do not correspond to currently installed mods."u8, + "Remove all mod settings in all of your collections that do not correspond to currently installed mods."u8, !enabled || _cleanupService.IsRunning)) _cleanupService.CleanupAllUnusedSettings(); if (!enabled) @@ -1089,7 +1095,7 @@ public sealed class SettingsTab : ITab } #endregion - + /// Draw the support button group on the right-hand side of the window. private void DrawSupportButtons() { @@ -1128,7 +1134,7 @@ public sealed class SettingsTab : ITab if (!Im.Tree.Header("Tags"u8)) return; - var tagIdx = Luna.TagButtons.Draw("Predefined Tags: "u8, + var tagIdx = TagButtons.Draw("Predefined Tags: "u8, "Predefined tags that can be added or removed from mods with a single click."u8, _predefinedTagManager, out var editedTag);