Change folder handling and introduce drag & drop for folders

This commit is contained in:
Ottermandias 2021-08-08 12:43:12 +02:00
parent ec99887387
commit 2532e73f9d
21 changed files with 1690 additions and 651 deletions

View file

@ -29,6 +29,9 @@ namespace Penumbra
public string CurrentCollection { get; set; } = "Default"; public string CurrentCollection { get; set; } = "Default";
public string DefaultCollection { get; set; } = "Default"; public string DefaultCollection { get; set; } = "Default";
public string ForcedCollection { get; set; } = ""; public string ForcedCollection { get; set; } = "";
public bool SortFoldersFirst { get; set; } = false;
public Dictionary< string, string > CharacterCollections { get; set; } = new(); public Dictionary< string, string > CharacterCollections { get; set; } = new();
public Dictionary< string, string > ModSortOrder { get; set; } = new(); public Dictionary< string, string > ModSortOrder { get; set; } = new();

View file

@ -1,11 +1,51 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Dalamud.Plugin; using Dalamud.Plugin;
using Penumbra.Mods;
using Penumbra.Util; using Penumbra.Util;
namespace Penumbra.Mod namespace Penumbra.Mod
{ {
public struct SortOrder : IComparable<SortOrder>
{
public ModFolder ParentFolder { get; set; }
private string _sortOrderName;
public string SortOrderName
{
get => _sortOrderName;
set => _sortOrderName = value.Replace( '/', '\\' );
}
public string SortOrderPath
=> ParentFolder.FullName;
public string FullName
{
get
{
var path = SortOrderPath;
return path.Any() ? $"{path}/{SortOrderName}" : SortOrderName;
}
}
public SortOrder( ModFolder parentFolder, string name )
{
ParentFolder = parentFolder;
_sortOrderName = name.Replace( '/', '\\' );
}
public string FullPath
=> SortOrderPath.Any() ? $"{SortOrderPath}/{SortOrderName}" : SortOrderName;
public int CompareTo( SortOrder other )
=> string.Compare(FullPath, other.FullPath, StringComparison.InvariantCultureIgnoreCase );
}
// ModData contains all permanent information about a mod, // ModData contains all permanent information about a mod,
// and is independent of collections or settings. // and is independent of collections or settings.
// It only changes when the user actively changes the mod or their filesystem. // It only changes when the user actively changes the mod or their filesystem.
@ -14,17 +54,22 @@ namespace Penumbra.Mod
public DirectoryInfo BasePath; public DirectoryInfo BasePath;
public ModMeta Meta; public ModMeta Meta;
public ModResources Resources; public ModResources Resources;
public string SortOrder;
public SortOrder SortOrder;
public SortedList< string, object? > ChangedItems { get; } = new(); public SortedList< string, object? > ChangedItems { get; } = new();
public string LowerChangedItemsString { get; private set; } = string.Empty;
public FileInfo MetaFile { get; set; } public FileInfo MetaFile { get; set; }
private ModData( DirectoryInfo basePath, ModMeta meta, ModResources resources ) private ModData( ModFolder parentFolder, DirectoryInfo basePath, ModMeta meta, ModResources resources )
{ {
BasePath = basePath; BasePath = basePath;
Meta = meta; Meta = meta;
Resources = resources; Resources = resources;
MetaFile = MetaFileInfo( basePath ); MetaFile = MetaFileInfo( basePath );
SortOrder = meta.Name.Replace( '/', '\\' ); SortOrder = new SortOrder( parentFolder, Meta.Name );
SortOrder.ParentFolder.AddMod( this );
ComputeChangedItems(); ComputeChangedItems();
} }
@ -44,12 +89,14 @@ namespace Penumbra.Mod
{ {
identifier.Identify( ChangedItems, path ); identifier.Identify( ChangedItems, path );
} }
LowerChangedItemsString = string.Join( "\0", ChangedItems.Keys.Select( k => k.ToLowerInvariant() ) );
} }
public static FileInfo MetaFileInfo( DirectoryInfo basePath ) public static FileInfo MetaFileInfo( DirectoryInfo basePath )
=> new( Path.Combine( basePath.FullName, "meta.json" ) ); => new( Path.Combine( basePath.FullName, "meta.json" ) );
public static ModData? LoadMod( DirectoryInfo basePath ) public static ModData? LoadMod( ModFolder parentFolder, DirectoryInfo basePath )
{ {
basePath.Refresh(); basePath.Refresh();
if( !basePath.Exists ) if( !basePath.Exists )
@ -77,10 +124,13 @@ namespace Penumbra.Mod
data.SetManipulations( meta, basePath ); data.SetManipulations( meta, basePath );
} }
return new ModData( basePath, meta, data ); return new ModData( parentFolder, basePath, meta, data );
} }
public void SaveMeta() public void SaveMeta()
=> Meta.SaveToFile( MetaFile ); => Meta.SaveToFile( MetaFile );
public override string ToString()
=> SortOrder.FullPath;
} }
} }

View file

@ -13,8 +13,37 @@ namespace Penumbra.Mod
public class ModMeta public class ModMeta
{ {
public uint FileVersion { get; set; } public uint FileVersion { get; set; }
public string Name { get; set; } = "Mod";
public string Author { get; set; } = ""; public string Name
{
get => _name;
set
{
_name = value;
LowerName = value.ToLowerInvariant();
}
}
private string _name = "Mod";
[JsonIgnore]
public string LowerName { get; private set; } = "mod";
private string _author = "";
public string Author
{
get => _author;
set
{
_author = value;
LowerAuthor = value.ToLowerInvariant();
}
}
[JsonIgnore]
public string LowerAuthor { get; private set; } = "";
public string Description { get; set; } = ""; public string Description { get; set; } = "";
public string Version { get; set; } = ""; public string Version { get; set; } = "";
public string Website { get; set; } = ""; public string Website { get; set; } = "";
@ -67,7 +96,7 @@ namespace Penumbra.Mod
if( meta != null ) if( meta != null )
{ {
meta.FileHash = text.GetHashCode(); meta.FileHash = text.GetHashCode();
meta.HasGroupsWithConfig = meta.Groups.Values.Any( g => g.SelectionType == SelectType.Multi || g.Options.Count > 1 ); meta.RefreshHasGroupsWithConfig();
} }
return meta; return meta;
@ -79,6 +108,14 @@ namespace Penumbra.Mod
} }
} }
public bool RefreshHasGroupsWithConfig()
{
var oldValue = HasGroupsWithConfig;
HasGroupsWithConfig = Groups.Values.Any( g => g.Options.Count > 1 || g.SelectionType == SelectType.Multi && g.Options.Count == 1 );
return oldValue != HasGroupsWithConfig;
}
public void SaveToFile( FileInfo filePath ) public void SaveToFile( FileInfo filePath )
{ {
try try

View file

@ -38,7 +38,7 @@ namespace Penumbra.Mods
{ {
foreach( var collection in Collections.Values.Where( c => c.Cache != null ) ) foreach( var collection in Collections.Values.Where( c => c.Cache != null ) )
{ {
collection.CreateCache( _manager.BasePath, _manager.Mods, false ); collection.CreateCache( _manager.BasePath, _manager.StructuredMods.AllMods(_manager.Config.SortFoldersFirst) );
} }
} }
@ -57,10 +57,6 @@ namespace Penumbra.Mods
if( metaChanges ) if( metaChanges )
{ {
collection.UpdateSetting( mod ); collection.UpdateSetting( mod );
if( nameChange )
{
collection.Cache?.SortMods();
}
} }
if( fileChanges.HasFlag( ResourceChange.Files ) if( fileChanges.HasFlag( ResourceChange.Files )
@ -143,7 +139,7 @@ namespace Penumbra.Mods
{ {
if( collection.Cache == null && collection.Name != string.Empty ) if( collection.Cache == null && collection.Name != string.Empty )
{ {
collection.CreateCache( _manager.BasePath, _manager.Mods, false ); collection.CreateCache( _manager.BasePath, _manager.StructuredMods.AllMods(_manager.Config.SortFoldersFirst) );
} }
} }

View file

@ -35,60 +35,55 @@ namespace Penumbra.Mods
Settings = settings.ToDictionary( kvp => kvp.Key, kvp => kvp.Value.DeepCopy() ); Settings = settings.ToDictionary( kvp => kvp.Key, kvp => kvp.Value.DeepCopy() );
} }
private bool CleanUnavailableSettings( Dictionary< string, ModData > data ) public Mod.Mod GetMod( ModData mod )
{ {
if( Settings.Count <= data.Count ) if( Settings.TryGetValue( mod.BasePath.Name, out var settings ) )
{ {
return false; return new Mod.Mod( settings, mod );
} }
List< string > removeList = new(); var newSettings = ModSettings.DefaultSettings( mod.Meta );
foreach( var settingKvp in Settings ) Settings.Add( mod.BasePath.Name, newSettings );
{ Save( Service< DalamudPluginInterface >.Get() );
if( !data.ContainsKey( settingKvp.Key ) ) return new Mod.Mod( newSettings, mod );
{
removeList.Add( settingKvp.Key );
}
} }
private bool CleanUnavailableSettings( Dictionary< string, ModData > data )
{
var removeList = Settings.Where( settingKvp => !data.ContainsKey( settingKvp.Key ) ).ToArray();
foreach( var s in removeList ) foreach( var s in removeList )
{ {
Settings.Remove( s ); Settings.Remove( s.Key );
} }
return removeList.Count > 0; return removeList.Length > 0;
} }
public void CreateCache( DirectoryInfo modDirectory, Dictionary< string, ModData > data, bool cleanUnavailable = false ) public void CreateCache( DirectoryInfo modDirectory, IEnumerable< ModData > data )
{ {
Cache = new ModCollectionCache( Name, modDirectory ); Cache = new ModCollectionCache( Name, modDirectory );
var changedSettings = false; var changedSettings = false;
foreach( var modKvp in data ) foreach( var mod in data )
{ {
if( Settings.TryGetValue( modKvp.Key, out var settings ) ) if( Settings.TryGetValue( mod.BasePath.Name, out var settings ) )
{ {
Cache.AvailableMods.Add( new Mod.Mod( settings, modKvp.Value ) ); Cache.AddMod( settings, mod );
} }
else else
{ {
changedSettings = true; changedSettings = true;
var newSettings = ModSettings.DefaultSettings( modKvp.Value.Meta ); var newSettings = ModSettings.DefaultSettings( mod.Meta );
Settings.Add( modKvp.Key, newSettings ); Settings.Add( mod.BasePath.Name, newSettings );
Cache.AvailableMods.Add( new Mod.Mod( newSettings, modKvp.Value ) ); Cache.AddMod( newSettings, mod );
} }
} }
if( cleanUnavailable )
{
changedSettings |= CleanUnavailableSettings( data );
}
if( changedSettings ) if( changedSettings )
{ {
Save( Service< DalamudPluginInterface >.Get() ); Save( Service< DalamudPluginInterface >.Get() );
} }
Cache.SortMods();
CalculateEffectiveFileList( modDirectory, true, false ); CalculateEffectiveFileList( modDirectory, true, false );
} }
@ -129,6 +124,8 @@ namespace Penumbra.Mods
public void CalculateEffectiveFileList( DirectoryInfo modDir, bool withMetaManipulations, bool activeCollection ) public void CalculateEffectiveFileList( DirectoryInfo modDir, bool withMetaManipulations, bool activeCollection )
{ {
PluginLog.Verbose( "Recalculating effective file list for {CollectionName} [{WithMetaManipulations}] [{IsActiveCollection}]", Name,
withMetaManipulations, activeCollection );
Cache ??= new ModCollectionCache( Name, modDir ); Cache ??= new ModCollectionCache( Name, modDir );
UpdateSettings(); UpdateSettings();
Cache.CalculateEffectiveFileList(); Cache.CalculateEffectiveFileList();
@ -233,14 +230,10 @@ namespace Penumbra.Mods
return; return;
} }
if( Settings.TryGetValue( data.BasePath.Name, out var settings ) ) Cache.AddMod( Settings.TryGetValue( data.BasePath.Name, out var settings )
{ ? settings
Cache.AddMod( settings, data ); : ModSettings.DefaultSettings( data.Meta ),
} data );
else
{
Cache.AddMod( ModSettings.DefaultSettings( data.Meta ), data );
}
} }
public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath ) public string? ResolveSwappedOrReplacementPath( GamePath gameResourcePath )

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Lumina.Data.Parsing;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Meta; using Penumbra.Meta;
using Penumbra.Mod; using Penumbra.Mod;
@ -21,12 +22,6 @@ namespace Penumbra.Mods
public ModCollectionCache( string collectionName, DirectoryInfo modDir ) public ModCollectionCache( string collectionName, DirectoryInfo modDir )
=> MetaManipulations = new MetaManager( collectionName, ResolvedFiles, modDir ); => MetaManipulations = new MetaManager( collectionName, ResolvedFiles, modDir );
public void SortMods()
{
AvailableMods.Sort( ( m1, m2 )
=> string.Compare( m1.Data.SortOrder, m2.Data.SortOrder, StringComparison.InvariantCultureIgnoreCase ) );
}
private void AddFiles( Dictionary< GamePath, Mod.Mod > registeredFiles, Mod.Mod mod ) private void AddFiles( Dictionary< GamePath, Mod.Mod > registeredFiles, Mod.Mod mod )
{ {
foreach( var file in mod.Data.Resources.ModFiles ) foreach( var file in mod.Data.Resources.ModFiles )
@ -82,8 +77,7 @@ namespace Penumbra.Mods
{ {
MetaManipulations.Reset( false ); MetaManipulations.Reset( false );
foreach( var mod in AvailableMods.Where( m => m.Settings.Enabled && m.Data.Resources.MetaManipulations.Count > 0 ) foreach( var mod in AvailableMods.Where( m => m.Settings.Enabled && m.Data.Resources.MetaManipulations.Count > 0 ) )
.OrderByDescending( m => m.Settings.Priority ) )
{ {
mod.Cache.ClearMetaConflicts(); mod.Cache.ClearMetaConflicts();
AddManipulations( mod ); AddManipulations( mod );
@ -98,7 +92,7 @@ namespace Penumbra.Mods
SwappedFiles.Clear(); SwappedFiles.Clear();
var registeredFiles = new Dictionary< GamePath, Mod.Mod >(); var registeredFiles = new Dictionary< GamePath, Mod.Mod >();
foreach( var mod in AvailableMods.Where( m => m.Settings.Enabled ).OrderByDescending( m => m.Settings.Priority ) ) foreach( var mod in AvailableMods.Where( m => m.Settings.Enabled ) )
{ {
mod.Cache.ClearFileConflicts(); mod.Cache.ClearFileConflicts();
AddFiles( registeredFiles, mod ); AddFiles( registeredFiles, mod );
@ -131,10 +125,20 @@ namespace Penumbra.Mods
} }
} }
private class PriorityComparer : IComparer< Mod.Mod >
{
public int Compare( Mod.Mod x, Mod.Mod y )
=> x.Settings.Priority.CompareTo( y.Settings.Priority );
}
private static readonly PriorityComparer Comparer = new();
public void AddMod( ModSettings settings, ModData data ) public void AddMod( ModSettings settings, ModData data )
{ {
AvailableMods.Add( new Mod.Mod( settings, data ) ); var newMod = new Mod.Mod( settings, data );
SortMods(); var idx = AvailableMods.BinarySearch( newMod, Comparer );
idx = idx < 0 ? ~idx : idx;
AvailableMods.Insert( idx, newMod );
if( settings.Enabled ) if( settings.Enabled )
{ {
CalculateEffectiveFileList(); CalculateEffectiveFileList();

View file

@ -0,0 +1,251 @@
using System;
using System.Linq;
using Penumbra.Mod;
using Penumbra.Util;
namespace Penumbra.Mods
{
public static partial class ModFileSystem
{
// The root folder that should be used as the base for all structured mods.
public static ModFolder Root = ModFolder.CreateRoot();
// Find a specific mod folder by its path from Root.
// Returns true if the folder was found, and false if not.
// The out parameter will contain the furthest existing folder.
public static bool Find( string path, out ModFolder folder )
{
var split = path.Split( new[] { '/' }, StringSplitOptions.RemoveEmptyEntries );
folder = Root;
foreach( var part in split )
{
if( !folder.FindSubFolder( part, out folder ) )
{
return false;
}
}
return true;
}
// Rename the SortOrderName of a single mod. Slashes are replaced by Backslashes.
// Saves and returns true if anything changed.
public static bool Rename( this ModData mod, string newName )
{
if( RenameNoSave( mod, newName ) )
{
SaveMod( mod );
return true;
}
return false;
}
// Rename the target folder, merging it and its subfolders if the new name already exists.
// Saves all mods manipulated thus, and returns true if anything changed.
public static bool Rename( this ModFolder target, string newName )
{
if( RenameNoSave( target, newName ) )
{
SaveModChildren( target );
return true;
}
return false;
}
// Move a single mod to the target folder.
// Returns true and saves if anything changed.
public static bool Move( this ModData mod, ModFolder target )
{
if( MoveNoSave( mod, target ) )
{
SaveMod( mod );
return true;
}
return false;
}
// Move a mod to the filesystem location specified by sortOrder and rename its SortOrderName.
// Creates all necessary Subfolders.
// Does NOT save.
public static void Move( this ModData mod, string sortOrder )
{
var split = sortOrder.Split( new[] { '/' }, StringSplitOptions.RemoveEmptyEntries );
var folder = Root;
for( var i = 0; i < split.Length - 1; ++i )
{
folder = folder.FindOrCreateSubFolder( split[ i ] ).Item1;
}
MoveNoSave( mod, folder );
RenameNoSave( mod, split.Last() );
}
// Moves folder to target.
// If an identically named subfolder of target already exists, merges instead.
// Root is not movable.
public static bool Move( this ModFolder folder, ModFolder target )
{
if( MoveNoSave( folder, target ) )
{
SaveModChildren( target );
return true;
}
return false;
}
// Merge source with target, moving all direct mod children of source to target,
// and moving all subfolders of source to target, or merging them with targets subfolders if they exist.
// Returns true and saves if anything changed.
public static bool Merge( this ModFolder source, ModFolder target )
{
if( MergeNoSave( source, target ) )
{
SaveModChildren( target );
return true;
}
return false;
}
}
// Internal stuff.
public static partial class ModFileSystem
{
// Reset all sort orders for all descendants of the given folder.
// Assumes that it is not called on Root, and thus does not remove unnecessary SortOrder entries.
private static void SaveModChildren( ModFolder target )
{
var config = Service< Configuration >.Get();
foreach( var mod in target.AllMods( true ) )
{
config.ModSortOrder[ mod.BasePath.Name ] = mod.SortOrder.FullName;
}
config.Save();
}
// Sets and saves the sort order of a single mod, removing the entry if it is unnecessary.
private static void SaveMod( ModData mod )
{
var config = Service< Configuration >.Get();
if( ReferenceEquals( mod.SortOrder.ParentFolder, Root )
&& string.Equals( mod.SortOrder.SortOrderName, mod.Meta.Name.Replace( '/', '\\' ), StringComparison.InvariantCultureIgnoreCase ) )
{
config.ModSortOrder.Remove( mod.BasePath.Name );
}
else
{
config.ModSortOrder[ mod.BasePath.Name ] = mod.SortOrder.FullName;
}
config.Save();
}
private static bool RenameNoSave( this ModFolder target, string newName )
{
if( ReferenceEquals( target, Root ) )
{
throw new InvalidOperationException( "Can not rename root." );
}
newName = newName.Replace( '/', '\\' );
if( target.Name == newName )
{
return false;
}
if( target.Parent!.FindSubFolder( newName, out var preExisting ) )
{
MergeNoSave( target, preExisting );
}
else
{
var parent = target.Parent;
parent.RemoveFolderIgnoreEmpty( target );
target.Name = newName;
parent.FindOrAddSubFolder( target );
}
return true;
}
private static bool RenameNoSave( ModData mod, string newName )
{
newName = newName.Replace( '/', '\\' );
if( mod.SortOrder.SortOrderName == newName )
{
return false;
}
mod.SortOrder.ParentFolder.RemoveModIgnoreEmpty( mod );
mod.SortOrder = new SortOrder( mod.SortOrder.ParentFolder, newName );
mod.SortOrder.ParentFolder.AddMod( mod );
return true;
}
private static bool MoveNoSave( ModData mod, ModFolder target )
{
var oldParent = mod.SortOrder.ParentFolder;
if( ReferenceEquals( target, oldParent ) )
{
return false;
}
oldParent.RemoveMod( mod );
mod.SortOrder = new SortOrder( target, mod.SortOrder.SortOrderName );
target.AddMod( mod );
return true;
}
private static bool MergeNoSave( ModFolder source, ModFolder target )
{
if( ReferenceEquals( source, target ) )
{
return false;
}
var any = false;
while( source.SubFolders.Count > 0 )
{
any |= MoveNoSave( source.SubFolders.First(), target );
}
while( source.Mods.Count > 0 )
{
any |= MoveNoSave( source.Mods.First(), target );
}
source.Parent?.RemoveSubFolder( source );
return any || source.Parent != null;
}
private static bool MoveNoSave( ModFolder folder, ModFolder target )
{
// Moving a folder into itself is not permitted.
if( ReferenceEquals( folder, target ) )
{
return false;
}
if( ReferenceEquals( target, folder.Parent! ) )
{
return false;
}
folder.Parent!.RemoveSubFolder( folder );
var subFolderIdx = target.FindOrAddSubFolder( folder );
if( subFolderIdx > 0 )
{
var main = target.SubFolders[ subFolderIdx ];
MergeNoSave( folder, main );
}
return true;
}
}
}

243
Penumbra/Mods/ModFolder.cs Normal file
View file

@ -0,0 +1,243 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Penumbra.Mod;
namespace Penumbra.Mods
{
public partial class ModFolder
{
public ModFolder? Parent;
public string FullName
{
get
{
var parentPath = Parent?.FullName ?? string.Empty;
return parentPath.Any() ? $"{parentPath}/{Name}" : Name;
}
}
private string _name = string.Empty;
public string Name
{
get => _name;
set => _name = value.Replace( '/', '\\' );
}
public List< ModFolder > SubFolders { get; } = new();
public List< ModData > Mods { get; } = new();
public ModFolder( ModFolder parent, string name )
{
Parent = parent;
Name = name;
}
public override string ToString()
=> FullName;
public int TotalDescendantMods()
=> Mods.Count + SubFolders.Sum( f => f.TotalDescendantMods() );
public int TotalDescendantFolders()
=> SubFolders.Sum( f => f.TotalDescendantFolders() );
// Return all descendant mods in the specified order.
public IEnumerable< ModData > AllMods( bool foldersFirst )
{
if( foldersFirst )
{
return SubFolders.SelectMany( f => f.AllMods( foldersFirst ) ).Concat( Mods );
}
return GetSortedEnumerator().SelectMany( f =>
{
if( f is ModFolder folder )
{
return folder.AllMods( false );
}
return new[] { ( ModData )f };
} );
}
// Return all descendant subfolders.
public IEnumerable< ModFolder > AllFolders()
=> SubFolders.SelectMany( f => f.AllFolders() ).Prepend( this );
// Iterate through all descendants in the specified order, returning subfolders as well as mods.
public IEnumerable< object > GetItems( bool foldersFirst )
=> foldersFirst ? SubFolders.Cast< object >().Concat( Mods ) : GetSortedEnumerator();
// Find a subfolder by name. Returns true and sets folder to it if it exists.
public bool FindSubFolder( string name, out ModFolder folder )
{
var subFolder = new ModFolder( this, name );
var idx = SubFolders.BinarySearch( subFolder, FolderComparer );
folder = idx >= 0 ? SubFolders[ idx ] : this;
return idx >= 0;
}
// Checks if an equivalent subfolder as folder already exists and returns its index.
// If it does not exist, inserts folder as a subfolder and returns the new index.
// Also sets this as folders parent.
public int FindOrAddSubFolder( ModFolder folder )
{
var idx = SubFolders.BinarySearch( folder, FolderComparer );
if( idx >= 0 )
{
return idx;
}
idx = ~idx;
SubFolders.Insert( idx, folder );
folder.Parent = this;
return idx;
}
// Checks if a subfolder with the given name already exists and returns it and its index.
// If it does not exists, creates and inserts it and returns the new subfolder and its index.
public (ModFolder, int) FindOrCreateSubFolder( string name )
{
var subFolder = new ModFolder( this, name );
var idx = FindOrAddSubFolder( subFolder );
return ( SubFolders[ idx ], idx );
}
// Remove folder as a subfolder if it exists.
// If this folder is empty afterwards, remove it from its parent.
public void RemoveSubFolder( ModFolder folder )
{
RemoveFolderIgnoreEmpty( folder );
CheckEmpty();
}
// Add the given mod as a child, if it is not already a child.
// Returns the index of the found or inserted mod.
public int AddMod( ModData mod )
{
var idx = Mods.BinarySearch( mod, ModComparer );
if( idx >= 0 )
{
return idx;
}
idx = ~idx;
Mods.Insert( idx, mod );
return idx;
}
// Remove mod as a child if it exists.
// If this folder is empty afterwards, remove it from its parent.
public void RemoveMod( ModData mod )
{
RemoveModIgnoreEmpty( mod );
CheckEmpty();
}
}
// Internals
public partial class ModFolder
{
// Create a Root folder without parent.
internal static ModFolder CreateRoot()
=> new( null!, string.Empty );
internal class ModFolderComparer : IComparer< ModFolder >
{
// Compare only the direct folder names since this is only used inside an enumeration of subfolders of one folder.
public int Compare( ModFolder x, ModFolder y )
=> ReferenceEquals( x, y )
? 0
: string.Compare( x.Name, y.Name, StringComparison.InvariantCultureIgnoreCase );
}
internal class ModDataComparer : IComparer< ModData >
{
// Compare only the direct SortOrderNames since this is only used inside an enumeration of direct mod children of one folder.
// Since mod SortOrderNames do not have to be unique inside a folder, also compare their BasePaths (and thus their identity) if necessary.
public int Compare( ModData x, ModData y )
{
if( ReferenceEquals( x, y ) )
{
return 0;
}
var cmp = string.Compare( x.SortOrder.SortOrderName, y.SortOrder.SortOrderName, StringComparison.InvariantCultureIgnoreCase );
if( cmp != 0 )
{
return cmp;
}
return string.Compare( x.BasePath.Name, y.BasePath.Name, StringComparison.InvariantCulture );
}
}
private static readonly ModFolderComparer FolderComparer = new();
private static readonly ModDataComparer ModComparer = new();
// Get an enumerator for actually sorted objects instead of folder-first objects.
private IEnumerable< object > GetSortedEnumerator()
{
var modIdx = 0;
foreach( var folder in SubFolders )
{
var folderString = folder.Name;
for( ; modIdx < Mods.Count; ++modIdx )
{
var mod = Mods[ modIdx ];
var modString = mod.SortOrder.SortOrderName;
if( string.Compare( folderString, modString, StringComparison.InvariantCultureIgnoreCase ) > 0 )
{
yield return mod;
}
else
{
break;
}
}
yield return folder;
}
for( ; modIdx < Mods.Count; ++modIdx )
{
yield return Mods[ modIdx ];
}
}
private void CheckEmpty()
{
if( Mods.Count == 0 && SubFolders.Count == 0 )
{
Parent?.RemoveSubFolder( this );
}
}
// Remove a subfolder but do not remove this folder from its parent if it is empty afterwards.
internal void RemoveFolderIgnoreEmpty( ModFolder folder )
{
var idx = SubFolders.BinarySearch( folder, FolderComparer );
if( idx < 0 )
{
return;
}
SubFolders[ idx ].Parent = null;
SubFolders.RemoveAt( idx );
}
// Remove a mod, but do not remove this folder from its parent if it is empty afterwards.
internal void RemoveModIgnoreEmpty( ModData mod )
{
var idx = Mods.BinarySearch( mod, ModComparer );
if( idx >= 0 )
{
Mods.RemoveAt( idx );
}
}
}
}

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Dalamud.Plugin; using Dalamud.Plugin;
using ImGuiScene;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Meta; using Penumbra.Meta;
using Penumbra.Mod; using Penumbra.Mod;
@ -17,6 +18,8 @@ namespace Penumbra.Mods
public DirectoryInfo BasePath { get; private set; } = null!; public DirectoryInfo BasePath { get; private set; } = null!;
public Dictionary< string, ModData > Mods { get; } = new(); public Dictionary< string, ModData > Mods { get; } = new();
public ModFolder StructuredMods { get; } = ModFileSystem.Root;
public CollectionManager Collections { get; } public CollectionManager Collections { get; }
public bool Valid { get; private set; } public bool Valid { get; private set; }
@ -53,25 +56,45 @@ namespace Penumbra.Mods
DiscoverMods(); DiscoverMods();
} }
private void SetModOrders( Configuration config ) private bool SetSortOrderPath( ModData mod, string path )
{
mod.Move( path );
var fixedPath = mod.SortOrder.FullPath;
if( !fixedPath.Any() || string.Equals( fixedPath, mod.Meta.Name, StringComparison.InvariantCultureIgnoreCase ) )
{
Config.ModSortOrder.Remove( mod.BasePath.Name );
return true;
}
if( path != fixedPath )
{
Config.ModSortOrder[ mod.BasePath.Name ] = fixedPath;
return true;
}
return false;
}
private void SetModStructure()
{ {
var changes = false; var changes = false;
foreach( var kvp in config.ModSortOrder.ToArray() )
foreach( var kvp in Config.ModSortOrder.ToArray() )
{ {
if( Mods.TryGetValue( kvp.Key, out var mod ) ) if( kvp.Value.Any() && Mods.TryGetValue( kvp.Key, out var mod ) )
{ {
mod.SortOrder = string.Join( "/", kvp.Value.Trim().Split( new[] { "/" }, StringSplitOptions.RemoveEmptyEntries ) ); changes |= SetSortOrderPath( mod, kvp.Value );
} }
else else
{ {
changes = true; changes = true;
config.ModSortOrder.Remove( kvp.Key ); Config.ModSortOrder.Remove( kvp.Key );
} }
} }
if( changes ) if( changes )
{ {
config.Save(); Config.Save();
} }
} }
@ -96,7 +119,7 @@ namespace Penumbra.Mods
{ {
foreach( var modFolder in BasePath.EnumerateDirectories() ) foreach( var modFolder in BasePath.EnumerateDirectories() )
{ {
var mod = ModData.LoadMod( modFolder ); var mod = ModData.LoadMod( StructuredMods, modFolder );
if( mod == null ) if( mod == null )
{ {
continue; continue;
@ -105,7 +128,7 @@ namespace Penumbra.Mods
Mods.Add( modFolder.Name, mod ); Mods.Add( modFolder.Name, mod );
} }
SetModOrders( _plugin.Configuration ); SetModStructure();
} }
Collections.RecreateCaches(); Collections.RecreateCaches();
@ -132,7 +155,7 @@ namespace Penumbra.Mods
public bool AddMod( DirectoryInfo modFolder ) public bool AddMod( DirectoryInfo modFolder )
{ {
var mod = ModData.LoadMod( modFolder ); var mod = ModData.LoadMod( StructuredMods, modFolder );
if( mod == null ) if( mod == null )
{ {
return false; return false;
@ -140,7 +163,10 @@ namespace Penumbra.Mods
if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) ) if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
{ {
mod.SortOrder = sortOrder; if( SetSortOrderPath( mod, sortOrder ) )
{
Config.Save();
}
} }
if( Mods.ContainsKey( modFolder.Name ) ) if( Mods.ContainsKey( modFolder.Name ) )
@ -173,11 +199,17 @@ namespace Penumbra.Mods
mod.ComputeChangedItems(); mod.ComputeChangedItems();
if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) ) if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) )
{ {
mod.SortOrder = sortOrder; mod.Move( sortOrder );
var path = mod.SortOrder.FullPath;
if( path != sortOrder )
{
Config.ModSortOrder[ mod.BasePath.Name ] = path;
Config.Save();
}
} }
else else
{ {
mod.SortOrder = mod.Meta.Name.Replace( '/', '\\' ); mod.SortOrder = new SortOrder( StructuredMods, mod.Meta.Name );
} }
} }

View file

@ -22,42 +22,31 @@ namespace Penumbra.Mods
mod.Meta.Name = newName; mod.Meta.Name = newName;
mod.SaveMeta(); mod.SaveMeta();
foreach( var collection in manager.Collections.Collections.Values.Where( c => c.Cache != null ) )
{
collection.Cache!.SortMods();
}
return true; return true;
} }
public static bool ChangeSortOrder( this ModManager manager, ModData mod, string newSortOrder ) public static bool ChangeSortOrder( this ModManager manager, ModData mod, string newSortOrder )
{ {
newSortOrder = string.Join( "/", newSortOrder.Trim().Split( new[] { "/" }, StringSplitOptions.RemoveEmptyEntries ) ); if( string.Equals(mod.SortOrder.FullPath, newSortOrder, StringComparison.InvariantCultureIgnoreCase ) )
if( mod.SortOrder == newSortOrder )
{ {
return false; return false;
} }
var modName = mod.Meta.Name.Replace( '/', '\\' ); var inRoot = new SortOrder( manager.StructuredMods, mod.Meta.Name );
if( newSortOrder == string.Empty || newSortOrder == modName ) if( newSortOrder == string.Empty || newSortOrder == inRoot.SortOrderName )
{ {
mod.SortOrder = modName; mod.SortOrder = inRoot;
manager.Config.ModSortOrder.Remove( mod.BasePath.Name ); manager.Config.ModSortOrder.Remove( mod.BasePath.Name );
} }
else else
{ {
mod.SortOrder = newSortOrder; mod.Move( newSortOrder );
manager.Config.ModSortOrder[ mod.BasePath.Name ] = newSortOrder; manager.Config.ModSortOrder[ mod.BasePath.Name ] = mod.SortOrder.FullPath;
} }
manager.Config.Save(); manager.Config.Save();
foreach( var collection in manager.Collections.Collections.Values.Where( c => c.Cache != null ) )
{
collection.Cache!.SortMods();
}
return true; return true;
} }

View file

@ -39,13 +39,20 @@ namespace Penumbra
private WebServer? _webServer; private WebServer? _webServer;
public static void SaveConfiguration()
{
var pi = Service< DalamudPluginInterface >.Get();
var config = Service< Configuration >.Get();
pi.SavePluginConfig( config );
}
public void Initialize( DalamudPluginInterface pluginInterface ) public void Initialize( DalamudPluginInterface pluginInterface )
{ {
PluginInterface = pluginInterface; PluginInterface = pluginInterface;
Service< DalamudPluginInterface >.Set( PluginInterface ); Service< DalamudPluginInterface >.Set( PluginInterface );
GameData.GameData.GetIdentifier( PluginInterface ); GameData.GameData.GetIdentifier( PluginInterface );
Configuration = Configuration.Load( PluginInterface ); Configuration = Configuration.Load( PluginInterface );
Service< Configuration >.Set( Configuration );
SoundShit = new MusicManager( this ); SoundShit = new MusicManager( this );
SoundShit.DisableStreaming(); SoundShit.DisableStreaming();

View file

@ -14,6 +14,7 @@ namespace Penumbra.UI
{ {
private class TabCollections private class TabCollections
{ {
public const string LabelCurrentCollection = "Current Collection";
private readonly Selector _selector; private readonly Selector _selector;
private readonly ModManager _manager; private readonly ModManager _manager;
private string _collectionNames = null!; private string _collectionNames = null!;
@ -130,11 +131,11 @@ namespace Penumbra.UI
} }
} }
private void DrawCurrentCollectionSelector() public void DrawCurrentCollectionSelector(bool tooltip)
{ {
var index = _currentCollectionIndex; var index = _currentCollectionIndex;
var combo = ImGui.Combo( "Current Collection", ref index, _collectionNames ); var combo = ImGui.Combo( LabelCurrentCollection, ref index, _collectionNames );
if( ImGui.IsItemHovered() ) if( tooltip && ImGui.IsItemHovered() )
{ {
ImGui.SetTooltip( ImGui.SetTooltip(
"This collection will be modified when using the Installed Mods tab and making changes. It does not apply to anything by itself." ); "This collection will be modified when using the Installed Mods tab and making changes. It does not apply to anything by itself." );
@ -145,6 +146,7 @@ namespace Penumbra.UI
_manager.Collections.SetCurrentCollection( _collections[ index + 1 ] ); _manager.Collections.SetCurrentCollection( _collections[ index + 1 ] );
_currentCollectionIndex = index; _currentCollectionIndex = index;
_selector.ReloadSelection(); _selector.ReloadSelection();
_selector.Cache.ResetModList();
} }
} }
@ -277,7 +279,7 @@ namespace Penumbra.UI
return; return;
} }
DrawCurrentCollectionSelector(); DrawCurrentCollectionSelector(true);
ImGui.Dummy( new Vector2( 0, 10 ) ); ImGui.Dummy( new Vector2( 0, 10 ) );
DrawNewCollectionInput(); DrawNewCollectionInput();

View file

@ -0,0 +1,46 @@
using System;
namespace Penumbra.UI
{
[Flags]
public enum ModFilter
{
Enabled = 1 << 0,
Disabled = 1 << 1,
NoConflict = 1 << 2,
SolvedConflict = 1 << 3,
UnsolvedConflict = 1 << 4,
HasNoMetaManipulations = 1 << 5,
HasMetaManipulations = 1 << 6,
HasNoFileSwaps = 1 << 7,
HasFileSwaps = 1 << 8,
HasConfig = 1 << 9,
HasNoConfig = 1 << 10,
HasNoFiles = 1 << 11,
HasFiles = 1 << 12,
};
public static class ModFilterExtensions
{
public const ModFilter UnfilteredStateMods = ( ModFilter )( ( 1 << 13 ) - 1 );
public static string ToName( this ModFilter filter )
=> filter switch
{
ModFilter.Enabled => "Enabled",
ModFilter.Disabled => "Disabled",
ModFilter.NoConflict => "No Conflicts",
ModFilter.SolvedConflict => "Solved Conflicts",
ModFilter.UnsolvedConflict => "Unsolved Conflicts",
ModFilter.HasNoMetaManipulations => "No Meta Manipulations",
ModFilter.HasMetaManipulations => "Meta Manipulations",
ModFilter.HasNoFileSwaps => "No File Swaps",
ModFilter.HasFileSwaps => "File Swaps",
ModFilter.HasNoConfig => "No Configuration",
ModFilter.HasConfig => "Configuration",
ModFilter.HasNoFiles => "No Files",
ModFilter.HasFiles => "Files",
_ => throw new ArgumentOutOfRangeException( nameof( filter ), filter, null ),
};
}
}

View file

@ -0,0 +1,262 @@
using System.Collections.Generic;
using System.Linq;
using Dalamud.Plugin;
using Penumbra.Mods;
namespace Penumbra.UI
{
public class ModListCache
{
public const uint DisabledModColor = 0xFF666666u;
public const uint ConflictingModColor = 0xFFAAAAFFu;
public const uint HandledConflictModColor = 0xFF88DDDDu;
private readonly ModManager _manager;
private readonly List< Mod.Mod > _modsInOrder = new();
private readonly List< (bool visible, uint color) > _visibleMods = new();
private readonly Dictionary< ModFolder, (bool visible, bool enabled) > _visibleFolders = new();
private string _modFilter = "";
private string _modFilterChanges = "";
private string _modFilterAuthor = "";
private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods;
public ModFilter StateFilter
{
get => _stateFilter;
set
{
var diff = _stateFilter != value;
_stateFilter = value;
if( diff )
{
ResetFilters();
}
}
}
public ModListCache( ModManager manager )
{
_manager = manager;
ResetModList();
}
public int Count
=> _modsInOrder.Count;
public void RemoveMod( Mod.Mod mod )
{
var idx = _modsInOrder.IndexOf( mod );
if( idx >= 0 )
{
_modsInOrder.RemoveAt( idx );
_visibleMods.RemoveAt( idx );
UpdateFolders();
}
}
private void UpdateFolders()
{
_visibleFolders.Clear();
for( var i = 0; i < _modsInOrder.Count; ++i )
{
if( _visibleMods[ i ].visible )
{
_visibleFolders[ _modsInOrder[ i ].Data.SortOrder.ParentFolder ] = ( true, true );
}
}
}
public void SetTextFilter( string filter )
{
var lower = filter.ToLowerInvariant();
if( lower.StartsWith( "c:" ) )
{
_modFilterChanges = lower.Substring( 2 );
_modFilter = string.Empty;
_modFilterAuthor = string.Empty;
}
else if( lower.StartsWith( "a:" ) )
{
_modFilterAuthor = lower.Substring( 2 );
_modFilter = string.Empty;
_modFilterChanges = string.Empty;
}
else
{
_modFilter = lower;
_modFilterAuthor = string.Empty;
_modFilterChanges = string.Empty;
}
ResetFilters();
}
public void ResetModList()
{
_modsInOrder.Clear();
_visibleMods.Clear();
_visibleFolders.Clear();
PluginLog.Verbose( "Resetting mod selector list..." );
if( !_modsInOrder.Any() )
{
foreach( var modData in _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) )
{
var mod = _manager.Collections.CurrentCollection.GetMod( modData );
_modsInOrder.Add( mod );
_visibleMods.Add( CheckFilters( mod ) );
}
}
}
public void ResetFilters()
{
_visibleMods.Clear();
_visibleFolders.Clear();
PluginLog.Verbose( "Resetting mod selector filters..." );
foreach( var mod in _modsInOrder )
{
_visibleMods.Add( CheckFilters( mod ) );
}
}
public (Mod.Mod? mod, int idx) GetModByName( string name )
{
for( var i = 0; i < Count; ++i )
{
if( _modsInOrder[ i ].Data.Meta.Name == name )
{
return ( _modsInOrder[ i ], i );
}
}
return ( null, 0 );
}
public (Mod.Mod? mod, int idx) GetModByBasePath( string basePath )
{
for( var i = 0; i < Count; ++i )
{
if( _modsInOrder[ i ].Data.BasePath.Name == basePath )
{
return ( _modsInOrder[ i ], i );
}
}
return ( null, 0 );
}
public (bool visible, bool enabled) GetFolder( ModFolder folder )
=> _visibleFolders.TryGetValue( folder, out var ret ) ? ret : ( false, false );
public (Mod.Mod?, bool visible, uint color) GetMod( int idx )
=> idx >= 0 && idx < _modsInOrder.Count
? ( _modsInOrder[ idx ], _visibleMods[ idx ].visible, _visibleMods[ idx ].color )
: ( null, false, 0 );
private bool CheckFlags( int count, ModFilter hasNoFlag, ModFilter hasFlag )
{
if( count == 0 )
{
if( StateFilter.HasFlag( hasNoFlag ) )
{
return false;
}
}
else if( StateFilter.HasFlag( hasFlag ) )
{
return false;
}
return true;
}
private (bool, uint) CheckFilters( Mod.Mod mod )
{
var ret = ( false, 0u );
if( _modFilter.Any() && !mod.Data.Meta.LowerName.Contains( _modFilter ) )
{
return ret;
}
if( _modFilterAuthor.Any() && !mod.Data.Meta.LowerAuthor.Contains( _modFilterAuthor ) )
{
return ret;
}
if( _modFilterChanges.Any() && !mod.Data.LowerChangedItemsString.Contains( _modFilterChanges ) )
{
return ret;
}
if( CheckFlags( mod.Data.Resources.ModFiles.Count, ModFilter.HasNoFiles, ModFilter.HasFiles ) )
{
return ret;
}
if( CheckFlags( mod.Data.Meta.FileSwaps.Count, ModFilter.HasNoFileSwaps, ModFilter.HasFileSwaps ) )
{
return ret;
}
if( CheckFlags( mod.Data.Resources.MetaManipulations.Count, ModFilter.HasNoMetaManipulations,
ModFilter.HasMetaManipulations ) )
{
return ret;
}
if( CheckFlags( mod.Data.Meta.HasGroupsWithConfig ? 1 : 0, ModFilter.HasNoConfig, ModFilter.HasConfig ) )
{
return ret;
}
if( !mod.Settings.Enabled )
{
if( !StateFilter.HasFlag( ModFilter.Disabled ) || !StateFilter.HasFlag( ModFilter.NoConflict ) )
{
return ret;
}
ret.Item2 = ret.Item2 == 0 ? DisabledModColor : ret.Item2;
}
if( mod.Settings.Enabled && !StateFilter.HasFlag( ModFilter.Enabled ) )
{
return ret;
}
if( mod.Cache.Conflicts.Any() )
{
if( mod.Cache.Conflicts.Keys.Any( m => m.Settings.Priority == mod.Settings.Priority ) )
{
if( !StateFilter.HasFlag( ModFilter.UnsolvedConflict ) )
{
return ret;
}
ret.Item2 = ret.Item2 == 0 ? ConflictingModColor : ret.Item2;
}
else
{
if( !StateFilter.HasFlag( ModFilter.SolvedConflict ) )
{
return ret;
}
ret.Item2 = ret.Item2 == 0 ? HandledConflictModColor : ret.Item2;
}
}
else if( !StateFilter.HasFlag( ModFilter.NoConflict ) )
{
return ret;
}
ret.Item1 = true;
_visibleFolders[ mod.Data.SortOrder.ParentFolder ] = ( true, true );
return ret;
}
}
}

View file

@ -431,6 +431,19 @@ namespace Penumbra.UI
if( changed ) if( changed )
{ {
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
// Since files may have changed, we need to recompute effective files.
foreach( var collection in _modManager.Collections.Collections.Values
.Where( c => c.Cache != null && c.Settings[ Mod!.Data.BasePath.Name ].Enabled ) )
{
collection.CalculateEffectiveFileList( _modManager.BasePath, false,
collection == _modManager.Collections.ActiveCollection );
}
// If the mod is enabled in the current collection, its conflicts may have changed.
if( Mod!.Settings.Enabled )
{
_selector.Cache.ResetFilters();
}
} }
} }
@ -558,14 +571,12 @@ namespace Penumbra.UI
if( ImGui.Checkbox( label, ref enabled ) && oldEnabled != enabled ) if( ImGui.Checkbox( label, ref enabled ) && oldEnabled != enabled )
{ {
Mod.Settings.Settings[ group.GroupName ] ^= 1 << idx; Mod.Settings.Settings[ group.GroupName ] ^= 1 << idx;
if( Mod.Settings.Enabled && _modManager.Collections.CurrentCollection.Cache != null )
{
_modManager.Collections.CurrentCollection.CalculateEffectiveFileList( Mod.Data.BasePath,
Mod.Data.Resources.MetaManipulations.Count > 0,
_modManager.Collections.CurrentCollection == _modManager.Collections.ActiveCollection );
}
Save(); Save();
// If the mod is enabled, recalculate files and filters.
if( Mod.Settings.Enabled )
{
_base.RecalculateCurrent( Mod.Data.Resources.MetaManipulations.Count > 0 );
}
} }
} }
@ -599,14 +610,12 @@ namespace Penumbra.UI
&& code != Mod.Settings.Settings[ group.GroupName ] ) && code != Mod.Settings.Settings[ group.GroupName ] )
{ {
Mod.Settings.Settings[ group.GroupName ] = code; Mod.Settings.Settings[ group.GroupName ] = code;
if( Mod.Settings.Enabled && _modManager.Collections.CurrentCollection.Cache != null )
{
_modManager.Collections.CurrentCollection.CalculateEffectiveFileList( Mod.Data.BasePath,
Mod.Data.Resources.MetaManipulations.Count > 0,
_modManager.Collections.CurrentCollection == _modManager.Collections.ActiveCollection );
}
Save(); Save();
// If the mod is enabled, recalculate files and filters.
if( Mod.Settings.Enabled )
{
_base.RecalculateCurrent( Mod.Data.Resources.MetaManipulations.Count > 0 );
}
} }
} }

View file

@ -111,7 +111,10 @@ namespace Penumbra.UI
var groupName = group.GroupName; var groupName = group.GroupName;
if( Custom.ImGuiCustom.BeginFramedGroupEdit( ref groupName ) ) if( Custom.ImGuiCustom.BeginFramedGroupEdit( ref groupName ) )
{ {
_modManager.ChangeModGroup( group.GroupName, groupName, Mod.Data ); if( _modManager.ChangeModGroup( group.GroupName, groupName, Mod.Data ) && Mod.Data.Meta.RefreshHasGroupsWithConfig() )
{
_selector.Cache.ResetFilters();
}
} }
} }
@ -127,6 +130,10 @@ namespace Penumbra.UI
group.Options.Add( new Option() group.Options.Add( new Option()
{ OptionName = newOption, OptionDesc = "", OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >() } ); { OptionName = newOption, OptionDesc = "", OptionFiles = new Dictionary< RelPath, HashSet< GamePath > >() } );
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
if( Mod!.Data.Meta.RefreshHasGroupsWithConfig() )
{
_selector.Cache.ResetFilters();
}
} }
} }
@ -163,6 +170,11 @@ namespace Penumbra.UI
{ OptionName = newName, OptionDesc = opt.OptionDesc, OptionFiles = opt.OptionFiles }; { OptionName = newName, OptionDesc = opt.OptionDesc, OptionFiles = opt.OptionFiles };
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
} }
if( Mod!.Data.Meta.RefreshHasGroupsWithConfig() )
{
_selector.Cache.ResetFilters();
}
} }
} }
@ -176,7 +188,10 @@ namespace Penumbra.UI
var groupName = group.GroupName; var groupName = group.GroupName;
if( ImGui.InputText( $"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) ) if( ImGui.InputText( $"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{ {
_modManager.ChangeModGroup( group.GroupName, groupName, Mod.Data ); if( _modManager.ChangeModGroup( group.GroupName, groupName, Mod.Data ) && Mod.Data.Meta.RefreshHasGroupsWithConfig() )
{
_selector.Cache.ResetFilters();
}
} }
} }
@ -220,6 +235,11 @@ namespace Penumbra.UI
} }
} }
} }
if( Mod.Data.Meta.RefreshHasGroupsWithConfig() )
{
_selector.Cache.ResetFilters();
}
} }
if( code != oldSetting ) if( code != oldSetting )
@ -247,6 +267,7 @@ namespace Penumbra.UI
ImGuiInputTextFlags.EnterReturnsTrue ) ) ImGuiInputTextFlags.EnterReturnsTrue ) )
{ {
_modManager.ChangeModGroup( "", newGroup, Mod.Data, SelectType.Single ); _modManager.ChangeModGroup( "", newGroup, Mod.Data, SelectType.Single );
// Adds empty group, so can not change filters.
} }
} }
@ -259,6 +280,7 @@ namespace Penumbra.UI
ImGuiInputTextFlags.EnterReturnsTrue ) ) ImGuiInputTextFlags.EnterReturnsTrue ) )
{ {
_modManager.ChangeModGroup( "", newGroup, Mod.Data, SelectType.Multi ); _modManager.ChangeModGroup( "", newGroup, Mod.Data, SelectType.Multi );
// Adds empty group, so can not change filters.
} }
} }
@ -298,7 +320,6 @@ namespace Penumbra.UI
var arrowWidth = ImGui.CalcTextSize( arrow ).X; var arrowWidth = ImGui.CalcTextSize( arrow ).X;
ImGui.PopFont(); ImGui.PopFont();
var width = ( ImGui.GetWindowWidth() - arrowWidth - 4 * ImGui.GetStyle().ItemSpacing.X ) / 2; var width = ( ImGui.GetWindowWidth() - arrowWidth - 4 * ImGui.GetStyle().ItemSpacing.X ) / 2;
for( var idx = 0; idx < swaps.Length + 1; ++idx ) for( var idx = 0; idx < swaps.Length + 1; ++idx )
{ {
@ -325,10 +346,8 @@ namespace Penumbra.UI
} }
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
if( Mod.Settings.Enabled )
{
_selector.ReloadCurrentMod(); _selector.ReloadCurrentMod();
} _selector.Cache.ResetModList();
} }
} }
@ -350,10 +369,8 @@ namespace Penumbra.UI
{ {
Meta.FileSwaps[ key ] = newValue; Meta.FileSwaps[ key ] = newValue;
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
if( Mod.Settings.Enabled )
{
_selector.ReloadCurrentMod(); _selector.ReloadCurrentMod();
} _selector.Cache.ResetModList();
} }
} }
} }

View file

@ -74,8 +74,11 @@ namespace Penumbra.UI
var name = Meta!.Name; var name = Meta!.Name;
if( Custom.ImGuiCustom.InputOrText( _editMode, LabelEditName, ref name, 64 ) && _modManager.RenameMod( name, Mod!.Data ) ) if( Custom.ImGuiCustom.InputOrText( _editMode, LabelEditName, ref name, 64 ) && _modManager.RenameMod( name, Mod!.Data ) )
{ {
_selector.RenameCurrentModLower( name );
_selector.SelectModByDir( Mod.Data.BasePath.Name ); _selector.SelectModByDir( Mod.Data.BasePath.Name );
if( !_modManager.Config.ModSortOrder.ContainsKey( Mod!.Data.BasePath.Name ) && Mod.Data.Rename( name ) )
{
_selector.Cache.ResetModList();
}
} }
} }
@ -119,6 +122,7 @@ namespace Penumbra.UI
{ {
Meta.Author = author; Meta.Author = author;
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
_selector.Cache.ResetFilters();
} }
ImGui.EndGroup(); ImGui.EndGroup();
@ -200,13 +204,8 @@ namespace Penumbra.UI
if( ImGui.InputInt( "Priority", ref priority, 0 ) && priority != Mod!.Settings.Priority ) if( ImGui.InputInt( "Priority", ref priority, 0 ) && priority != Mod!.Settings.Priority )
{ {
Mod.Settings.Priority = priority; Mod.Settings.Priority = priority;
var collection = _modManager.Collections.CurrentCollection; _selector.Cache.ResetFilters();
collection.Save( _base._plugin.PluginInterface! ); _base.SaveCurrentCollection( Mod.Data.Resources.MetaManipulations.Count > 0 );
if( collection.Cache != null )
{
collection.CalculateEffectiveFileList( _modManager.BasePath, Mod.Data.Resources.MetaManipulations.Count > 0,
collection == _modManager.Collections.ActiveCollection );
}
} }
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
@ -222,23 +221,19 @@ namespace Penumbra.UI
if( ImGui.Checkbox( LabelModEnabled, ref enabled ) ) if( ImGui.Checkbox( LabelModEnabled, ref enabled ) )
{ {
Mod.Settings.Enabled = enabled; Mod.Settings.Enabled = enabled;
var collection = _modManager.Collections.CurrentCollection; _selector.Cache.ResetFilters();
collection.Save( _base._plugin.PluginInterface! ); _base.SaveCurrentCollection( Mod.Data.Resources.MetaManipulations.Count > 0 );
if( collection.Cache != null )
{
collection.CalculateEffectiveFileList( _modManager.BasePath, Mod.Data.Resources.MetaManipulations.Count > 0,
collection == _modManager.Collections.ActiveCollection );
}
} }
} }
public static bool DrawSortOrder( ModData mod, ModManager manager, Selector selector ) public static bool DrawSortOrder( ModData mod, ModManager manager, Selector selector )
{ {
var currentSortOrder = mod.SortOrder; var currentSortOrder = mod.SortOrder.FullPath;
ImGui.SetNextItemWidth( 300 ); ImGui.SetNextItemWidth( 300 );
if( ImGui.InputText( "Sort Order", ref currentSortOrder, 256, ImGuiInputTextFlags.EnterReturnsTrue ) ) if( ImGui.InputText( "Sort Order", ref currentSortOrder, 256, ImGuiInputTextFlags.EnterReturnsTrue ) )
{ {
manager.ChangeSortOrder( mod, currentSortOrder ); manager.ChangeSortOrder( mod, currentSortOrder );
selector.Cache.ResetModList();
selector.SelectModByDir( mod.BasePath.Name ); selector.SelectModByDir( mod.BasePath.Name );
return true; return true;
} }
@ -337,7 +332,6 @@ namespace Penumbra.UI
{ {
Service< ModManager >.Get()!.RenameModFolder( Mod.Data, newDir, false ); Service< ModManager >.Get()!.RenameModFolder( Mod.Data, newDir, false );
_selector.ResetModNamesLower();
_selector.SelectModByDir( _newName ); _selector.SelectModByDir( _newName );
closeParent = true; closeParent = true;
@ -400,7 +394,6 @@ namespace Penumbra.UI
} }
} }
private void DrawRenameModFolderButton() private void DrawRenameModFolderButton()
{ {
DrawRenameModFolderPopup(); DrawRenameModFolderPopup();
@ -452,7 +445,9 @@ namespace Penumbra.UI
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{ {
ImGui.SetTooltip( ImGui.SetTooltip(
"Force a recomputation of the metadata_manipulations.json file from all .meta files in the folder.\nAlso reloads the mod.\nBe aware that this removes all manually added metadata changes." ); "Force a recomputation of the metadata_manipulations.json file from all .meta files in the folder.\n"
+ "Also reloads the mod.\n"
+ "Be aware that this removes all manually added metadata changes." );
} }
} }
@ -507,11 +502,6 @@ namespace Penumbra.UI
public void Draw() public void Draw()
{ {
if( Mod == null )
{
return;
}
try try
{ {
var ret = ImGui.BeginChild( LabelModPanel, AutoFillSize, true ); var ret = ImGui.BeginChild( LabelModPanel, AutoFillSize, true );
@ -520,6 +510,12 @@ namespace Penumbra.UI
return; return;
} }
if( Mod == null )
{
ImGui.EndChild();
return;
}
DrawHeaderLine(); DrawHeaderLine();
// Next line with fixed distance. // Next line with fixed distance.

File diff suppressed because it is too large Load diff

View file

@ -22,6 +22,7 @@ namespace Penumbra.UI
private const string LabelEnabled = "Enable Mods"; private const string LabelEnabled = "Enable Mods";
private const string LabelEnabledPlayerWatch = "Enable automatic Character Redraws"; private const string LabelEnabledPlayerWatch = "Enable automatic Character Redraws";
private const string LabelWaitFrames = "Wait Frames"; private const string LabelWaitFrames = "Wait Frames";
private const string LabelSortFoldersFirst = "Sort Mod Folders Before Mods";
private const string LabelScaleModSelector = "Scale Mod Selector With Window Size"; private const string LabelScaleModSelector = "Scale Mod Selector With Window Size";
private const string LabelShowAdvanced = "Show Advanced Settings"; private const string LabelShowAdvanced = "Show Advanced Settings";
private const string LabelLogLoadedFiles = "Log all loaded files"; private const string LabelLogLoadedFiles = "Log all loaded files";
@ -100,6 +101,17 @@ namespace Penumbra.UI
} }
} }
private void DrawSortFoldersFirstBox()
{
var foldersFirst = _config.SortFoldersFirst;
if( ImGui.Checkbox( LabelSortFoldersFirst, ref foldersFirst ) )
{
_config.SortFoldersFirst = foldersFirst;
_base._menu.InstalledTab.Selector.Cache.ResetModList();
_configChanged = true;
}
}
private void DrawScaleModSelectorBox() private void DrawScaleModSelectorBox()
{ {
var scaleModSelector = _config.ScaleModSelector; var scaleModSelector = _config.ScaleModSelector;
@ -238,6 +250,7 @@ namespace Penumbra.UI
Custom.ImGuiCustom.VerticalDistance( DefaultVerticalSpace ); Custom.ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawScaleModSelectorBox(); DrawScaleModSelectorBox();
DrawSortFoldersFirstBox();
DrawShowAdvancedBox(); DrawShowAdvancedBox();
if( _config.ShowAdvanced ) if( _config.ShowAdvanced )

View file

@ -16,6 +16,7 @@ namespace Penumbra.UI
private readonly ManageModsButton _manageModsButton; private readonly ManageModsButton _manageModsButton;
private readonly MenuBar _menuBar; private readonly MenuBar _menuBar;
private readonly SettingsMenu _menu; private readonly SettingsMenu _menu;
private readonly ModManager _modManager;
public SettingsInterface( Plugin plugin ) public SettingsInterface( Plugin plugin )
{ {
@ -23,6 +24,7 @@ namespace Penumbra.UI
_manageModsButton = new ManageModsButton( this ); _manageModsButton = new ManageModsButton( this );
_menuBar = new MenuBar( this ); _menuBar = new MenuBar( this );
_menu = new SettingsMenu( this ); _menu = new SettingsMenu( this );
_modManager = Service< ModManager >.Get();
} }
public void FlipVisibility() public void FlipVisibility()
@ -40,12 +42,27 @@ namespace Penumbra.UI
private void ReloadMods() private void ReloadMods()
{ {
_menu.InstalledTab.Selector.ResetModNamesLower();
_menu.InstalledTab.Selector.ClearSelection(); _menu.InstalledTab.Selector.ClearSelection();
_modManager.DiscoverMods( _plugin.Configuration.ModDirectory );
_menu.InstalledTab.Selector.Cache.ResetModList();
}
var modManager = Service< ModManager >.Get(); private void SaveCurrentCollection( bool recalculateMeta )
modManager.DiscoverMods( _plugin.Configuration.ModDirectory ); {
_menu.InstalledTab.Selector.ResetModNamesLower(); var current = _modManager.Collections.CurrentCollection;
current.Save( _plugin.PluginInterface );
RecalculateCurrent( recalculateMeta );
}
private void RecalculateCurrent( bool recalculateMeta )
{
var current = _modManager.Collections.CurrentCollection;
if( current.Cache != null )
{
current.CalculateEffectiveFileList( _modManager.BasePath, recalculateMeta,
current == _modManager.Collections.ActiveCollection );
_menu.InstalledTab.Selector.Cache.ResetFilters();
}
} }
} }
} }

View file

@ -18,8 +18,8 @@ namespace Penumbra.UI
private readonly TabSettings _settingsTab; private readonly TabSettings _settingsTab;
private readonly TabImport _importTab; private readonly TabImport _importTab;
private readonly TabBrowser _browserTab; private readonly TabBrowser _browserTab;
private readonly TabCollections _collectionsTab; internal readonly TabCollections CollectionsTab;
public readonly TabInstalled InstalledTab; internal readonly TabInstalled InstalledTab;
private readonly TabEffective _effectiveTab; private readonly TabEffective _effectiveTab;
public SettingsMenu( SettingsInterface ui ) public SettingsMenu( SettingsInterface ui )
@ -29,7 +29,7 @@ namespace Penumbra.UI
_importTab = new TabImport( _base ); _importTab = new TabImport( _base );
_browserTab = new TabBrowser(); _browserTab = new TabBrowser();
InstalledTab = new TabInstalled( _base ); InstalledTab = new TabInstalled( _base );
_collectionsTab = new TabCollections( InstalledTab.Selector ); CollectionsTab = new TabCollections( InstalledTab.Selector );
_effectiveTab = new TabEffective(); _effectiveTab = new TabEffective();
} }
@ -62,7 +62,7 @@ namespace Penumbra.UI
ImGui.BeginTabBar( PenumbraSettingsLabel ); ImGui.BeginTabBar( PenumbraSettingsLabel );
_settingsTab.Draw(); _settingsTab.Draw();
_collectionsTab.Draw(); CollectionsTab.Draw();
_importTab.Draw(); _importTab.Draw();
if( Service< ModManager >.Get().Valid && !_importTab.IsImporting() ) if( Service< ModManager >.Get().Valid && !_importTab.IsImporting() )