Even almoster...

This commit is contained in:
Ottermandias 2022-04-20 11:03:19 +02:00
parent 65bd1d1b52
commit 8dd681bdda
19 changed files with 2625 additions and 1865 deletions

@ -1 +1 @@
Subproject commit 4cc1024096905b0b20c1559c534b1dd3fe7b25ad
Subproject commit a832fb6ca5e7c6cb4e35a51a08d30d1800f405da

View file

@ -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 );

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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

View file

@ -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;
}

View file

@ -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 );
}
}

View 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 ) );
}
}
}

View file

@ -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();
}
}
}

View file

@ -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 );
}
}

View file

@ -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 );
}
}
}

View file

@ -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

View file

@ -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
{

View file

@ -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 );
// }
// }
// } );
//}
}

View file

@ -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." );
}
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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 );
}
}