mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Even almoster...
This commit is contained in:
parent
65bd1d1b52
commit
8dd681bdda
19 changed files with 2625 additions and 1865 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit 4cc1024096905b0b20c1559c534b1dd3fe7b25ad
|
||||
Subproject commit a832fb6ca5e7c6cb4e35a51a08d30d1800f405da
|
||||
|
|
@ -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 );
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
288
Penumbra/UI/ConfigWindow.CollectionsTab.Inheritance.cs
Normal file
288
Penumbra/UI/ConfigWindow.CollectionsTab.Inheritance.cs
Normal file
|
|
@ -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 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 )
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
// }
|
||||
// }
|
||||
// } );
|
||||
//}
|
||||
}
|
||||
|
|
@ -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." );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue