From 65bd1d1b529e21093d81925304f2bddb5c9e0870 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 18 Apr 2022 16:14:13 +0200 Subject: [PATCH] Almost there... --- OtterGui | 2 +- Penumbra/Collections/CollectionManager.cs | 6 +- Penumbra/Configuration.Constants.cs | 16 + Penumbra/Configuration.cs | 30 +- Penumbra/Dalamud.cs | 2 +- .../Interop/Loader/ResourceLoader.Debug.cs | 2 + Penumbra/Interop/Structs/Material.cs | 23 + Penumbra/Interop/Structs/RenderModel.cs | 41 + Penumbra/MigrateConfiguration.cs | 331 ++++---- Penumbra/Mods/Manager/Mod2.Manager.Root.cs | 21 +- Penumbra/Mods/Mod2.Meta.Migration.cs | 8 +- .../{ModFileSystemA.cs => ModFileSystem.cs} | 6 +- Penumbra/Penumbra.cs | 31 +- Penumbra/Penumbra.csproj | 180 ++-- Penumbra/UI/Classes/Colors.cs | 3 + .../Classes/ModFileSystemSelector.Filters.cs | 8 +- Penumbra/UI/Classes/ModFileSystemSelector.cs | 31 +- Penumbra/UI/ConfigWindow.ChangedItemsTab.cs | 54 ++ Penumbra/UI/ConfigWindow.CollectionsTab.cs | 204 ++++- Penumbra/UI/ConfigWindow.DebugTab.cs | 369 +++++++- Penumbra/UI/ConfigWindow.EffectiveTab.cs | 190 ++++- Penumbra/UI/ConfigWindow.Misc.cs | 74 +- Penumbra/UI/ConfigWindow.ModsTab.Details.cs | 714 ++++++++++++++++ .../UI/ConfigWindow.ModsTab.DetailsEdit.cs | 381 +++++++++ ...onfigWindow.ModsTab.DetailsManipulation.cs | 773 +++++++++++++++++ Penumbra/UI/ConfigWindow.ModsTab.Import.cs | 177 ++++ ...Panel.cs => ConfigWindow.ModsTab.Panel.cs} | 0 Penumbra/UI/ConfigWindow.ModsTab.Selector.cs | 797 ++++++++++++++++++ Penumbra/UI/ConfigWindow.ModsTab.cs | 84 +- Penumbra/UI/ConfigWindow.ResourceTab.cs | 163 ++++ .../UI/ConfigWindow.SettingsTab.Advanced.cs | 169 ++++ .../ConfigWindow.SettingsTab.ModSelector.cs | 91 ++ Penumbra/UI/ConfigWindow.SettingsTab.cs | 281 ++---- Penumbra/UI/ConfigWindow.cs | 14 +- Penumbra/UI/Custom/ImGuiFramedGroup.cs | 146 ---- Penumbra/UI/Custom/ImGuiRenameableCombo.cs | 54 -- Penumbra/UI/Custom/ImGuiResizingTextInput.cs | 49 -- Penumbra/UI/Custom/ImGuiUtil.cs | 88 -- Penumbra/UI/Custom/Raii/Color.cs | 52 -- Penumbra/UI/Custom/Raii/EndStack.cs | 40 - Penumbra/UI/Custom/Raii/Font.cs | 39 - Penumbra/UI/Custom/Raii/Indent.cs | 72 -- Penumbra/UI/Custom/Raii/Style.cs | 92 -- Penumbra/UI/LaunchButton.cs | 2 + Penumbra/UI/MenuTabs/TabBrowser.cs | 39 - Penumbra/UI/MenuTabs/TabChangedItems.cs | 60 -- Penumbra/UI/MenuTabs/TabCollections.cs | 420 --------- Penumbra/UI/MenuTabs/TabDebug.Model.cs | 131 --- Penumbra/UI/MenuTabs/TabDebug.cs | 352 -------- Penumbra/UI/MenuTabs/TabEffective.cs | 229 ----- Penumbra/UI/MenuTabs/TabImport.cs | 190 ----- .../UI/MenuTabs/TabInstalled/ModListCache.cs | 326 ------- .../UI/MenuTabs/TabInstalled/TabInstalled.cs | 37 - .../TabInstalled/TabInstalledDetails.cs | 713 ---------------- .../TabInstalled/TabInstalledDetailsEdit.cs | 380 --------- .../TabInstalledDetailsManipulations.cs | 773 ----------------- .../TabInstalled/TabInstalledSelector.cs | 797 ------------------ Penumbra/UI/MenuTabs/TabResourceManager.cs | 189 ----- Penumbra/UI/MenuTabs/TabSettings.cs | 381 --------- 59 files changed, 4733 insertions(+), 6194 deletions(-) create mode 100644 Penumbra/Configuration.Constants.cs create mode 100644 Penumbra/Interop/Structs/Material.cs create mode 100644 Penumbra/Interop/Structs/RenderModel.cs rename Penumbra/Mods/{ModFileSystemA.cs => ModFileSystem.cs} (92%) create mode 100644 Penumbra/UI/ConfigWindow.ChangedItemsTab.cs create mode 100644 Penumbra/UI/ConfigWindow.ModsTab.Details.cs create mode 100644 Penumbra/UI/ConfigWindow.ModsTab.DetailsEdit.cs create mode 100644 Penumbra/UI/ConfigWindow.ModsTab.DetailsManipulation.cs create mode 100644 Penumbra/UI/ConfigWindow.ModsTab.Import.cs rename Penumbra/UI/{MenuTabs/TabInstalled/TabInstalledModPanel.cs => ConfigWindow.ModsTab.Panel.cs} (100%) create mode 100644 Penumbra/UI/ConfigWindow.ModsTab.Selector.cs create mode 100644 Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs create mode 100644 Penumbra/UI/ConfigWindow.SettingsTab.ModSelector.cs delete mode 100644 Penumbra/UI/Custom/ImGuiFramedGroup.cs delete mode 100644 Penumbra/UI/Custom/ImGuiRenameableCombo.cs delete mode 100644 Penumbra/UI/Custom/ImGuiResizingTextInput.cs delete mode 100644 Penumbra/UI/Custom/ImGuiUtil.cs delete mode 100644 Penumbra/UI/Custom/Raii/Color.cs delete mode 100644 Penumbra/UI/Custom/Raii/EndStack.cs delete mode 100644 Penumbra/UI/Custom/Raii/Font.cs delete mode 100644 Penumbra/UI/Custom/Raii/Indent.cs delete mode 100644 Penumbra/UI/Custom/Raii/Style.cs delete mode 100644 Penumbra/UI/MenuTabs/TabBrowser.cs delete mode 100644 Penumbra/UI/MenuTabs/TabChangedItems.cs delete mode 100644 Penumbra/UI/MenuTabs/TabCollections.cs delete mode 100644 Penumbra/UI/MenuTabs/TabDebug.Model.cs delete mode 100644 Penumbra/UI/MenuTabs/TabDebug.cs delete mode 100644 Penumbra/UI/MenuTabs/TabEffective.cs delete mode 100644 Penumbra/UI/MenuTabs/TabImport.cs delete mode 100644 Penumbra/UI/MenuTabs/TabInstalled/ModListCache.cs delete mode 100644 Penumbra/UI/MenuTabs/TabInstalled/TabInstalled.cs delete mode 100644 Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs delete mode 100644 Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsEdit.cs delete mode 100644 Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsManipulations.cs delete mode 100644 Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs delete mode 100644 Penumbra/UI/MenuTabs/TabResourceManager.cs delete mode 100644 Penumbra/UI/MenuTabs/TabSettings.cs diff --git a/OtterGui b/OtterGui index 05619f96..4cc10240 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 05619f966e6acfe8b0b6e947243c5d930c7525a4 +Subproject commit 4cc1024096905b0b20c1559c534b1dd3fe7b25ad diff --git a/Penumbra/Collections/CollectionManager.cs b/Penumbra/Collections/CollectionManager.cs index 3a7335cc..30fd44fb 100644 --- a/Penumbra/Collections/CollectionManager.cs +++ b/Penumbra/Collections/CollectionManager.cs @@ -56,6 +56,9 @@ public partial class ModCollection IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerable< ModCollection > GetEnumeratorWithEmpty() + => _collections; + public Manager( Mod2.Manager manager ) { _modManager = manager; @@ -227,7 +230,8 @@ public partial class ModCollection case ModOptionChangeType.OptionAdded: case ModOptionChangeType.OptionDeleted: case ModOptionChangeType.OptionChanged: - default: throw new ArgumentOutOfRangeException( nameof( type ), type, null ); + default: + throw new ArgumentOutOfRangeException( nameof( type ), type, null ); } } diff --git a/Penumbra/Configuration.Constants.cs b/Penumbra/Configuration.Constants.cs new file mode 100644 index 00000000..0cdcd9ec --- /dev/null +++ b/Penumbra/Configuration.Constants.cs @@ -0,0 +1,16 @@ +namespace Penumbra; + +public partial class Configuration +{ + // Contains some default values or boundaries for config values. + public static class Constants + { + public const int CurrentVersion = 3; + public const float MaxAbsoluteSize = 600; + public const int DefaultAbsoluteSize = 250; + public const float MinAbsoluteSize = 50; + public const int MaxScaledSize = 80; + public const int DefaultScaledSize = 20; + public const int MinScaledSize = 5; + } +} \ No newline at end of file diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index 63c4b303..05d9cc81 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Windows.Forms; using Dalamud.Configuration; using Dalamud.Logging; -using Penumbra.UI; +using OtterGui.Filesystem; using Penumbra.UI.Classes; namespace Penumbra; @@ -12,11 +11,12 @@ namespace Penumbra; [Serializable] public partial class Configuration : IPluginConfiguration { - private const int CurrentVersion = 2; - - public int Version { get; set; } = CurrentVersion; + public int Version { get; set; } = Constants.CurrentVersion; public bool EnableMods { get; set; } = true; + public string ModDirectory { get; set; } = string.Empty; + + #if DEBUG public bool DebugMode { get; set; } = true; #else @@ -27,39 +27,39 @@ public partial class Configuration : IPluginConfiguration public bool EnableResourceLogging { get; set; } = false; public string ResourceLoggingFilter { get; set; } = string.Empty; + + public SortMode SortMode { get; set; } = SortMode.FoldersFirst; public bool ScaleModSelector { get; set; } = false; + public float ModSelectorAbsoluteSize { get; set; } = Constants.DefaultAbsoluteSize; + public int ModSelectorScaledSize { get; set; } = Constants.DefaultScaledSize; + public bool ShowAdvanced { get; set; } - - public bool DisableFileSystemNotifications { get; set; } - public bool DisableSoundStreaming { get; set; } = true; public bool EnableHttpApi { get; set; } - public string ModDirectory { get; set; } = string.Empty; - - public bool SortFoldersFirst { get; set; } = false; - public bool HasReadCharacterCollectionDesc { get; set; } = false; - public Dictionary< ColorId, uint > Colors { get; set; } = Enum.GetValues< ColorId >().ToDictionary( c => c, c => c.Data().DefaultColor ); + // Load the current configuration. + // Includes adding new colors and migrating from old versions. public static Configuration Load() { var iConfiguration = Dalamud.PluginInterface.GetPluginConfig(); var configuration = iConfiguration as Configuration ?? new Configuration(); - if( iConfiguration is { Version: CurrentVersion } ) + if( iConfiguration is { Version: Constants.CurrentVersion } ) { configuration.AddColors( false ); return configuration; } - MigrateConfiguration.Migrate( configuration ); + Migration.Migrate( configuration ); configuration.AddColors( true ); return configuration; } + // Save the current configuration. public void Save() { try diff --git a/Penumbra/Dalamud.cs b/Penumbra/Dalamud.cs index b8f0c425..443c0ec0 100644 --- a/Penumbra/Dalamud.cs +++ b/Penumbra/Dalamud.cs @@ -30,6 +30,6 @@ public class Dalamud [PluginService][RequiredVersion("1.0")] public static TargetManager Targets { get; private set; } = null!; [PluginService][RequiredVersion("1.0")] public static ObjectTable Objects { get; private set; } = null!; [PluginService][RequiredVersion("1.0")] public static TitleScreenMenu TitleScreenMenu { get; private set; } = null!; - [PluginService][RequiredVersion("1.0")] public static GameGui GameGui { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public static GameGui GameGui { get; private set; } = null!; // @formatter:on } \ No newline at end of file diff --git a/Penumbra/Interop/Loader/ResourceLoader.Debug.cs b/Penumbra/Interop/Loader/ResourceLoader.Debug.cs index ada899fd..e0dad891 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.Debug.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Debug.cs @@ -157,6 +157,8 @@ public unsafe partial class ResourceLoader => IterateResourceMap( resourceMap, action ) ) ); } + // Update the list of currently replaced resources. + // Only used when the Replaced Resources Tab in the Debug tab is open. public void UpdateDebugInfo() { for( var i = 0; i < _debugList.Count; ++i ) diff --git a/Penumbra/Interop/Structs/Material.cs b/Penumbra/Interop/Structs/Material.cs new file mode 100644 index 00000000..dbd7c2b0 --- /dev/null +++ b/Penumbra/Interop/Structs/Material.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; +using FFXIVClientStructs.FFXIV.Client.Graphics.Render; + +namespace Penumbra.Interop.Structs; + +[StructLayout( LayoutKind.Explicit )] +public unsafe struct Material +{ + [FieldOffset( 0x10 )] + public ResourceHandle* ResourceHandle; + + [FieldOffset( 0x28 )] + public void* MaterialData; + + [FieldOffset( 0x48 )] + public Texture* Tex1; + + [FieldOffset( 0x60 )] + public Texture* Tex2; + + [FieldOffset( 0x78 )] + public Texture* Tex3; +} \ No newline at end of file diff --git a/Penumbra/Interop/Structs/RenderModel.cs b/Penumbra/Interop/Structs/RenderModel.cs new file mode 100644 index 00000000..b9e04908 --- /dev/null +++ b/Penumbra/Interop/Structs/RenderModel.cs @@ -0,0 +1,41 @@ +using System.Runtime.InteropServices; +using FFXIVClientStructs.FFXIV.Client.Graphics.Render; + +namespace Penumbra.Interop.Structs; + +[StructLayout( LayoutKind.Explicit )] +public unsafe struct RenderModel +{ + [FieldOffset( 0x18 )] + public RenderModel* PreviousModel; + + [FieldOffset( 0x20 )] + public RenderModel* NextModel; + + [FieldOffset( 0x30 )] + public ResourceHandle* ResourceHandle; + + [FieldOffset( 0x40 )] + public Skeleton* Skeleton; + + [FieldOffset( 0x58 )] + public void** BoneList; + + [FieldOffset( 0x60 )] + public int BoneListCount; + + [FieldOffset( 0x68 )] + private void* UnkDXBuffer1; + + [FieldOffset( 0x70 )] + private void* UnkDXBuffer2; + + [FieldOffset( 0x78 )] + private void* UnkDXBuffer3; + + [FieldOffset( 0x90 )] + public void** Materials; + + [FieldOffset( 0x98 )] + public int MaterialCount; +} \ No newline at end of file diff --git a/Penumbra/MigrateConfiguration.cs b/Penumbra/MigrateConfiguration.cs index b4c05850..1344bf27 100644 --- a/Penumbra/MigrateConfiguration.cs +++ b/Penumbra/MigrateConfiguration.cs @@ -5,188 +5,217 @@ using System.Linq; using Dalamud.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using OtterGui.Filesystem; using Penumbra.Collections; using Penumbra.Mods; namespace Penumbra; -public class MigrateConfiguration +public partial class Configuration { - private Configuration _config = null!; - private JObject _data = null!; - - public string CurrentCollection = ModCollection.DefaultCollection; - public string DefaultCollection = ModCollection.DefaultCollection; - public string ForcedCollection = string.Empty; - public Dictionary< string, string > CharacterCollections = new(); - public Dictionary< string, string > ModSortOrder = new(); - public bool InvertModListOrder; - - - public static void Migrate( Configuration config ) + // Contains everything to migrate from older versions of the config to the current, + // including deprecated fields. + private class Migration { - var m = new MigrateConfiguration - { - _config = config, - _data = JObject.Parse( File.ReadAllText( Dalamud.PluginInterface.ConfigFile.FullName ) ), - }; + private Configuration _config = null!; + private JObject _data = null!; - m.CreateBackup(); - m.Version0To1(); - m.Version1To2(); - } + public string CurrentCollection = ModCollection.DefaultCollection; + public string DefaultCollection = ModCollection.DefaultCollection; + public string ForcedCollection = string.Empty; + public Dictionary< string, string > CharacterCollections = new(); + public Dictionary< string, string > ModSortOrder = new(); + public bool InvertModListOrder; + public bool SortFoldersFirst; - private void Version1To2() - { - if( _config.Version != 1 ) + public static void Migrate( Configuration config ) { - return; + var m = new Migration + { + _config = config, + _data = JObject.Parse( File.ReadAllText( Dalamud.PluginInterface.ConfigFile.FullName ) ), + }; + + CreateBackup(); + m.Version0To1(); + m.Version1To2(); + m.Version2To3(); } - ResettleSortOrder(); - ResettleCollectionSettings(); - ResettleForcedCollection(); - _config.Version = 2; - } - - private void ResettleForcedCollection() - { - ForcedCollection = _data[ nameof( ForcedCollection ) ]?.ToObject< string >() ?? ForcedCollection; - if( ForcedCollection.Length <= 0 ) + // SortFoldersFirst was changed from a bool to the enum SortMode. + private void Version2To3() { - return; + if( _config.Version != 2 ) + { + return; + } + + SortFoldersFirst = _data[ nameof( SortFoldersFirst ) ]?.ToObject< bool >() ?? false; + _config.SortMode = SortFoldersFirst ? SortMode.FoldersFirst : SortMode.Lexicographical; + _config.Version = 3; } - foreach( var collection in Directory.EnumerateFiles( ModCollection.CollectionDirectory, "*.json" ) ) + // The forced collection was removed due to general inheritance. + // Sort Order was moved to a separate file and may contain empty folders. + // Active collections in general were moved to their own file. + private void Version1To2() { + if( _config.Version != 1 ) + { + return; + } + + ResettleSortOrder(); + ResettleCollectionSettings(); + ResettleForcedCollection(); + _config.Version = 2; + } + + private void ResettleForcedCollection() + { + ForcedCollection = _data[ nameof( ForcedCollection ) ]?.ToObject< string >() ?? ForcedCollection; + if( ForcedCollection.Length <= 0 ) + { + return; + } + + // Add the previous forced collection to all current collections except itself as an inheritance. + foreach( var collection in Directory.EnumerateFiles( ModCollection.CollectionDirectory, "*.json" ) ) + { + try + { + var jObject = JObject.Parse( File.ReadAllText( collection ) ); + if( jObject[ nameof( ModCollection.Name ) ]?.ToObject< string >() != ForcedCollection ) + { + jObject[ nameof( ModCollection.Inheritance ) ] = JToken.FromObject( new List< string >() { ForcedCollection } ); + File.WriteAllText( collection, jObject.ToString() ); + } + } + catch( Exception e ) + { + PluginLog.Error( + $"Could not transfer forced collection {ForcedCollection} to inheritance of collection {collection}:\n{e}" ); + } + } + } + + // Move the current sort order to its own file. + private void ResettleSortOrder() + { + ModSortOrder = _data[ nameof( ModSortOrder ) ]?.ToObject< Dictionary< string, string > >() ?? ModSortOrder; + var file = Mod2.Manager.ModFileSystemFile; + using var stream = File.Open( file, File.Exists( file ) ? FileMode.Truncate : FileMode.CreateNew ); + using var writer = new StreamWriter( stream ); + using var j = new JsonTextWriter( writer ); + j.Formatting = Formatting.Indented; + j.WriteStartObject(); + j.WritePropertyName( "Data" ); + j.WriteStartObject(); + foreach( var (mod, path) in ModSortOrder ) + { + j.WritePropertyName( mod, true ); + j.WriteValue( path ); + } + + j.WriteEndObject(); + j.WritePropertyName( "EmptyFolders" ); + j.WriteStartArray(); + j.WriteEndArray(); + j.WriteEndObject(); + } + + // Move the active collections to their own file. + private void ResettleCollectionSettings() + { + CurrentCollection = _data[ nameof( CurrentCollection ) ]?.ToObject< string >() ?? CurrentCollection; + DefaultCollection = _data[ nameof( DefaultCollection ) ]?.ToObject< string >() ?? DefaultCollection; + CharacterCollections = _data[ nameof( CharacterCollections ) ]?.ToObject< Dictionary< string, string > >() ?? CharacterCollections; + ModCollection.Manager.SaveActiveCollections( DefaultCollection, CurrentCollection, + CharacterCollections.Select( kvp => ( kvp.Key, kvp.Value ) ) ); + } + + // Collections were introduced and the previous CurrentCollection got put into ModDirectory. + private void Version0To1() + { + if( _config.Version != 0 ) + { + return; + } + + _config.ModDirectory = _data[ nameof( CurrentCollection ) ]?.ToObject< string >() ?? string.Empty; + _config.Version = 1; + ResettleCollectionJson(); + } + + // Move the previous mod configurations to a new default collection file. + private void ResettleCollectionJson() + { + var collectionJson = new FileInfo( Path.Combine( _config.ModDirectory, "collection.json" ) ); + if( !collectionJson.Exists ) + { + return; + } + + var defaultCollection = ModCollection.CreateNewEmpty( ModCollection.DefaultCollection ); + var defaultCollectionFile = defaultCollection.FileName; + if( defaultCollectionFile.Exists ) + { + return; + } + try { - var jObject = JObject.Parse( File.ReadAllText( collection ) ); - if( jObject[ nameof( ModCollection.Name ) ]?.ToObject< string >() != ForcedCollection ) + var text = File.ReadAllText( collectionJson.FullName ); + var data = JArray.Parse( text ); + + var maxPriority = 0; + var dict = new Dictionary< string, ModSettings2.SavedSettings >(); + foreach( var setting in data.Cast< JObject >() ) { - jObject[ nameof( ModCollection.Inheritance ) ] = JToken.FromObject( new List< string >() { ForcedCollection } ); - File.WriteAllText( collection, jObject.ToString() ); + var modName = ( string )setting[ "FolderName" ]!; + var enabled = ( bool )setting[ "Enabled" ]!; + var priority = ( int )setting[ "Priority" ]!; + var settings = setting[ "Settings" ]!.ToObject< Dictionary< string, uint > >() + ?? setting[ "Conf" ]!.ToObject< Dictionary< string, uint > >(); + + dict[ modName ] = new ModSettings2.SavedSettings() + { + Enabled = enabled, + Priority = priority, + Settings = settings!, + }; + maxPriority = Math.Max( maxPriority, priority ); } + + InvertModListOrder = _data[ nameof( InvertModListOrder ) ]?.ToObject< bool >() ?? InvertModListOrder; + if( !InvertModListOrder ) + { + dict = dict.ToDictionary( kvp => kvp.Key, kvp => kvp.Value with { Priority = maxPriority - kvp.Value.Priority } ); + } + + defaultCollection = ModCollection.MigrateFromV0( ModCollection.DefaultCollection, dict ); + defaultCollection.Save(); } catch( Exception e ) { - PluginLog.Error( - $"Could not transfer forced collection {ForcedCollection} to inheritance of collection {collection}:\n{e}" ); + PluginLog.Error( $"Could not migrate the old collection file to new collection files:\n{e}" ); + throw; } } - } - private void ResettleSortOrder() - { - ModSortOrder = _data[ nameof( ModSortOrder ) ]?.ToObject< Dictionary< string, string > >() ?? ModSortOrder; - var file = Mod2.Manager.ModFileSystemFile; - using var stream = File.Open( file, File.Exists( file ) ? FileMode.Truncate : FileMode.CreateNew ); - using var writer = new StreamWriter( stream ); - using var j = new JsonTextWriter( writer ); - j.Formatting = Formatting.Indented; - j.WriteStartObject(); - j.WritePropertyName( "Data" ); - j.WriteStartObject(); - foreach( var (mod, path) in ModSortOrder ) + // Create a backup of the configuration file specifically. + private static void CreateBackup() { - j.WritePropertyName( mod, true ); - j.WriteValue( path ); - } - - j.WriteEndObject(); - j.WritePropertyName( "EmptyFolders" ); - j.WriteStartArray(); - j.WriteEndArray(); - j.WriteEndObject(); - } - - private void ResettleCollectionSettings() - { - CurrentCollection = _data[ nameof( CurrentCollection ) ]?.ToObject< string >() ?? CurrentCollection; - DefaultCollection = _data[ nameof( DefaultCollection ) ]?.ToObject< string >() ?? DefaultCollection; - CharacterCollections = _data[ nameof( CharacterCollections ) ]?.ToObject< Dictionary< string, string > >() ?? CharacterCollections; - ModCollection.Manager.SaveActiveCollections( DefaultCollection, CurrentCollection, - CharacterCollections.Select( kvp => ( kvp.Key, kvp.Value ) ) ); - } - - private void Version0To1() - { - if( _config.Version != 0 ) - { - return; - } - - _config.ModDirectory = _data[ nameof( CurrentCollection ) ]?.ToObject< string >() ?? string.Empty; - _config.Version = 1; - ResettleCollectionJson(); - } - - private void ResettleCollectionJson() - { - var collectionJson = new FileInfo( Path.Combine( _config.ModDirectory, "collection.json" ) ); - if( !collectionJson.Exists ) - { - return; - } - - var defaultCollection = ModCollection.CreateNewEmpty( ModCollection.DefaultCollection ); - var defaultCollectionFile = defaultCollection.FileName; - if( defaultCollectionFile.Exists ) - { - return; - } - - try - { - var text = File.ReadAllText( collectionJson.FullName ); - var data = JArray.Parse( text ); - - var maxPriority = 0; - var dict = new Dictionary< string, ModSettings2.SavedSettings >(); - foreach( var setting in data.Cast< JObject >() ) + var name = Dalamud.PluginInterface.ConfigFile.FullName; + var bakName = name + ".bak"; + try { - var modName = ( string )setting[ "FolderName" ]!; - var enabled = ( bool )setting[ "Enabled" ]!; - var priority = ( int )setting[ "Priority" ]!; - var settings = setting[ "Settings" ]!.ToObject< Dictionary< string, uint > >() - ?? setting[ "Conf" ]!.ToObject< Dictionary< string, uint > >(); - - dict[ modName ] = new ModSettings2.SavedSettings() - { - Enabled = enabled, - Priority = priority, - Settings = settings!, - }; - maxPriority = Math.Max( maxPriority, priority ); + File.Copy( name, bakName, true ); } - - InvertModListOrder = _data[ nameof( InvertModListOrder ) ]?.ToObject< bool >() ?? InvertModListOrder; - if( !InvertModListOrder ) + catch( Exception e ) { - dict = dict.ToDictionary( kvp => kvp.Key, kvp => kvp.Value with { Priority = maxPriority - kvp.Value.Priority } ); + PluginLog.Error( $"Could not create backup copy of config at {bakName}:\n{e}" ); } - - defaultCollection = ModCollection.MigrateFromV0( ModCollection.DefaultCollection, dict ); - defaultCollection.Save(); - } - catch( Exception e ) - { - PluginLog.Error( $"Could not migrate the old collection file to new collection files:\n{e}" ); - throw; - } - } - - private void CreateBackup() - { - var name = Dalamud.PluginInterface.ConfigFile.FullName; - var bakName = name + ".bak"; - try - { - File.Copy( name, bakName, true ); - } - catch( Exception e ) - { - PluginLog.Error( $"Could not create backup copy of config at {bakName}:\n{e}" ); } } } \ No newline at end of file diff --git a/Penumbra/Mods/Manager/Mod2.Manager.Root.cs b/Penumbra/Mods/Manager/Mod2.Manager.Root.cs index 85216b66..59f61e6c 100644 --- a/Penumbra/Mods/Manager/Mod2.Manager.Root.cs +++ b/Penumbra/Mods/Manager/Mod2.Manager.Root.cs @@ -65,24 +65,19 @@ public sealed partial class Mod2 _mods.Clear(); BasePath.Refresh(); - // TODO - //StructuredMods.SubFolders.Clear(); - //StructuredMods.Mods.Clear(); if( Valid && BasePath.Exists ) { foreach( var modFolder in BasePath.EnumerateDirectories() ) { - //var mod = LoadMod( StructuredMods, modFolder ); - //if( mod == null ) - //{ - // continue; - //} - // - //mod.Index = _mods.Count; - //_mods.Add( mod ); + var mod = LoadMod( modFolder ); + if( mod == null ) + { + continue; + } + + mod.Index = _mods.Count; + _mods.Add( mod ); } - - //SetModStructure(); } ModDiscoveryFinished?.Invoke(); diff --git a/Penumbra/Mods/Mod2.Meta.Migration.cs b/Penumbra/Mods/Mod2.Meta.Migration.cs index a675c660..5b13121f 100644 --- a/Penumbra/Mods/Mod2.Meta.Migration.cs +++ b/Penumbra/Mods/Mod2.Meta.Migration.cs @@ -36,12 +36,10 @@ public sealed partial class Mod2 foreach( var unusedFile in mod.FindUnusedFiles() ) { - if( unusedFile.ToGamePath( mod.BasePath, out var gamePath ) ) + if( unusedFile.ToGamePath( mod.BasePath, out var gamePath ) + && !mod._default.FileData.TryAdd( gamePath, unusedFile ) ) { - if( !mod._default.FileData.TryAdd( gamePath, unusedFile ) ) - { - PluginLog.Error( $"Could not add {gamePath} because it already points to {mod._default.FileData[ gamePath ]}." ); - } + PluginLog.Error( $"Could not add {gamePath} because it already points to {mod._default.FileData[ gamePath ]}." ); } } diff --git a/Penumbra/Mods/ModFileSystemA.cs b/Penumbra/Mods/ModFileSystem.cs similarity index 92% rename from Penumbra/Mods/ModFileSystemA.cs rename to Penumbra/Mods/ModFileSystem.cs index c2b62921..3a65011b 100644 --- a/Penumbra/Mods/ModFileSystemA.cs +++ b/Penumbra/Mods/ModFileSystem.cs @@ -4,7 +4,7 @@ using OtterGui.Filesystem; namespace Penumbra.Mods; -public sealed class ModFileSystemA : FileSystem< Mod2 >, IDisposable +public sealed class ModFileSystem : FileSystem< Mod2 >, IDisposable { // Save the current sort order. // Does not save or copy the backup in the current mod directory, @@ -13,9 +13,9 @@ public sealed class ModFileSystemA : FileSystem< Mod2 >, IDisposable => SaveToFile( new FileInfo( Mod2.Manager.ModFileSystemFile ), SaveMod, true ); // Create a new ModFileSystem from the currently loaded mods and the current sort order file. - public static ModFileSystemA Load() + public static ModFileSystem Load() { - var ret = new ModFileSystemA(); + var ret = new ModFileSystem(); ret.Reload(); ret.Changed += ret.OnChange; diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 9bdb5bd4..f4a931fa 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using Dalamud.Game.Command; using Dalamud.Interface.Windowing; using Dalamud.Logging; @@ -27,11 +28,13 @@ public class Penumbra : IDalamudPlugin public string Name => "Penumbra"; - public string PluginDebugTitleStr - => "Penumbra - Debug Build"; - private const string CommandName = "/penumbra"; + public static string Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? string.Empty; + + public static string CommitHash = + Assembly.GetExecutingAssembly().GetCustomAttribute< AssemblyInformationalVersionAttribute >()?.InformationalVersion ?? "Unknown"; + public static Configuration Config { get; private set; } = null!; public static ResidentResourceManager ResidentResources { get; private set; } = null!; @@ -47,14 +50,14 @@ public class Penumbra : IDalamudPlugin public readonly PathResolver PathResolver; public readonly MusicManager MusicManager; public readonly ObjectReloader ObjectReloader; - public readonly ModFileSystemA ModFileSystem; + public readonly ModFileSystem ModFileSystem; public readonly PenumbraApi Api; public readonly PenumbraIpc Ipc; private readonly ConfigWindow _configWindow; private readonly LaunchButton _launchButton; private readonly WindowSystem _windowSystem; - private WebServer? _webServer; + internal WebServer? WebServer; public Penumbra( DalamudPluginInterface pluginInterface ) { @@ -78,7 +81,7 @@ public class Penumbra : IDalamudPlugin ModManager = new Mod2.Manager( Config.ModDirectory ); ModManager.DiscoverMods(); CollectionManager = new ModCollection.Manager( ModManager ); - ModFileSystem = ModFileSystemA.Load(); + ModFileSystem = ModFileSystem.Load(); ObjectReloader = new ObjectReloader(); PathResolver = new PathResolver( ResourceLoader ); @@ -113,6 +116,7 @@ public class Penumbra : IDalamudPlugin if( Config.DebugMode ) { ResourceLoader.EnableDebug(); + _configWindow.IsOpen = true; } if( Config.EnableFullResourceLogging ) @@ -126,11 +130,6 @@ public class Penumbra : IDalamudPlugin } ResidentResources.Reload(); - - foreach( var folder in ModManager.BasePath.EnumerateDirectories() ) - { - var m = Mod2.LoadMod( folder ); - } } private void SetupInterface( out ConfigWindow cfg, out LaunchButton btn, out WindowSystem system ) @@ -210,7 +209,7 @@ public class Penumbra : IDalamudPlugin ShutdownWebServer(); - _webServer = new WebServer( o => o + WebServer = new WebServer( o => o .WithUrlPrefix( prefix ) .WithMode( HttpListenerMode.EmbedIO ) ) .WithCors( prefix ) @@ -218,15 +217,15 @@ public class Penumbra : IDalamudPlugin .WithController( () => new ModsController( this ) ) .WithController( () => new RedrawController( this ) ) ); - _webServer.StateChanged += ( _, e ) => PluginLog.Information( $"WebServer New State - {e.NewState}" ); + WebServer.StateChanged += ( _, e ) => PluginLog.Information( $"WebServer New State - {e.NewState}" ); - _webServer.RunAsync(); + WebServer.RunAsync(); } public void ShutdownWebServer() { - _webServer?.Dispose(); - _webServer = null; + WebServer?.Dispose(); + WebServer = null; } public void Dispose() diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index 0ce77a6e..2a14edb7 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -1,89 +1,93 @@ - - - net5.0-windows - preview - x64 - Penumbra - absolute gangstas - Penumbra - Copyright © 2020 - 1.0.0.0 - 1.0.0.0 - bin\$(Configuration)\ - true - enable - true - true - - - - full - DEBUG;TRACE;USE_EQP;USE_EQDP;USE_GMP;USE_EST;USE_CMP;USE_IMC - - - - pdbonly - $(DefineConstants)TRACE;USE_EQP;USE_EQDP;USE_GMP;USE_EST;USE_CMP;USE_IMC - - - - $(MSBuildWarningsAsMessages);MSB3277 - - - - - - - - - - - PreserveNewest - - - - - - $(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll - False - - - $(AppData)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll - False - - - $(AppData)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll - False - - - $(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll - False - - - $(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll - False - - - $(AppData)\XIVLauncher\addon\Hooks\dev\FFXIVClientStructs.dll - False - - - - - - - - - - - - - - - - - - Always - - + + + net5.0-windows + preview + x64 + Penumbra + absolute gangstas + Penumbra + Copyright © 2020 + 1.0.0.0 + 1.0.0.0 + bin\$(Configuration)\ + true + enable + true + true + + + + full + DEBUG;TRACE;USE_EQP;USE_EQDP;USE_GMP;USE_EST;USE_CMP;USE_IMC + + + + pdbonly + $(DefineConstants)TRACE;USE_EQP;USE_EQDP;USE_GMP;USE_EST;USE_CMP;USE_IMC + + + + $(MSBuildWarningsAsMessages);MSB3277 + + + + + PreserveNewest + + + + + + $(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll + False + + + $(AppData)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll + False + + + $(AppData)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll + False + + + $(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll + False + + + $(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll + False + + + $(AppData)\XIVLauncher\addon\Hooks\dev\FFXIVClientStructs.dll + False + + + + + + + + + + + + + + + + + + Always + + + + + + + + + + $(GitCommitHash) + + \ No newline at end of file diff --git a/Penumbra/UI/Classes/Colors.cs b/Penumbra/UI/Classes/Colors.cs index f51c99fb..7ed19b08 100644 --- a/Penumbra/UI/Classes/Colors.cs +++ b/Penumbra/UI/Classes/Colors.cs @@ -15,11 +15,13 @@ public enum ColorId FolderExpanded, FolderCollapsed, FolderLine, + ItemId, } public static class Colors { public const uint PressEnterWarningBg = 0xFF202080; + public const uint RegexWarningBorder = 0xFF0000B0; public static (uint DefaultColor, string Name, string Description) Data( this ColorId color ) => color switch @@ -36,6 +38,7 @@ public static class Colors ColorId.FolderExpanded => ( 0xFFFFF0C0, "Expanded Mod Folder", "A mod folder that is currently expanded." ), ColorId.FolderCollapsed => ( 0xFFFFF0C0, "Collapsed Mod Folder", "A mod folder that is currently collapsed." ), ColorId.FolderLine => ( 0xFFFFF0C0, "Expanded Mod Folder Line", "The line signifying which descendants belong to an expanded mod folder." ), + ColorId.ItemId => ( 0xFF808080, "Item Id", "The numeric model id of the given item to the right of changed items." ), _ => throw new ArgumentOutOfRangeException( nameof( color ), color, null ), // @formatter:on }; diff --git a/Penumbra/UI/Classes/ModFileSystemSelector.Filters.cs b/Penumbra/UI/Classes/ModFileSystemSelector.Filters.cs index 6caf5bd8..66473453 100644 --- a/Penumbra/UI/Classes/ModFileSystemSelector.Filters.cs +++ b/Penumbra/UI/Classes/ModFileSystemSelector.Filters.cs @@ -77,18 +77,18 @@ public partial class ModFileSystemSelector // or they contain the path search string. protected override bool ApplyFiltersAndState( FileSystem< Mod2 >.IPath path, out ModState state ) { - if( path is ModFileSystemA.Folder f ) + if( path is ModFileSystem.Folder f ) { state = default; return ModFilterExtensions.UnfilteredStateMods != _stateFilter || FilterValue.Length > 0 && !f.FullName().Contains( FilterValue, IgnoreCase ); } - return ApplyFiltersAndState( ( ModFileSystemA.Leaf )path, out state ); + return ApplyFiltersAndState( ( ModFileSystem.Leaf )path, out state ); } // Apply the string filters. - private bool ApplyStringFilters( ModFileSystemA.Leaf leaf, Mod2 mod ) + private bool ApplyStringFilters( ModFileSystem.Leaf leaf, Mod2 mod ) { return _filterType switch { @@ -226,7 +226,7 @@ public partial class ModFileSystemSelector } // Combined wrapper for handling all filters and setting state. - private bool ApplyFiltersAndState( ModFileSystemA.Leaf leaf, out ModState state ) + private bool ApplyFiltersAndState( ModFileSystem.Leaf leaf, out ModState state ) { state = new ModState { Color = ColorId.EnabledMod.Value() }; var mod = leaf.Value; diff --git a/Penumbra/UI/Classes/ModFileSystemSelector.cs b/Penumbra/UI/Classes/ModFileSystemSelector.cs index 57ec835f..3ee304cb 100644 --- a/Penumbra/UI/Classes/ModFileSystemSelector.cs +++ b/Penumbra/UI/Classes/ModFileSystemSelector.cs @@ -17,7 +17,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo public ModSettings2 SelectedSettings { get; private set; } = ModSettings2.Empty; public ModCollection SelectedSettingCollection { get; private set; } = ModCollection.Empty; - public ModFileSystemSelector( ModFileSystemA fileSystem, IReadOnlySet< Mod2 > newMods ) + public ModFileSystemSelector( ModFileSystem fileSystem, IReadOnlySet< Mod2 > newMods ) : base( fileSystem ) { _newMods = newMods; @@ -29,6 +29,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo AddButton( DeleteModButton, 1000 ); SetFilterTooltip(); + SelectionChanged += OnSelectionChange; Penumbra.CollectionManager.CollectionChanged += OnCollectionChange; Penumbra.CollectionManager.Current.ModSettingChanged += OnSettingChange; Penumbra.CollectionManager.Current.InheritanceChanged += OnInheritanceChange; @@ -49,7 +50,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo // Customization points. public override SortMode SortMode - => Penumbra.Config.SortFoldersFirst ? SortMode.FoldersFirst : SortMode.Lexicographical; + => Penumbra.Config.SortMode; protected override uint ExpandedFolderColor => ColorId.FolderExpanded.Value(); @@ -69,7 +70,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo // Add custom context menu items. - private static void EnableDescendants( ModFileSystemA.Folder folder ) + private static void EnableDescendants( ModFileSystem.Folder folder ) { if( ImGui.MenuItem( "Enable Descendants" ) ) { @@ -77,7 +78,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo } } - private static void DisableDescendants( ModFileSystemA.Folder folder ) + private static void DisableDescendants( ModFileSystem.Folder folder ) { if( ImGui.MenuItem( "Disable Descendants" ) ) { @@ -85,7 +86,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo } } - private static void InheritDescendants( ModFileSystemA.Folder folder ) + private static void InheritDescendants( ModFileSystem.Folder folder ) { if( ImGui.MenuItem( "Inherit Descendants" ) ) { @@ -93,7 +94,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo } } - private static void OwnDescendants( ModFileSystemA.Folder folder ) + private static void OwnDescendants( ModFileSystem.Folder folder ) { if( ImGui.MenuItem( "Stop Inheriting Descendants" ) ) { @@ -118,9 +119,9 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo // Helpers. - private static void SetDescendants( ModFileSystemA.Folder folder, bool enabled, bool inherit = false ) + private static void SetDescendants( ModFileSystem.Folder folder, bool enabled, bool inherit = false ) { - var mods = folder.GetAllDescendants( SortMode.Lexicographical ).OfType< ModFileSystemA.Leaf >().Select( l => l.Value ); + var mods = folder.GetAllDescendants( SortMode.Lexicographical ).OfType< ModFileSystem.Leaf >().Select( l => l.Value ); if( inherit ) { Penumbra.CollectionManager.Current.SetMultipleModInheritances( mods, enabled ); @@ -138,14 +139,14 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo SetFilterDirty(); if( modIdx == Selected?.Index ) { - OnSelectionChange( SelectedLeaf, SelectedLeaf, default ); + OnSelectionChange( Selected, Selected, default ); } } private void OnInheritanceChange( bool _ ) { SetFilterDirty(); - OnSelectionChange( SelectedLeaf, SelectedLeaf, default ); + OnSelectionChange( Selected, Selected, default ); } private void OnCollectionChange( ModCollection.Type type, ModCollection? oldCollection, ModCollection? newCollection, string? _ ) @@ -168,10 +169,10 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo } SetFilterDirty(); - OnSelectionChange( SelectedLeaf, SelectedLeaf, default ); + OnSelectionChange( Selected, Selected, default ); } - private void OnSelectionChange( ModFileSystemA.Leaf? _1, ModFileSystemA.Leaf? newSelection, in ModState _2 ) + private void OnSelectionChange( Mod2? _1, Mod2? newSelection, in ModState _2 ) { if( newSelection == null ) { @@ -180,7 +181,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo } else { - ( var settings, SelectedSettingCollection ) = Penumbra.CollectionManager.Current[ newSelection.Value.Index ]; + ( var settings, SelectedSettingCollection ) = Penumbra.CollectionManager.Current[ newSelection.Index ]; SelectedSettings = settings ?? ModSettings2.Empty; } } @@ -198,8 +199,8 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo { if( _lastSelectedDirectory.Length > 0 ) { - SelectedLeaf = ( ModFileSystemA.Leaf? )FileSystem.Root.GetAllDescendants( SortMode.Lexicographical ) - .FirstOrDefault( l => l is ModFileSystemA.Leaf m && m.Value.BasePath.FullName == _lastSelectedDirectory ); + SelectedLeaf = ( ModFileSystem.Leaf? )FileSystem.Root.GetAllDescendants( SortMode.Lexicographical ) + .FirstOrDefault( l => l is ModFileSystem.Leaf m && m.Value.BasePath.FullName == _lastSelectedDirectory ); _lastSelectedDirectory = string.Empty; } } diff --git a/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs b/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs new file mode 100644 index 00000000..109bc32b --- /dev/null +++ b/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; + +namespace Penumbra.UI; + +public partial class ConfigWindow +{ + private LowerString _changedItemFilter = LowerString.Empty; + + public void DrawChangedItemTab() + { + using var tab = ImRaii.TabItem( "Changed Items" ); + if( !tab ) + { + return; + } + + ImGui.SetNextItemWidth( -1 ); + LowerString.InputWithHint( "##changedItemsFilter", "Filter...", ref _changedItemFilter, 64 ); + + using var child = ImRaii.Child( "##changedItemsChild", -Vector2.One ); + if( !child ) + { + return; + } + + var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y; + var skips = ImGuiClip.GetNecessarySkips( height ); + using var list = ImRaii.Table( "##changedItems", 1, ImGuiTableFlags.RowBg, -Vector2.One ); + if( !list ) + { + return; + } + + var items = Penumbra.CollectionManager.Default.ChangedItems; + var rest = _changedItemFilter.IsEmpty + ? ImGuiClip.ClippedDraw( items, skips, DrawChangedItem, items.Count ) + : ImGuiClip.FilteredClippedDraw( items, skips, FilterChangedItem, DrawChangedItem ); + ImGuiClip.DrawEndDummy( rest, height ); + } + + private bool FilterChangedItem( KeyValuePair< string, object? > item ) + => item.Key.Contains( _changedItemFilter.Lower, StringComparison.InvariantCultureIgnoreCase ); + + private void DrawChangedItem( KeyValuePair< string, object? > item ) + { + ImGui.TableNextColumn(); + DrawChangedItem( item.Key, item.Value, ImGui.GetStyle().ScrollbarSize ); + } +} \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.CollectionsTab.cs b/Penumbra/UI/ConfigWindow.CollectionsTab.cs index eeacb28e..7f5f8ee7 100644 --- a/Penumbra/UI/ConfigWindow.CollectionsTab.cs +++ b/Penumbra/UI/ConfigWindow.CollectionsTab.cs @@ -1,10 +1,205 @@ +using System.Linq; using System.Numerics; +using Dalamud.Interface; +using Dalamud.Interface.Components; +using ImGuiNET; +using OtterGui; using OtterGui.Raii; +using Penumbra.Collections; namespace Penumbra.UI; public partial class ConfigWindow { + private string _newCollectionName = string.Empty; + private string _newCharacterName = string.Empty; + + private void CreateNewCollection( bool duplicate ) + { + if( Penumbra.CollectionManager.AddCollection( _newCollectionName, duplicate ? Penumbra.CollectionManager.Current : null ) ) + { + _newCollectionName = string.Empty; + } + } + + private static void DrawCleanCollectionButton() + { + if( ImGui.Button( "Clean Settings" ) ) + { + Penumbra.CollectionManager.Current.CleanUnavailableSettings(); + } + + ImGuiUtil.HoverTooltip( "Remove all stored settings for mods not currently available and fix invalid settings.\nUse at own risk." ); + } + + private void DrawNewCollectionInput() + { + ImGui.SetNextItemWidth( _inputTextWidth.X ); + ImGui.InputTextWithHint( "##New Collection", "New Collection Name", ref _newCollectionName, 64 ); + ImGui.SameLine(); + ImGuiComponents.HelpMarker( + "A collection is a set of settings for your installed mods, including their enabled status, their priorities and their mod-specific configuration.\n" + + "You can use multiple collections to quickly switch between sets of mods." ); + + var createCondition = _newCollectionName.Length > 0; + var tt = createCondition ? string.Empty : "Please enter a name before creating a collection."; + if( ImGuiUtil.DrawDisabledButton( "Create New Empty Collection", Vector2.Zero, tt, !createCondition ) ) + { + CreateNewCollection( false ); + } + + ImGui.SameLine(); + if( ImGuiUtil.DrawDisabledButton( "Duplicate Current Collection", Vector2.Zero, tt, !createCondition ) ) + { + CreateNewCollection( true ); + } + + var deleteCondition = Penumbra.CollectionManager.Current.Name != ModCollection.DefaultCollection; + tt = deleteCondition ? string.Empty : "You can not delete the default collection."; + ImGui.SameLine(); + if( ImGuiUtil.DrawDisabledButton( "Delete Current Collection", Vector2.Zero, tt, !deleteCondition ) ) + { + Penumbra.CollectionManager.RemoveCollection( Penumbra.CollectionManager.Current ); + } + + if( Penumbra.Config.ShowAdvanced ) + { + ImGui.SameLine(); + DrawCleanCollectionButton(); + } + } + + public void DrawCurrentCollectionSelector() + { + DrawCollectionSelector( "##current", _inputTextWidth.X, ModCollection.Type.Current, false, null ); + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker( "Current Collection", + "This collection will be modified when using the Installed Mods tab and making changes. It does not apply to anything by itself." ); + } + + private void DrawDefaultCollectionSelector() + { + DrawCollectionSelector( "##default", _inputTextWidth.X, ModCollection.Type.Default, true, null ); + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker( "Default Collection", + "Mods in the default collection are loaded for any character that is not explicitly named in the character collections below.\n" + + "They also take precedence before the forced collection." ); + } + + private void DrawNewCharacterCollection() + { + const string description = "Character Collections apply specifically to game objects of the given name.\n" + + "The default collection does not apply to any character that has a character collection specified.\n" + + "Certain actors - like the ones in cutscenes or preview windows - will try to use appropriate character collections.\n"; + + ImGui.SetNextItemWidth(_inputTextWidth.X ); + ImGui.InputTextWithHint( "##NewCharacter", "New Character Name", ref _newCharacterName, 32 ); + ImGui.SameLine(); + var disabled = _newCharacterName.Length == 0; + var tt = disabled ? "Please enter a Character name before creating the collection.\n\n" + description : description; + if( ImGuiUtil.DrawDisabledButton( "Create New Character Collection", Vector2.Zero, tt, disabled) ) + { + Penumbra.CollectionManager.CreateCharacterCollection( _newCharacterName ); + _newCharacterName = string.Empty; + } + } + + private void DrawCharacterCollectionSelectors() + { + using var child = ImRaii.Child( "##Collections", -Vector2.One, true ); + if( !child ) + return; + + DrawDefaultCollectionSelector(); + + foreach( var name in Penumbra.CollectionManager.Characters.Keys.ToArray() ) + { + using var id = ImRaii.PushId( name ); + DrawCollectionSelector( string.Empty, _inputTextWidth.X, ModCollection.Type.Character, true, name ); + ImGui.SameLine(); + if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), Vector2.One * ImGui.GetFrameHeight(), string.Empty, false, true) ) + { + Penumbra.CollectionManager.RemoveCharacterCollection( name ); + } + ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + ImGui.Text( name ); + } + DrawNewCharacterCollection(); + } + + //private static void DrawInheritance( ModCollection collection ) + // { + // ImGui.PushID( collection.Index ); + // if( ImGui.TreeNodeEx( collection.Name, ImGuiTreeNodeFlags.DefaultOpen ) ) + // { + // foreach( var inheritance in collection.Inheritance ) + // { + // DrawInheritance( inheritance ); + // } + // } + // + // ImGui.PopID(); + // } + // + // private void DrawCurrentCollectionInheritance() + // { + // if( !ImGui.BeginListBox( "##inheritanceList", + // new Vector2( SettingsMenu.InputTextWidth, ImGui.GetTextLineHeightWithSpacing() * 10 ) ) ) + // { + // return; + // } + // + // using var end = ImGuiRaii.DeferredEnd( ImGui.EndListBox ); + // DrawInheritance( _collections[ _currentCollectionIndex + 1 ] ); + // } + // + //private static int _newInheritanceIdx = 0; + // + //private void DrawNewInheritanceSelection() + //{ + // ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth - ImGui.GetFrameHeight() - ImGui.GetStyle().ItemSpacing.X ); + // if( ImGui.BeginCombo( "##newInheritance", Penumbra.CollectionManager[ _newInheritanceIdx ].Name ) ) + // { + // using var end = ImGuiRaii.DeferredEnd( ImGui.EndCombo ); + // foreach( var collection in Penumbra.CollectionManager ) + // { + // if( ImGui.Selectable( collection.Name, _newInheritanceIdx == collection.Index ) ) + // { + // _newInheritanceIdx = collection.Index; + // } + // } + // } + // + // ImGui.SameLine(); + // var valid = _newInheritanceIdx > ModCollection.Empty.Index + // && _collections[ _currentCollectionIndex + 1 ].Index != _newInheritanceIdx + // && _collections[ _currentCollectionIndex + 1 ].Inheritance.All( c => c.Index != _newInheritanceIdx ); + // using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f, !valid ); + // using var font = ImGuiRaii.PushFont( UiBuilder.IconFont ); + // if( ImGui.Button( $"{FontAwesomeIcon.Plus.ToIconString()}##newInheritanceAdd", ImGui.GetFrameHeight() * Vector2.One ) && valid ) + // { + // _collections[ _currentCollectionIndex + 1 ].AddInheritance( Penumbra.CollectionManager[ _newInheritanceIdx ] ); + // } + // + // style.Pop(); + // font.Pop(); + // ImGuiComponents.HelpMarker( "Add a new inheritance to the collection." ); + //} + + private void DrawMainSelectors() + { + using var main = ImRaii.Child( "##CollectionsMain", new Vector2( -1, ImGui.GetTextLineHeightWithSpacing() * 17 ), true ); + if( !main ) + { + return; + } + + DrawCurrentCollectionSelector(); + ImGuiHelpers.ScaledDummy( 0, 10 ); + DrawNewCollectionInput(); + } + public void DrawCollectionsTab() { using var tab = ImRaii.TabItem( "Collections" ); @@ -13,10 +208,9 @@ public partial class ConfigWindow return; } - using var child = ImRaii.Child( "##CollectionsTab", -Vector2.One ); - if( !child ) - { - return; - } + + DrawMainSelectors(); + DrawCharacterCollectionSelectors(); } + } \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.DebugTab.cs b/Penumbra/UI/ConfigWindow.DebugTab.cs index a76969b0..1bd4fefc 100644 --- a/Penumbra/UI/ConfigWindow.DebugTab.cs +++ b/Penumbra/UI/ConfigWindow.DebugTab.cs @@ -1,13 +1,26 @@ +using System; +using System.IO; +using System.Linq; using System.Numerics; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.FFXIV.Client.System.Resource; +using ImGuiNET; +using OtterGui; using OtterGui.Raii; +using Penumbra.Api; +using Penumbra.Interop.Structs; +using CharacterUtility = Penumbra.Interop.CharacterUtility; namespace Penumbra.UI; public partial class ConfigWindow { #if DEBUG - private const bool DefaultVisibility = true; + private const string DebugVersionString = "(Debug)"; + private const bool DefaultVisibility = true; #else + private const string DebugVersionString = "(Release)"; private const bool DefaultVisibility = false; #endif @@ -31,5 +44,359 @@ public partial class ConfigWindow { return; } + + DrawDebugTabGeneral(); + ImGui.NewLine(); + DrawDebugTabReplacedResources(); + ImGui.NewLine(); + DrawPathResolverDebug(); + ImGui.NewLine(); + DrawDebugCharacterUtility(); + ImGui.NewLine(); + DrawDebugResidentResources(); + ImGui.NewLine(); + DrawPlayerModelInfo(); + ImGui.NewLine(); + DrawDebugTabIpc(); + ImGui.NewLine(); + } + + // Draw general information about mod and collection state. + private void DrawDebugTabGeneral() + { + if( !ImGui.CollapsingHeader( "General" ) ) + { + return; + } + + using var table = ImRaii.Table( "##DebugGeneralTable", 2, ImGuiTableFlags.SizingFixedFit, + new Vector2( -1, ImGui.GetTextLineHeightWithSpacing() * 1 ) ); + if( !table ) + { + return; + } + + var manager = Penumbra.ModManager; + PrintValue( "Penumbra Version", $"{Penumbra.Version} {DebugVersionString}" ); + PrintValue( "Git Commit Hash", Penumbra.CommitHash ); + PrintValue( "Current Collection", Penumbra.CollectionManager.Current.Name ); + PrintValue( " has Cache", Penumbra.CollectionManager.Current.HasCache.ToString() ); + PrintValue( "Default Collection", Penumbra.CollectionManager.Default.Name ); + PrintValue( " has Cache", Penumbra.CollectionManager.Default.HasCache.ToString() ); + PrintValue( "Mod Manager BasePath", manager.BasePath.Name ); + PrintValue( "Mod Manager BasePath-Full", manager.BasePath.FullName ); + PrintValue( "Mod Manager BasePath IsRooted", Path.IsPathRooted( Penumbra.Config.ModDirectory ).ToString() ); + PrintValue( "Mod Manager BasePath Exists", Directory.Exists( manager.BasePath.FullName ).ToString() ); + PrintValue( "Mod Manager Valid", manager.Valid.ToString() ); + PrintValue( "Path Resolver Enabled", _penumbra.PathResolver.Enabled.ToString() ); + PrintValue( "Music Manager Streaming Disabled", ( !_penumbra.MusicManager.StreamingEnabled ).ToString() ); + PrintValue( "Web Server Enabled", ( _penumbra.WebServer != null ).ToString() ); + } + + // Draw all resources currently replaced by Penumbra and (if existing) the resources they replace. + // Resources are collected by iterating through the + private static unsafe void DrawDebugTabReplacedResources() + { + if( !ImGui.CollapsingHeader( "Replaced Resources" ) ) + { + return; + } + + Penumbra.ResourceLoader.UpdateDebugInfo(); + + if( Penumbra.ResourceLoader.DebugList.Count == 0 ) + { + return; + } + + using var table = ImRaii.Table( "##ReplacedResources", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, + -Vector2.UnitX ); + if( !table ) + { + return; + } + + foreach( var data in Penumbra.ResourceLoader.DebugList.Values.ToArray() ) + { + var refCountManip = data.ManipulatedResource == null ? 0 : data.ManipulatedResource->RefCount; + var refCountOrig = data.OriginalResource == null ? 0 : data.OriginalResource->RefCount; + ImGui.TableNextColumn(); + ImGui.Text( data.ManipulatedPath.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( ( ( ulong )data.ManipulatedResource ).ToString( "X" ) ); + ImGui.TableNextColumn(); + ImGui.Text( refCountManip.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( data.OriginalPath.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( ( ( ulong )data.OriginalResource ).ToString( "X" ) ); + ImGui.TableNextColumn(); + ImGui.Text( refCountOrig.ToString() ); + } + } + + // Draw information about which draw objects correspond to which game objects + // and which paths are due to be loaded by which collection. + private unsafe void DrawPathResolverDebug() + { + if( !ImGui.CollapsingHeader( "Path Resolver" ) ) + { + return; + } + + using var drawTree = ImRaii.TreeNode( "Draw Object to Object" ); + if( drawTree ) + { + using var table = ImRaii.Table( "###DrawObjectResolverTable", 5, ImGuiTableFlags.SizingFixedFit ); + if( table ) + { + foreach( var (ptr, (c, idx)) in _penumbra.PathResolver.DrawObjectToObject ) + { + ImGui.TableNextColumn(); + ImGui.Text( ptr.ToString( "X" ) ); + ImGui.TableNextColumn(); + ImGui.Text( idx.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( Dalamud.Objects[ idx ]?.Address.ToString() ?? "NULL" ); + ImGui.TableNextColumn(); + ImGui.Text( Dalamud.Objects[ idx ]?.Name.ToString() ?? "NULL" ); + ImGui.TableNextColumn(); + ImGui.Text( c.Name ); + } + } + } + + drawTree.Dispose(); + + using var pathTree = ImRaii.TreeNode( "Path Collections" ); + if( pathTree ) + { + using var table = ImRaii.Table( "###PathCollectionResolverTable", 2, ImGuiTableFlags.SizingFixedFit ); + if( table ) + { + foreach( var (path, collection) in _penumbra.PathResolver.PathCollections ) + { + ImGui.TableNextColumn(); + ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length ); + ImGui.TableNextColumn(); + ImGui.Text( collection.Name ); + } + } + } + } + + // Draw information about the character utility class from SE, + // displaying all files, their sizes, the default files and the default sizes. + public unsafe void DrawDebugCharacterUtility() + { + if( !ImGui.CollapsingHeader( "Character Utility" ) ) + { + return; + } + + using var table = ImRaii.Table( "##CharacterUtility", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, + -Vector2.UnitX ); + if( !table ) + { + return; + } + + for( var i = 0; i < CharacterUtility.RelevantIndices.Length; ++i ) + { + var idx = CharacterUtility.RelevantIndices[ i ]; + var resource = ( ResourceHandle* )Penumbra.CharacterUtility.Address->Resources[ idx ]; + ImGui.TableNextColumn(); + ImGui.Text( $"0x{( ulong )resource:X}" ); + ImGui.TableNextColumn(); + Text( resource ); + ImGui.TableNextColumn(); + ImGui.Selectable( $"0x{resource->GetData().Data:X}" ); + if( ImGui.IsItemClicked() ) + { + var (data, length) = resource->GetData(); + if( data != IntPtr.Zero && length > 0 ) + { + ImGui.SetClipboardText( string.Join( " ", + new ReadOnlySpan< byte >( ( byte* )data, length ).ToArray().Select( b => b.ToString( "X2" ) ) ) ); + } + } + + ImGuiUtil.HoverTooltip( "Click to copy bytes to clipboard." ); + + ImGui.TableNextColumn(); + ImGui.Text( $"{resource->GetData().Length}" ); + ImGui.TableNextColumn(); + ImGui.Selectable( $"0x{Penumbra.CharacterUtility.DefaultResources[ i ].Address:X}" ); + if( ImGui.IsItemClicked() ) + { + ImGui.SetClipboardText( string.Join( " ", + new ReadOnlySpan< byte >( ( byte* )Penumbra.CharacterUtility.DefaultResources[ i ].Address, + Penumbra.CharacterUtility.DefaultResources[ i ].Size ).ToArray().Select( b => b.ToString( "X2" ) ) ) ); + } + + ImGuiUtil.HoverTooltip( "Click to copy bytes to clipboard." ); + + ImGui.TableNextColumn(); + ImGui.Text( $"{Penumbra.CharacterUtility.DefaultResources[ i ].Size}" ); + } + } + + // Draw information about the resident resource files. + public unsafe void DrawDebugResidentResources() + { + if( !ImGui.CollapsingHeader( "Resident Resources" ) ) + { + return; + } + + if( Penumbra.ResidentResources.Address == null || Penumbra.ResidentResources.Address->NumResources == 0 ) + { + return; + } + + using var table = ImRaii.Table( "##ResidentResources", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, + -Vector2.UnitX ); + if( !table ) + { + return; + } + + for( var i = 0; i < Penumbra.ResidentResources.Address->NumResources; ++i ) + { + var resource = Penumbra.ResidentResources.Address->ResourceList[ i ]; + ImGui.TableNextColumn(); + ImGui.Text( $"0x{( ulong )resource:X}" ); + ImGui.TableNextColumn(); + Text( resource ); + } + } + + // Draw information about the models, materials and resources currently loaded by the local player. + private static unsafe void DrawPlayerModelInfo() + { + var player = Dalamud.ClientState.LocalPlayer; + var name = player?.Name.ToString() ?? "NULL"; + if( !ImGui.CollapsingHeader( $"Player Model Info: {name}##Draw" ) || player == null ) + { + return; + } + + var model = ( CharacterBase* )( ( Character* )player.Address )->GameObject.GetDrawObject(); + if( model == null ) + { + return; + } + + using var table = ImRaii.Table( $"##{name}DrawTable", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit ); + if( !table ) + { + return; + } + + ImGui.TableNextColumn(); + ImGui.TableHeader( "Slot" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( "Imc Ptr" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( "Imc File" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( "Model Ptr" ); + ImGui.TableNextColumn(); + ImGui.TableHeader( "Model File" ); + + for( var i = 0; i < model->SlotCount; ++i ) + { + var imc = ( ResourceHandle* )model->IMCArray[ i ]; + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text( $"Slot {i}" ); + ImGui.TableNextColumn(); + ImGui.Text( imc == null ? "NULL" : $"0x{( ulong )imc:X}" ); + ImGui.TableNextColumn(); + if( imc != null ) + { + Text( imc ); + } + + var mdl = ( RenderModel* )model->ModelArray[ i ]; + ImGui.TableNextColumn(); + ImGui.Text( mdl == null ? "NULL" : $"0x{( ulong )mdl:X}" ); + if( mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara ) + { + continue; + } + + ImGui.TableNextColumn(); + { + Text( mdl->ResourceHandle ); + } + } + } + + // Draw information about IPC options and availability. + private void DrawDebugTabIpc() + { + if( !ImGui.CollapsingHeader( "IPC" ) ) + { + return; + } + + var ipc = _penumbra.Ipc; + ImGui.Text( $"API Version: {ipc.Api.ApiVersion}" ); + ImGui.Text( "Available subscriptions:" ); + using var indent = ImRaii.PushIndent(); + if( ipc.ProviderApiVersion != null ) + { + ImGui.Text( PenumbraIpc.LabelProviderApiVersion ); + } + + if( ipc.ProviderRedrawName != null ) + { + ImGui.Text( PenumbraIpc.LabelProviderRedrawName ); + } + + if( ipc.ProviderRedrawObject != null ) + { + ImGui.Text( PenumbraIpc.LabelProviderRedrawObject ); + } + + if( ipc.ProviderRedrawAll != null ) + { + ImGui.Text( PenumbraIpc.LabelProviderRedrawAll ); + } + + if( ipc.ProviderResolveDefault != null ) + { + ImGui.Text( PenumbraIpc.LabelProviderResolveDefault ); + } + + if( ipc.ProviderResolveCharacter != null ) + { + ImGui.Text( PenumbraIpc.LabelProviderResolveCharacter ); + } + + if( ipc.ProviderChangedItemTooltip != null ) + { + ImGui.Text( PenumbraIpc.LabelProviderChangedItemTooltip ); + } + + if( ipc.ProviderChangedItemClick != null ) + { + ImGui.Text( PenumbraIpc.LabelProviderChangedItemClick ); + } + + if( ipc.ProviderGetChangedItems != null ) + { + ImGui.Text( PenumbraIpc.LabelProviderGetChangedItems ); + } + } + + // Helper to print a property and its value in a 2-column table. + private static void PrintValue( string name, string value ) + { + ImGui.TableNextColumn(); + ImGui.Text( name ); + ImGui.TableNextColumn(); + ImGui.Text( value ); } } \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.EffectiveTab.cs b/Penumbra/UI/ConfigWindow.EffectiveTab.cs index db44fc62..b097bcb5 100644 --- a/Penumbra/UI/ConfigWindow.EffectiveTab.cs +++ b/Penumbra/UI/ConfigWindow.EffectiveTab.cs @@ -1,10 +1,18 @@ +using System.Collections.Generic; +using System.Linq; using System.Numerics; +using Dalamud.Interface; +using ImGuiNET; +using OtterGui; using OtterGui.Raii; +using Penumbra.Collections; +using Penumbra.GameData.ByteString; namespace Penumbra.UI; public partial class ConfigWindow { + // Draw the effective tab if ShowAdvanced is on. public void DrawEffectiveChangesTab() { if( !Penumbra.Config.ShowAdvanced ) @@ -18,10 +26,190 @@ public partial class ConfigWindow return; } - using var child = ImRaii.Child( "##EffectiveChangesTab", -Vector2.One ); + SetupEffectiveSizes(); + DrawFilters(); + using var child = ImRaii.Child( "##EffectiveChangesTab", -Vector2.One, false ); if( !child ) { return; } + + var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y; + var skips = ImGuiClip.GetNecessarySkips( height ); + using var table = ImRaii.Table( "##EffectiveChangesTable", 3, ImGuiTableFlags.RowBg ); + if( !table ) + { + return; + } + + ImGui.TableSetupColumn( "##gamePath", ImGuiTableColumnFlags.WidthFixed, _effectiveLeftTextLength ); + ImGui.TableSetupColumn( string.Empty, ImGuiTableColumnFlags.WidthFixed, _effectiveArrowLength ); + ImGui.TableSetupColumn( "##file", ImGuiTableColumnFlags.WidthFixed, _effectiveRightTextLength ); + + DrawEffectiveRows( Penumbra.CollectionManager.Default, skips, height, + _effectiveFilePathFilter.Length > 0 || _effectiveGamePathFilter.Length > 0 ); + } + + // Sizes + private float _effectiveLeftTextLength; + private float _effectiveRightTextLength; + private float _effectiveUnscaledArrowLength; + private float _effectiveArrowLength; + + // Setup table sizes. + private void SetupEffectiveSizes() + { + if( _effectiveUnscaledArrowLength == 0 ) + { + using var font = ImRaii.PushFont( UiBuilder.IconFont ); + _effectiveUnscaledArrowLength = ImGui.CalcTextSize( FontAwesomeIcon.LongArrowAltLeft.ToIconString() ).X / ImGuiHelpers.GlobalScale; + } + + _effectiveArrowLength = _effectiveUnscaledArrowLength * ImGuiHelpers.GlobalScale; + _effectiveLeftTextLength = 450 * ImGuiHelpers.GlobalScale; + _effectiveRightTextLength = ImGui.GetWindowSize().X - _effectiveArrowLength - _effectiveLeftTextLength; + } + + // Filters + private LowerString _effectiveGamePathFilter = LowerString.Empty; + private LowerString _effectiveFilePathFilter = LowerString.Empty; + + // Draw the header line for filters + private void DrawFilters() + { + var tmp = _effectiveGamePathFilter.Text; + ImGui.SetNextItemWidth( _effectiveLeftTextLength ); + if( ImGui.InputTextWithHint( "##gamePathFilter", "Filter game path...", ref tmp, 256 ) ) + { + _effectiveGamePathFilter = tmp; + } + + ImGui.SameLine( _effectiveArrowLength + _effectiveLeftTextLength + 3 * ImGui.GetStyle().ItemSpacing.X ); + ImGui.SetNextItemWidth( -1 ); + tmp = _effectiveFilePathFilter.Text; + if( ImGui.InputTextWithHint( "##fileFilter", "Filter file path...", ref tmp, 256 ) ) + { + _effectiveFilePathFilter = tmp; + } + } + + // Draw all rows respecting filters and using clipping. + private void DrawEffectiveRows( ModCollection active, int skips, float height, bool hasFilters ) + { + // We can use the known counts if no filters are active. + var stop = hasFilters + ? ImGuiClip.FilteredClippedDraw( active.ResolvedFiles, skips, CheckFilters, DrawLine ) + : ImGuiClip.ClippedDraw( active.ResolvedFiles, skips, DrawLine, active.ResolvedFiles.Count ); + + var m = active.MetaCache; + // If no meta manipulations are active, we can just draw the end dummy. + if( m is { Count: > 0 } ) + { + // We can treat all meta manipulations the same, + // we are only really interested in their ToString function here. + static (object, int) Convert< T >( KeyValuePair< T, int > kvp ) + => ( kvp.Key!, kvp.Value ); + + var it = m.Cmp.Manipulations.Select( Convert ) + .Concat( m.Eqp.Manipulations.Select( Convert ) ) + .Concat( m.Eqdp.Manipulations.Select( Convert ) ) + .Concat( m.Gmp.Manipulations.Select( Convert ) ) + .Concat( m.Est.Manipulations.Select( Convert ) ) + .Concat( m.Imc.Manipulations.Select( Convert ) ); + + // Filters mean we can not use the known counts. + if( hasFilters ) + { + var it2 = it.Select( p => ( p.Item1.ToString() ?? string.Empty, Penumbra.ModManager.Mods[ p.Item2 ].Name ) ); + if( stop >= 0 ) + { + ImGuiClip.DrawEndDummy( stop + it2.Count( CheckFilters ), height ); + } + else + { + stop = ImGuiClip.FilteredClippedDraw( it2, skips, CheckFilters, DrawLine, ~stop ); + ImGuiClip.DrawEndDummy( stop, height ); + } + } + else + { + if( stop >= 0 ) + { + ImGuiClip.DrawEndDummy( stop + m.Count, height ); + } + else + { + stop = ImGuiClip.ClippedDraw( it, skips, DrawLine, m.Count, ~stop ); + ImGuiClip.DrawEndDummy( stop, height ); + } + } + } + else + { + ImGuiClip.DrawEndDummy( stop, height ); + } + } + + // Draw a line for a game path and its redirected file. + private static void DrawLine( KeyValuePair< Utf8GamePath, FullPath > pair ) + { + var (path, name) = pair; + ImGui.TableNextColumn(); + CopyOnClickSelectable( path.Path ); + + ImGui.TableNextColumn(); + ImGuiUtil.PrintIcon( FontAwesomeIcon.LongArrowAltLeft ); + ImGui.TableNextColumn(); + CopyOnClickSelectable( name.InternalName ); + } + + // Draw a line for a path and its name. + private static void DrawLine( (string, LowerString) pair ) + { + var (path, name) = pair; + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable( path ); + + ImGui.TableNextColumn(); + ImGuiUtil.PrintIcon( FontAwesomeIcon.LongArrowAltLeft ); + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable( name ); + } + + // Draw a line for a unfiltered/unconverted manipulation and mod-index pair. + private static void DrawLine( (object, int) pair ) + { + var (manipulation, modIdx) = pair; + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable( manipulation.ToString() ?? string.Empty ); + + ImGui.TableNextColumn(); + ImGuiUtil.PrintIcon( FontAwesomeIcon.LongArrowAltLeft ); + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable( Penumbra.ModManager.Mods[ modIdx ].Name ); + } + + // Check filters for file replacements. + private bool CheckFilters( KeyValuePair< Utf8GamePath, FullPath > kvp ) + { + var (gamePath, fullPath) = kvp; + if( _effectiveGamePathFilter.Length > 0 && !gamePath.ToString().Contains( _effectiveGamePathFilter.Lower ) ) + { + return false; + } + + return _effectiveFilePathFilter.Length == 0 || fullPath.FullName.ToLowerInvariant().Contains( _effectiveFilePathFilter.Lower ); + } + + // Check filters for meta manipulations. + private bool CheckFilters( (string, LowerString) kvp ) + { + var (name, path) = kvp; + if( _effectiveGamePathFilter.Length > 0 && !name.ToLowerInvariant().Contains( _effectiveGamePathFilter.Lower ) ) + { + return false; + } + + return _effectiveFilePathFilter.Length == 0 || path.Contains( _effectiveFilePathFilter.Lower ); } } \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.Misc.cs b/Penumbra/UI/ConfigWindow.Misc.cs index d4608546..8258fe33 100644 --- a/Penumbra/UI/ConfigWindow.Misc.cs +++ b/Penumbra/UI/ConfigWindow.Misc.cs @@ -1,20 +1,35 @@ +using System; +using System.Linq; using System.Numerics; using ImGuiNET; using Lumina.Data.Parsing; using Lumina.Excel.GeneratedSheets; +using OtterGui; +using OtterGui.Raii; +using Penumbra.Collections; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; -using Penumbra.UI.Custom; +using Penumbra.Interop.Structs; +using Penumbra.UI.Classes; namespace Penumbra.UI; public partial class ConfigWindow { + // Draw text given by a Utf8String. internal static unsafe void Text( Utf8String s ) - { - ImGuiNative.igTextUnformatted( s.Path, s.Path + s.Length ); - } + => ImGuiNative.igTextUnformatted( s.Path, s.Path + s.Length ); + // Draw text given by a byte pointer. + internal static unsafe void Text( byte* s, int length ) + => ImGuiNative.igTextUnformatted( s, s + length ); + + // Draw the name of a resource file. + internal static unsafe void Text( ResourceHandle* resource ) + => Text( resource->FileName(), resource->FileNameLength ); + + // Draw a changed item, invoking the Api-Events for clicks and tooltips. + // Also draw the item Id in grey internal void DrawChangedItem( string name, object? data, float itemIdOffset = 0 ) { var ret = ImGui.Selectable( name ) ? MouseButton.Left : MouseButton.None; @@ -28,18 +43,57 @@ public partial class ConfigWindow if( _penumbra.Api.HasTooltip && ImGui.IsItemHovered() ) { - ImGui.BeginTooltip(); - using var tooltip = ImGuiRaii.DeferredEnd( ImGui.EndTooltip ); + using var tt = ImRaii.Tooltip(); _penumbra.Api.InvokeTooltip( data ); } if( data is Item it ) { - var modelId = $"({( ( Quad )it.ModelMain ).A})"; - var offset = ImGui.CalcTextSize( modelId ).X - ImGui.GetStyle().ItemInnerSpacing.X + itemIdOffset; + ImGui.SameLine( ImGui.GetContentRegionAvail().X ); + ImGuiUtil.RightJustify( $"({( ( Quad )it.ModelMain ).A})", ColorId.ItemId.Value() ); + } + } - ImGui.SameLine( ImGui.GetWindowContentRegionWidth() - offset ); - ImGui.TextColored( new Vector4( 0.5f, 0.5f, 0.5f, 1 ), modelId ); + // A selectable that copies its text to clipboard on selection and provides a on-hover tooltip about that, + // using an Utf8String. + internal static unsafe void CopyOnClickSelectable( Utf8String text ) + { + if( ImGuiNative.igSelectable_Bool( text.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero ) != 0 ) + { + ImGuiNative.igSetClipboardText( text.Path ); + } + + if( ImGui.IsItemHovered() ) + { + ImGui.SetTooltip( "Click to copy to clipboard." ); + } + } + + // Draw a collection selector of a certain width for a certain type. + private static void DrawCollectionSelector( string label, float width, ModCollection.Type type, bool withEmpty, string? characterName ) + { + ImGui.SetNextItemWidth( width ); + var current = type switch + { + ModCollection.Type.Default => Penumbra.CollectionManager.Default, + ModCollection.Type.Character => Penumbra.CollectionManager.Character( characterName ?? string.Empty ), + ModCollection.Type.Current => Penumbra.CollectionManager.Current, + _ => throw new ArgumentOutOfRangeException( nameof( type ), type, null ), + }; + + using var combo = ImRaii.Combo( label, current.Name ); + if( !combo ) + { + return; + } + + foreach( var collection in Penumbra.CollectionManager.GetEnumeratorWithEmpty().Skip( withEmpty ? 0 : 1 ) ) + { + using var id = ImRaii.PushId( collection.Index ); + if( ImGui.Selectable( collection.Name, collection == current ) ) + { + Penumbra.CollectionManager.SetCollection( collection, type, characterName ); + } } } } \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.ModsTab.Details.cs b/Penumbra/UI/ConfigWindow.ModsTab.Details.cs new file mode 100644 index 00000000..6dfb603f --- /dev/null +++ b/Penumbra/UI/ConfigWindow.ModsTab.Details.cs @@ -0,0 +1,714 @@ +//using System.IO; +//using System.Linq; +//using System.Numerics; +//using Dalamud.Interface; +//using FFXIVClientStructs.FFXIV.Client.UI.Misc; +//using ImGuiNET; +//using Lumina.Data.Parsing; +//using Lumina.Excel.GeneratedSheets; +//using Penumbra.GameData.ByteString; +//using Penumbra.GameData.Enums; +//using Penumbra.GameData.Util; +//using Penumbra.Meta; +//using Penumbra.Meta.Manipulations; +//using Penumbra.Mods; +//using Penumbra.UI.Custom; +//using Penumbra.Util; +//using ImGui = ImGuiNET.ImGui; +// +//namespace Penumbra.UI; +// +//public partial class SettingsInterface +//{ +// private partial class PluginDetails +// { +// private const string LabelPluginDetails = "PenumbraPluginDetails"; +// private const string LabelAboutTab = "About"; +// private const string LabelChangedItemsTab = "Changed Items"; +// private const string LabelChangedItemsHeader = "##changedItems"; +// private const string LabelConflictsTab = "Mod Conflicts"; +// private const string LabelConflictsHeader = "##conflicts"; +// private const string LabelFileSwapTab = "File Swaps"; +// private const string LabelFileSwapHeader = "##fileSwaps"; +// private const string LabelFileListTab = "Files"; +// private const string LabelFileListHeader = "##fileList"; +// private const string LabelGroupSelect = "##groupSelect"; +// private const string LabelOptionSelect = "##optionSelect"; +// private const string LabelConfigurationTab = "Configuration"; +// +// private const string TooltipFilesTab = +// "Green files replace their standard game path counterpart (not in any option) or are in all options of a Single-Select option.\n" +// + "Yellow files are restricted to some options."; +// +// private const float OptionSelectionWidth = 140f; +// private const float CheckMarkSize = 50f; +// private const uint ColorDarkGreen = 0xFF00A000; +// private const uint ColorGreen = 0xFF00C800; +// private const uint ColorYellow = 0xFF00C8C8; +// private const uint ColorDarkRed = 0xFF0000A0; +// private const uint ColorRed = 0xFF0000C8; +// +// +// private bool _editMode; +// private int _selectedGroupIndex; +// private OptionGroup? _selectedGroup; +// private int _selectedOptionIndex; +// private ConfigModule.Option? _selectedOption; +// private string _currentGamePaths = ""; +// +// private (FullPath name, bool selected, uint color, Utf8RelPath relName)[]? _fullFilenameList; +// +// private readonly Selector _selector; +// private readonly SettingsInterface _base; +// +// private void SelectGroup( int idx ) +// { +// // Not using the properties here because we need it to be not null forgiving in this case. +// var numGroups = _selector.Mod?.Data.Meta.Groups.Count ?? 0; +// _selectedGroupIndex = idx; +// if( _selectedGroupIndex >= numGroups ) +// { +// _selectedGroupIndex = 0; +// } +// +// if( numGroups > 0 ) +// { +// _selectedGroup = Meta.Groups.ElementAt( _selectedGroupIndex ).Value; +// } +// else +// { +// _selectedGroup = null; +// } +// } +// +// private void SelectGroup() +// => SelectGroup( _selectedGroupIndex ); +// +// private void SelectOption( int idx ) +// { +// _selectedOptionIndex = idx; +// if( _selectedOptionIndex >= _selectedGroup?.Options.Count ) +// { +// _selectedOptionIndex = 0; +// } +// +// if( _selectedGroup?.Options.Count > 0 ) +// { +// _selectedOption = ( ( OptionGroup )_selectedGroup ).Options[ _selectedOptionIndex ]; +// } +// else +// { +// _selectedOption = null; +// } +// } +// +// private void SelectOption() +// => SelectOption( _selectedOptionIndex ); +// +// public void ResetState() +// { +// _fullFilenameList = null; +// SelectGroup(); +// SelectOption(); +// } +// +// public PluginDetails( SettingsInterface ui, Selector s ) +// { +// _base = ui; +// _selector = s; +// ResetState(); +// } +// +// // This is only drawn when we have a mod selected, so we can forgive nulls. +// private FullMod Mod +// => _selector.Mod!; +// +// private ModMeta Meta +// => Mod.Data.Meta; +// +// private void DrawAboutTab() +// { +// if( !_editMode && Meta.Description.Length == 0 ) +// { +// return; +// } +// +// if( !ImGui.BeginTabItem( LabelAboutTab ) ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); +// +// var desc = Meta.Description; +// var flags = _editMode +// ? ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CtrlEnterForNewLine +// : ImGuiInputTextFlags.ReadOnly; +// +// if( _editMode ) +// { +// if( ImGui.InputTextMultiline( LabelDescEdit, ref desc, 1 << 16, +// AutoFillSize, flags ) ) +// { +// Meta.Description = desc; +// _selector.SaveCurrentMod(); +// } +// +// ImGuiCustom.HoverTooltip( TooltipAboutEdit ); +// } +// else +// { +// ImGui.TextWrapped( desc ); +// } +// } +// +// private void DrawChangedItemsTab() +// { +// if( Mod.Data.ChangedItems.Count == 0 || !ImGui.BeginTabItem( LabelChangedItemsTab ) ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); +// +// if( !ImGui.BeginListBox( LabelChangedItemsHeader, AutoFillSize ) ) +// { +// return; +// } +// +// raii.Push( ImGui.EndListBox ); +// foreach( var (name, data) in Mod.Data.ChangedItems ) +// { +// _base.DrawChangedItem( name, data ); +// } +// } +// +// private void DrawConflictTab() +// { +// var conflicts = Penumbra.CollectionManager.Current.ModConflicts( Mod.Data.Index ).ToList(); +// if( conflicts.Count == 0 || !ImGui.BeginTabItem( LabelConflictsTab ) ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); +// +// ImGui.SetNextItemWidth( -1 ); +// if( !ImGui.BeginListBox( LabelConflictsHeader, AutoFillSize ) ) +// { +// return; +// } +// +// raii.Push( ImGui.EndListBox ); +// using var indent = ImGuiRaii.PushIndent( 0 ); +// Mods.Mod? oldBadMod = null; +// foreach( var conflict in conflicts ) +// { +// var badMod = Penumbra.ModManager[ conflict.Mod2 ]; +// if( badMod != oldBadMod ) +// { +// if( oldBadMod != null ) +// { +// indent.Pop( 30f ); +// } +// +// if( ImGui.Selectable( badMod.Meta.Name ) ) +// { +// _selector.SelectModByDir( badMod.BasePath.Name ); +// } +// +// ImGui.SameLine(); +// using var color = ImGuiRaii.PushColor( ImGuiCol.Text, conflict.Mod1Priority ? ColorGreen : ColorRed ); +// ImGui.Text( $"(Priority {Penumbra.CollectionManager.Current[ conflict.Mod2 ].Settings!.Priority})" ); +// +// indent.Push( 30f ); +// } +// +// if( conflict.Data is Utf8GamePath p ) +// { +// unsafe +// { +// ImGuiNative.igSelectable_Bool( p.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero ); +// } +// } +// else if( conflict.Data is MetaManipulation m ) +// { +// ImGui.Selectable( m.Manipulation?.ToString() ?? string.Empty ); +// } +// +// oldBadMod = badMod; +// } +// } +// +// private void DrawFileSwapTab() +// { +// if( _editMode ) +// { +// DrawFileSwapTabEdit(); +// return; +// } +// +// if( !Meta.FileSwaps.Any() || !ImGui.BeginTabItem( LabelFileSwapTab ) ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); +// +// const ImGuiTableFlags flags = ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX; +// +// ImGui.SetNextItemWidth( -1 ); +// if( !ImGui.BeginTable( LabelFileSwapHeader, 3, flags, AutoFillSize ) ) +// { +// return; +// } +// +// raii.Push( ImGui.EndTable ); +// +// foreach( var (source, target) in Meta.FileSwaps ) +// { +// ImGui.TableNextColumn(); +// ImGuiCustom.CopyOnClickSelectable( source.Path ); +// +// ImGui.TableNextColumn(); +// ImGuiCustom.PrintIcon( FontAwesomeIcon.LongArrowAltRight ); +// +// ImGui.TableNextColumn(); +// ImGuiCustom.CopyOnClickSelectable( target.InternalName ); +// +// ImGui.TableNextRow(); +// } +// } +// +// private void UpdateFilenameList() +// { +// if( _fullFilenameList != null ) +// { +// return; +// } +// +// _fullFilenameList = Mod.Data.Resources.ModFiles +// .Select( f => ( f, false, ColorGreen, Utf8RelPath.FromFile( f, Mod.Data.BasePath, out var p ) ? p : Utf8RelPath.Empty ) ) +// .ToArray(); +// +// if( Meta.Groups.Count == 0 ) +// { +// return; +// } +// +// for( var i = 0; i < Mod.Data.Resources.ModFiles.Count; ++i ) +// { +// foreach( var group in Meta.Groups.Values ) +// { +// var inAll = true; +// foreach( var option in group.Options ) +// { +// if( option.OptionFiles.ContainsKey( _fullFilenameList[ i ].relName ) ) +// { +// _fullFilenameList[ i ].color = ColorYellow; +// } +// else +// { +// inAll = false; +// } +// } +// +// if( inAll && group.SelectionType == SelectType.Single ) +// { +// _fullFilenameList[ i ].color = ColorGreen; +// } +// } +// } +// } +// +// private void DrawFileListTab() +// { +// if( !ImGui.BeginTabItem( LabelFileListTab ) ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); +// ImGuiCustom.HoverTooltip( TooltipFilesTab ); +// +// ImGui.SetNextItemWidth( -1 ); +// if( ImGui.BeginListBox( LabelFileListHeader, AutoFillSize ) ) +// { +// raii.Push( ImGui.EndListBox ); +// UpdateFilenameList(); +// using var colorRaii = new ImGuiRaii.Color(); +// foreach( var (name, _, color, _) in _fullFilenameList! ) +// { +// colorRaii.Push( ImGuiCol.Text, color ); +// ImGui.Selectable( name.FullName ); +// colorRaii.Pop(); +// } +// } +// else +// { +// _fullFilenameList = null; +// } +// } +// +// private static int HandleDefaultString( Utf8GamePath[] gamePaths, out int removeFolders ) +// { +// removeFolders = 0; +// var defaultIndex = gamePaths.IndexOf( p => p.Path.StartsWith( DefaultUtf8GamePath ) ); +// if( defaultIndex < 0 ) +// { +// return defaultIndex; +// } +// +// var path = gamePaths[ defaultIndex ].Path; +// if( path.Length == TextDefaultGamePath.Length ) +// { +// return defaultIndex; +// } +// +// if( path[ TextDefaultGamePath.Length ] != ( byte )'-' +// || !int.TryParse( path.Substring( TextDefaultGamePath.Length + 1 ).ToString(), out removeFolders ) ) +// { +// return -1; +// } +// +// return defaultIndex; +// } +// +// private void HandleSelectedFilesButton( bool remove ) +// { +// if( _selectedOption == null ) +// { +// return; +// } +// +// var option = ( ConfigModule.Option )_selectedOption; +// +// var gamePaths = _currentGamePaths.Split( ';' ) +// .Select( p => Utf8GamePath.FromString( p, out var path, false ) ? path : Utf8GamePath.Empty ).Where( p => !p.IsEmpty ).ToArray(); +// if( gamePaths.Length == 0 ) +// { +// return; +// } +// +// var defaultIndex = HandleDefaultString( gamePaths, out var removeFolders ); +// var changed = false; +// for( var i = 0; i < Mod.Data.Resources.ModFiles.Count; ++i ) +// { +// if( !_fullFilenameList![ i ].selected ) +// { +// continue; +// } +// +// _fullFilenameList![ i ].selected = false; +// var relName = _fullFilenameList[ i ].relName; +// if( defaultIndex >= 0 ) +// { +// gamePaths[ defaultIndex ] = relName.ToGamePath( removeFolders ); +// } +// +// if( remove && option.OptionFiles.TryGetValue( relName, out var setPaths ) ) +// { +// if( setPaths.RemoveWhere( p => gamePaths.Contains( p ) ) > 0 ) +// { +// changed = true; +// } +// +// if( setPaths.Count == 0 && option.OptionFiles.Remove( relName ) ) +// { +// changed = true; +// } +// } +// else +// { +// changed = gamePaths +// .Aggregate( changed, ( current, gamePath ) => current | option.AddFile( relName, gamePath ) ); +// } +// } +// +// if( changed ) +// { +// _fullFilenameList = null; +// _selector.SaveCurrentMod(); +// var idx = Penumbra.ModManager.Mods.IndexOf( Mod.Data ); +// // Since files may have changed, we need to recompute effective files. +// foreach( var collection in Penumbra.CollectionManager +// .Where( c => c.HasCache && c[ idx ].Settings?.Enabled == true ) ) +// { +// collection.CalculateEffectiveFileList( false, collection == Penumbra.CollectionManager.Default ); +// } +// +// // If the mod is enabled in the current collection, its conflicts may have changed. +// if( Mod.Settings.Enabled ) +// { +// _selector.Cache.TriggerFilterReset(); +// } +// } +// } +// +// private void DrawAddToGroupButton() +// { +// if( ImGui.Button( ButtonAddToGroup ) ) +// { +// HandleSelectedFilesButton( false ); +// } +// } +// +// private void DrawRemoveFromGroupButton() +// { +// if( ImGui.Button( ButtonRemoveFromGroup ) ) +// { +// HandleSelectedFilesButton( true ); +// } +// } +// +// private void DrawGamePathInput() +// { +// ImGui.SetNextItemWidth( -1 ); +// ImGui.InputTextWithHint( LabelGamePathsEditBox, "Hover for help...", ref _currentGamePaths, +// 128 ); +// ImGuiCustom.HoverTooltip( TooltipGamePathsEdit ); +// } +// +// private void DrawGroupRow() +// { +// if( _selectedGroup == null ) +// { +// SelectGroup(); +// } +// +// if( _selectedOption == null ) +// { +// SelectOption(); +// } +// +// if( !DrawEditGroupSelector() ) +// { +// return; +// } +// +// ImGui.SameLine(); +// if( !DrawEditOptionSelector() ) +// { +// return; +// } +// +// ImGui.SameLine(); +// DrawAddToGroupButton(); +// ImGui.SameLine(); +// DrawRemoveFromGroupButton(); +// ImGui.SameLine(); +// DrawGamePathInput(); +// } +// +// private void DrawFileAndGamePaths( int idx ) +// { +// void Selectable( uint colorNormal, uint colorReplace ) +// { +// var loc = _fullFilenameList![ idx ].color; +// if( loc == colorNormal ) +// { +// loc = colorReplace; +// } +// +// using var colors = ImGuiRaii.PushColor( ImGuiCol.Text, loc ); +// ImGui.Selectable( _fullFilenameList[ idx ].name.FullName, ref _fullFilenameList[ idx ].selected ); +// } +// +// const float indentWidth = 30f; +// if( _selectedOption == null ) +// { +// Selectable( 0, ColorGreen ); +// return; +// } +// +// var fileName = _fullFilenameList![ idx ].relName; +// var optionFiles = ( ( ConfigModule.Option )_selectedOption ).OptionFiles; +// if( optionFiles.TryGetValue( fileName, out var gamePaths ) ) +// { +// Selectable( 0, ColorGreen ); +// +// using var indent = ImGuiRaii.PushIndent( indentWidth ); +// foreach( var gamePath in gamePaths.ToArray() ) +// { +// var tmp = gamePath.ToString(); +// var old = tmp; +// if( ImGui.InputText( $"##{fileName}_{gamePath}", ref tmp, 128, ImGuiInputTextFlags.EnterReturnsTrue ) +// && tmp != old ) +// { +// gamePaths.Remove( gamePath ); +// if( tmp.Length > 0 && Utf8GamePath.FromString( tmp, out var p, true ) ) +// { +// gamePaths.Add( p ); +// } +// else if( gamePaths.Count == 0 ) +// { +// optionFiles.Remove( fileName ); +// } +// +// _selector.SaveCurrentMod(); +// _selector.ReloadCurrentMod(); +// } +// } +// } +// else +// { +// Selectable( ColorYellow, ColorRed ); +// } +// } +// +// private void DrawMultiSelectorCheckBox( OptionGroup group, int idx, int flag, string label ) +// { +// var enabled = ( flag & ( 1 << idx ) ) != 0; +// var oldEnabled = enabled; +// if( ImGui.Checkbox( label, ref enabled ) && oldEnabled != enabled ) +// { +// Penumbra.CollectionManager.Current.SetModSetting( Mod.Data.Index, group.GroupName, +// Mod.Settings.Settings[ group.GroupName ] ^ ( 1 << idx ) ); +// // If the mod is enabled, recalculate files and filters. +// if( Mod.Settings.Enabled ) +// { +// _selector.Cache.TriggerFilterReset(); +// } +// } +// } +// +// private void DrawMultiSelector( OptionGroup group ) +// { +// if( group.Options.Count == 0 ) +// { +// return; +// } +// +// ImGuiCustom.BeginFramedGroup( group.GroupName ); +// using var raii = ImGuiRaii.DeferredEnd( ImGuiCustom.EndFramedGroup ); +// for( var i = 0; i < group.Options.Count; ++i ) +// { +// DrawMultiSelectorCheckBox( group, i, Mod.Settings.Settings[ group.GroupName ], +// $"{group.Options[ i ].OptionName}##{group.GroupName}" ); +// } +// } +// +// private void DrawSingleSelector( OptionGroup group ) +// { +// if( group.Options.Count < 2 ) +// { +// return; +// } +// +// var code = Mod.Settings.Settings[ group.GroupName ]; +// if( ImGui.Combo( group.GroupName, ref code +// , group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) +// && code != Mod.Settings.Settings[ group.GroupName ] ) +// { +// Penumbra.CollectionManager.Current.SetModSetting( Mod.Data.Index, group.GroupName, code ); +// if( Mod.Settings.Enabled ) +// { +// _selector.Cache.TriggerFilterReset(); +// } +// } +// } +// +// private void DrawGroupSelectors() +// { +// foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Single ) ) +// { +// DrawSingleSelector( g ); +// } +// +// foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Multi ) ) +// { +// DrawMultiSelector( g ); +// } +// } +// +// private void DrawConfigurationTab() +// { +// if( !_editMode && !Meta.HasGroupsWithConfig || !ImGui.BeginTabItem( LabelConfigurationTab ) ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); +// if( _editMode ) +// { +// DrawGroupSelectorsEdit(); +// } +// else +// { +// DrawGroupSelectors(); +// } +// } +// +// private void DrawMetaManipulationsTab() +// { +// if( !_editMode && Mod.Data.Resources.MetaManipulations.Count == 0 || !ImGui.BeginTabItem( "Meta Manipulations" ) ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); +// +// if( !ImGui.BeginListBox( "##MetaManipulations", AutoFillSize ) ) +// { +// return; +// } +// +// raii.Push( ImGui.EndListBox ); +// +// var manips = Mod.Data.Resources.MetaManipulations; +// var changes = false; +// if( _editMode || manips.DefaultData.Count > 0 ) +// { +// if( ImGui.CollapsingHeader( "Default" ) ) +// { +// changes = DrawMetaManipulationsTable( "##DefaultManips", manips.DefaultData, ref manips.Count ); +// } +// } +// +// foreach( var (groupName, group) in manips.GroupData ) +// { +// foreach( var (optionName, option) in group ) +// { +// if( ImGui.CollapsingHeader( $"{groupName} - {optionName}" ) ) +// { +// changes |= DrawMetaManipulationsTable( $"##{groupName}{optionName}manips", option, ref manips.Count ); +// } +// } +// } +// +// if( changes ) +// { +// Mod.Data.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( Mod.Data.BasePath ) ); +// Mod.Data.Resources.SetManipulations( Meta, Mod.Data.BasePath, false ); +// _selector.ReloadCurrentMod( true, false ); +// } +// } +// +// public void Draw( bool editMode ) +// { +// _editMode = editMode; +// if( !ImGui.BeginTabBar( LabelPluginDetails ) ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabBar ); +// DrawAboutTab(); +// DrawChangedItemsTab(); +// +// DrawConfigurationTab(); +// if( _editMode ) +// { +// DrawFileListTabEdit(); +// } +// else +// { +// DrawFileListTab(); +// } +// +// DrawFileSwapTab(); +// DrawMetaManipulationsTab(); +// DrawConflictTab(); +// } +// } +//} \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.ModsTab.DetailsEdit.cs b/Penumbra/UI/ConfigWindow.ModsTab.DetailsEdit.cs new file mode 100644 index 00000000..a6024185 --- /dev/null +++ b/Penumbra/UI/ConfigWindow.ModsTab.DetailsEdit.cs @@ -0,0 +1,381 @@ +//using System.Collections.Generic; +//using System.Linq; +//using System.Numerics; +//using Dalamud.Interface; +//using FFXIVClientStructs.FFXIV.Client.UI.Misc; +//using ImGuiNET; +//using Penumbra.GameData.ByteString; +//using Penumbra.GameData.Util; +//using Penumbra.Mods; +//using Penumbra.UI.Custom; +//using Penumbra.Util; +// +//namespace Penumbra.UI; +// +//public partial class SettingsInterface +//{ +// private partial class PluginDetails +// { +// private const string LabelDescEdit = "##descedit"; +// private const string LabelNewSingleGroupEdit = "##newSingleGroup"; +// private const string LabelNewMultiGroup = "##newMultiGroup"; +// private const string LabelGamePathsEditBox = "##gamePathsEdit"; +// private const string ButtonAddToGroup = "Add to Group"; +// private const string ButtonRemoveFromGroup = "Remove from Group"; +// private const string TooltipAboutEdit = "Use Ctrl+Enter for newlines."; +// private const string TextNoOptionAvailable = "[Not Available]"; +// private const string TextDefaultGamePath = "default"; +// private static readonly Utf8String DefaultUtf8GamePath = Utf8String.FromStringUnsafe( TextDefaultGamePath, true ); +// private const char GamePathsSeparator = ';'; +// +// private static readonly string TooltipFilesTabEdit = +// $"{TooltipFilesTab}\n" +// + $"Red Files are replaced in another group or a different option in this group, but not contained in the current option."; +// +// private static readonly string TooltipGamePathsEdit = +// $"Enter all game paths to add or remove, separated by '{GamePathsSeparator}'.\n" +// + $"Use '{TextDefaultGamePath}' to add the original file path." +// + $"Use '{TextDefaultGamePath}-#' to skip the first # relative directories."; +// +// private const float MultiEditBoxWidth = 300f; +// +// private bool DrawEditGroupSelector() +// { +// ImGui.SetNextItemWidth( OptionSelectionWidth * ImGuiHelpers.GlobalScale ); +// if( Meta!.Groups.Count == 0 ) +// { +// ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex, TextNoOptionAvailable, 1 ); +// return false; +// } +// +// if( ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex +// , Meta.Groups.Values.Select( g => g.GroupName ).ToArray() +// , Meta.Groups.Count ) ) +// { +// SelectGroup(); +// SelectOption( 0 ); +// } +// +// return true; +// } +// +// private bool DrawEditOptionSelector() +// { +// ImGui.SameLine(); +// ImGui.SetNextItemWidth( OptionSelectionWidth ); +// if( ( _selectedGroup?.Options.Count ?? 0 ) == 0 ) +// { +// ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, TextNoOptionAvailable, 1 ); +// return false; +// } +// +// var group = ( OptionGroup )_selectedGroup!; +// if( ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, group.Options.Select( o => o.OptionName ).ToArray(), +// group.Options.Count ) ) +// { +// SelectOption(); +// } +// +// return true; +// } +// +// private void DrawFileListTabEdit() +// { +// if( ImGui.BeginTabItem( LabelFileListTab ) ) +// { +// UpdateFilenameList(); +// if( ImGui.IsItemHovered() ) +// { +// ImGui.SetTooltip( _editMode ? TooltipFilesTabEdit : TooltipFilesTab ); +// } +// +// ImGui.SetNextItemWidth( -1 ); +// if( ImGui.BeginListBox( LabelFileListHeader, AutoFillSize - Vector2.UnitY * 1.5f * ImGui.GetTextLineHeight() ) ) +// { +// for( var i = 0; i < Mod!.Data.Resources.ModFiles.Count; ++i ) +// { +// DrawFileAndGamePaths( i ); +// } +// } +// +// ImGui.EndListBox(); +// +// DrawGroupRow(); +// ImGui.EndTabItem(); +// } +// else +// { +// _fullFilenameList = null; +// } +// } +// +// private ImGuiRaii.EndStack DrawMultiSelectorEditBegin( OptionGroup group ) +// { +// var groupName = group.GroupName; +// if( ImGuiCustom.BeginFramedGroupEdit( ref groupName ) ) +// { +// if( Penumbra.ModManager.ChangeModGroup( group.GroupName, groupName, Mod.Data ) && Mod.Data.Meta.RefreshHasGroupsWithConfig() ) +// { +// _selector.Cache.TriggerFilterReset(); +// } +// } +// +// return ImGuiRaii.DeferredEnd( ImGuiCustom.EndFramedGroup ); +// } +// +// private void DrawMultiSelectorEditAdd( OptionGroup group, float nameBoxStart ) +// { +// var newOption = ""; +// ImGui.SetCursorPosX( nameBoxStart ); +// ImGui.SetNextItemWidth( MultiEditBoxWidth * ImGuiHelpers.GlobalScale ); +// if( ImGui.InputTextWithHint( $"##new_{group.GroupName}_l", "Add new option...", ref newOption, 64, +// ImGuiInputTextFlags.EnterReturnsTrue ) +// && newOption.Length != 0 ) +// { +// group.Options.Add( new ConfigModule.Option() +// { OptionName = newOption, OptionDesc = "", OptionFiles = new Dictionary< Utf8RelPath, HashSet< Utf8GamePath > >() } ); +// _selector.SaveCurrentMod(); +// if( Mod!.Data.Meta.RefreshHasGroupsWithConfig() ) +// { +// _selector.Cache.TriggerFilterReset(); +// } +// } +// } +// +// private void DrawMultiSelectorEdit( OptionGroup group ) +// { +// var nameBoxStart = CheckMarkSize; +// var flag = Mod!.Settings.Settings[ group.GroupName ]; +// +// using var raii = DrawMultiSelectorEditBegin( group ); +// for( var i = 0; i < group.Options.Count; ++i ) +// { +// var opt = group.Options[ i ]; +// var label = $"##{group.GroupName}_{i}"; +// DrawMultiSelectorCheckBox( group, i, flag, label ); +// +// ImGui.SameLine(); +// var newName = opt.OptionName; +// +// if( nameBoxStart == CheckMarkSize ) +// { +// nameBoxStart = ImGui.GetCursorPosX(); +// } +// +// ImGui.SetNextItemWidth( MultiEditBoxWidth * ImGuiHelpers.GlobalScale ); +// if( ImGui.InputText( $"{label}_l", ref newName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) ) +// { +// if( newName.Length == 0 ) +// { +// Penumbra.ModManager.RemoveModOption( i, group, Mod.Data ); +// } +// else if( newName != opt.OptionName ) +// { +// group.Options[ i ] = new ConfigModule.Option() +// { OptionName = newName, OptionDesc = opt.OptionDesc, OptionFiles = opt.OptionFiles }; +// _selector.SaveCurrentMod(); +// } +// +// if( Mod!.Data.Meta.RefreshHasGroupsWithConfig() ) +// { +// _selector.Cache.TriggerFilterReset(); +// } +// } +// } +// +// DrawMultiSelectorEditAdd( group, nameBoxStart ); +// } +// +// private void DrawSingleSelectorEditGroup( OptionGroup group ) +// { +// var groupName = group.GroupName; +// if( ImGui.InputText( $"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) ) +// { +// if( Penumbra.ModManager.ChangeModGroup( group.GroupName, groupName, Mod.Data ) && Mod.Data.Meta.RefreshHasGroupsWithConfig() ) +// { +// _selector.Cache.TriggerFilterReset(); +// } +// } +// } +// +// private float DrawSingleSelectorEdit( OptionGroup group ) +// { +// var oldSetting = Mod!.Settings.Settings[ group.GroupName ]; +// var code = oldSetting; +// if( ImGuiCustom.RenameableCombo( $"##{group.GroupName}", ref code, out var newName, +// group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) ) +// { +// if( code == group.Options.Count ) +// { +// if( newName.Length > 0 ) +// { +// Penumbra.CollectionManager.Current.SetModSetting(Mod.Data.Index, group.GroupName, code); +// group.Options.Add( new ConfigModule.Option() +// { +// OptionName = newName, +// OptionDesc = "", +// OptionFiles = new Dictionary< Utf8RelPath, HashSet< Utf8GamePath > >(), +// } ); +// _selector.SaveCurrentMod(); +// } +// } +// else +// { +// if( newName.Length == 0 ) +// { +// Penumbra.ModManager.RemoveModOption( code, group, Mod.Data ); +// } +// else +// { +// if( newName != group.Options[ code ].OptionName ) +// { +// group.Options[ code ] = new ConfigModule.Option() +// { +// OptionName = newName, OptionDesc = group.Options[ code ].OptionDesc, +// OptionFiles = group.Options[ code ].OptionFiles, +// }; +// _selector.SaveCurrentMod(); +// } +// } +// } +// +// if( Mod.Data.Meta.RefreshHasGroupsWithConfig() ) +// { +// _selector.Cache.TriggerFilterReset(); +// } +// } +// +// ImGui.SameLine(); +// var labelEditPos = ImGui.GetCursorPosX(); +// DrawSingleSelectorEditGroup( group ); +// +// return labelEditPos; +// } +// +// private void DrawAddSingleGroupField( float labelEditPos ) +// { +// var newGroup = ""; +// ImGui.SetCursorPosX( labelEditPos ); +// if( labelEditPos == CheckMarkSize ) +// { +// ImGui.SetNextItemWidth( MultiEditBoxWidth * ImGuiHelpers.GlobalScale ); +// } +// +// if( ImGui.InputTextWithHint( LabelNewSingleGroupEdit, "Add new Single Group...", ref newGroup, 64, +// ImGuiInputTextFlags.EnterReturnsTrue ) ) +// { +// Penumbra.ModManager.ChangeModGroup( "", newGroup, Mod.Data, SelectType.Single ); +// // Adds empty group, so can not change filters. +// } +// } +// +// private void DrawAddMultiGroupField() +// { +// var newGroup = ""; +// ImGui.SetCursorPosX( CheckMarkSize ); +// ImGui.SetNextItemWidth( MultiEditBoxWidth * ImGuiHelpers.GlobalScale ); +// if( ImGui.InputTextWithHint( LabelNewMultiGroup, "Add new Multi Group...", ref newGroup, 64, +// ImGuiInputTextFlags.EnterReturnsTrue ) ) +// { +// Penumbra.ModManager.ChangeModGroup( "", newGroup, Mod.Data, SelectType.Multi ); +// // Adds empty group, so can not change filters. +// } +// } +// +// private void DrawGroupSelectorsEdit() +// { +// var labelEditPos = CheckMarkSize; +// var groups = Meta.Groups.Values.ToArray(); +// foreach( var g in groups.Where( g => g.SelectionType == SelectType.Single ) ) +// { +// labelEditPos = DrawSingleSelectorEdit( g ); +// } +// +// DrawAddSingleGroupField( labelEditPos ); +// +// foreach( var g in groups.Where( g => g.SelectionType == SelectType.Multi ) ) +// { +// DrawMultiSelectorEdit( g ); +// } +// +// DrawAddMultiGroupField(); +// } +// +// private void DrawFileSwapTabEdit() +// { +// if( !ImGui.BeginTabItem( LabelFileSwapTab ) ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); +// +// ImGui.SetNextItemWidth( -1 ); +// if( !ImGui.BeginListBox( LabelFileSwapHeader, AutoFillSize ) ) +// { +// return; +// } +// +// raii.Push( ImGui.EndListBox ); +// +// var swaps = Meta.FileSwaps.Keys.ToArray(); +// +// ImGui.PushFont( UiBuilder.IconFont ); +// var arrowWidth = ImGui.CalcTextSize( FontAwesomeIcon.LongArrowAltRight.ToIconString() ).X; +// ImGui.PopFont(); +// +// var width = ( ImGui.GetWindowWidth() - arrowWidth - 4 * ImGui.GetStyle().ItemSpacing.X ) / 2; +// for( var idx = 0; idx < swaps.Length + 1; ++idx ) +// { +// var key = idx == swaps.Length ? Utf8GamePath.Empty : swaps[ idx ]; +// var value = idx == swaps.Length ? FullPath.Empty : Meta.FileSwaps[ key ]; +// var keyString = key.ToString(); +// var valueString = value.ToString(); +// +// ImGui.SetNextItemWidth( width ); +// if( ImGui.InputTextWithHint( $"##swapLhs_{idx}", "Enter new file to be replaced...", ref keyString, +// GamePath.MaxGamePathLength, ImGuiInputTextFlags.EnterReturnsTrue ) ) +// { +// if( Utf8GamePath.FromString( keyString, out var newKey, true ) && newKey.CompareTo( key ) != 0 ) +// { +// if( idx < swaps.Length ) +// { +// Meta.FileSwaps.Remove( key ); +// } +// +// if( !newKey.IsEmpty ) +// { +// Meta.FileSwaps[ newKey ] = value; +// } +// +// _selector.SaveCurrentMod(); +// _selector.ReloadCurrentMod(); +// } +// } +// +// if( idx >= swaps.Length ) +// { +// continue; +// } +// +// ImGui.SameLine(); +// ImGuiCustom.PrintIcon( FontAwesomeIcon.LongArrowAltRight ); +// ImGui.SameLine(); +// +// ImGui.SetNextItemWidth( width ); +// if( ImGui.InputTextWithHint( $"##swapRhs_{idx}", "Enter new replacement path...", ref valueString, +// GamePath.MaxGamePathLength, +// ImGuiInputTextFlags.EnterReturnsTrue ) ) +// { +// var newValue = new FullPath( valueString.ToLowerInvariant() ); +// if( newValue.CompareTo( value ) != 0 ) +// { +// Meta.FileSwaps[ key ] = newValue; +// _selector.SaveCurrentMod(); +// _selector.Cache.TriggerListReset(); +// } +// } +// } +// } +// } +//} \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.ModsTab.DetailsManipulation.cs b/Penumbra/UI/ConfigWindow.ModsTab.DetailsManipulation.cs new file mode 100644 index 00000000..f1b63d5a --- /dev/null +++ b/Penumbra/UI/ConfigWindow.ModsTab.DetailsManipulation.cs @@ -0,0 +1,773 @@ +//using System; +//using System.Collections.Generic; +//using System.ComponentModel; +//using System.Linq; +//using System.Numerics; +//using Dalamud.Interface; +//using ImGuiNET; +//using Penumbra.GameData.Enums; +//using Penumbra.GameData.Structs; +//using Penumbra.Meta.Files; +//using Penumbra.Meta.Manipulations; +//using Penumbra.UI.Custom; +//using ObjectType = Penumbra.GameData.Enums.ObjectType; +// +//namespace Penumbra.UI; +// +//public partial class SettingsInterface +//{ +// private partial class PluginDetails +// { +// private int _newManipTypeIdx = 0; +// private ushort _newManipSetId = 0; +// private ushort _newManipSecondaryId = 0; +// private int _newManipSubrace = 0; +// private int _newManipRace = 0; +// private int _newManipAttribute = 0; +// private int _newManipEquipSlot = 0; +// private int _newManipObjectType = 0; +// private int _newManipGender = 0; +// private int _newManipBodySlot = 0; +// private ushort _newManipVariant = 0; +// +// +// private static readonly (string, EquipSlot)[] EqpEquipSlots = +// { +// ( "Head", EquipSlot.Head ), +// ( "Body", EquipSlot.Body ), +// ( "Hands", EquipSlot.Hands ), +// ( "Legs", EquipSlot.Legs ), +// ( "Feet", EquipSlot.Feet ), +// }; +// +// private static readonly (string, EquipSlot)[] EqdpEquipSlots = +// { +// EqpEquipSlots[ 0 ], +// EqpEquipSlots[ 1 ], +// EqpEquipSlots[ 2 ], +// EqpEquipSlots[ 3 ], +// EqpEquipSlots[ 4 ], +// ( "Ears", EquipSlot.Ears ), +// ( "Neck", EquipSlot.Neck ), +// ( "Wrist", EquipSlot.Wrists ), +// ( "Left Finger", EquipSlot.LFinger ), +// ( "Right Finger", EquipSlot.RFinger ), +// }; +// +// private static readonly (string, ModelRace)[] Races = +// { +// ( ModelRace.Midlander.ToName(), ModelRace.Midlander ), +// ( ModelRace.Highlander.ToName(), ModelRace.Highlander ), +// ( ModelRace.Elezen.ToName(), ModelRace.Elezen ), +// ( ModelRace.Miqote.ToName(), ModelRace.Miqote ), +// ( ModelRace.Roegadyn.ToName(), ModelRace.Roegadyn ), +// ( ModelRace.Lalafell.ToName(), ModelRace.Lalafell ), +// ( ModelRace.AuRa.ToName(), ModelRace.AuRa ), +// ( ModelRace.Viera.ToName(), ModelRace.Viera ), +// ( ModelRace.Hrothgar.ToName(), ModelRace.Hrothgar ), +// }; +// +// private static readonly (string, Gender)[] Genders = +// { +// ( Gender.Male.ToName(), Gender.Male ), +// ( Gender.Female.ToName(), Gender.Female ), +// ( Gender.MaleNpc.ToName(), Gender.MaleNpc ), +// ( Gender.FemaleNpc.ToName(), Gender.FemaleNpc ), +// }; +// +// private static readonly (string, EstManipulation.EstType)[] EstTypes = +// { +// ( "Hair", EstManipulation.EstType.Hair ), +// ( "Face", EstManipulation.EstType.Face ), +// ( "Body", EstManipulation.EstType.Body ), +// ( "Head", EstManipulation.EstType.Head ), +// }; +// +// private static readonly (string, SubRace)[] Subraces = +// { +// ( SubRace.Midlander.ToName(), SubRace.Midlander ), +// ( SubRace.Highlander.ToName(), SubRace.Highlander ), +// ( SubRace.Wildwood.ToName(), SubRace.Wildwood ), +// ( SubRace.Duskwight.ToName(), SubRace.Duskwight ), +// ( SubRace.SeekerOfTheSun.ToName(), SubRace.SeekerOfTheSun ), +// ( SubRace.KeeperOfTheMoon.ToName(), SubRace.KeeperOfTheMoon ), +// ( SubRace.Seawolf.ToName(), SubRace.Seawolf ), +// ( SubRace.Hellsguard.ToName(), SubRace.Hellsguard ), +// ( SubRace.Plainsfolk.ToName(), SubRace.Plainsfolk ), +// ( SubRace.Dunesfolk.ToName(), SubRace.Dunesfolk ), +// ( SubRace.Raen.ToName(), SubRace.Raen ), +// ( SubRace.Xaela.ToName(), SubRace.Xaela ), +// ( SubRace.Rava.ToName(), SubRace.Rava ), +// ( SubRace.Veena.ToName(), SubRace.Veena ), +// ( SubRace.Helion.ToName(), SubRace.Helion ), +// ( SubRace.Lost.ToName(), SubRace.Lost ), +// }; +// +// private static readonly (string, RspAttribute)[] RspAttributes = +// { +// ( RspAttribute.MaleMinSize.ToFullString(), RspAttribute.MaleMinSize ), +// ( RspAttribute.MaleMaxSize.ToFullString(), RspAttribute.MaleMaxSize ), +// ( RspAttribute.FemaleMinSize.ToFullString(), RspAttribute.FemaleMinSize ), +// ( RspAttribute.FemaleMaxSize.ToFullString(), RspAttribute.FemaleMaxSize ), +// ( RspAttribute.BustMinX.ToFullString(), RspAttribute.BustMinX ), +// ( RspAttribute.BustMaxX.ToFullString(), RspAttribute.BustMaxX ), +// ( RspAttribute.BustMinY.ToFullString(), RspAttribute.BustMinY ), +// ( RspAttribute.BustMaxY.ToFullString(), RspAttribute.BustMaxY ), +// ( RspAttribute.BustMinZ.ToFullString(), RspAttribute.BustMinZ ), +// ( RspAttribute.BustMaxZ.ToFullString(), RspAttribute.BustMaxZ ), +// ( RspAttribute.MaleMinTail.ToFullString(), RspAttribute.MaleMinTail ), +// ( RspAttribute.MaleMaxTail.ToFullString(), RspAttribute.MaleMaxTail ), +// ( RspAttribute.FemaleMinTail.ToFullString(), RspAttribute.FemaleMinTail ), +// ( RspAttribute.FemaleMaxTail.ToFullString(), RspAttribute.FemaleMaxTail ), +// }; +// +// +// private static readonly (string, ObjectType)[] ImcObjectType = +// { +// ( "Equipment", ObjectType.Equipment ), +// ( "Customization", ObjectType.Character ), +// ( "Weapon", ObjectType.Weapon ), +// ( "Demihuman", ObjectType.DemiHuman ), +// ( "Monster", ObjectType.Monster ), +// }; +// +// private static readonly (string, BodySlot)[] ImcBodySlots = +// { +// ( "Hair", BodySlot.Hair ), +// ( "Face", BodySlot.Face ), +// ( "Body", BodySlot.Body ), +// ( "Tail", BodySlot.Tail ), +// ( "Ears", BodySlot.Zear ), +// }; +// +// private static bool PrintCheckBox( string name, ref bool value, bool def ) +// { +// var color = value == def ? 0 : value ? ColorDarkGreen : ColorDarkRed; +// if( color == 0 ) +// { +// return ImGui.Checkbox( name, ref value ); +// } +// +// using var colorRaii = ImGuiRaii.PushColor( ImGuiCol.Text, color ); +// var ret = ImGui.Checkbox( name, ref value ); +// return ret; +// } +// +// private bool RestrictedInputInt( string name, ref ushort value, ushort min, ushort max ) +// { +// int tmp = value; +// if( ImGui.InputInt( name, ref tmp, 0, 0, _editMode ? ImGuiInputTextFlags.EnterReturnsTrue : ImGuiInputTextFlags.ReadOnly ) +// && tmp != value +// && tmp >= min +// && tmp <= max ) +// { +// value = ( ushort )tmp; +// return true; +// } +// +// return false; +// } +// +// private static bool DefaultButton< T >( string name, ref T value, T defaultValue ) where T : IComparable< T > +// { +// var compare = defaultValue.CompareTo( value ); +// var color = compare < 0 ? ColorDarkGreen : +// compare > 0 ? ColorDarkRed : ImGui.ColorConvertFloat4ToU32( ImGui.GetStyle().Colors[ ( int )ImGuiCol.Button ] ); +// +// using var colorRaii = ImGuiRaii.PushColor( ImGuiCol.Button, color ); +// var ret = ImGui.Button( name, Vector2.UnitX * 120 ) && compare != 0; +// ImGui.SameLine(); +// return ret; +// } +// +// private bool DrawInputWithDefault( string name, ref ushort value, ushort defaultValue, ushort max ) +// => DefaultButton( $"{( _editMode ? "Set to " : "" )}Default: {defaultValue}##imc{name}", ref value, defaultValue ) +// || RestrictedInputInt( name, ref value, 0, max ); +// +// private static bool CustomCombo< T >( string label, IList< (string, T) > namesAndValues, out T value, ref int idx ) +// { +// value = idx < namesAndValues.Count ? namesAndValues[ idx ].Item2 : default!; +// +// if( !ImGui.BeginCombo( label, idx < namesAndValues.Count ? namesAndValues[ idx ].Item1 : string.Empty ) ) +// { +// return false; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndCombo ); +// +// for( var i = 0; i < namesAndValues.Count; ++i ) +// { +// if( !ImGui.Selectable( $"{namesAndValues[ i ].Item1}##{label}{i}", idx == i ) || idx == i ) +// { +// continue; +// } +// +// idx = i; +// value = namesAndValues[ i ].Item2; +// return true; +// } +// +// return false; +// } +// +// private bool DrawEqpRow( int manipIdx, IList< MetaManipulation > list ) +// { +// var ret = false; +// var id = list[ manipIdx ].Eqp; +// var val = id.Entry; +// +// +// if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) +// { +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); +// var defaults = ExpandedEqpFile.GetDefault( id.SetId ); +// var attributes = Eqp.EqpAttributes[ id.Slot ]; +// +// foreach( var flag in attributes ) +// { +// var name = flag.ToLocalName(); +// var tmp = val.HasFlag( flag ); +// if( PrintCheckBox( $"{name}##manip", ref tmp, defaults.HasFlag( flag ) ) && _editMode && tmp != val.HasFlag( flag ) ) +// { +// list[ manipIdx ] = new MetaManipulation( new EqpManipulation( tmp ? val | flag : val & ~flag, id.Slot, id.SetId ) ); +// ret = true; +// } +// } +// } +// +// ImGui.Text( ObjectType.Equipment.ToString() ); +// ImGui.TableNextColumn(); +// ImGui.Text( id.SetId.ToString() ); +// ImGui.TableNextColumn(); +// ImGui.Text( id.Slot.ToString() ); +// return ret; +// } +// +// private bool DrawGmpRow( int manipIdx, IList< MetaManipulation > list ) +// { +// var ret = false; +// var id = list[ manipIdx ].Gmp; +// var val = id.Entry; +// +// if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) +// { +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); +// var defaults = ExpandedGmpFile.GetDefault( id.SetId ); +// var enabled = val.Enabled; +// var animated = val.Animated; +// var rotationA = val.RotationA; +// var rotationB = val.RotationB; +// var rotationC = val.RotationC; +// ushort unk = val.UnknownTotal; +// +// ret |= PrintCheckBox( "Visor Enabled##manip", ref enabled, defaults.Enabled ) && enabled != val.Enabled; +// ret |= PrintCheckBox( "Visor Animated##manip", ref animated, defaults.Animated ); +// ret |= DrawInputWithDefault( "Rotation A##manip", ref rotationA, defaults.RotationA, 0x3FF ); +// ret |= DrawInputWithDefault( "Rotation B##manip", ref rotationB, defaults.RotationB, 0x3FF ); +// ret |= DrawInputWithDefault( "Rotation C##manip", ref rotationC, defaults.RotationC, 0x3FF ); +// ret |= DrawInputWithDefault( "Unknown Byte##manip", ref unk, defaults.UnknownTotal, 0xFF ); +// +// if( ret && _editMode ) +// { +// list[ manipIdx ] = new MetaManipulation( new GmpManipulation( new GmpEntry +// { +// Animated = animated, Enabled = enabled, UnknownTotal = ( byte )unk, +// RotationA = rotationA, RotationB = rotationB, RotationC = rotationC, +// }, id.SetId ) ); +// } +// } +// +// ImGui.Text( ObjectType.Equipment.ToString() ); +// ImGui.TableNextColumn(); +// ImGui.Text( id.SetId.ToString() ); +// ImGui.TableNextColumn(); +// ImGui.Text( EquipSlot.Head.ToString() ); +// return ret; +// } +// +// private static (bool, bool) GetEqdpBits( EquipSlot slot, EqdpEntry entry ) +// { +// return slot switch +// { +// EquipSlot.Head => ( entry.HasFlag( EqdpEntry.Head1 ), entry.HasFlag( EqdpEntry.Head2 ) ), +// EquipSlot.Body => ( entry.HasFlag( EqdpEntry.Body1 ), entry.HasFlag( EqdpEntry.Body2 ) ), +// EquipSlot.Hands => ( entry.HasFlag( EqdpEntry.Hands1 ), entry.HasFlag( EqdpEntry.Hands2 ) ), +// EquipSlot.Legs => ( entry.HasFlag( EqdpEntry.Legs1 ), entry.HasFlag( EqdpEntry.Legs2 ) ), +// EquipSlot.Feet => ( entry.HasFlag( EqdpEntry.Feet1 ), entry.HasFlag( EqdpEntry.Feet2 ) ), +// EquipSlot.Neck => ( entry.HasFlag( EqdpEntry.Neck1 ), entry.HasFlag( EqdpEntry.Neck2 ) ), +// EquipSlot.Ears => ( entry.HasFlag( EqdpEntry.Ears1 ), entry.HasFlag( EqdpEntry.Ears2 ) ), +// EquipSlot.Wrists => ( entry.HasFlag( EqdpEntry.Wrists1 ), entry.HasFlag( EqdpEntry.Wrists2 ) ), +// EquipSlot.RFinger => ( entry.HasFlag( EqdpEntry.RingR1 ), entry.HasFlag( EqdpEntry.RingR2 ) ), +// EquipSlot.LFinger => ( entry.HasFlag( EqdpEntry.RingL1 ), entry.HasFlag( EqdpEntry.RingL2 ) ), +// _ => ( false, false ), +// }; +// } +// +// private static EqdpEntry SetEqdpBits( EquipSlot slot, EqdpEntry value, bool bit1, bool bit2 ) +// { +// switch( slot ) +// { +// case EquipSlot.Head: +// value = bit1 ? value | EqdpEntry.Head1 : value & ~EqdpEntry.Head1; +// value = bit2 ? value | EqdpEntry.Head2 : value & ~EqdpEntry.Head2; +// return value; +// case EquipSlot.Body: +// value = bit1 ? value | EqdpEntry.Body1 : value & ~EqdpEntry.Body1; +// value = bit2 ? value | EqdpEntry.Body2 : value & ~EqdpEntry.Body2; +// return value; +// case EquipSlot.Hands: +// value = bit1 ? value | EqdpEntry.Hands1 : value & ~EqdpEntry.Hands1; +// value = bit2 ? value | EqdpEntry.Hands2 : value & ~EqdpEntry.Hands2; +// return value; +// case EquipSlot.Legs: +// value = bit1 ? value | EqdpEntry.Legs1 : value & ~EqdpEntry.Legs1; +// value = bit2 ? value | EqdpEntry.Legs2 : value & ~EqdpEntry.Legs2; +// return value; +// case EquipSlot.Feet: +// value = bit1 ? value | EqdpEntry.Feet1 : value & ~EqdpEntry.Feet1; +// value = bit2 ? value | EqdpEntry.Feet2 : value & ~EqdpEntry.Feet2; +// return value; +// case EquipSlot.Neck: +// value = bit1 ? value | EqdpEntry.Neck1 : value & ~EqdpEntry.Neck1; +// value = bit2 ? value | EqdpEntry.Neck2 : value & ~EqdpEntry.Neck2; +// return value; +// case EquipSlot.Ears: +// value = bit1 ? value | EqdpEntry.Ears1 : value & ~EqdpEntry.Ears1; +// value = bit2 ? value | EqdpEntry.Ears2 : value & ~EqdpEntry.Ears2; +// return value; +// case EquipSlot.Wrists: +// value = bit1 ? value | EqdpEntry.Wrists1 : value & ~EqdpEntry.Wrists1; +// value = bit2 ? value | EqdpEntry.Wrists2 : value & ~EqdpEntry.Wrists2; +// return value; +// case EquipSlot.RFinger: +// value = bit1 ? value | EqdpEntry.RingR1 : value & ~EqdpEntry.RingR1; +// value = bit2 ? value | EqdpEntry.RingR2 : value & ~EqdpEntry.RingR2; +// return value; +// case EquipSlot.LFinger: +// value = bit1 ? value | EqdpEntry.RingL1 : value & ~EqdpEntry.RingL1; +// value = bit2 ? value | EqdpEntry.RingL2 : value & ~EqdpEntry.RingL2; +// return value; +// } +// +// return value; +// } +// +// private bool DrawEqdpRow( int manipIdx, IList< MetaManipulation > list ) +// { +// var ret = false; +// var id = list[ manipIdx ].Eqdp; +// var val = id.Entry; +// +// if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) +// { +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); +// var defaults = ExpandedEqdpFile.GetDefault( id.FileIndex(), id.SetId ); +// var (bit1, bit2) = GetEqdpBits( id.Slot, val ); +// var (defBit1, defBit2) = GetEqdpBits( id.Slot, defaults ); +// +// ret |= PrintCheckBox( "Bit 1##manip", ref bit1, defBit1 ); +// ret |= PrintCheckBox( "Bit 2##manip", ref bit2, defBit2 ); +// +// if( ret && _editMode ) +// { +// list[ manipIdx ] = new MetaManipulation( new EqdpManipulation( SetEqdpBits( id.Slot, val, bit1, bit2 ), id.Slot, id.Gender, +// id.Race, id.SetId ) ); +// } +// } +// +// ImGui.Text( id.Slot.IsAccessory() +// ? ObjectType.Accessory.ToString() +// : ObjectType.Equipment.ToString() ); +// ImGui.TableNextColumn(); +// ImGui.Text( id.SetId.ToString() ); +// ImGui.TableNextColumn(); +// ImGui.Text( id.Slot.ToString() ); +// ImGui.TableNextColumn(); +// ImGui.Text( id.Race.ToString() ); +// ImGui.TableNextColumn(); +// ImGui.Text( id.Gender.ToString() ); +// return ret; +// } +// +// private bool DrawEstRow( int manipIdx, IList< MetaManipulation > list ) +// { +// var ret = false; +// var id = list[ manipIdx ].Est; +// var val = id.Entry; +// +// if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) +// { +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); +// var defaults = EstFile.GetDefault( id.Slot, Names.CombinedRace( id.Gender, id.Race ), id.SetId ); +// if( DrawInputWithDefault( "No Idea what this does!##manip", ref val, defaults, ushort.MaxValue ) && _editMode ) +// { +// list[ manipIdx ] = new MetaManipulation( new EstManipulation( id.Gender, id.Race, id.Slot, id.SetId, val ) ); +// ret = true; +// } +// } +// +// ImGui.Text( id.Slot.ToString() ); +// ImGui.TableNextColumn(); +// ImGui.Text( id.SetId.ToString() ); +// ImGui.TableNextColumn(); +// ImGui.TableNextColumn(); +// ImGui.Text( id.Race.ToName() ); +// ImGui.TableNextColumn(); +// ImGui.Text( id.Gender.ToName() ); +// +// return ret; +// } +// +// private bool DrawImcRow( int manipIdx, IList< MetaManipulation > list ) +// { +// var ret = false; +// var id = list[ manipIdx ].Imc; +// var val = id.Entry; +// +// if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) +// { +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); +// var defaults = new ImcFile( id.GamePath() ).GetEntry( ImcFile.PartIndex( id.EquipSlot ), id.Variant ); +// ushort materialId = val.MaterialId; +// ushort vfxId = val.VfxId; +// ushort decalId = val.DecalId; +// var soundId = ( ushort )val.SoundId; +// var attributeMask = val.AttributeMask; +// var materialAnimationId = ( ushort )val.MaterialAnimationId; +// ret |= DrawInputWithDefault( "Material Id", ref materialId, defaults.MaterialId, byte.MaxValue ); +// ret |= DrawInputWithDefault( "Vfx Id", ref vfxId, defaults.VfxId, byte.MaxValue ); +// ret |= DrawInputWithDefault( "Decal Id", ref decalId, defaults.DecalId, byte.MaxValue ); +// ret |= DrawInputWithDefault( "Sound Id", ref soundId, defaults.SoundId, 0x3F ); +// ret |= DrawInputWithDefault( "Attribute Mask", ref attributeMask, defaults.AttributeMask, 0x3FF ); +// ret |= DrawInputWithDefault( "Material Animation Id", ref materialAnimationId, defaults.MaterialAnimationId, +// byte.MaxValue ); +// +// if( ret && _editMode ) +// { +// var value = new ImcEntry( ( byte )materialId, ( byte )decalId, attributeMask, ( byte )soundId, ( byte )vfxId, +// ( byte )materialAnimationId ); +// list[ manipIdx ] = new MetaManipulation( new ImcManipulation( id, value ) ); +// } +// } +// +// ImGui.Text( id.ObjectType.ToString() ); +// ImGui.TableNextColumn(); +// ImGui.Text( id.PrimaryId.ToString() ); +// ImGui.TableNextColumn(); +// if( id.ObjectType is ObjectType.Accessory or ObjectType.Equipment ) +// { +// ImGui.Text( id.ObjectType is ObjectType.Equipment or ObjectType.Accessory +// ? id.EquipSlot.ToString() +// : id.BodySlot.ToString() ); +// } +// +// ImGui.TableNextColumn(); +// ImGui.TableNextColumn(); +// ImGui.TableNextColumn(); +// if( id.ObjectType != ObjectType.Equipment +// && id.ObjectType != ObjectType.Accessory ) +// { +// ImGui.Text( id.SecondaryId.ToString() ); +// } +// +// ImGui.TableNextColumn(); +// ImGui.Text( id.Variant.ToString() ); +// return ret; +// } +// +// private bool DrawRspRow( int manipIdx, IList< MetaManipulation > list ) +// { +// var ret = false; +// var id = list[ manipIdx ].Rsp; +// var defaults = CmpFile.GetDefault( id.SubRace, id.Attribute ); +// var val = id.Entry; +// if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) +// { +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); +// if( DefaultButton( +// $"{( _editMode ? "Set to " : "" )}Default: {defaults:F3}##scaleManip", ref val, defaults ) +// && _editMode ) +// { +// list[ manipIdx ] = new MetaManipulation( new RspManipulation( id.SubRace, id.Attribute, val ) ); +// ret = true; +// } +// +// ImGui.SetNextItemWidth( 50 * ImGuiHelpers.GlobalScale ); +// if( ImGui.InputFloat( "Scale###manip", ref val, 0, 0, "%.3f", +// _editMode ? ImGuiInputTextFlags.EnterReturnsTrue : ImGuiInputTextFlags.ReadOnly ) +// && val >= 0 +// && val <= 5 +// && _editMode ) +// { +// list[ manipIdx ] = new MetaManipulation( new RspManipulation( id.SubRace, id.Attribute, val ) ); +// ret = true; +// } +// } +// +// ImGui.Text( id.Attribute.ToUngenderedString() ); +// ImGui.TableNextColumn(); +// ImGui.TableNextColumn(); +// ImGui.TableNextColumn(); +// ImGui.Text( id.SubRace.ToString() ); +// ImGui.TableNextColumn(); +// ImGui.Text( id.Attribute.ToGender().ToString() ); +// return ret; +// } +// +// private bool DrawManipulationRow( ref int manipIdx, IList< MetaManipulation > list, ref int count ) +// { +// var type = list[ manipIdx ].ManipulationType; +// +// if( _editMode ) +// { +// ImGui.TableNextColumn(); +// using var font = ImGuiRaii.PushFont( UiBuilder.IconFont ); +// if( ImGui.Button( $"{FontAwesomeIcon.Trash.ToIconString()}##manipDelete{manipIdx}" ) ) +// { +// list.RemoveAt( manipIdx ); +// ImGui.TableNextRow(); +// --manipIdx; +// --count; +// return true; +// } +// } +// +// ImGui.TableNextColumn(); +// ImGui.Text( type.ToString() ); +// ImGui.TableNextColumn(); +// +// var changes = false; +// switch( type ) +// { +// case MetaManipulation.Type.Eqp: +// changes = DrawEqpRow( manipIdx, list ); +// ImGui.TableSetColumnIndex( 9 ); +// if( ImGui.Selectable( $"{list[ manipIdx ].Eqp.Entry}##{manipIdx}" ) ) +// { +// ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); +// } +// +// break; +// case MetaManipulation.Type.Gmp: +// changes = DrawGmpRow( manipIdx, list ); +// ImGui.TableSetColumnIndex( 9 ); +// if( ImGui.Selectable( $"{list[ manipIdx ].Gmp.Entry.Value}##{manipIdx}" ) ) +// { +// ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); +// } +// +// break; +// case MetaManipulation.Type.Eqdp: +// changes = DrawEqdpRow( manipIdx, list ); +// ImGui.TableSetColumnIndex( 9 ); +// var (bit1, bit2) = GetEqdpBits( list[ manipIdx ].Eqdp.Slot, list[ manipIdx ].Eqdp.Entry ); +// if( ImGui.Selectable( $"{bit1} {bit2}##{manipIdx}" ) ) +// { +// ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); +// } +// +// break; +// case MetaManipulation.Type.Est: +// changes = DrawEstRow( manipIdx, list ); +// ImGui.TableSetColumnIndex( 9 ); +// if( ImGui.Selectable( $"{list[ manipIdx ].Est.Entry}##{manipIdx}" ) ) +// { +// ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); +// } +// +// break; +// case MetaManipulation.Type.Imc: +// changes = DrawImcRow( manipIdx, list ); +// ImGui.TableSetColumnIndex( 9 ); +// if( ImGui.Selectable( $"{list[ manipIdx ].Imc.Entry.MaterialId}##{manipIdx}" ) ) +// { +// ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); +// } +// +// break; +// case MetaManipulation.Type.Rsp: +// changes = DrawRspRow( manipIdx, list ); +// ImGui.TableSetColumnIndex( 9 ); +// if( ImGui.Selectable( $"{list[ manipIdx ].Rsp.Entry}##{manipIdx}" ) ) +// { +// ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); +// } +// +// break; +// } +// +// +// ImGui.TableNextRow(); +// return changes; +// } +// +// +// private MetaManipulation.Type DrawNewTypeSelection() +// { +// ImGui.RadioButton( "IMC##newManipType", ref _newManipTypeIdx, 1 ); +// ImGui.SameLine(); +// ImGui.RadioButton( "EQDP##newManipType", ref _newManipTypeIdx, 2 ); +// ImGui.SameLine(); +// ImGui.RadioButton( "EQP##newManipType", ref _newManipTypeIdx, 3 ); +// ImGui.SameLine(); +// ImGui.RadioButton( "EST##newManipType", ref _newManipTypeIdx, 4 ); +// ImGui.SameLine(); +// ImGui.RadioButton( "GMP##newManipType", ref _newManipTypeIdx, 5 ); +// ImGui.SameLine(); +// ImGui.RadioButton( "RSP##newManipType", ref _newManipTypeIdx, 6 ); +// return ( MetaManipulation.Type )_newManipTypeIdx; +// } +// +// private bool DrawNewManipulationPopup( string popupName, IList< MetaManipulation > list, ref int count ) +// { +// var change = false; +// if( !ImGui.BeginPopup( popupName ) ) +// { +// return change; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); +// var manipType = DrawNewTypeSelection(); +// MetaManipulation? newManip = null; +// switch( manipType ) +// { +// case MetaManipulation.Type.Imc: +// { +// RestrictedInputInt( "Set Id##newManipImc", ref _newManipSetId, 0, ushort.MaxValue ); +// RestrictedInputInt( "Variant##newManipImc", ref _newManipVariant, 0, byte.MaxValue ); +// CustomCombo( "Object Type", ImcObjectType, out var objectType, ref _newManipObjectType ); +// ImcManipulation imc = new(); +// switch( objectType ) +// { +// case ObjectType.Equipment: +// CustomCombo( "Equipment Slot", EqdpEquipSlots, out var equipSlot, ref _newManipEquipSlot ); +// imc = new ImcManipulation( equipSlot, _newManipVariant, _newManipSetId, new ImcEntry() ); +// break; +// case ObjectType.DemiHuman: +// case ObjectType.Weapon: +// case ObjectType.Monster: +// RestrictedInputInt( "Secondary Id##newManipImc", ref _newManipSecondaryId, 0, ushort.MaxValue ); +// CustomCombo( "Body Slot", ImcBodySlots, out var bodySlot, ref _newManipBodySlot ); +// imc = new ImcManipulation( objectType, bodySlot, _newManipSetId, _newManipSecondaryId, +// _newManipVariant, new ImcEntry() ); +// break; +// } +// +// newManip = new MetaManipulation( new ImcManipulation( imc.ObjectType, imc.BodySlot, imc.PrimaryId, imc.SecondaryId, +// imc.Variant, imc.EquipSlot, ImcFile.GetDefault( imc.GamePath(), imc.EquipSlot, imc.Variant ) ) ); +// +// break; +// } +// case MetaManipulation.Type.Eqdp: +// { +// RestrictedInputInt( "Set Id##newManipEqdp", ref _newManipSetId, 0, ushort.MaxValue ); +// CustomCombo( "Equipment Slot", EqdpEquipSlots, out var equipSlot, ref _newManipEquipSlot ); +// CustomCombo( "Race", Races, out var race, ref _newManipRace ); +// CustomCombo( "Gender", Genders, out var gender, ref _newManipGender ); +// var eqdp = new EqdpManipulation( new EqdpEntry(), equipSlot, gender, race, _newManipSetId ); +// newManip = new MetaManipulation( new EqdpManipulation( ExpandedEqdpFile.GetDefault( eqdp.FileIndex(), eqdp.SetId ), +// equipSlot, gender, race, _newManipSetId ) ); +// break; +// } +// case MetaManipulation.Type.Eqp: +// { +// RestrictedInputInt( "Set Id##newManipEqp", ref _newManipSetId, 0, ushort.MaxValue ); +// CustomCombo( "Equipment Slot", EqpEquipSlots, out var equipSlot, ref _newManipEquipSlot ); +// newManip = new MetaManipulation( new EqpManipulation( ExpandedEqpFile.GetDefault( _newManipSetId ) & Eqp.Mask( equipSlot ), +// equipSlot, _newManipSetId ) ); +// break; +// } +// case MetaManipulation.Type.Est: +// { +// RestrictedInputInt( "Set Id##newManipEst", ref _newManipSetId, 0, ushort.MaxValue ); +// CustomCombo( "Est Type", EstTypes, out var estType, ref _newManipObjectType ); +// CustomCombo( "Race", Races, out var race, ref _newManipRace ); +// CustomCombo( "Gender", Genders, out var gender, ref _newManipGender ); +// newManip = new MetaManipulation( new EstManipulation( gender, race, estType, _newManipSetId, +// EstFile.GetDefault( estType, Names.CombinedRace( gender, race ), _newManipSetId ) ) ); +// break; +// } +// case MetaManipulation.Type.Gmp: +// RestrictedInputInt( "Set Id##newManipGmp", ref _newManipSetId, 0, ushort.MaxValue ); +// newManip = new MetaManipulation( new GmpManipulation( ExpandedGmpFile.GetDefault( _newManipSetId ), _newManipSetId ) ); +// break; +// case MetaManipulation.Type.Rsp: +// CustomCombo( "Subrace", Subraces, out var subRace, ref _newManipSubrace ); +// CustomCombo( "Attribute", RspAttributes, out var rspAttribute, ref _newManipAttribute ); +// newManip = new MetaManipulation( new RspManipulation( subRace, rspAttribute, +// CmpFile.GetDefault( subRace, rspAttribute ) ) ); +// break; +// } +// +// if( ImGui.Button( "Create Manipulation##newManip", Vector2.UnitX * -1 ) +// && newManip != null +// && list.All( m => !m.Equals( newManip ) ) ) +// { +// list.Add( newManip.Value ); +// change = true; +// ++count; +// ImGui.CloseCurrentPopup(); +// } +// +// return change; +// } +// +// private bool DrawMetaManipulationsTable( string label, IList< MetaManipulation > list, ref int count ) +// { +// var numRows = _editMode ? 11 : 10; +// var changes = false; +// +// +// if( list.Count > 0 +// && ImGui.BeginTable( label, numRows, +// ImGuiTableFlags.BordersInner | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit ) ) +// { +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTable ); +// if( _editMode ) +// { +// ImGui.TableNextColumn(); +// } +// +// ImGui.TableNextColumn(); +// ImGui.TableHeader( $"Type##{label}" ); +// ImGui.TableNextColumn(); +// ImGui.TableHeader( $"Object Type##{label}" ); +// ImGui.TableNextColumn(); +// ImGui.TableHeader( $"Set##{label}" ); +// ImGui.TableNextColumn(); +// ImGui.TableHeader( $"Slot##{label}" ); +// ImGui.TableNextColumn(); +// ImGui.TableHeader( $"Race##{label}" ); +// ImGui.TableNextColumn(); +// ImGui.TableHeader( $"Gender##{label}" ); +// ImGui.TableNextColumn(); +// ImGui.TableHeader( $"Secondary ID##{label}" ); +// ImGui.TableNextColumn(); +// ImGui.TableHeader( $"Variant##{label}" ); +// ImGui.TableNextColumn(); +// ImGui.TableNextColumn(); +// ImGui.TableHeader( $"Value##{label}" ); +// ImGui.TableNextRow(); +// +// for( var i = 0; i < list.Count; ++i ) +// { +// changes |= DrawManipulationRow( ref i, list, ref count ); +// } +// } +// +// var popupName = $"##newManip{label}"; +// if( _editMode ) +// { +// changes |= DrawNewManipulationPopup( $"##newManip{label}", list, ref count ); +// if( ImGui.Button( $"Add New Manipulation##{label}", Vector2.UnitX * -1 ) ) +// { +// ImGui.OpenPopup( popupName ); +// } +// +// return changes; +// } +// +// return false; +// } +// } +//} \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.ModsTab.Import.cs b/Penumbra/UI/ConfigWindow.ModsTab.Import.cs new file mode 100644 index 00000000..917408fe --- /dev/null +++ b/Penumbra/UI/ConfigWindow.ModsTab.Import.cs @@ -0,0 +1,177 @@ +namespace Penumbra.UI; + +//public partial class ConfigWindow +//{ +// private class TabImport +// { +// private const string LabelTab = "Import Mods"; +// private const string LabelImportButton = "Import TexTools Modpacks"; +// private const string LabelFileDialog = "Pick one or more modpacks."; +// private const string LabelFileImportRunning = "Import in progress..."; +// private const string FileTypeFilter = "TexTools TTMP Modpack (*.ttmp2)|*.ttmp*|All files (*.*)|*.*"; +// private const string TooltipModpack1 = "Writing modpack to disk before extracting..."; +// +// private const uint ColorRed = 0xFF0000C8; +// private const uint ColorYellow = 0xFF00C8C8; +// +// private static readonly Vector2 ImportBarSize = new(-1, 0); +// +// private bool _isImportRunning; +// private string _errorMessage = string.Empty; +// private TexToolsImport? _texToolsImport; +// private readonly SettingsInterface _base; +// +// public readonly HashSet< string > NewMods = new(); +// +// public TabImport( SettingsInterface ui ) +// => _base = ui; +// +// public bool IsImporting() +// => _isImportRunning; +// +// private void RunImportTask() +// { +// _isImportRunning = true; +// Task.Run( async () => +// { +// try +// { +// var picker = new OpenFileDialog +// { +// Multiselect = true, +// Filter = FileTypeFilter, +// CheckFileExists = true, +// Title = LabelFileDialog, +// }; +// +// var result = await picker.ShowDialogAsync(); +// +// if( result == DialogResult.OK ) +// { +// _errorMessage = string.Empty; +// +// foreach( var fileName in picker.FileNames ) +// { +// PluginLog.Information( $"-> {fileName} START" ); +// +// try +// { +// _texToolsImport = new TexToolsImport( Penumbra.ModManager.BasePath ); +// var dir = _texToolsImport.ImportModPack( new FileInfo( fileName ) ); +// if( dir.Name.Any() ) +// { +// NewMods.Add( dir.Name ); +// } +// +// PluginLog.Information( $"-> {fileName} OK!" ); +// } +// catch( Exception ex ) +// { +// PluginLog.LogError( ex, "Failed to import modpack at {0}", fileName ); +// _errorMessage = ex.Message; +// } +// } +// +// var directory = _texToolsImport?.ExtractedDirectory; +// _texToolsImport = null; +// _base.ReloadMods(); +// if( directory != null ) +// { +// _base._menu.InstalledTab.Selector.SelectModOnUpdate( directory.Name ); +// } +// } +// } +// catch( Exception e ) +// { +// PluginLog.Error( $"Error opening file picker dialogue:\n{e}" ); +// } +// +// _isImportRunning = false; +// } ); +// } +// +// private void DrawImportButton() +// { +// if( !Penumbra.ModManager.Valid ) +// { +// using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f ); +// ImGui.Button( LabelImportButton ); +// style.Pop(); +// +// using var color = ImGuiRaii.PushColor( ImGuiCol.Text, ColorRed ); +// ImGui.Text( "Can not import since the mod directory path is not valid." ); +// ImGui.Dummy( Vector2.UnitY * ImGui.GetTextLineHeightWithSpacing() ); +// color.Pop(); +// +// ImGui.Text( "Please set the mod directory in the settings tab." ); +// ImGui.Text( "This folder should preferably be close to the root directory of your (preferably SSD) drive, for example" ); +// color.Push( ImGuiCol.Text, ColorYellow ); +// ImGui.Text( " D:\\ffxivmods" ); +// color.Pop(); +// ImGui.Text( "You can return to this tab once you've done that." ); +// } +// else if( ImGui.Button( LabelImportButton ) ) +// { +// RunImportTask(); +// } +// } +// +// private void DrawImportProgress() +// { +// ImGui.Button( LabelFileImportRunning ); +// +// if( _texToolsImport == null ) +// { +// return; +// } +// +// switch( _texToolsImport.State ) +// { +// case ImporterState.None: break; +// case ImporterState.WritingPackToDisk: +// ImGui.Text( TooltipModpack1 ); +// break; +// case ImporterState.ExtractingModFiles: +// { +// var str = +// $"{_texToolsImport.CurrentModPack} - {_texToolsImport.CurrentProgress} of {_texToolsImport.TotalProgress} files"; +// +// ImGui.ProgressBar( _texToolsImport.Progress, ImportBarSize, str ); +// break; +// } +// case ImporterState.Done: break; +// default: throw new ArgumentOutOfRangeException(); +// } +// } +// +// private void DrawFailedImportMessage() +// { +// using var color = ImGuiRaii.PushColor( ImGuiCol.Text, ColorRed ); +// ImGui.Text( $"One or more of your modpacks failed to import:\n\t\t{_errorMessage}" ); +// } +// +// public void Draw() +// { +// if( !ImGui.BeginTabItem( LabelTab ) ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); +// +// if( !_isImportRunning ) +// { +// DrawImportButton(); +// } +// else +// { +// DrawImportProgress(); +// } +// +// if( _errorMessage.Any() ) +// { +// DrawFailedImportMessage(); +// } +// } +// } +//} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs b/Penumbra/UI/ConfigWindow.ModsTab.Panel.cs similarity index 100% rename from Penumbra/UI/MenuTabs/TabInstalled/TabInstalledModPanel.cs rename to Penumbra/UI/ConfigWindow.ModsTab.Panel.cs diff --git a/Penumbra/UI/ConfigWindow.ModsTab.Selector.cs b/Penumbra/UI/ConfigWindow.ModsTab.Selector.cs new file mode 100644 index 00000000..3190decb --- /dev/null +++ b/Penumbra/UI/ConfigWindow.ModsTab.Selector.cs @@ -0,0 +1,797 @@ +//using System; +//using System.Collections.Generic; +//using System.IO; +//using System.Linq; +//using System.Numerics; +//using System.Runtime.InteropServices; +//using System.Windows.Forms.VisualStyles; +//using Dalamud.Interface; +//using Dalamud.Logging; +//using ImGuiNET; +//using Penumbra.Collections; +//using Penumbra.Importer; +//using Penumbra.Mods; +//using Penumbra.UI.Classes; +//using Penumbra.UI.Custom; +//using Penumbra.Util; +// +//namespace Penumbra.UI; +// +//public partial class SettingsInterface +//{ +// // Constants +// private partial class Selector +// { +// private const string LabelSelectorList = "##availableModList"; +// private const string LabelModFilter = "##ModFilter"; +// private const string LabelAddModPopup = "AddModPopup"; +// private const string LabelModHelpPopup = "Help##Selector"; +// +// private const string TooltipModFilter = +// "Filter mods for those containing the given substring.\nEnter c:[string] to filter for mods changing specific items.\nEnter a:[string] to filter for mods by specific authors."; +// +// private const string TooltipDelete = "Delete the selected mod"; +// private const string TooltipAdd = "Add an empty mod"; +// private const string DialogDeleteMod = "PenumbraDeleteMod"; +// private const string ButtonYesDelete = "Yes, delete it"; +// private const string ButtonNoDelete = "No, keep it"; +// +// private const float SelectorPanelWidth = 240f; +// +// private static readonly Vector2 SelectorButtonSizes = new(100, 0); +// private static readonly Vector2 HelpButtonSizes = new(40, 0); +// +// private static readonly Vector4 DeleteModNameColor = new(0.7f, 0.1f, 0.1f, 1); +// } +// +// // Buttons +// private partial class Selector +// { +// // === Delete === +// private int? _deleteIndex; +// +// private void DrawModTrashButton() +// { +// using var raii = ImGuiRaii.PushFont( UiBuilder.IconFont ); +// +// if( ImGui.Button( FontAwesomeIcon.Trash.ToIconString(), SelectorButtonSizes * _selectorScalingFactor ) && _index >= 0 ) +// { +// _deleteIndex = _index; +// } +// +// raii.Pop(); +// +// ImGuiCustom.HoverTooltip( TooltipDelete ); +// } +// +// private void DrawDeleteModal() +// { +// if( _deleteIndex == null ) +// { +// return; +// } +// +// ImGui.OpenPopup( DialogDeleteMod ); +// +// var _ = true; +// ImGui.SetNextWindowPos( ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, Vector2.One / 2 ); +// var ret = ImGui.BeginPopupModal( DialogDeleteMod, ref _, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoDecoration ); +// if( !ret ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); +// +// if( Mod == null ) +// { +// _deleteIndex = null; +// ImGui.CloseCurrentPopup(); +// return; +// } +// +// ImGui.Text( "Are you sure you want to delete the following mod:" ); +// var halfLine = new Vector2( ImGui.GetTextLineHeight() / 2 ); +// ImGui.Dummy( halfLine ); +// ImGui.TextColored( DeleteModNameColor, Mod.Data.Meta.Name ); +// ImGui.Dummy( halfLine ); +// +// var buttonSize = ImGuiHelpers.ScaledVector2( 120, 0 ); +// if( ImGui.Button( ButtonYesDelete, buttonSize ) ) +// { +// ImGui.CloseCurrentPopup(); +// var mod = Mod; +// Cache.RemoveMod( mod ); +// Penumbra.ModManager.DeleteMod( mod.Data.BasePath ); +// ModFileSystem.InvokeChange(); +// ClearSelection(); +// } +// +// ImGui.SameLine(); +// +// if( ImGui.Button( ButtonNoDelete, buttonSize ) ) +// { +// ImGui.CloseCurrentPopup(); +// _deleteIndex = null; +// } +// } +// +// // === Add === +// private bool _modAddKeyboardFocus = true; +// +// private void DrawModAddButton() +// { +// using var raii = ImGuiRaii.PushFont( UiBuilder.IconFont ); +// +// if( ImGui.Button( FontAwesomeIcon.Plus.ToIconString(), SelectorButtonSizes * _selectorScalingFactor ) ) +// { +// _modAddKeyboardFocus = true; +// ImGui.OpenPopup( LabelAddModPopup ); +// } +// +// raii.Pop(); +// +// ImGuiCustom.HoverTooltip( TooltipAdd ); +// +// DrawModAddPopup(); +// } +// +// private void DrawModAddPopup() +// { +// if( !ImGui.BeginPopup( LabelAddModPopup ) ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); +// +// if( _modAddKeyboardFocus ) +// { +// ImGui.SetKeyboardFocusHere(); +// _modAddKeyboardFocus = false; +// } +// +// var newName = ""; +// if( ImGui.InputTextWithHint( "##AddMod", "New Mod Name...", ref newName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) ) +// { +// try +// { +// var newDir = TexToolsImport.CreateModFolder( new DirectoryInfo( Penumbra.Config!.ModDirectory ), +// newName ); +// var modMeta = new ModMeta +// { +// Author = "Unknown", +// Name = newName.Replace( '/', '\\' ), +// Description = string.Empty, +// }; +// +// var metaFile = new FileInfo( Path.Combine( newDir.FullName, "meta.json" ) ); +// modMeta.SaveToFile( metaFile ); +// Penumbra.ModManager.AddMod( newDir ); +// ModFileSystem.InvokeChange(); +// SelectModOnUpdate( newDir.Name ); +// } +// catch( Exception e ) +// { +// PluginLog.Error( $"Could not create directory for new Mod {newName}:\n{e}" ); +// } +// +// ImGui.CloseCurrentPopup(); +// } +// +// if( ImGui.IsKeyPressed( ImGui.GetKeyIndex( ImGuiKey.Escape ) ) ) +// { +// ImGui.CloseCurrentPopup(); +// } +// } +// +// // === Help === +// private void DrawModHelpButton() +// { +// using var raii = ImGuiRaii.PushFont( UiBuilder.IconFont ); +// if( ImGui.Button( FontAwesomeIcon.QuestionCircle.ToIconString(), HelpButtonSizes * _selectorScalingFactor ) ) +// { +// ImGui.OpenPopup( LabelModHelpPopup ); +// } +// } +// +// private static void DrawModHelpPopup() +// { +// ImGui.SetNextWindowPos( ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, Vector2.One / 2 ); +// ImGui.SetNextWindowSize( new Vector2( 5 * SelectorPanelWidth, 34 * ImGui.GetTextLineHeightWithSpacing() ), +// ImGuiCond.Appearing ); +// var _ = true; +// if( !ImGui.BeginPopupModal( LabelModHelpPopup, ref _, ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove ) ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); +// +// ImGui.Dummy( Vector2.UnitY * ImGui.GetTextLineHeight() ); +// ImGui.Text( "Mod Selector" ); +// ImGui.BulletText( "Select a mod to obtain more information." ); +// ImGui.BulletText( "Mod names are colored according to their current state in the collection:" ); +// ImGui.Indent(); +// ImGui.Bullet(); +// ImGui.SameLine(); +// ImGui.Text( "Enabled in the current collection." ); +// ImGui.Bullet(); +// ImGui.SameLine(); +// ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( ColorId.DisabledMod.Value() ), "Disabled in the current collection." ); +// ImGui.Bullet(); +// ImGui.SameLine(); +// ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( ColorId.NewMod.Value() ), +// "Newly imported during this session. Will go away when first enabling a mod or when Penumbra is reloaded." ); +// ImGui.Bullet(); +// ImGui.SameLine(); +// ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( ColorId.HandledConflictMod.Value() ), +// "Enabled and conflicting with another enabled Mod, but on different priorities (i.e. the conflict is solved)." ); +// ImGui.Bullet(); +// ImGui.SameLine(); +// ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( ColorId.DisabledMod.Value() ), +// "Enabled and conflicting with another enabled Mod on the same priority." ); +// ImGui.Unindent(); +// ImGui.BulletText( "Right-click a mod to enter its sort order, which is its name by default." ); +// ImGui.Indent(); +// ImGui.BulletText( "A sort order differing from the mods name will not be displayed, it will just be used for ordering." ); +// ImGui.BulletText( +// "If the sort order string contains Forward-Slashes ('/'), the preceding substring will be turned into collapsible folders that can group mods." ); +// ImGui.BulletText( +// "Collapsible folders can contain further collapsible folders, so \"folder1/folder2/folder3/1\" will produce 3 folders\n" +// + "\t\t[folder1] -> [folder2] -> [folder3] -> [ModName],\n" +// + "where ModName will be sorted as if it was the string '1'." ); +// ImGui.Unindent(); +// ImGui.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." ); +// ImGui.BulletText( "Right-clicking a folder opens a context menu." ); +// ImGui.Indent(); +// ImGui.BulletText( +// "You can rename folders in the context menu. Leave the text blank and press enter to merge the folder with its parent." ); +// ImGui.BulletText( "You can also enable or disable all descendant mods of a folder." ); +// ImGui.Unindent(); +// ImGui.BulletText( "Use the Filter Mods... input at the top to filter the list for mods with names containing the text." ); +// ImGui.Indent(); +// ImGui.BulletText( "You can enter c:[string] to filter for Changed Items instead." ); +// ImGui.BulletText( "You can enter a:[string] to filter for Mod Authors instead." ); +// ImGui.Unindent(); +// ImGui.BulletText( "Use the expandable menu beside the input to filter for mods fulfilling specific criteria." ); +// ImGui.Dummy( Vector2.UnitY * ImGui.GetTextLineHeight() ); +// ImGui.Text( "Mod Management" ); +// ImGui.BulletText( "You can delete the currently selected mod with the trashcan button." ); +// ImGui.BulletText( "You can add a completely empty mod with the plus button." ); +// ImGui.BulletText( "You can import TTMP-based mods in the import tab." ); +// ImGui.BulletText( +// "You can import penumbra-based mods by moving the corresponding folder into your mod directory in a file explorer, then rediscovering mods." ); +// ImGui.BulletText( +// "If you enable Advanced Options in the Settings tab, you can toggle Edit Mode to manipulate your selected mod even further." ); +// ImGui.Dummy( Vector2.UnitY * ImGui.GetTextLineHeight() ); +// ImGui.Dummy( Vector2.UnitX * 2 * SelectorPanelWidth ); +// ImGui.SameLine(); +// if( ImGui.Button( "Understood", Vector2.UnitX * SelectorPanelWidth ) ) +// { +// ImGui.CloseCurrentPopup(); +// } +// } +// +// // === Main === +// private void DrawModsSelectorButtons() +// { +// // Selector controls +// using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.WindowPadding, ZeroVector ) +// .Push( ImGuiStyleVar.FrameRounding, 0 ); +// +// DrawModAddButton(); +// ImGui.SameLine(); +// DrawModHelpButton(); +// ImGui.SameLine(); +// DrawModTrashButton(); +// } +// } +// +// // Filters +// private partial class Selector +// { +// private string _modFilterInput = ""; +// +// private void DrawTextFilter() +// { +// ImGui.SetNextItemWidth( SelectorPanelWidth * _selectorScalingFactor - 22 * ImGuiHelpers.GlobalScale ); +// var tmp = _modFilterInput; +// if( ImGui.InputTextWithHint( LabelModFilter, "Filter Mods...", ref tmp, 256 ) && _modFilterInput != tmp ) +// { +// Cache.SetTextFilter( tmp ); +// _modFilterInput = tmp; +// } +// +// ImGuiCustom.HoverTooltip( TooltipModFilter ); +// } +// +// private void DrawToggleFilter() +// { +// if( ImGui.BeginCombo( "##ModStateFilter", "", +// ImGuiComboFlags.NoPreview | ImGuiComboFlags.PopupAlignLeft | ImGuiComboFlags.HeightLargest ) ) +// { +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndCombo ); +// var flags = ( int )Cache.StateFilter; +// foreach( ModFilter flag in Enum.GetValues( typeof( ModFilter ) ) ) +// { +// ImGui.CheckboxFlags( flag.ToName(), ref flags, ( int )flag ); +// } +// +// Cache.StateFilter = ( ModFilter )flags; +// } +// +// ImGuiCustom.HoverTooltip( "Filter mods for their activation status." ); +// } +// +// private void DrawModsSelectorFilter() +// { +// using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.ItemSpacing, ZeroVector ); +// DrawTextFilter(); +// ImGui.SameLine(); +// DrawToggleFilter(); +// } +// } +// +// // Drag'n Drop +// private partial class Selector +// { +// private const string DraggedModLabel = "ModIndex"; +// private const string DraggedFolderLabel = "FolderName"; +// +// private readonly IntPtr _dragDropPayload = Marshal.AllocHGlobal( 4 ); +// +// private static unsafe bool IsDropping( string name ) +// => ImGui.AcceptDragDropPayload( name ).NativePtr != null; +// +// private void DragDropTarget( ModFolder folder ) +// { +// if( !ImGui.BeginDragDropTarget() ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndDragDropTarget ); +// +// if( IsDropping( DraggedModLabel ) ) +// { +// var payload = ImGui.GetDragDropPayload(); +// var modIndex = Marshal.ReadInt32( payload.Data ); +// var mod = Cache.GetMod( modIndex ).Item1; +// mod?.Data.Move( folder ); +// } +// else if( IsDropping( DraggedFolderLabel ) ) +// { +// var payload = ImGui.GetDragDropPayload(); +// var folderName = Marshal.PtrToStringUni( payload.Data ); +// if( ModFileSystem.Find( folderName!, out var droppedFolder ) +// && !ReferenceEquals( droppedFolder, folder ) +// && !folder.FullName.StartsWith( folderName!, StringComparison.InvariantCultureIgnoreCase ) ) +// { +// droppedFolder.Move( folder ); +// } +// } +// } +// +// private void DragDropSourceFolder( ModFolder folder ) +// { +// if( !ImGui.BeginDragDropSource() ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndDragDropSource ); +// +// var folderName = folder.FullName; +// var ptr = Marshal.StringToHGlobalUni( folderName ); +// ImGui.SetDragDropPayload( DraggedFolderLabel, ptr, ( uint )( folderName.Length + 1 ) * 2 ); +// ImGui.Text( $"Moving {folderName}..." ); +// } +// +// private void DragDropSourceMod( int modIndex, string modName ) +// { +// if( !ImGui.BeginDragDropSource() ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndDragDropSource ); +// +// Marshal.WriteInt32( _dragDropPayload, modIndex ); +// ImGui.SetDragDropPayload( "ModIndex", _dragDropPayload, 4 ); +// ImGui.Text( $"Moving {modName}..." ); +// } +// +// ~Selector() +// => Marshal.FreeHGlobal( _dragDropPayload ); +// } +// +// // Selection +// private partial class Selector +// { +// public Mods.FullMod? Mod { get; private set; } +// private int _index; +// private string _nextDir = string.Empty; +// +// private void SetSelection( int idx, Mods.FullMod? info ) +// { +// Mod = info; +// if( idx != _index ) +// { +// _base._menu.InstalledTab.ModPanel.Details.ResetState(); +// } +// +// _index = idx; +// _deleteIndex = null; +// } +// +// private void SetSelection( int idx ) +// { +// if( idx >= Cache.Count ) +// { +// idx = -1; +// } +// +// if( idx < 0 ) +// { +// SetSelection( 0, null ); +// } +// else +// { +// SetSelection( idx, Cache.GetMod( idx ).Item1 ); +// } +// } +// +// public void ReloadSelection() +// => SetSelection( _index, Cache.GetMod( _index ).Item1 ); +// +// public void ClearSelection() +// => SetSelection( -1 ); +// +// public void SelectModOnUpdate( string directory ) +// => _nextDir = directory; +// +// public void SelectModByDir( string name ) +// { +// var (mod, idx) = Cache.GetModByBasePath( name ); +// SetSelection( idx, mod ); +// } +// +// public void ReloadCurrentMod( bool reloadMeta = false, bool recomputeMeta = false, bool force = false ) +// { +// if( Mod == null ) +// { +// return; +// } +// +// if( _index >= 0 && Penumbra.ModManager.UpdateMod( Mod.Data, reloadMeta, recomputeMeta, force ) ) +// { +// SelectModOnUpdate( Mod.Data.BasePath.Name ); +// _base._menu.InstalledTab.ModPanel.Details.ResetState(); +// } +// } +// +// public void SaveCurrentMod() +// => Mod?.Data.SaveMeta(); +// } +// +// // Right-Clicks +// private partial class Selector +// { +// // === Mod === +// private void DrawModOrderPopup( string popupName, Mods.FullMod mod, bool firstOpen ) +// { +// if( !ImGui.BeginPopup( popupName ) ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); +// +// if( ModPanel.DrawSortOrder( mod.Data, Penumbra.ModManager, this ) ) +// { +// ImGui.CloseCurrentPopup(); +// } +// +// if( firstOpen ) +// { +// ImGui.SetKeyboardFocusHere( mod.Data.Order.FullPath.Length - 1 ); +// } +// } +// +// // === Folder === +// private string _newFolderName = string.Empty; +// private int _expandIndex = -1; +// private bool _expandCollapse; +// private bool _currentlyExpanding; +// +// private void ChangeStatusOfChildren( ModFolder folder, int currentIdx, bool toWhat ) +// { +// var change = false; +// var metaManips = false; +// foreach( var _ in folder.AllMods( Penumbra.ModManager.Config.SortFoldersFirst ) ) +// { +// var (mod, _, _) = Cache.GetMod( currentIdx++ ); +// if( mod != null ) +// { +// change |= mod.Settings.Enabled != toWhat; +// mod!.Settings.Enabled = toWhat; +// metaManips |= mod.Data.Resources.MetaManipulations.Count > 0; +// } +// } +// +// if( !change ) +// { +// return; +// } +// +// Cache.TriggerFilterReset(); +// var collection = Penumbra.CollectionManager.Current; +// if( collection.HasCache ) +// { +// collection.CalculateEffectiveFileList( metaManips, collection == Penumbra.CollectionManager.Default ); +// } +// +// collection.Save(); +// } +// +// private void DrawRenameFolderInput( ModFolder folder ) +// { +// ImGui.SetNextItemWidth( 150 * ImGuiHelpers.GlobalScale ); +// if( !ImGui.InputTextWithHint( "##NewFolderName", "Rename Folder...", ref _newFolderName, 64, +// ImGuiInputTextFlags.EnterReturnsTrue ) ) +// { +// return; +// } +// +// if( _newFolderName.Any() ) +// { +// folder.Rename( _newFolderName ); +// } +// else +// { +// folder.Merge( folder.Parent! ); +// } +// +// _newFolderName = string.Empty; +// } +// +// private void DrawFolderContextMenu( ModFolder folder, int currentIdx, string treeName ) +// { +// if( !ImGui.BeginPopup( treeName ) ) +// { +// return; +// } +// +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); +// +// if( ImGui.MenuItem( "Expand All Descendants" ) ) +// { +// _expandIndex = currentIdx; +// _expandCollapse = false; +// } +// +// if( ImGui.MenuItem( "Collapse All Descendants" ) ) +// { +// _expandIndex = currentIdx; +// _expandCollapse = true; +// } +// +// if( ImGui.MenuItem( "Enable All Descendants" ) ) +// { +// ChangeStatusOfChildren( folder, currentIdx, true ); +// } +// +// if( ImGui.MenuItem( "Disable All Descendants" ) ) +// { +// ChangeStatusOfChildren( folder, currentIdx, false ); +// } +// +// ImGuiHelpers.ScaledDummy( 0, 10 ); +// DrawRenameFolderInput( folder ); +// } +// } +// +// // Main-Interface +// private partial class Selector +// { +// private readonly SettingsInterface _base; +// public readonly ModListCache Cache; +// +// private float _selectorScalingFactor = 1; +// +// public Selector( SettingsInterface ui, IReadOnlySet< string > newMods ) +// { +// _base = ui; +// Cache = new ModListCache( Penumbra.ModManager, newMods ); +// } +// +// private void DrawCollectionButton( string label, string tooltipLabel, float size, ModCollection collection ) +// { +// if( collection == ModCollection.Empty +// || collection == Penumbra.CollectionManager.Current ) +// { +// using var _ = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f ); +// ImGui.Button( label, Vector2.UnitX * size ); +// } +// else if( ImGui.Button( label, Vector2.UnitX * size ) ) +// { +// _base._menu.CollectionsTab.SetCurrentCollection( collection ); +// } +// +// ImGuiCustom.HoverTooltip( +// $"Switches to the currently set {tooltipLabel} collection, if it is not set to None and it is not the current collection already." ); +// } +// +// private void DrawHeaderBar() +// { +// const float size = 200; +// +// DrawModsSelectorFilter(); +// var textSize = ImGui.CalcTextSize( "Current Collection" ).X + ImGui.GetStyle().ItemInnerSpacing.X; +// var comboSize = size * ImGui.GetIO().FontGlobalScale; +// var offset = comboSize + textSize; +// +// var buttonSize = Math.Max( ImGui.GetWindowContentRegionWidth() +// - offset +// - SelectorPanelWidth * _selectorScalingFactor +// - 3 * ImGui.GetStyle().ItemSpacing.X, 5f ); +// ImGui.SameLine(); +// DrawCollectionButton( "Default", "default", buttonSize, Penumbra.CollectionManager.Default ); +// +// +// ImGui.SameLine(); +// ImGui.SetNextItemWidth( comboSize ); +// using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.ItemSpacing, Vector2.Zero ); +// _base._menu.CollectionsTab.DrawCurrentCollectionSelector( false ); +// } +// +// private void DrawFolderContent( ModFolder folder, ref int idx ) +// { +// // Collection may be manipulated. +// foreach( var item in folder.GetItems( Penumbra.ModManager.Config.SortFoldersFirst ).ToArray() ) +// { +// if( item is ModFolder sub ) +// { +// var (visible, _) = Cache.GetFolder( sub ); +// if( visible ) +// { +// DrawModFolder( sub, ref idx ); +// } +// else +// { +// idx += sub.TotalDescendantMods(); +// } +// } +// else if( item is Mods.Mod _ ) +// { +// var (mod, visible, color) = Cache.GetMod( idx ); +// if( mod != null && visible ) +// { +// DrawMod( mod, idx++, color ); +// } +// else +// { +// ++idx; +// } +// } +// } +// } +// +// private void DrawModFolder( ModFolder folder, ref int idx ) +// { +// var treeName = $"{folder.Name}##{folder.FullName}"; +// var open = ImGui.TreeNodeEx( treeName ); +// using var raii = ImGuiRaii.DeferredEnd( ImGui.TreePop, open ); +// +// if( idx == _expandIndex ) +// { +// _currentlyExpanding = true; +// } +// +// if( _currentlyExpanding ) +// { +// ImGui.SetNextItemOpen( !_expandCollapse ); +// } +// +// if( ImGui.IsItemClicked( ImGuiMouseButton.Right ) ) +// { +// _newFolderName = string.Empty; +// ImGui.OpenPopup( treeName ); +// } +// +// DrawFolderContextMenu( folder, idx, treeName ); +// DragDropTarget( folder ); +// DragDropSourceFolder( folder ); +// +// if( open ) +// { +// DrawFolderContent( folder, ref idx ); +// } +// else +// { +// idx += folder.TotalDescendantMods(); +// } +// +// if( idx == _expandIndex ) +// { +// _currentlyExpanding = false; +// _expandIndex = -1; +// } +// } +// +// private void DrawMod( Mods.FullMod mod, int modIndex, uint color ) +// { +// using var colorRaii = ImGuiRaii.PushColor( ImGuiCol.Text, color, color != 0 ); +// +// var selected = ImGui.Selectable( $"{mod.Data.Meta.Name}##{modIndex}", modIndex == _index ); +// colorRaii.Pop(); +// +// var popupName = $"##SortOrderPopup{modIndex}"; +// var firstOpen = false; +// if( ImGui.IsItemClicked( ImGuiMouseButton.Right ) ) +// { +// ImGui.OpenPopup( popupName ); +// firstOpen = true; +// } +// +// DragDropTarget( mod.Data.Order.ParentFolder ); +// DragDropSourceMod( modIndex, mod.Data.Meta.Name ); +// +// DrawModOrderPopup( popupName, mod, firstOpen ); +// +// if( selected ) +// { +// SetSelection( modIndex, mod ); +// } +// } +// +// public void Draw() +// { +// if( Cache.Update() ) +// { +// if( _nextDir.Any() ) +// { +// SelectModByDir( _nextDir ); +// _nextDir = string.Empty; +// } +// else if( Mod != null ) +// { +// SelectModByDir( Mod.Data.BasePath.Name ); +// } +// } +// +// _selectorScalingFactor = ImGuiHelpers.GlobalScale +// * ( Penumbra.Config.ScaleModSelector +// ? ImGui.GetWindowWidth() / SettingsMenu.MinSettingsSize.X +// : 1f ); +// // Selector pane +// DrawHeaderBar(); +// using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.ItemSpacing, Vector2.Zero ); +// ImGui.BeginGroup(); +// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndGroup ) +// .Push( ImGui.EndChild ); +// // Inlay selector list +// if( ImGui.BeginChild( LabelSelectorList, +// new Vector2( SelectorPanelWidth * _selectorScalingFactor, -ImGui.GetFrameHeightWithSpacing() ), +// true, ImGuiWindowFlags.HorizontalScrollbar ) ) +// { +// style.Push( ImGuiStyleVar.IndentSpacing, 12.5f ); +// +// var modIndex = 0; +// DrawFolderContent( Penumbra.ModManager.StructuredMods, ref modIndex ); +// style.Pop(); +// } +// +// raii.Pop(); +// +// DrawModsSelectorButtons(); +// +// style.Pop(); +// DrawModHelpPopup(); +// +// DrawDeleteModal(); +// } +// } +//} \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.ModsTab.cs b/Penumbra/UI/ConfigWindow.ModsTab.cs index 8b5bc12e..3af34018 100644 --- a/Penumbra/UI/ConfigWindow.ModsTab.cs +++ b/Penumbra/UI/ConfigWindow.ModsTab.cs @@ -1,5 +1,9 @@ +using System; using System.Numerics; +using ImGuiNET; +using OtterGui; using OtterGui.Raii; +using Penumbra.Collections; namespace Penumbra.UI; @@ -7,16 +11,92 @@ public partial class ConfigWindow { public void DrawModsTab() { + if( !Penumbra.ModManager.Valid ) + { + return; + } + using var tab = ImRaii.TabItem( "Mods" ); if( !tab ) { return; } - using var child = ImRaii.Child( "##ModsTab", -Vector2.One ); - if( !child ) + Selector.Draw( GetModSelectorSize() ); + ImGui.SameLine(); + using var group = ImRaii.Group(); + DrawHeaderLine(); + + using var child = ImRaii.Child( "##ModsTabMod", -Vector2.One, true ); + if( child ) + { + DrawModPanel(); + } + } + + private void DrawModPanel() + { + if( Selector.Selected == null ) { return; } } + + // Draw the header line that can quick switch between collections. + private void DrawHeaderLine() + { + using var style = ImRaii.PushStyle( ImGuiStyleVar.FrameRounding, 0 ).Push( ImGuiStyleVar.ItemSpacing, Vector2.Zero ); + var buttonSize = new Vector2( ImGui.GetContentRegionAvail().X / 8f, 0 ); + + DrawDefaultCollectionButton( 3 * buttonSize ); + ImGui.SameLine(); + DrawInheritedCollectionButton( 3 * buttonSize ); + ImGui.SameLine(); + DrawCollectionSelector( "##collection", 2 * buttonSize.X, ModCollection.Type.Current, false, null ); + } + + private static void DrawDefaultCollectionButton( Vector2 width ) + { + var name = $"Default Collection ({Penumbra.CollectionManager.Default.Name})"; + var isCurrent = Penumbra.CollectionManager.Default == Penumbra.CollectionManager.Current; + var isEmpty = Penumbra.CollectionManager.Default == ModCollection.Empty; + var tt = isCurrent ? "The current collection is already the configured default collection." + : isEmpty ? "The default collection is configured to be empty." + : "Set the current collection to the configured default collection."; + if( ImGuiUtil.DrawDisabledButton( name, width, tt, isCurrent || isEmpty ) ) + { + Penumbra.CollectionManager.SetCollection( Penumbra.CollectionManager.Default, ModCollection.Type.Current ); + } + } + + private void DrawInheritedCollectionButton( Vector2 width ) + { + var noModSelected = Selector.Selected == null; + var collection = Selector.SelectedSettingCollection; + var modInherited = collection != Penumbra.CollectionManager.Current; + var (name, tt) = ( noModSelected, modInherited ) switch + { + (true, _) => ( "Inherited Collection", "No mod selected." ), + (false, true) => ( $"Inherited Collection ({collection.Name})", + "Set the current collection to the collection the selected mod inherits its settings from." ), + (false, false) => ( "Not Inherited", "The selected mod does not inherit its settings." ), + }; + if( ImGuiUtil.DrawDisabledButton( name, width, tt, noModSelected || !modInherited ) ) + { + Penumbra.CollectionManager.SetCollection( collection, ModCollection.Type.Current ); + } + } + + // Get the correct size for the mod selector based on current config. + private static float GetModSelectorSize() + { + var absoluteSize = Math.Clamp( Penumbra.Config.ModSelectorAbsoluteSize, Configuration.Constants.MinAbsoluteSize, + Math.Min( Configuration.Constants.MaxAbsoluteSize, ImGui.GetContentRegionAvail().X - 100 ) ); + var relativeSize = Penumbra.Config.ScaleModSelector + ? Math.Clamp( Penumbra.Config.ModSelectorScaledSize, Configuration.Constants.MinScaledSize, Configuration.Constants.MaxScaledSize ) + : 0; + return !Penumbra.Config.ScaleModSelector + ? absoluteSize + : Math.Max( absoluteSize, relativeSize * ImGui.GetContentRegionAvail().X / 100 ); + } } \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.ResourceTab.cs b/Penumbra/UI/ConfigWindow.ResourceTab.cs index db1e1d67..bbdef888 100644 --- a/Penumbra/UI/ConfigWindow.ResourceTab.cs +++ b/Penumbra/UI/ConfigWindow.ResourceTab.cs @@ -1,10 +1,22 @@ +using System; +using System.Linq; using System.Numerics; +using Dalamud.Interface; +using FFXIVClientStructs.FFXIV.Client.System.Resource; +using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using FFXIVClientStructs.STD; +using ImGuiNET; +using OtterGui; using OtterGui.Raii; +using Penumbra.GameData.ByteString; +using Penumbra.Interop.Loader; +using Penumbra.UI.Custom; namespace Penumbra.UI; public partial class ConfigWindow { + // Draw a tab to iterate over the main resource maps and see what resources are currently loaded. public void DrawResourceManagerTab() { if( !DebugTabVisible ) @@ -18,10 +30,161 @@ public partial class ConfigWindow return; } + // Filter for resources containing the input string. + ImGui.SetNextItemWidth( -1 ); + ImGui.InputTextWithHint( "##resourceFilter", "Filter...", ref _resourceManagerFilter, Utf8GamePath.MaxGamePathLength ); + using var child = ImRaii.Child( "##ResourceManagerTab", -Vector2.One ); if( !child ) { return; } + + unsafe + { + ResourceLoader.IterateGraphs( DrawCategoryContainer ); + } } + + private float _hashColumnWidth; + private float _pathColumnWidth; + private float _refsColumnWidth; + private string _resourceManagerFilter = string.Empty; + + private unsafe void DrawResourceMap( ResourceCategory category, uint ext, StdMap< uint, Pointer< ResourceHandle > >* map ) + { + if( map == null ) + { + return; + } + + var label = GetNodeLabel( ( uint )category, ext, map->Count ); + using var tree = ImRaii.TreeNode( label ); + if( !tree || map->Count == 0 ) + { + return; + } + + using var table = ImRaii.Table( "##table", 4, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg ); + if( !table ) + { + return; + } + + ImGui.TableSetupColumn( "Hash", ImGuiTableColumnFlags.WidthFixed, _hashColumnWidth ); + ImGui.TableSetupColumn( "Ptr", ImGuiTableColumnFlags.WidthFixed, _hashColumnWidth ); + ImGui.TableSetupColumn( "Path", ImGuiTableColumnFlags.WidthFixed, _pathColumnWidth ); + ImGui.TableSetupColumn( "Refs", ImGuiTableColumnFlags.WidthFixed, _refsColumnWidth ); + ImGui.TableHeadersRow(); + + ResourceLoader.IterateResourceMap( map, ( hash, r ) => + { + // Filter unwanted names. + if( _resourceManagerFilter.Length != 0 + && !r->FileName.ToString().Contains( _resourceManagerFilter, StringComparison.InvariantCultureIgnoreCase ) ) + { + return; + } + + var address = $"0x{( ulong )r:X}"; + ImGuiUtil.TextNextColumn( $"0x{hash:X8}" ); + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable( address ); + + var resource = ( Interop.Structs.ResourceHandle* )r; + ImGui.TableNextColumn(); + Text( resource ); + if( ImGui.IsItemClicked() ) + { + var data = Interop.Structs.ResourceHandle.GetData( resource ); + if( data != null ) + { + var length = ( int )Interop.Structs.ResourceHandle.GetLength( resource ); + ImGui.SetClipboardText( string.Join( " ", + new ReadOnlySpan< byte >( data, length ).ToArray().Select( b => b.ToString( "X2" ) ) ) ); + } + } + + ImGuiUtil.HoverTooltip( "Click to copy byte-wise file data to clipboard, if any." ); + + ImGuiUtil.TextNextColumn( r->RefCount.ToString() ); + } ); + } + + // Draw a full category for the resource manager. + private unsafe void DrawCategoryContainer( ResourceCategory category, + StdMap< uint, Pointer< StdMap< uint, Pointer< ResourceHandle > > > >* map ) + { + if( map == null ) + { + return; + } + + using var tree = ImRaii.TreeNode( $"({( uint )category:D2}) {category} - {map->Count}###{( uint )category}" ); + if( tree ) + { + SetTableWidths(); + ResourceLoader.IterateExtMap( map, ( ext, m ) => DrawResourceMap( category, ext, m ) ); + } + } + + // Obtain a label for an extension node. + private static string GetNodeLabel( uint label, uint type, ulong count ) + { + var (lowest, mid1, mid2, highest) = Functions.SplitBytes( type ); + return highest == 0 + ? $"({type:X8}) {( char )mid2}{( char )mid1}{( char )lowest} - {count}###{label}{type}" + : $"({type:X8}) {( char )highest}{( char )mid2}{( char )mid1}{( char )lowest} - {count}###{label}{type}"; + } + + // Set the widths for a resource table. + private void SetTableWidths() + { + _hashColumnWidth = 100 * ImGuiHelpers.GlobalScale; + _pathColumnWidth = ImGui.GetWindowContentRegionWidth() - 300 * ImGuiHelpers.GlobalScale; + _refsColumnWidth = 30 * ImGuiHelpers.GlobalScale; + } + + //private static unsafe void DrawResourceProblems() + //{ + // if( !ImGui.CollapsingHeader( "Resource Problems##ResourceManager" ) + // || !ImGui.BeginTable( "##ProblemsTable", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit ) ) + // { + // return; + // } + // + // using var end = ImGuiRaii.DeferredEnd( ImGui.EndTable ); + // + // ResourceLoader.IterateResources( ( _, r ) => + // { + // if( r->RefCount < 10000 ) + // { + // return; + // } + // + // ImGui.TableNextColumn(); + // ImGui.Text( r->Category.ToString() ); + // ImGui.TableNextColumn(); + // ImGui.Text( r->FileType.ToString( "X" ) ); + // ImGui.TableNextColumn(); + // ImGui.Text( r->Id.ToString( "X" ) ); + // ImGui.TableNextColumn(); + // ImGui.Text( ( ( ulong )r ).ToString( "X" ) ); + // ImGui.TableNextColumn(); + // ImGui.Text( r->RefCount.ToString() ); + // ImGui.TableNextColumn(); + // ref var name = ref r->FileName; + // if( name.Capacity > 15 ) + // { + // ImGuiNative.igTextUnformatted( name.BufferPtr, name.BufferPtr + name.Length ); + // } + // else + // { + // fixed( byte* ptr = name.Buffer ) + // { + // ImGuiNative.igTextUnformatted( ptr, ptr + name.Length ); + // } + // } + // } ); + //} } \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs b/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs new file mode 100644 index 00000000..5aede757 --- /dev/null +++ b/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs @@ -0,0 +1,169 @@ +using Dalamud.Interface; +using Dalamud.Interface.Components; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using Penumbra.GameData.ByteString; +using Penumbra.UI.Classes; + +namespace Penumbra.UI; + +public partial class ConfigWindow +{ + // Sets the resource logger state when toggled, + // and the filter when entered. + private void DrawRequestedResourceLogging() + { + var tmp = Penumbra.Config.EnableResourceLogging; + if( ImGui.Checkbox( "##resourceLogging", ref tmp ) ) + { + _penumbra.ResourceLogger.SetState( tmp ); + } + + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker( "Enable Requested Resource Logging", "Log all game paths FFXIV requests to the plugin log.\n" + + "You can filter the logged paths for those containing the entered string or matching the regex, if the entered string compiles to a valid regex.\n" + + "Red boundary indicates invalid regex." ); + + ImGui.SameLine(); + + // Red borders if the string is not a valid regex. + var tmpString = Penumbra.Config.ResourceLoggingFilter; + using var color = ImRaii.PushColor( ImGuiCol.Border, Colors.RegexWarningBorder, !_penumbra.ResourceLogger.ValidRegex ); + using var style = ImRaii.PushStyle( ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale, !_penumbra.ResourceLogger.ValidRegex ); + ImGui.SetNextItemWidth( -1 ); + if( ImGui.InputTextWithHint( "##ResourceLogFilter", "Filter...", ref tmpString, Utf8GamePath.MaxGamePathLength ) ) + { + _penumbra.ResourceLogger.SetFilter( tmpString ); + } + } + + // Toggling audio streaming will need to apply to the music manager + // and rediscover mods due to determining whether .scds will be loaded or not. + private void DrawDisableSoundStreamingBox() + { + var tmp = Penumbra.Config.DisableSoundStreaming; + if( ImGui.Checkbox( "##streaming", ref tmp ) && tmp != Penumbra.Config.DisableSoundStreaming ) + { + Penumbra.Config.DisableSoundStreaming = tmp; + Penumbra.Config.Save(); + if( tmp ) + { + _penumbra.MusicManager.DisableStreaming(); + } + else + { + _penumbra.MusicManager.EnableStreaming(); + } + + Penumbra.ModManager.DiscoverMods(); + } + + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker( "Disable Audio Streaming", + "Disable streaming in the games audio engine.\n" + + "If you do not disable streaming, you can not replace sound files in the game (*.scd files), they will be ignored by Penumbra.\n\n" + + "Only touch this if you experience sound problems.\n" + + "If you toggle this, make sure no modified or to-be-modified sound file is currently playing or was recently playing, else you might crash.\n" + + "You might need to restart your game for this to fully take effect." ); + } + + // Creates and destroys the web server when toggled. + private void DrawEnableHttpApiBox() + { + var http = Penumbra.Config.EnableHttpApi; + if( ImGui.Checkbox( "##http", ref http ) ) + { + if( http ) + { + _penumbra.CreateWebServer(); + } + else + { + _penumbra.ShutdownWebServer(); + } + + Penumbra.Config.EnableHttpApi = http; + Penumbra.Config.Save(); + } + + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker( "Enable HTTP API", + "Enables other applications, e.g. Anamnesis, to use some Penumbra functions, like requesting redraws." ); + } + + // Should only be used for debugging. + private static void DrawEnableFullResourceLoggingBox() + { + var tmp = Penumbra.Config.EnableFullResourceLogging; + if( ImGui.Checkbox( "##fullLogging", ref tmp ) && tmp != Penumbra.Config.EnableFullResourceLogging ) + { + if( tmp ) + { + Penumbra.ResourceLoader.EnableFullLogging(); + } + else + { + Penumbra.ResourceLoader.DisableFullLogging(); + } + + Penumbra.Config.EnableFullResourceLogging = tmp; + Penumbra.Config.Save(); + } + + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker( "Enable Full Resource Logging", + "[DEBUG] Enable the logging of all ResourceLoader events indiscriminately." ); + } + + // Should only be used for debugging. + private static void DrawEnableDebugModeBox() + { + var tmp = Penumbra.Config.DebugMode; + if( ImGui.Checkbox( "##debugMode", ref tmp ) && tmp != Penumbra.Config.DebugMode ) + { + if( tmp ) + { + Penumbra.ResourceLoader.EnableDebug(); + } + else + { + Penumbra.ResourceLoader.DisableDebug(); + } + + Penumbra.Config.DebugMode = tmp; + Penumbra.Config.Save(); + } + + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker( "Enable Debug Mode", + "[DEBUG] Enable the Debug Tab and Resource Manager Tab as well as some additional data collection. Also open the config window on plugin load." ); + } + + private static void DrawReloadResourceButton() + { + if( ImGui.Button( "Reload Resident Resources" ) ) + { + Penumbra.ResidentResources.Reload(); + } + + ImGuiUtil.HoverTooltip( "Reload some specific files that the game keeps in memory at all times.\n" + + "You usually should not need to do this." ); + } + + private void DrawAdvancedSettings() + { + if( !Penumbra.Config.ShowAdvanced || !ImGui.CollapsingHeader( "Advanced" ) ) + { + return; + } + + DrawRequestedResourceLogging(); + DrawDisableSoundStreamingBox(); + DrawEnableHttpApiBox(); + DrawEnableDebugModeBox(); + DrawEnableFullResourceLoggingBox(); + DrawReloadResourceButton(); + ImGui.NewLine(); + } +} \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.SettingsTab.ModSelector.cs b/Penumbra/UI/ConfigWindow.SettingsTab.ModSelector.cs new file mode 100644 index 00000000..53ef7d84 --- /dev/null +++ b/Penumbra/UI/ConfigWindow.SettingsTab.ModSelector.cs @@ -0,0 +1,91 @@ +using System; +using ImGuiNET; +using OtterGui; +using OtterGui.Filesystem; +using OtterGui.Raii; + +namespace Penumbra.UI; + +public partial class ConfigWindow +{ + // Store separately to use IsItemDeactivatedAfterEdit. + private float _absoluteSelectorSize = Penumbra.Config.ModSelectorAbsoluteSize; + private int _relativeSelectorSize = Penumbra.Config.ModSelectorScaledSize; + + // Different supported sort modes as a combo. + private void DrawFolderSortType() + { + var sortMode = Penumbra.Config.SortMode; + ImGui.SetNextItemWidth( _inputTextWidth.X ); + using var combo = ImRaii.Combo( "##sortMode", sortMode.Data().Name ); + if( combo ) + { + foreach( var val in Enum.GetValues< SortMode >() ) + { + var (name, desc) = val.Data(); + if( ImGui.Selectable( name, val == sortMode ) && val != sortMode ) + { + Penumbra.Config.SortMode = val; + Selector.SetFilterDirty(); + Penumbra.Config.Save(); + } + + ImGuiUtil.HoverTooltip( desc ); + } + } + + combo.Dispose(); + ImGuiUtil.LabeledHelpMarker( "Sort Mode", "Choose the sort mode for the mod selector in the mods tab." ); + } + + private void DrawAbsoluteSizeSelector() + { + if( ImGuiUtil.DragFloat( "##absoluteSize", ref _absoluteSelectorSize, _inputTextWidth.X, 1, + Configuration.Constants.MinAbsoluteSize, Configuration.Constants.MaxAbsoluteSize, "%.0f" ) + && _absoluteSelectorSize != Penumbra.Config.ModSelectorAbsoluteSize ) + { + Penumbra.Config.ModSelectorAbsoluteSize = _absoluteSelectorSize; + Penumbra.Config.Save(); + } + + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker( "Mod Selector Absolute Size", "The minimal absolute size of the mod selector in the mod tab in pixels." ); + } + + private void DrawRelativeSizeSelector() + { + var scaleModSelector = Penumbra.Config.ScaleModSelector; + if( ImGui.Checkbox( "Scale Mod Selector With Window Size", ref scaleModSelector ) ) + { + Penumbra.Config.ScaleModSelector = scaleModSelector; + Penumbra.Config.Save(); + } + + ImGui.SameLine(); + if( ImGuiUtil.DragInt( "##relativeSize", ref _relativeSelectorSize, _inputTextWidth.X - ImGui.GetCursorPosX(), 0.1f, + Configuration.Constants.MinScaledSize, Configuration.Constants.MaxScaledSize, "%i%%" ) + && _relativeSelectorSize != Penumbra.Config.ModSelectorScaledSize ) + { + Penumbra.Config.ModSelectorScaledSize = _relativeSelectorSize; + Penumbra.Config.Save(); + } + + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker( "Mod Selector Relative Size", + "Instead of keeping the mod-selector in the Installed Mods tab a fixed width, this will let it scale with the total size of the Penumbra window." ); + } + + private void DrawModSelectorSettings() + { + if( !ImGui.CollapsingHeader( "Mod Selector" ) ) + { + return; + } + + DrawFolderSortType(); + DrawAbsoluteSizeSelector(); + DrawRelativeSizeSelector(); + + ImGui.NewLine(); + } +} \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.SettingsTab.cs b/Penumbra/UI/ConfigWindow.SettingsTab.cs index 224adccf..c07d4413 100644 --- a/Penumbra/UI/ConfigWindow.SettingsTab.cs +++ b/Penumbra/UI/ConfigWindow.SettingsTab.cs @@ -1,29 +1,57 @@ using System; using System.Diagnostics; using System.IO; -using System.Linq; using System.Numerics; using Dalamud.Interface; +using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Components; +using Dalamud.Logging; using ImGuiNET; using OtterGui; using OtterGui.Raii; -using Penumbra.GameData.ByteString; +using OtterGui.Widgets; using Penumbra.UI.Classes; +using Penumbra.UI.Custom; namespace Penumbra.UI; public partial class ConfigWindow { - private string _newModDirectory = string.Empty; + private string _settingsNewModDirectory = string.Empty; + private readonly FileDialogManager _dialogManager = new(); + private bool _dialogOpen; - private static bool DrawPressEnterWarning( string old ) + private static bool DrawPressEnterWarning( string old, float width ) { using var color = ImRaii.PushColor( ImGuiCol.Button, Colors.PressEnterWarningBg ); - var w = new Vector2( ImGui.CalcItemWidth(), 0 ); + var w = new Vector2( width, 0 ); return ImGui.Button( $"Press Enter or Click Here to Save (Current Directory: {old})", w ); } + private void DrawDirectoryPickerButton() + { + if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Folder.ToIconString(), ImGui.GetFrameHeight() * Vector2.One, + "Select a directory via dialog.", false, true ) ) + { + if( _dialogOpen ) + { + _dialogManager.Reset(); + _dialogOpen = false; + } + else + { + //_dialogManager.OpenFolderDialog( "Choose Mod Directory", ( b, s ) => + //{ + // _newModDirectory = b ? s : _newModDirectory; + // _dialogOpen = false; + //}, _newModDirectory, false); + _dialogOpen = true; + } + } + + _dialogManager.Draw(); + } + private static void DrawOpenDirectoryButton( int id, DirectoryInfo directory, bool condition ) { using var _ = ImRaii.PushId( id ); @@ -40,41 +68,54 @@ public partial class ConfigWindow private void DrawRootFolder() { - using var group = ImRaii.Group(); - ImGui.SetNextItemWidth( _inputTextWidth.X ); - var save = ImGui.InputText( "Root Directory", ref _newModDirectory, 255, ImGuiInputTextFlags.EnterReturnsTrue ); + // Initialize first time. + if( _settingsNewModDirectory.Length == 0 ) + { + _settingsNewModDirectory = Penumbra.Config.ModDirectory; + } + + var spacing = 3 * ImGuiHelpers.GlobalScale; + using var group = ImRaii.Group(); + ImGui.SetNextItemWidth( _inputTextWidth.X - spacing - ImGui.GetFrameHeight() ); + var save = ImGui.InputText( "##rootDirectory", ref _settingsNewModDirectory, 255, ImGuiInputTextFlags.EnterReturnsTrue ); + using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, new Vector2( spacing, 0 ) ); ImGui.SameLine(); - ImGuiComponents.HelpMarker( "This is where Penumbra will store your extracted mod files.\n" + DrawDirectoryPickerButton(); + style.Pop(); + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker( "Root Directory", "This is where Penumbra will store your extracted mod files.\n" + "TTMP files are not copied, just extracted.\n" + "This directory needs to be accessible and you need write access here.\n" + "It is recommended that this directory is placed on a fast hard drive, preferably an SSD.\n" + "It should also be placed near the root of a logical drive - the shorter the total path to this folder, the better.\n" + "Definitely do not place it in your Dalamud directory or any sub-directory thereof." ); - ImGui.SameLine(); - DrawOpenDirectoryButton( 0, Penumbra.ModManager.BasePath, Penumbra.ModManager.Valid ); group.Dispose(); + ImGui.SameLine(); + var pos = ImGui.GetCursorPosX(); + ImGui.NewLine(); - if( Penumbra.Config.ModDirectory == _newModDirectory || _newModDirectory.Length == 0 ) + if( Penumbra.Config.ModDirectory == _settingsNewModDirectory || _settingsNewModDirectory.Length == 0 ) { return; } - if( save || DrawPressEnterWarning( Penumbra.Config.ModDirectory ) ) + if( save || DrawPressEnterWarning( Penumbra.Config.ModDirectory, pos ) ) { - Penumbra.ModManager.DiscoverMods( _newModDirectory ); + Penumbra.ModManager.DiscoverMods( _settingsNewModDirectory ); } } - private void DrawRediscoverButton() + private static void DrawRediscoverButton() { + DrawOpenDirectoryButton( 0, Penumbra.ModManager.BasePath, Penumbra.ModManager.Valid ); + ImGui.SameLine(); if( ImGui.Button( "Rediscover Mods" ) ) { Penumbra.ModManager.DiscoverMods(); } - ImGui.SameLine(); - ImGuiComponents.HelpMarker( "Force Penumbra to completely re-scan your root directory as if it was restarted." ); + ImGuiUtil.HoverTooltip( "Force Penumbra to completely re-scan your root directory as if it was restarted." ); } private void DrawEnabledBox() @@ -86,190 +127,41 @@ public partial class ConfigWindow } } - private void DrawShowAdvancedBox() + private static void DrawShowAdvancedBox() { var showAdvanced = Penumbra.Config.ShowAdvanced; - if( ImGui.Checkbox( "Show Advanced Settings", ref showAdvanced ) ) + if( ImGui.Checkbox( "##showAdvanced", ref showAdvanced ) ) { Penumbra.Config.ShowAdvanced = showAdvanced; Penumbra.Config.Save(); } ImGui.SameLine(); - ImGuiComponents.HelpMarker( "Enable some advanced options in this window and in the mod selector.\n" + ImGuiUtil.LabeledHelpMarker( "Show Advanced Settings", "Enable some advanced options in this window and in the mod selector.\n" + "This is required to enable manually editing any mod information." ); } - private void DrawFolderSortType() + private static void DrawColorSettings() { - // TODO provide all options - var foldersFirst = Penumbra.Config.SortFoldersFirst; - if( ImGui.Checkbox( "Sort Mod-Folders Before Mods", ref foldersFirst ) ) + if( !ImGui.CollapsingHeader( "Colors" ) ) { - Penumbra.Config.SortFoldersFirst = foldersFirst; - Selector.SetFilterDirty(); - Penumbra.Config.Save(); + return; } - ImGui.SameLine(); - ImGuiComponents.HelpMarker( - "Prioritizes all mod-folders in the mod-selector in the Installed Mods tab so that folders come before single mods, instead of being sorted completely alphabetically" ); - } - - private void DrawScaleModSelectorBox() - { - // TODO set scale - var scaleModSelector = Penumbra.Config.ScaleModSelector; - if( ImGui.Checkbox( "Scale Mod Selector With Window Size", ref scaleModSelector ) ) + foreach( var color in Enum.GetValues< ColorId >() ) { - Penumbra.Config.ScaleModSelector = scaleModSelector; - Penumbra.Config.Save(); - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( - "Instead of keeping the mod-selector in the Installed Mods tab a fixed width, this will let it scale with the total size of the Penumbra window." ); - } - - private void DrawDisableSoundStreamingBox() - { - var tmp = Penumbra.Config.DisableSoundStreaming; - if( ImGui.Checkbox( "Disable Audio Streaming", ref tmp ) && tmp != Penumbra.Config.DisableSoundStreaming ) - { - Penumbra.Config.DisableSoundStreaming = tmp; - Penumbra.Config.Save(); - if( tmp ) + var (defaultColor, name, description) = color.Data(); + var currentColor = Penumbra.Config.Colors.TryGetValue( color, out var current ) ? current : defaultColor; + if( Widget.ColorPicker( name, description, currentColor, c => Penumbra.Config.Colors[ color ] = c, defaultColor ) ) { - _penumbra.MusicManager.DisableStreaming(); + Penumbra.Config.Save(); } - else - { - _penumbra.MusicManager.EnableStreaming(); - } - - Penumbra.ModManager.DiscoverMods(); } - ImGui.SameLine(); - ImGuiComponents.HelpMarker( - "Disable streaming in the games audio engine.\n" - + "If you do not disable streaming, you can not replace sound files in the game (*.scd files), they will be ignored by Penumbra.\n\n" - + "Only touch this if you experience sound problems.\n" - + "If you toggle this, make sure no modified or to-be-modified sound file is currently playing or was recently playing, else you might crash." ); + ImGui.NewLine(); } - private void DrawEnableHttpApiBox() - { - var http = Penumbra.Config.EnableHttpApi; - if( ImGui.Checkbox( "Enable HTTP API", ref http ) ) - { - if( http ) - { - _penumbra.CreateWebServer(); - } - else - { - _penumbra.ShutdownWebServer(); - } - - Penumbra.Config.EnableHttpApi = http; - Penumbra.Config.Save(); - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( - "Enables other applications, e.g. Anamnesis, to use some Penumbra functions, like requesting redraws." ); - } - - private static void DrawReloadResourceButton() - { - if( ImGui.Button( "Reload Resident Resources" ) ) - { - Penumbra.ResidentResources.Reload(); - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( "Reload some specific files that the game keeps in memory at all times.\n" - + "You usually should not need to do this." ); - } - - private void DrawEnableFullResourceLoggingBox() - { - var tmp = Penumbra.Config.EnableFullResourceLogging; - if( ImGui.Checkbox( "Enable Full Resource Logging", ref tmp ) && tmp != Penumbra.Config.EnableFullResourceLogging ) - { - if( tmp ) - { - Penumbra.ResourceLoader.EnableFullLogging(); - } - else - { - Penumbra.ResourceLoader.DisableFullLogging(); - } - - Penumbra.Config.EnableFullResourceLogging = tmp; - Penumbra.Config.Save(); - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( "[DEBUG] Enable the logging of all ResourceLoader events indiscriminately." ); - } - - private void DrawEnableDebugModeBox() - { - var tmp = Penumbra.Config.DebugMode; - if( ImGui.Checkbox( "Enable Debug Mode", ref tmp ) && tmp != Penumbra.Config.DebugMode ) - { - if( tmp ) - { - Penumbra.ResourceLoader.EnableDebug(); - } - else - { - Penumbra.ResourceLoader.DisableDebug(); - } - - Penumbra.Config.DebugMode = tmp; - Penumbra.Config.Save(); - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( "[DEBUG] Enable the Debug Tab and Resource Manager Tab as well as some additional data collection." ); - } - - private void DrawRequestedResourceLogging() - { - var tmp = Penumbra.Config.EnableResourceLogging; - if( ImGui.Checkbox( "Enable Requested Resource Logging", ref tmp ) ) - { - _penumbra.ResourceLogger.SetState( tmp ); - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( "Log all game paths FFXIV requests to the plugin log.\n" - + "You can filter the logged paths for those containing the entered string or matching the regex, if the entered string compiles to a valid regex.\n" - + "Red boundary indicates invalid regex." ); - ImGui.SameLine(); - var tmpString = Penumbra.Config.ResourceLoggingFilter; - using var color = ImRaii.PushColor( ImGuiCol.Border, 0xFF0000B0, !_penumbra.ResourceLogger.ValidRegex ); - using var style = ImRaii.PushStyle( ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale, !_penumbra.ResourceLogger.ValidRegex ); - if( ImGui.InputTextWithHint( "##ResourceLogFilter", "Filter...", ref tmpString, Utf8GamePath.MaxGamePathLength ) ) - { - _penumbra.ResourceLogger.SetFilter( tmpString ); - } - } - - private void DrawAdvancedSettings() - { - DrawRequestedResourceLogging(); - DrawDisableSoundStreamingBox(); - DrawEnableHttpApiBox(); - DrawReloadResourceButton(); - DrawEnableDebugModeBox(); - DrawEnableFullResourceLoggingBox(); - } - public void DrawSettingsTab() { using var tab = ImRaii.TabItem( "Settings" ); @@ -284,34 +176,15 @@ public partial class ConfigWindow return; } - DrawRootFolder(); - - DrawRediscoverButton(); - - ImGui.Dummy( _verticalSpace ); DrawEnabledBox(); - - ImGui.Dummy( _verticalSpace ); - DrawFolderSortType(); - DrawScaleModSelectorBox(); DrawShowAdvancedBox(); + ImGui.NewLine(); + DrawRootFolder(); + DrawRediscoverButton(); + ImGui.NewLine(); - if( Penumbra.Config.ShowAdvanced ) - { - DrawAdvancedSettings(); - } - - if( ImGui.CollapsingHeader( "Colors" ) ) - { - foreach( var color in Enum.GetValues< ColorId >() ) - { - var (defaultColor, name, description) = color.Data(); - var currentColor = Penumbra.Config.Colors.TryGetValue( color, out var current ) ? current : defaultColor; - if( ImGuiUtil.ColorPicker( name, description, currentColor, c => Penumbra.Config.Colors[ color ] = c, defaultColor ) ) - { - Penumbra.Config.Save(); - } - } - } + DrawModSelectorSettings(); + DrawColorSettings(); + DrawAdvancedSettings(); } } \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.cs b/Penumbra/UI/ConfigWindow.cs index 58250e6b..338fa511 100644 --- a/Penumbra/UI/ConfigWindow.cs +++ b/Penumbra/UI/ConfigWindow.cs @@ -27,7 +27,7 @@ public sealed partial class ConfigWindow : Window, IDisposable RespectCloseHotkey = true; SizeConstraints = new WindowSizeConstraints() { - MinimumSize = new Vector2( 1024, 768 ), + MinimumSize = new Vector2( 800, 600 ), MaximumSize = new Vector2( 4096, 2160 ), }; } @@ -39,6 +39,7 @@ public sealed partial class ConfigWindow : Window, IDisposable DrawSettingsTab(); DrawModsTab(); DrawCollectionsTab(); + DrawChangedItemTab(); DrawEffectiveChangesTab(); DrawDebugTab(); DrawResourceManagerTab(); @@ -50,19 +51,14 @@ public sealed partial class ConfigWindow : Window, IDisposable } private static string GetLabel() - { - var version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? ""; - return version.Length == 0 + => Penumbra.Version.Length == 0 ? "Penumbra###PenumbraConfigWindow" - : $"Penumbra v{version}###PenumbraConfigWindow"; - } + : $"Penumbra v{Penumbra.Version}###PenumbraConfigWindow"; - private Vector2 _verticalSpace; private Vector2 _inputTextWidth; private void SetupSizes() { - _verticalSpace = new Vector2( 0, 20f * ImGuiHelpers.GlobalScale ); - _inputTextWidth = new Vector2( 450f * ImGuiHelpers.GlobalScale, 0 ); + _inputTextWidth = new Vector2( 350f * ImGuiHelpers.GlobalScale, 0 ); } } \ No newline at end of file diff --git a/Penumbra/UI/Custom/ImGuiFramedGroup.cs b/Penumbra/UI/Custom/ImGuiFramedGroup.cs deleted file mode 100644 index 0330eb32..00000000 --- a/Penumbra/UI/Custom/ImGuiFramedGroup.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Numerics; -using ImGuiNET; - -namespace Penumbra.UI.Custom -{ - public static partial class ImGuiCustom - { - public static void BeginFramedGroup( string label ) - => BeginFramedGroupInternal( ref label, Vector2.Zero, false ); - - public static void BeginFramedGroup( string label, Vector2 minSize ) - => BeginFramedGroupInternal( ref label, minSize, false ); - - public static bool BeginFramedGroupEdit( ref string label ) - => BeginFramedGroupInternal( ref label, Vector2.Zero, true ); - - public static bool BeginFramedGroupEdit( ref string label, Vector2 minSize ) - => BeginFramedGroupInternal( ref label, minSize, true ); - - private static bool BeginFramedGroupInternal( ref string label, Vector2 minSize, bool edit ) - { - var itemSpacing = ImGui.GetStyle().ItemSpacing; - var frameHeight = ImGui.GetFrameHeight(); - var halfFrameHeight = new Vector2( ImGui.GetFrameHeight() / 2, 0 ); - - ImGui.BeginGroup(); // First group - - ImGui.PushStyleVar( ImGuiStyleVar.FramePadding, Vector2.Zero ); - ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, Vector2.Zero ); - - ImGui.BeginGroup(); // Second group - - var effectiveSize = minSize; - if( effectiveSize.X < 0 ) - { - effectiveSize.X = ImGui.GetContentRegionAvail().X; - } - - // Ensure width. - ImGui.Dummy( Vector2.UnitX * effectiveSize.X ); - // Ensure left half boundary width/distance. - ImGui.Dummy( halfFrameHeight ); - - ImGui.SameLine(); - ImGui.BeginGroup(); // Third group. - // Ensure right half of boundary width/distance - ImGui.Dummy( halfFrameHeight ); - - // Label block - ImGui.SameLine(); - var ret = false; - if( edit ) - { - ret = ResizingTextInput( ref label, 1024 ); - } - else - { - ImGui.TextUnformatted( label ); - } - - var labelMin = ImGui.GetItemRectMin(); - var labelMax = ImGui.GetItemRectMax(); - ImGui.SameLine(); - // Ensure height and distance to label. - ImGui.Dummy( Vector2.UnitY * ( frameHeight + itemSpacing.Y ) ); - - ImGui.BeginGroup(); // Fourth Group. - - ImGui.PopStyleVar( 2 ); - - // This seems wrong? - //ImGui.SetWindowSize( new Vector2( ImGui.GetWindowSize().X - frameHeight, ImGui.GetWindowSize().Y ) ); - - var itemWidth = ImGui.CalcItemWidth(); - ImGui.PushItemWidth( Math.Max( 0f, itemWidth - frameHeight ) ); - - LabelStack.Add( ( labelMin, labelMax ) ); - return ret; - } - - private static void DrawClippedRect( Vector2 clipMin, Vector2 clipMax, Vector2 drawMin, Vector2 drawMax, uint color, float thickness ) - { - ImGui.PushClipRect( clipMin, clipMax, true ); - ImGui.GetWindowDrawList().AddRect( drawMin, drawMax, color, thickness ); - ImGui.PopClipRect(); - } - - public static void EndFramedGroup() - { - var borderColor = ImGui.ColorConvertFloat4ToU32( ImGui.GetStyle().Colors[ ( int )ImGuiCol.Border ] ); - var itemSpacing = ImGui.GetStyle().ItemSpacing; - var frameHeight = ImGui.GetFrameHeight(); - var halfFrameHeight = new Vector2( ImGui.GetFrameHeight() / 2, 0 ); - - ImGui.PopItemWidth(); - - ImGui.PushStyleVar( ImGuiStyleVar.FramePadding, Vector2.Zero ); - ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, Vector2.Zero ); - - ImGui.EndGroup(); // Close fourth group - ImGui.EndGroup(); // Close third group - - ImGui.SameLine(); - // Ensure right distance. - ImGui.Dummy( halfFrameHeight ); - // Ensure bottom distance - ImGui.Dummy( Vector2.UnitY * ( frameHeight / 2 - itemSpacing.Y ) ); - ImGui.EndGroup(); // Close second group - - var itemMin = ImGui.GetItemRectMin(); - var itemMax = ImGui.GetItemRectMax(); - var (currentLabelMin, currentLabelMax) = LabelStack[ ^1 ]; - LabelStack.RemoveAt( LabelStack.Count - 1 ); - - var halfFrame = new Vector2( frameHeight / 8, frameHeight / 2 ); - currentLabelMin.X -= itemSpacing.X; - currentLabelMax.X += itemSpacing.X; - var frameMin = itemMin + halfFrame; - var frameMax = itemMax - Vector2.UnitX * halfFrame.X; - - // Left - DrawClippedRect( new Vector2( -float.MaxValue, -float.MaxValue ), new Vector2( currentLabelMin.X, float.MaxValue ), frameMin, - frameMax, borderColor, halfFrame.X ); - // Right - DrawClippedRect( new Vector2( currentLabelMax.X, -float.MaxValue ), new Vector2( float.MaxValue, float.MaxValue ), frameMin, - frameMax, borderColor, halfFrame.X ); - // Top - DrawClippedRect( new Vector2( currentLabelMin.X, -float.MaxValue ), new Vector2( currentLabelMax.X, currentLabelMin.Y ), frameMin, - frameMax, borderColor, halfFrame.X ); - // Bottom - DrawClippedRect( new Vector2( currentLabelMin.X, currentLabelMax.Y ), new Vector2( currentLabelMax.X, float.MaxValue ), frameMin, - frameMax, borderColor, halfFrame.X ); - - ImGui.PopStyleVar( 2 ); - // This seems wrong? - // ImGui.SetWindowSize( new Vector2( ImGui.GetWindowSize().X + frameHeight, ImGui.GetWindowSize().Y ) ); - ImGui.Dummy( Vector2.Zero ); - - ImGui.EndGroup(); // Close first group - } - - private static readonly List< (Vector2, Vector2) > LabelStack = new(); - } -} \ No newline at end of file diff --git a/Penumbra/UI/Custom/ImGuiRenameableCombo.cs b/Penumbra/UI/Custom/ImGuiRenameableCombo.cs deleted file mode 100644 index 2e81f125..00000000 --- a/Penumbra/UI/Custom/ImGuiRenameableCombo.cs +++ /dev/null @@ -1,54 +0,0 @@ -using ImGuiNET; - -namespace Penumbra.UI.Custom -{ - public static partial class ImGuiCustom - { - public static bool RenameableCombo( string label, ref int currentItem, out string newName, string[] items, int numItems ) - { - var ret = false; - newName = ""; - var newOption = ""; - if( !ImGui.BeginCombo( label, numItems > 0 ? items[ currentItem ] : newOption ) ) - { - return false; - } - - for( var i = 0; i < numItems; ++i ) - { - var isSelected = i == currentItem; - ImGui.SetNextItemWidth( -1 ); - if( ImGui.InputText( $"##{label}_{i}", ref items[ i ], 64, ImGuiInputTextFlags.EnterReturnsTrue ) ) - { - currentItem = i; - newName = items[ i ]; - ret = true; - ImGui.CloseCurrentPopup(); - } - - if( isSelected ) - { - ImGui.SetItemDefaultFocus(); - } - } - - ImGui.SetNextItemWidth( -1 ); - if( ImGui.InputTextWithHint( $"##{label}_new", "Add new item...", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue ) ) - { - currentItem = numItems; - newName = newOption; - ret = true; - ImGui.CloseCurrentPopup(); - } - - if( numItems == 0 ) - { - ImGui.SetItemDefaultFocus(); - } - - ImGui.EndCombo(); - - return ret; - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/Custom/ImGuiResizingTextInput.cs b/Penumbra/UI/Custom/ImGuiResizingTextInput.cs deleted file mode 100644 index f7ef2c53..00000000 --- a/Penumbra/UI/Custom/ImGuiResizingTextInput.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Collections.Generic; -using ImGuiNET; - -namespace Penumbra.UI.Custom -{ - public static partial class ImGuiCustom - { - public static bool InputOrText( bool editable, string label, ref string text, uint maxLength ) - { - if( editable ) - { - return ResizingTextInput( label, ref text, maxLength ); - } - - ImGui.Text( text ); - return false; - } - - public static bool ResizingTextInput( string label, ref string input, uint maxLength ) - => ResizingTextInputIntern( label, ref input, maxLength ).Item1; - - public static bool ResizingTextInput( ref string input, uint maxLength ) - { - var (ret, id) = ResizingTextInputIntern( $"##{input}", ref input, maxLength ); - if( ret ) - { - TextInputWidths.Remove( id ); - } - - return ret; - } - - private static (bool, uint) ResizingTextInputIntern( string label, ref string input, uint maxLength ) - { - var id = ImGui.GetID( label ); - if( !TextInputWidths.TryGetValue( id, out var width ) ) - { - width = ImGui.CalcTextSize( input ).X + 10; - } - - ImGui.SetNextItemWidth( width ); - var ret = ImGui.InputText( label, ref input, maxLength, ImGuiInputTextFlags.EnterReturnsTrue ); - TextInputWidths[ id ] = ImGui.CalcTextSize( input ).X + 10; - return ( ret, id ); - } - - private static readonly Dictionary< uint, float > TextInputWidths = new(); - } -} \ No newline at end of file diff --git a/Penumbra/UI/Custom/ImGuiUtil.cs b/Penumbra/UI/Custom/ImGuiUtil.cs deleted file mode 100644 index f2082e8d..00000000 --- a/Penumbra/UI/Custom/ImGuiUtil.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Numerics; -using System.Windows.Forms; -using Dalamud.Interface; -using ImGuiNET; -using Penumbra.GameData.ByteString; - -namespace Penumbra.UI.Custom -{ - public static partial class ImGuiCustom - { - public static void CopyOnClickSelectable( string text ) - { - if( ImGui.Selectable( text ) ) - { - Clipboard.SetText( text ); - } - - if( ImGui.IsItemHovered() ) - { - ImGui.SetTooltip( "Click to copy to clipboard." ); - } - } - - public static unsafe void CopyOnClickSelectable( Utf8String text ) - { - if( ImGuiNative.igSelectable_Bool( text.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero ) != 0 ) - { - ImGuiNative.igSetClipboardText( text.Path ); - } - - if( ImGui.IsItemHovered() ) - { - ImGui.SetTooltip( "Click to copy to clipboard." ); - } - } - } - - public static partial class ImGuiCustom - { - public static void VerticalDistance( float distance ) - { - ImGui.SetCursorPosY( ImGui.GetCursorPosY() + distance * ImGuiHelpers.GlobalScale ); - } - - public static void RightJustifiedText( float pos, string text ) - { - ImGui.SetCursorPosX( pos - ImGui.CalcTextSize( text ).X - 2 * ImGui.GetStyle().ItemSpacing.X ); - ImGui.Text( text ); - } - - public static void RightJustifiedLabel( float pos, string text ) - { - ImGui.SetCursorPosX( pos - ImGui.CalcTextSize( text ).X - ImGui.GetStyle().ItemSpacing.X / 2 ); - ImGui.Text( text ); - ImGui.SameLine( pos ); - } - } - - public static partial class ImGuiCustom - { - public static void HoverTooltip( string text ) - { - if( ImGui.IsItemHovered() ) - { - ImGui.SetTooltip( text ); - } - } - } - - public static partial class ImGuiCustom - { - public static bool DisableButton( string label, bool condition ) - { - using var alpha = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f, !condition ); - return ImGui.Button( label ) && condition; - } - } - - public static partial class ImGuiCustom - { - public static void PrintIcon( FontAwesomeIcon icon ) - { - ImGui.PushFont( UiBuilder.IconFont ); - ImGui.TextUnformatted( icon.ToIconString() ); - ImGui.PopFont(); - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/Custom/Raii/Color.cs b/Penumbra/UI/Custom/Raii/Color.cs deleted file mode 100644 index 95541bd8..00000000 --- a/Penumbra/UI/Custom/Raii/Color.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Numerics; -using ImGuiNET; - -namespace Penumbra.UI.Custom -{ - public static partial class ImGuiRaii - { - public static Color PushColor( ImGuiCol idx, uint color, bool condition = true ) - => new Color().Push( idx, color, condition ); - - public static Color PushColor( ImGuiCol idx, Vector4 color, bool condition = true ) - => new Color().Push( idx, color, condition ); - - public class Color : IDisposable - { - private int _count; - - public Color Push( ImGuiCol idx, uint color, bool condition = true ) - { - if( condition ) - { - ImGui.PushStyleColor( idx, color ); - ++_count; - } - - return this; - } - - public Color Push( ImGuiCol idx, Vector4 color, bool condition = true ) - { - if( condition ) - { - ImGui.PushStyleColor( idx, color ); - ++_count; - } - - return this; - } - - public void Pop( int num = 1 ) - { - num = Math.Min( num, _count ); - _count -= num; - ImGui.PopStyleColor( num ); - } - - public void Dispose() - => Pop( _count ); - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/Custom/Raii/EndStack.cs b/Penumbra/UI/Custom/Raii/EndStack.cs deleted file mode 100644 index ea1e03f9..00000000 --- a/Penumbra/UI/Custom/Raii/EndStack.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Penumbra.UI.Custom -{ - public static partial class ImGuiRaii - { - public static EndStack DeferredEnd( Action a, bool condition = true ) - => new EndStack().Push( a, condition ); - - public class EndStack : IDisposable - { - private readonly Stack< Action > _cleanActions = new(); - - public EndStack Push( Action a, bool condition = true ) - { - if( condition ) - { - _cleanActions.Push( a ); - } - - return this; - } - - - public EndStack Pop( int num = 1 ) - { - while( num-- > 0 && _cleanActions.TryPop( out var action ) ) - { - action.Invoke(); - } - - return this; - } - - public void Dispose() - => Pop( _cleanActions.Count ); - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/Custom/Raii/Font.cs b/Penumbra/UI/Custom/Raii/Font.cs deleted file mode 100644 index e9656d4d..00000000 --- a/Penumbra/UI/Custom/Raii/Font.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using ImGuiNET; - -namespace Penumbra.UI.Custom -{ - public static partial class ImGuiRaii - { - public static Font PushFont( ImFontPtr font ) - => new( font ); - - public class Font : IDisposable - { - private int _count; - - public Font( ImFontPtr font ) - => Push( font ); - - public Font Push( ImFontPtr font ) - { - ImGui.PushFont( font ); - ++_count; - return this; - } - - public void Pop( int num = 1 ) - { - num = Math.Min( num, _count ); - _count -= num; - while( num-- > 0 ) - { - ImGui.PopFont(); - } - } - - public void Dispose() - => Pop( _count ); - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/Custom/Raii/Indent.cs b/Penumbra/UI/Custom/Raii/Indent.cs deleted file mode 100644 index da9abd8e..00000000 --- a/Penumbra/UI/Custom/Raii/Indent.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Diagnostics; -using Dalamud.Interface; -using ImGuiNET; - -namespace Penumbra.UI.Custom -{ - public static partial class ImGuiRaii - { - public static Indent PushIndent( float f, bool scaled = true, bool condition = true ) - => new Indent().Push( f, condition ); - - public static Indent PushIndent( int i = 1, bool scaled = true, bool condition = true ) - => new Indent().Push( i, condition ); - - public class Indent : IDisposable - { - private float _indentation; - - public Indent Push( float indent, bool scaled = true, bool condition = true ) - { - Debug.Assert( indent >= 0f ); - if( condition ) - { - if( scaled ) - { - indent *= ImGuiHelpers.GlobalScale; - } - - ImGui.Indent( indent ); - _indentation += indent; - } - - return this; - } - - public Indent Push( uint i = 1, bool scaled = true, bool condition = true ) - { - if( condition ) - { - var spacing = i * ImGui.GetStyle().IndentSpacing * ( scaled ? ImGuiHelpers.GlobalScale : 1f ); - ImGui.Indent( spacing ); - _indentation += spacing; - } - - return this; - } - - public void Pop( float indent, bool scaled = true ) - { - if( scaled ) - { - indent *= ImGuiHelpers.GlobalScale; - } - - Debug.Assert( indent >= 0f ); - ImGui.Unindent( indent ); - _indentation -= indent; - } - - public void Pop( uint i, bool scaled = true ) - { - var spacing = i * ImGui.GetStyle().IndentSpacing * ( scaled ? ImGuiHelpers.GlobalScale : 1f ); - ImGui.Unindent( spacing ); - _indentation += spacing; - } - - public void Dispose() - => Pop( _indentation, false ); - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/Custom/Raii/Style.cs b/Penumbra/UI/Custom/Raii/Style.cs deleted file mode 100644 index acc29ab6..00000000 --- a/Penumbra/UI/Custom/Raii/Style.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Numerics; -using ImGuiNET; - -namespace Penumbra.UI.Custom -{ - public static partial class ImGuiRaii - { - public static Style PushStyle( ImGuiStyleVar idx, float value, bool condition = true ) - => new Style().Push( idx, value, condition ); - - public static Style PushStyle( ImGuiStyleVar idx, Vector2 value, bool condition = true ) - => new Style().Push( idx, value, condition ); - - public class Style : IDisposable - { - private int _count; - - [System.Diagnostics.Conditional( "DEBUG" )] - private static void CheckStyleIdx( ImGuiStyleVar idx, Type type ) - { - var shouldThrow = idx switch - { - ImGuiStyleVar.Alpha => type != typeof( float ), - ImGuiStyleVar.WindowPadding => type != typeof( Vector2 ), - ImGuiStyleVar.WindowRounding => type != typeof( float ), - ImGuiStyleVar.WindowBorderSize => type != typeof( float ), - ImGuiStyleVar.WindowMinSize => type != typeof( Vector2 ), - ImGuiStyleVar.WindowTitleAlign => type != typeof( Vector2 ), - ImGuiStyleVar.ChildRounding => type != typeof( float ), - ImGuiStyleVar.ChildBorderSize => type != typeof( float ), - ImGuiStyleVar.PopupRounding => type != typeof( float ), - ImGuiStyleVar.PopupBorderSize => type != typeof( float ), - ImGuiStyleVar.FramePadding => type != typeof( Vector2 ), - ImGuiStyleVar.FrameRounding => type != typeof( float ), - ImGuiStyleVar.FrameBorderSize => type != typeof( float ), - ImGuiStyleVar.ItemSpacing => type != typeof( Vector2 ), - ImGuiStyleVar.ItemInnerSpacing => type != typeof( Vector2 ), - ImGuiStyleVar.IndentSpacing => type != typeof( float ), - ImGuiStyleVar.CellPadding => type != typeof( Vector2 ), - ImGuiStyleVar.ScrollbarSize => type != typeof( float ), - ImGuiStyleVar.ScrollbarRounding => type != typeof( float ), - ImGuiStyleVar.GrabMinSize => type != typeof( float ), - ImGuiStyleVar.GrabRounding => type != typeof( float ), - ImGuiStyleVar.TabRounding => type != typeof( float ), - ImGuiStyleVar.ButtonTextAlign => type != typeof( Vector2 ), - ImGuiStyleVar.SelectableTextAlign => type != typeof( Vector2 ), - _ => throw new ArgumentOutOfRangeException( nameof( idx ), idx, null ), - }; - - if( shouldThrow ) - { - throw new ArgumentException( $"Unable to push {type} to {idx}." ); - } - } - - public Style Push( ImGuiStyleVar idx, float value, bool condition = true ) - { - if( condition ) - { - CheckStyleIdx( idx, typeof( float ) ); - ImGui.PushStyleVar( idx, value ); - ++_count; - } - - return this; - } - - public Style Push( ImGuiStyleVar idx, Vector2 value, bool condition = true ) - { - if( condition ) - { - CheckStyleIdx( idx, typeof( Vector2 ) ); - ImGui.PushStyleVar( idx, value ); - ++_count; - } - - return this; - } - - public void Pop( int num = 1 ) - { - num = Math.Min( num, _count ); - _count -= num; - ImGui.PopStyleVar( num ); - } - - public void Dispose() - => Pop( _count ); - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/LaunchButton.cs b/Penumbra/UI/LaunchButton.cs index 0033916f..425f7e3e 100644 --- a/Penumbra/UI/LaunchButton.cs +++ b/Penumbra/UI/LaunchButton.cs @@ -5,6 +5,8 @@ using ImGuiScene; namespace Penumbra.UI; +// A Launch Button used in the title screen of the game, +// using the Dalamud-provided collapsible submenu. public class LaunchButton : IDisposable { private readonly ConfigWindow _configWindow; diff --git a/Penumbra/UI/MenuTabs/TabBrowser.cs b/Penumbra/UI/MenuTabs/TabBrowser.cs deleted file mode 100644 index 1dc57a16..00000000 --- a/Penumbra/UI/MenuTabs/TabBrowser.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Collections.Generic; -using System.Runtime.InteropServices; -using OtterGui.Raii; -using Penumbra.Mods; -using Penumbra.UI.Classes; - -namespace Penumbra.UI; - -[StructLayout( LayoutKind.Sequential, Pack = 1 )] -public struct ModState -{ - public uint Color; -} - -public partial class SettingsInterface -{ - private class TabBrowser - { - private readonly ModFileSystemA _fileSystem; - private readonly ModFileSystemSelector _selector; - - public TabBrowser() - { - _fileSystem = ModFileSystemA.Load(); - _selector = new ModFileSystemSelector( _fileSystem, new HashSet() ); - } - - public void Draw() - { - using var ret = ImRaii.TabItem( "Available Mods" ); - if( !ret ) - { - return; - } - - _selector.Draw( 400 ); - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabChangedItems.cs b/Penumbra/UI/MenuTabs/TabChangedItems.cs deleted file mode 100644 index 56bddabd..00000000 --- a/Penumbra/UI/MenuTabs/TabChangedItems.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using ImGuiNET; -using Penumbra.UI.Custom; - -namespace Penumbra.UI; - -public partial class SettingsInterface -{ - private class TabChangedItems - { - private const string LabelTab = "Changed Items"; - private readonly SettingsInterface _base; - - private string _filter = string.Empty; - private string _filterLower = string.Empty; - - public TabChangedItems( SettingsInterface ui ) - => _base = ui; - - public void Draw() - { - if( !ImGui.BeginTabItem( LabelTab ) ) - { - return; - } - - var items = Penumbra.CollectionManager.Default.ChangedItems; - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); - - ImGui.SetNextItemWidth( -1 ); - if( ImGui.InputTextWithHint( "##ChangedItemsFilter", "Filter...", ref _filter, 64 ) ) - { - _filterLower = _filter.ToLowerInvariant(); - } - - if( !ImGui.BeginTable( "##ChangedItemsTable", 1, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, AutoFillSize ) ) - { - return; - } - - raii.Push( ImGui.EndTable ); - - var list = items.AsEnumerable(); - - if( _filter.Length > 0 ) - { - list = list.Where( kvp => kvp.Key.ToLowerInvariant().Contains( _filterLower ) ); - } - - foreach( var (name, data) in list ) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - _base.DrawChangedItem( name, data, ImGui.GetStyle().ScrollbarSize ); - } - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabCollections.cs b/Penumbra/UI/MenuTabs/TabCollections.cs deleted file mode 100644 index 760da897..00000000 --- a/Penumbra/UI/MenuTabs/TabCollections.cs +++ /dev/null @@ -1,420 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; -using Dalamud.Interface.Components; -using Dalamud.Logging; -using ImGuiNET; -using Penumbra.Collections; -using Penumbra.Mods; -using Penumbra.UI.Custom; -using Penumbra.Util; - -namespace Penumbra.UI; - -public partial class SettingsInterface -{ - private class TabCollections - { - private const string CharacterCollectionHelpPopup = "Character Collection Information"; - private readonly Selector _selector; - private string _collectionNames = null!; - private string _collectionNamesWithNone = null!; - private ModCollection[] _collections = null!; - private int _currentCollectionIndex; - private int _currentDefaultIndex; - private readonly Dictionary< string, int > _currentCharacterIndices = new(); - private string _newCollectionName = string.Empty; - private string _newCharacterName = string.Empty; - - private void UpdateNames() - { - _collections = Penumbra.CollectionManager.Prepend( ModCollection.Empty ).ToArray(); - _collectionNames = string.Join( "\0", _collections.Skip( 1 ).Select( c => c.Name ) ) + '\0'; - _collectionNamesWithNone = "None\0" + _collectionNames; - UpdateIndices(); - } - - - private int GetIndex( ModCollection collection ) - { - var ret = _collections.IndexOf( c => c.Name == collection.Name ); - if( ret < 0 ) - { - PluginLog.Error( $"Collection {collection.Name} is not found in collections." ); - return 0; - } - - return ret; - } - - private void UpdateIndex() - => _currentCollectionIndex = GetIndex( Penumbra.CollectionManager.Current ) - 1; - - public void UpdateDefaultIndex() - => _currentDefaultIndex = GetIndex( Penumbra.CollectionManager.Default ); - - private void UpdateCharacterIndices() - { - _currentCharacterIndices.Clear(); - foreach( var (character, collection) in Penumbra.CollectionManager.Characters ) - { - _currentCharacterIndices[ character ] = GetIndex( collection ); - } - } - - private void UpdateIndices() - { - UpdateIndex(); - UpdateDefaultIndex(); - UpdateCharacterIndices(); - } - - public TabCollections( Selector selector ) - { - _selector = selector; - UpdateNames(); - } - - private void CreateNewCollection( bool duplicate ) - { - if( Penumbra.CollectionManager.AddCollection( _newCollectionName, duplicate ? Penumbra.CollectionManager.Current : null ) ) - { - UpdateNames(); - SetCurrentCollection( Penumbra.CollectionManager[ _newCollectionName ]!, true ); - } - - _newCollectionName = string.Empty; - } - - private static void DrawCleanCollectionButton() - { - if( ImGui.Button( "Clean Settings" ) ) - { - Penumbra.CollectionManager.Current.CleanUnavailableSettings(); - } - - ImGuiCustom.HoverTooltip( - "Remove all stored settings for mods not currently available and fix invalid settings.\nUse at own risk." ); - } - - private void DrawNewCollectionInput() - { - ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth ); - ImGui.InputTextWithHint( "##New Collection", "New Collection Name", ref _newCollectionName, 64 ); - ImGui.SameLine(); - ImGuiComponents.HelpMarker( - "A collection is a set of settings for your installed mods, including their enabled status, their priorities and their mod-specific configuration.\n" - + "You can use multiple collections to quickly switch between sets of mods." ); - - using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f, _newCollectionName.Length == 0 ); - - if( ImGui.Button( "Create New Empty Collection" ) && _newCollectionName.Length > 0 ) - { - CreateNewCollection( false ); - } - - var hover = ImGui.IsItemHovered(); - ImGui.SameLine(); - if( ImGui.Button( "Duplicate Current Collection" ) && _newCollectionName.Length > 0 ) - { - CreateNewCollection( true ); - } - - hover |= ImGui.IsItemHovered(); - - style.Pop(); - if( _newCollectionName.Length == 0 && hover ) - { - ImGui.SetTooltip( "Please enter a name before creating a collection." ); - } - - var deleteCondition = Penumbra.CollectionManager.Current.Name != ModCollection.DefaultCollection; - ImGui.SameLine(); - if( ImGuiCustom.DisableButton( "Delete Current Collection", deleteCondition ) ) - { - Penumbra.CollectionManager.RemoveCollection( Penumbra.CollectionManager.Current ); - SetCurrentCollection( Penumbra.CollectionManager.Current, true ); - UpdateNames(); - } - - if( !deleteCondition ) - { - ImGuiCustom.HoverTooltip( "You can not delete the default collection." ); - } - - if( Penumbra.Config.ShowAdvanced ) - { - ImGui.SameLine(); - DrawCleanCollectionButton(); - } - } - - private void SetCurrentCollection( int idx, bool force ) - { - if( !force && idx == _currentCollectionIndex ) - { - return; - } - - Penumbra.CollectionManager.SetCollection( _collections[ idx + 1 ], ModCollection.Type.Current ); - _currentCollectionIndex = idx; - _selector.Cache.TriggerListReset(); - if( _selector.Mod != null ) - { - _selector.SelectModOnUpdate( _selector.Mod.Data.BasePath.Name ); - } - } - - public void SetCurrentCollection( ModCollection collection, bool force = false ) - { - var idx = Array.IndexOf( _collections, collection ) - 1; - if( idx >= 0 ) - { - SetCurrentCollection( idx, force ); - } - } - - public void DrawCurrentCollectionSelector( bool tooltip ) - { - var index = _currentCollectionIndex; - ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth ); - var combo = ImGui.Combo( "Current Collection", ref index, _collectionNames ); - ImGui.SameLine(); - ImGuiComponents.HelpMarker( - "This collection will be modified when using the Installed Mods tab and making changes. It does not apply to anything by itself." ); - - if( combo ) - { - SetCurrentCollection( index, false ); - } - } - - private static void DrawInheritance( ModCollection collection ) - { - ImGui.PushID( collection.Index ); - if( ImGui.TreeNodeEx( collection.Name, ImGuiTreeNodeFlags.DefaultOpen ) ) - { - foreach( var inheritance in collection.Inheritance ) - { - DrawInheritance( inheritance ); - } - } - - ImGui.PopID(); - } - - private void DrawCurrentCollectionInheritance() - { - if( !ImGui.BeginListBox( "##inheritanceList", - new Vector2( SettingsMenu.InputTextWidth, ImGui.GetTextLineHeightWithSpacing() * 10 ) ) ) - { - return; - } - - using var end = ImGuiRaii.DeferredEnd( ImGui.EndListBox ); - DrawInheritance( _collections[ _currentCollectionIndex + 1 ] ); - } - - private static int _newInheritanceIdx = 0; - - private void DrawNewInheritanceSelection() - { - ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth - ImGui.GetFrameHeight() - ImGui.GetStyle().ItemSpacing.X ); - if( ImGui.BeginCombo( "##newInheritance", Penumbra.CollectionManager[ _newInheritanceIdx ].Name ) ) - { - using var end = ImGuiRaii.DeferredEnd( ImGui.EndCombo ); - foreach( var collection in Penumbra.CollectionManager ) - { - if( ImGui.Selectable( collection.Name, _newInheritanceIdx == collection.Index ) ) - { - _newInheritanceIdx = collection.Index; - } - } - } - - ImGui.SameLine(); - var valid = _newInheritanceIdx > ModCollection.Empty.Index - && _collections[ _currentCollectionIndex + 1 ].Index != _newInheritanceIdx - && _collections[ _currentCollectionIndex + 1 ].Inheritance.All( c => c.Index != _newInheritanceIdx ); - using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f, !valid ); - using var font = ImGuiRaii.PushFont( UiBuilder.IconFont ); - if( ImGui.Button( $"{FontAwesomeIcon.Plus.ToIconString()}##newInheritanceAdd", ImGui.GetFrameHeight() * Vector2.One ) && valid ) - { - _collections[ _currentCollectionIndex + 1 ].AddInheritance( Penumbra.CollectionManager[ _newInheritanceIdx ] ); - } - - style.Pop(); - font.Pop(); - ImGuiComponents.HelpMarker( "Add a new inheritance to the collection." ); - } - - private void DrawDefaultCollectionSelector() - { - var index = _currentDefaultIndex; - ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth ); - if( ImGui.Combo( "##Default Collection", ref index, _collectionNamesWithNone ) && index != _currentDefaultIndex ) - { - Penumbra.CollectionManager.SetCollection( _collections[ index ], ModCollection.Type.Default ); - _currentDefaultIndex = index; - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( - "Mods in the default collection are loaded for any character that is not explicitly named in the character collections below.\n" - + "They also take precedence before the forced collection." ); - - ImGui.SameLine(); - ImGui.Text( "Default Collection" ); - } - - private void DrawNewCharacterCollection() - { - ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth ); - ImGui.InputTextWithHint( "##New Character", "New Character Name", ref _newCharacterName, 32 ); - ImGui.SameLine(); - ImGuiComponents.HelpMarker( "Click Me for Information!" ); - ImGui.OpenPopupOnItemClick( CharacterCollectionHelpPopup, ImGuiPopupFlags.MouseButtonLeft ); - - ImGui.SameLine(); - if( ImGuiCustom.DisableButton( "Create New Character Collection", - _newCharacterName.Length > 0 && Penumbra.Config.HasReadCharacterCollectionDesc ) ) - { - Penumbra.CollectionManager.CreateCharacterCollection( _newCharacterName ); - _currentCharacterIndices[ _newCharacterName ] = 0; - _newCharacterName = string.Empty; - } - - ImGuiCustom.HoverTooltip( "Please enter a Character name before creating the collection.\n" - + "You also need to have read the help text for character collections." ); - - DrawCharacterCollectionHelp(); - } - - private static void DrawCharacterCollectionHelp() - { - var size = new Vector2( 700 * ImGuiHelpers.GlobalScale, 34 * ImGui.GetTextLineHeightWithSpacing() ); - ImGui.SetNextWindowPos( ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, Vector2.One / 2 ); - ImGui.SetNextWindowSize( size, ImGuiCond.Appearing ); - var _ = true; - if( ImGui.BeginPopupModal( CharacterCollectionHelpPopup, ref _, ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove ) ) - { - const string header = "Character Collections are a Hack! Use them at your own risk."; - using var end = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - var textWidth = ImGui.CalcTextSize( header ).X; - ImGui.NewLine(); - ImGui.SetCursorPosX( ( size.X - textWidth ) / 2 ); - using var color = ImGuiRaii.PushColor( ImGuiCol.Text, 0xFF0000B8 ); - ImGui.Text( header ); - color.Pop(); - ImGui.NewLine(); - ImGui.TextWrapped( - "Character Collections are collections that get applied whenever the named character gets redrawn by Penumbra," - + " whether by a manual '/penumbra redraw' command, or by the automatic redrawing feature.\n" - + "This means that they specifically require redrawing of a character to even apply, and thus can not work with mods that modify something that does not depend on characters being drawn, such as:\n" - + " - animations\n" - + " - sounds\n" - + " - most effects\n" - + " - most ui elements.\n" - + "They can also not work with actors that are not named, like the Character Preview or TryOn Actors, and they can not work in cutscenes, since redrawing in cutscenes would cancel all animations.\n" - + "They also do not work with every character customization (like skin, tattoo, hair, etc. changes) since those are not always re-requested by the game on redrawing a player. They may work, they may not, you need to test it.\n" - + "\n" - + "Due to the nature of meta manipulating mods, you can not mix meta manipulations inside a Character (or the Default) collection with meta manipulations inside the Forced collection.\n" - + "\n" - + "To verify that you have actually read this, you need to hold control and shift while clicking the Understood button for it to take effect.\n" - + "Due to the nature of redrawing being a hack, weird things (or maybe even crashes) may happen when using Character Collections. The way this works is:\n" - + " - Penumbra queues a redraw of an actor.\n" - + " - When the redraw queue reaches that actor, the actor gets undrawn (turned invisible).\n" - + " - Penumbra checks the actors name and if it matches a Character Collection, it replaces the Default collection with that one.\n" - + " - Penumbra triggers the redraw of that actor. The game requests files.\n" - + " - Penumbra potentially redirects those file requests to the modded files in the active collection, which is either Default or Character. (Or, afterwards, Forced).\n" - + " - The actor is drawn.\n" - + " - Penumbra returns the active collection to the Default Collection.\n" - + "If any of those steps fails, or if the file requests take too long, it may happen that a character is drawn with half of its models from the Default and the other half from the Character Collection, or a modded Model is loaded, but not its corresponding modded textures, which lets it stay invisible, or similar problems." ); - - var buttonSize = ImGuiHelpers.ScaledVector2( 150, 0 ); - var offset = ( size.X - buttonSize.X ) / 2; - ImGui.SetCursorPos( new Vector2( offset, size.Y - 3 * ImGui.GetTextLineHeightWithSpacing() ) ); - var state = ImGui.GetIO().KeyCtrl && ImGui.GetIO().KeyShift; - color.Push( ImGuiCol.ButtonHovered, 0xFF00A000, state ); - if( ImGui.Button( "Understood!", buttonSize ) ) - { - if( state && !Penumbra.Config.HasReadCharacterCollectionDesc ) - { - Penumbra.Config.HasReadCharacterCollectionDesc = true; - Penumbra.Config.Save(); - } - - ImGui.CloseCurrentPopup(); - } - } - } - - - private void DrawCharacterCollectionSelectors() - { - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndChild ); - if( !ImGui.BeginChild( "##CollectionChild", AutoFillSize, true ) ) - { - return; - } - - DrawDefaultCollectionSelector(); - - foreach( var (name, collection) in Penumbra.CollectionManager.Characters.ToArray() ) - { - var idx = _currentCharacterIndices[ name ]; - var tmp = idx; - ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth ); - if( ImGui.Combo( $"##{name}collection", ref tmp, _collectionNamesWithNone ) && idx != tmp ) - { - Penumbra.CollectionManager.SetCollection( _collections[ tmp ], ModCollection.Type.Character, name ); - _currentCharacterIndices[ name ] = tmp; - } - - ImGui.SameLine(); - - using var font = ImGuiRaii.PushFont( UiBuilder.IconFont ); - - if( ImGui.Button( $"{FontAwesomeIcon.Trash.ToIconString()}##{name}", Vector2.One * ImGui.GetFrameHeight() ) ) - { - Penumbra.CollectionManager.RemoveCharacterCollection( name ); - } - - font.Pop(); - - ImGui.SameLine(); - ImGui.AlignTextToFramePadding(); - ImGui.Text( name ); - } - - DrawNewCharacterCollection(); - } - - public void Draw() - { - if( !ImGui.BeginTabItem( "Collections" ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ) - .Push( ImGui.EndChild ); - - if( ImGui.BeginChild( "##CollectionHandling", new Vector2( -1, ImGui.GetTextLineHeightWithSpacing() * 17 ), true ) ) - { - DrawCurrentCollectionSelector( true ); - ImGuiHelpers.ScaledDummy( 0, 10 ); - DrawNewCollectionInput(); - ImGuiHelpers.ScaledDummy( 0, 10 ); - DrawCurrentCollectionInheritance(); - DrawNewInheritanceSelection(); - } - - raii.Pop(); - - DrawCharacterCollectionSelectors(); - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabDebug.Model.cs b/Penumbra/UI/MenuTabs/TabDebug.Model.cs deleted file mode 100644 index 527a265d..00000000 --- a/Penumbra/UI/MenuTabs/TabDebug.Model.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System.Runtime.InteropServices; -using FFXIVClientStructs.FFXIV.Client.Game.Character; -using FFXIVClientStructs.FFXIV.Client.Graphics.Render; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using FFXIVClientStructs.FFXIV.Client.System.Resource; -using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; -using ImGuiNET; -using Penumbra.UI.Custom; - -namespace Penumbra.UI; - -public partial class SettingsInterface -{ - [StructLayout( LayoutKind.Explicit )] - private unsafe struct RenderModel - { - [FieldOffset( 0x18 )] - public RenderModel* PreviousModel; - - [FieldOffset( 0x20 )] - public RenderModel* NextModel; - - [FieldOffset( 0x30 )] - public ResourceHandle* ResourceHandle; - - [FieldOffset( 0x40 )] - public Skeleton* Skeleton; - - [FieldOffset( 0x58 )] - public void** BoneList; - - [FieldOffset( 0x60 )] - public int BoneListCount; - - [FieldOffset( 0x68 )] - private void* UnkDXBuffer1; - - [FieldOffset( 0x70 )] - private void* UnkDXBuffer2; - - [FieldOffset( 0x78 )] - private void* UnkDXBuffer3; - - [FieldOffset( 0x90 )] - public void** Materials; - - [FieldOffset( 0x98 )] - public int MaterialCount; - } - - [StructLayout( LayoutKind.Explicit )] - private unsafe struct Material - { - [FieldOffset( 0x10 )] - public ResourceHandle* ResourceHandle; - - [FieldOffset( 0x28 )] - public void* MaterialData; - - [FieldOffset( 0x48 )] - public Texture* Tex1; - - [FieldOffset( 0x60 )] - public Texture* Tex2; - - [FieldOffset( 0x78 )] - public Texture* Tex3; - } - - private static unsafe void DrawPlayerModelInfo() - { - var player = Dalamud.ClientState.LocalPlayer; - var name = player?.Name.ToString() ?? "NULL"; - if( !ImGui.CollapsingHeader( $"Player Model Info: {name}##Draw" ) || player == null ) - { - return; - } - - var model = ( CharacterBase* )( ( Character* )player.Address )->GameObject.GetDrawObject(); - if( model == null ) - { - return; - } - - if( !ImGui.BeginTable( $"##{name}DrawTable", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTable ); - ImGui.TableNextColumn(); - ImGui.TableHeader( "Slot" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( "Imc Ptr" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( "Imc File" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( "Model Ptr" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( "Model File" ); - - for( var i = 0; i < model->SlotCount; ++i ) - { - var imc = ( ResourceHandle* )model->IMCArray[ i ]; - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text( $"Slot {i}" ); - ImGui.TableNextColumn(); - ImGui.Text( imc == null ? "NULL" : $"0x{( ulong )imc:X}" ); - ImGui.TableNextColumn(); - if( imc != null ) - { - ImGui.Text( imc->FileName.ToString() ); - } - - var mdl = ( RenderModel* )model->ModelArray[ i ]; - ImGui.TableNextColumn(); - ImGui.Text( mdl == null ? "NULL" : $"0x{( ulong )mdl:X}" ); - if( mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara ) - { - continue; - } - - ImGui.TableNextColumn(); - if( mdl != null ) - { - ImGui.Text( mdl->ResourceHandle->FileName.ToString() ); - } - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabDebug.cs b/Penumbra/UI/MenuTabs/TabDebug.cs deleted file mode 100644 index 18353834..00000000 --- a/Penumbra/UI/MenuTabs/TabDebug.cs +++ /dev/null @@ -1,352 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Numerics; -using ImGuiNET; -using OtterGui.Raii; -using Penumbra.Api; -using Penumbra.UI.Custom; -using CharacterUtility = Penumbra.Interop.CharacterUtility; -using ResourceHandle = Penumbra.Interop.Structs.ResourceHandle; - -namespace Penumbra.UI; - -public partial class SettingsInterface -{ - private string ImGuiIdTester = string.Empty; - - private void DrawImGuiIdTester() - { - ImGui.SetNextItemWidth( 200 ); - ImGui.InputText( "##abc1", ref ImGuiIdTester, 32 ); - ImGui.SameLine(); - ImGui.Text( ImGui.GetID( ImGuiIdTester ).ToString( "X" ) ); - } - - - private static void PrintValue( string name, string value ) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text( name ); - ImGui.TableNextColumn(); - ImGui.Text( value ); - } - - private void DrawDebugTabGeneral() - { - if( !ImGui.CollapsingHeader( "General##Debug" ) ) - { - return; - } - - if( !ImGui.BeginTable( "##DebugGeneralTable", 2, ImGuiTableFlags.SizingFixedFit, - new Vector2( -1, ImGui.GetTextLineHeightWithSpacing() * 1 ) ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTable ); - - var manager = Penumbra.ModManager; - PrintValue( "Current Collection", Penumbra.CollectionManager.Current.Name ); - PrintValue( " has Cache", Penumbra.CollectionManager.Current.HasCache.ToString() ); - PrintValue( "Default Collection", Penumbra.CollectionManager.Default.Name ); - PrintValue( " has Cache", Penumbra.CollectionManager.Default.HasCache.ToString() ); - PrintValue( "Mod Manager BasePath", manager.BasePath.Name ); - PrintValue( "Mod Manager BasePath-Full", manager.BasePath.FullName ); - PrintValue( "Mod Manager BasePath IsRooted", Path.IsPathRooted( Penumbra.Config.ModDirectory ).ToString() ); - PrintValue( "Mod Manager BasePath Exists", Directory.Exists( manager.BasePath.FullName ).ToString() ); - PrintValue( "Mod Manager Valid", manager.Valid.ToString() ); - PrintValue( "Path Resolver Enabled", _penumbra.PathResolver.Enabled.ToString() ); - //PrintValue( "Resource Loader Enabled", _penumbra.ResourceLoader.IsEnabled.ToString() ); - } - - private void DrawDebugTabIpc() - { - if( !ImGui.CollapsingHeader( "IPC##Debug" ) ) - { - return; - } - - var ipc = _penumbra.Ipc; - ImGui.Text( $"API Version: {ipc.Api.ApiVersion}" ); - ImGui.Text( "Available subscriptions:" ); - using var indent = ImGuiRaii.PushIndent(); - if( ipc.ProviderApiVersion != null ) - { - ImGui.Text( PenumbraIpc.LabelProviderApiVersion ); - } - - if( ipc.ProviderRedrawName != null ) - { - ImGui.Text( PenumbraIpc.LabelProviderRedrawName ); - } - - if( ipc.ProviderRedrawObject != null ) - { - ImGui.Text( PenumbraIpc.LabelProviderRedrawObject ); - } - - if( ipc.ProviderRedrawAll != null ) - { - ImGui.Text( PenumbraIpc.LabelProviderRedrawAll ); - } - - if( ipc.ProviderResolveDefault != null ) - { - ImGui.Text( PenumbraIpc.LabelProviderResolveDefault ); - } - - if( ipc.ProviderResolveCharacter != null ) - { - ImGui.Text( PenumbraIpc.LabelProviderResolveCharacter ); - } - - if( ipc.ProviderChangedItemTooltip != null ) - { - ImGui.Text( PenumbraIpc.LabelProviderChangedItemTooltip ); - } - - if( ipc.ProviderChangedItemClick != null ) - { - ImGui.Text( PenumbraIpc.LabelProviderChangedItemClick ); - } - } - - private void DrawDebugTabMissingFiles() - { - if( !ImGui.CollapsingHeader( "Missing Files##Debug" ) ) - { - return; - } - - if( !Penumbra.CollectionManager.Current.HasCache - || !ImGui.BeginTable( "##MissingFilesDebugList", 1, ImGuiTableFlags.RowBg, -Vector2.UnitX ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTable ); - - foreach( var file in Penumbra.CollectionManager.Current.MissingFiles ) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - if( ImGui.Selectable( file.FullName ) ) - { - ImGui.SetClipboardText( file.FullName ); - } - - ImGuiCustom.HoverTooltip( "Click to copy to clipboard." ); - } - } - - private unsafe void DrawDebugTabReplacedResources() - { - if( !ImGui.CollapsingHeader( "Replaced Resources##Debug" ) ) - { - return; - } - - Penumbra.ResourceLoader.UpdateDebugInfo(); - - if( Penumbra.ResourceLoader.DebugList.Count == 0 - || !ImGui.BeginTable( "##ReplacedResourcesDebugList", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.UnitX ) ) - { - return; - } - - using var end = ImGuiRaii.DeferredEnd( ImGui.EndTable ); - - foreach( var data in Penumbra.ResourceLoader.DebugList.Values.ToArray() ) - { - var refCountManip = data.ManipulatedResource == null ? 0 : data.ManipulatedResource->RefCount; - var refCountOrig = data.OriginalResource == null ? 0 : data.OriginalResource->RefCount; - ImGui.TableNextColumn(); - ImGui.Text( data.ManipulatedPath.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( ( ( ulong )data.ManipulatedResource ).ToString( "X" ) ); - ImGui.TableNextColumn(); - ImGui.Text( refCountManip.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( data.OriginalPath.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( ( ( ulong )data.OriginalResource ).ToString( "X" ) ); - ImGui.TableNextColumn(); - ImGui.Text( refCountOrig.ToString() ); - } - } - - public unsafe void DrawDebugCharacterUtility() - { - if( !ImGui.CollapsingHeader( "Character Utility##Debug" ) ) - { - return; - } - - if( !ImGui.BeginTable( "##CharacterUtilityDebugList", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.UnitX ) ) - { - return; - } - - using var end = ImGuiRaii.DeferredEnd( ImGui.EndTable ); - - for( var i = 0; i < CharacterUtility.RelevantIndices.Length; ++i ) - { - var idx = CharacterUtility.RelevantIndices[ i ]; - var resource = ( ResourceHandle* )Penumbra.CharacterUtility.Address->Resources[ idx ]; - ImGui.TableNextColumn(); - ImGui.Text( $"0x{( ulong )resource:X}" ); - ImGui.TableNextColumn(); - ImGuiNative.igTextUnformatted( resource->FileName(), resource->FileName() + resource->FileNameLength ); - ImGui.TableNextColumn(); - ImGui.Text( $"0x{resource->GetData().Data:X}" ); - if( ImGui.IsItemClicked() ) - { - var (data, length) = resource->GetData(); - ImGui.SetClipboardText( string.Join( " ", - new ReadOnlySpan< byte >( ( byte* )data, length ).ToArray().Select( b => b.ToString( "X2" ) ) ) ); - } - - ImGui.TableNextColumn(); - ImGui.Text( $"{resource->GetData().Length}" ); - ImGui.TableNextColumn(); - ImGui.Text( $"0x{Penumbra.CharacterUtility.DefaultResources[ i ].Address:X}" ); - if( ImGui.IsItemClicked() ) - { - ImGui.SetClipboardText( string.Join( " ", - new ReadOnlySpan< byte >( ( byte* )Penumbra.CharacterUtility.DefaultResources[ i ].Address, - Penumbra.CharacterUtility.DefaultResources[ i ].Size ).ToArray().Select( b => b.ToString( "X2" ) ) ) ); - } - - ImGui.TableNextColumn(); - ImGui.Text( $"{Penumbra.CharacterUtility.DefaultResources[ i ].Size}" ); - } - } - - - public unsafe void DrawDebugResidentResources() - { - if( !ImGui.CollapsingHeader( "Resident Resources##Debug" ) ) - { - return; - } - - if( Penumbra.ResidentResources.Address == null || Penumbra.ResidentResources.Address->NumResources == 0 ) - { - return; - } - - if( !ImGui.BeginTable( "##Resident ResourcesDebugList", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.UnitX ) ) - { - return; - } - - using var end = ImGuiRaii.DeferredEnd( ImGui.EndTable ); - - for( var i = 0; i < Penumbra.ResidentResources.Address->NumResources; ++i ) - { - var resource = Penumbra.ResidentResources.Address->ResourceList[ i ]; - ImGui.TableNextColumn(); - ImGui.Text( $"0x{( ulong )resource:X}" ); - ImGui.TableNextColumn(); - ImGuiNative.igTextUnformatted( resource->FileName(), resource->FileName() + resource->FileNameLength ); - } - } - - private unsafe void DrawPathResolverDebug() - { - if( !ImGui.CollapsingHeader( "Path Resolver##Debug" ) ) - { - return; - } - - if( ImGui.TreeNodeEx( "Draw Object to Object" ) ) - { - using var end = ImGuiRaii.DeferredEnd( ImGui.TreePop ); - if( ImGui.BeginTable( "###DrawObjectResolverTable", 5, ImGuiTableFlags.SizingFixedFit ) ) - { - end.Push( ImGui.EndTable ); - foreach( var (ptr, (c, idx)) in _penumbra.PathResolver.DrawObjectToObject ) - { - ImGui.TableNextColumn(); - ImGui.Text( ptr.ToString( "X" ) ); - ImGui.TableNextColumn(); - ImGui.Text( idx.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( Dalamud.Objects[ idx ]?.Address.ToString() ?? "NULL" ); - ImGui.TableNextColumn(); - ImGui.Text( Dalamud.Objects[ idx ]?.Name.ToString() ?? "NULL" ); - ImGui.TableNextColumn(); - ImGui.Text( c.Name ); - } - } - } - - if( ImGui.TreeNodeEx( "Path Collections" ) ) - { - using var end = ImGuiRaii.DeferredEnd( ImGui.TreePop ); - if( ImGui.BeginTable( "###PathCollectionResolverTable", 2, ImGuiTableFlags.SizingFixedFit ) ) - { - end.Push( ImGui.EndTable ); - foreach( var (path, collection) in _penumbra.PathResolver.PathCollections ) - { - ImGui.TableNextColumn(); - ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length ); - ImGui.TableNextColumn(); - ImGui.Text( collection.Name ); - } - } - } - } - - private void DrawDebugTabUtility() - { - if( !ImGui.CollapsingHeader( "Utilities##Debug" ) ) - { - return; - } - - DrawImGuiIdTester(); - } - - private void DrawDebugTab() - { - if( !ImGui.BeginTabItem( "Debug Tab" ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); - - if( !ImGui.BeginChild( "##DebugChild", -Vector2.One ) ) - { - ImGui.EndChild(); - return; - } - - raii.Push( ImGui.EndChild ); - - DrawDebugTabGeneral(); - ImGui.NewLine(); - DrawDebugTabReplacedResources(); - ImGui.NewLine(); - DrawResourceProblems(); - ImGui.NewLine(); - DrawDebugTabMissingFiles(); - ImGui.NewLine(); - DrawPlayerModelInfo(); - ImGui.NewLine(); - DrawPathResolverDebug(); - ImGui.NewLine(); - DrawDebugCharacterUtility(); - ImGui.NewLine(); - DrawDebugResidentResources(); - ImGui.NewLine(); - DrawDebugTabIpc(); - ImGui.NewLine(); - DrawDebugTabUtility(); - ImGui.NewLine(); - } -} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabEffective.cs b/Penumbra/UI/MenuTabs/TabEffective.cs deleted file mode 100644 index e9a962e7..00000000 --- a/Penumbra/UI/MenuTabs/TabEffective.cs +++ /dev/null @@ -1,229 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Dalamud.Interface; -using ImGuiNET; -using OtterGui; -using Penumbra.Collections; -using Penumbra.GameData.ByteString; -using Penumbra.GameData.Util; -using Penumbra.UI.Custom; -using Penumbra.Util; - -namespace Penumbra.UI; - -public partial class SettingsInterface -{ - private class TabEffective - { - private const string LabelTab = "Effective Changes"; - - private LowerString _gamePathFilter = LowerString.Empty; - private LowerString _filePathFilter = LowerString.Empty; - - private const float LeftTextLength = 600; - - private float _arrowLength = 0; - - private static void DrawLine( Utf8GamePath path, FullPath name ) - { - ImGui.TableNextColumn(); - ImGuiCustom.CopyOnClickSelectable( path.Path ); - - ImGui.TableNextColumn(); - ImGuiCustom.PrintIcon( FontAwesomeIcon.LongArrowAltLeft ); - ImGui.SameLine(); - ImGuiCustom.CopyOnClickSelectable( name.InternalName ); - } - - private static void DrawLine( string path, string name ) - { - ImGui.TableNextColumn(); - ImGuiCustom.CopyOnClickSelectable( path ); - - ImGui.TableNextColumn(); - ImGuiCustom.PrintIcon( FontAwesomeIcon.LongArrowAltLeft ); - ImGui.SameLine(); - ImGuiCustom.CopyOnClickSelectable( name ); - } - - private void DrawFilters() - { - if( _arrowLength == 0 ) - { - using var font = ImGuiRaii.PushFont( UiBuilder.IconFont ); - _arrowLength = ImGui.CalcTextSize( FontAwesomeIcon.LongArrowAltLeft.ToIconString() ).X / ImGuiHelpers.GlobalScale; - } - - ImGui.SetNextItemWidth( LeftTextLength * ImGuiHelpers.GlobalScale ); - var tmp = _gamePathFilter.Text; - if( ImGui.InputTextWithHint( "##effective_changes_gfilter", "Filter game path...", ref tmp, 256 ) ) - { - _gamePathFilter = tmp; - } - - ImGui.SameLine( ( LeftTextLength + _arrowLength ) * ImGuiHelpers.GlobalScale + 3 * ImGui.GetStyle().ItemSpacing.X ); - ImGui.SetNextItemWidth( -1 ); - tmp = _filePathFilter.Text; - if( ImGui.InputTextWithHint( "##effective_changes_ffilter", "Filter file path...", ref tmp, 256 ) ) - { - _filePathFilter = tmp; - } - } - - private bool CheckFilters( KeyValuePair< Utf8GamePath, FullPath > kvp ) - { - if( _gamePathFilter.Length > 0 && !kvp.Key.ToString().Contains( _gamePathFilter.Lower ) ) - { - return false; - } - - return _filePathFilter.Length == 0 || kvp.Value.FullName.ToLowerInvariant().Contains( _filePathFilter.Lower ); - } - - private bool CheckFilters( KeyValuePair< Utf8GamePath, Utf8GamePath > kvp ) - { - if( _gamePathFilter.Length > 0 && !kvp.Key.ToString().Contains( _gamePathFilter.Lower ) ) - { - return false; - } - - return _filePathFilter.Length == 0 || kvp.Value.ToString().Contains( _filePathFilter.Lower ); - } - - private bool CheckFilters( (string, LowerString) kvp ) - { - if( _gamePathFilter.Length > 0 && !kvp.Item1.ToLowerInvariant().Contains( _gamePathFilter.Lower ) ) - { - return false; - } - - return _filePathFilter.Length == 0 || kvp.Item2.Contains( _filePathFilter.Lower ); - } - - private void DrawFilteredRows( ModCollection active ) - { - foreach( var (gp, fp) in active.ResolvedFiles.Where( CheckFilters ) ) - { - DrawLine( gp, fp ); - } - - var cache = active.MetaCache; - if( cache == null ) - { - return; - } - - foreach( var (mp, mod) in cache.Cmp.Manipulations - .Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name ) ) - .Where( CheckFilters ) ) - { - DrawLine( mp, mod ); - } - - foreach( var (mp, mod) in cache.Eqp.Manipulations - .Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name ) ) - .Where( CheckFilters ) ) - { - DrawLine( mp, mod ); - } - - foreach( var (mp, mod) in cache.Eqdp.Manipulations - .Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name ) ) - .Where( CheckFilters ) ) - { - DrawLine( mp, mod ); - } - - foreach( var (mp, mod) in cache.Gmp.Manipulations - .Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name ) ) - .Where( CheckFilters ) ) - { - DrawLine( mp, mod ); - } - - foreach( var (mp, mod) in cache.Est.Manipulations - .Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name ) ) - .Where( CheckFilters ) ) - { - DrawLine( mp, mod ); - } - - foreach( var (mp, mod) in cache.Imc.Manipulations - .Select( p => ( p.Key.ToString(), Penumbra.ModManager.Mods[ p.Value ].Meta.Name ) ) - .Where( CheckFilters ) ) - { - DrawLine( mp, mod ); - } - } - - public void Draw() - { - if( !ImGui.BeginTabItem( LabelTab ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); - - DrawFilters(); - - const ImGuiTableFlags flags = ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX; - - var resolved = Penumbra.CollectionManager.Default.ResolvedFiles; - var meta = Penumbra.CollectionManager.Default.MetaCache; - var metaCount = meta?.Count ?? 0; - var resolvedCount = resolved.Count; - - var totalLines = resolvedCount + metaCount; - if( totalLines == 0 ) - { - return; - } - - if( !ImGui.BeginTable( "##effective_changes", 2, flags, AutoFillSize ) ) - { - return; - } - - raii.Push( ImGui.EndTable ); - ImGui.TableSetupColumn( "##tableGamePathCol", ImGuiTableColumnFlags.None, LeftTextLength * ImGuiHelpers.GlobalScale ); - - if( _filePathFilter.Length > 0 || _gamePathFilter.Length > 0 ) - { - DrawFilteredRows( Penumbra.CollectionManager.Default ); - } - else - { - ImGuiListClipperPtr clipper; - unsafe - { - clipper = new ImGuiListClipperPtr( ImGuiNative.ImGuiListClipper_ImGuiListClipper() ); - } - - clipper.Begin( totalLines ); - - - while( clipper.Step() ) - { - for( var actualRow = clipper.DisplayStart; actualRow < clipper.DisplayEnd; actualRow++ ) - { - var row = actualRow; - ImGui.TableNextRow(); - if( row < resolvedCount ) - { - var (gamePath, file) = resolved.ElementAt( row ); - DrawLine( gamePath, file ); - } - else if( ( row -= resolved.Count ) < metaCount ) - { - // TODO - //var (manip, mod) = activeCollection!.MetaManipulations.Manipulations.ElementAt( row ); - DrawLine( 0.ToString(), 0.ToString() ); - } - } - } - } - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabImport.cs b/Penumbra/UI/MenuTabs/TabImport.cs deleted file mode 100644 index 4b49f88a..00000000 --- a/Penumbra/UI/MenuTabs/TabImport.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; -using System.Windows.Forms; -using Dalamud.Logging; -using ImGuiNET; -using Penumbra.Importer; -using Penumbra.UI.Custom; -using Penumbra.Util; - -namespace Penumbra.UI; - -public partial class SettingsInterface -{ - private class TabImport - { - private const string LabelTab = "Import Mods"; - private const string LabelImportButton = "Import TexTools Modpacks"; - private const string LabelFileDialog = "Pick one or more modpacks."; - private const string LabelFileImportRunning = "Import in progress..."; - private const string FileTypeFilter = "TexTools TTMP Modpack (*.ttmp2)|*.ttmp*|All files (*.*)|*.*"; - private const string TooltipModpack1 = "Writing modpack to disk before extracting..."; - - private const uint ColorRed = 0xFF0000C8; - private const uint ColorYellow = 0xFF00C8C8; - - private static readonly Vector2 ImportBarSize = new(-1, 0); - - private bool _isImportRunning; - private string _errorMessage = string.Empty; - private TexToolsImport? _texToolsImport; - private readonly SettingsInterface _base; - - public readonly HashSet< string > NewMods = new(); - - public TabImport( SettingsInterface ui ) - => _base = ui; - - public bool IsImporting() - => _isImportRunning; - - private void RunImportTask() - { - _isImportRunning = true; - Task.Run( async () => - { - try - { - var picker = new OpenFileDialog - { - Multiselect = true, - Filter = FileTypeFilter, - CheckFileExists = true, - Title = LabelFileDialog, - }; - - var result = await picker.ShowDialogAsync(); - - if( result == DialogResult.OK ) - { - _errorMessage = string.Empty; - - foreach( var fileName in picker.FileNames ) - { - PluginLog.Information( $"-> {fileName} START" ); - - try - { - _texToolsImport = new TexToolsImport( Penumbra.ModManager.BasePath ); - var dir = _texToolsImport.ImportModPack( new FileInfo( fileName ) ); - if( dir.Name.Any() ) - { - NewMods.Add( dir.Name ); - } - - PluginLog.Information( $"-> {fileName} OK!" ); - } - catch( Exception ex ) - { - PluginLog.LogError( ex, "Failed to import modpack at {0}", fileName ); - _errorMessage = ex.Message; - } - } - - var directory = _texToolsImport?.ExtractedDirectory; - _texToolsImport = null; - _base.ReloadMods(); - if( directory != null ) - { - _base._menu.InstalledTab.Selector.SelectModOnUpdate( directory.Name ); - } - } - } - catch( Exception e ) - { - PluginLog.Error( $"Error opening file picker dialogue:\n{e}" ); - } - - _isImportRunning = false; - } ); - } - - private void DrawImportButton() - { - if( !Penumbra.ModManager.Valid ) - { - using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f ); - ImGui.Button( LabelImportButton ); - style.Pop(); - - using var color = ImGuiRaii.PushColor( ImGuiCol.Text, ColorRed ); - ImGui.Text( "Can not import since the mod directory path is not valid." ); - ImGui.Dummy( Vector2.UnitY * ImGui.GetTextLineHeightWithSpacing() ); - color.Pop(); - - ImGui.Text( "Please set the mod directory in the settings tab." ); - ImGui.Text( "This folder should preferably be close to the root directory of your (preferably SSD) drive, for example" ); - color.Push( ImGuiCol.Text, ColorYellow ); - ImGui.Text( " D:\\ffxivmods" ); - color.Pop(); - ImGui.Text( "You can return to this tab once you've done that." ); - } - else if( ImGui.Button( LabelImportButton ) ) - { - RunImportTask(); - } - } - - private void DrawImportProgress() - { - ImGui.Button( LabelFileImportRunning ); - - if( _texToolsImport == null ) - { - return; - } - - switch( _texToolsImport.State ) - { - case ImporterState.None: break; - case ImporterState.WritingPackToDisk: - ImGui.Text( TooltipModpack1 ); - break; - case ImporterState.ExtractingModFiles: - { - var str = - $"{_texToolsImport.CurrentModPack} - {_texToolsImport.CurrentProgress} of {_texToolsImport.TotalProgress} files"; - - ImGui.ProgressBar( _texToolsImport.Progress, ImportBarSize, str ); - break; - } - case ImporterState.Done: break; - default: throw new ArgumentOutOfRangeException(); - } - } - - private void DrawFailedImportMessage() - { - using var color = ImGuiRaii.PushColor( ImGuiCol.Text, ColorRed ); - ImGui.Text( $"One or more of your modpacks failed to import:\n\t\t{_errorMessage}" ); - } - - public void Draw() - { - if( !ImGui.BeginTabItem( LabelTab ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); - - if( !_isImportRunning ) - { - DrawImportButton(); - } - else - { - DrawImportProgress(); - } - - if( _errorMessage.Any() ) - { - DrawFailedImportMessage(); - } - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabInstalled/ModListCache.cs b/Penumbra/UI/MenuTabs/TabInstalled/ModListCache.cs deleted file mode 100644 index be4bf261..00000000 --- a/Penumbra/UI/MenuTabs/TabInstalled/ModListCache.cs +++ /dev/null @@ -1,326 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Dalamud.Logging; -using OtterGui; -using Penumbra.Mods; -using Penumbra.UI.Classes; -using Penumbra.Util; - -namespace Penumbra.UI; - -public class ModListCache : IDisposable -{ - private readonly Mod.Manager _manager; - - private readonly List< FullMod > _modsInOrder = new(); - private readonly List< (bool visible, uint color) > _visibleMods = new(); - private readonly Dictionary< ModFolder, (bool visible, bool enabled) > _visibleFolders = new(); - private readonly IReadOnlySet< string > _newMods; - - private LowerString _modFilter = LowerString.Empty; - private LowerString _modFilterAuthor = LowerString.Empty; - private LowerString _modFilterChanges = LowerString.Empty; - - private bool _listResetNecessary; - private bool _filterResetNecessary; - - private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods; - - public ModFilter StateFilter - { - get => _stateFilter; - set - { - var diff = _stateFilter != value; - _stateFilter = value; - if( diff ) - { - TriggerFilterReset(); - } - } - } - - public ModListCache( Mod.Manager manager, IReadOnlySet< string > newMods ) - { - _manager = manager; - _newMods = newMods; - ResetModList(); - ModFileSystem.ModFileSystemChanged += TriggerListReset; - } - - public void Dispose() - { - ModFileSystem.ModFileSystemChanged -= TriggerListReset; - } - - public int Count - => _modsInOrder.Count; - - - public bool Update() - { - if( _listResetNecessary ) - { - ResetModList(); - return true; - } - - if( _filterResetNecessary ) - { - ResetFilters(); - return true; - } - - return false; - } - - public void TriggerListReset() - => _listResetNecessary = true; - - public void TriggerFilterReset() - => _filterResetNecessary = true; - - public void RemoveMod( FullMod mod ) - { - var idx = _modsInOrder.IndexOf( mod ); - if( idx >= 0 ) - { - _modsInOrder.RemoveAt( idx ); - _visibleMods.RemoveAt( idx ); - UpdateFolders(); - } - } - - private void SetFolderAndParentsVisible( ModFolder? folder ) - { - while( folder != null && ( !_visibleFolders.TryGetValue( folder, out var state ) || !state.visible ) ) - { - _visibleFolders[ folder ] = ( true, true ); - folder = folder.Parent; - } - } - - private void UpdateFolders() - { - _visibleFolders.Clear(); - - for( var i = 0; i < _modsInOrder.Count; ++i ) - { - if( _visibleMods[ i ].visible ) - { - SetFolderAndParentsVisible( _modsInOrder[ i ].Data.Order.ParentFolder ); - } - } - } - - public void SetTextFilter( string filter ) - { - var lower = filter.ToLowerInvariant(); - if( lower.StartsWith( "c:" ) ) - { - _modFilterChanges = lower[ 2.. ]; - _modFilter = LowerString.Empty; - _modFilterAuthor = LowerString.Empty; - } - else if( lower.StartsWith( "a:" ) ) - { - _modFilterAuthor = lower[ 2.. ]; - _modFilter = LowerString.Empty; - _modFilterChanges = LowerString.Empty; - } - else - { - _modFilter = lower; - _modFilterAuthor = LowerString.Empty; - _modFilterChanges = LowerString.Empty; - } - - ResetFilters(); - } - - private void ResetModList() - { - _modsInOrder.Clear(); - _visibleMods.Clear(); - _visibleFolders.Clear(); - - PluginLog.Debug( "Resetting mod selector list..." ); - if( _modsInOrder.Count == 0 ) - { - foreach( var modData in _manager.StructuredMods.AllMods( Penumbra.Config.SortFoldersFirst ) ) - { - var idx = Penumbra.ModManager.Mods.IndexOf( modData ); - var mod = new FullMod( Penumbra.CollectionManager.Current[ idx ].Settings ?? ModSettings.DefaultSettings( modData.Meta ), - modData ); - _modsInOrder.Add( mod ); - _visibleMods.Add( CheckFilters( mod ) ); - } - } - - _listResetNecessary = false; - _filterResetNecessary = false; - } - - private void ResetFilters() - { - _visibleMods.Clear(); - _visibleFolders.Clear(); - PluginLog.Debug( "Resetting mod selector filters..." ); - foreach( var mod in _modsInOrder ) - { - _visibleMods.Add( CheckFilters( mod ) ); - } - - _filterResetNecessary = false; - } - - public (FullMod? mod, int idx) GetModByName( string name ) - { - for( var i = 0; i < Count; ++i ) - { - if( _modsInOrder[ i ].Data.Meta.Name == name ) - { - return ( _modsInOrder[ i ], i ); - } - } - - return ( null, 0 ); - } - - public (FullMod? mod, int idx) GetModByBasePath( string basePath ) - { - for( var i = 0; i < Count; ++i ) - { - if( _modsInOrder[ i ].Data.BasePath.Name == basePath ) - { - return ( _modsInOrder[ i ], i ); - } - } - - return ( null, 0 ); - } - - public (bool visible, bool enabled) GetFolder( ModFolder folder ) - => _visibleFolders.TryGetValue( folder, out var ret ) ? ret : ( false, false ); - - public (FullMod?, bool visible, uint color) GetMod( int idx ) - => idx >= 0 && idx < _modsInOrder.Count - ? ( _modsInOrder[ idx ], _visibleMods[ idx ].visible, _visibleMods[ idx ].color ) - : ( null, false, 0 ); - - private bool CheckFlags( int count, ModFilter hasNoFlag, ModFilter hasFlag ) - { - if( count == 0 ) - { - if( StateFilter.HasFlag( hasNoFlag ) ) - { - return false; - } - } - else if( StateFilter.HasFlag( hasFlag ) ) - { - return false; - } - - return true; - } - - private (bool, uint) CheckFilters( FullMod mod ) - { - var ret = ( false, 0u ); - - if( _modFilter.Length > 0 && !mod.Data.Meta.Name.Contains( _modFilter ) ) - { - return ret; - } - - if( _modFilterAuthor.Length > 0 && !mod.Data.Meta.Author.Contains( _modFilterAuthor ) ) - { - return ret; - } - - if( _modFilterChanges.Length > 0 && !mod.Data.LowerChangedItemsString.Contains( _modFilterChanges ) ) - { - return ret; - } - - if( CheckFlags( mod.Data.Resources.ModFiles.Count, ModFilter.HasNoFiles, ModFilter.HasFiles ) ) - { - return ret; - } - - if( CheckFlags( mod.Data.Meta.FileSwaps.Count, ModFilter.HasNoFileSwaps, ModFilter.HasFileSwaps ) ) - { - return ret; - } - - if( CheckFlags( mod.Data.Resources.MetaManipulations.Count, ModFilter.HasNoMetaManipulations, - ModFilter.HasMetaManipulations ) ) - { - return ret; - } - - if( CheckFlags( mod.Data.Meta.HasGroupsWithConfig ? 1 : 0, ModFilter.HasNoConfig, ModFilter.HasConfig ) ) - { - return ret; - } - - var isNew = _newMods.Contains( mod.Data.BasePath.Name ); - if( CheckFlags( isNew ? 1 : 0, ModFilter.IsNew, ModFilter.NotNew ) ) - { - return ret; - } - - if( !mod.Settings.Enabled ) - { - if( !StateFilter.HasFlag( ModFilter.Disabled ) || !StateFilter.HasFlag( ModFilter.NoConflict ) ) - { - return ret; - } - - ret.Item2 = ret.Item2 == 0 ? ColorId.DisabledMod.Value() : ret.Item2; - } - - if( mod.Settings.Enabled && !StateFilter.HasFlag( ModFilter.Enabled ) ) - { - return ret; - } - - var conflicts = Penumbra.CollectionManager.Current.ModConflicts( mod.Data.Index ).ToList(); - if( conflicts.Count > 0 ) - { - if( conflicts.Any( c => !c.Solved ) ) - { - if( !StateFilter.HasFlag( ModFilter.UnsolvedConflict ) ) - { - return ret; - } - - ret.Item2 = ret.Item2 == 0 ? ColorId.ConflictingMod.Value() : ret.Item2; - } - else - { - if( !StateFilter.HasFlag( ModFilter.SolvedConflict ) ) - { - return ret; - } - - ret.Item2 = ret.Item2 == 0 ? ColorId.HandledConflictMod.Value() : ret.Item2; - } - } - else if( !StateFilter.HasFlag( ModFilter.NoConflict ) ) - { - return ret; - } - - ret.Item1 = true; - if( isNew ) - { - ret.Item2 = ColorId.NewMod.Value(); - } - - SetFolderAndParentsVisible( mod.Data.Order.ParentFolder ); - return ret; - } -} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalled.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalled.cs deleted file mode 100644 index beecf9b6..00000000 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalled.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections.Generic; -using ImGuiNET; -using Penumbra.UI.Custom; - -namespace Penumbra.UI; - -public partial class SettingsInterface -{ - private class TabInstalled - { - private const string LabelTab = "Installed Mods"; - - public readonly Selector Selector; - public readonly ModPanel ModPanel; - - public TabInstalled( SettingsInterface ui, HashSet< string > newMods ) - { - Selector = new Selector( ui, newMods ); - ModPanel = new ModPanel( ui, Selector, newMods ); - } - - public void Draw() - { - var ret = ImGui.BeginTabItem( LabelTab ); - if( !ret ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); - - Selector.Draw(); - ImGui.SameLine(); - ModPanel.Draw(); - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs deleted file mode 100644 index 66d40c65..00000000 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs +++ /dev/null @@ -1,713 +0,0 @@ -using System.IO; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; -using ImGuiNET; -using Lumina.Data.Parsing; -using Lumina.Excel.GeneratedSheets; -using Penumbra.GameData.ByteString; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Util; -using Penumbra.Meta; -using Penumbra.Meta.Manipulations; -using Penumbra.Mods; -using Penumbra.UI.Custom; -using Penumbra.Util; -using ImGui = ImGuiNET.ImGui; - -namespace Penumbra.UI; - -public partial class SettingsInterface -{ - private partial class PluginDetails - { - private const string LabelPluginDetails = "PenumbraPluginDetails"; - private const string LabelAboutTab = "About"; - private const string LabelChangedItemsTab = "Changed Items"; - private const string LabelChangedItemsHeader = "##changedItems"; - private const string LabelConflictsTab = "Mod Conflicts"; - private const string LabelConflictsHeader = "##conflicts"; - private const string LabelFileSwapTab = "File Swaps"; - private const string LabelFileSwapHeader = "##fileSwaps"; - private const string LabelFileListTab = "Files"; - private const string LabelFileListHeader = "##fileList"; - private const string LabelGroupSelect = "##groupSelect"; - private const string LabelOptionSelect = "##optionSelect"; - private const string LabelConfigurationTab = "Configuration"; - - private const string TooltipFilesTab = - "Green files replace their standard game path counterpart (not in any option) or are in all options of a Single-Select option.\n" - + "Yellow files are restricted to some options."; - - private const float OptionSelectionWidth = 140f; - private const float CheckMarkSize = 50f; - private const uint ColorDarkGreen = 0xFF00A000; - private const uint ColorGreen = 0xFF00C800; - private const uint ColorYellow = 0xFF00C8C8; - private const uint ColorDarkRed = 0xFF0000A0; - private const uint ColorRed = 0xFF0000C8; - - - private bool _editMode; - private int _selectedGroupIndex; - private OptionGroup? _selectedGroup; - private int _selectedOptionIndex; - private Option? _selectedOption; - private string _currentGamePaths = ""; - - private (FullPath name, bool selected, uint color, Utf8RelPath relName)[]? _fullFilenameList; - - private readonly Selector _selector; - private readonly SettingsInterface _base; - - private void SelectGroup( int idx ) - { - // Not using the properties here because we need it to be not null forgiving in this case. - var numGroups = _selector.Mod?.Data.Meta.Groups.Count ?? 0; - _selectedGroupIndex = idx; - if( _selectedGroupIndex >= numGroups ) - { - _selectedGroupIndex = 0; - } - - if( numGroups > 0 ) - { - _selectedGroup = Meta.Groups.ElementAt( _selectedGroupIndex ).Value; - } - else - { - _selectedGroup = null; - } - } - - private void SelectGroup() - => SelectGroup( _selectedGroupIndex ); - - private void SelectOption( int idx ) - { - _selectedOptionIndex = idx; - if( _selectedOptionIndex >= _selectedGroup?.Options.Count ) - { - _selectedOptionIndex = 0; - } - - if( _selectedGroup?.Options.Count > 0 ) - { - _selectedOption = ( ( OptionGroup )_selectedGroup ).Options[ _selectedOptionIndex ]; - } - else - { - _selectedOption = null; - } - } - - private void SelectOption() - => SelectOption( _selectedOptionIndex ); - - public void ResetState() - { - _fullFilenameList = null; - SelectGroup(); - SelectOption(); - } - - public PluginDetails( SettingsInterface ui, Selector s ) - { - _base = ui; - _selector = s; - ResetState(); - } - - // This is only drawn when we have a mod selected, so we can forgive nulls. - private FullMod Mod - => _selector.Mod!; - - private ModMeta Meta - => Mod.Data.Meta; - - private void DrawAboutTab() - { - if( !_editMode && Meta.Description.Length == 0 ) - { - return; - } - - if( !ImGui.BeginTabItem( LabelAboutTab ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); - - var desc = Meta.Description; - var flags = _editMode - ? ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CtrlEnterForNewLine - : ImGuiInputTextFlags.ReadOnly; - - if( _editMode ) - { - if( ImGui.InputTextMultiline( LabelDescEdit, ref desc, 1 << 16, - AutoFillSize, flags ) ) - { - Meta.Description = desc; - _selector.SaveCurrentMod(); - } - - ImGuiCustom.HoverTooltip( TooltipAboutEdit ); - } - else - { - ImGui.TextWrapped( desc ); - } - } - - private void DrawChangedItemsTab() - { - if( Mod.Data.ChangedItems.Count == 0 || !ImGui.BeginTabItem( LabelChangedItemsTab ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); - - if( !ImGui.BeginListBox( LabelChangedItemsHeader, AutoFillSize ) ) - { - return; - } - - raii.Push( ImGui.EndListBox ); - foreach( var (name, data) in Mod.Data.ChangedItems ) - { - _base.DrawChangedItem( name, data ); - } - } - - private void DrawConflictTab() - { - var conflicts = Penumbra.CollectionManager.Current.ModConflicts( Mod.Data.Index ).ToList(); - if( conflicts.Count == 0 || !ImGui.BeginTabItem( LabelConflictsTab ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); - - ImGui.SetNextItemWidth( -1 ); - if( !ImGui.BeginListBox( LabelConflictsHeader, AutoFillSize ) ) - { - return; - } - - raii.Push( ImGui.EndListBox ); - using var indent = ImGuiRaii.PushIndent( 0 ); - Mods.Mod? oldBadMod = null; - foreach( var conflict in conflicts ) - { - var badMod = Penumbra.ModManager[ conflict.Mod2 ]; - if( badMod != oldBadMod ) - { - if( oldBadMod != null ) - { - indent.Pop( 30f ); - } - - if( ImGui.Selectable( badMod.Meta.Name ) ) - { - _selector.SelectModByDir( badMod.BasePath.Name ); - } - - ImGui.SameLine(); - using var color = ImGuiRaii.PushColor( ImGuiCol.Text, conflict.Mod1Priority ? ColorGreen : ColorRed ); - ImGui.Text( $"(Priority {Penumbra.CollectionManager.Current[ conflict.Mod2 ].Settings!.Priority})" ); - - indent.Push( 30f ); - } - - if( conflict.Data is Utf8GamePath p ) - { - unsafe - { - ImGuiNative.igSelectable_Bool( p.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero ); - } - } - else if( conflict.Data is MetaManipulation m ) - { - ImGui.Selectable( m.Manipulation?.ToString() ?? string.Empty ); - } - - oldBadMod = badMod; - } - } - - private void DrawFileSwapTab() - { - if( _editMode ) - { - DrawFileSwapTabEdit(); - return; - } - - if( !Meta.FileSwaps.Any() || !ImGui.BeginTabItem( LabelFileSwapTab ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); - - const ImGuiTableFlags flags = ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX; - - ImGui.SetNextItemWidth( -1 ); - if( !ImGui.BeginTable( LabelFileSwapHeader, 3, flags, AutoFillSize ) ) - { - return; - } - - raii.Push( ImGui.EndTable ); - - foreach( var (source, target) in Meta.FileSwaps ) - { - ImGui.TableNextColumn(); - ImGuiCustom.CopyOnClickSelectable( source.Path ); - - ImGui.TableNextColumn(); - ImGuiCustom.PrintIcon( FontAwesomeIcon.LongArrowAltRight ); - - ImGui.TableNextColumn(); - ImGuiCustom.CopyOnClickSelectable( target.InternalName ); - - ImGui.TableNextRow(); - } - } - - private void UpdateFilenameList() - { - if( _fullFilenameList != null ) - { - return; - } - - _fullFilenameList = Mod.Data.Resources.ModFiles - .Select( f => ( f, false, ColorGreen, Utf8RelPath.FromFile( f, Mod.Data.BasePath, out var p ) ? p : Utf8RelPath.Empty ) ) - .ToArray(); - - if( Meta.Groups.Count == 0 ) - { - return; - } - - for( var i = 0; i < Mod.Data.Resources.ModFiles.Count; ++i ) - { - foreach( var group in Meta.Groups.Values ) - { - var inAll = true; - foreach( var option in group.Options ) - { - if( option.OptionFiles.ContainsKey( _fullFilenameList[ i ].relName ) ) - { - _fullFilenameList[ i ].color = ColorYellow; - } - else - { - inAll = false; - } - } - - if( inAll && group.SelectionType == SelectType.Single ) - { - _fullFilenameList[ i ].color = ColorGreen; - } - } - } - } - - private void DrawFileListTab() - { - if( !ImGui.BeginTabItem( LabelFileListTab ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); - ImGuiCustom.HoverTooltip( TooltipFilesTab ); - - ImGui.SetNextItemWidth( -1 ); - if( ImGui.BeginListBox( LabelFileListHeader, AutoFillSize ) ) - { - raii.Push( ImGui.EndListBox ); - UpdateFilenameList(); - using var colorRaii = new ImGuiRaii.Color(); - foreach( var (name, _, color, _) in _fullFilenameList! ) - { - colorRaii.Push( ImGuiCol.Text, color ); - ImGui.Selectable( name.FullName ); - colorRaii.Pop(); - } - } - else - { - _fullFilenameList = null; - } - } - - private static int HandleDefaultString( Utf8GamePath[] gamePaths, out int removeFolders ) - { - removeFolders = 0; - var defaultIndex = gamePaths.IndexOf( p => p.Path.StartsWith( DefaultUtf8GamePath ) ); - if( defaultIndex < 0 ) - { - return defaultIndex; - } - - var path = gamePaths[ defaultIndex ].Path; - if( path.Length == TextDefaultGamePath.Length ) - { - return defaultIndex; - } - - if( path[ TextDefaultGamePath.Length ] != ( byte )'-' - || !int.TryParse( path.Substring( TextDefaultGamePath.Length + 1 ).ToString(), out removeFolders ) ) - { - return -1; - } - - return defaultIndex; - } - - private void HandleSelectedFilesButton( bool remove ) - { - if( _selectedOption == null ) - { - return; - } - - var option = ( Option )_selectedOption; - - var gamePaths = _currentGamePaths.Split( ';' ) - .Select( p => Utf8GamePath.FromString( p, out var path, false ) ? path : Utf8GamePath.Empty ).Where( p => !p.IsEmpty ).ToArray(); - if( gamePaths.Length == 0 ) - { - return; - } - - var defaultIndex = HandleDefaultString( gamePaths, out var removeFolders ); - var changed = false; - for( var i = 0; i < Mod.Data.Resources.ModFiles.Count; ++i ) - { - if( !_fullFilenameList![ i ].selected ) - { - continue; - } - - _fullFilenameList![ i ].selected = false; - var relName = _fullFilenameList[ i ].relName; - if( defaultIndex >= 0 ) - { - gamePaths[ defaultIndex ] = relName.ToGamePath( removeFolders ); - } - - if( remove && option.OptionFiles.TryGetValue( relName, out var setPaths ) ) - { - if( setPaths.RemoveWhere( p => gamePaths.Contains( p ) ) > 0 ) - { - changed = true; - } - - if( setPaths.Count == 0 && option.OptionFiles.Remove( relName ) ) - { - changed = true; - } - } - else - { - changed = gamePaths - .Aggregate( changed, ( current, gamePath ) => current | option.AddFile( relName, gamePath ) ); - } - } - - if( changed ) - { - _fullFilenameList = null; - _selector.SaveCurrentMod(); - var idx = Penumbra.ModManager.Mods.IndexOf( Mod.Data ); - // Since files may have changed, we need to recompute effective files. - foreach( var collection in Penumbra.CollectionManager - .Where( c => c.HasCache && c[ idx ].Settings?.Enabled == true ) ) - { - collection.CalculateEffectiveFileList( false, collection == Penumbra.CollectionManager.Default ); - } - - // If the mod is enabled in the current collection, its conflicts may have changed. - if( Mod.Settings.Enabled ) - { - _selector.Cache.TriggerFilterReset(); - } - } - } - - private void DrawAddToGroupButton() - { - if( ImGui.Button( ButtonAddToGroup ) ) - { - HandleSelectedFilesButton( false ); - } - } - - private void DrawRemoveFromGroupButton() - { - if( ImGui.Button( ButtonRemoveFromGroup ) ) - { - HandleSelectedFilesButton( true ); - } - } - - private void DrawGamePathInput() - { - ImGui.SetNextItemWidth( -1 ); - ImGui.InputTextWithHint( LabelGamePathsEditBox, "Hover for help...", ref _currentGamePaths, - 128 ); - ImGuiCustom.HoverTooltip( TooltipGamePathsEdit ); - } - - private void DrawGroupRow() - { - if( _selectedGroup == null ) - { - SelectGroup(); - } - - if( _selectedOption == null ) - { - SelectOption(); - } - - if( !DrawEditGroupSelector() ) - { - return; - } - - ImGui.SameLine(); - if( !DrawEditOptionSelector() ) - { - return; - } - - ImGui.SameLine(); - DrawAddToGroupButton(); - ImGui.SameLine(); - DrawRemoveFromGroupButton(); - ImGui.SameLine(); - DrawGamePathInput(); - } - - private void DrawFileAndGamePaths( int idx ) - { - void Selectable( uint colorNormal, uint colorReplace ) - { - var loc = _fullFilenameList![ idx ].color; - if( loc == colorNormal ) - { - loc = colorReplace; - } - - using var colors = ImGuiRaii.PushColor( ImGuiCol.Text, loc ); - ImGui.Selectable( _fullFilenameList[ idx ].name.FullName, ref _fullFilenameList[ idx ].selected ); - } - - const float indentWidth = 30f; - if( _selectedOption == null ) - { - Selectable( 0, ColorGreen ); - return; - } - - var fileName = _fullFilenameList![ idx ].relName; - var optionFiles = ( ( Option )_selectedOption ).OptionFiles; - if( optionFiles.TryGetValue( fileName, out var gamePaths ) ) - { - Selectable( 0, ColorGreen ); - - using var indent = ImGuiRaii.PushIndent( indentWidth ); - foreach( var gamePath in gamePaths.ToArray() ) - { - var tmp = gamePath.ToString(); - var old = tmp; - if( ImGui.InputText( $"##{fileName}_{gamePath}", ref tmp, 128, ImGuiInputTextFlags.EnterReturnsTrue ) - && tmp != old ) - { - gamePaths.Remove( gamePath ); - if( tmp.Length > 0 && Utf8GamePath.FromString( tmp, out var p, true ) ) - { - gamePaths.Add( p ); - } - else if( gamePaths.Count == 0 ) - { - optionFiles.Remove( fileName ); - } - - _selector.SaveCurrentMod(); - _selector.ReloadCurrentMod(); - } - } - } - else - { - Selectable( ColorYellow, ColorRed ); - } - } - - private void DrawMultiSelectorCheckBox( OptionGroup group, int idx, int flag, string label ) - { - var enabled = ( flag & ( 1 << idx ) ) != 0; - var oldEnabled = enabled; - if( ImGui.Checkbox( label, ref enabled ) && oldEnabled != enabled ) - { - Penumbra.CollectionManager.Current.SetModSetting( Mod.Data.Index, group.GroupName, - Mod.Settings.Settings[ group.GroupName ] ^ ( 1 << idx ) ); - // If the mod is enabled, recalculate files and filters. - if( Mod.Settings.Enabled ) - { - _selector.Cache.TriggerFilterReset(); - } - } - } - - private void DrawMultiSelector( OptionGroup group ) - { - if( group.Options.Count == 0 ) - { - return; - } - - ImGuiCustom.BeginFramedGroup( group.GroupName ); - using var raii = ImGuiRaii.DeferredEnd( ImGuiCustom.EndFramedGroup ); - for( var i = 0; i < group.Options.Count; ++i ) - { - DrawMultiSelectorCheckBox( group, i, Mod.Settings.Settings[ group.GroupName ], - $"{group.Options[ i ].OptionName}##{group.GroupName}" ); - } - } - - private void DrawSingleSelector( OptionGroup group ) - { - if( group.Options.Count < 2 ) - { - return; - } - - var code = Mod.Settings.Settings[ group.GroupName ]; - if( ImGui.Combo( group.GroupName, ref code - , group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) - && code != Mod.Settings.Settings[ group.GroupName ] ) - { - Penumbra.CollectionManager.Current.SetModSetting( Mod.Data.Index, group.GroupName, code ); - if( Mod.Settings.Enabled ) - { - _selector.Cache.TriggerFilterReset(); - } - } - } - - private void DrawGroupSelectors() - { - foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Single ) ) - { - DrawSingleSelector( g ); - } - - foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Multi ) ) - { - DrawMultiSelector( g ); - } - } - - private void DrawConfigurationTab() - { - if( !_editMode && !Meta.HasGroupsWithConfig || !ImGui.BeginTabItem( LabelConfigurationTab ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); - if( _editMode ) - { - DrawGroupSelectorsEdit(); - } - else - { - DrawGroupSelectors(); - } - } - - private void DrawMetaManipulationsTab() - { - if( !_editMode && Mod.Data.Resources.MetaManipulations.Count == 0 || !ImGui.BeginTabItem( "Meta Manipulations" ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); - - if( !ImGui.BeginListBox( "##MetaManipulations", AutoFillSize ) ) - { - return; - } - - raii.Push( ImGui.EndListBox ); - - var manips = Mod.Data.Resources.MetaManipulations; - var changes = false; - if( _editMode || manips.DefaultData.Count > 0 ) - { - if( ImGui.CollapsingHeader( "Default" ) ) - { - changes = DrawMetaManipulationsTable( "##DefaultManips", manips.DefaultData, ref manips.Count ); - } - } - - foreach( var (groupName, group) in manips.GroupData ) - { - foreach( var (optionName, option) in group ) - { - if( ImGui.CollapsingHeader( $"{groupName} - {optionName}" ) ) - { - changes |= DrawMetaManipulationsTable( $"##{groupName}{optionName}manips", option, ref manips.Count ); - } - } - } - - if( changes ) - { - Mod.Data.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( Mod.Data.BasePath ) ); - Mod.Data.Resources.SetManipulations( Meta, Mod.Data.BasePath, false ); - _selector.ReloadCurrentMod( true, false ); - } - } - - public void Draw( bool editMode ) - { - _editMode = editMode; - if( !ImGui.BeginTabBar( LabelPluginDetails ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabBar ); - DrawAboutTab(); - DrawChangedItemsTab(); - - DrawConfigurationTab(); - if( _editMode ) - { - DrawFileListTabEdit(); - } - else - { - DrawFileListTab(); - } - - DrawFileSwapTab(); - DrawMetaManipulationsTab(); - DrawConflictTab(); - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsEdit.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsEdit.cs deleted file mode 100644 index 7a80f150..00000000 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsEdit.cs +++ /dev/null @@ -1,380 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; -using ImGuiNET; -using Penumbra.GameData.ByteString; -using Penumbra.GameData.Util; -using Penumbra.Mods; -using Penumbra.UI.Custom; -using Penumbra.Util; - -namespace Penumbra.UI; - -public partial class SettingsInterface -{ - private partial class PluginDetails - { - private const string LabelDescEdit = "##descedit"; - private const string LabelNewSingleGroupEdit = "##newSingleGroup"; - private const string LabelNewMultiGroup = "##newMultiGroup"; - private const string LabelGamePathsEditBox = "##gamePathsEdit"; - private const string ButtonAddToGroup = "Add to Group"; - private const string ButtonRemoveFromGroup = "Remove from Group"; - private const string TooltipAboutEdit = "Use Ctrl+Enter for newlines."; - private const string TextNoOptionAvailable = "[Not Available]"; - private const string TextDefaultGamePath = "default"; - private static readonly Utf8String DefaultUtf8GamePath = Utf8String.FromStringUnsafe( TextDefaultGamePath, true ); - private const char GamePathsSeparator = ';'; - - private static readonly string TooltipFilesTabEdit = - $"{TooltipFilesTab}\n" - + $"Red Files are replaced in another group or a different option in this group, but not contained in the current option."; - - private static readonly string TooltipGamePathsEdit = - $"Enter all game paths to add or remove, separated by '{GamePathsSeparator}'.\n" - + $"Use '{TextDefaultGamePath}' to add the original file path." - + $"Use '{TextDefaultGamePath}-#' to skip the first # relative directories."; - - private const float MultiEditBoxWidth = 300f; - - private bool DrawEditGroupSelector() - { - ImGui.SetNextItemWidth( OptionSelectionWidth * ImGuiHelpers.GlobalScale ); - if( Meta!.Groups.Count == 0 ) - { - ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex, TextNoOptionAvailable, 1 ); - return false; - } - - if( ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex - , Meta.Groups.Values.Select( g => g.GroupName ).ToArray() - , Meta.Groups.Count ) ) - { - SelectGroup(); - SelectOption( 0 ); - } - - return true; - } - - private bool DrawEditOptionSelector() - { - ImGui.SameLine(); - ImGui.SetNextItemWidth( OptionSelectionWidth ); - if( ( _selectedGroup?.Options.Count ?? 0 ) == 0 ) - { - ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, TextNoOptionAvailable, 1 ); - return false; - } - - var group = ( OptionGroup )_selectedGroup!; - if( ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, group.Options.Select( o => o.OptionName ).ToArray(), - group.Options.Count ) ) - { - SelectOption(); - } - - return true; - } - - private void DrawFileListTabEdit() - { - if( ImGui.BeginTabItem( LabelFileListTab ) ) - { - UpdateFilenameList(); - if( ImGui.IsItemHovered() ) - { - ImGui.SetTooltip( _editMode ? TooltipFilesTabEdit : TooltipFilesTab ); - } - - ImGui.SetNextItemWidth( -1 ); - if( ImGui.BeginListBox( LabelFileListHeader, AutoFillSize - Vector2.UnitY * 1.5f * ImGui.GetTextLineHeight() ) ) - { - for( var i = 0; i < Mod!.Data.Resources.ModFiles.Count; ++i ) - { - DrawFileAndGamePaths( i ); - } - } - - ImGui.EndListBox(); - - DrawGroupRow(); - ImGui.EndTabItem(); - } - else - { - _fullFilenameList = null; - } - } - - private ImGuiRaii.EndStack DrawMultiSelectorEditBegin( OptionGroup group ) - { - var groupName = group.GroupName; - if( ImGuiCustom.BeginFramedGroupEdit( ref groupName ) ) - { - if( Penumbra.ModManager.ChangeModGroup( group.GroupName, groupName, Mod.Data ) && Mod.Data.Meta.RefreshHasGroupsWithConfig() ) - { - _selector.Cache.TriggerFilterReset(); - } - } - - return ImGuiRaii.DeferredEnd( ImGuiCustom.EndFramedGroup ); - } - - private void DrawMultiSelectorEditAdd( OptionGroup group, float nameBoxStart ) - { - var newOption = ""; - ImGui.SetCursorPosX( nameBoxStart ); - ImGui.SetNextItemWidth( MultiEditBoxWidth * ImGuiHelpers.GlobalScale ); - if( ImGui.InputTextWithHint( $"##new_{group.GroupName}_l", "Add new option...", ref newOption, 64, - ImGuiInputTextFlags.EnterReturnsTrue ) - && newOption.Length != 0 ) - { - group.Options.Add( new Option() - { OptionName = newOption, OptionDesc = "", OptionFiles = new Dictionary< Utf8RelPath, HashSet< Utf8GamePath > >() } ); - _selector.SaveCurrentMod(); - if( Mod!.Data.Meta.RefreshHasGroupsWithConfig() ) - { - _selector.Cache.TriggerFilterReset(); - } - } - } - - private void DrawMultiSelectorEdit( OptionGroup group ) - { - var nameBoxStart = CheckMarkSize; - var flag = Mod!.Settings.Settings[ group.GroupName ]; - - using var raii = DrawMultiSelectorEditBegin( group ); - for( var i = 0; i < group.Options.Count; ++i ) - { - var opt = group.Options[ i ]; - var label = $"##{group.GroupName}_{i}"; - DrawMultiSelectorCheckBox( group, i, flag, label ); - - ImGui.SameLine(); - var newName = opt.OptionName; - - if( nameBoxStart == CheckMarkSize ) - { - nameBoxStart = ImGui.GetCursorPosX(); - } - - ImGui.SetNextItemWidth( MultiEditBoxWidth * ImGuiHelpers.GlobalScale ); - if( ImGui.InputText( $"{label}_l", ref newName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) ) - { - if( newName.Length == 0 ) - { - Penumbra.ModManager.RemoveModOption( i, group, Mod.Data ); - } - else if( newName != opt.OptionName ) - { - group.Options[ i ] = new Option() - { OptionName = newName, OptionDesc = opt.OptionDesc, OptionFiles = opt.OptionFiles }; - _selector.SaveCurrentMod(); - } - - if( Mod!.Data.Meta.RefreshHasGroupsWithConfig() ) - { - _selector.Cache.TriggerFilterReset(); - } - } - } - - DrawMultiSelectorEditAdd( group, nameBoxStart ); - } - - private void DrawSingleSelectorEditGroup( OptionGroup group ) - { - var groupName = group.GroupName; - if( ImGui.InputText( $"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) ) - { - if( Penumbra.ModManager.ChangeModGroup( group.GroupName, groupName, Mod.Data ) && Mod.Data.Meta.RefreshHasGroupsWithConfig() ) - { - _selector.Cache.TriggerFilterReset(); - } - } - } - - private float DrawSingleSelectorEdit( OptionGroup group ) - { - var oldSetting = Mod!.Settings.Settings[ group.GroupName ]; - var code = oldSetting; - if( ImGuiCustom.RenameableCombo( $"##{group.GroupName}", ref code, out var newName, - group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) ) - { - if( code == group.Options.Count ) - { - if( newName.Length > 0 ) - { - Penumbra.CollectionManager.Current.SetModSetting(Mod.Data.Index, group.GroupName, code); - group.Options.Add( new Option() - { - OptionName = newName, - OptionDesc = "", - OptionFiles = new Dictionary< Utf8RelPath, HashSet< Utf8GamePath > >(), - } ); - _selector.SaveCurrentMod(); - } - } - else - { - if( newName.Length == 0 ) - { - Penumbra.ModManager.RemoveModOption( code, group, Mod.Data ); - } - else - { - if( newName != group.Options[ code ].OptionName ) - { - group.Options[ code ] = new Option() - { - OptionName = newName, OptionDesc = group.Options[ code ].OptionDesc, - OptionFiles = group.Options[ code ].OptionFiles, - }; - _selector.SaveCurrentMod(); - } - } - } - - if( Mod.Data.Meta.RefreshHasGroupsWithConfig() ) - { - _selector.Cache.TriggerFilterReset(); - } - } - - ImGui.SameLine(); - var labelEditPos = ImGui.GetCursorPosX(); - DrawSingleSelectorEditGroup( group ); - - return labelEditPos; - } - - private void DrawAddSingleGroupField( float labelEditPos ) - { - var newGroup = ""; - ImGui.SetCursorPosX( labelEditPos ); - if( labelEditPos == CheckMarkSize ) - { - ImGui.SetNextItemWidth( MultiEditBoxWidth * ImGuiHelpers.GlobalScale ); - } - - if( ImGui.InputTextWithHint( LabelNewSingleGroupEdit, "Add new Single Group...", ref newGroup, 64, - ImGuiInputTextFlags.EnterReturnsTrue ) ) - { - Penumbra.ModManager.ChangeModGroup( "", newGroup, Mod.Data, SelectType.Single ); - // Adds empty group, so can not change filters. - } - } - - private void DrawAddMultiGroupField() - { - var newGroup = ""; - ImGui.SetCursorPosX( CheckMarkSize ); - ImGui.SetNextItemWidth( MultiEditBoxWidth * ImGuiHelpers.GlobalScale ); - if( ImGui.InputTextWithHint( LabelNewMultiGroup, "Add new Multi Group...", ref newGroup, 64, - ImGuiInputTextFlags.EnterReturnsTrue ) ) - { - Penumbra.ModManager.ChangeModGroup( "", newGroup, Mod.Data, SelectType.Multi ); - // Adds empty group, so can not change filters. - } - } - - private void DrawGroupSelectorsEdit() - { - var labelEditPos = CheckMarkSize; - var groups = Meta.Groups.Values.ToArray(); - foreach( var g in groups.Where( g => g.SelectionType == SelectType.Single ) ) - { - labelEditPos = DrawSingleSelectorEdit( g ); - } - - DrawAddSingleGroupField( labelEditPos ); - - foreach( var g in groups.Where( g => g.SelectionType == SelectType.Multi ) ) - { - DrawMultiSelectorEdit( g ); - } - - DrawAddMultiGroupField(); - } - - private void DrawFileSwapTabEdit() - { - if( !ImGui.BeginTabItem( LabelFileSwapTab ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); - - ImGui.SetNextItemWidth( -1 ); - if( !ImGui.BeginListBox( LabelFileSwapHeader, AutoFillSize ) ) - { - return; - } - - raii.Push( ImGui.EndListBox ); - - var swaps = Meta.FileSwaps.Keys.ToArray(); - - ImGui.PushFont( UiBuilder.IconFont ); - var arrowWidth = ImGui.CalcTextSize( FontAwesomeIcon.LongArrowAltRight.ToIconString() ).X; - ImGui.PopFont(); - - var width = ( ImGui.GetWindowWidth() - arrowWidth - 4 * ImGui.GetStyle().ItemSpacing.X ) / 2; - for( var idx = 0; idx < swaps.Length + 1; ++idx ) - { - var key = idx == swaps.Length ? Utf8GamePath.Empty : swaps[ idx ]; - var value = idx == swaps.Length ? FullPath.Empty : Meta.FileSwaps[ key ]; - var keyString = key.ToString(); - var valueString = value.ToString(); - - ImGui.SetNextItemWidth( width ); - if( ImGui.InputTextWithHint( $"##swapLhs_{idx}", "Enter new file to be replaced...", ref keyString, - GamePath.MaxGamePathLength, ImGuiInputTextFlags.EnterReturnsTrue ) ) - { - if( Utf8GamePath.FromString( keyString, out var newKey, true ) && newKey.CompareTo( key ) != 0 ) - { - if( idx < swaps.Length ) - { - Meta.FileSwaps.Remove( key ); - } - - if( !newKey.IsEmpty ) - { - Meta.FileSwaps[ newKey ] = value; - } - - _selector.SaveCurrentMod(); - _selector.ReloadCurrentMod(); - } - } - - if( idx >= swaps.Length ) - { - continue; - } - - ImGui.SameLine(); - ImGuiCustom.PrintIcon( FontAwesomeIcon.LongArrowAltRight ); - ImGui.SameLine(); - - ImGui.SetNextItemWidth( width ); - if( ImGui.InputTextWithHint( $"##swapRhs_{idx}", "Enter new replacement path...", ref valueString, - GamePath.MaxGamePathLength, - ImGuiInputTextFlags.EnterReturnsTrue ) ) - { - var newValue = new FullPath( valueString.ToLowerInvariant() ); - if( newValue.CompareTo( value ) != 0 ) - { - Meta.FileSwaps[ key ] = newValue; - _selector.SaveCurrentMod(); - _selector.Cache.TriggerListReset(); - } - } - } - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsManipulations.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsManipulations.cs deleted file mode 100644 index 8331ceb8..00000000 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsManipulations.cs +++ /dev/null @@ -1,773 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; -using ImGuiNET; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Penumbra.Meta.Files; -using Penumbra.Meta.Manipulations; -using Penumbra.UI.Custom; -using ObjectType = Penumbra.GameData.Enums.ObjectType; - -namespace Penumbra.UI; - -public partial class SettingsInterface -{ - private partial class PluginDetails - { - private int _newManipTypeIdx = 0; - private ushort _newManipSetId = 0; - private ushort _newManipSecondaryId = 0; - private int _newManipSubrace = 0; - private int _newManipRace = 0; - private int _newManipAttribute = 0; - private int _newManipEquipSlot = 0; - private int _newManipObjectType = 0; - private int _newManipGender = 0; - private int _newManipBodySlot = 0; - private ushort _newManipVariant = 0; - - - private static readonly (string, EquipSlot)[] EqpEquipSlots = - { - ( "Head", EquipSlot.Head ), - ( "Body", EquipSlot.Body ), - ( "Hands", EquipSlot.Hands ), - ( "Legs", EquipSlot.Legs ), - ( "Feet", EquipSlot.Feet ), - }; - - private static readonly (string, EquipSlot)[] EqdpEquipSlots = - { - EqpEquipSlots[ 0 ], - EqpEquipSlots[ 1 ], - EqpEquipSlots[ 2 ], - EqpEquipSlots[ 3 ], - EqpEquipSlots[ 4 ], - ( "Ears", EquipSlot.Ears ), - ( "Neck", EquipSlot.Neck ), - ( "Wrist", EquipSlot.Wrists ), - ( "Left Finger", EquipSlot.LFinger ), - ( "Right Finger", EquipSlot.RFinger ), - }; - - private static readonly (string, ModelRace)[] Races = - { - ( ModelRace.Midlander.ToName(), ModelRace.Midlander ), - ( ModelRace.Highlander.ToName(), ModelRace.Highlander ), - ( ModelRace.Elezen.ToName(), ModelRace.Elezen ), - ( ModelRace.Miqote.ToName(), ModelRace.Miqote ), - ( ModelRace.Roegadyn.ToName(), ModelRace.Roegadyn ), - ( ModelRace.Lalafell.ToName(), ModelRace.Lalafell ), - ( ModelRace.AuRa.ToName(), ModelRace.AuRa ), - ( ModelRace.Viera.ToName(), ModelRace.Viera ), - ( ModelRace.Hrothgar.ToName(), ModelRace.Hrothgar ), - }; - - private static readonly (string, Gender)[] Genders = - { - ( Gender.Male.ToName(), Gender.Male ), - ( Gender.Female.ToName(), Gender.Female ), - ( Gender.MaleNpc.ToName(), Gender.MaleNpc ), - ( Gender.FemaleNpc.ToName(), Gender.FemaleNpc ), - }; - - private static readonly (string, EstManipulation.EstType)[] EstTypes = - { - ( "Hair", EstManipulation.EstType.Hair ), - ( "Face", EstManipulation.EstType.Face ), - ( "Body", EstManipulation.EstType.Body ), - ( "Head", EstManipulation.EstType.Head ), - }; - - private static readonly (string, SubRace)[] Subraces = - { - ( SubRace.Midlander.ToName(), SubRace.Midlander ), - ( SubRace.Highlander.ToName(), SubRace.Highlander ), - ( SubRace.Wildwood.ToName(), SubRace.Wildwood ), - ( SubRace.Duskwight.ToName(), SubRace.Duskwight ), - ( SubRace.SeekerOfTheSun.ToName(), SubRace.SeekerOfTheSun ), - ( SubRace.KeeperOfTheMoon.ToName(), SubRace.KeeperOfTheMoon ), - ( SubRace.Seawolf.ToName(), SubRace.Seawolf ), - ( SubRace.Hellsguard.ToName(), SubRace.Hellsguard ), - ( SubRace.Plainsfolk.ToName(), SubRace.Plainsfolk ), - ( SubRace.Dunesfolk.ToName(), SubRace.Dunesfolk ), - ( SubRace.Raen.ToName(), SubRace.Raen ), - ( SubRace.Xaela.ToName(), SubRace.Xaela ), - ( SubRace.Rava.ToName(), SubRace.Rava ), - ( SubRace.Veena.ToName(), SubRace.Veena ), - ( SubRace.Helion.ToName(), SubRace.Helion ), - ( SubRace.Lost.ToName(), SubRace.Lost ), - }; - - private static readonly (string, RspAttribute)[] RspAttributes = - { - ( RspAttribute.MaleMinSize.ToFullString(), RspAttribute.MaleMinSize ), - ( RspAttribute.MaleMaxSize.ToFullString(), RspAttribute.MaleMaxSize ), - ( RspAttribute.FemaleMinSize.ToFullString(), RspAttribute.FemaleMinSize ), - ( RspAttribute.FemaleMaxSize.ToFullString(), RspAttribute.FemaleMaxSize ), - ( RspAttribute.BustMinX.ToFullString(), RspAttribute.BustMinX ), - ( RspAttribute.BustMaxX.ToFullString(), RspAttribute.BustMaxX ), - ( RspAttribute.BustMinY.ToFullString(), RspAttribute.BustMinY ), - ( RspAttribute.BustMaxY.ToFullString(), RspAttribute.BustMaxY ), - ( RspAttribute.BustMinZ.ToFullString(), RspAttribute.BustMinZ ), - ( RspAttribute.BustMaxZ.ToFullString(), RspAttribute.BustMaxZ ), - ( RspAttribute.MaleMinTail.ToFullString(), RspAttribute.MaleMinTail ), - ( RspAttribute.MaleMaxTail.ToFullString(), RspAttribute.MaleMaxTail ), - ( RspAttribute.FemaleMinTail.ToFullString(), RspAttribute.FemaleMinTail ), - ( RspAttribute.FemaleMaxTail.ToFullString(), RspAttribute.FemaleMaxTail ), - }; - - - private static readonly (string, ObjectType)[] ImcObjectType = - { - ( "Equipment", ObjectType.Equipment ), - ( "Customization", ObjectType.Character ), - ( "Weapon", ObjectType.Weapon ), - ( "Demihuman", ObjectType.DemiHuman ), - ( "Monster", ObjectType.Monster ), - }; - - private static readonly (string, BodySlot)[] ImcBodySlots = - { - ( "Hair", BodySlot.Hair ), - ( "Face", BodySlot.Face ), - ( "Body", BodySlot.Body ), - ( "Tail", BodySlot.Tail ), - ( "Ears", BodySlot.Zear ), - }; - - private static bool PrintCheckBox( string name, ref bool value, bool def ) - { - var color = value == def ? 0 : value ? ColorDarkGreen : ColorDarkRed; - if( color == 0 ) - { - return ImGui.Checkbox( name, ref value ); - } - - using var colorRaii = ImGuiRaii.PushColor( ImGuiCol.Text, color ); - var ret = ImGui.Checkbox( name, ref value ); - return ret; - } - - private bool RestrictedInputInt( string name, ref ushort value, ushort min, ushort max ) - { - int tmp = value; - if( ImGui.InputInt( name, ref tmp, 0, 0, _editMode ? ImGuiInputTextFlags.EnterReturnsTrue : ImGuiInputTextFlags.ReadOnly ) - && tmp != value - && tmp >= min - && tmp <= max ) - { - value = ( ushort )tmp; - return true; - } - - return false; - } - - private static bool DefaultButton< T >( string name, ref T value, T defaultValue ) where T : IComparable< T > - { - var compare = defaultValue.CompareTo( value ); - var color = compare < 0 ? ColorDarkGreen : - compare > 0 ? ColorDarkRed : ImGui.ColorConvertFloat4ToU32( ImGui.GetStyle().Colors[ ( int )ImGuiCol.Button ] ); - - using var colorRaii = ImGuiRaii.PushColor( ImGuiCol.Button, color ); - var ret = ImGui.Button( name, Vector2.UnitX * 120 ) && compare != 0; - ImGui.SameLine(); - return ret; - } - - private bool DrawInputWithDefault( string name, ref ushort value, ushort defaultValue, ushort max ) - => DefaultButton( $"{( _editMode ? "Set to " : "" )}Default: {defaultValue}##imc{name}", ref value, defaultValue ) - || RestrictedInputInt( name, ref value, 0, max ); - - private static bool CustomCombo< T >( string label, IList< (string, T) > namesAndValues, out T value, ref int idx ) - { - value = idx < namesAndValues.Count ? namesAndValues[ idx ].Item2 : default!; - - if( !ImGui.BeginCombo( label, idx < namesAndValues.Count ? namesAndValues[ idx ].Item1 : string.Empty ) ) - { - return false; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndCombo ); - - for( var i = 0; i < namesAndValues.Count; ++i ) - { - if( !ImGui.Selectable( $"{namesAndValues[ i ].Item1}##{label}{i}", idx == i ) || idx == i ) - { - continue; - } - - idx = i; - value = namesAndValues[ i ].Item2; - return true; - } - - return false; - } - - private bool DrawEqpRow( int manipIdx, IList< MetaManipulation > list ) - { - var ret = false; - var id = list[ manipIdx ].Eqp; - var val = id.Entry; - - - if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) - { - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - var defaults = ExpandedEqpFile.GetDefault( id.SetId ); - var attributes = Eqp.EqpAttributes[ id.Slot ]; - - foreach( var flag in attributes ) - { - var name = flag.ToLocalName(); - var tmp = val.HasFlag( flag ); - if( PrintCheckBox( $"{name}##manip", ref tmp, defaults.HasFlag( flag ) ) && _editMode && tmp != val.HasFlag( flag ) ) - { - list[ manipIdx ] = new MetaManipulation( new EqpManipulation( tmp ? val | flag : val & ~flag, id.Slot, id.SetId ) ); - ret = true; - } - } - } - - ImGui.Text( ObjectType.Equipment.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( id.SetId.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( id.Slot.ToString() ); - return ret; - } - - private bool DrawGmpRow( int manipIdx, IList< MetaManipulation > list ) - { - var ret = false; - var id = list[ manipIdx ].Gmp; - var val = id.Entry; - - if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) - { - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - var defaults = ExpandedGmpFile.GetDefault( id.SetId ); - var enabled = val.Enabled; - var animated = val.Animated; - var rotationA = val.RotationA; - var rotationB = val.RotationB; - var rotationC = val.RotationC; - ushort unk = val.UnknownTotal; - - ret |= PrintCheckBox( "Visor Enabled##manip", ref enabled, defaults.Enabled ) && enabled != val.Enabled; - ret |= PrintCheckBox( "Visor Animated##manip", ref animated, defaults.Animated ); - ret |= DrawInputWithDefault( "Rotation A##manip", ref rotationA, defaults.RotationA, 0x3FF ); - ret |= DrawInputWithDefault( "Rotation B##manip", ref rotationB, defaults.RotationB, 0x3FF ); - ret |= DrawInputWithDefault( "Rotation C##manip", ref rotationC, defaults.RotationC, 0x3FF ); - ret |= DrawInputWithDefault( "Unknown Byte##manip", ref unk, defaults.UnknownTotal, 0xFF ); - - if( ret && _editMode ) - { - list[ manipIdx ] = new MetaManipulation( new GmpManipulation( new GmpEntry - { - Animated = animated, Enabled = enabled, UnknownTotal = ( byte )unk, - RotationA = rotationA, RotationB = rotationB, RotationC = rotationC, - }, id.SetId ) ); - } - } - - ImGui.Text( ObjectType.Equipment.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( id.SetId.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( EquipSlot.Head.ToString() ); - return ret; - } - - private static (bool, bool) GetEqdpBits( EquipSlot slot, EqdpEntry entry ) - { - return slot switch - { - EquipSlot.Head => ( entry.HasFlag( EqdpEntry.Head1 ), entry.HasFlag( EqdpEntry.Head2 ) ), - EquipSlot.Body => ( entry.HasFlag( EqdpEntry.Body1 ), entry.HasFlag( EqdpEntry.Body2 ) ), - EquipSlot.Hands => ( entry.HasFlag( EqdpEntry.Hands1 ), entry.HasFlag( EqdpEntry.Hands2 ) ), - EquipSlot.Legs => ( entry.HasFlag( EqdpEntry.Legs1 ), entry.HasFlag( EqdpEntry.Legs2 ) ), - EquipSlot.Feet => ( entry.HasFlag( EqdpEntry.Feet1 ), entry.HasFlag( EqdpEntry.Feet2 ) ), - EquipSlot.Neck => ( entry.HasFlag( EqdpEntry.Neck1 ), entry.HasFlag( EqdpEntry.Neck2 ) ), - EquipSlot.Ears => ( entry.HasFlag( EqdpEntry.Ears1 ), entry.HasFlag( EqdpEntry.Ears2 ) ), - EquipSlot.Wrists => ( entry.HasFlag( EqdpEntry.Wrists1 ), entry.HasFlag( EqdpEntry.Wrists2 ) ), - EquipSlot.RFinger => ( entry.HasFlag( EqdpEntry.RingR1 ), entry.HasFlag( EqdpEntry.RingR2 ) ), - EquipSlot.LFinger => ( entry.HasFlag( EqdpEntry.RingL1 ), entry.HasFlag( EqdpEntry.RingL2 ) ), - _ => ( false, false ), - }; - } - - private static EqdpEntry SetEqdpBits( EquipSlot slot, EqdpEntry value, bool bit1, bool bit2 ) - { - switch( slot ) - { - case EquipSlot.Head: - value = bit1 ? value | EqdpEntry.Head1 : value & ~EqdpEntry.Head1; - value = bit2 ? value | EqdpEntry.Head2 : value & ~EqdpEntry.Head2; - return value; - case EquipSlot.Body: - value = bit1 ? value | EqdpEntry.Body1 : value & ~EqdpEntry.Body1; - value = bit2 ? value | EqdpEntry.Body2 : value & ~EqdpEntry.Body2; - return value; - case EquipSlot.Hands: - value = bit1 ? value | EqdpEntry.Hands1 : value & ~EqdpEntry.Hands1; - value = bit2 ? value | EqdpEntry.Hands2 : value & ~EqdpEntry.Hands2; - return value; - case EquipSlot.Legs: - value = bit1 ? value | EqdpEntry.Legs1 : value & ~EqdpEntry.Legs1; - value = bit2 ? value | EqdpEntry.Legs2 : value & ~EqdpEntry.Legs2; - return value; - case EquipSlot.Feet: - value = bit1 ? value | EqdpEntry.Feet1 : value & ~EqdpEntry.Feet1; - value = bit2 ? value | EqdpEntry.Feet2 : value & ~EqdpEntry.Feet2; - return value; - case EquipSlot.Neck: - value = bit1 ? value | EqdpEntry.Neck1 : value & ~EqdpEntry.Neck1; - value = bit2 ? value | EqdpEntry.Neck2 : value & ~EqdpEntry.Neck2; - return value; - case EquipSlot.Ears: - value = bit1 ? value | EqdpEntry.Ears1 : value & ~EqdpEntry.Ears1; - value = bit2 ? value | EqdpEntry.Ears2 : value & ~EqdpEntry.Ears2; - return value; - case EquipSlot.Wrists: - value = bit1 ? value | EqdpEntry.Wrists1 : value & ~EqdpEntry.Wrists1; - value = bit2 ? value | EqdpEntry.Wrists2 : value & ~EqdpEntry.Wrists2; - return value; - case EquipSlot.RFinger: - value = bit1 ? value | EqdpEntry.RingR1 : value & ~EqdpEntry.RingR1; - value = bit2 ? value | EqdpEntry.RingR2 : value & ~EqdpEntry.RingR2; - return value; - case EquipSlot.LFinger: - value = bit1 ? value | EqdpEntry.RingL1 : value & ~EqdpEntry.RingL1; - value = bit2 ? value | EqdpEntry.RingL2 : value & ~EqdpEntry.RingL2; - return value; - } - - return value; - } - - private bool DrawEqdpRow( int manipIdx, IList< MetaManipulation > list ) - { - var ret = false; - var id = list[ manipIdx ].Eqdp; - var val = id.Entry; - - if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) - { - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - var defaults = ExpandedEqdpFile.GetDefault( id.FileIndex(), id.SetId ); - var (bit1, bit2) = GetEqdpBits( id.Slot, val ); - var (defBit1, defBit2) = GetEqdpBits( id.Slot, defaults ); - - ret |= PrintCheckBox( "Bit 1##manip", ref bit1, defBit1 ); - ret |= PrintCheckBox( "Bit 2##manip", ref bit2, defBit2 ); - - if( ret && _editMode ) - { - list[ manipIdx ] = new MetaManipulation( new EqdpManipulation( SetEqdpBits( id.Slot, val, bit1, bit2 ), id.Slot, id.Gender, - id.Race, id.SetId ) ); - } - } - - ImGui.Text( id.Slot.IsAccessory() - ? ObjectType.Accessory.ToString() - : ObjectType.Equipment.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( id.SetId.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( id.Slot.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( id.Race.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( id.Gender.ToString() ); - return ret; - } - - private bool DrawEstRow( int manipIdx, IList< MetaManipulation > list ) - { - var ret = false; - var id = list[ manipIdx ].Est; - var val = id.Entry; - - if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) - { - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - var defaults = EstFile.GetDefault( id.Slot, Names.CombinedRace( id.Gender, id.Race ), id.SetId ); - if( DrawInputWithDefault( "No Idea what this does!##manip", ref val, defaults, ushort.MaxValue ) && _editMode ) - { - list[ manipIdx ] = new MetaManipulation( new EstManipulation( id.Gender, id.Race, id.Slot, id.SetId, val ) ); - ret = true; - } - } - - ImGui.Text( id.Slot.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( id.SetId.ToString() ); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.Text( id.Race.ToName() ); - ImGui.TableNextColumn(); - ImGui.Text( id.Gender.ToName() ); - - return ret; - } - - private bool DrawImcRow( int manipIdx, IList< MetaManipulation > list ) - { - var ret = false; - var id = list[ manipIdx ].Imc; - var val = id.Entry; - - if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) - { - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - var defaults = new ImcFile( id.GamePath() ).GetEntry( ImcFile.PartIndex( id.EquipSlot ), id.Variant ); - ushort materialId = val.MaterialId; - ushort vfxId = val.VfxId; - ushort decalId = val.DecalId; - var soundId = ( ushort )val.SoundId; - var attributeMask = val.AttributeMask; - var materialAnimationId = ( ushort )val.MaterialAnimationId; - ret |= DrawInputWithDefault( "Material Id", ref materialId, defaults.MaterialId, byte.MaxValue ); - ret |= DrawInputWithDefault( "Vfx Id", ref vfxId, defaults.VfxId, byte.MaxValue ); - ret |= DrawInputWithDefault( "Decal Id", ref decalId, defaults.DecalId, byte.MaxValue ); - ret |= DrawInputWithDefault( "Sound Id", ref soundId, defaults.SoundId, 0x3F ); - ret |= DrawInputWithDefault( "Attribute Mask", ref attributeMask, defaults.AttributeMask, 0x3FF ); - ret |= DrawInputWithDefault( "Material Animation Id", ref materialAnimationId, defaults.MaterialAnimationId, - byte.MaxValue ); - - if( ret && _editMode ) - { - var value = new ImcEntry( ( byte )materialId, ( byte )decalId, attributeMask, ( byte )soundId, ( byte )vfxId, - ( byte )materialAnimationId ); - list[ manipIdx ] = new MetaManipulation( new ImcManipulation( id, value ) ); - } - } - - ImGui.Text( id.ObjectType.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( id.PrimaryId.ToString() ); - ImGui.TableNextColumn(); - if( id.ObjectType is ObjectType.Accessory or ObjectType.Equipment ) - { - ImGui.Text( id.ObjectType is ObjectType.Equipment or ObjectType.Accessory - ? id.EquipSlot.ToString() - : id.BodySlot.ToString() ); - } - - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - if( id.ObjectType != ObjectType.Equipment - && id.ObjectType != ObjectType.Accessory ) - { - ImGui.Text( id.SecondaryId.ToString() ); - } - - ImGui.TableNextColumn(); - ImGui.Text( id.Variant.ToString() ); - return ret; - } - - private bool DrawRspRow( int manipIdx, IList< MetaManipulation > list ) - { - var ret = false; - var id = list[ manipIdx ].Rsp; - var defaults = CmpFile.GetDefault( id.SubRace, id.Attribute ); - var val = id.Entry; - if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) ) - { - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - if( DefaultButton( - $"{( _editMode ? "Set to " : "" )}Default: {defaults:F3}##scaleManip", ref val, defaults ) - && _editMode ) - { - list[ manipIdx ] = new MetaManipulation( new RspManipulation( id.SubRace, id.Attribute, val ) ); - ret = true; - } - - ImGui.SetNextItemWidth( 50 * ImGuiHelpers.GlobalScale ); - if( ImGui.InputFloat( "Scale###manip", ref val, 0, 0, "%.3f", - _editMode ? ImGuiInputTextFlags.EnterReturnsTrue : ImGuiInputTextFlags.ReadOnly ) - && val >= 0 - && val <= 5 - && _editMode ) - { - list[ manipIdx ] = new MetaManipulation( new RspManipulation( id.SubRace, id.Attribute, val ) ); - ret = true; - } - } - - ImGui.Text( id.Attribute.ToUngenderedString() ); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.Text( id.SubRace.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( id.Attribute.ToGender().ToString() ); - return ret; - } - - private bool DrawManipulationRow( ref int manipIdx, IList< MetaManipulation > list, ref int count ) - { - var type = list[ manipIdx ].ManipulationType; - - if( _editMode ) - { - ImGui.TableNextColumn(); - using var font = ImGuiRaii.PushFont( UiBuilder.IconFont ); - if( ImGui.Button( $"{FontAwesomeIcon.Trash.ToIconString()}##manipDelete{manipIdx}" ) ) - { - list.RemoveAt( manipIdx ); - ImGui.TableNextRow(); - --manipIdx; - --count; - return true; - } - } - - ImGui.TableNextColumn(); - ImGui.Text( type.ToString() ); - ImGui.TableNextColumn(); - - var changes = false; - switch( type ) - { - case MetaManipulation.Type.Eqp: - changes = DrawEqpRow( manipIdx, list ); - ImGui.TableSetColumnIndex( 9 ); - if( ImGui.Selectable( $"{list[ manipIdx ].Eqp.Entry}##{manipIdx}" ) ) - { - ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); - } - - break; - case MetaManipulation.Type.Gmp: - changes = DrawGmpRow( manipIdx, list ); - ImGui.TableSetColumnIndex( 9 ); - if( ImGui.Selectable( $"{list[ manipIdx ].Gmp.Entry.Value}##{manipIdx}" ) ) - { - ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); - } - - break; - case MetaManipulation.Type.Eqdp: - changes = DrawEqdpRow( manipIdx, list ); - ImGui.TableSetColumnIndex( 9 ); - var (bit1, bit2) = GetEqdpBits( list[ manipIdx ].Eqdp.Slot, list[ manipIdx ].Eqdp.Entry ); - if( ImGui.Selectable( $"{bit1} {bit2}##{manipIdx}" ) ) - { - ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); - } - - break; - case MetaManipulation.Type.Est: - changes = DrawEstRow( manipIdx, list ); - ImGui.TableSetColumnIndex( 9 ); - if( ImGui.Selectable( $"{list[ manipIdx ].Est.Entry}##{manipIdx}" ) ) - { - ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); - } - - break; - case MetaManipulation.Type.Imc: - changes = DrawImcRow( manipIdx, list ); - ImGui.TableSetColumnIndex( 9 ); - if( ImGui.Selectable( $"{list[ manipIdx ].Imc.Entry.MaterialId}##{manipIdx}" ) ) - { - ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); - } - - break; - case MetaManipulation.Type.Rsp: - changes = DrawRspRow( manipIdx, list ); - ImGui.TableSetColumnIndex( 9 ); - if( ImGui.Selectable( $"{list[ manipIdx ].Rsp.Entry}##{manipIdx}" ) ) - { - ImGui.OpenPopup( $"##MetaPopup{manipIdx}" ); - } - - break; - } - - - ImGui.TableNextRow(); - return changes; - } - - - private MetaManipulation.Type DrawNewTypeSelection() - { - ImGui.RadioButton( "IMC##newManipType", ref _newManipTypeIdx, 1 ); - ImGui.SameLine(); - ImGui.RadioButton( "EQDP##newManipType", ref _newManipTypeIdx, 2 ); - ImGui.SameLine(); - ImGui.RadioButton( "EQP##newManipType", ref _newManipTypeIdx, 3 ); - ImGui.SameLine(); - ImGui.RadioButton( "EST##newManipType", ref _newManipTypeIdx, 4 ); - ImGui.SameLine(); - ImGui.RadioButton( "GMP##newManipType", ref _newManipTypeIdx, 5 ); - ImGui.SameLine(); - ImGui.RadioButton( "RSP##newManipType", ref _newManipTypeIdx, 6 ); - return ( MetaManipulation.Type )_newManipTypeIdx; - } - - private bool DrawNewManipulationPopup( string popupName, IList< MetaManipulation > list, ref int count ) - { - var change = false; - if( !ImGui.BeginPopup( popupName ) ) - { - return change; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - var manipType = DrawNewTypeSelection(); - MetaManipulation? newManip = null; - switch( manipType ) - { - case MetaManipulation.Type.Imc: - { - RestrictedInputInt( "Set Id##newManipImc", ref _newManipSetId, 0, ushort.MaxValue ); - RestrictedInputInt( "Variant##newManipImc", ref _newManipVariant, 0, byte.MaxValue ); - CustomCombo( "Object Type", ImcObjectType, out var objectType, ref _newManipObjectType ); - ImcManipulation imc = new(); - switch( objectType ) - { - case ObjectType.Equipment: - CustomCombo( "Equipment Slot", EqdpEquipSlots, out var equipSlot, ref _newManipEquipSlot ); - imc = new ImcManipulation( equipSlot, _newManipVariant, _newManipSetId, new ImcEntry() ); - break; - case ObjectType.DemiHuman: - case ObjectType.Weapon: - case ObjectType.Monster: - RestrictedInputInt( "Secondary Id##newManipImc", ref _newManipSecondaryId, 0, ushort.MaxValue ); - CustomCombo( "Body Slot", ImcBodySlots, out var bodySlot, ref _newManipBodySlot ); - imc = new ImcManipulation( objectType, bodySlot, _newManipSetId, _newManipSecondaryId, - _newManipVariant, new ImcEntry() ); - break; - } - - newManip = new MetaManipulation( new ImcManipulation( imc.ObjectType, imc.BodySlot, imc.PrimaryId, imc.SecondaryId, - imc.Variant, imc.EquipSlot, ImcFile.GetDefault( imc.GamePath(), imc.EquipSlot, imc.Variant ) ) ); - - break; - } - case MetaManipulation.Type.Eqdp: - { - RestrictedInputInt( "Set Id##newManipEqdp", ref _newManipSetId, 0, ushort.MaxValue ); - CustomCombo( "Equipment Slot", EqdpEquipSlots, out var equipSlot, ref _newManipEquipSlot ); - CustomCombo( "Race", Races, out var race, ref _newManipRace ); - CustomCombo( "Gender", Genders, out var gender, ref _newManipGender ); - var eqdp = new EqdpManipulation( new EqdpEntry(), equipSlot, gender, race, _newManipSetId ); - newManip = new MetaManipulation( new EqdpManipulation( ExpandedEqdpFile.GetDefault( eqdp.FileIndex(), eqdp.SetId ), - equipSlot, gender, race, _newManipSetId ) ); - break; - } - case MetaManipulation.Type.Eqp: - { - RestrictedInputInt( "Set Id##newManipEqp", ref _newManipSetId, 0, ushort.MaxValue ); - CustomCombo( "Equipment Slot", EqpEquipSlots, out var equipSlot, ref _newManipEquipSlot ); - newManip = new MetaManipulation( new EqpManipulation( ExpandedEqpFile.GetDefault( _newManipSetId ) & Eqp.Mask( equipSlot ), - equipSlot, _newManipSetId ) ); - break; - } - case MetaManipulation.Type.Est: - { - RestrictedInputInt( "Set Id##newManipEst", ref _newManipSetId, 0, ushort.MaxValue ); - CustomCombo( "Est Type", EstTypes, out var estType, ref _newManipObjectType ); - CustomCombo( "Race", Races, out var race, ref _newManipRace ); - CustomCombo( "Gender", Genders, out var gender, ref _newManipGender ); - newManip = new MetaManipulation( new EstManipulation( gender, race, estType, _newManipSetId, - EstFile.GetDefault( estType, Names.CombinedRace( gender, race ), _newManipSetId ) ) ); - break; - } - case MetaManipulation.Type.Gmp: - RestrictedInputInt( "Set Id##newManipGmp", ref _newManipSetId, 0, ushort.MaxValue ); - newManip = new MetaManipulation( new GmpManipulation( ExpandedGmpFile.GetDefault( _newManipSetId ), _newManipSetId ) ); - break; - case MetaManipulation.Type.Rsp: - CustomCombo( "Subrace", Subraces, out var subRace, ref _newManipSubrace ); - CustomCombo( "Attribute", RspAttributes, out var rspAttribute, ref _newManipAttribute ); - newManip = new MetaManipulation( new RspManipulation( subRace, rspAttribute, - CmpFile.GetDefault( subRace, rspAttribute ) ) ); - break; - } - - if( ImGui.Button( "Create Manipulation##newManip", Vector2.UnitX * -1 ) - && newManip != null - && list.All( m => !m.Equals( newManip ) ) ) - { - list.Add( newManip.Value ); - change = true; - ++count; - ImGui.CloseCurrentPopup(); - } - - return change; - } - - private bool DrawMetaManipulationsTable( string label, IList< MetaManipulation > list, ref int count ) - { - var numRows = _editMode ? 11 : 10; - var changes = false; - - - if( list.Count > 0 - && ImGui.BeginTable( label, numRows, - ImGuiTableFlags.BordersInner | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit ) ) - { - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTable ); - if( _editMode ) - { - ImGui.TableNextColumn(); - } - - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Type##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Object Type##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Set##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Slot##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Race##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Gender##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Secondary ID##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Variant##{label}" ); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.TableHeader( $"Value##{label}" ); - ImGui.TableNextRow(); - - for( var i = 0; i < list.Count; ++i ) - { - changes |= DrawManipulationRow( ref i, list, ref count ); - } - } - - var popupName = $"##newManip{label}"; - if( _editMode ) - { - changes |= DrawNewManipulationPopup( $"##newManip{label}", list, ref count ); - if( ImGui.Button( $"Add New Manipulation##{label}", Vector2.UnitX * -1 ) ) - { - ImGui.OpenPopup( popupName ); - } - - return changes; - } - - return false; - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs deleted file mode 100644 index 9b6f7279..00000000 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs +++ /dev/null @@ -1,797 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Numerics; -using System.Runtime.InteropServices; -using System.Windows.Forms.VisualStyles; -using Dalamud.Interface; -using Dalamud.Logging; -using ImGuiNET; -using Penumbra.Collections; -using Penumbra.Importer; -using Penumbra.Mods; -using Penumbra.UI.Classes; -using Penumbra.UI.Custom; -using Penumbra.Util; - -namespace Penumbra.UI; - -public partial class SettingsInterface -{ - // Constants - private partial class Selector - { - private const string LabelSelectorList = "##availableModList"; - private const string LabelModFilter = "##ModFilter"; - private const string LabelAddModPopup = "AddModPopup"; - private const string LabelModHelpPopup = "Help##Selector"; - - private const string TooltipModFilter = - "Filter mods for those containing the given substring.\nEnter c:[string] to filter for mods changing specific items.\nEnter a:[string] to filter for mods by specific authors."; - - private const string TooltipDelete = "Delete the selected mod"; - private const string TooltipAdd = "Add an empty mod"; - private const string DialogDeleteMod = "PenumbraDeleteMod"; - private const string ButtonYesDelete = "Yes, delete it"; - private const string ButtonNoDelete = "No, keep it"; - - private const float SelectorPanelWidth = 240f; - - private static readonly Vector2 SelectorButtonSizes = new(100, 0); - private static readonly Vector2 HelpButtonSizes = new(40, 0); - - private static readonly Vector4 DeleteModNameColor = new(0.7f, 0.1f, 0.1f, 1); - } - - // Buttons - private partial class Selector - { - // === Delete === - private int? _deleteIndex; - - private void DrawModTrashButton() - { - using var raii = ImGuiRaii.PushFont( UiBuilder.IconFont ); - - if( ImGui.Button( FontAwesomeIcon.Trash.ToIconString(), SelectorButtonSizes * _selectorScalingFactor ) && _index >= 0 ) - { - _deleteIndex = _index; - } - - raii.Pop(); - - ImGuiCustom.HoverTooltip( TooltipDelete ); - } - - private void DrawDeleteModal() - { - if( _deleteIndex == null ) - { - return; - } - - ImGui.OpenPopup( DialogDeleteMod ); - - var _ = true; - ImGui.SetNextWindowPos( ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, Vector2.One / 2 ); - var ret = ImGui.BeginPopupModal( DialogDeleteMod, ref _, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoDecoration ); - if( !ret ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - - if( Mod == null ) - { - _deleteIndex = null; - ImGui.CloseCurrentPopup(); - return; - } - - ImGui.Text( "Are you sure you want to delete the following mod:" ); - var halfLine = new Vector2( ImGui.GetTextLineHeight() / 2 ); - ImGui.Dummy( halfLine ); - ImGui.TextColored( DeleteModNameColor, Mod.Data.Meta.Name ); - ImGui.Dummy( halfLine ); - - var buttonSize = ImGuiHelpers.ScaledVector2( 120, 0 ); - if( ImGui.Button( ButtonYesDelete, buttonSize ) ) - { - ImGui.CloseCurrentPopup(); - var mod = Mod; - Cache.RemoveMod( mod ); - Penumbra.ModManager.DeleteMod( mod.Data.BasePath ); - ModFileSystem.InvokeChange(); - ClearSelection(); - } - - ImGui.SameLine(); - - if( ImGui.Button( ButtonNoDelete, buttonSize ) ) - { - ImGui.CloseCurrentPopup(); - _deleteIndex = null; - } - } - - // === Add === - private bool _modAddKeyboardFocus = true; - - private void DrawModAddButton() - { - using var raii = ImGuiRaii.PushFont( UiBuilder.IconFont ); - - if( ImGui.Button( FontAwesomeIcon.Plus.ToIconString(), SelectorButtonSizes * _selectorScalingFactor ) ) - { - _modAddKeyboardFocus = true; - ImGui.OpenPopup( LabelAddModPopup ); - } - - raii.Pop(); - - ImGuiCustom.HoverTooltip( TooltipAdd ); - - DrawModAddPopup(); - } - - private void DrawModAddPopup() - { - if( !ImGui.BeginPopup( LabelAddModPopup ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - - if( _modAddKeyboardFocus ) - { - ImGui.SetKeyboardFocusHere(); - _modAddKeyboardFocus = false; - } - - var newName = ""; - if( ImGui.InputTextWithHint( "##AddMod", "New Mod Name...", ref newName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) ) - { - try - { - var newDir = TexToolsImport.CreateModFolder( new DirectoryInfo( Penumbra.Config!.ModDirectory ), - newName ); - var modMeta = new ModMeta - { - Author = "Unknown", - Name = newName.Replace( '/', '\\' ), - Description = string.Empty, - }; - - var metaFile = new FileInfo( Path.Combine( newDir.FullName, "meta.json" ) ); - modMeta.SaveToFile( metaFile ); - Penumbra.ModManager.AddMod( newDir ); - ModFileSystem.InvokeChange(); - SelectModOnUpdate( newDir.Name ); - } - catch( Exception e ) - { - PluginLog.Error( $"Could not create directory for new Mod {newName}:\n{e}" ); - } - - ImGui.CloseCurrentPopup(); - } - - if( ImGui.IsKeyPressed( ImGui.GetKeyIndex( ImGuiKey.Escape ) ) ) - { - ImGui.CloseCurrentPopup(); - } - } - - // === Help === - private void DrawModHelpButton() - { - using var raii = ImGuiRaii.PushFont( UiBuilder.IconFont ); - if( ImGui.Button( FontAwesomeIcon.QuestionCircle.ToIconString(), HelpButtonSizes * _selectorScalingFactor ) ) - { - ImGui.OpenPopup( LabelModHelpPopup ); - } - } - - private static void DrawModHelpPopup() - { - ImGui.SetNextWindowPos( ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, Vector2.One / 2 ); - ImGui.SetNextWindowSize( new Vector2( 5 * SelectorPanelWidth, 34 * ImGui.GetTextLineHeightWithSpacing() ), - ImGuiCond.Appearing ); - var _ = true; - if( !ImGui.BeginPopupModal( LabelModHelpPopup, ref _, ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - - ImGui.Dummy( Vector2.UnitY * ImGui.GetTextLineHeight() ); - ImGui.Text( "Mod Selector" ); - ImGui.BulletText( "Select a mod to obtain more information." ); - ImGui.BulletText( "Mod names are colored according to their current state in the collection:" ); - ImGui.Indent(); - ImGui.Bullet(); - ImGui.SameLine(); - ImGui.Text( "Enabled in the current collection." ); - ImGui.Bullet(); - ImGui.SameLine(); - ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( ColorId.DisabledMod.Value() ), "Disabled in the current collection." ); - ImGui.Bullet(); - ImGui.SameLine(); - ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( ColorId.NewMod.Value() ), - "Newly imported during this session. Will go away when first enabling a mod or when Penumbra is reloaded." ); - ImGui.Bullet(); - ImGui.SameLine(); - ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( ColorId.HandledConflictMod.Value() ), - "Enabled and conflicting with another enabled Mod, but on different priorities (i.e. the conflict is solved)." ); - ImGui.Bullet(); - ImGui.SameLine(); - ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( ColorId.DisabledMod.Value() ), - "Enabled and conflicting with another enabled Mod on the same priority." ); - ImGui.Unindent(); - ImGui.BulletText( "Right-click a mod to enter its sort order, which is its name by default." ); - ImGui.Indent(); - ImGui.BulletText( "A sort order differing from the mods name will not be displayed, it will just be used for ordering." ); - ImGui.BulletText( - "If the sort order string contains Forward-Slashes ('/'), the preceding substring will be turned into collapsible folders that can group mods." ); - ImGui.BulletText( - "Collapsible folders can contain further collapsible folders, so \"folder1/folder2/folder3/1\" will produce 3 folders\n" - + "\t\t[folder1] -> [folder2] -> [folder3] -> [ModName],\n" - + "where ModName will be sorted as if it was the string '1'." ); - ImGui.Unindent(); - ImGui.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." ); - ImGui.BulletText( "Right-clicking a folder opens a context menu." ); - ImGui.Indent(); - ImGui.BulletText( - "You can rename folders in the context menu. Leave the text blank and press enter to merge the folder with its parent." ); - ImGui.BulletText( "You can also enable or disable all descendant mods of a folder." ); - ImGui.Unindent(); - ImGui.BulletText( "Use the Filter Mods... input at the top to filter the list for mods with names containing the text." ); - ImGui.Indent(); - ImGui.BulletText( "You can enter c:[string] to filter for Changed Items instead." ); - ImGui.BulletText( "You can enter a:[string] to filter for Mod Authors instead." ); - ImGui.Unindent(); - ImGui.BulletText( "Use the expandable menu beside the input to filter for mods fulfilling specific criteria." ); - ImGui.Dummy( Vector2.UnitY * ImGui.GetTextLineHeight() ); - ImGui.Text( "Mod Management" ); - ImGui.BulletText( "You can delete the currently selected mod with the trashcan button." ); - ImGui.BulletText( "You can add a completely empty mod with the plus button." ); - ImGui.BulletText( "You can import TTMP-based mods in the import tab." ); - ImGui.BulletText( - "You can import penumbra-based mods by moving the corresponding folder into your mod directory in a file explorer, then rediscovering mods." ); - ImGui.BulletText( - "If you enable Advanced Options in the Settings tab, you can toggle Edit Mode to manipulate your selected mod even further." ); - ImGui.Dummy( Vector2.UnitY * ImGui.GetTextLineHeight() ); - ImGui.Dummy( Vector2.UnitX * 2 * SelectorPanelWidth ); - ImGui.SameLine(); - if( ImGui.Button( "Understood", Vector2.UnitX * SelectorPanelWidth ) ) - { - ImGui.CloseCurrentPopup(); - } - } - - // === Main === - private void DrawModsSelectorButtons() - { - // Selector controls - using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.WindowPadding, ZeroVector ) - .Push( ImGuiStyleVar.FrameRounding, 0 ); - - DrawModAddButton(); - ImGui.SameLine(); - DrawModHelpButton(); - ImGui.SameLine(); - DrawModTrashButton(); - } - } - - // Filters - private partial class Selector - { - private string _modFilterInput = ""; - - private void DrawTextFilter() - { - ImGui.SetNextItemWidth( SelectorPanelWidth * _selectorScalingFactor - 22 * ImGuiHelpers.GlobalScale ); - var tmp = _modFilterInput; - if( ImGui.InputTextWithHint( LabelModFilter, "Filter Mods...", ref tmp, 256 ) && _modFilterInput != tmp ) - { - Cache.SetTextFilter( tmp ); - _modFilterInput = tmp; - } - - ImGuiCustom.HoverTooltip( TooltipModFilter ); - } - - private void DrawToggleFilter() - { - if( ImGui.BeginCombo( "##ModStateFilter", "", - ImGuiComboFlags.NoPreview | ImGuiComboFlags.PopupAlignLeft | ImGuiComboFlags.HeightLargest ) ) - { - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndCombo ); - var flags = ( int )Cache.StateFilter; - foreach( ModFilter flag in Enum.GetValues( typeof( ModFilter ) ) ) - { - ImGui.CheckboxFlags( flag.ToName(), ref flags, ( int )flag ); - } - - Cache.StateFilter = ( ModFilter )flags; - } - - ImGuiCustom.HoverTooltip( "Filter mods for their activation status." ); - } - - private void DrawModsSelectorFilter() - { - using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.ItemSpacing, ZeroVector ); - DrawTextFilter(); - ImGui.SameLine(); - DrawToggleFilter(); - } - } - - // Drag'n Drop - private partial class Selector - { - private const string DraggedModLabel = "ModIndex"; - private const string DraggedFolderLabel = "FolderName"; - - private readonly IntPtr _dragDropPayload = Marshal.AllocHGlobal( 4 ); - - private static unsafe bool IsDropping( string name ) - => ImGui.AcceptDragDropPayload( name ).NativePtr != null; - - private void DragDropTarget( ModFolder folder ) - { - if( !ImGui.BeginDragDropTarget() ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndDragDropTarget ); - - if( IsDropping( DraggedModLabel ) ) - { - var payload = ImGui.GetDragDropPayload(); - var modIndex = Marshal.ReadInt32( payload.Data ); - var mod = Cache.GetMod( modIndex ).Item1; - mod?.Data.Move( folder ); - } - else if( IsDropping( DraggedFolderLabel ) ) - { - var payload = ImGui.GetDragDropPayload(); - var folderName = Marshal.PtrToStringUni( payload.Data ); - if( ModFileSystem.Find( folderName!, out var droppedFolder ) - && !ReferenceEquals( droppedFolder, folder ) - && !folder.FullName.StartsWith( folderName!, StringComparison.InvariantCultureIgnoreCase ) ) - { - droppedFolder.Move( folder ); - } - } - } - - private void DragDropSourceFolder( ModFolder folder ) - { - if( !ImGui.BeginDragDropSource() ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndDragDropSource ); - - var folderName = folder.FullName; - var ptr = Marshal.StringToHGlobalUni( folderName ); - ImGui.SetDragDropPayload( DraggedFolderLabel, ptr, ( uint )( folderName.Length + 1 ) * 2 ); - ImGui.Text( $"Moving {folderName}..." ); - } - - private void DragDropSourceMod( int modIndex, string modName ) - { - if( !ImGui.BeginDragDropSource() ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndDragDropSource ); - - Marshal.WriteInt32( _dragDropPayload, modIndex ); - ImGui.SetDragDropPayload( "ModIndex", _dragDropPayload, 4 ); - ImGui.Text( $"Moving {modName}..." ); - } - - ~Selector() - => Marshal.FreeHGlobal( _dragDropPayload ); - } - - // Selection - private partial class Selector - { - public Mods.FullMod? Mod { get; private set; } - private int _index; - private string _nextDir = string.Empty; - - private void SetSelection( int idx, Mods.FullMod? info ) - { - Mod = info; - if( idx != _index ) - { - _base._menu.InstalledTab.ModPanel.Details.ResetState(); - } - - _index = idx; - _deleteIndex = null; - } - - private void SetSelection( int idx ) - { - if( idx >= Cache.Count ) - { - idx = -1; - } - - if( idx < 0 ) - { - SetSelection( 0, null ); - } - else - { - SetSelection( idx, Cache.GetMod( idx ).Item1 ); - } - } - - public void ReloadSelection() - => SetSelection( _index, Cache.GetMod( _index ).Item1 ); - - public void ClearSelection() - => SetSelection( -1 ); - - public void SelectModOnUpdate( string directory ) - => _nextDir = directory; - - public void SelectModByDir( string name ) - { - var (mod, idx) = Cache.GetModByBasePath( name ); - SetSelection( idx, mod ); - } - - public void ReloadCurrentMod( bool reloadMeta = false, bool recomputeMeta = false, bool force = false ) - { - if( Mod == null ) - { - return; - } - - if( _index >= 0 && Penumbra.ModManager.UpdateMod( Mod.Data, reloadMeta, recomputeMeta, force ) ) - { - SelectModOnUpdate( Mod.Data.BasePath.Name ); - _base._menu.InstalledTab.ModPanel.Details.ResetState(); - } - } - - public void SaveCurrentMod() - => Mod?.Data.SaveMeta(); - } - - // Right-Clicks - private partial class Selector - { - // === Mod === - private void DrawModOrderPopup( string popupName, Mods.FullMod mod, bool firstOpen ) - { - if( !ImGui.BeginPopup( popupName ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - - if( ModPanel.DrawSortOrder( mod.Data, Penumbra.ModManager, this ) ) - { - ImGui.CloseCurrentPopup(); - } - - if( firstOpen ) - { - ImGui.SetKeyboardFocusHere( mod.Data.Order.FullPath.Length - 1 ); - } - } - - // === Folder === - private string _newFolderName = string.Empty; - private int _expandIndex = -1; - private bool _expandCollapse; - private bool _currentlyExpanding; - - private void ChangeStatusOfChildren( ModFolder folder, int currentIdx, bool toWhat ) - { - var change = false; - var metaManips = false; - foreach( var _ in folder.AllMods( Penumbra.ModManager.Config.SortFoldersFirst ) ) - { - var (mod, _, _) = Cache.GetMod( currentIdx++ ); - if( mod != null ) - { - change |= mod.Settings.Enabled != toWhat; - mod!.Settings.Enabled = toWhat; - metaManips |= mod.Data.Resources.MetaManipulations.Count > 0; - } - } - - if( !change ) - { - return; - } - - Cache.TriggerFilterReset(); - var collection = Penumbra.CollectionManager.Current; - if( collection.HasCache ) - { - collection.CalculateEffectiveFileList( metaManips, collection == Penumbra.CollectionManager.Default ); - } - - collection.Save(); - } - - private void DrawRenameFolderInput( ModFolder folder ) - { - ImGui.SetNextItemWidth( 150 * ImGuiHelpers.GlobalScale ); - if( !ImGui.InputTextWithHint( "##NewFolderName", "Rename Folder...", ref _newFolderName, 64, - ImGuiInputTextFlags.EnterReturnsTrue ) ) - { - return; - } - - if( _newFolderName.Any() ) - { - folder.Rename( _newFolderName ); - } - else - { - folder.Merge( folder.Parent! ); - } - - _newFolderName = string.Empty; - } - - private void DrawFolderContextMenu( ModFolder folder, int currentIdx, string treeName ) - { - if( !ImGui.BeginPopup( treeName ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - - if( ImGui.MenuItem( "Expand All Descendants" ) ) - { - _expandIndex = currentIdx; - _expandCollapse = false; - } - - if( ImGui.MenuItem( "Collapse All Descendants" ) ) - { - _expandIndex = currentIdx; - _expandCollapse = true; - } - - if( ImGui.MenuItem( "Enable All Descendants" ) ) - { - ChangeStatusOfChildren( folder, currentIdx, true ); - } - - if( ImGui.MenuItem( "Disable All Descendants" ) ) - { - ChangeStatusOfChildren( folder, currentIdx, false ); - } - - ImGuiHelpers.ScaledDummy( 0, 10 ); - DrawRenameFolderInput( folder ); - } - } - - // Main-Interface - private partial class Selector - { - private readonly SettingsInterface _base; - public readonly ModListCache Cache; - - private float _selectorScalingFactor = 1; - - public Selector( SettingsInterface ui, IReadOnlySet< string > newMods ) - { - _base = ui; - Cache = new ModListCache( Penumbra.ModManager, newMods ); - } - - private void DrawCollectionButton( string label, string tooltipLabel, float size, ModCollection collection ) - { - if( collection == ModCollection.Empty - || collection == Penumbra.CollectionManager.Current ) - { - using var _ = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f ); - ImGui.Button( label, Vector2.UnitX * size ); - } - else if( ImGui.Button( label, Vector2.UnitX * size ) ) - { - _base._menu.CollectionsTab.SetCurrentCollection( collection ); - } - - ImGuiCustom.HoverTooltip( - $"Switches to the currently set {tooltipLabel} collection, if it is not set to None and it is not the current collection already." ); - } - - private void DrawHeaderBar() - { - const float size = 200; - - DrawModsSelectorFilter(); - var textSize = ImGui.CalcTextSize( "Current Collection" ).X + ImGui.GetStyle().ItemInnerSpacing.X; - var comboSize = size * ImGui.GetIO().FontGlobalScale; - var offset = comboSize + textSize; - - var buttonSize = Math.Max( ImGui.GetWindowContentRegionWidth() - - offset - - SelectorPanelWidth * _selectorScalingFactor - - 3 * ImGui.GetStyle().ItemSpacing.X, 5f ); - ImGui.SameLine(); - DrawCollectionButton( "Default", "default", buttonSize, Penumbra.CollectionManager.Default ); - - - ImGui.SameLine(); - ImGui.SetNextItemWidth( comboSize ); - using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.ItemSpacing, Vector2.Zero ); - _base._menu.CollectionsTab.DrawCurrentCollectionSelector( false ); - } - - private void DrawFolderContent( ModFolder folder, ref int idx ) - { - // Collection may be manipulated. - foreach( var item in folder.GetItems( Penumbra.ModManager.Config.SortFoldersFirst ).ToArray() ) - { - if( item is ModFolder sub ) - { - var (visible, _) = Cache.GetFolder( sub ); - if( visible ) - { - DrawModFolder( sub, ref idx ); - } - else - { - idx += sub.TotalDescendantMods(); - } - } - else if( item is Mods.Mod _ ) - { - var (mod, visible, color) = Cache.GetMod( idx ); - if( mod != null && visible ) - { - DrawMod( mod, idx++, color ); - } - else - { - ++idx; - } - } - } - } - - private void DrawModFolder( ModFolder folder, ref int idx ) - { - var treeName = $"{folder.Name}##{folder.FullName}"; - var open = ImGui.TreeNodeEx( treeName ); - using var raii = ImGuiRaii.DeferredEnd( ImGui.TreePop, open ); - - if( idx == _expandIndex ) - { - _currentlyExpanding = true; - } - - if( _currentlyExpanding ) - { - ImGui.SetNextItemOpen( !_expandCollapse ); - } - - if( ImGui.IsItemClicked( ImGuiMouseButton.Right ) ) - { - _newFolderName = string.Empty; - ImGui.OpenPopup( treeName ); - } - - DrawFolderContextMenu( folder, idx, treeName ); - DragDropTarget( folder ); - DragDropSourceFolder( folder ); - - if( open ) - { - DrawFolderContent( folder, ref idx ); - } - else - { - idx += folder.TotalDescendantMods(); - } - - if( idx == _expandIndex ) - { - _currentlyExpanding = false; - _expandIndex = -1; - } - } - - private void DrawMod( Mods.FullMod mod, int modIndex, uint color ) - { - using var colorRaii = ImGuiRaii.PushColor( ImGuiCol.Text, color, color != 0 ); - - var selected = ImGui.Selectable( $"{mod.Data.Meta.Name}##{modIndex}", modIndex == _index ); - colorRaii.Pop(); - - var popupName = $"##SortOrderPopup{modIndex}"; - var firstOpen = false; - if( ImGui.IsItemClicked( ImGuiMouseButton.Right ) ) - { - ImGui.OpenPopup( popupName ); - firstOpen = true; - } - - DragDropTarget( mod.Data.Order.ParentFolder ); - DragDropSourceMod( modIndex, mod.Data.Meta.Name ); - - DrawModOrderPopup( popupName, mod, firstOpen ); - - if( selected ) - { - SetSelection( modIndex, mod ); - } - } - - public void Draw() - { - if( Cache.Update() ) - { - if( _nextDir.Any() ) - { - SelectModByDir( _nextDir ); - _nextDir = string.Empty; - } - else if( Mod != null ) - { - SelectModByDir( Mod.Data.BasePath.Name ); - } - } - - _selectorScalingFactor = ImGuiHelpers.GlobalScale - * ( Penumbra.Config.ScaleModSelector - ? ImGui.GetWindowWidth() / SettingsMenu.MinSettingsSize.X - : 1f ); - // Selector pane - DrawHeaderBar(); - using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.ItemSpacing, Vector2.Zero ); - ImGui.BeginGroup(); - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndGroup ) - .Push( ImGui.EndChild ); - // Inlay selector list - if( ImGui.BeginChild( LabelSelectorList, - new Vector2( SelectorPanelWidth * _selectorScalingFactor, -ImGui.GetFrameHeightWithSpacing() ), - true, ImGuiWindowFlags.HorizontalScrollbar ) ) - { - style.Push( ImGuiStyleVar.IndentSpacing, 12.5f ); - - var modIndex = 0; - DrawFolderContent( Penumbra.ModManager.StructuredMods, ref modIndex ); - style.Pop(); - } - - raii.Pop(); - - DrawModsSelectorButtons(); - - style.Pop(); - DrawModHelpPopup(); - - DrawDeleteModal(); - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabResourceManager.cs b/Penumbra/UI/MenuTabs/TabResourceManager.cs deleted file mode 100644 index f8f8d9a4..00000000 --- a/Penumbra/UI/MenuTabs/TabResourceManager.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; -using FFXIVClientStructs.FFXIV.Client.System.Resource; -using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; -using FFXIVClientStructs.STD; -using ImGuiNET; -using Penumbra.GameData.ByteString; -using Penumbra.Interop.Loader; -using Penumbra.UI.Custom; - -namespace Penumbra.UI; - -public partial class SettingsInterface -{ - private static string GetNodeLabel( uint label, uint type, ulong count ) - { - var byte1 = type >> 24; - var byte2 = ( type >> 16 ) & 0xFF; - var byte3 = ( type >> 8 ) & 0xFF; - var byte4 = type & 0xFF; - return byte1 == 0 - ? $"({type:X8}) {( char )byte2}{( char )byte3}{( char )byte4} - {count}###{label}{type}Debug" - : $"({type:X8}) {( char )byte1}{( char )byte2}{( char )byte3}{( char )byte4} - {count}###{label}{type}Debug"; - } - - private unsafe void DrawResourceMap( ResourceCategory category, uint ext, StdMap< uint, Pointer< ResourceHandle > >* map ) - { - if( map == null ) - { - return; - } - - var label = GetNodeLabel( ( uint )category, ext, map->Count ); - if( !ImGui.TreeNodeEx( label ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.TreePop ); - - if( map->Count == 0 || !ImGui.BeginTable( $"##{label}_table", 4, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg ) ) - { - return; - } - - raii.Push( ImGui.EndTable ); - - ImGui.TableSetupColumn( "Hash", ImGuiTableColumnFlags.WidthFixed, 100 * ImGuiHelpers.GlobalScale ); - ImGui.TableSetupColumn( "Ptr", ImGuiTableColumnFlags.WidthFixed, 100 * ImGuiHelpers.GlobalScale ); - ImGui.TableSetupColumn( "Path", ImGuiTableColumnFlags.WidthFixed, - ImGui.GetWindowContentRegionWidth() - 300 * ImGuiHelpers.GlobalScale ); - ImGui.TableSetupColumn( "Refs", ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale ); - ImGui.TableHeadersRow(); - - ResourceLoader.IterateResourceMap( map, ( hash, r ) => - { - if( _filter.Length != 0 && !r->FileName.ToString().Contains( _filter, StringComparison.InvariantCultureIgnoreCase ) ) - { - return; - } - - ImGui.TableNextColumn(); - ImGui.Text( $"0x{hash:X8}" ); - ImGui.TableNextColumn(); - var address = $"0x{( ulong )r:X}"; - ImGui.Text( address ); - if( ImGui.IsItemClicked() ) - { - ImGui.SetClipboardText( address ); - } - - ref var name = ref r->FileName; - ImGui.TableNextColumn(); - if( name.Capacity > 15 ) - { - ImGuiNative.igTextUnformatted( name.BufferPtr, name.BufferPtr + name.Length ); - } - else - { - fixed( byte* ptr = name.Buffer ) - { - ImGuiNative.igTextUnformatted( ptr, ptr + name.Length ); - } - } - - if( ImGui.IsItemClicked() ) - { - var data = Interop.Structs.ResourceHandle.GetData( ( Interop.Structs.ResourceHandle* )r ); - if( data != null ) - { - var length = ( int )Interop.Structs.ResourceHandle.GetLength( ( Interop.Structs.ResourceHandle* )r ); - ImGui.SetClipboardText( string.Join( " ", - new ReadOnlySpan< byte >( data, length ).ToArray().Select( b => b.ToString( "X2" ) ) ) ); - } - //ImGuiNative.igSetClipboardText( ( byte* )Structs.ResourceHandle.GetData( ( IntPtr )r ) ); - } - - ImGui.TableNextColumn(); - ImGui.Text( r->RefCount.ToString() ); - } ); - } - - private unsafe void DrawCategoryContainer( ResourceCategory category, - StdMap< uint, Pointer< StdMap< uint, Pointer< ResourceHandle > > > >* map ) - { - if( map == null || !ImGui.TreeNodeEx( $"({( uint )category:D2}) {category} - {map->Count}###{( uint )category}Debug" ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.TreePop ); - ResourceLoader.IterateExtMap( map, ( ext, map ) => DrawResourceMap( category, ext, map ) ); - } - - - private static unsafe void DrawResourceProblems() - { - if( !ImGui.CollapsingHeader( "Resource Problems##ResourceManager" ) - || !ImGui.BeginTable( "##ProblemsTable", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit ) ) - { - return; - } - - using var end = ImGuiRaii.DeferredEnd( ImGui.EndTable ); - - ResourceLoader.IterateResources( ( _, r ) => - { - if( r->RefCount < 10000 ) - { - return; - } - - ImGui.TableNextColumn(); - ImGui.Text( r->Category.ToString() ); - ImGui.TableNextColumn(); - ImGui.Text( r->FileType.ToString( "X" ) ); - ImGui.TableNextColumn(); - ImGui.Text( r->Id.ToString( "X" ) ); - ImGui.TableNextColumn(); - ImGui.Text( ( ( ulong )r ).ToString( "X" ) ); - ImGui.TableNextColumn(); - ImGui.Text( r->RefCount.ToString() ); - ImGui.TableNextColumn(); - ref var name = ref r->FileName; - if( name.Capacity > 15 ) - { - ImGuiNative.igTextUnformatted( name.BufferPtr, name.BufferPtr + name.Length ); - } - else - { - fixed( byte* ptr = name.Buffer ) - { - ImGuiNative.igTextUnformatted( ptr, ptr + name.Length ); - } - } - } ); - } - - private string _filter = string.Empty; - - private unsafe void DrawResourceManagerTab() - { - if( !ImGui.BeginTabItem( "Resource Manager Tab" ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); - - var resourceHandler = *ResourceLoader.ResourceManager; - - if( resourceHandler == null ) - { - return; - } - - ImGui.InputTextWithHint( "##resourceFilter", "Filter...", ref _filter, Utf8GamePath.MaxGamePathLength ); - - raii.Push( ImGui.EndChild ); - if( !ImGui.BeginChild( "##ResourceManagerChild", -Vector2.One, true ) ) - { - return; - } - - ResourceLoader.IterateGraphs( DrawCategoryContainer ); - } -} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabSettings.cs b/Penumbra/UI/MenuTabs/TabSettings.cs deleted file mode 100644 index 0589faf6..00000000 --- a/Penumbra/UI/MenuTabs/TabSettings.cs +++ /dev/null @@ -1,381 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Numerics; -using Dalamud.Interface; -using Dalamud.Interface.Components; -using ImGuiNET; -using OtterGui; -using Penumbra.GameData.ByteString; -using Penumbra.UI.Classes; -using Penumbra.UI.Custom; - -namespace Penumbra.UI; - -public partial class SettingsInterface -{ - private class TabSettings - { - private readonly SettingsInterface _base; - private readonly Configuration _config; - private bool _configChanged; - private string _newModDirectory; - - public TabSettings( SettingsInterface ui ) - { - _base = ui; - _config = Penumbra.Config; - _configChanged = false; - _newModDirectory = _config.ModDirectory; - } - - private static bool DrawPressEnterWarning( string old ) - { - const uint red = 0xFF202080; - using var color = ImGuiRaii.PushColor( ImGuiCol.Button, red ); - var w = Vector2.UnitX * ImGui.CalcItemWidth(); - return ImGui.Button( $"Press Enter or Click Here to Save (Current Directory: {old})", w ); - } - - private static void DrawOpenDirectoryButton( int id, DirectoryInfo directory, bool condition ) - { - ImGui.PushID( id ); - var ret = ImGui.Button( "Open Directory" ); - ImGuiCustom.HoverTooltip( "Open this directory in your configured file explorer." ); - if( ret && condition && Directory.Exists( directory.FullName ) ) - { - Process.Start( new ProcessStartInfo( directory.FullName ) - { - UseShellExecute = true, - } ); - } - - ImGui.PopID(); - } - - private void DrawRootFolder() - { - ImGui.BeginGroup(); - ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth ); - var save = ImGui.InputText( "Root Directory", ref _newModDirectory, 255, ImGuiInputTextFlags.EnterReturnsTrue ); - ImGui.SameLine(); - ImGuiComponents.HelpMarker( "This is where Penumbra will store your extracted mod files.\n" - + "TTMP files are not copied, just extracted.\n" - + "This directory needs to be accessible and you need write access here.\n" - + "It is recommended that this directory is placed on a fast hard drive, preferably an SSD.\n" - + "It should also be placed near the root of a logical drive - the shorter the total path to this folder, the better.\n" - + "Definitely do not place it in your Dalamud directory or any sub-directory thereof." ); - ImGui.SameLine(); - var modManager = Penumbra.ModManager; - DrawOpenDirectoryButton( 0, modManager.BasePath, modManager.Valid ); - ImGui.EndGroup(); - - if( _config.ModDirectory == _newModDirectory || !_newModDirectory.Any() ) - { - return; - } - - if( save || DrawPressEnterWarning( _config.ModDirectory ) ) - { - _base._menu.InstalledTab.Selector.ClearSelection(); - modManager.DiscoverMods( _newModDirectory ); - _base._menu.InstalledTab.Selector.Cache.TriggerListReset(); - _newModDirectory = _config.ModDirectory; - } - } - - private void DrawRediscoverButton() - { - if( ImGui.Button( "Rediscover Mods" ) ) - { - _base._menu.InstalledTab.Selector.ClearSelection(); - Penumbra.ModManager.DiscoverMods(); - _base._menu.InstalledTab.Selector.Cache.TriggerListReset(); - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( "Force Penumbra to completely re-scan your root directory as if it was restarted." ); - } - - private void DrawEnabledBox() - { - var enabled = _config.EnableMods; - if( ImGui.Checkbox( "Enable Mods", ref enabled ) ) - { - _base._penumbra.SetEnabled( enabled ); - } - } - - private void DrawShowAdvancedBox() - { - var showAdvanced = _config.ShowAdvanced; - if( ImGui.Checkbox( "Show Advanced Settings", ref showAdvanced ) ) - { - _config.ShowAdvanced = showAdvanced; - _configChanged = true; - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( "Enable some advanced options in this window and in the mod selector.\n" - + "This is required to enable manually editing any mod information." ); - } - - private void DrawSortFoldersFirstBox() - { - var foldersFirst = _config.SortFoldersFirst; - if( ImGui.Checkbox( "Sort Mod-Folders Before Mods", ref foldersFirst ) ) - { - _config.SortFoldersFirst = foldersFirst; - _base._menu.InstalledTab.Selector.Cache.TriggerListReset(); - _configChanged = true; - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( - "Prioritizes all mod-folders in the mod-selector in the Installed Mods tab so that folders come before single mods, instead of being sorted completely alphabetically" ); - } - - private void DrawScaleModSelectorBox() - { - var scaleModSelector = _config.ScaleModSelector; - if( ImGui.Checkbox( "Scale Mod Selector With Window Size", ref scaleModSelector ) ) - { - _config.ScaleModSelector = scaleModSelector; - _configChanged = true; - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( - "Instead of keeping the mod-selector in the Installed Mods tab a fixed width, this will let it scale with the total size of the Penumbra window." ); - } - - private void DrawDisableSoundStreamingBox() - { - var tmp = Penumbra.Config.DisableSoundStreaming; - if( ImGui.Checkbox( "Disable Audio Streaming", ref tmp ) && tmp != Penumbra.Config.DisableSoundStreaming ) - { - Penumbra.Config.DisableSoundStreaming = tmp; - _configChanged = true; - if( tmp ) - { - _base._penumbra.MusicManager.DisableStreaming(); - } - else - { - _base._penumbra.MusicManager.EnableStreaming(); - } - - _base.ReloadMods(); - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( - "Disable streaming in the games audio engine.\n" - + "If you do not disable streaming, you can not replace sound files in the game (*.scd files), they will be ignored by Penumbra.\n\n" - + "Only touch this if you experience sound problems.\n" - + "If you toggle this, make sure no modified or to-be-modified sound file is currently playing or was recently playing, else you might crash." ); - } - - private void DrawLogLoadedFilesBox() - { - //ImGui.Checkbox( "Log Loaded Files", ref _base._penumbra.ResourceLoader.LogAllFiles ); - //ImGui.SameLine(); - //var regex = _base._penumbra.ResourceLoader.LogFileFilter?.ToString() ?? string.Empty; - //var tmp = regex; - //ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth ); - //if( ImGui.InputTextWithHint( "##LogFilter", "Matching this Regex...", ref tmp, 64 ) && tmp != regex ) - //{ - // try - // { - // var newRegex = tmp.Length > 0 ? new Regex( tmp, RegexOptions.Compiled ) : null; - // _base._penumbra.ResourceLoader.LogFileFilter = newRegex; - // } - // catch( Exception e ) - // { - // PluginLog.Debug( "Could not create regex:\n{Exception}", e ); - // } - //} - // - //ImGui.SameLine(); - //ImGuiComponents.HelpMarker( "Log all loaded files that match the given Regex to the PluginLog." ); - } - - private void DrawDisableNotificationsBox() - { - var fsWatch = _config.DisableFileSystemNotifications; - if( ImGui.Checkbox( "Disable Filesystem Change Notifications", ref fsWatch ) ) - { - _config.DisableFileSystemNotifications = fsWatch; - _configChanged = true; - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( "Currently does nothing." ); - } - - private void DrawEnableHttpApiBox() - { - var http = _config.EnableHttpApi; - if( ImGui.Checkbox( "Enable HTTP API", ref http ) ) - { - if( http ) - { - _base._penumbra.CreateWebServer(); - } - else - { - _base._penumbra.ShutdownWebServer(); - } - - _config.EnableHttpApi = http; - _configChanged = true; - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( - "Enables other applications, e.g. Anamnesis, to use some Penumbra functions, like requesting redraws." ); - } - - private static void DrawReloadResourceButton() - { - if( ImGui.Button( "Reload Resident Resources" ) ) - { - Penumbra.ResidentResources.Reload(); - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( "Reload some specific files that the game keeps in memory at all times.\n" - + "You usually should not need to do this." ); - } - - private void DrawEnableFullResourceLoggingBox() - { - var tmp = _config.EnableFullResourceLogging; - if( ImGui.Checkbox( "Enable Full Resource Logging", ref tmp ) && tmp != _config.EnableFullResourceLogging ) - { - if( tmp ) - { - Penumbra.ResourceLoader.EnableFullLogging(); - } - else - { - Penumbra.ResourceLoader.DisableFullLogging(); - } - - _config.EnableFullResourceLogging = tmp; - _configChanged = true; - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( "[DEBUG] Enable the logging of all ResourceLoader events indiscriminately." ); - } - - private void DrawEnableDebugModeBox() - { - var tmp = _config.DebugMode; - if( ImGui.Checkbox( "Enable Debug Mode", ref tmp ) && tmp != _config.DebugMode ) - { - if( tmp ) - { - Penumbra.ResourceLoader.EnableDebug(); - } - else - { - Penumbra.ResourceLoader.DisableDebug(); - } - - _config.DebugMode = tmp; - _configChanged = true; - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( "[DEBUG] Enable the Debug Tab and Resource Manager Tab as well as some additional data collection." ); - } - - private void DrawRequestedResourceLogging() - { - var tmp = _config.EnableResourceLogging; - if( ImGui.Checkbox( "Enable Requested Resource Logging", ref tmp ) ) - { - _base._penumbra.ResourceLogger.SetState( tmp ); - } - - ImGui.SameLine(); - ImGuiComponents.HelpMarker( "Log all game paths FFXIV requests to the plugin log.\n" - + "You can filter the logged paths for those containing the entered string or matching the regex, if the entered string compiles to a valid regex.\n" - + "Red boundary indicates invalid regex." ); - ImGui.SameLine(); - var tmpString = Penumbra.Config.ResourceLoggingFilter; - using var color = ImGuiRaii.PushColor( ImGuiCol.Border, 0xFF0000B0, !_base._penumbra.ResourceLogger.ValidRegex ); - using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale, - !_base._penumbra.ResourceLogger.ValidRegex ); - if( ImGui.InputTextWithHint( "##ResourceLogFilter", "Filter...", ref tmpString, Utf8GamePath.MaxGamePathLength ) ) - { - _base._penumbra.ResourceLogger.SetFilter( tmpString ); - } - } - - private void DrawAdvancedSettings() - { - DrawRequestedResourceLogging(); - DrawDisableSoundStreamingBox(); - DrawLogLoadedFilesBox(); - DrawDisableNotificationsBox(); - DrawEnableHttpApiBox(); - DrawReloadResourceButton(); - DrawEnableDebugModeBox(); - DrawEnableFullResourceLoggingBox(); - } - - public static unsafe void Text( Utf8String s ) - { - ImGuiNative.igTextUnformatted( ( byte* )s.Path, ( byte* )s.Path + s.Length ); - } - - public void Draw() - { - if( !ImGui.BeginTabItem( "Settings" ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); - DrawRootFolder(); - - DrawRediscoverButton(); - - ImGuiCustom.VerticalDistance( DefaultVerticalSpace ); - DrawEnabledBox(); - - ImGuiCustom.VerticalDistance( DefaultVerticalSpace ); - DrawScaleModSelectorBox(); - DrawSortFoldersFirstBox(); - DrawShowAdvancedBox(); - - if( _config.ShowAdvanced ) - { - DrawAdvancedSettings(); - } - - if( ImGui.CollapsingHeader( "Colors" ) ) - { - foreach( var color in Enum.GetValues< ColorId >() ) - { - var (defaultColor, name, description) = color.Data(); - var currentColor = Penumbra.Config.Colors.TryGetValue( color, out var current ) ? current : defaultColor; - if( ImGuiUtil.ColorPicker( name, description, currentColor, c => Penumbra.Config.Colors[ color ] = c, defaultColor ) ) - { - _configChanged = true; - } - } - } - - if( _configChanged ) - { - _config.Save(); - _configChanged = false; - } - } - } -} \ No newline at end of file