diff --git a/OtterGui b/OtterGui index 4cc10240..a832fb6c 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 4cc1024096905b0b20c1559c534b1dd3fe7b25ad +Subproject commit a832fb6ca5e7c6cb4e35a51a08d30d1800f405da diff --git a/Penumbra/Collections/CollectionManager.cs b/Penumbra/Collections/CollectionManager.cs index 30fd44fb..a1766e96 100644 --- a/Penumbra/Collections/CollectionManager.cs +++ b/Penumbra/Collections/CollectionManager.cs @@ -81,6 +81,29 @@ public partial class ModCollection _modManager.ModPathChanged -= OnModPathChanged; } + // Returns true if the name is not empty, it is not the name of the empty collection + // and no existing collection results in the same filename as name. + public bool CanAddCollection( string name, out string fixedName ) + { + if( name.Length == 0 ) + { + fixedName = string.Empty; + return false; + } + + name = name.RemoveInvalidPathSymbols().ToLowerInvariant(); + if( name.Length == 0 + || name == Empty.Name.ToLowerInvariant() + || _collections.Any( c => c.Name.RemoveInvalidPathSymbols().ToLowerInvariant() == name ) ) + { + fixedName = string.Empty; + return false; + } + + fixedName = name; + return true; + } + // Add a new collection of the given name. // If duplicate is not-null, the new collection will be a duplicate of it. // If the name of the collection would result in an already existing filename, skip it. @@ -88,12 +111,9 @@ public partial class ModCollection // Also sets the current collection to the new collection afterwards. public bool AddCollection( string name, ModCollection? duplicate ) { - var nameFixed = name.RemoveInvalidPathSymbols().ToLowerInvariant(); - if( nameFixed.Length == 0 - || nameFixed == Empty.Name.ToLowerInvariant() - || _collections.Any( c => c.Name.RemoveInvalidPathSymbols().ToLowerInvariant() == nameFixed ) ) + if( !CanAddCollection( name, out var fixedName ) ) { - PluginLog.Warning( $"The new collection {name} would lead to the same path as one that already exists." ); + PluginLog.Warning( $"The new collection {name} would lead to the same path {fixedName} as one that already exists." ); return false; } @@ -108,6 +128,7 @@ public partial class ModCollection // Remove the given collection if it exists and is neither the empty nor the default-named collection. // If the removed collection was active, it also sets the corresponding collection to the appropriate default. + // Also removes the collection from inheritances of all other collections. public bool RemoveCollection( int idx ) { if( idx <= Empty.Index || idx >= _collections.Count ) @@ -140,9 +161,18 @@ public partial class ModCollection var collection = _collections[ idx ]; collection.Delete(); _collections.RemoveAt( idx ); - for( var i = idx; i < _collections.Count; ++i ) + foreach( var c in _collections ) { - --_collections[ i ].Index; + var inheritedIdx = c._inheritance.IndexOf( collection ); + if( inheritedIdx >= 0 ) + { + c.RemoveInheritance( inheritedIdx ); + } + + if( c.Index > idx ) + { + --c.Index; + } } CollectionChanged.Invoke( Type.Inactive, collection, null ); diff --git a/Penumbra/Collections/ModCollection.Changes.cs b/Penumbra/Collections/ModCollection.Changes.cs index 78e6ee07..4b350758 100644 --- a/Penumbra/Collections/ModCollection.Changes.cs +++ b/Penumbra/Collections/ModCollection.Changes.cs @@ -137,7 +137,7 @@ public partial class ModCollection return false; } - _settings[ idx ] = inherit ? null : this[ idx ].Settings ?? ModSettings2.DefaultSettings( Penumbra.ModManager.Mods[ idx ] ); + _settings[ idx ] = inherit ? null : this[ idx ].Settings?.DeepCopy() ?? ModSettings2.DefaultSettings( Penumbra.ModManager.Mods[ idx ] ); return true; } diff --git a/Penumbra/Collections/ModCollection.Inheritance.cs b/Penumbra/Collections/ModCollection.Inheritance.cs index 34156396..b9c75d9f 100644 --- a/Penumbra/Collections/ModCollection.Inheritance.cs +++ b/Penumbra/Collections/ModCollection.Inheritance.cs @@ -23,23 +23,57 @@ public partial class ModCollection // Iterate over all collections inherited from in depth-first order. // Skip already visited collections to avoid circular dependencies. public IEnumerable< ModCollection > GetFlattenedInheritance() - { - yield return this; + => InheritedCollections( this ).Distinct(); - foreach( var collection in _inheritance.SelectMany( c => c.GetFlattenedInheritance() ) - .Where( c => !ReferenceEquals( this, c ) ) - .Distinct() ) - { - yield return collection; - } + // All inherited collections in application order without filtering for duplicates. + private static IEnumerable< ModCollection > InheritedCollections( ModCollection collection ) + => collection.Inheritance.SelectMany( InheritedCollections ).Prepend( collection ); + + // Reasons why a collection can not be inherited from. + public enum ValidInheritance + { + Valid, + Self, // Can not inherit from self + Empty, // Can not inherit from the empty collection + Contained, // Already inherited from + Circle, // Inheritance would lead to a circle. } + // Check whether a collection can be inherited from. + public ValidInheritance CheckValidInheritance( ModCollection? collection ) + { + if( collection == null || ReferenceEquals( collection, Empty ) ) + { + return ValidInheritance.Empty; + } + + if( ReferenceEquals( collection, this ) ) + { + return ValidInheritance.Self; + } + + if( _inheritance.Contains( collection ) ) + { + return ValidInheritance.Contained; + } + + if( InheritedCollections( collection ).Any( c => c == this ) ) + { + return ValidInheritance.Circle; + } + + return ValidInheritance.Valid; + } + + private bool CheckForCircle( ModCollection collection ) + => ReferenceEquals( collection, this ) || _inheritance.Any( c => c.CheckForCircle( collection ) ); + // Add a new collection to the inheritance list. // We do not check if this collection would be visited before, // only that it is unique in the list itself. public bool AddInheritance( ModCollection collection ) { - if( ReferenceEquals( collection, this ) || _inheritance.Contains( collection ) ) + if( CheckValidInheritance( collection ) != ValidInheritance.Valid ) { return false; } diff --git a/Penumbra/UI/Classes/Colors.cs b/Penumbra/UI/Classes/Colors.cs index 7ed19b08..0a16758e 100644 --- a/Penumbra/UI/Classes/Colors.cs +++ b/Penumbra/UI/Classes/Colors.cs @@ -22,6 +22,7 @@ public static class Colors { public const uint PressEnterWarningBg = 0xFF202080; public const uint RegexWarningBorder = 0xFF0000B0; + public const uint MetaInfoText = 0xAAFFFFFF; public static (uint DefaultColor, string Name, string Description) Data( this ColorId color ) => color switch diff --git a/Penumbra/UI/Classes/ModFileSystemSelector.cs b/Penumbra/UI/Classes/ModFileSystemSelector.cs index 3ee304cb..ca0fbaf0 100644 --- a/Penumbra/UI/Classes/ModFileSystemSelector.cs +++ b/Penumbra/UI/Classes/ModFileSystemSelector.cs @@ -48,6 +48,9 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo Penumbra.CollectionManager.CollectionChanged -= OnCollectionChange; } + public new ModFileSystem.Leaf? SelectedLeaf + => base.SelectedLeaf; + // Customization points. public override SortMode SortMode => Penumbra.Config.SortMode; @@ -199,7 +202,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo { if( _lastSelectedDirectory.Length > 0 ) { - SelectedLeaf = ( ModFileSystem.Leaf? )FileSystem.Root.GetAllDescendants( SortMode.Lexicographical ) + base.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 index 109bc32b..f7aedac3 100644 --- a/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs +++ b/Penumbra/UI/ConfigWindow.ChangedItemsTab.cs @@ -11,8 +11,19 @@ public partial class ConfigWindow { private LowerString _changedItemFilter = LowerString.Empty; - public void DrawChangedItemTab() + // Draw a simple clipped table containing all changed items. + private void DrawChangedItemTab() { + // Functions in here for less pollution. + bool FilterChangedItem( KeyValuePair< string, object? > item ) + => item.Key.Contains( _changedItemFilter.Lower, StringComparison.InvariantCultureIgnoreCase ); + + void DrawChangedItemColumn( KeyValuePair< string, object? > item ) + { + ImGui.TableNextColumn(); + DrawChangedItem( item.Key, item.Value, ImGui.GetStyle().ScrollbarSize ); + } + using var tab = ImRaii.TabItem( "Changed Items" ); if( !tab ) { @@ -36,19 +47,10 @@ public partial class ConfigWindow return; } - var items = Penumbra.CollectionManager.Default.ChangedItems; + var items = Penumbra.CollectionManager.Default.ChangedItems; var rest = _changedItemFilter.IsEmpty - ? ImGuiClip.ClippedDraw( items, skips, DrawChangedItem, items.Count ) - : ImGuiClip.FilteredClippedDraw( items, skips, FilterChangedItem, DrawChangedItem ); + ? ImGuiClip.ClippedDraw( items, skips, DrawChangedItemColumn, items.Count ) + : ImGuiClip.FilteredClippedDraw( items, skips, FilterChangedItem, DrawChangedItemColumn ); 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.Inheritance.cs b/Penumbra/UI/ConfigWindow.CollectionsTab.Inheritance.cs new file mode 100644 index 00000000..ac803463 --- /dev/null +++ b/Penumbra/UI/ConfigWindow.CollectionsTab.Inheritance.cs @@ -0,0 +1,288 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Dalamud.Interface; +using Dalamud.Interface.Components; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using Penumbra.Collections; +using Penumbra.UI.Classes; +using Penumbra.Util; + +namespace Penumbra.UI; + +public partial class ConfigWindow +{ + private partial class CollectionsTab + { + private const int InheritedCollectionHeight = 10; + private const string InheritanceDragDropLabel = "##InheritanceMove"; + + // Keep for reuse. + private readonly HashSet< ModCollection > _seenInheritedCollections = new(32); + + // Execute changes only outside of loops. + private ModCollection? _newInheritance; + private ModCollection? _movedInheritance; + private (int, int)? _inheritanceAction; + private ModCollection? _newCurrentCollection; + + // Draw the whole inheritance block. + private void DrawInheritanceBlock() + { + using var id = ImRaii.PushId( "##Inheritance" ); + DrawCurrentCollectionInheritance(); + DrawInheritanceTrashButton(); + DrawNewInheritanceSelection(); + DelayedActions(); + } + + // If an inherited collection is expanded, + // draw all its flattened, distinct children in order with a tree-line. + private void DrawInheritedChildren( ModCollection collection ) + { + using var id = ImRaii.PushId( collection.Index ); + using var indent = ImRaii.PushIndent(); + + // Get start point for the lines (top of the selector). + // Tree line stuff. + var lineStart = ImGui.GetCursorScreenPos(); + var offsetX = -ImGui.GetStyle().IndentSpacing + ImGui.GetTreeNodeToLabelSpacing() / 2; + var drawList = ImGui.GetWindowDrawList(); + var lineSize = Math.Max( 0, ImGui.GetStyle().IndentSpacing - 9 * ImGuiHelpers.GlobalScale ); + lineStart.X += offsetX; + lineStart.Y -= 2 * ImGuiHelpers.GlobalScale; + var lineEnd = lineStart; + + // Skip the collection itself. + foreach( var inheritance in collection.GetFlattenedInheritance().Skip( 1 ) ) + { + // Draw the child, already seen collections are colored as conflicts. + using var color = ImRaii.PushColor( ImGuiCol.Text, ColorId.HandledConflictMod.Value(), + _seenInheritedCollections.Contains( inheritance ) ); + _seenInheritedCollections.Add( inheritance ); + + ImRaii.TreeNode( inheritance.Name, ImGuiTreeNodeFlags.NoTreePushOnOpen | ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet ); + var (minRect, maxRect) = ( ImGui.GetItemRectMin(), ImGui.GetItemRectMax() ); + DrawInheritanceTreeClicks( inheritance, false ); + + // Tree line stuff. + if( minRect.X == 0 ) + { + continue; + } + + // Draw the notch and increase the line length. + var midPoint = ( minRect.Y + maxRect.Y ) / 2f - 1f; + drawList.AddLine( new Vector2( lineStart.X, midPoint ), new Vector2( lineStart.X + lineSize, midPoint ), Colors.MetaInfoText, + ImGuiHelpers.GlobalScale ); + lineEnd.Y = midPoint; + } + + // Finally, draw the folder line. + drawList.AddLine( lineStart, lineEnd, Colors.MetaInfoText, ImGuiHelpers.GlobalScale ); + } + + // Draw a single primary inherited collection. + private void DrawInheritance( ModCollection collection ) + { + using var color = ImRaii.PushColor( ImGuiCol.Text, ColorId.HandledConflictMod.Value(), + _seenInheritedCollections.Contains( collection ) ); + _seenInheritedCollections.Add( collection ); + using var tree = ImRaii.TreeNode( collection.Name, ImGuiTreeNodeFlags.NoTreePushOnOpen ); + color.Pop(); + DrawInheritanceTreeClicks( collection, true ); + DrawInheritanceDropSource( collection ); + DrawInheritanceDropTarget( collection ); + + if( tree ) + { + DrawInheritedChildren( collection ); + } + else + { + // We still want to keep track of conflicts. + _seenInheritedCollections.UnionWith( collection.GetFlattenedInheritance() ); + } + } + + // Draw the list box containing the current inheritance information. + private void DrawCurrentCollectionInheritance() + { + using var list = ImRaii.ListBox( "##inheritanceList", + new Vector2( _window._inputTextWidth.X - ImGui.GetFrameHeight() - ImGui.GetStyle().ItemSpacing.X, + ImGui.GetTextLineHeightWithSpacing() * InheritedCollectionHeight ) ); + if( !list ) + { + return; + } + + _seenInheritedCollections.Clear(); + _seenInheritedCollections.Add( Penumbra.CollectionManager.Current ); + foreach( var collection in Penumbra.CollectionManager.Current.Inheritance.ToList() ) + { + DrawInheritance( collection ); + } + } + + // Draw a drag and drop button to delete. + private void DrawInheritanceTrashButton() + { + ImGui.SameLine(); + var size = new Vector2( ImGui.GetFrameHeight(), ImGui.GetTextLineHeightWithSpacing() * InheritedCollectionHeight ); + var buttonColor = ImGui.GetColorU32( ImGuiCol.Button ); + // Prevent hovering from highlighting the button. + using var color = ImRaii.PushColor( ImGuiCol.ButtonActive, buttonColor ) + .Push( ImGuiCol.ButtonHovered, buttonColor ); + ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), size, + "Drag primary inheritance here to remove it from the list.", false, true ); + + using var target = ImRaii.DragDropTarget(); + if( target.Success && ImGuiUtil.IsDropping( InheritanceDragDropLabel ) ) + { + _inheritanceAction = ( Penumbra.CollectionManager.Current.Inheritance.IndexOf( _movedInheritance ), -1 ); + } + } + + // Set the current collection, or delete or move an inheritance if the action was triggered during iteration. + // Can not be done during iteration to keep collections unchanged. + private void DelayedActions() + { + if( _newCurrentCollection != null ) + { + Penumbra.CollectionManager.SetCollection( _newCurrentCollection, ModCollection.Type.Current ); + _newCurrentCollection = null; + } + + if( _inheritanceAction == null ) + { + return; + } + + if( _inheritanceAction.Value.Item1 >= 0 ) + { + if( _inheritanceAction.Value.Item2 == -1 ) + { + Penumbra.CollectionManager.Current.RemoveInheritance( _inheritanceAction.Value.Item1 ); + } + else + { + Penumbra.CollectionManager.Current.MoveInheritance( _inheritanceAction.Value.Item1, _inheritanceAction.Value.Item2 ); + } + } + + _inheritanceAction = null; + } + + // Draw the selector to add new inheritances. + // The add button is only available if the selected collection can actually be added. + private void DrawNewInheritanceSelection() + { + DrawNewInheritanceCombo(); + ImGui.SameLine(); + var inheritance = Penumbra.CollectionManager.Current.CheckValidInheritance( _newInheritance ); + var tt = inheritance switch + { + ModCollection.ValidInheritance.Empty => "No valid collection to inherit from selected.", + ModCollection.ValidInheritance.Valid => "Add a new inheritance to the collection.", + ModCollection.ValidInheritance.Self => "Can not inherit from itself.", + ModCollection.ValidInheritance.Contained => "Already inheriting from the selected collection.", + ModCollection.ValidInheritance.Circle => "Inheriting from selected collection would lead to cyclic inheritance.", + _ => string.Empty, + }; + if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Plus.ToIconString(), ImGui.GetFrameHeight() * Vector2.One, tt, + inheritance != ModCollection.ValidInheritance.Valid, true ) + && Penumbra.CollectionManager.Current.AddInheritance( _newInheritance! ) ) + { + _newInheritance = null; + } + + if( inheritance != ModCollection.ValidInheritance.Valid ) + { + _newInheritance = null; + } + + ImGuiComponents.HelpMarker( tt ); + } + + // Draw the combo to select new potential inheritances. + // Only valid inheritances are drawn in the preview, or nothing if no inheritance is available. + private void DrawNewInheritanceCombo() + { + ImGui.SetNextItemWidth( _window._inputTextWidth.X - ImGui.GetFrameHeight() - ImGui.GetStyle().ItemSpacing.X ); + _newInheritance ??= Penumbra.CollectionManager.FirstOrDefault( c + => c != Penumbra.CollectionManager.Current && !Penumbra.CollectionManager.Current.Inheritance.Contains( c ) ) + ?? ModCollection.Empty; + using var combo = ImRaii.Combo( "##newInheritance", _newInheritance.Name ); + if( !combo ) + { + return; + } + + foreach( var collection in Penumbra.CollectionManager + .Where( c => Penumbra.CollectionManager.Current.CheckValidInheritance( c ) == ModCollection.ValidInheritance.Valid ) ) + { + if( ImGui.Selectable( collection.Name, _newInheritance == collection ) ) + { + _newInheritance = collection; + } + } + } + + // Move an inherited collection when dropped onto another. + // Move is delayed due to collection changes. + private void DrawInheritanceDropTarget( ModCollection collection ) + { + using var target = ImRaii.DragDropTarget(); + if( target.Success && ImGuiUtil.IsDropping( InheritanceDragDropLabel ) ) + { + if( _movedInheritance != null ) + { + var idx1 = Penumbra.CollectionManager.Current.Inheritance.IndexOf( _movedInheritance ); + var idx2 = Penumbra.CollectionManager.Current.Inheritance.IndexOf( collection ); + if( idx1 >= 0 && idx2 >= 0 ) + { + _inheritanceAction = ( idx1, idx2 ); + } + } + + _movedInheritance = null; + } + } + + // Move an inherited collection. + private void DrawInheritanceDropSource( ModCollection collection ) + { + using var source = ImRaii.DragDropSource(); + if( source ) + { + ImGui.SetDragDropPayload( InheritanceDragDropLabel, IntPtr.Zero, 0 ); + _movedInheritance = collection; + ImGui.Text( $"Moving {_movedInheritance?.Name ?? "Unknown"}..." ); + } + } + + // Ctrl + Right-Click -> Switch current collection to this (for all). + // Ctrl + Shift + Right-Click -> Delete this inheritance (only if withDelete). + // Deletion is delayed due to collection changes. + private void DrawInheritanceTreeClicks( ModCollection collection, bool withDelete ) + { + if( ImGui.GetIO().KeyCtrl && ImGui.IsItemClicked( ImGuiMouseButton.Right ) ) + { + if( withDelete && ImGui.GetIO().KeyShift ) + { + _inheritanceAction = ( Penumbra.CollectionManager.Current.Inheritance.IndexOf( collection ), -1 ); + } + else + { + _newCurrentCollection = collection; + } + } + + ImGuiUtil.HoverTooltip( "Control + Right-Click to switch the current collection to this one." + + ( withDelete ? "\nControl + Shift + Right-Click to remove this inheritance." : string.Empty ) ); + } + } +} \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.CollectionsTab.cs b/Penumbra/UI/ConfigWindow.CollectionsTab.cs index 7f5f8ee7..6d38cb69 100644 --- a/Penumbra/UI/ConfigWindow.CollectionsTab.cs +++ b/Penumbra/UI/ConfigWindow.CollectionsTab.cs @@ -11,206 +11,180 @@ namespace Penumbra.UI; public partial class ConfigWindow { - private string _newCollectionName = string.Empty; - private string _newCharacterName = string.Empty; - - private void CreateNewCollection( bool duplicate ) + // Encapsulate for less pollution. + private partial class CollectionsTab { - if( Penumbra.CollectionManager.AddCollection( _newCollectionName, duplicate ? Penumbra.CollectionManager.Current : null ) ) + private readonly ConfigWindow _window; + + public CollectionsTab( ConfigWindow window ) + => _window = window; + + public void Draw() { - _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) ) + using var tab = ImRaii.TabItem( "Collections" ); + if( !tab ) { - Penumbra.CollectionManager.RemoveCharacterCollection( name ); + return; } + + DrawMainSelectors(); + DrawCharacterCollectionSelectors(); + } + + + // Input text fields. + private string _newCollectionName = string.Empty; + private bool _canAddCollection = false; + private string _newCharacterName = string.Empty; + + // Create a new collection that is either empty or a duplicate of the current collection. + // Resets the new collection name. + 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." ); + } + + // Draw the new collection input as well as its buttons. + private void DrawNewCollectionInput() + { + // Input for new collection name. Also checks for validity when changed. + ImGui.SetNextItemWidth( _window._inputTextWidth.X ); + if( ImGui.InputTextWithHint( "##New Collection", "New Collection Name", ref _newCollectionName, 64 ) ) + { + _canAddCollection = Penumbra.CollectionManager.CanAddCollection( _newCollectionName, out _ ); + } + ImGui.SameLine(); - ImGui.AlignTextToFramePadding(); - ImGui.Text( name ); + 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." ); + + // Creation buttons. + var tt = _canAddCollection ? string.Empty : "Please enter a unique name before creating a collection."; + if( ImGuiUtil.DrawDisabledButton( "Create New Empty Collection", Vector2.Zero, tt, !_canAddCollection ) ) + { + CreateNewCollection( false ); + } + + ImGui.SameLine(); + if( ImGuiUtil.DrawDisabledButton( "Duplicate Current Collection", Vector2.Zero, tt, !_canAddCollection ) ) + { + CreateNewCollection( true ); + } + + // Deletion conditions. + var deleteCondition = Penumbra.CollectionManager.Current.Name != ModCollection.DefaultCollection; + tt = deleteCondition ? string.Empty : "You can not delete the default collection."; + ImGui.SameLine(); + if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), Vector2.Zero, tt, !deleteCondition, true ) ) + { + Penumbra.CollectionManager.RemoveCollection( Penumbra.CollectionManager.Current ); + } + + if( Penumbra.Config.ShowAdvanced ) + { + ImGui.SameLine(); + DrawCleanCollectionButton(); + } } - 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 ) + private void DrawCurrentCollectionSelector() { - return; + DrawCollectionSelector( "##current", _window._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." ); } - DrawCurrentCollectionSelector(); - ImGuiHelpers.ScaledDummy( 0, 10 ); - DrawNewCollectionInput(); - } - - public void DrawCollectionsTab() - { - using var tab = ImRaii.TabItem( "Collections" ); - if( !tab ) + private void DrawDefaultCollectionSelector() { - return; + DrawCollectionSelector( "##default", _window._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." ); } - - DrawMainSelectors(); - DrawCharacterCollectionSelectors(); - } + // We do not check for valid character names. + 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( _window._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, _window._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 void DrawMainSelectors() + { + var size = new Vector2( -1, + ImGui.GetTextLineHeightWithSpacing() * InheritedCollectionHeight + + _window._defaultSpace.Y * 2 + + ImGui.GetFrameHeightWithSpacing() * 4 + + ImGui.GetStyle().ItemSpacing.Y * 6 ); + using var main = ImRaii.Child( "##CollectionsMain", size, true ); + if( !main ) + { + return; + } + + DrawCurrentCollectionSelector(); + ImGui.Dummy( _window._defaultSpace ); + DrawNewCollectionInput(); + ImGui.Dummy( _window._defaultSpace ); + DrawInheritanceBlock(); + } + } } \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.DebugTab.cs b/Penumbra/UI/ConfigWindow.DebugTab.cs index 1bd4fefc..85b713c7 100644 --- a/Penumbra/UI/ConfigWindow.DebugTab.cs +++ b/Penumbra/UI/ConfigWindow.DebugTab.cs @@ -9,6 +9,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.Api; +using Penumbra.Interop.Loader; using Penumbra.Interop.Structs; using CharacterUtility = Penumbra.Interop.CharacterUtility; @@ -16,387 +17,447 @@ namespace Penumbra.UI; public partial class ConfigWindow { + private class DebugTab + { + private readonly ConfigWindow _window; + + public DebugTab( ConfigWindow window ) + => _window = window; + #if DEBUG - private const string DebugVersionString = "(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; + private const string DebugVersionString = "(Release)"; + private const bool DefaultVisibility = false; #endif - public bool DebugTabVisible = DefaultVisibility; + public bool DebugTabVisible = DefaultVisibility; - public void DrawDebugTab() - { - if( !DebugTabVisible ) + public void Draw() { - return; - } - - using var tab = ImRaii.TabItem( "Debug" ); - if( !tab ) - { - return; - } - - using var child = ImRaii.Child( "##DebugTab", -Vector2.One ); - if( !child ) - { - 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 ) + if( !DebugTabVisible ) { - foreach( var (ptr, (c, idx)) in _penumbra.PathResolver.DrawObjectToObject ) + return; + } + + using var tab = ImRaii.TabItem( "Debug" ); + if( !tab ) + { + return; + } + + using var child = ImRaii.Child( "##DebugTab", -Vector2.One ); + if( !child ) + { + return; + } + + DrawDebugTabGeneral(); + ImGui.NewLine(); + DrawDebugTabReplacedResources(); + ImGui.NewLine(); + DrawPathResolverDebug(); + ImGui.NewLine(); + DrawDebugCharacterUtility(); + ImGui.NewLine(); + DrawDebugResidentResources(); + ImGui.NewLine(); + DrawResourceProblems(); + 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", _window._penumbra.PathResolver.Enabled.ToString() ); + PrintValue( "Music Manager Streaming Disabled", ( !_window._penumbra.MusicManager.StreamingEnabled ).ToString() ); + PrintValue( "Web Server Enabled", ( _window._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 ) { - 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 ); + foreach( var (ptr, (c, idx)) in _window._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 _window._penumbra.PathResolver.PathCollections ) + { + ImGui.TableNextColumn(); + ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length ); + ImGui.TableNextColumn(); + ImGui.Text( collection.Name ); + } } } } - drawTree.Dispose(); - - using var pathTree = ImRaii.TreeNode( "Path Collections" ); - if( pathTree ) + // 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() { - using var table = ImRaii.Table( "###PathCollectionResolverTable", 2, ImGuiTableFlags.SizingFixedFit ); - if( table ) + if( !ImGui.CollapsingHeader( "Character Utility" ) ) { - foreach( var (path, collection) in _penumbra.PathResolver.PathCollections ) - { - ImGui.TableNextColumn(); - ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length ); - ImGui.TableNextColumn(); - ImGui.Text( collection.Name ); - } + return; } - } - } - // 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() ) + using var table = ImRaii.Table( "##CharacterUtility", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, + -Vector2.UnitX ); + if( !table ) { - var (data, length) = resource->GetData(); - if( data != IntPtr.Zero && length > 0 ) + 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* )data, length ).ToArray().Select( b => b.ToString( "X2" ) ) ) ); + 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 ); } } + } - 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() ) + // Draw resources with unusual reference count. + private static unsafe void DrawResourceProblems() + { + var header = ImGui.CollapsingHeader( "Resource Problems" ); + ImGuiUtil.HoverTooltip( "Draw resources with unusually high reference count to detect overflows." ); + if( !header ) { - ImGui.SetClipboardText( string.Join( " ", - new ReadOnlySpan< byte >( ( byte* )Penumbra.CharacterUtility.DefaultResources[ i ].Address, - Penumbra.CharacterUtility.DefaultResources[ i ].Size ).ToArray().Select( b => b.ToString( "X2" ) ) ) ); + return; } - 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 ) + using var table = ImRaii.Table( "##ProblemsTable", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit ); + if( !table ) { - Text( imc ); + return; } - 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 ) + ResourceLoader.IterateResources( ( _, r ) => { - continue; + 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 ); + } + } + } ); + } + + + // Draw information about IPC options and availability. + private void DrawDebugTabIpc() + { + if( !ImGui.CollapsingHeader( "IPC" ) ) + { + return; } - ImGui.TableNextColumn(); + var ipc = _window._penumbra.Ipc; + ImGui.Text( $"API Version: {ipc.Api.ApiVersion}" ); + ImGui.Text( "Available subscriptions:" ); + using var indent = ImRaii.PushIndent(); + if( ipc.ProviderApiVersion != null ) { - Text( mdl->ResourceHandle ); + 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 ); } } - } - // Draw information about IPC options and availability. - private void DrawDebugTabIpc() - { - if( !ImGui.CollapsingHeader( "IPC" ) ) + // Helper to print a property and its value in a 2-column table. + private static void PrintValue( string name, string value ) { - return; + ImGui.TableNextColumn(); + ImGui.Text( name ); + ImGui.TableNextColumn(); + ImGui.Text( value ); } - - 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 b097bcb5..fc1cc3d2 100644 --- a/Penumbra/UI/ConfigWindow.EffectiveTab.cs +++ b/Penumbra/UI/ConfigWindow.EffectiveTab.cs @@ -12,204 +12,208 @@ namespace Penumbra.UI; public partial class ConfigWindow { - // Draw the effective tab if ShowAdvanced is on. - public void DrawEffectiveChangesTab() + private class EffectiveTab { - if( !Penumbra.Config.ShowAdvanced ) + // Draw the effective tab if ShowAdvanced is on. + public void Draw() { - return; - } - - using var tab = ImRaii.TabItem( "Effective Changes" ); - if( !tab ) - { - return; - } - - 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 ) + if( !Penumbra.Config.ShowAdvanced ) { - var it2 = it.Select( p => ( p.Item1.ToString() ?? string.Empty, Penumbra.ModManager.Mods[ p.Item2 ].Name ) ); - if( stop >= 0 ) + return; + } + + using var tab = ImRaii.TabItem( "Effective Changes" ); + if( !tab ) + { + return; + } + + 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; + + // Filters + private LowerString _effectiveGamePathFilter = LowerString.Empty; + private LowerString _effectiveFilePathFilter = LowerString.Empty; + + // 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; + } + + // 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 ) { - ImGuiClip.DrawEndDummy( stop + it2.Count( CheckFilters ), height ); + 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 { - stop = ImGuiClip.FilteredClippedDraw( it2, skips, CheckFilters, DrawLine, ~stop ); - ImGuiClip.DrawEndDummy( stop, height ); + if( stop >= 0 ) + { + ImGuiClip.DrawEndDummy( stop + m.Count, height ); + } + else + { + stop = ImGuiClip.ClippedDraw( it, skips, DrawLine, m.Count, ~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 ); - } + ImGuiClip.DrawEndDummy( stop, height ); } } - else + + // Draw a line for a game path and its redirected file. + private static void DrawLine( KeyValuePair< Utf8GamePath, FullPath > pair ) { - ImGuiClip.DrawEndDummy( stop, height ); - } - } + var (path, name) = pair; + ImGui.TableNextColumn(); + CopyOnClickSelectable( path.Path ); - // 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; + ImGui.TableNextColumn(); + ImGuiUtil.PrintIcon( FontAwesomeIcon.LongArrowAltLeft ); + ImGui.TableNextColumn(); + CopyOnClickSelectable( name.InternalName ); } - 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 ) ) + // Draw a line for a path and its name. + private static void DrawLine( (string, LowerString) pair ) { - return false; + var (path, name) = pair; + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable( path ); + + ImGui.TableNextColumn(); + ImGuiUtil.PrintIcon( FontAwesomeIcon.LongArrowAltLeft ); + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable( name ); } - return _effectiveFilePathFilter.Length == 0 || path.Contains( _effectiveFilePathFilter.Lower ); + // 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 8258fe33..b4cf7835 100644 --- a/Penumbra/UI/ConfigWindow.Misc.cs +++ b/Penumbra/UI/ConfigWindow.Misc.cs @@ -43,8 +43,16 @@ public partial class ConfigWindow if( _penumbra.Api.HasTooltip && ImGui.IsItemHovered() ) { - using var tt = ImRaii.Tooltip(); + // We can not be sure that any subscriber actually prints something in any case. + // Circumvent ugly blank tooltip with less-ugly useless tooltip. + using var tt = ImRaii.Tooltip(); + using var group = ImRaii.Group(); _penumbra.Api.InvokeTooltip( data ); + group.Dispose(); + if( ImGui.GetItemRectSize() == Vector2.Zero ) + { + ImGui.Text( "No actions available." ); + } } if( data is Item it ) diff --git a/Penumbra/UI/ConfigWindow.ModsTab.Panel.cs b/Penumbra/UI/ConfigWindow.ModsTab.Panel.cs index 435dffd4..63434602 100644 --- a/Penumbra/UI/ConfigWindow.ModsTab.Panel.cs +++ b/Penumbra/UI/ConfigWindow.ModsTab.Panel.cs @@ -7,608 +7,607 @@ using Dalamud.Interface; using Dalamud.Logging; using ImGuiNET; using Penumbra.Mods; -using Penumbra.UI.Custom; using Penumbra.Util; namespace Penumbra.UI; public partial class SettingsInterface { - private class ModPanel - { - private const string LabelModPanel = "selectedModInfo"; - private const string LabelEditName = "##editName"; - private const string LabelEditVersion = "##editVersion"; - private const string LabelEditAuthor = "##editAuthor"; - private const string LabelEditWebsite = "##editWebsite"; - private const string LabelModEnabled = "Enabled"; - private const string LabelEditingEnabled = "Enable Editing"; - private const string LabelOverWriteDir = "OverwriteDir"; - private const string ButtonOpenWebsite = "Open Website"; - private const string ButtonOpenModFolder = "Open Mod Folder"; - private const string ButtonRenameModFolder = "Rename Mod Folder"; - private const string ButtonEditJson = "Edit JSON"; - private const string ButtonReloadJson = "Reload JSON"; - private const string ButtonDeduplicate = "Deduplicate"; - private const string ButtonNormalize = "Normalize"; - private const string TooltipOpenModFolder = "Open the directory containing this mod in your default file explorer."; - private const string TooltipRenameModFolder = "Rename the directory containing this mod without opening another application."; - private const string TooltipEditJson = "Open the JSON configuration file in your default application for .json."; - private const string TooltipReloadJson = "Reload the configuration of all mods."; - private const string PopupRenameFolder = "Rename Folder"; - - private const string TooltipDeduplicate = - "Try to find identical files and remove duplicate occurences to reduce the mods disk size.\n" - + "Introduces an invisible single-option Group \"Duplicates\".\nExperimental - use at own risk!"; - - private const string TooltipNormalize = - "Try to reduce unnecessary options or subdirectories to default options if possible.\nExperimental - use at own risk!"; - - private const float HeaderLineDistance = 10f; - private static readonly Vector4 GreyColor = new(1f, 1f, 1f, 0.66f); - - private readonly SettingsInterface _base; - private readonly Selector _selector; - private readonly HashSet< string > _newMods; - public readonly PluginDetails Details; - - private bool _editMode; - private string _currentWebsite; - private bool _validWebsite; - - private string _fromMaterial = string.Empty; - private string _toMaterial = string.Empty; - - public ModPanel( SettingsInterface ui, Selector s, HashSet< string > newMods ) - { - _base = ui; - _selector = s; - _newMods = newMods; - Details = new PluginDetails( _base, _selector ); - _currentWebsite = Meta?.Website ?? ""; - } - - private Mods.FullMod? Mod - => _selector.Mod; - - private ModMeta? Meta - => Mod?.Data.Meta; - - private void DrawName() - { - var name = Meta!.Name.Text; - var modManager = Penumbra.ModManager; - if( ImGuiCustom.InputOrText( _editMode, LabelEditName, ref name, 64 ) && modManager.RenameMod( name, Mod!.Data ) ) - { - _selector.SelectModOnUpdate( Mod.Data.BasePath.Name ); - if( !modManager.TemporaryModSortOrder.ContainsKey( Mod!.Data.BasePath.Name ) ) - { - Mod.Data.Rename( name ); - } - } - } - - private void DrawVersion() - { - if( _editMode ) - { - ImGui.BeginGroup(); - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndGroup ); - ImGui.Text( "(Version " ); - - using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.ItemSpacing, ZeroVector ); - ImGui.SameLine(); - var version = Meta!.Version; - if( ImGuiCustom.ResizingTextInput( LabelEditVersion, ref version, 16 ) - && version != Meta.Version ) - { - Meta.Version = version; - _selector.SaveCurrentMod(); - } - - ImGui.SameLine(); - ImGui.Text( ")" ); - } - else if( Meta!.Version.Length > 0 ) - { - ImGui.Text( $"(Version {Meta.Version})" ); - } - } - - private void DrawAuthor() - { - ImGui.BeginGroup(); - ImGui.TextColored( GreyColor, "by" ); - - ImGui.SameLine(); - var author = Meta!.Author.Text; - if( ImGuiCustom.InputOrText( _editMode, LabelEditAuthor, ref author, 64 ) - && author != Meta.Author ) - { - Meta.Author = author; - _selector.SaveCurrentMod(); - _selector.Cache.TriggerFilterReset(); - } - - ImGui.EndGroup(); - } - - private void DrawWebsite() - { - ImGui.BeginGroup(); - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndGroup ); - if( _editMode ) - { - ImGui.TextColored( GreyColor, "from" ); - ImGui.SameLine(); - var website = Meta!.Website; - if( ImGuiCustom.ResizingTextInput( LabelEditWebsite, ref website, 512 ) - && website != Meta.Website ) - { - Meta.Website = website; - _selector.SaveCurrentMod(); - } - } - else if( Meta!.Website.Length > 0 ) - { - if( _currentWebsite != Meta.Website ) - { - _currentWebsite = Meta.Website; - _validWebsite = Uri.TryCreate( Meta.Website, UriKind.Absolute, out var uriResult ) - && ( uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp ); - } - - if( _validWebsite ) - { - if( ImGui.SmallButton( ButtonOpenWebsite ) ) - { - try - { - var process = new ProcessStartInfo( Meta.Website ) - { - UseShellExecute = true, - }; - Process.Start( process ); - } - catch( System.ComponentModel.Win32Exception ) - { - // Do nothing. - } - } - - ImGuiCustom.HoverTooltip( Meta.Website ); - } - else - { - ImGui.TextColored( GreyColor, "from" ); - ImGui.SameLine(); - ImGui.Text( Meta.Website ); - } - } - } - - private void DrawHeaderLine() - { - DrawName(); - ImGui.SameLine(); - DrawVersion(); - ImGui.SameLine(); - DrawAuthor(); - ImGui.SameLine(); - DrawWebsite(); - } - - private void DrawPriority() - { - var priority = Mod!.Settings.Priority; - ImGui.SetNextItemWidth( 50 * ImGuiHelpers.GlobalScale ); - if( ImGui.InputInt( "Priority", ref priority, 0 ) && priority != Mod!.Settings.Priority ) - { - Penumbra.CollectionManager.Current.SetModPriority( Mod.Data.Index, priority ); - _selector.Cache.TriggerFilterReset(); - } - - ImGuiCustom.HoverTooltip( - "Higher priority mods take precedence over other mods in the case of file conflicts.\n" - + "In case of identical priority, the alphabetically first mod takes precedence." ); - } - - private void DrawEnabledMark() - { - var enabled = Mod!.Settings.Enabled; - if( ImGui.Checkbox( LabelModEnabled, ref enabled ) ) - { - Penumbra.CollectionManager.Current.SetModState( Mod.Data.Index, enabled ); - if( enabled ) - { - _newMods.Remove( Mod.Data.BasePath.Name ); - } - _selector.Cache.TriggerFilterReset(); - } - } - - public static bool DrawSortOrder( Mods.Mod mod, Mods.Mod.Manager manager, Selector selector ) - { - var currentSortOrder = mod.Order.FullPath; - ImGui.SetNextItemWidth( 300 * ImGuiHelpers.GlobalScale ); - if( ImGui.InputText( "Sort Order", ref currentSortOrder, 256, ImGuiInputTextFlags.EnterReturnsTrue ) ) - { - manager.ChangeSortOrder( mod, currentSortOrder ); - selector.SelectModOnUpdate( mod.BasePath.Name ); - return true; - } - - return false; - } - - private void DrawEditableMark() - { - ImGui.Checkbox( LabelEditingEnabled, ref _editMode ); - } - - private void DrawOpenModFolderButton() - { - Mod!.Data.BasePath.Refresh(); - if( ImGui.Button( ButtonOpenModFolder ) && Mod.Data.BasePath.Exists ) - { - Process.Start( new ProcessStartInfo( Mod!.Data.BasePath.FullName ) { UseShellExecute = true } ); - } - - ImGuiCustom.HoverTooltip( TooltipOpenModFolder ); - } - - private string _newName = ""; - private bool _keyboardFocus = true; - - private void RenameModFolder( string newName ) - { - _newName = newName.ReplaceBadXivSymbols(); - if( _newName.Length == 0 ) - { - PluginLog.Debug( "New Directory name {NewName} was empty after removing invalid symbols.", newName ); - ImGui.CloseCurrentPopup(); - } - else if( !string.Equals( _newName, Mod!.Data.BasePath.Name, StringComparison.InvariantCultureIgnoreCase ) ) - { - var dir = Mod!.Data.BasePath; - DirectoryInfo newDir = new(Path.Combine( dir.Parent!.FullName, _newName )); - - if( newDir.Exists ) - { - ImGui.OpenPopup( LabelOverWriteDir ); - } - else if( Penumbra.ModManager.RenameModFolder( Mod.Data, newDir ) ) - { - _selector.ReloadCurrentMod(); - ImGui.CloseCurrentPopup(); - } - } - else if( !string.Equals( _newName, Mod!.Data.BasePath.Name, StringComparison.InvariantCulture ) ) - { - var dir = Mod!.Data.BasePath; - DirectoryInfo newDir = new(Path.Combine( dir.Parent!.FullName, _newName )); - var sourceUri = new Uri( dir.FullName ); - var targetUri = new Uri( newDir.FullName ); - if( sourceUri.Equals( targetUri ) ) - { - var tmpFolder = new DirectoryInfo( TempFile.TempFileName( dir.Parent! ).FullName ); - if( Penumbra.ModManager.RenameModFolder( Mod.Data, tmpFolder ) ) - { - if( !Penumbra.ModManager.RenameModFolder( Mod.Data, newDir ) ) - { - PluginLog.Error( "Could not recapitalize folder after renaming, reverting rename." ); - Penumbra.ModManager.RenameModFolder( Mod.Data, dir ); - } - - _selector.ReloadCurrentMod(); - } - - ImGui.CloseCurrentPopup(); - } - else - { - ImGui.OpenPopup( LabelOverWriteDir ); - } - } - } - - private static bool MergeFolderInto( DirectoryInfo source, DirectoryInfo target ) - { - try - { - foreach( var file in source.EnumerateFiles( "*", SearchOption.AllDirectories ) ) - { - var targetFile = new FileInfo( Path.Combine( target.FullName, file.FullName.Substring( source.FullName.Length + 1 ) ) ); - if( targetFile.Exists ) - { - targetFile.Delete(); - } - - targetFile.Directory?.Create(); - file.MoveTo( targetFile.FullName ); - } - - source.Delete( true ); - return true; - } - catch( Exception e ) - { - PluginLog.Error( $"Could not merge directory {source.FullName} into {target.FullName}:\n{e}" ); - } - - return false; - } - - private bool OverwriteDirPopup() - { - var closeParent = false; - var _ = true; - if( !ImGui.BeginPopupModal( LabelOverWriteDir, ref _, ImGuiWindowFlags.AlwaysAutoResize ) ) - { - return closeParent; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - - var dir = Mod!.Data.BasePath; - DirectoryInfo newDir = new(Path.Combine( dir.Parent!.FullName, _newName )); - ImGui.Text( - $"The mod directory {newDir} already exists.\nDo you want to merge / overwrite both mods?\nThis may corrupt the resulting mod in irrecoverable ways." ); - var buttonSize = ImGuiHelpers.ScaledVector2( 120, 0 ); - if( ImGui.Button( "Yes", buttonSize ) && MergeFolderInto( dir, newDir ) ) - { - Penumbra.ModManager.RenameModFolder( Mod.Data, newDir, false ); - - _selector.SelectModOnUpdate( _newName ); - - closeParent = true; - ImGui.CloseCurrentPopup(); - } - - ImGui.SameLine(); - - if( ImGui.Button( "Cancel", buttonSize ) ) - { - _keyboardFocus = true; - ImGui.CloseCurrentPopup(); - } - - return closeParent; - } - - private void DrawRenameModFolderPopup() - { - var _ = true; - _keyboardFocus |= !ImGui.IsPopupOpen( PopupRenameFolder ); - - ImGui.SetNextWindowPos( ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, new Vector2( 0.5f, 1f ) ); - if( !ImGui.BeginPopupModal( PopupRenameFolder, ref _, ImGuiWindowFlags.AlwaysAutoResize ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); - - if( ImGui.IsKeyPressed( ImGui.GetKeyIndex( ImGuiKey.Escape ) ) ) - { - ImGui.CloseCurrentPopup(); - } - - var newName = Mod!.Data.BasePath.Name; - - if( _keyboardFocus ) - { - ImGui.SetKeyboardFocusHere(); - _keyboardFocus = false; - } - - if( ImGui.InputText( "New Folder Name##RenameFolderInput", ref newName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) ) - { - RenameModFolder( newName ); - } - - ImGui.TextColored( GreyColor, - "Please restrict yourself to ascii symbols that are valid in a windows path,\nother symbols will be replaced by underscores." ); - - ImGui.SetNextWindowPos( ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, Vector2.One / 2 ); - - - if( OverwriteDirPopup() ) - { - ImGui.CloseCurrentPopup(); - } - } - - private void DrawRenameModFolderButton() - { - DrawRenameModFolderPopup(); - if( ImGui.Button( ButtonRenameModFolder ) ) - { - ImGui.OpenPopup( PopupRenameFolder ); - } - - ImGuiCustom.HoverTooltip( TooltipRenameModFolder ); - } - - private void DrawEditJsonButton() - { - if( ImGui.Button( ButtonEditJson ) ) - { - _selector.SaveCurrentMod(); - Process.Start( new ProcessStartInfo( Mod!.Data.MetaFile.FullName ) { UseShellExecute = true } ); - } - - ImGuiCustom.HoverTooltip( TooltipEditJson ); - } - - private void DrawReloadJsonButton() - { - if( ImGui.Button( ButtonReloadJson ) ) - { - _selector.ReloadCurrentMod( true, false ); - } - - ImGuiCustom.HoverTooltip( TooltipReloadJson ); - } - - private void DrawResetMetaButton() - { - if( ImGui.Button( "Recompute Metadata" ) ) - { - _selector.ReloadCurrentMod( true, true, true ); - } - - ImGuiCustom.HoverTooltip( - "Force a recomputation of the metadata_manipulations.json file from all .meta files in the folder.\n" - + "Also reloads the mod.\n" - + "Be aware that this removes all manually added metadata changes." ); - } - - private void DrawDeduplicateButton() - { - if( ImGui.Button( ButtonDeduplicate ) ) - { - ModCleanup.Deduplicate( Mod!.Data.BasePath, Meta! ); - _selector.SaveCurrentMod(); - _selector.ReloadCurrentMod( true, true, true ); - } - - ImGuiCustom.HoverTooltip( TooltipDeduplicate ); - } - - private void DrawNormalizeButton() - { - if( ImGui.Button( ButtonNormalize ) ) - { - ModCleanup.Normalize( Mod!.Data.BasePath, Meta! ); - _selector.SaveCurrentMod(); - _selector.ReloadCurrentMod( true, true, true ); - } - - ImGuiCustom.HoverTooltip( TooltipNormalize ); - } - - private void DrawAutoGenerateGroupsButton() - { - if( ImGui.Button( "Auto-Generate Groups" ) ) - { - ModCleanup.AutoGenerateGroups( Mod!.Data.BasePath, Meta! ); - _selector.SaveCurrentMod(); - _selector.ReloadCurrentMod( true, true ); - } - - ImGuiCustom.HoverTooltip( "Automatically generate single-select groups from all folders (clears existing groups):\n" - + "First subdirectory: Option Group\n" - + "Second subdirectory: Option Name\n" - + "Afterwards: Relative file paths.\n" - + "Experimental - Use at own risk!" ); - } - - private void DrawSplitButton() - { - if( ImGui.Button( "Split Mod" ) ) - { - ModCleanup.SplitMod( Mod!.Data ); - } - - ImGuiCustom.HoverTooltip( - "Split off all options of a mod into single mods that are placed in a collective folder.\n" - + "Does not remove or change the mod itself, just create (potentially inefficient) copies.\n" - + "Experimental - Use at own risk!" ); - } - - private void DrawMaterialChangeRow() - { - ImGui.SetNextItemWidth( 150 * ImGuiHelpers.GlobalScale ); - ImGui.InputTextWithHint( "##fromMaterial", "From Material Suffix...", ref _fromMaterial, 16 ); - ImGui.SameLine(); - using var font = ImGuiRaii.PushFont( UiBuilder.IconFont ); - ImGui.Text( FontAwesomeIcon.LongArrowAltRight.ToIconString() ); - font.Pop(); - ImGui.SameLine(); - ImGui.SetNextItemWidth( 150 * ImGuiHelpers.GlobalScale ); - ImGui.InputTextWithHint( "##toMaterial", "To Material Suffix...", ref _toMaterial, 16 ); - ImGui.SameLine(); - var validStrings = ModelChanger.ValidStrings( _fromMaterial, _toMaterial ); - using var alpha = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f, !validStrings ); - if( ImGui.Button( "Convert" ) && validStrings ) - { - ModelChanger.ChangeModMaterials( Mod!.Data, _fromMaterial, _toMaterial ); - } - - alpha.Pop(); - - ImGuiCustom.HoverTooltip( - "Change the skin material of all models in this mod reference " - + "from the suffix given in the first text input to " - + "the suffix given in the second input.\n" - + "Enter only the suffix, e.g. 'd' or 'a' or 'bibo', not the whole path.\n" - + "This overwrites .mdl files, use at your own risk!" ); - } - - private void DrawEditLine() - { - DrawOpenModFolderButton(); - ImGui.SameLine(); - DrawRenameModFolderButton(); - ImGui.SameLine(); - DrawEditJsonButton(); - ImGui.SameLine(); - DrawReloadJsonButton(); - - DrawResetMetaButton(); - ImGui.SameLine(); - DrawDeduplicateButton(); - ImGui.SameLine(); - DrawNormalizeButton(); - ImGui.SameLine(); - DrawAutoGenerateGroupsButton(); - ImGui.SameLine(); - DrawSplitButton(); - - DrawMaterialChangeRow(); - - DrawSortOrder( Mod!.Data, Penumbra.ModManager, _selector ); - } - - public void Draw() - { - try - { - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndChild ); - var ret = ImGui.BeginChild( LabelModPanel, AutoFillSize, true ); - - if( !ret || Mod == null ) - { - return; - } - - DrawHeaderLine(); - - // Next line with fixed distance. - ImGuiCustom.VerticalDistance( HeaderLineDistance ); - - DrawEnabledMark(); - ImGui.SameLine(); - DrawPriority(); - if( Penumbra.Config.ShowAdvanced ) - { - ImGui.SameLine(); - DrawEditableMark(); - } - - // Next line, if editable. - if( _editMode ) - { - DrawEditLine(); - } - - Details.Draw( _editMode ); - } - catch( Exception ex ) - { - PluginLog.LogError( ex, "Oh no" ); - } - } - } + //private class ModPanel + //{ + // private const string LabelModPanel = "selectedModInfo"; + // private const string LabelEditName = "##editName"; + // private const string LabelEditVersion = "##editVersion"; + // private const string LabelEditAuthor = "##editAuthor"; + // private const string LabelEditWebsite = "##editWebsite"; + // private const string LabelModEnabled = "Enabled"; + // private const string LabelEditingEnabled = "Enable Editing"; + // private const string LabelOverWriteDir = "OverwriteDir"; + // private const string ButtonOpenWebsite = "Open Website"; + // private const string ButtonOpenModFolder = "Open Mod Folder"; + // private const string ButtonRenameModFolder = "Rename Mod Folder"; + // private const string ButtonEditJson = "Edit JSON"; + // private const string ButtonReloadJson = "Reload JSON"; + // private const string ButtonDeduplicate = "Deduplicate"; + // private const string ButtonNormalize = "Normalize"; + // private const string TooltipOpenModFolder = "Open the directory containing this mod in your default file explorer."; + // private const string TooltipRenameModFolder = "Rename the directory containing this mod without opening another application."; + // private const string TooltipEditJson = "Open the JSON configuration file in your default application for .json."; + // private const string TooltipReloadJson = "Reload the configuration of all mods."; + // private const string PopupRenameFolder = "Rename Folder"; + // + // private const string TooltipDeduplicate = + // "Try to find identical files and remove duplicate occurences to reduce the mods disk size.\n" + // + "Introduces an invisible single-option Group \"Duplicates\".\nExperimental - use at own risk!"; + // + // private const string TooltipNormalize = + // "Try to reduce unnecessary options or subdirectories to default options if possible.\nExperimental - use at own risk!"; + // + // private const float HeaderLineDistance = 10f; + // private static readonly Vector4 GreyColor = new(1f, 1f, 1f, 0.66f); + // + // private readonly SettingsInterface _base; + // private readonly Selector _selector; + // private readonly HashSet< string > _newMods; + // public readonly PluginDetails Details; + // + // private bool _editMode; + // private string _currentWebsite; + // private bool _validWebsite; + // + // private string _fromMaterial = string.Empty; + // private string _toMaterial = string.Empty; + // + // public ModPanel( SettingsInterface ui, Selector s, HashSet< string > newMods ) + // { + // _base = ui; + // _selector = s; + // _newMods = newMods; + // Details = new PluginDetails( _base, _selector ); + // _currentWebsite = Meta?.Website ?? ""; + // } + // + // private Mods.FullMod? Mod + // => _selector.Mod; + // + // private ModMeta? Meta + // => Mod?.Data.Meta; + // + // private void DrawName() + // { + // var name = Meta!.Name.Text; + // var modManager = Penumbra.ModManager; + // if( ImGuiCustom.InputOrText( _editMode, LabelEditName, ref name, 64 ) && modManager.RenameMod( name, Mod!.Data ) ) + // { + // _selector.SelectModOnUpdate( Mod.Data.BasePath.Name ); + // if( !modManager.TemporaryModSortOrder.ContainsKey( Mod!.Data.BasePath.Name ) ) + // { + // Mod.Data.Rename( name ); + // } + // } + // } + // + // private void DrawVersion() + // { + // if( _editMode ) + // { + // ImGui.BeginGroup(); + // using var raii = ImGuiRaii.DeferredEnd( ImGui.EndGroup ); + // ImGui.Text( "(Version " ); + // + // using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.ItemSpacing, ZeroVector ); + // ImGui.SameLine(); + // var version = Meta!.Version; + // if( ImGuiCustom.ResizingTextInput( LabelEditVersion, ref version, 16 ) + // && version != Meta.Version ) + // { + // Meta.Version = version; + // _selector.SaveCurrentMod(); + // } + // + // ImGui.SameLine(); + // ImGui.Text( ")" ); + // } + // else if( Meta!.Version.Length > 0 ) + // { + // ImGui.Text( $"(Version {Meta.Version})" ); + // } + // } + // + // private void DrawAuthor() + // { + // ImGui.BeginGroup(); + // ImGui.TextColored( GreyColor, "by" ); + // + // ImGui.SameLine(); + // var author = Meta!.Author.Text; + // if( ImGuiCustom.InputOrText( _editMode, LabelEditAuthor, ref author, 64 ) + // && author != Meta.Author ) + // { + // Meta.Author = author; + // _selector.SaveCurrentMod(); + // _selector.Cache.TriggerFilterReset(); + // } + // + // ImGui.EndGroup(); + // } + // + // private void DrawWebsite() + // { + // ImGui.BeginGroup(); + // using var raii = ImGuiRaii.DeferredEnd( ImGui.EndGroup ); + // if( _editMode ) + // { + // ImGui.TextColored( GreyColor, "from" ); + // ImGui.SameLine(); + // var website = Meta!.Website; + // if( ImGuiCustom.ResizingTextInput( LabelEditWebsite, ref website, 512 ) + // && website != Meta.Website ) + // { + // Meta.Website = website; + // _selector.SaveCurrentMod(); + // } + // } + // else if( Meta!.Website.Length > 0 ) + // { + // if( _currentWebsite != Meta.Website ) + // { + // _currentWebsite = Meta.Website; + // _validWebsite = Uri.TryCreate( Meta.Website, UriKind.Absolute, out var uriResult ) + // && ( uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp ); + // } + // + // if( _validWebsite ) + // { + // if( ImGui.SmallButton( ButtonOpenWebsite ) ) + // { + // try + // { + // var process = new ProcessStartInfo( Meta.Website ) + // { + // UseShellExecute = true, + // }; + // Process.Start( process ); + // } + // catch( System.ComponentModel.Win32Exception ) + // { + // // Do nothing. + // } + // } + // + // ImGuiCustom.HoverTooltip( Meta.Website ); + // } + // else + // { + // ImGui.TextColored( GreyColor, "from" ); + // ImGui.SameLine(); + // ImGui.Text( Meta.Website ); + // } + // } + // } + // + // private void DrawHeaderLine() + // { + // DrawName(); + // ImGui.SameLine(); + // DrawVersion(); + // ImGui.SameLine(); + // DrawAuthor(); + // ImGui.SameLine(); + // DrawWebsite(); + // } + // + // private void DrawPriority() + // { + // var priority = Mod!.Settings.Priority; + // ImGui.SetNextItemWidth( 50 * ImGuiHelpers.GlobalScale ); + // if( ImGui.InputInt( "Priority", ref priority, 0 ) && priority != Mod!.Settings.Priority ) + // { + // Penumbra.CollectionManager.Current.SetModPriority( Mod.Data.Index, priority ); + // _selector.Cache.TriggerFilterReset(); + // } + // + // ImGuiCustom.HoverTooltip( + // "Higher priority mods take precedence over other mods in the case of file conflicts.\n" + // + "In case of identical priority, the alphabetically first mod takes precedence." ); + // } + // + // private void DrawEnabledMark() + // { + // var enabled = Mod!.Settings.Enabled; + // if( ImGui.Checkbox( LabelModEnabled, ref enabled ) ) + // { + // Penumbra.CollectionManager.Current.SetModState( Mod.Data.Index, enabled ); + // if( enabled ) + // { + // _newMods.Remove( Mod.Data.BasePath.Name ); + // } + // _selector.Cache.TriggerFilterReset(); + // } + // } + // + // public static bool DrawSortOrder( Mods.Mod mod, Mods.Mod.Manager manager, Selector selector ) + // { + // var currentSortOrder = mod.Order.FullPath; + // ImGui.SetNextItemWidth( 300 * ImGuiHelpers.GlobalScale ); + // if( ImGui.InputText( "Sort Order", ref currentSortOrder, 256, ImGuiInputTextFlags.EnterReturnsTrue ) ) + // { + // manager.ChangeSortOrder( mod, currentSortOrder ); + // selector.SelectModOnUpdate( mod.BasePath.Name ); + // return true; + // } + // + // return false; + // } + // + // private void DrawEditableMark() + // { + // ImGui.Checkbox( LabelEditingEnabled, ref _editMode ); + // } + // + // private void DrawOpenModFolderButton() + // { + // Mod!.Data.BasePath.Refresh(); + // if( ImGui.Button( ButtonOpenModFolder ) && Mod.Data.BasePath.Exists ) + // { + // Process.Start( new ProcessStartInfo( Mod!.Data.BasePath.FullName ) { UseShellExecute = true } ); + // } + // + // ImGuiCustom.HoverTooltip( TooltipOpenModFolder ); + // } + // + // private string _newName = ""; + // private bool _keyboardFocus = true; + // + // private void RenameModFolder( string newName ) + // { + // _newName = newName.ReplaceBadXivSymbols(); + // if( _newName.Length == 0 ) + // { + // PluginLog.Debug( "New Directory name {NewName} was empty after removing invalid symbols.", newName ); + // ImGui.CloseCurrentPopup(); + // } + // else if( !string.Equals( _newName, Mod!.Data.BasePath.Name, StringComparison.InvariantCultureIgnoreCase ) ) + // { + // var dir = Mod!.Data.BasePath; + // DirectoryInfo newDir = new(Path.Combine( dir.Parent!.FullName, _newName )); + // + // if( newDir.Exists ) + // { + // ImGui.OpenPopup( LabelOverWriteDir ); + // } + // else if( Penumbra.ModManager.RenameModFolder( Mod.Data, newDir ) ) + // { + // _selector.ReloadCurrentMod(); + // ImGui.CloseCurrentPopup(); + // } + // } + // else if( !string.Equals( _newName, Mod!.Data.BasePath.Name, StringComparison.InvariantCulture ) ) + // { + // var dir = Mod!.Data.BasePath; + // DirectoryInfo newDir = new(Path.Combine( dir.Parent!.FullName, _newName )); + // var sourceUri = new Uri( dir.FullName ); + // var targetUri = new Uri( newDir.FullName ); + // if( sourceUri.Equals( targetUri ) ) + // { + // var tmpFolder = new DirectoryInfo( TempFile.TempFileName( dir.Parent! ).FullName ); + // if( Penumbra.ModManager.RenameModFolder( Mod.Data, tmpFolder ) ) + // { + // if( !Penumbra.ModManager.RenameModFolder( Mod.Data, newDir ) ) + // { + // PluginLog.Error( "Could not recapitalize folder after renaming, reverting rename." ); + // Penumbra.ModManager.RenameModFolder( Mod.Data, dir ); + // } + // + // _selector.ReloadCurrentMod(); + // } + // + // ImGui.CloseCurrentPopup(); + // } + // else + // { + // ImGui.OpenPopup( LabelOverWriteDir ); + // } + // } + // } + // + // private static bool MergeFolderInto( DirectoryInfo source, DirectoryInfo target ) + // { + // try + // { + // foreach( var file in source.EnumerateFiles( "*", SearchOption.AllDirectories ) ) + // { + // var targetFile = new FileInfo( Path.Combine( target.FullName, file.FullName.Substring( source.FullName.Length + 1 ) ) ); + // if( targetFile.Exists ) + // { + // targetFile.Delete(); + // } + // + // targetFile.Directory?.Create(); + // file.MoveTo( targetFile.FullName ); + // } + // + // source.Delete( true ); + // return true; + // } + // catch( Exception e ) + // { + // PluginLog.Error( $"Could not merge directory {source.FullName} into {target.FullName}:\n{e}" ); + // } + // + // return false; + // } + // + // private bool OverwriteDirPopup() + // { + // var closeParent = false; + // var _ = true; + // if( !ImGui.BeginPopupModal( LabelOverWriteDir, ref _, ImGuiWindowFlags.AlwaysAutoResize ) ) + // { + // return closeParent; + // } + // + // using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); + // + // var dir = Mod!.Data.BasePath; + // DirectoryInfo newDir = new(Path.Combine( dir.Parent!.FullName, _newName )); + // ImGui.Text( + // $"The mod directory {newDir} already exists.\nDo you want to merge / overwrite both mods?\nThis may corrupt the resulting mod in irrecoverable ways." ); + // var buttonSize = ImGuiHelpers.ScaledVector2( 120, 0 ); + // if( ImGui.Button( "Yes", buttonSize ) && MergeFolderInto( dir, newDir ) ) + // { + // Penumbra.ModManager.RenameModFolder( Mod.Data, newDir, false ); + // + // _selector.SelectModOnUpdate( _newName ); + // + // closeParent = true; + // ImGui.CloseCurrentPopup(); + // } + // + // ImGui.SameLine(); + // + // if( ImGui.Button( "Cancel", buttonSize ) ) + // { + // _keyboardFocus = true; + // ImGui.CloseCurrentPopup(); + // } + // + // return closeParent; + // } + // + // private void DrawRenameModFolderPopup() + // { + // var _ = true; + // _keyboardFocus |= !ImGui.IsPopupOpen( PopupRenameFolder ); + // + // ImGui.SetNextWindowPos( ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, new Vector2( 0.5f, 1f ) ); + // if( !ImGui.BeginPopupModal( PopupRenameFolder, ref _, ImGuiWindowFlags.AlwaysAutoResize ) ) + // { + // return; + // } + // + // using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup ); + // + // if( ImGui.IsKeyPressed( ImGui.GetKeyIndex( ImGuiKey.Escape ) ) ) + // { + // ImGui.CloseCurrentPopup(); + // } + // + // var newName = Mod!.Data.BasePath.Name; + // + // if( _keyboardFocus ) + // { + // ImGui.SetKeyboardFocusHere(); + // _keyboardFocus = false; + // } + // + // if( ImGui.InputText( "New Folder Name##RenameFolderInput", ref newName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) ) + // { + // RenameModFolder( newName ); + // } + // + // ImGui.TextColored( GreyColor, + // "Please restrict yourself to ascii symbols that are valid in a windows path,\nother symbols will be replaced by underscores." ); + // + // ImGui.SetNextWindowPos( ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, Vector2.One / 2 ); + // + // + // if( OverwriteDirPopup() ) + // { + // ImGui.CloseCurrentPopup(); + // } + // } + // + // private void DrawRenameModFolderButton() + // { + // DrawRenameModFolderPopup(); + // if( ImGui.Button( ButtonRenameModFolder ) ) + // { + // ImGui.OpenPopup( PopupRenameFolder ); + // } + // + // ImGuiCustom.HoverTooltip( TooltipRenameModFolder ); + // } + // + // private void DrawEditJsonButton() + // { + // if( ImGui.Button( ButtonEditJson ) ) + // { + // _selector.SaveCurrentMod(); + // Process.Start( new ProcessStartInfo( Mod!.Data.MetaFile.FullName ) { UseShellExecute = true } ); + // } + // + // ImGuiCustom.HoverTooltip( TooltipEditJson ); + // } + // + // private void DrawReloadJsonButton() + // { + // if( ImGui.Button( ButtonReloadJson ) ) + // { + // _selector.ReloadCurrentMod( true, false ); + // } + // + // ImGuiCustom.HoverTooltip( TooltipReloadJson ); + // } + // + // private void DrawResetMetaButton() + // { + // if( ImGui.Button( "Recompute Metadata" ) ) + // { + // _selector.ReloadCurrentMod( true, true, true ); + // } + // + // ImGuiCustom.HoverTooltip( + // "Force a recomputation of the metadata_manipulations.json file from all .meta files in the folder.\n" + // + "Also reloads the mod.\n" + // + "Be aware that this removes all manually added metadata changes." ); + // } + // + // private void DrawDeduplicateButton() + // { + // if( ImGui.Button( ButtonDeduplicate ) ) + // { + // ModCleanup.Deduplicate( Mod!.Data.BasePath, Meta! ); + // _selector.SaveCurrentMod(); + // _selector.ReloadCurrentMod( true, true, true ); + // } + // + // ImGuiCustom.HoverTooltip( TooltipDeduplicate ); + // } + // + // private void DrawNormalizeButton() + // { + // if( ImGui.Button( ButtonNormalize ) ) + // { + // ModCleanup.Normalize( Mod!.Data.BasePath, Meta! ); + // _selector.SaveCurrentMod(); + // _selector.ReloadCurrentMod( true, true, true ); + // } + // + // ImGuiCustom.HoverTooltip( TooltipNormalize ); + // } + // + // private void DrawAutoGenerateGroupsButton() + // { + // if( ImGui.Button( "Auto-Generate Groups" ) ) + // { + // ModCleanup.AutoGenerateGroups( Mod!.Data.BasePath, Meta! ); + // _selector.SaveCurrentMod(); + // _selector.ReloadCurrentMod( true, true ); + // } + // + // ImGuiCustom.HoverTooltip( "Automatically generate single-select groups from all folders (clears existing groups):\n" + // + "First subdirectory: Option Group\n" + // + "Second subdirectory: Option Name\n" + // + "Afterwards: Relative file paths.\n" + // + "Experimental - Use at own risk!" ); + // } + // + // private void DrawSplitButton() + // { + // if( ImGui.Button( "Split Mod" ) ) + // { + // ModCleanup.SplitMod( Mod!.Data ); + // } + // + // ImGuiCustom.HoverTooltip( + // "Split off all options of a mod into single mods that are placed in a collective folder.\n" + // + "Does not remove or change the mod itself, just create (potentially inefficient) copies.\n" + // + "Experimental - Use at own risk!" ); + // } + // + // private void DrawMaterialChangeRow() + // { + // ImGui.SetNextItemWidth( 150 * ImGuiHelpers.GlobalScale ); + // ImGui.InputTextWithHint( "##fromMaterial", "From Material Suffix...", ref _fromMaterial, 16 ); + // ImGui.SameLine(); + // using var font = ImGuiRaii.PushFont( UiBuilder.IconFont ); + // ImGui.Text( FontAwesomeIcon.LongArrowAltRight.ToIconString() ); + // font.Pop(); + // ImGui.SameLine(); + // ImGui.SetNextItemWidth( 150 * ImGuiHelpers.GlobalScale ); + // ImGui.InputTextWithHint( "##toMaterial", "To Material Suffix...", ref _toMaterial, 16 ); + // ImGui.SameLine(); + // var validStrings = ModelChanger.ValidStrings( _fromMaterial, _toMaterial ); + // using var alpha = ImGuiRaii.PushStyle( ImGuiStyleVar.Alpha, 0.5f, !validStrings ); + // if( ImGui.Button( "Convert" ) && validStrings ) + // { + // ModelChanger.ChangeModMaterials( Mod!.Data, _fromMaterial, _toMaterial ); + // } + // + // alpha.Pop(); + // + // ImGuiCustom.HoverTooltip( + // "Change the skin material of all models in this mod reference " + // + "from the suffix given in the first text input to " + // + "the suffix given in the second input.\n" + // + "Enter only the suffix, e.g. 'd' or 'a' or 'bibo', not the whole path.\n" + // + "This overwrites .mdl files, use at your own risk!" ); + // } + // + // private void DrawEditLine() + // { + // DrawOpenModFolderButton(); + // ImGui.SameLine(); + // DrawRenameModFolderButton(); + // ImGui.SameLine(); + // DrawEditJsonButton(); + // ImGui.SameLine(); + // DrawReloadJsonButton(); + // + // DrawResetMetaButton(); + // ImGui.SameLine(); + // DrawDeduplicateButton(); + // ImGui.SameLine(); + // DrawNormalizeButton(); + // ImGui.SameLine(); + // DrawAutoGenerateGroupsButton(); + // ImGui.SameLine(); + // DrawSplitButton(); + // + // DrawMaterialChangeRow(); + // + // DrawSortOrder( Mod!.Data, Penumbra.ModManager, _selector ); + // } + // + // public void Draw() + // { + // try + // { + // using var raii = ImGuiRaii.DeferredEnd( ImGui.EndChild ); + // var ret = ImGui.BeginChild( LabelModPanel, AutoFillSize, true ); + // + // if( !ret || Mod == null ) + // { + // return; + // } + // + // DrawHeaderLine(); + // + // // Next line with fixed distance. + // ImGuiCustom.VerticalDistance( HeaderLineDistance ); + // + // DrawEnabledMark(); + // ImGui.SameLine(); + // DrawPriority(); + // if( Penumbra.Config.ShowAdvanced ) + // { + // ImGui.SameLine(); + // DrawEditableMark(); + // } + // + // // Next line, if editable. + // if( _editMode ) + // { + // DrawEditLine(); + // } + // + // Details.Draw( _editMode ); + // } + // catch( Exception ex ) + // { + // PluginLog.LogError( ex, "Oh no" ); + // } + // } + //} } \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.ModsTab.cs b/Penumbra/UI/ConfigWindow.ModsTab.cs index 3af34018..b0d1145e 100644 --- a/Penumbra/UI/ConfigWindow.ModsTab.cs +++ b/Penumbra/UI/ConfigWindow.ModsTab.cs @@ -1,12 +1,386 @@ using System; +using System.Diagnostics; using System.Numerics; +using Dalamud.Interface; using ImGuiNET; using OtterGui; using OtterGui.Raii; +using OtterGui.Widgets; using Penumbra.Collections; +using Penumbra.Mods; +using Penumbra.UI.Classes; namespace Penumbra.UI; +public partial class ConfigWindow +{ + private class ModPanel + { + private readonly ConfigWindow _window; + private bool _valid; + private bool _emptySetting; + private bool _inherited; + private ModFileSystem.Leaf _leaf = null!; + private Mod2 _mod = null!; + private ModSettings2 _settings = null!; + private ModCollection _collection = null!; + private string _lastWebsite = string.Empty; + private bool _websiteValid; + + private string? _currentSortOrderPath; + private int? _currentPriority; + + public ModPanel( ConfigWindow window ) + => _window = window; + + private void Init( ModFileSystemSelector selector ) + { + _valid = selector.Selected != null; + if( !_valid ) + { + return; + } + + _leaf = selector.SelectedLeaf!; + _mod = selector.Selected!; + _settings = selector.SelectedSettings; + _collection = selector.SelectedSettingCollection; + _emptySetting = _settings == ModSettings2.Empty; + _inherited = _collection != Penumbra.CollectionManager.Current; + } + + public void Draw( ModFileSystemSelector selector ) + { + Init( selector ); + if( !_valid ) + { + return; + } + + DrawInheritedWarning(); + DrawHeaderLine(); + DrawFilesystemPath(); + DrawEnabledInput(); + ImGui.SameLine(); + DrawPriorityInput(); + DrawRemoveSettings(); + DrawTabBar(); + } + + private void DrawDescriptionTab() + { + if( _mod.Description.Length == 0 ) + { + return; + } + + using var tab = ImRaii.TabItem( "Description" ); + if( !tab ) + { + return; + } + + using var child = ImRaii.Child( "##tab" ); + if( !child ) + { + return; + } + + ImGui.TextWrapped( _mod.Description ); + } + + private void DrawSettingsTab() + { + if( !_mod.HasOptions ) + { + return; + } + + using var tab = ImRaii.TabItem( "Settings" ); + if( !tab ) + { + return; + } + + using var child = ImRaii.Child( "##tab" ); + if( !child ) + { + return; + } + + for( var idx = 0; idx < _mod.Groups.Count; ++idx ) + { + var group = _mod.Groups[ idx ]; + if( group.Type == SelectType.Single && group.IsOption ) + { + using var id = ImRaii.PushId( idx ); + var selectedOption = _emptySetting ? 0 : ( int )_settings.Settings[ idx ]; + ImGui.SetNextItemWidth( _window._inputTextWidth.X ); + using var combo = ImRaii.Combo( string.Empty, group[ selectedOption ].Name ); + if( combo ) + { + for( var idx2 = 0; idx2 < group.Count; ++idx2 ) + { + if( ImGui.Selectable( group[ idx2 ].Name, idx2 == selectedOption ) ) + { + Penumbra.CollectionManager.Current.SetModSetting( _mod.Index, idx, ( uint )idx2 ); + } + } + } + + combo.Dispose(); + ImGui.SameLine(); + if( group.Description.Length > 0 ) + { + ImGuiUtil.LabeledHelpMarker( group.Name, group.Description ); + } + else + { + ImGui.Text( group.Name ); + } + } + } + + // TODO add description + for( var idx = 0; idx < _mod.Groups.Count; ++idx ) + { + var group = _mod.Groups[ idx ]; + if( group.Type == SelectType.Multi && group.IsOption ) + { + using var id = ImRaii.PushId( idx ); + var flags = _emptySetting ? 0u : _settings.Settings[ idx ]; + Widget.BeginFramedGroup( group.Name ); + for( var idx2 = 0; idx2 < group.Count; ++idx2 ) + { + var flag = 1u << idx2; + var setting = ( flags & flag ) != 0; + if( ImGui.Checkbox( group[ idx2 ].Name, ref setting ) ) + { + flags = setting ? flags | flag : flags & ~flag; + Penumbra.CollectionManager.Current.SetModSetting( _mod.Index, idx, flags ); + } + } + + Widget.EndFramedGroup(); + } + } + } + + private void DrawChangedItemsTab() + { + if( _mod.ChangedItems.Count == 0 ) + { + return; + } + + using var tab = ImRaii.TabItem( "Changed Items" ); + if( !tab ) + { + return; + } + + using var list = ImRaii.ListBox( "##changedItems", -Vector2.One ); + if( !list ) + { + return; + } + + foreach( var (name, data) in _mod.ChangedItems ) + { + _window.DrawChangedItem( name, data ); + } + } + + private void DrawTabBar() + { + using var tabBar = ImRaii.TabBar( "##ModTabs" ); + if( !tabBar ) + { + return; + } + + DrawDescriptionTab(); + DrawSettingsTab(); + DrawChangedItemsTab(); + } + + private void DrawInheritedWarning() + { + if( _inherited ) + { + using var color = ImRaii.PushColor( ImGuiCol.Button, Colors.PressEnterWarningBg ); + var w = new Vector2( ImGui.GetContentRegionAvail().X, 0 ); + if( ImGui.Button( $"These settings are inherited from {_collection.Name}.", w ) ) + { + Penumbra.CollectionManager.Current.SetModInheritance( _mod.Index, false ); + } + } + } + + private void DrawPriorityInput() + { + var priority = _currentPriority ?? _settings.Priority; + ImGui.SetNextItemWidth( 50 * ImGuiHelpers.GlobalScale ); + if( ImGui.InputInt( "Priority", ref priority, 0, 0 ) ) + { + _currentPriority = priority; + } + + if( ImGui.IsItemDeactivatedAfterEdit() && _currentPriority.HasValue ) + { + if( _currentPriority != _settings.Priority ) + { + Penumbra.CollectionManager.Current.SetModPriority( _mod.Index, _currentPriority.Value ); + } + + _currentPriority = null; + } + } + + private void DrawRemoveSettings() + { + if( _inherited ) + { + return; + } + + ImGui.SameLine(); + if( ImGui.Button( "Remove Settings" ) ) + { + Penumbra.CollectionManager.Current.SetModInheritance( _mod.Index, true ); + } + + ImGuiUtil.HoverTooltip( "Remove current settings from this collection so that it can inherit them.\n" + + "If no inherited collection has settings for this mod, it will be disabled." ); + } + + private void DrawEnabledInput() + { + var enabled = _settings.Enabled; + if( ImGui.Checkbox( "Enabled", ref enabled ) ) + { + Penumbra.CollectionManager.Current.SetModState( _mod.Index, enabled ); + } + } + + private void DrawFilesystemPath() + { + var fullName = _leaf.FullName(); + var path = _currentSortOrderPath ?? fullName; + ImGui.SetNextItemWidth( 300 * ImGuiHelpers.GlobalScale ); + if( ImGui.InputText( "Sort Order", ref path, 256 ) ) + { + _currentSortOrderPath = path; + } + + if( ImGui.IsItemDeactivatedAfterEdit() && _currentSortOrderPath != null ) + { + if( _currentSortOrderPath != fullName ) + { + _window._penumbra.ModFileSystem.RenameAndMove( _leaf, _currentSortOrderPath ); + } + + _currentSortOrderPath = null; + } + } + + + // Draw the first info line for the mod panel, + // containing all basic meta information. + private void DrawHeaderLine() + { + DrawName(); + ImGui.SameLine(); + DrawVersion(); + ImGui.SameLine(); + DrawAuthor(); + ImGui.SameLine(); + DrawWebsite(); + } + + // Draw the mod name. + private void DrawName() + { + ImGui.Text( _mod.Name.Text ); + } + + // Draw the author of the mod, if any. + private void DrawAuthor() + { + using var group = ImRaii.Group(); + ImGuiUtil.TextColored( Colors.MetaInfoText, "by" ); + ImGui.SameLine(); + ImGui.Text( _mod.Author.IsEmpty ? "Unknown" : _mod.Author.Text ); + } + + // Draw the mod version, if any. + private void DrawVersion() + { + if( _mod.Version.Length > 0 ) + { + ImGui.Text( $"(Version {_mod.Version})" ); + } + else + { + ImGui.Dummy( Vector2.Zero ); + } + } + + // Update the last seen website and check for validity. + private void UpdateWebsite( string newWebsite ) + { + if( _lastWebsite == newWebsite ) + { + return; + } + + _lastWebsite = newWebsite; + _websiteValid = Uri.TryCreate( _lastWebsite, UriKind.Absolute, out var uriResult ) + && ( uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp ); + } + + // Draw the website source either as a button to open the site, + // if it is a valid http website, or as pure text. + private void DrawWebsite() + { + UpdateWebsite( _mod.Website ); + if( _lastWebsite.Length == 0 ) + { + ImGui.Dummy( Vector2.Zero ); + return; + } + + using var group = ImRaii.Group(); + if( _websiteValid ) + { + if( ImGui.Button( "Open Website" ) ) + { + try + { + var process = new ProcessStartInfo( _lastWebsite ) + { + UseShellExecute = true, + }; + Process.Start( process ); + } + catch + { + // ignored + } + } + + ImGuiUtil.HoverTooltip( _lastWebsite ); + } + else + { + ImGuiUtil.TextColored( Colors.MetaInfoText, "from" ); + ImGui.SameLine(); + ImGui.Text( _lastWebsite ); + } + } + } +} + public partial class ConfigWindow { public void DrawModsTab() @@ -22,7 +396,7 @@ public partial class ConfigWindow return; } - Selector.Draw( GetModSelectorSize() ); + _selector.Draw( GetModSelectorSize() ); ImGui.SameLine(); using var group = ImRaii.Group(); DrawHeaderLine(); @@ -30,17 +404,10 @@ public partial class ConfigWindow using var child = ImRaii.Child( "##ModsTabMod", -Vector2.One, true ); if( child ) { - DrawModPanel(); + _modPanel.Draw( _selector ); } } - private void DrawModPanel() - { - if( Selector.Selected == null ) - { - return; - } - } // Draw the header line that can quick switch between collections. private void DrawHeaderLine() @@ -71,8 +438,8 @@ public partial class ConfigWindow private void DrawInheritedCollectionButton( Vector2 width ) { - var noModSelected = Selector.Selected == null; - var collection = Selector.SelectedSettingCollection; + var noModSelected = _selector.Selected == null; + var collection = _selector.SelectedSettingCollection; var modInherited = collection != Penumbra.CollectionManager.Current; var (name, tt) = ( noModSelected, modInherited ) switch { diff --git a/Penumbra/UI/ConfigWindow.ResourceTab.cs b/Penumbra/UI/ConfigWindow.ResourceTab.cs index bbdef888..8d5cdfae 100644 --- a/Penumbra/UI/ConfigWindow.ResourceTab.cs +++ b/Penumbra/UI/ConfigWindow.ResourceTab.cs @@ -10,181 +10,145 @@ 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() + private class ResourceTab { - if( !DebugTabVisible ) + private readonly ConfigWindow _window; + + public ResourceTab( ConfigWindow window ) + => _window = window; + + private float _hashColumnWidth; + private float _pathColumnWidth; + private float _refsColumnWidth; + private string _resourceManagerFilter = string.Empty; + + // Draw a tab to iterate over the main resource maps and see what resources are currently loaded. + public void Draw() { - return; - } - - using var tab = ImRaii.TabItem( "Resource Manager" ); - if( !tab ) - { - 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 ) ) + if( !_window._debugTab.DebugTabVisible ) { 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() ) + using var tab = ImRaii.TabItem( "Resource Manager" ); + if( !tab ) { - 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" ) ) ) ); - } + return; } - ImGuiUtil.HoverTooltip( "Click to copy byte-wise file data to clipboard, if any." ); + // Filter for resources containing the input string. + ImGui.SetNextItemWidth( -1 ); + ImGui.InputTextWithHint( "##resourceFilter", "Filter...", ref _resourceManagerFilter, Utf8GamePath.MaxGamePathLength ); - ImGuiUtil.TextNextColumn( r->RefCount.ToString() ); - } ); - } + using var child = ImRaii.Child( "##ResourceManagerTab", -Vector2.One ); + if( !child ) + { + return; + } - // 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; + unsafe + { + ResourceLoader.IterateGraphs( DrawCategoryContainer ); + } } - using var tree = ImRaii.TreeNode( $"({( uint )category:D2}) {category} - {map->Count}###{( uint )category}" ); - if( tree ) + private unsafe void DrawResourceMap( ResourceCategory category, uint ext, StdMap< uint, Pointer< ResourceHandle > >* map ) { - SetTableWidths(); - ResourceLoader.IterateExtMap( map, ( ext, m ) => DrawResourceMap( category, ext, m ) ); + 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; } } - - // 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 index 5aede757..79280aee 100644 --- a/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs +++ b/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs @@ -1,5 +1,4 @@ using Dalamud.Interface; -using Dalamud.Interface.Components; using ImGuiNET; using OtterGui; using OtterGui.Raii; @@ -10,160 +9,164 @@ namespace Penumbra.UI; public partial class ConfigWindow { - // Sets the resource logger state when toggled, - // and the filter when entered. - private void DrawRequestedResourceLogging() + private partial class SettingsTab { - var tmp = Penumbra.Config.EnableResourceLogging; - if( ImGui.Checkbox( "##resourceLogging", ref tmp ) ) + private void DrawAdvancedSettings() { - _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 ) + if( !Penumbra.Config.ShowAdvanced || !ImGui.CollapsingHeader( "Advanced" ) ) { - _penumbra.MusicManager.DisableStreaming(); - } - else - { - _penumbra.MusicManager.EnableStreaming(); + return; } - Penumbra.ModManager.DiscoverMods(); + DrawRequestedResourceLogging(); + DrawDisableSoundStreamingBox(); + DrawEnableHttpApiBox(); + DrawEnableDebugModeBox(); + DrawEnableFullResourceLoggingBox(); + DrawReloadResourceButton(); + ImGui.NewLine(); } - 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 ) ) + // Sets the resource logger state when toggled, + // and the filter when entered. + private void DrawRequestedResourceLogging() { - if( http ) + var tmp = Penumbra.Config.EnableResourceLogging; + if( ImGui.Checkbox( "##resourceLogging", ref tmp ) ) { - _penumbra.CreateWebServer(); - } - else - { - _penumbra.ShutdownWebServer(); + _window._penumbra.ResourceLogger.SetState( tmp ); } - Penumbra.Config.EnableHttpApi = http; - Penumbra.Config.Save(); + 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, !_window._penumbra.ResourceLogger.ValidRegex ); + using var style = ImRaii.PushStyle( ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale, + !_window._penumbra.ResourceLogger.ValidRegex ); + ImGui.SetNextItemWidth( -1 ); + if( ImGui.InputTextWithHint( "##ResourceLogFilter", "Filter...", ref tmpString, Utf8GamePath.MaxGamePathLength ) ) + { + _window._penumbra.ResourceLogger.SetFilter( tmpString ); + } } - 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 ) + // 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() { - if( tmp ) + var tmp = Penumbra.Config.DisableSoundStreaming; + if( ImGui.Checkbox( "##streaming", ref tmp ) && tmp != Penumbra.Config.DisableSoundStreaming ) { - Penumbra.ResourceLoader.EnableFullLogging(); - } - else - { - Penumbra.ResourceLoader.DisableFullLogging(); + Penumbra.Config.DisableSoundStreaming = tmp; + Penumbra.Config.Save(); + if( tmp ) + { + _window._penumbra.MusicManager.DisableStreaming(); + } + else + { + _window._penumbra.MusicManager.EnableStreaming(); + } + + Penumbra.ModManager.DiscoverMods(); } - Penumbra.Config.EnableFullResourceLogging = tmp; - Penumbra.Config.Save(); + 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." ); } - 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 ) + // Creates and destroys the web server when toggled. + private void DrawEnableHttpApiBox() { - if( tmp ) + var http = Penumbra.Config.EnableHttpApi; + if( ImGui.Checkbox( "##http", ref http ) ) { - Penumbra.ResourceLoader.EnableDebug(); - } - else - { - Penumbra.ResourceLoader.DisableDebug(); + if( http ) + { + _window._penumbra.CreateWebServer(); + } + else + { + _window._penumbra.ShutdownWebServer(); + } + + Penumbra.Config.EnableHttpApi = http; + Penumbra.Config.Save(); } - Penumbra.Config.DebugMode = tmp; - Penumbra.Config.Save(); + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker( "Enable HTTP API", + "Enables other applications, e.g. Anamnesis, to use some Penumbra functions, like requesting redraws." ); } - 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" ) ) + // Should only be used for debugging. + private static void DrawEnableFullResourceLoggingBox() { - Penumbra.ResidentResources.Reload(); + 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." ); } - 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" ) ) + // Should only be used for debugging. + private static void DrawEnableDebugModeBox() { - return; + 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." ); } - DrawRequestedResourceLogging(); - DrawDisableSoundStreamingBox(); - DrawEnableHttpApiBox(); - DrawEnableDebugModeBox(); - DrawEnableFullResourceLoggingBox(); - DrawReloadResourceButton(); - ImGui.NewLine(); + 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." ); + } } } \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.SettingsTab.ModSelector.cs b/Penumbra/UI/ConfigWindow.SettingsTab.ModSelector.cs index 53ef7d84..55d9a66d 100644 --- a/Penumbra/UI/ConfigWindow.SettingsTab.ModSelector.cs +++ b/Penumbra/UI/ConfigWindow.SettingsTab.ModSelector.cs @@ -8,84 +8,90 @@ 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() + private partial class SettingsTab { - var sortMode = Penumbra.Config.SortMode; - ImGui.SetNextItemWidth( _inputTextWidth.X ); - using var combo = ImRaii.Combo( "##sortMode", sortMode.Data().Name ); - if( combo ) + private void DrawModSelectorSettings() { - foreach( var val in Enum.GetValues< SortMode >() ) + if( !ImGui.CollapsingHeader( "Mod Selector" ) ) { - 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 ); + return; } + + DrawFolderSortType(); + DrawAbsoluteSizeSelector(); + DrawRelativeSizeSelector(); + + ImGui.NewLine(); } - combo.Dispose(); - ImGuiUtil.LabeledHelpMarker( "Sort Mode", "Choose the sort mode for the mod selector in the mods tab." ); - } + // Store separately to use IsItemDeactivatedAfterEdit. + private float _absoluteSelectorSize = Penumbra.Config.ModSelectorAbsoluteSize; + private int _relativeSelectorSize = Penumbra.Config.ModSelectorScaledSize; - private void DrawAbsoluteSizeSelector() - { - if( ImGuiUtil.DragFloat( "##absoluteSize", ref _absoluteSelectorSize, _inputTextWidth.X, 1, - Configuration.Constants.MinAbsoluteSize, Configuration.Constants.MaxAbsoluteSize, "%.0f" ) - && _absoluteSelectorSize != Penumbra.Config.ModSelectorAbsoluteSize ) + // Different supported sort modes as a combo. + private void DrawFolderSortType() { - Penumbra.Config.ModSelectorAbsoluteSize = _absoluteSelectorSize; - Penumbra.Config.Save(); + var sortMode = Penumbra.Config.SortMode; + ImGui.SetNextItemWidth( _window._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; + _window._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." ); } - 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 ) ) + // Absolute size in pixels. + private void DrawAbsoluteSizeSelector() { - Penumbra.Config.ScaleModSelector = scaleModSelector; - Penumbra.Config.Save(); + if( ImGuiUtil.DragFloat( "##absoluteSize", ref _absoluteSelectorSize, _window._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." ); } - 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 ) + // Relative size toggle and percentage. + private void DrawRelativeSizeSelector() { - Penumbra.Config.ModSelectorScaledSize = _relativeSelectorSize; - Penumbra.Config.Save(); + 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, _window._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." ); } - - 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 c07d4413..0739bd5b 100644 --- a/Penumbra/UI/ConfigWindow.SettingsTab.cs +++ b/Penumbra/UI/ConfigWindow.SettingsTab.cs @@ -4,187 +4,188 @@ using System.IO; 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 OtterGui.Widgets; using Penumbra.UI.Classes; -using Penumbra.UI.Custom; namespace Penumbra.UI; public partial class ConfigWindow { - private string _settingsNewModDirectory = string.Empty; - private readonly FileDialogManager _dialogManager = new(); - private bool _dialogOpen; - - private static bool DrawPressEnterWarning( string old, float width ) + private partial class SettingsTab { - using var color = ImRaii.PushColor( ImGuiCol.Button, Colors.PressEnterWarningBg ); - var w = new Vector2( width, 0 ); - return ImGui.Button( $"Press Enter or Click Here to Save (Current Directory: {old})", w ); - } + private readonly ConfigWindow _window; - private void DrawDirectoryPickerButton() - { - if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Folder.ToIconString(), ImGui.GetFrameHeight() * Vector2.One, - "Select a directory via dialog.", false, true ) ) + public SettingsTab( ConfigWindow window ) + => _window = window; + + public void Draw() { - if( _dialogOpen ) + using var tab = ImRaii.TabItem( "Settings" ); + if( !tab ) { - _dialogManager.Reset(); - _dialogOpen = false; + return; } - else + + using var child = ImRaii.Child( "##SettingsTab", -Vector2.One, false ); + if( !child ) { - //_dialogManager.OpenFolderDialog( "Choose Mod Directory", ( b, s ) => - //{ - // _newModDirectory = b ? s : _newModDirectory; - // _dialogOpen = false; - //}, _newModDirectory, false); - _dialogOpen = true; + return; + } + + DrawEnabledBox(); + DrawShowAdvancedBox(); + ImGui.NewLine(); + DrawRootFolder(); + DrawRediscoverButton(); + ImGui.NewLine(); + + DrawModSelectorSettings(); + DrawColorSettings(); + DrawAdvancedSettings(); + } + + private string? _settingsNewModDirectory; + private readonly FileDialogManager _dialogManager = new(); + private bool _dialogOpen; + + private static bool DrawPressEnterWarning( string old, float width ) + { + using var color = ImRaii.PushColor( ImGuiCol.Button, Colors.PressEnterWarningBg ); + 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 + { + // TODO + //_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 ); + var ret = ImGui.Button( "Open Directory" ); + ImGuiUtil.HoverTooltip( "Open this directory in your configured file explorer." ); + if( ret && condition && Directory.Exists( directory.FullName ) ) + { + Process.Start( new ProcessStartInfo( directory.FullName ) + { + UseShellExecute = true, + } ); } } - _dialogManager.Draw(); - } - - private static void DrawOpenDirectoryButton( int id, DirectoryInfo directory, bool condition ) - { - using var _ = ImRaii.PushId( id ); - var ret = ImGui.Button( "Open Directory" ); - ImGuiUtil.HoverTooltip( "Open this directory in your configured file explorer." ); - if( ret && condition && Directory.Exists( directory.FullName ) ) + private void DrawRootFolder() { - Process.Start( new ProcessStartInfo( directory.FullName ) + _settingsNewModDirectory ??= Penumbra.Config.ModDirectory; + + var spacing = 3 * ImGuiHelpers.GlobalScale; + using var group = ImRaii.Group(); + ImGui.SetNextItemWidth( _window._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(); + 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." ); + group.Dispose(); + ImGui.SameLine(); + var pos = ImGui.GetCursorPosX(); + ImGui.NewLine(); + + if( Penumbra.Config.ModDirectory == _settingsNewModDirectory || _settingsNewModDirectory.Length == 0 ) { - UseShellExecute = true, - } ); - } - } + return; + } - private void DrawRootFolder() - { - // 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(); - 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." ); - group.Dispose(); - ImGui.SameLine(); - var pos = ImGui.GetCursorPosX(); - ImGui.NewLine(); - - if( Penumbra.Config.ModDirectory == _settingsNewModDirectory || _settingsNewModDirectory.Length == 0 ) - { - return; - } - - if( save || DrawPressEnterWarning( Penumbra.Config.ModDirectory, pos ) ) - { - Penumbra.ModManager.DiscoverMods( _settingsNewModDirectory ); - } - } - - - private static void DrawRediscoverButton() - { - DrawOpenDirectoryButton( 0, Penumbra.ModManager.BasePath, Penumbra.ModManager.Valid ); - ImGui.SameLine(); - if( ImGui.Button( "Rediscover Mods" ) ) - { - Penumbra.ModManager.DiscoverMods(); - } - - ImGuiUtil.HoverTooltip( "Force Penumbra to completely re-scan your root directory as if it was restarted." ); - } - - private void DrawEnabledBox() - { - var enabled = Penumbra.Config.EnableMods; - if( ImGui.Checkbox( "Enable Mods", ref enabled ) ) - { - _penumbra.SetEnabled( enabled ); - } - } - - private static void DrawShowAdvancedBox() - { - var showAdvanced = Penumbra.Config.ShowAdvanced; - if( ImGui.Checkbox( "##showAdvanced", ref showAdvanced ) ) - { - Penumbra.Config.ShowAdvanced = showAdvanced; - Penumbra.Config.Save(); - } - - ImGui.SameLine(); - 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 static void DrawColorSettings() - { - if( !ImGui.CollapsingHeader( "Colors" ) ) - { - return; - } - - 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( Widget.ColorPicker( name, description, currentColor, c => Penumbra.Config.Colors[ color ] = c, defaultColor ) ) + if( save || DrawPressEnterWarning( Penumbra.Config.ModDirectory, pos ) ) { + Penumbra.ModManager.DiscoverMods( _settingsNewModDirectory ); + } + } + + + private static void DrawRediscoverButton() + { + DrawOpenDirectoryButton( 0, Penumbra.ModManager.BasePath, Penumbra.ModManager.Valid ); + ImGui.SameLine(); + if( ImGui.Button( "Rediscover Mods" ) ) + { + Penumbra.ModManager.DiscoverMods(); + } + + ImGuiUtil.HoverTooltip( "Force Penumbra to completely re-scan your root directory as if it was restarted." ); + } + + private void DrawEnabledBox() + { + var enabled = Penumbra.Config.EnableMods; + if( ImGui.Checkbox( "Enable Mods", ref enabled ) ) + { + _window._penumbra.SetEnabled( enabled ); + } + } + + private static void DrawShowAdvancedBox() + { + var showAdvanced = Penumbra.Config.ShowAdvanced; + if( ImGui.Checkbox( "##showAdvanced", ref showAdvanced ) ) + { + Penumbra.Config.ShowAdvanced = showAdvanced; Penumbra.Config.Save(); } + + ImGui.SameLine(); + 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." ); } - ImGui.NewLine(); - } - - - public void DrawSettingsTab() - { - using var tab = ImRaii.TabItem( "Settings" ); - if( !tab ) + private static void DrawColorSettings() { - return; + if( !ImGui.CollapsingHeader( "Colors" ) ) + { + return; + } + + 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( Widget.ColorPicker( name, description, currentColor, c => Penumbra.Config.Colors[ color ] = c, defaultColor ) ) + { + Penumbra.Config.Save(); + } + } + + ImGui.NewLine(); } - - using var child = ImRaii.Child( "##SettingsTab", -Vector2.One, false ); - if( !child ) - { - return; - } - - DrawEnabledBox(); - DrawShowAdvancedBox(); - ImGui.NewLine(); - DrawRootFolder(); - DrawRediscoverButton(); - ImGui.NewLine(); - - DrawModSelectorSettings(); - DrawColorSettings(); - DrawAdvancedSettings(); } } \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.cs b/Penumbra/UI/ConfigWindow.cs index 338fa511..59b03525 100644 --- a/Penumbra/UI/ConfigWindow.cs +++ b/Penumbra/UI/ConfigWindow.cs @@ -14,17 +14,30 @@ namespace Penumbra.UI; public sealed partial class ConfigWindow : Window, IDisposable { private readonly Penumbra _penumbra; - public readonly ModFileSystemSelector Selector; + private readonly SettingsTab _settingsTab; + private readonly ModFileSystemSelector _selector; + private readonly ModPanel _modPanel; + private readonly CollectionsTab _collectionsTab; + private readonly EffectiveTab _effectiveTab; + private readonly DebugTab _debugTab; + private readonly ResourceTab _resourceTab; public ConfigWindow( Penumbra penumbra ) : base( GetLabel() ) { - _penumbra = penumbra; - Selector = new ModFileSystemSelector( _penumbra.ModFileSystem, new HashSet< Mod2 >() ); // TODO - Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true; + _penumbra = penumbra; + _settingsTab = new SettingsTab( this ); + _selector = new ModFileSystemSelector( _penumbra.ModFileSystem, new HashSet< Mod2 >() ); // TODO + _modPanel = new ModPanel( this ); + _collectionsTab = new CollectionsTab( this ); + _effectiveTab = new EffectiveTab(); + _debugTab = new DebugTab( this ); + _resourceTab = new ResourceTab( this ); + + Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true; Dalamud.PluginInterface.UiBuilder.DisableCutsceneUiHide = true; - Dalamud.PluginInterface.UiBuilder.DisableUserUiHide = true; - RespectCloseHotkey = true; + Dalamud.PluginInterface.UiBuilder.DisableUserUiHide = true; + RespectCloseHotkey = true; SizeConstraints = new WindowSizeConstraints() { MinimumSize = new Vector2( 800, 600 ), @@ -36,18 +49,18 @@ public sealed partial class ConfigWindow : Window, IDisposable { using var bar = ImRaii.TabBar( string.Empty, ImGuiTabBarFlags.NoTooltip ); SetupSizes(); - DrawSettingsTab(); + _settingsTab.Draw(); DrawModsTab(); - DrawCollectionsTab(); + _collectionsTab.Draw(); DrawChangedItemTab(); - DrawEffectiveChangesTab(); - DrawDebugTab(); - DrawResourceManagerTab(); + _effectiveTab.Draw(); + _debugTab.Draw(); + _resourceTab.Draw(); } public void Dispose() { - Selector.Dispose(); + _selector.Dispose(); } private static string GetLabel() @@ -55,10 +68,12 @@ public sealed partial class ConfigWindow : Window, IDisposable ? "Penumbra###PenumbraConfigWindow" : $"Penumbra v{Penumbra.Version}###PenumbraConfigWindow"; + private Vector2 _defaultSpace; private Vector2 _inputTextWidth; private void SetupSizes() { - _inputTextWidth = new Vector2( 350f * ImGuiHelpers.GlobalScale, 0 ); + _defaultSpace = new Vector2( 0, 10 * ImGuiHelpers.GlobalScale ); + _inputTextWidth = new Vector2( 350f * ImGuiHelpers.GlobalScale, 0 ); } } \ No newline at end of file