Merge pull request #17 from Ottermandias/SmallFixes

Small fixes
This commit is contained in:
Adam 2021-02-17 10:19:51 +11:00 committed by GitHub
commit 7d23505bfd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 2709 additions and 2195 deletions

View file

@ -2,7 +2,7 @@
[*] [*]
charset=utf-8 charset=utf-8
end_of_line=lf end_of_line=lf
trim_trailing_whitespace=false trim_trailing_whitespace=true
insert_final_newline=false insert_final_newline=false
indent_style=space indent_style=space
indent_size=4 indent_size=4
@ -10,6 +10,7 @@ indent_size=4
# Microsoft .NET properties # Microsoft .NET properties
csharp_new_line_before_members_in_object_initializers=false csharp_new_line_before_members_in_object_initializers=false
csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
csharp_prefer_braces=true:none
csharp_space_after_cast=false csharp_space_after_cast=false
csharp_space_after_keywords_in_control_flow_statements=false csharp_space_after_keywords_in_control_flow_statements=false
csharp_space_between_method_call_parameter_list_parentheses=true csharp_space_between_method_call_parameter_list_parentheses=true
@ -30,9 +31,26 @@ dotnet_style_qualification_for_property=false:suggestion
dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion
# ReSharper properties # ReSharper properties
resharper_align_multiline_binary_expressions_chain=false
resharper_align_multiline_calls_chain=false
resharper_autodetect_indent_settings=true resharper_autodetect_indent_settings=true
resharper_braces_redundant=true
resharper_constructor_or_destructor_body=expression_body
resharper_csharp_empty_block_style=together
resharper_csharp_max_line_length=144
resharper_csharp_space_within_array_access_brackets=true resharper_csharp_space_within_array_access_brackets=true
resharper_enforce_line_ending_style=true resharper_enforce_line_ending_style=true
resharper_int_align_assignments=true
resharper_int_align_comments=true
resharper_int_align_fields=true
resharper_int_align_invocations=false
resharper_int_align_nested_ternary=true
resharper_int_align_properties=false
resharper_int_align_switch_expressions=true
resharper_int_align_switch_sections=true
resharper_int_align_variables=true
resharper_local_function_body=expression_body
resharper_method_or_operator_body=expression_body
resharper_place_attribute_on_same_line=false resharper_place_attribute_on_same_line=false
resharper_space_after_cast=false resharper_space_after_cast=false
resharper_space_within_checked_parentheses=true resharper_space_within_checked_parentheses=true

View file

@ -9,10 +9,7 @@ namespace Penumbra.API
{ {
private readonly Plugin _plugin; private readonly Plugin _plugin;
public ModsController( Plugin plugin ) public ModsController( Plugin plugin ) => _plugin = plugin;
{
_plugin = plugin;
}
[Route( HttpVerbs.Get, "/mods" )] [Route( HttpVerbs.Get, "/mods" )]
public object GetMods() public object GetMods()

View file

@ -1,7 +1,7 @@
using Dalamud.Configuration;
using Dalamud.Plugin;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Dalamud.Configuration;
using Dalamud.Plugin;
namespace Penumbra namespace Penumbra
{ {

View file

@ -38,10 +38,7 @@ namespace Penumbra
{ {
public IntPtr Handle { get; set; } public IntPtr Handle { get; set; }
public DialogHandle( IntPtr handle ) public DialogHandle( IntPtr handle ) => Handle = handle;
{
Handle = handle;
}
} }
public class HiddenForm : Form public class HiddenForm : Form
@ -52,9 +49,9 @@ namespace Penumbra
public HiddenForm( CommonDialog form, IWin32Window owner, TaskCompletionSource< DialogResult > taskSource ) public HiddenForm( CommonDialog form, IWin32Window owner, TaskCompletionSource< DialogResult > taskSource )
{ {
this._form = form; _form = form;
this._owner = owner; _owner = owner;
this._taskSource = taskSource; _taskSource = taskSource;
Opacity = 0; Opacity = 0;
FormBorderStyle = FormBorderStyle.None; FormBorderStyle = FormBorderStyle.None;

View file

@ -25,7 +25,8 @@ namespace Penumbra.Extensions
/// <typeparam name="TField">The type of the underlying field</typeparam> /// <typeparam name="TField">The type of the underlying field</typeparam>
/// <returns>A delegate that will return a reference to a particular field - zero copy</returns> /// <returns>A delegate that will return a reference to a particular field - zero copy</returns>
/// <exception cref="MissingFieldException"></exception> /// <exception cref="MissingFieldException"></exception>
private static RefGet< TObject, TField > CreateRefGetter< TObject, TField >( string fieldName ) where TField : unmanaged private static RefGet< TObject, TField > CreateRefGetter< TObject, TField >( string fieldName )
where TField : unmanaged
{ {
const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;

View file

@ -14,11 +14,11 @@ namespace Penumbra.Game
public unsafe delegate void* UnloadPlayerResourcesPrototype( IntPtr pResourceManager ); public unsafe delegate void* UnloadPlayerResourcesPrototype( IntPtr pResourceManager );
public LoadPlayerResourcesPrototype LoadPlayerResources { get; private set; } public LoadPlayerResourcesPrototype LoadPlayerResources { get; }
public UnloadPlayerResourcesPrototype UnloadPlayerResources { get; private set; } public UnloadPlayerResourcesPrototype UnloadPlayerResources { get; }
// Object addresses // Object addresses
private IntPtr _playerResourceManagerAddress; private readonly IntPtr _playerResourceManagerAddress;
public IntPtr PlayerResourceManagerPtr => Marshal.ReadIntPtr( _playerResourceManagerAddress ); public IntPtr PlayerResourceManagerPtr => Marshal.ReadIntPtr( _playerResourceManagerAddress );
public GameUtils( DalamudPluginInterface pluginInterface ) public GameUtils( DalamudPluginInterface pluginInterface )

View file

@ -1,9 +1,10 @@
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Dalamud.Game.ClientState.Actors; using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types; using Dalamud.Game.ClientState.Actors.Types;
using System.Threading.Tasks;
namespace Penumbra namespace Penumbra.Game
{ {
public static class RefreshActors public static class RefreshActors
{ {
@ -25,30 +26,36 @@ namespace Penumbra
Marshal.WriteInt32( renderModePtr, renderStatus & ~ModelInvisibilityFlag ); Marshal.WriteInt32( renderModePtr, renderStatus & ~ModelInvisibilityFlag );
} }
if (actor.ObjectKind == Dalamud.Game.ClientState.Actors.ObjectKind.Player) if( actor.ObjectKind == ObjectKind.Player )
{ {
DrawObject( RenderTaskPlayerDelay ); DrawObject( RenderTaskPlayerDelay );
await Task.Delay( RenderTaskPlayerDelay ); await Task.Delay( RenderTaskPlayerDelay );
} }
else else
{
DrawObject( RenderTaskOtherDelay ); DrawObject( RenderTaskOtherDelay );
}
} }
public static void RedrawSpecific( ActorTable actors, string name ) public static void RedrawSpecific( ActorTable actors, string name )
{ {
if( name?.Length == 0 ) if( name?.Length == 0 )
{
RedrawAll( actors ); RedrawAll( actors );
}
foreach (var actor in actors) foreach( var actor in actors.Where( A => A.Name == name ) )
if (actor.Name == name) {
Redraw( actor ); Redraw( actor );
} }
}
public static void RedrawAll( ActorTable actors ) public static void RedrawAll( ActorTable actors )
{ {
foreach( var actor in actors ) foreach( var actor in actors )
{
Redraw( actor ); Redraw( actor );
} }
} }
} }
}

View file

@ -8,10 +8,7 @@ namespace Penumbra.Importer
{ {
private readonly FileStream _fileStream; private readonly FileStream _fileStream;
public MagicTempFileStreamManagerAndDeleterFuckery( FileStream stream ) : base( stream ) public MagicTempFileStreamManagerAndDeleterFuckery( FileStream stream ) : base( stream ) => _fileStream = stream;
{
_fileStream = stream;
}
public new void Dispose() public new void Dispose()
{ {

View file

@ -21,7 +21,7 @@ namespace Penumbra.Importer
public ImporterState State { get; private set; } public ImporterState State { get; private set; }
public long TotalProgress { get; private set; } public long TotalProgress { get; private set; } = 0;
public long CurrentProgress { get; private set; } public long CurrentProgress { get; private set; }
public float Progress public float Progress
@ -50,19 +50,7 @@ namespace Penumbra.Importer
{ {
CurrentModPack = modPackFile.Name; CurrentModPack = modPackFile.Name;
switch( modPackFile.Extension ) VerifyVersionAndImport( modPackFile );
{
case ".ttmp":
ImportV1ModPack( modPackFile );
break;
case ".ttmp2":
ImportV2ModPack( modPackFile );
break;
default:
throw new ArgumentException( $"Unrecognized modpack format: {modPackFile.Extension}", nameof(modPackFile) );
}
State = ImporterState.Done; State = ImporterState.Done;
} }
@ -88,15 +76,39 @@ namespace Penumbra.Importer
return new MagicTempFileStreamManagerAndDeleterFuckery( fs ); return new MagicTempFileStreamManagerAndDeleterFuckery( fs );
} }
private void ImportV1ModPack( FileInfo modPackFile ) private void VerifyVersionAndImport( FileInfo modPackFile )
{
using var zfs = modPackFile.OpenRead();
using var extractedModPack = new ZipFile( zfs );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modRaw = GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 );
// At least a better validation than going by the extension.
if( modRaw.Contains( "\"TTMPVersion\":" ) )
{
if( modPackFile.Extension != ".ttmp2" )
{
PluginLog.Warning( $"File {modPackFile.FullName} seems to be a V2 TTMP, but has the wrong extension." );
}
ImportV2ModPack( modPackFile, extractedModPack, modRaw );
}
else
{
if( modPackFile.Extension != ".ttmp" )
{
PluginLog.Warning( $"File {modPackFile.FullName} seems to be a V1 TTMP, but has the wrong extension." );
}
ImportV1ModPack( modPackFile, extractedModPack, modRaw );
}
}
private void ImportV1ModPack( FileInfo modPackFile, ZipFile extractedModPack, string modRaw )
{ {
PluginLog.Log( " -> Importing V1 ModPack" ); PluginLog.Log( " -> Importing V1 ModPack" );
using var zfs = modPackFile.OpenRead(); var modListRaw = modRaw.Split(
using var extractedModPack = new ZipFile( zfs );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modListRaw = GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 ).Split(
new[] { "\r\n", "\r", "\n" }, new[] { "\r\n", "\r", "\n" },
StringSplitOptions.None StringSplitOptions.None
); );
@ -129,33 +141,26 @@ namespace Penumbra.Importer
ExtractSimpleModList( newModFolder, modList, modData ); ExtractSimpleModList( newModFolder, modList, modData );
} }
private void ImportV2ModPack( FileInfo modPackFile ) private void ImportV2ModPack( FileInfo modPackFile, ZipFile extractedModPack, string modRaw )
{ {
using var zfs = modPackFile.OpenRead(); var modList = JsonConvert.DeserializeObject< SimpleModPack >( modRaw );
using var extractedModPack = new ZipFile( zfs );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modList = JsonConvert.DeserializeObject< SimpleModPack >( GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 ) );
if( modList.TTMPVersion.EndsWith( "s" ) ) if( modList.TTMPVersion.EndsWith( "s" ) )
{ {
ImportSimpleV2ModPack( extractedModPack ); ImportSimpleV2ModPack( extractedModPack, modList );
return; return;
} }
if( modList.TTMPVersion.EndsWith( "w" ) ) if( modList.TTMPVersion.EndsWith( "w" ) )
{ {
ImportExtendedV2ModPack( extractedModPack ); ImportExtendedV2ModPack( extractedModPack, modRaw );
} }
} }
private void ImportSimpleV2ModPack( ZipFile extractedModPack ) private void ImportSimpleV2ModPack( ZipFile extractedModPack, SimpleModPack modList )
{ {
PluginLog.Log( " -> Importing Simple V2 ModPack" ); PluginLog.Log( " -> Importing Simple V2 ModPack" );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modList = JsonConvert.DeserializeObject< SimpleModPack >( GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 ) );
// Create a new ModMeta from the TTMP modlist info // Create a new ModMeta from the TTMP modlist info
var modMeta = new ModMeta var modMeta = new ModMeta
{ {
@ -179,12 +184,11 @@ namespace Penumbra.Importer
ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData ); ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData );
} }
private void ImportExtendedV2ModPack( ZipFile extractedModPack ) private void ImportExtendedV2ModPack( ZipFile extractedModPack, string modRaw )
{ {
PluginLog.Log( " -> Importing Extended V2 ModPack" ); PluginLog.Log( " -> Importing Extended V2 ModPack" );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" ); var modList = JsonConvert.DeserializeObject< ExtendedModPack >( modRaw );
var modList = JsonConvert.DeserializeObject< ExtendedModPack >( GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 ) );
// Create a new ModMeta from the TTMP modlist info // Create a new ModMeta from the TTMP modlist info
var modMeta = new ModMeta var modMeta = new ModMeta
@ -202,29 +206,33 @@ namespace Penumbra.Importer
var newModFolder = new DirectoryInfo( var newModFolder = new DirectoryInfo(
Path.Combine( _outDirectory.FullName, Path.Combine( _outDirectory.FullName,
Path.GetFileNameWithoutExtension( modList.Name ) Path.GetFileNameWithoutExtension( modList.Name ).ReplaceInvalidPathSymbols()
) )
); );
newModFolder.Create(); newModFolder.Create();
if( modList.SimpleModsList != null ) if( modList.SimpleModsList != null )
{
ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData ); ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData );
}
if( modList.ModPackPages == null ) if( modList.ModPackPages == null )
{
return; return;
}
// Iterate through all pages // Iterate through all pages
foreach( var page in modList.ModPackPages) foreach( var group in modList.ModPackPages.SelectMany( page => page.ModGroups ) )
{ {
foreach(var group in page.ModGroups) { var groupFolder = new DirectoryInfo( Path.Combine( newModFolder.FullName, group.GroupName.ReplaceInvalidPathSymbols() ) );
var groupFolder = new DirectoryInfo(Path.Combine(newModFolder.FullName, group.GroupName)); foreach( var option in group.OptionList )
foreach(var option in group.OptionList) { {
var optionFolder = new DirectoryInfo( Path.Combine( groupFolder.FullName, option.Name ) ); var optionFolder = new DirectoryInfo( Path.Combine( groupFolder.FullName, option.Name.ReplaceInvalidPathSymbols() ) );
ExtractSimpleModList( optionFolder, option.ModsJsons, modData ); ExtractSimpleModList( optionFolder, option.ModsJsons, modData );
} }
AddMeta( newModFolder, groupFolder, group, modMeta ); AddMeta( newModFolder, groupFolder, group, modMeta );
} }
}
File.WriteAllText( File.WriteAllText(
Path.Combine( newModFolder.FullName, "meta.json" ), Path.Combine( newModFolder.FullName, "meta.json" ),
@ -238,26 +246,29 @@ namespace Penumbra.Importer
{ {
SelectionType = group.SelectionType, SelectionType = group.SelectionType,
GroupName = group.GroupName, GroupName = group.GroupName,
Options = new List<Option>(), Options = new List< Option >()
}; };
foreach( var opt in group.OptionList ) foreach( var opt in group.OptionList )
{ {
var optio = new Option var option = new Option
{ {
OptionName = opt.Name, OptionName = opt.Name,
OptionDesc = String.IsNullOrEmpty(opt.Description) ? "" : opt.Description, OptionDesc = string.IsNullOrEmpty( opt.Description ) ? "" : opt.Description,
OptionFiles = new Dictionary< string, HashSet< string > >() OptionFiles = new Dictionary< string, HashSet< string > >()
}; };
var optDir = new DirectoryInfo(Path.Combine( groupFolder.FullName, opt.Name)); var optDir = new DirectoryInfo( Path.Combine( groupFolder.FullName, opt.Name.ReplaceInvalidPathSymbols() ) );
if (optDir.Exists) if( !optDir.Exists )
{ {
foreach( var file in optDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) ) foreach( var file in optDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
{ {
optio.AddFile(file.FullName.Substring(baseFolder.FullName.Length).TrimStart('\\'), file.FullName.Substring(optDir.FullName.Length).TrimStart('\\').Replace('\\','/')); option.AddFile( file.FullName.Substring( baseFolder.FullName.Length ).TrimStart( '\\' ),
file.FullName.Substring( optDir.FullName.Length ).TrimStart( '\\' ).Replace( '\\', '/' ) );
} }
} }
Inf.Options.Add( optio );
Inf.Options.Add( option );
} }
meta.Groups.Add( group.GroupName, Inf ); meta.Groups.Add( group.GroupName, Inf );
} }
@ -273,17 +284,11 @@ namespace Penumbra.Importer
// haha allocation go brr // haha allocation go brr
var wtf = mods.ToList(); var wtf = mods.ToList();
TotalProgress = wtf.LongCount(); TotalProgress += wtf.LongCount();
// Extract each SimpleMod into the new mod folder // Extract each SimpleMod into the new mod folder
foreach( var simpleMod in wtf ) foreach( var simpleMod in wtf.Where( M => M != null ) )
{ {
if( simpleMod == null )
{
// do we increment here too???? can this even happen?????
continue;
}
ExtractMod( outDirectory, simpleMod, dataStream ); ExtractMod( outDirectory, simpleMod, dataStream );
CurrentProgress++; CurrentProgress++;
} }
@ -301,13 +306,15 @@ namespace Penumbra.Importer
extractedFile.Directory?.Create(); extractedFile.Directory?.Create();
if( extractedFile.FullName.EndsWith( "mdl" ) ) if( extractedFile.FullName.EndsWith( "mdl" ) )
{
ProcessMdl( data.Data ); ProcessMdl( data.Data );
}
File.WriteAllBytes( extractedFile.FullName, data.Data ); File.WriteAllBytes( extractedFile.FullName, data.Data );
} }
catch( Exception ex ) catch( Exception ex )
{ {
PluginLog.LogError( ex, "Could not export mod." ); PluginLog.LogError( ex, "Could not extract mod." );
} }
} }
@ -326,10 +333,7 @@ namespace Penumbra.Importer
mdl[ modelHeaderStart + modelHeaderLodOffset ] = 1; mdl[ modelHeaderStart + modelHeaderLodOffset ] = 1;
} }
private static Stream GetStreamFromZipEntry( ZipFile file, ZipEntry entry ) private static Stream GetStreamFromZipEntry( ZipFile file, ZipEntry entry ) => file.GetInputStream( entry );
{
return file.GetInputStream( entry );
}
private static string GetStringFromZipEntry( ZipFile file, ZipEntry entry, Encoding encoding ) private static string GetStringFromZipEntry( ZipFile file, ZipEntry entry, Encoding encoding )
{ {

View file

@ -1,79 +1,81 @@
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Cryptography;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Collections; using System.Security.Cryptography;
using Dalamud.Plugin; using Dalamud.Plugin;
namespace Penumbra.Models namespace Penumbra.Models
{ {
public class Deduplicator public class Deduplicator
{ {
private DirectoryInfo baseDir; private readonly DirectoryInfo _baseDir;
private int baseDirLength; private readonly int _baseDirLength;
private ModMeta mod; private readonly ModMeta _mod;
private SHA256 hasher = null; private SHA256 _hasher;
private Dictionary<long, List<FileInfo>> filesBySize; private readonly Dictionary< long, List< FileInfo > > _filesBySize = new();
private ref SHA256 Sha() private ref SHA256 Sha()
{ {
if (hasher == null) _hasher ??= SHA256.Create();
hasher = SHA256.Create(); return ref _hasher;
return ref hasher;
} }
public Deduplicator( DirectoryInfo baseDir, ModMeta mod ) public Deduplicator( DirectoryInfo baseDir, ModMeta mod )
{ {
this.baseDir = baseDir; _baseDir = baseDir;
this.baseDirLength = baseDir.FullName.Length; _baseDirLength = baseDir.FullName.Length;
this.mod = mod; _mod = mod;
filesBySize = new();
BuildDict(); BuildDict();
} }
private void BuildDict() private void BuildDict()
{ {
foreach( var file in baseDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) ) foreach( var file in _baseDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
{ {
var fileLength = file.Length; var fileLength = file.Length;
if (filesBySize.TryGetValue(fileLength, out var files)) if( _filesBySize.TryGetValue( fileLength, out var files ) )
{
files.Add( file ); files.Add( file );
}
else else
filesBySize[fileLength] = new(){ file }; {
_filesBySize[ fileLength ] = new List< FileInfo >() { file };
}
} }
} }
public void Run() public void Run()
{ {
foreach (var pair in filesBySize) foreach( var pair in _filesBySize.Where( pair => pair.Value.Count >= 2 ) )
{ {
if (pair.Value.Count < 2)
continue;
if( pair.Value.Count == 2 ) if( pair.Value.Count == 2 )
{ {
if( CompareFilesDirectly( pair.Value[ 0 ], pair.Value[ 1 ] ) ) if( CompareFilesDirectly( pair.Value[ 0 ], pair.Value[ 1 ] ) )
{
ReplaceFile( pair.Value[ 0 ], pair.Value[ 1 ] ); ReplaceFile( pair.Value[ 0 ], pair.Value[ 1 ] );
} }
}
else else
{ {
var deleted = Enumerable.Repeat( false, pair.Value.Count ).ToArray(); var deleted = Enumerable.Repeat( false, pair.Value.Count ).ToArray();
var hashes = pair.Value.Select( F => ComputeHash(F)).ToArray(); var hashes = pair.Value.Select( ComputeHash ).ToArray();
for( var i = 0; i < pair.Value.Count; ++i ) for( var i = 0; i < pair.Value.Count; ++i )
{ {
if( deleted[ i ] ) if( deleted[ i ] )
{
continue; continue;
}
for( var j = i + 1; j < pair.Value.Count; ++j ) for( var j = i + 1; j < pair.Value.Count; ++j )
{ {
if (deleted[j]) if( deleted[ j ] || !CompareHashes( hashes[ i ], hashes[ j ] ) )
continue; {
if (!CompareHashes(hashes[i], hashes[j]))
continue; continue;
}
ReplaceFile( pair.Value[ i ], pair.Value[ j ] ); ReplaceFile( pair.Value[ i ], pair.Value[ j ] );
deleted[ j ] = true; deleted[ j ] = true;
@ -81,16 +83,17 @@ namespace Penumbra.Models
} }
} }
} }
ClearEmptySubDirectories(baseDir);
ClearEmptySubDirectories( _baseDir );
} }
private void ReplaceFile( FileInfo f1, FileInfo f2 ) private void ReplaceFile( FileInfo f1, FileInfo f2 )
{ {
var relName1 = f1.FullName.Substring(baseDirLength).TrimStart('\\'); var relName1 = f1.FullName.Substring( _baseDirLength ).TrimStart( '\\' );
var relName2 = f2.FullName.Substring(baseDirLength).TrimStart('\\'); var relName2 = f2.FullName.Substring( _baseDirLength ).TrimStart( '\\' );
var inOption = false; var inOption = false;
foreach (var group in mod.Groups.Select( g => g.Value.Options)) foreach( var group in _mod.Groups.Select( g => g.Value.Options ) )
{ {
foreach( var option in group ) foreach( var option in group )
{ {
@ -98,48 +101,50 @@ namespace Penumbra.Models
{ {
inOption = true; inOption = true;
foreach( var value in values ) foreach( var value in values )
{
option.AddFile( relName1, value ); option.AddFile( relName1, value );
}
option.OptionFiles.Remove( relName2 ); option.OptionFiles.Remove( relName2 );
} }
} }
} }
if( !inOption ) if( !inOption )
{ {
const string duplicates = "Duplicates"; const string duplicates = "Duplicates";
if (!mod.Groups.ContainsKey(duplicates)) if( !_mod.Groups.ContainsKey( duplicates ) )
{ {
InstallerInfo info = new() InstallerInfo info = new()
{ {
GroupName = duplicates, GroupName = duplicates,
SelectionType = SelectType.Single, SelectionType = SelectType.Single,
Options = new() Options = new List< Option >()
{ {
new() new()
{ {
OptionName = "Required", OptionName = "Required",
OptionDesc = "", OptionDesc = "",
OptionFiles = new() OptionFiles = new Dictionary< string, HashSet< string > >()
} }
} }
}; };
mod.Groups.Add(duplicates, info); _mod.Groups.Add( duplicates, info );
} }
mod.Groups[duplicates].Options[0].AddFile(relName1, relName2.Replace('\\', '/'));
mod.Groups[duplicates].Options[0].AddFile(relName1, relName1.Replace('\\', '/')); _mod.Groups[ duplicates ].Options[ 0 ].AddFile( relName1, relName2.Replace( '\\', '/' ) );
_mod.Groups[ duplicates ].Options[ 0 ].AddFile( relName1, relName1.Replace( '\\', '/' ) );
} }
PluginLog.Information( $"File {relName1} and {relName2} are identical. Deleting the second." ); PluginLog.Information( $"File {relName1} and {relName2} are identical. Deleting the second." );
f2.Delete(); f2.Delete();
} }
public static bool CompareFilesDirectly( FileInfo f1, FileInfo f2 ) public static bool CompareFilesDirectly( FileInfo f1, FileInfo f2 )
{ => File.ReadAllBytes( f1.FullName ).SequenceEqual( File.ReadAllBytes( f2.FullName ) );
return File.ReadAllBytes(f1.FullName).SequenceEqual(File.ReadAllBytes(f2.FullName));
}
public static bool CompareHashes( byte[] f1, byte[] f2 ) public static bool CompareHashes( byte[] f1, byte[] f2 )
{ => StructuralComparisons.StructuralEqualityComparer.Equals( f1, f2 );
return StructuralComparisons.StructuralEqualityComparer.Equals(f1, f2);
}
public byte[] ComputeHash( FileInfo f ) public byte[] ComputeHash( FileInfo f )
{ {
@ -156,8 +161,10 @@ namespace Penumbra.Models
{ {
ClearEmptySubDirectories( subDir ); ClearEmptySubDirectories( subDir );
if( subDir.GetFiles().Length == 0 && subDir.GetDirectories().Length == 0 ) if( subDir.GetFiles().Length == 0 && subDir.GetDirectories().Length == 0 )
{
subDir.Delete(); subDir.Delete();
} }
} }
} }
} }
}

View file

@ -5,8 +5,10 @@ namespace Penumbra.Models
{ {
public enum SelectType public enum SelectType
{ {
Single, Multi Single,
Multi
} }
public struct Option public struct Option
{ {
public string OptionName; public string OptionName;
@ -18,17 +20,22 @@ namespace Penumbra.Models
public bool AddFile( string filePath, string gamePath ) public bool AddFile( string filePath, string gamePath )
{ {
if( OptionFiles.TryGetValue( filePath, out var set ) ) if( OptionFiles.TryGetValue( filePath, out var set ) )
{
return set.Add( gamePath ); return set.Add( gamePath );
else }
OptionFiles[filePath] = new(){ gamePath };
OptionFiles[ filePath ] = new HashSet< string >() { gamePath };
return true; return true;
} }
} }
public struct InstallerInfo { public struct InstallerInfo
{
public string GroupName; public string GroupName;
[JsonConverter( typeof( Newtonsoft.Json.Converters.StringEnumConverter ) )] [JsonConverter( typeof( Newtonsoft.Json.Converters.StringEnumConverter ) )]
public SelectType SelectionType; public SelectType SelectionType;
public List< Option > Options; public List< Option > Options;
} }
} }

View file

@ -1,5 +1,5 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
using Penumbra.Mods; using Penumbra.Mods;
namespace Penumbra.Models namespace Penumbra.Models

View file

@ -1,8 +1,8 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Linq;
using System.IO;
using System; using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
namespace Penumbra.Models namespace Penumbra.Models
{ {
@ -31,8 +31,11 @@ namespace Penumbra.Models
try try
{ {
var meta = JsonConvert.DeserializeObject< ModMeta >( File.ReadAllText( filePath ) ); var meta = JsonConvert.DeserializeObject< ModMeta >( File.ReadAllText( filePath ) );
meta.HasGroupWithConfig = meta.Groups != null && meta.Groups.Count > 0 meta.HasGroupWithConfig =
meta.Groups != null
&& meta.Groups.Count > 0
&& meta.Groups.Values.Any( G => G.SelectionType == SelectType.Multi || G.Options.Count > 1 ); && meta.Groups.Values.Any( G => G.SelectionType == SelectType.Multi || G.Options.Count > 1 );
return meta; return meta;
} }
catch( Exception ) catch( Exception )

View file

@ -16,10 +16,7 @@ namespace Penumbra.Mods
public ResourceMod[] EnabledMods { get; set; } public ResourceMod[] EnabledMods { get; set; }
public ModCollection( DirectoryInfo basePath ) public ModCollection( DirectoryInfo basePath ) => _basePath = basePath;
{
_basePath = basePath;
}
public void Load( bool invertOrder = false ) public void Load( bool invertOrder = false )
{ {
@ -51,7 +48,7 @@ namespace Penumbra.Mods
} }
#endif #endif
ModSettings ??= new(); ModSettings ??= new List< ModInfo >();
var foundMods = new List< string >(); var foundMods = new List< string >();
foreach( var modDir in _basePath.EnumerateDirectories() ) foreach( var modDir in _basePath.EnumerateDirectories() )

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 Dalamud.Plugin;
using Penumbra.Models; using Penumbra.Models;
namespace Penumbra.Mods namespace Penumbra.Mods
@ -16,10 +17,7 @@ namespace Penumbra.Mods
private DirectoryInfo _basePath; private DirectoryInfo _basePath;
public ModManager( Plugin plugin ) public ModManager( Plugin plugin ) => _plugin = plugin;
{
_plugin = plugin;
}
public void DiscoverMods() public void DiscoverMods()
{ {
@ -113,7 +111,7 @@ namespace Penumbra.Mods
mod.FileConflicts?.Clear(); mod.FileConflicts?.Clear();
if( settings.Conf == null ) if( settings.Conf == null )
{ {
settings.Conf = new(); settings.Conf = new Dictionary< string, int >();
_plugin.ModManager.Mods.Save(); _plugin.ModManager.Mods.Save();
} }
@ -154,13 +152,14 @@ namespace Penumbra.Mods
{ {
var relativeFilePath = file.FullName.Substring( baseDir.Length ).TrimStart( '\\' ); var relativeFilePath = file.FullName.Substring( baseDir.Length ).TrimStart( '\\' );
bool doNotAdd = false; var doNotAdd = false;
HashSet< string > paths; HashSet< string > paths;
foreach( var group in mod.Meta.Groups.Select( G => G.Value ) ) foreach( var group in mod.Meta.Groups.Select( G => G.Value ) )
{ {
if( !settings.Conf.TryGetValue( group.GroupName, out var setting ) if( !settings.Conf.TryGetValue( group.GroupName, out var setting )
|| ( group.SelectionType == SelectType.Single && settings.Conf[ group.GroupName ] >= group.Options.Count ) ) || group.SelectionType == SelectType.Single
&& settings.Conf[ group.GroupName ] >= group.Options.Count )
{ {
settings.Conf[ group.GroupName ] = 0; settings.Conf[ group.GroupName ] = 0;
_plugin.ModManager.Mods.Save(); _plugin.ModManager.Mods.Save();
@ -168,10 +167,14 @@ namespace Penumbra.Mods
} }
if( group.Options.Count == 0 ) if( group.Options.Count == 0 )
{
continue; continue;
}
if( group.SelectionType == SelectType.Multi ) if( group.SelectionType == SelectType.Multi )
settings.Conf[ group.GroupName ] &= ( ( 1 << group.Options.Count ) - 1 ); {
settings.Conf[ group.GroupName ] &= ( 1 << group.Options.Count ) - 1;
}
switch( group.SelectionType ) switch( group.SelectionType )
{ {
@ -182,15 +185,10 @@ namespace Penumbra.Mods
} }
else else
{ {
for( var i = 0; i < group.Options.Count; ++i ) if( group.Options.Where( ( o, i ) => i != setting )
{ .Any( option => option.OptionFiles.ContainsKey( relativeFilePath ) ) )
if( i == setting )
continue;
if( group.Options[ i ].OptionFiles.ContainsKey( relativeFilePath ) )
{ {
doNotAdd = true; doNotAdd = true;
break;
}
} }
} }
@ -206,8 +204,10 @@ namespace Penumbra.Mods
} }
} }
else if( group.Options[ i ].OptionFiles.ContainsKey( relativeFilePath ) ) else if( group.Options[ i ].OptionFiles.ContainsKey( relativeFilePath ) )
{
doNotAdd = true; doNotAdd = true;
} }
}
break; break;
} }
@ -215,7 +215,7 @@ namespace Penumbra.Mods
if( !doNotAdd ) if( !doNotAdd )
{ {
AddFiles( new() { relativeFilePath.Replace( '\\', '/' ) }, out doNotAdd, file, registeredFiles, mod ); AddFiles( new HashSet< string > { relativeFilePath.Replace( '\\', '/' ) }, out doNotAdd, file, registeredFiles, mod );
} }
} }
} }
@ -245,8 +245,19 @@ namespace Penumbra.Mods
} }
public void DeleteMod( ResourceMod mod ) public void DeleteMod( ResourceMod mod )
{
if( mod?.ModBasePath?.Exists ?? false )
{
try
{ {
Directory.Delete( mod.ModBasePath.FullName, true ); Directory.Delete( mod.ModBasePath.FullName, true );
}
catch( Exception e )
{
PluginLog.Error( $"Could not delete the mod {mod.ModBasePath.Name}:\n{e}" );
}
}
DiscoverMods(); DiscoverMods();
} }
@ -267,9 +278,7 @@ namespace Penumbra.Mods
} }
public string GetSwappedFilePath( string gameResourcePath ) public string GetSwappedFilePath( string gameResourcePath )
{ => SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null;
return SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null;
}
public string ResolveSwappedOrReplacementFilePath( string gameResourcePath ) public string ResolveSwappedOrReplacementFilePath( string gameResourcePath )
{ {

View file

@ -21,6 +21,10 @@
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Dalamud"> <Reference Include="Dalamud">
<HintPath>$(DALAMUD_ROOT)\Dalamud.dll</HintPath> <HintPath>$(DALAMUD_ROOT)\Dalamud.dll</HintPath>

View file

@ -57,6 +57,7 @@ namespace Penumbra
GameUtils.ReloadPlayerResources(); GameUtils.ReloadPlayerResources();
SettingsInterface = new SettingsInterface( this ); SettingsInterface = new SettingsInterface( this );
PluginInterface.UiBuilder.OnBuildUi += SettingsInterface.Draw; PluginInterface.UiBuilder.OnBuildUi += SettingsInterface.Draw;
PluginDebugTitleStr = $"{Name} - Debug Build"; PluginDebugTitleStr = $"{Name} - Debug Build";
@ -123,9 +124,14 @@ namespace Penumbra
case "redraw": case "redraw":
{ {
if( args.Length > 1 ) if( args.Length > 1 )
{
RefreshActors.RedrawSpecific( PluginInterface.ClientState.Actors, string.Join( " ", args.Skip( 1 ) ) ); RefreshActors.RedrawSpecific( PluginInterface.ClientState.Actors, string.Join( " ", args.Skip( 1 ) ) );
}
else else
{
RefreshActors.RedrawAll( PluginInterface.ClientState.Actors ); RefreshActors.RedrawAll( PluginInterface.ClientState.Actors );
}
break; break;
} }
} }

View file

@ -1,14 +1,14 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using Dalamud.Plugin; using Dalamud.Plugin;
using Penumbra.Structs; using Penumbra.Structs;
using Penumbra.Util; using Penumbra.Util;
using FileMode = Penumbra.Structs.FileMode;
using Reloaded.Hooks; using Reloaded.Hooks;
using Reloaded.Hooks.Definitions; using Reloaded.Hooks.Definitions;
using Reloaded.Hooks.Definitions.X64; using Reloaded.Hooks.Definitions.X64;
using FileMode = Penumbra.Structs.FileMode;
namespace Penumbra namespace Penumbra
{ {
@ -192,7 +192,9 @@ namespace Penumbra
public void Enable() public void Enable()
{ {
if( IsEnabled ) if( IsEnabled )
{
return; return;
}
ReadSqpackHook.Activate(); ReadSqpackHook.Activate();
GetResourceSyncHook.Activate(); GetResourceSyncHook.Activate();
@ -208,7 +210,9 @@ namespace Penumbra
public void Disable() public void Disable()
{ {
if( !IsEnabled ) if( !IsEnabled )
{
return; return;
}
ReadSqpackHook.Disable(); ReadSqpackHook.Disable();
GetResourceSyncHook.Disable(); GetResourceSyncHook.Disable();
@ -220,7 +224,9 @@ namespace Penumbra
public void Dispose() public void Dispose()
{ {
if( IsEnabled ) if( IsEnabled )
{
Disable(); Disable();
}
// ReadSqpackHook.Disable(); // ReadSqpackHook.Disable();
// GetResourceSyncHook.Disable(); // GetResourceSyncHook.Disable();

View file

@ -1,9 +1,10 @@
namespace Penumbra.Structs namespace Penumbra.Structs
{ {
public enum FileMode : uint public enum FileMode : uint
{ {
LoadUnpackedResource = 0, LoadUnpackedResource = 0,
LoadFileResource = 1, // Shit in My Games uses this LoadFileResource = 1, // Shit in My Games uses this
// some shit here, the game does some jump if its < 0xA for other files for some reason but there's no impl, probs debug? // some shit here, the game does some jump if its < 0xA for other files for some reason but there's no impl, probs debug?
LoadIndexResource = 0xA, // load index/index2 LoadIndexResource = 0xA, // load index/index2
LoadSqPackResource = 0xB LoadSqPackResource = 0xB

View file

@ -1,4 +1,4 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Penumbra.Structs namespace Penumbra.Structs
{ {

View file

@ -1,4 +1,4 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Penumbra.Structs namespace Penumbra.Structs
{ {

View file

@ -28,10 +28,12 @@ namespace Penumbra.UI
var effectiveSize = minSize; var effectiveSize = minSize;
if( effectiveSize.X < 0 ) if( effectiveSize.X < 0 )
{
effectiveSize.X = ImGui.GetContentRegionAvail().X; effectiveSize.X = ImGui.GetContentRegionAvail().X;
}
// Ensure width. // Ensure width.
ImGui.Dummy(new(effectiveSize.X, 0)); ImGui.Dummy( new Vector2( effectiveSize.X, 0 ) );
// Ensure left half boundary width/distance. // Ensure left half boundary width/distance.
ImGui.Dummy( halfFrameHeight ); ImGui.Dummy( halfFrameHeight );
@ -44,9 +46,13 @@ namespace Penumbra.UI
ImGui.SameLine(); ImGui.SameLine();
var ret = false; var ret = false;
if( edit ) if( edit )
ret = ImGuiCustom.ResizingTextInput(ref label, 1024); {
ret = ResizingTextInput( ref label, 1024 );
}
else else
{
ImGui.TextUnformatted( label ); ImGui.TextUnformatted( label );
}
var labelMin = ImGui.GetItemRectMin(); var labelMin = ImGui.GetItemRectMin();
var labelMax = ImGui.GetItemRectMax(); var labelMax = ImGui.GetItemRectMax();
@ -76,10 +82,10 @@ namespace Penumbra.UI
public static void EndFramedGroup() public static void EndFramedGroup()
{ {
uint borderColor = ImGui.ColorConvertFloat4ToU32(ImGui.GetStyle().Colors[(int)ImGuiCol.Border]); var borderColor = ImGui.ColorConvertFloat4ToU32( ImGui.GetStyle().Colors[ ( int )ImGuiCol.Border ] );
Vector2 itemSpacing = ImGui.GetStyle().ItemSpacing; var itemSpacing = ImGui.GetStyle().ItemSpacing;
float frameHeight = ImGui.GetFrameHeight(); var frameHeight = ImGui.GetFrameHeight();
Vector2 halfFrameHeight = new(ImGui.GetFrameHeight() / 2, 0); var halfFrameHeight = new Vector2( ImGui.GetFrameHeight() / 2, 0 );
ImGui.PopItemWidth(); ImGui.PopItemWidth();
@ -108,13 +114,17 @@ namespace Penumbra.UI
var frameMax = itemMax - new Vector2( halfFrame.X, 0 ); var frameMax = itemMax - new Vector2( halfFrame.X, 0 );
// Left // Left
DrawClippedRect(new(-float.MaxValue , -float.MaxValue ), new(currentLabelMin.X, float.MaxValue ), frameMin, frameMax, borderColor, halfFrame.X); DrawClippedRect( new Vector2( -float.MaxValue, -float.MaxValue ), new Vector2( currentLabelMin.X, float.MaxValue ), frameMin,
frameMax, borderColor, halfFrame.X );
// Right // Right
DrawClippedRect(new(currentLabelMax.X, -float.MaxValue ), new(float.MaxValue , float.MaxValue ), frameMin, frameMax, borderColor, halfFrame.X); DrawClippedRect( new Vector2( currentLabelMax.X, -float.MaxValue ), new Vector2( float.MaxValue, float.MaxValue ), frameMin,
frameMax, borderColor, halfFrame.X );
// Top // Top
DrawClippedRect(new(currentLabelMin.X, -float.MaxValue ), new(currentLabelMax.X, currentLabelMin.Y), frameMin, frameMax, borderColor, halfFrame.X); DrawClippedRect( new Vector2( currentLabelMin.X, -float.MaxValue ), new Vector2( currentLabelMax.X, currentLabelMin.Y ), frameMin,
frameMax, borderColor, halfFrame.X );
// Bottom // Bottom
DrawClippedRect(new(currentLabelMin.X, currentLabelMax.Y), new(currentLabelMax.X, float.MaxValue ), frameMin, frameMax, borderColor, halfFrame.X); DrawClippedRect( new Vector2( currentLabelMin.X, currentLabelMax.Y ), new Vector2( currentLabelMax.X, float.MaxValue ), frameMin,
frameMax, borderColor, halfFrame.X );
ImGui.PopStyleVar( 2 ); ImGui.PopStyleVar( 2 );
ImGui.SetWindowSize( new Vector2( ImGui.GetWindowSize().X + frameHeight, ImGui.GetWindowSize().Y ) ); ImGui.SetWindowSize( new Vector2( ImGui.GetWindowSize().X + frameHeight, ImGui.GetWindowSize().Y ) );
@ -125,6 +135,6 @@ namespace Penumbra.UI
private static readonly Vector2 ZeroVector = new( 0, 0 ); private static readonly Vector2 ZeroVector = new( 0, 0 );
private static List<(Vector2, Vector2)> labelStack = new(); private static readonly List< (Vector2, Vector2) > labelStack = new();
} }
} }

View file

@ -4,13 +4,16 @@ namespace Penumbra.UI
{ {
public static partial class ImGuiCustom public static partial class ImGuiCustom
{ {
public static bool RenameableCombo(string label, ref int currentItem, ref string newName, string[] items, int numItems) public static bool RenameableCombo( string label, ref int currentItem, out string newName, string[] items, int numItems )
{ {
var ret = false; var ret = false;
newName = ""; newName = "";
var newOption = ""; var newOption = "";
if (ImGui.BeginCombo(label, (numItems > 0) ? items[currentItem] : newOption)) if( !ImGui.BeginCombo( label, numItems > 0 ? items[ currentItem ] : newOption ) )
{ {
return false;
}
for( var i = 0; i < numItems; ++i ) for( var i = 0; i < numItems; ++i )
{ {
var isSelected = i == currentItem; var isSelected = i == currentItem;
@ -22,9 +25,13 @@ namespace Penumbra.UI
ret = true; ret = true;
ImGui.CloseCurrentPopup(); ImGui.CloseCurrentPopup();
} }
if( isSelected ) if( isSelected )
{
ImGui.SetItemDefaultFocus(); ImGui.SetItemDefaultFocus();
} }
}
ImGui.SetNextItemWidth( -1 ); ImGui.SetNextItemWidth( -1 );
if( ImGui.InputText( $"##{label}_new", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue ) ) if( ImGui.InputText( $"##{label}_new", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{ {
@ -33,10 +40,14 @@ namespace Penumbra.UI
ret = true; ret = true;
ImGui.CloseCurrentPopup(); ImGui.CloseCurrentPopup();
} }
if( numItems == 0 ) if( numItems == 0 )
{
ImGui.SetItemDefaultFocus(); ImGui.SetItemDefaultFocus();
ImGui.EndCombo();
} }
ImGui.EndCombo();
return ret; return ret;
} }
} }

View file

@ -8,33 +8,42 @@ namespace Penumbra.UI
public static bool InputOrText( bool editable, string label, ref string text, uint maxLength ) public static bool InputOrText( bool editable, string label, ref string text, uint maxLength )
{ {
if( editable ) if( editable )
{
return ResizingTextInput( label, ref text, maxLength ); return ResizingTextInput( label, ref text, maxLength );
}
ImGui.Text( text ); ImGui.Text( text );
return false; return false;
} }
public static bool ResizingTextInput(string label, ref string input, uint maxLength) => ResizingTextInputIntern(label, ref input, maxLength).Item1; public static bool ResizingTextInput( string label, ref string input, uint maxLength ) =>
ResizingTextInputIntern( label, ref input, maxLength ).Item1;
public static bool ResizingTextInput( ref string input, uint maxLength ) public static bool ResizingTextInput( ref string input, uint maxLength )
{ {
var (ret, id) = ResizingTextInputIntern( $"##{input}", ref input, maxLength ); var (ret, id) = ResizingTextInputIntern( $"##{input}", ref input, maxLength );
if( ret ) if( ret )
_textInputWidths.Remove(id); {
TextInputWidths.Remove( id );
}
return ret; return ret;
} }
private static (bool, uint) ResizingTextInputIntern( string label, ref string input, uint maxLength ) private static (bool, uint) ResizingTextInputIntern( string label, ref string input, uint maxLength )
{ {
var id = ImGui.GetID( label ); var id = ImGui.GetID( label );
if (!_textInputWidths.TryGetValue(id, out var width)) if( !TextInputWidths.TryGetValue( id, out var width ) )
{
width = ImGui.CalcTextSize( input ).X + 10; width = ImGui.CalcTextSize( input ).X + 10;
}
ImGui.SetNextItemWidth( width ); ImGui.SetNextItemWidth( width );
var ret = ImGui.InputText( label, ref input, maxLength, ImGuiInputTextFlags.EnterReturnsTrue ); var ret = ImGui.InputText( label, ref input, maxLength, ImGuiInputTextFlags.EnterReturnsTrue );
_textInputWidths[id] = ImGui.CalcTextSize(input).X + 10; TextInputWidths[ id ] = ImGui.CalcTextSize( input ).X + 10;
return ( ret, id ); return ( ret, id );
} }
private static readonly Dictionary<uint, float> _textInputWidths = new(); private static readonly Dictionary< uint, float > TextInputWidths = new();
} }
} }

View file

@ -21,6 +21,5 @@ namespace Penumbra.UI
ImGui.Text( text ); ImGui.Text( text );
ImGui.SameLine( pos ); ImGui.SameLine( pos );
} }
} }
} }

View file

@ -17,7 +17,8 @@ namespace Penumbra.UI
private static readonly Vector2 WindowSize = new( Width, Height ); private static readonly Vector2 WindowSize = new( Width, Height );
private static readonly Vector2 WindowPosOffset = new( Padding + Width, Padding + Height ); private static readonly Vector2 WindowPosOffset = new( Padding + Width, Padding + Height );
private readonly ImGuiWindowFlags ButtonFlags = ImGuiWindowFlags.AlwaysAutoResize private const ImGuiWindowFlags ButtonFlags =
ImGuiWindowFlags.AlwaysAutoResize
| ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoBackground
| ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDecoration
| ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoMove
@ -36,21 +37,27 @@ namespace Penumbra.UI
public void Draw() public void Draw()
{ {
if( !_condition.Any() && !_base._menu.Visible ) if( _condition.Any() || _base._menu.Visible )
{ {
return;
}
var ss = ImGui.GetIO().DisplaySize; var ss = ImGui.GetIO().DisplaySize;
ImGui.SetNextWindowPos( ss - WindowPosOffset, ImGuiCond.Always ); ImGui.SetNextWindowPos( ss - WindowPosOffset, ImGuiCond.Always );
if( ImGui.Begin(MenuButtonsName, ButtonFlags) ) if( !ImGui.Begin( MenuButtonsName, ButtonFlags ) )
{ {
return;
}
if( ImGui.Button( MenuButtonLabel, WindowSize ) ) if( ImGui.Button( MenuButtonLabel, WindowSize ) )
{
_base.FlipVisibility(); _base.FlipVisibility();
}
ImGui.End(); ImGui.End();
} }
} }
} }
} }
}
}

View file

@ -10,9 +10,10 @@ namespace Penumbra.UI
private const string MenuItemToggle = "Toggle UI"; private const string MenuItemToggle = "Toggle UI";
private const string SlashCommand = "/penumbra"; private const string SlashCommand = "/penumbra";
private const string MenuItemRediscover = "Rediscover Mods"; private const string MenuItemRediscover = "Rediscover Mods";
private const string MenuItemHide = "Hide Menu Bar";
#if DEBUG #if DEBUG
private const bool _showDebugBar = true; private bool _showDebugBar = true;
#else #else
private const bool _showDebugBar = false; private const bool _showDebugBar = false;
#endif #endif
@ -22,15 +23,28 @@ namespace Penumbra.UI
public void Draw() public void Draw()
{ {
if( _showDebugBar && ImGui.BeginMainMenuBar() ) if( !_showDebugBar || !ImGui.BeginMainMenuBar() )
{ {
return;
}
if( ImGui.BeginMenu( MenuLabel ) ) if( ImGui.BeginMenu( MenuLabel ) )
{ {
if( ImGui.MenuItem( MenuItemToggle, SlashCommand, _base._menu.Visible ) ) if( ImGui.MenuItem( MenuItemToggle, SlashCommand, _base._menu.Visible ) )
{
_base.FlipVisibility(); _base.FlipVisibility();
}
if( ImGui.MenuItem( MenuItemRediscover ) ) if( ImGui.MenuItem( MenuItemRediscover ) )
{
_base.ReloadMods(); _base.ReloadMods();
}
#if DEBUG
if( ImGui.MenuItem( MenuItemHide ) )
{
_showDebugBar = false;
}
#endif
ImGui.EndMenu(); ImGui.EndMenu();
} }
@ -40,4 +54,3 @@ namespace Penumbra.UI
} }
} }
} }
}

View file

@ -19,9 +19,9 @@ namespace Penumbra.UI
public SettingsInterface( Plugin plugin ) public SettingsInterface( Plugin plugin )
{ {
_plugin = plugin; _plugin = plugin;
_launchButton = new(this); _launchButton = new LaunchButton( this );
_menuBar = new(this); _menuBar = new MenuBar( this );
_menu = new(this); _menu = new SettingsMenu( this );
} }
public void FlipVisibility() => _menu.Visible = !_menu.Visible; public void FlipVisibility() => _menu.Visible = !_menu.Visible;
@ -35,12 +35,14 @@ namespace Penumbra.UI
private void ReloadMods() private void ReloadMods()
{ {
_menu._installedTab._selector.ClearSelection(); _menu.InstalledTab.Selector.ResetModNamesLower();
_menu.InstalledTab.Selector.ClearSelection();
// create the directory if it doesn't exist // create the directory if it doesn't exist
Directory.CreateDirectory( _plugin.Configuration.CurrentCollection ); Directory.CreateDirectory( _plugin.Configuration.CurrentCollection );
_plugin.ModManager.DiscoverMods( _plugin.Configuration.CurrentCollection ); _plugin.ModManager.DiscoverMods( _plugin.Configuration.CurrentCollection );
_menu._effectiveTab.RebuildFileList(_plugin.Configuration.ShowAdvanced); _menu.EffectiveTab.RebuildFileList( _plugin.Configuration.ShowAdvanced );
_menu.InstalledTab.Selector.ResetModNamesLower();
} }
} }
} }

View file

@ -13,20 +13,20 @@ namespace Penumbra.UI
private static readonly Vector2 MaxSettingsSize = new( 69420, 42069 ); private static readonly Vector2 MaxSettingsSize = new( 69420, 42069 );
private readonly SettingsInterface _base; private readonly SettingsInterface _base;
public readonly TabSettings _settingsTab; private readonly TabSettings _settingsTab;
public readonly TabImport _importTab; private readonly TabImport _importTab;
public readonly TabBrowser _browserTab; private readonly TabBrowser _browserTab;
public readonly TabInstalled _installedTab; public readonly TabInstalled InstalledTab;
public readonly TabEffective _effectiveTab; public readonly TabEffective EffectiveTab;
public SettingsMenu( SettingsInterface ui ) public SettingsMenu( SettingsInterface ui )
{ {
_base = ui; _base = ui;
_settingsTab = new(_base); _settingsTab = new TabSettings( _base );
_importTab = new(_base); _importTab = new TabImport( _base );
_browserTab = new(); _browserTab = new TabBrowser();
_installedTab = new(_base); InstalledTab = new TabInstalled( _base );
_effectiveTab = new(_base); EffectiveTab = new TabEffective( _base );
} }
#if DEBUG #if DEBUG
@ -39,7 +39,9 @@ namespace Penumbra.UI
public void Draw() public void Draw()
{ {
if( !Visible ) if( !Visible )
{
return; return;
}
ImGui.SetNextWindowSizeConstraints( MinSettingsSize, MaxSettingsSize ); ImGui.SetNextWindowSizeConstraints( MinSettingsSize, MaxSettingsSize );
#if DEBUG #if DEBUG
@ -48,7 +50,9 @@ namespace Penumbra.UI
var ret = ImGui.Begin( _base._plugin.Name, ref Visible ); var ret = ImGui.Begin( _base._plugin.Name, ref Visible );
#endif #endif
if( !ret ) if( !ret )
{
return; return;
}
ImGui.BeginTabBar( PenumbraSettingsLabel ); ImGui.BeginTabBar( PenumbraSettingsLabel );
@ -58,10 +62,12 @@ namespace Penumbra.UI
if( !_importTab.IsImporting() ) if( !_importTab.IsImporting() )
{ {
_browserTab.Draw(); _browserTab.Draw();
_installedTab.Draw(); InstalledTab.Draw();
if( _base._plugin.Configuration.ShowAdvanced ) if( _base._plugin.Configuration.ShowAdvanced )
_effectiveTab.Draw(); {
EffectiveTab.Draw();
}
} }
ImGui.EndTabBar(); ImGui.EndTabBar();

View file

@ -12,7 +12,9 @@ namespace Penumbra.UI
{ {
var ret = ImGui.BeginTabItem( "Available Mods" ); var ret = ImGui.BeginTabItem( "Available Mods" );
if( !ret ) if( !ret )
{
return; return;
}
ImGui.Text( "woah" ); ImGui.Text( "woah" );
ImGui.EndTabItem(); ImGui.EndTabItem();

View file

@ -11,9 +11,9 @@ namespace Penumbra.UI
private const string LabelTab = "Effective File List"; private const string LabelTab = "Effective File List";
private const float TextSizePadding = 5f; private const float TextSizePadding = 5f;
private ModManager _mods; private readonly ModManager _mods;
private (string, string)[] _fileList = null; private (string, string)[] _fileList;
private float _maxGamePath = 0f; private float _maxGamePath;
public TabEffective( SettingsInterface ui ) public TabEffective( SettingsInterface ui )
{ {
@ -26,7 +26,7 @@ namespace Penumbra.UI
if( advanced ) if( advanced )
{ {
_fileList = _mods.ResolvedFiles.Select( P => ( P.Value.FullName, P.Key ) ).ToArray(); _fileList = _mods.ResolvedFiles.Select( P => ( P.Value.FullName, P.Key ) ).ToArray();
_maxGamePath = ((_fileList.Length > 0) ? _fileList.Max( P => ImGui.CalcTextSize(P.Item2).X ) : 0f) + TextSizePadding; _maxGamePath = ( _fileList.Length > 0 ? _fileList.Max( P => ImGui.CalcTextSize( P.Item2 ).X ) : 0f ) + TextSizePadding;
} }
else else
{ {
@ -49,12 +49,16 @@ namespace Penumbra.UI
{ {
var ret = ImGui.BeginTabItem( LabelTab ); var ret = ImGui.BeginTabItem( LabelTab );
if( !ret ) if( !ret )
{
return; return;
}
if( ImGui.ListBoxHeader( "##effective_files", AutoFillSize ) ) if( ImGui.ListBoxHeader( "##effective_files", AutoFillSize ) )
{ {
foreach( var file in _fileList ) foreach( var file in _fileList )
{
DrawFileLine( file ); DrawFileLine( file );
}
ImGui.ListBoxFooter(); ImGui.ListBoxFooter();
} }

View file

@ -1,11 +1,11 @@
using ImGuiNET; using System;
using System.IO;
using System.Numerics;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using System.IO;
using System;
using Penumbra.Importer;
using Dalamud.Plugin; using Dalamud.Plugin;
using System.Numerics; using ImGuiNET;
using Penumbra.Importer;
namespace Penumbra.UI namespace Penumbra.UI
{ {
@ -15,9 +15,9 @@ namespace Penumbra.UI
{ {
private const string LabelTab = "Import Mods"; private const string LabelTab = "Import Mods";
private const string LabelImportButton = "Import TexTools Modpacks"; private const string LabelImportButton = "Import TexTools Modpacks";
private const string FileTypeFilter = "TexTools TTMP Modpack (*.ttmp2)|*.ttmp*|All files (*.*)|*.*";
private const string LabelFileDialog = "Pick one or more modpacks."; private const string LabelFileDialog = "Pick one or more modpacks.";
private const string LabelFileImportRunning = "Import in progress..."; private const string LabelFileImportRunning = "Import in progress...";
private const string FileTypeFilter = "TexTools TTMP Modpack (*.ttmp2)|*.ttmp*|All files (*.*)|*.*";
private const string TooltipModpack1 = "Writing modpack to disk before extracting..."; private const string TooltipModpack1 = "Writing modpack to disk before extracting...";
private const string FailedImport = "One or more of your modpacks failed to import.\nPlease submit a bug report."; private const string FailedImport = "One or more of your modpacks failed to import.\nPlease submit a bug report.";
@ -74,6 +74,7 @@ namespace Penumbra.UI
_texToolsImport = null; _texToolsImport = null;
_base.ReloadMods(); _base.ReloadMods();
} }
_isImportRunning = false; _isImportRunning = false;
} ); } );
} }
@ -90,8 +91,11 @@ namespace Penumbra.UI
{ {
ImGui.Button( LabelFileImportRunning ); ImGui.Button( LabelFileImportRunning );
if( _texToolsImport != null ) if( _texToolsImport == null )
{ {
return;
}
switch( _texToolsImport.State ) switch( _texToolsImport.State )
{ {
case ImporterState.None: case ImporterState.None:
@ -113,9 +117,8 @@ namespace Penumbra.UI
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
} }
}
private void DrawFailedImportMessage() private static void DrawFailedImportMessage()
{ {
ImGui.PushStyleColor( ImGuiCol.Text, ColorRed ); ImGui.PushStyleColor( ImGuiCol.Text, ColorRed );
ImGui.Text( FailedImport ); ImGui.Text( FailedImport );
@ -126,15 +129,23 @@ namespace Penumbra.UI
{ {
var ret = ImGui.BeginTabItem( LabelTab ); var ret = ImGui.BeginTabItem( LabelTab );
if( !ret ) if( !ret )
{
return; return;
}
if( !_isImportRunning ) if( !_isImportRunning )
{
DrawImportButton(); DrawImportButton();
}
else else
{
DrawImportProgress(); DrawImportProgress();
}
if( _hasError ) if( _hasError )
{
DrawFailedImportMessage(); DrawFailedImportMessage();
}
ImGui.EndTabItem(); ImGui.EndTabItem();
} }

View file

@ -4,22 +4,22 @@ namespace Penumbra.UI
{ {
public partial class SettingsInterface public partial class SettingsInterface
{ {
private partial class TabInstalled private class TabInstalled
{ {
private const string LabelTab = "Installed Mods"; private const string LabelTab = "Installed Mods";
private readonly SettingsInterface _base; private readonly SettingsInterface _base;
public readonly Selector _selector; public readonly Selector Selector;
public readonly ModPanel _modPanel; public readonly ModPanel ModPanel;
public TabInstalled( SettingsInterface ui ) public TabInstalled( SettingsInterface ui )
{ {
_base = ui; _base = ui;
_selector = new(_base); Selector = new Selector( _base );
_modPanel = new(_base, _selector); ModPanel = new ModPanel( _base, Selector );
} }
private void DrawNoModsAvailable() private static void DrawNoModsAvailable()
{ {
ImGui.Text( "You don't have any mods :(" ); ImGui.Text( "You don't have any mods :(" );
ImGuiCustom.VerticalDistance( 20f ); ImGuiCustom.VerticalDistance( 20f );
@ -33,20 +33,22 @@ namespace Penumbra.UI
{ {
var ret = ImGui.BeginTabItem( LabelTab ); var ret = ImGui.BeginTabItem( LabelTab );
if( !ret ) if( !ret )
{
return; return;
}
if( _base._plugin.ModManager.Mods != null ) if( _base._plugin.ModManager.Mods != null )
{ {
_selector.Draw(); Selector.Draw();
ImGui.SameLine(); ImGui.SameLine();
_modPanel.Draw(); ModPanel.Draw();
} }
else else
{
DrawNoModsAvailable(); DrawNoModsAvailable();
}
ImGui.EndTabItem(); ImGui.EndTabItem();
return;
} }
} }
} }

View file

@ -1,8 +1,7 @@
using Penumbra.Models;
using ImGuiNET;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using ImGuiNET;
using Penumbra.Models;
namespace Penumbra.UI namespace Penumbra.UI
{ {
@ -12,21 +11,22 @@ namespace Penumbra.UI
public static void RemoveOrChange( this List< string > list, string newString, int idx ) public static void RemoveOrChange( this List< string > list, string newString, int idx )
{ {
if( newString?.Length == 0 ) if( newString?.Length == 0 )
{
list.RemoveAt( idx ); list.RemoveAt( idx );
}
else else
{
list[ idx ] = newString; list[ idx ] = newString;
} }
} }
}
public partial class SettingsInterface public partial class SettingsInterface
{ {
private class PluginDetails private partial class PluginDetails
{ {
#region ========== Literals ===============
private const string LabelPluginDetails = "PenumbraPluginDetails"; private const string LabelPluginDetails = "PenumbraPluginDetails";
private const string LabelAboutTab = "About"; private const string LabelAboutTab = "About";
private const string TooltipAboutEdit = "Use Ctrl+Enter for newlines.";
private const string LabelDescEdit = "##descedit";
private const string LabelChangedItemsTab = "Changed Items"; private const string LabelChangedItemsTab = "Changed Items";
private const string LabelChangedItemsHeader = "##changedItems"; private const string LabelChangedItemsHeader = "##changedItems";
private const string LabelChangedItemIdx = "##citem_"; private const string LabelChangedItemIdx = "##citem_";
@ -37,34 +37,21 @@ namespace Penumbra.UI
private const string LabelFileSwapHeader = "##fileSwaps"; private const string LabelFileSwapHeader = "##fileSwaps";
private const string LabelFileListTab = "Files"; private const string LabelFileListTab = "Files";
private const string LabelFileListHeader = "##fileList"; private const string LabelFileListHeader = "##fileList";
private const string TooltipFilesTab = "Green files replace their standard game path counterpart (not in any option) or are in all options of a Single-Select option.\nYellow files are restricted to some options.";
private const string ButtonAddToGroup = "Add to Group";
private const string ButtonRemoveFromGroup = "Remove from Group";
private const string LabelGroupSelect = "##groupSelect"; private const string LabelGroupSelect = "##groupSelect";
private const string LabelOptionSelect = "##optionSelect"; private const string LabelOptionSelect = "##optionSelect";
private const string TextNoOptionAvailable = "[No Option Available]";
private const string LabelConfigurationTab = "Configuration"; private const string LabelConfigurationTab = "Configuration";
private const string LabelNewSingleGroup = "New Single Group";
private const string LabelNewSingleGroupEdit = "##newSingleGroup";
private const string LabelNewMultiGroup = "New Multi Group";
private const string TextDefaultGamePath = "default";
private const string LabelGamePathsEdit = "Game Paths";
private const string LabelGamePathsEditBox = "##gamePathsEdit";
private const string TooltipGamePathText = "Click to copy to clipboard.";
private static readonly string TooltipGamePathsEdit = $"Enter all game paths to add or remove, separated by '{GamePathsSeparator}'.\nUse '{TextDefaultGamePath}' to add the original file path.";
private static readonly string TooltipFilesTabEdit = $"{TooltipFilesTab}\nRed Files are replaced in another group or a different option in this group, but not contained in the current option.";
private const char GamePathsSeparator = ';'; private const string TooltipFilesTab =
"Green files replace their standard game path counterpart (not in any option) or are in all options of a Single-Select option.\n" +
"Yellow files are restricted to some options.";
private const float TextSizePadding = 5f; private const float TextSizePadding = 5f;
private const float OptionSelectionWidth = 140f; private const float OptionSelectionWidth = 140f;
private const float CheckMarkSize = 50f; private const float CheckMarkSize = 50f;
private const float MultiEditBoxWidth = 300f;
private const uint ColorGreen = 0xFF00C800; private const uint ColorGreen = 0xFF00C800;
private const uint ColorYellow = 0xFF00C8C8; private const uint ColorYellow = 0xFF00C8C8;
private const uint ColorRed = 0xFF0000C8; private const uint ColorRed = 0xFF0000C8;
#endregion
#region ========== State ==================
private bool _editMode = false; private bool _editMode = false;
private int _selectedGroupIndex = 0; private int _selectedGroupIndex = 0;
private InstallerInfo? _selectedGroup = null; private InstallerInfo? _selectedGroup = null;
@ -76,29 +63,48 @@ namespace Penumbra.UI
private (string name, bool selected, uint color, string relName)[] _fullFilenameList = null; private (string name, bool selected, uint color, string relName)[] _fullFilenameList = null;
public void SelectGroup(int idx) private readonly Selector _selector;
private readonly SettingsInterface _base;
private void SelectGroup( int idx )
{ {
_selectedGroupIndex = idx; _selectedGroupIndex = idx;
if( _selectedGroupIndex >= Meta?.Groups?.Count ) if( _selectedGroupIndex >= Meta?.Groups?.Count )
{
_selectedGroupIndex = 0; _selectedGroupIndex = 0;
}
if( Meta?.Groups?.Count > 0 ) if( Meta?.Groups?.Count > 0 )
{
_selectedGroup = Meta.Groups.ElementAt( _selectedGroupIndex ).Value; _selectedGroup = Meta.Groups.ElementAt( _selectedGroupIndex ).Value;
}
else else
{
_selectedGroup = null; _selectedGroup = null;
} }
public void SelectGroup() => SelectGroup(_selectedGroupIndex); }
public void SelectOption(int idx) private void SelectGroup() => SelectGroup( _selectedGroupIndex );
private void SelectOption( int idx )
{ {
_selectedOptionIndex = idx; _selectedOptionIndex = idx;
if( _selectedOptionIndex >= _selectedGroup?.Options.Count ) if( _selectedOptionIndex >= _selectedGroup?.Options.Count )
{
_selectedOptionIndex = 0; _selectedOptionIndex = 0;
}
if( _selectedGroup?.Options.Count > 0 ) if( _selectedGroup?.Options.Count > 0 )
{
_selectedOption = ( ( InstallerInfo )_selectedGroup ).Options[ _selectedOptionIndex ]; _selectedOption = ( ( InstallerInfo )_selectedGroup ).Options[ _selectedOptionIndex ];
}
else else
{
_selectedOption = null; _selectedOption = null;
} }
public void SelectOption() => SelectOption(_selectedOptionIndex); }
private void SelectOption() => SelectOption( _selectedOptionIndex );
public void ResetState() public void ResetState()
{ {
@ -109,9 +115,6 @@ namespace Penumbra.UI
SelectOption(); SelectOption();
} }
private readonly Selector _selector;
private readonly SettingsInterface _base;
public PluginDetails( SettingsInterface ui, Selector s ) public PluginDetails( SettingsInterface ui, Selector s )
{ {
_base = ui; _base = ui;
@ -119,26 +122,28 @@ namespace Penumbra.UI
ResetState(); ResetState();
} }
private ModInfo Mod { get{ return _selector.Mod(); } } private ModInfo Mod => _selector.Mod();
private ModMeta Meta { get{ return Mod?.Mod?.Meta; } } private ModMeta Meta => Mod?.Mod?.Meta;
private void Save() private void Save()
{ {
_base._plugin.ModManager.Mods.Save(); _base._plugin.ModManager.Mods.Save();
_base._plugin.ModManager.CalculateEffectiveFileList(); _base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced); _base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
} }
#endregion
#region ========== Tabs ===================
private void DrawAboutTab() private void DrawAboutTab()
{ {
if( !_editMode && Meta.Description?.Length == 0 ) if( !_editMode && Meta.Description?.Length == 0 )
return;
if(ImGui.BeginTabItem( LabelAboutTab ) )
{ {
return;
}
if( !ImGui.BeginTabItem( LabelAboutTab ) )
{
return;
}
var desc = Meta.Description; var desc = Meta.Description;
var flags = _editMode var flags = _editMode
? ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CtrlEnterForNewLine ? ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CtrlEnterForNewLine
@ -151,9 +156,12 @@ namespace Penumbra.UI
Meta.Description = desc; Meta.Description = desc;
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
} }
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipAboutEdit ); ImGui.SetTooltip( TooltipAboutEdit );
} }
}
else else
{ {
ImGui.TextWrapped( desc ); ImGui.TextWrapped( desc );
@ -161,12 +169,15 @@ namespace Penumbra.UI
ImGui.EndTabItem(); ImGui.EndTabItem();
} }
}
private void DrawChangedItemsTab() private void DrawChangedItemsTab()
{ {
if (!_editMode && Meta.ChangedItems?.Count == 0) if( !_editMode && ( Meta.ChangedItems?.Count ?? 0 ) == 0 )
{
return; return;
}
Meta.ChangedItems ??= new List< string >();
var flags = _editMode var flags = _editMode
? ImGuiInputTextFlags.EnterReturnsTrue ? ImGuiInputTextFlags.EnterReturnsTrue
@ -177,8 +188,8 @@ namespace Penumbra.UI
ImGui.SetNextItemWidth( -1 ); ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( LabelChangedItemsHeader, AutoFillSize ) ) if( ImGui.ListBoxHeader( LabelChangedItemsHeader, AutoFillSize ) )
{ {
if (_changedItemsList == null) _changedItemsList ??= Meta.ChangedItems.Select( ( I, index ) => ( $"{LabelChangedItemIdx}{index}", I ) ).ToArray();
_changedItemsList = Meta.ChangedItems.Select( (I, index) => ($"{LabelChangedItemIdx}{index}", I) ).ToArray();
for( var i = 0; i < Meta.ChangedItems.Count; ++i ) for( var i = 0; i < Meta.ChangedItems.Count; ++i )
{ {
ImGui.SetNextItemWidth( -1 ); ImGui.SetNextItemWidth( -1 );
@ -188,6 +199,7 @@ namespace Penumbra.UI
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
} }
} }
var newItem = ""; var newItem = "";
if( _editMode ) if( _editMode )
{ {
@ -197,27 +209,42 @@ namespace Penumbra.UI
if( newItem.Length > 0 ) if( newItem.Length > 0 )
{ {
if( Meta.ChangedItems == null ) if( Meta.ChangedItems == null )
Meta.ChangedItems = new(){ newItem }; {
Meta.ChangedItems = new List< string >() { newItem };
}
else else
{
Meta.ChangedItems.Add( newItem ); Meta.ChangedItems.Add( newItem );
}
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
} }
} }
} }
ImGui.ListBoxFooter(); ImGui.ListBoxFooter();
} }
ImGui.EndTabItem(); ImGui.EndTabItem();
} }
else else
{
_changedItemsList = null; _changedItemsList = null;
} }
}
private void DrawConflictTab() private void DrawConflictTab()
{ {
if( Mod.Mod.FileConflicts.Any() ) if( !Mod.Mod.FileConflicts.Any() )
{ {
if( ImGui.BeginTabItem( LabelConflictsTab ) ) return;
}
if( !ImGui.BeginTabItem( LabelConflictsTab ) )
{ {
return;
}
ImGui.SetNextItemWidth( -1 ); ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( LabelConflictsHeader, AutoFillSize ) ) if( ImGui.ListBoxHeader( LabelConflictsHeader, AutoFillSize ) )
{ {
@ -225,29 +252,36 @@ namespace Penumbra.UI
{ {
var mod = kv.Key; var mod = kv.Key;
if( ImGui.Selectable( mod ) ) if( ImGui.Selectable( mod ) )
{
_selector.SelectModByName( mod ); _selector.SelectModByName( mod );
}
ImGui.Indent( 15 ); ImGui.Indent( 15 );
foreach( var file in kv.Value ) foreach( var file in kv.Value )
{
ImGui.Selectable( file ); ImGui.Selectable( file );
}
ImGui.Unindent( 15 ); ImGui.Unindent( 15 );
} }
ImGui.ListBoxFooter(); ImGui.ListBoxFooter();
} }
ImGui.EndTabItem(); ImGui.EndTabItem();
} }
}
}
private void DrawFileSwapTab() private void DrawFileSwapTab()
{ {
if( Meta.FileSwaps.Any() ) if( !Meta.FileSwaps.Any() )
{ {
return;
}
if( ImGui.BeginTabItem( LabelFileSwapTab ) ) if( ImGui.BeginTabItem( LabelFileSwapTab ) )
{ {
if (_fileSwapOffset == null) _fileSwapOffset ??= Meta.FileSwaps.Max( P => ImGui.CalcTextSize( P.Key ).X ) + TextSizePadding;
_fileSwapOffset = Meta.FileSwaps.Max( P => ImGui.CalcTextSize(P.Key).X) + TextSizePadding;
ImGui.SetNextItemWidth( -1 ); ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( LabelFileSwapHeader, AutoFillSize ) ) if( ImGui.ListBoxHeader( LabelFileSwapHeader, AutoFillSize ) )
{ {
@ -259,41 +293,58 @@ namespace Penumbra.UI
ImGui.SameLine(); ImGui.SameLine();
ImGui.Selectable( file.Value ); ImGui.Selectable( file.Value );
} }
ImGui.ListBoxFooter(); ImGui.ListBoxFooter();
} }
ImGui.EndTabItem(); ImGui.EndTabItem();
} }
else else
{
_fileSwapOffset = null; _fileSwapOffset = null;
} }
} }
#endregion
#region ========== FileList ===============
private void UpdateFilenameList() private void UpdateFilenameList()
{ {
if (_fullFilenameList == null) if( _fullFilenameList != null )
{ {
return;
}
var len = Mod.Mod.ModBasePath.FullName.Length; var len = Mod.Mod.ModBasePath.FullName.Length;
_fullFilenameList = Mod.Mod.ModFiles.Select( F => ( F.FullName, false, ColorGreen, "" ) ).ToArray(); _fullFilenameList = Mod.Mod.ModFiles.Select( F => ( F.FullName, false, ColorGreen, "" ) ).ToArray();
if( Meta.Groups?.Count == 0 ) if( Meta.Groups?.Count == 0 )
{
return; return;
}
for( var i = 0; i < Mod.Mod.ModFiles.Count; ++i ) for( var i = 0; i < Mod.Mod.ModFiles.Count; ++i )
{ {
_fullFilenameList[ i ].relName = _fullFilenameList[ i ].name.Substring( len ).TrimStart( '\\' ); _fullFilenameList[ i ].relName = _fullFilenameList[ i ].name.Substring( len ).TrimStart( '\\' );
foreach (var Group in Meta.Groups.Values) if( Meta.Groups == null )
{
continue;
}
foreach( var group in Meta.Groups.Values )
{ {
var inAll = true; var inAll = true;
foreach (var Option in Group.Options) foreach( var option in group.Options )
{
if( option.OptionFiles.ContainsKey( _fullFilenameList[ i ].relName ) )
{ {
if (Option.OptionFiles.ContainsKey(_fullFilenameList[i].relName))
_fullFilenameList[ i ].color = ColorYellow; _fullFilenameList[ i ].color = ColorYellow;
}
else else
{
inAll = false; inAll = false;
} }
if (inAll && Group.SelectionType == SelectType.Single) }
if( inAll && group.SelectionType == SelectType.Single )
{
_fullFilenameList[ i ].color = ColorGreen; _fullFilenameList[ i ].color = ColorGreen;
} }
} }
@ -302,10 +353,15 @@ namespace Penumbra.UI
private void DrawFileListTab() private void DrawFileListTab()
{ {
if( ImGui.BeginTabItem( LabelFileListTab ) ) if( !ImGui.BeginTabItem( LabelFileListTab ) )
{ {
return;
}
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipFilesTab ); ImGui.SetTooltip( TooltipFilesTab );
}
ImGui.SetNextItemWidth( -1 ); ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( LabelFileListHeader, AutoFillSize ) ) if( ImGui.ListBoxHeader( LabelFileListHeader, AutoFillSize ) )
@ -317,104 +373,85 @@ namespace Penumbra.UI
ImGui.Selectable( file.name ); ImGui.Selectable( file.name );
ImGui.PopStyleColor(); ImGui.PopStyleColor();
} }
ImGui.ListBoxFooter(); ImGui.ListBoxFooter();
} }
else else
{
_fullFilenameList = null; _fullFilenameList = null;
ImGui.EndTabItem();
} }
ImGui.EndTabItem();
} }
private void HandleSelectedFilesButton( bool remove ) private void HandleSelectedFilesButton( bool remove )
{ {
if( _selectedOption == null ) if( _selectedOption == null )
{
return; return;
}
var option = ( Option )_selectedOption; var option = ( Option )_selectedOption;
var gamePaths = _currentGamePaths.Split( ';' ); var gamePaths = _currentGamePaths.Split( ';' );
if( gamePaths.Length == 0 || gamePaths[ 0 ].Length == 0 ) if( gamePaths.Length == 0 || gamePaths[ 0 ].Length == 0 )
{
return; return;
int? defaultIndex = null;
for (var i = 0; i < gamePaths.Length; ++i)
{
if (gamePaths[i] == TextDefaultGamePath )
{
defaultIndex = i;
break;
}
} }
var baseLength = Mod.Mod.ModBasePath.FullName.Length; var defaultIndex = gamePaths.IndexOf( p => p == TextDefaultGamePath );
var changed = false; var changed = false;
for( var i = 0; i < Mod.Mod.ModFiles.Count; ++i ) for( var i = 0; i < Mod.Mod.ModFiles.Count; ++i )
{ {
if( !_fullFilenameList[ i ].selected ) if( !_fullFilenameList[ i ].selected )
{
continue; continue;
}
var fileName = _fullFilenameList[ i ].relName; var fileName = _fullFilenameList[ i ].relName;
if (defaultIndex != null) if( defaultIndex >= 0 )
{
gamePaths[ ( int )defaultIndex ] = fileName.Replace( '\\', '/' ); gamePaths[ ( int )defaultIndex ] = fileName.Replace( '\\', '/' );
}
if( remove && option.OptionFiles.TryGetValue( fileName, out var setPaths ) ) if( remove && option.OptionFiles.TryGetValue( fileName, out var setPaths ) )
{ {
if( setPaths.RemoveWhere( P => gamePaths.Contains( P ) ) > 0 ) if( setPaths.RemoveWhere( P => gamePaths.Contains( P ) ) > 0 )
{
changed = true; changed = true;
}
if( setPaths.Count == 0 && option.OptionFiles.Remove( fileName ) ) if( setPaths.Count == 0 && option.OptionFiles.Remove( fileName ) )
{
changed = true; changed = true;
} }
}
else else
{ {
foreach(var gamePath in gamePaths) changed = gamePaths.Aggregate( changed, ( current, gamePath ) => current | option.AddFile( fileName, gamePath ) );
changed |= option.AddFile(fileName, gamePath);
} }
} }
if( changed ) if( changed )
{
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
} }
}
private void DrawAddToGroupButton() private void DrawAddToGroupButton()
{ {
if( ImGui.Button( ButtonAddToGroup ) ) if( ImGui.Button( ButtonAddToGroup ) )
{
HandleSelectedFilesButton( false ); HandleSelectedFilesButton( false );
} }
}
private void DrawRemoveFromGroupButton() private void DrawRemoveFromGroupButton()
{ {
if( ImGui.Button( ButtonRemoveFromGroup ) ) if( ImGui.Button( ButtonRemoveFromGroup ) )
{
HandleSelectedFilesButton( true ); HandleSelectedFilesButton( true );
} }
private void DrawEditGroupSelector()
{
ImGui.SetNextItemWidth( OptionSelectionWidth );
if (Meta.Groups.Count == 0)
{
ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex, TextNoOptionAvailable, 1);
}
else
{
if (ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex, Meta.Groups.Values.Select( G => G.GroupName ).ToArray(), Meta.Groups.Count))
{
SelectGroup();
SelectOption(0);
}
}
}
private void DrawEditOptionSelector()
{
ImGui.SameLine();
ImGui.SetNextItemWidth( OptionSelectionWidth );
if (_selectedGroup?.Options.Count == 0)
{
ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, TextNoOptionAvailable, 1);
return;
}
var group = (InstallerInfo) _selectedGroup;
if (ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, group.Options.Select(O => O.OptionName).ToArray(), group.Options.Count))
SelectOption();
} }
private void DrawGamePathInput() private void DrawGamePathInput()
@ -424,19 +461,34 @@ namespace Penumbra.UI
ImGui.SetNextItemWidth( -1 ); ImGui.SetNextItemWidth( -1 );
ImGui.InputText( LabelGamePathsEditBox, ref _currentGamePaths, 128 ); ImGui.InputText( LabelGamePathsEditBox, ref _currentGamePaths, 128 );
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipGamePathsEdit ); ImGui.SetTooltip( TooltipGamePathsEdit );
} }
}
private void DrawGroupRow() private void DrawGroupRow()
{ {
if( _selectedGroup == null ) if( _selectedGroup == null )
{
SelectGroup(); SelectGroup();
if (_selectedOption == null) }
SelectOption();
if( _selectedOption == null )
{
SelectOption();
}
if( !DrawEditGroupSelector() )
{
return;
}
DrawEditGroupSelector();
ImGui.SameLine(); ImGui.SameLine();
DrawEditOptionSelector(); if( !DrawEditOptionSelector() )
{
return;
}
ImGui.SameLine(); ImGui.SameLine();
DrawAddToGroupButton(); DrawAddToGroupButton();
ImGui.SameLine(); ImGui.SameLine();
@ -451,7 +503,10 @@ namespace Penumbra.UI
{ {
var loc = _fullFilenameList[ idx ].color; var loc = _fullFilenameList[ idx ].color;
if( loc == colorNormal ) if( loc == colorNormal )
{
loc = colorReplace; loc = colorReplace;
}
ImGui.PushStyleColor( ImGuiCol.Text, loc ); ImGui.PushStyleColor( ImGuiCol.Text, loc );
ImGui.Selectable( _fullFilenameList[ idx ].name, ref _fullFilenameList[ idx ].selected ); ImGui.Selectable( _fullFilenameList[ idx ].name, ref _fullFilenameList[ idx ].selected );
ImGui.PopStyleColor(); ImGui.PopStyleColor();
@ -472,282 +527,54 @@ namespace Penumbra.UI
ImGui.Indent( indent ); ImGui.Indent( indent );
foreach( var gamePath in gamePaths ) foreach( var gamePath in gamePaths )
{ {
ImGui.Text(gamePath); var tmp = gamePath;
if (ImGui.IsItemClicked()) if( ImGui.InputText( $"##{fileName}_{gamePath}", ref tmp, 128, ImGuiInputTextFlags.EnterReturnsTrue )
ImGui.SetClipboardText(gamePath); && tmp != gamePath )
if (ImGui.IsItemHovered()) {
ImGui.SetTooltip( TooltipGamePathText ); gamePaths.Remove( gamePath );
if( tmp.Length > 0 )
{
gamePaths.Add( tmp );
} }
_selector.SaveCurrentMod();
_selector.ReloadCurrentMod();
}
}
ImGui.Unindent( indent ); ImGui.Unindent( indent );
} }
else else
{
Selectable( ColorYellow, ColorRed ); Selectable( ColorYellow, ColorRed );
} }
private void DrawFileListTabEdit()
{
if( ImGui.BeginTabItem( LabelFileListTab ) )
{
UpdateFilenameList();
if (ImGui.IsItemHovered())
ImGui.SetTooltip( _editMode ? TooltipFilesTabEdit : TooltipFilesTab );
ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( LabelFileListHeader, AutoFillSize - new Vector2(0, 1.5f * ImGui.GetTextLineHeight()) ) )
for(var i = 0; i < Mod.Mod.ModFiles.Count; ++i)
DrawFileAndGamePaths(i);
ImGui.ListBoxFooter();
DrawGroupRow();
ImGui.EndTabItem();
} }
else
_fullFilenameList = null;
}
#endregion
#region ========== Configuration ==========
#region ========== MultiSelectorEdit ==========
private bool DrawMultiSelectorEditBegin(InstallerInfo group)
{
var groupName = group.GroupName;
if (ImGuiCustom.BeginFramedGroupEdit(ref groupName)
&& groupName != group.GroupName && !Meta.Groups.ContainsKey(groupName))
{
var oldConf = Mod.Conf[group.GroupName];
Meta.Groups.Remove(group.GroupName);
Mod.Conf.Remove(group.GroupName);
if (groupName.Length > 0)
{
Meta.Groups[groupName] = new(){ GroupName = groupName, SelectionType = SelectType.Multi, Options = group.Options };
Mod.Conf[groupName] = oldConf;
}
return true;
}
return false;
}
private void DrawMultiSelectorEditAdd(InstallerInfo group, float nameBoxStart)
{
var newOption = "";
ImGui.SetCursorPosX(nameBoxStart);
ImGui.SetNextItemWidth(MultiEditBoxWidth);
if (ImGui.InputText($"##new_{group.GroupName}_l", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue))
{
if (newOption.Length != 0)
{
group.Options.Add(new(){ OptionName = newOption, OptionDesc = "", OptionFiles = new() });
_selector.SaveCurrentMod();
}
}
}
private void DrawMultiSelectorEdit(InstallerInfo group)
{
var nameBoxStart = CheckMarkSize;
var flag = Mod.Conf[group.GroupName];
var modChanged = DrawMultiSelectorEditBegin(group);
for (var i = 0; i < group.Options.Count; ++i)
{
var opt = group.Options[i];
var label = $"##{opt.OptionName}_{group.GroupName}";
DrawMultiSelectorCheckBox(group, i, flag, label);
ImGui.SameLine();
var newName = opt.OptionName;
if (nameBoxStart == CheckMarkSize)
nameBoxStart = ImGui.GetCursorPosX();
ImGui.SetNextItemWidth(MultiEditBoxWidth);
if (ImGui.InputText($"{label}_l", ref newName, 64, ImGuiInputTextFlags.EnterReturnsTrue))
{
if (newName.Length == 0)
{
group.Options.RemoveAt(i);
var bitmaskFront = (1 << i) - 1;
Mod.Conf[group.GroupName] = (flag & bitmaskFront) | ((flag & ~bitmaskFront) >> 1);
modChanged = true;
}
else if (newName != opt.OptionName)
{
group.Options[i] = new(){ OptionName = newName, OptionDesc = opt.OptionDesc, OptionFiles = opt.OptionFiles };
_selector.SaveCurrentMod();
}
}
}
DrawMultiSelectorEditAdd(group, nameBoxStart);
if (modChanged)
{
_selector.SaveCurrentMod();
Save();
}
ImGuiCustom.EndFramedGroup();
}
#endregion
#region ========== SingleSelectorEdit ==========
private bool DrawSingleSelectorEditGroup(InstallerInfo group)
{
var groupName = group.GroupName;
if (ImGui.InputText($"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue)
&& !Meta.Groups.ContainsKey(groupName))
{
var oldConf = Mod.Conf[group.GroupName];
if (groupName != group.GroupName)
{
Meta.Groups.Remove(group.GroupName);
Mod.Conf.Remove(group.GroupName);
}
if (groupName.Length > 0)
{
Meta.Groups.Add(groupName, new InstallerInfo(){ GroupName = groupName, Options = group.Options, SelectionType = SelectType.Single } );
Mod.Conf[groupName] = oldConf;
}
return true;
}
return false;
}
private float DrawSingleSelectorEdit(InstallerInfo group)
{
var code = Mod.Conf[group.GroupName];
var selectionChanged = false;
var modChanged = false;
var newName = "";
if (ImGuiCustom.RenameableCombo($"##{group.GroupName}", ref code, ref newName, group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count))
{
if (code == group.Options.Count)
{
if (newName.Length > 0)
{
selectionChanged = true;
modChanged = true;
Mod.Conf[group.GroupName] = code;
group.Options.Add(new(){ OptionName = newName, OptionDesc = "", OptionFiles = new()});
}
}
else
{
if (newName.Length == 0)
{
modChanged = true;
group.Options.RemoveAt(code);
if (code >= group.Options.Count)
code = 0;
}
else if (newName != group.Options[code].OptionName)
{
modChanged = true;
group.Options[code] = new Option(){ OptionName = newName, OptionDesc = group.Options[code].OptionDesc, OptionFiles = group.Options[code].OptionFiles};
}
if (Mod.Conf[group.GroupName] != code)
{
selectionChanged = true;
Mod.Conf[group.GroupName] = code;
}
}
}
ImGui.SameLine();
var labelEditPos = ImGui.GetCursorPosX();
modChanged |= DrawSingleSelectorEditGroup(group);
if (modChanged)
_selector.SaveCurrentMod();
if (selectionChanged)
Save();
return labelEditPos;
}
#endregion
private void AddNewGroup(string newGroup, SelectType selectType)
{
if (!Meta.Groups.ContainsKey(newGroup) && newGroup.Length > 0)
{
Meta.Groups[newGroup] = new ()
{
GroupName = newGroup,
SelectionType = selectType,
Options = new()
} ;
Mod.Conf[newGroup] = 0;
_selector.SaveCurrentMod();
Save();
}
}
private void DrawAddSingleGroupField(float labelEditPos)
{
var newGroup = "";
if(labelEditPos == CheckMarkSize)
{
ImGui.SetCursorPosX(CheckMarkSize);
ImGui.SetNextItemWidth(MultiEditBoxWidth);
if (ImGui.InputText(LabelNewSingleGroup, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue))
AddNewGroup(newGroup, SelectType.Single);
}
else
{
ImGuiCustom.RightJustifiedLabel(labelEditPos, LabelNewSingleGroup );
if (ImGui.InputText(LabelNewSingleGroupEdit, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue))
AddNewGroup(newGroup, SelectType.Single);
}
}
private void DrawAddMultiGroupField()
{
var newGroup = "";
ImGui.SetCursorPosX(CheckMarkSize);
ImGui.SetNextItemWidth(MultiEditBoxWidth);
if (ImGui.InputText(LabelNewMultiGroup, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue))
AddNewGroup(newGroup, SelectType.Multi);
}
private void DrawGroupSelectorsEdit()
{
var labelEditPos = CheckMarkSize;
foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Single ) )
labelEditPos = DrawSingleSelectorEdit(g);
DrawAddSingleGroupField(labelEditPos);
foreach(var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Multi ))
DrawMultiSelectorEdit(g);
DrawAddMultiGroupField();
}
#region Non-Edit
private void DrawMultiSelectorCheckBox( InstallerInfo group, int idx, int flag, string label ) private void DrawMultiSelectorCheckBox( InstallerInfo group, int idx, int flag, string label )
{ {
var opt = group.Options[ idx ]; var opt = group.Options[ idx ];
var enabled = ( flag & ( 1 << idx ) ) != 0; var enabled = ( flag & ( 1 << idx ) ) != 0;
var oldEnabled = enabled; var oldEnabled = enabled;
if (ImGui.Checkbox(label, ref enabled)) if( ImGui.Checkbox( label, ref enabled ) && oldEnabled != enabled )
{ {
if (oldEnabled != enabled) Mod.Conf[ group.GroupName ] ^= 1 << idx;
{
Mod.Conf[group.GroupName] ^= (1 << idx);
Save(); Save();
} }
} }
}
private void DrawMultiSelector( InstallerInfo group ) private void DrawMultiSelector( InstallerInfo group )
{ {
if( group.Options.Count == 0 ) if( group.Options.Count == 0 )
{
return; return;
}
ImGuiCustom.BeginFramedGroup( group.GroupName ); ImGuiCustom.BeginFramedGroup( group.GroupName );
for( var i = 0; i < group.Options.Count; ++i ) for( var i = 0; i < group.Options.Count; ++i )
DrawMultiSelectorCheckBox(group, i, Mod.Conf[group.GroupName], $"{group.Options[i].OptionName}##{group.GroupName}"); {
DrawMultiSelectorCheckBox( group, i, Mod.Conf[ group.GroupName ],
$"{group.Options[ i ].OptionName}##{group.GroupName}" );
}
ImGuiCustom.EndFramedGroup(); ImGuiCustom.EndFramedGroup();
} }
@ -755,9 +582,13 @@ namespace Penumbra.UI
private void DrawSingleSelector( InstallerInfo group ) private void DrawSingleSelector( InstallerInfo group )
{ {
if( group.Options.Count < 2 ) if( group.Options.Count < 2 )
{
return; return;
}
var code = Mod.Conf[ group.GroupName ]; var code = Mod.Conf[ group.GroupName ];
if( ImGui.Combo( group.GroupName, ref code, group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) ) if( ImGui.Combo( group.GroupName, ref code
, group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) )
{ {
Mod.Conf[ group.GroupName ] = code; Mod.Conf[ group.GroupName ] = code;
Save(); Save();
@ -767,29 +598,39 @@ namespace Penumbra.UI
private void DrawGroupSelectors() private void DrawGroupSelectors()
{ {
foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Single ) ) foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Single ) )
{
DrawSingleSelector( g ); DrawSingleSelector( g );
}
foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Multi ) ) foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Multi ) )
{
DrawMultiSelector( g ); DrawMultiSelector( g );
}
return; return;
} }
#endregion
private void DrawConfigurationTab() private void DrawConfigurationTab()
{ {
if( !_editMode && !Meta.HasGroupWithConfig ) if( !_editMode && !Meta.HasGroupWithConfig )
{
return; return;
}
if( ImGui.BeginTabItem( LabelConfigurationTab ) ) if( ImGui.BeginTabItem( LabelConfigurationTab ) )
{ {
if( _editMode ) if( _editMode )
{
DrawGroupSelectorsEdit(); DrawGroupSelectorsEdit();
}
else else
{
DrawGroupSelectors(); DrawGroupSelectors();
}
ImGui.EndTabItem(); ImGui.EndTabItem();
} }
} }
#endregion
public void Draw( bool editMode ) public void Draw( bool editMode )
{ {
@ -800,9 +641,14 @@ namespace Penumbra.UI
DrawChangedItemsTab(); DrawChangedItemsTab();
DrawConfigurationTab(); DrawConfigurationTab();
if( _editMode ) if( _editMode )
{
DrawFileListTabEdit(); DrawFileListTabEdit();
}
else else
{
DrawFileListTab(); DrawFileListTab();
}
DrawFileSwapTab(); DrawFileSwapTab();
DrawConflictTab(); DrawConflictTab();

View file

@ -0,0 +1,355 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using ImGuiNET;
using Penumbra.Models;
namespace Penumbra.UI
{
public partial class SettingsInterface
{
private partial class PluginDetails
{
private const string LabelDescEdit = "##descedit";
private const string LabelNewSingleGroup = "New Single Group";
private const string LabelNewSingleGroupEdit = "##newSingleGroup";
private const string LabelNewMultiGroup = "New Multi Group";
private const string LabelGamePathsEdit = "Game Paths";
private const string LabelGamePathsEditBox = "##gamePathsEdit";
private const string ButtonAddToGroup = "Add to Group";
private const string ButtonRemoveFromGroup = "Remove from Group";
private const string TooltipAboutEdit = "Use Ctrl+Enter for newlines.";
private const string TextNoOptionAvailable = "[Not Available]";
private const string TextDefaultGamePath = "default";
private const char GamePathsSeparator = ';';
private static readonly string TooltipFilesTabEdit =
$"{TooltipFilesTab}\n" +
$"Red Files are replaced in another group or a different option in this group, but not contained in the current option.";
private static readonly string TooltipGamePathsEdit =
$"Enter all game paths to add or remove, separated by '{GamePathsSeparator}'.\n" +
$"Use '{TextDefaultGamePath}' to add the original file path.";
private const float MultiEditBoxWidth = 300f;
private bool DrawEditGroupSelector()
{
ImGui.SetNextItemWidth( OptionSelectionWidth );
if( Meta.Groups.Count == 0 )
{
ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex, TextNoOptionAvailable, 1 );
return false;
}
if( ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex
, Meta.Groups.Values.Select( G => G.GroupName ).ToArray()
, Meta.Groups.Count ) )
{
SelectGroup();
SelectOption( 0 );
}
return true;
}
private bool DrawEditOptionSelector()
{
ImGui.SameLine();
ImGui.SetNextItemWidth( OptionSelectionWidth );
if( ( _selectedGroup?.Options.Count ?? 0 ) == 0 )
{
ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, TextNoOptionAvailable, 1 );
return false;
}
var group = ( InstallerInfo )_selectedGroup;
if( ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, group.Options.Select( O => O.OptionName ).ToArray(),
group.Options.Count ) )
{
SelectOption();
}
return true;
}
private void DrawFileListTabEdit()
{
if( ImGui.BeginTabItem( LabelFileListTab ) )
{
UpdateFilenameList();
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( _editMode ? TooltipFilesTabEdit : TooltipFilesTab );
}
ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( LabelFileListHeader, AutoFillSize - new Vector2( 0, 1.5f * ImGui.GetTextLineHeight() ) ) )
{
for( var i = 0; i < Mod.Mod.ModFiles.Count; ++i )
{
DrawFileAndGamePaths( i );
}
}
ImGui.ListBoxFooter();
DrawGroupRow();
ImGui.EndTabItem();
}
else
{
_fullFilenameList = null;
}
}
private bool DrawMultiSelectorEditBegin( InstallerInfo group )
{
var groupName = group.GroupName;
if( ImGuiCustom.BeginFramedGroupEdit( ref groupName )
&& groupName != group.GroupName && !Meta.Groups.ContainsKey( groupName ) )
{
var oldConf = Mod.Conf[ group.GroupName ];
Meta.Groups.Remove( group.GroupName );
Mod.Conf.Remove( group.GroupName );
if( groupName.Length > 0 )
{
Meta.Groups[ groupName ] = new InstallerInfo()
{ GroupName = groupName, SelectionType = SelectType.Multi, Options = group.Options };
Mod.Conf[ groupName ] = oldConf;
}
return true;
}
return false;
}
private void DrawMultiSelectorEditAdd( InstallerInfo group, float nameBoxStart )
{
var newOption = "";
ImGui.SetCursorPosX( nameBoxStart );
ImGui.SetNextItemWidth( MultiEditBoxWidth );
if( ImGui.InputText( $"##new_{group.GroupName}_l", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue )
&& newOption.Length != 0 )
{
group.Options.Add( new Option()
{ OptionName = newOption, OptionDesc = "", OptionFiles = new Dictionary< string, HashSet< string > >() } );
_selector.SaveCurrentMod();
}
}
private void DrawMultiSelectorEdit( InstallerInfo group )
{
var nameBoxStart = CheckMarkSize;
var flag = Mod.Conf[ group.GroupName ];
var modChanged = DrawMultiSelectorEditBegin( group );
for( var i = 0; i < group.Options.Count; ++i )
{
var opt = group.Options[ i ];
var label = $"##{opt.OptionName}_{group.GroupName}";
DrawMultiSelectorCheckBox( group, i, flag, label );
ImGui.SameLine();
var newName = opt.OptionName;
if( nameBoxStart == CheckMarkSize )
{
nameBoxStart = ImGui.GetCursorPosX();
}
ImGui.SetNextItemWidth( MultiEditBoxWidth );
if( ImGui.InputText( $"{label}_l", ref newName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{
if( newName.Length == 0 )
{
group.Options.RemoveAt( i );
var bitmaskFront = ( 1 << i ) - 1;
Mod.Conf[ group.GroupName ] = ( flag & bitmaskFront ) | ( ( flag & ~bitmaskFront ) >> 1 );
modChanged = true;
}
else if( newName != opt.OptionName )
{
group.Options[ i ] = new Option()
{ OptionName = newName, OptionDesc = opt.OptionDesc, OptionFiles = opt.OptionFiles };
_selector.SaveCurrentMod();
}
}
}
DrawMultiSelectorEditAdd( group, nameBoxStart );
if( modChanged )
{
_selector.SaveCurrentMod();
Save();
}
ImGuiCustom.EndFramedGroup();
}
private bool DrawSingleSelectorEditGroup( InstallerInfo group )
{
var groupName = group.GroupName;
if( ImGui.InputText( $"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue )
&& !Meta.Groups.ContainsKey( groupName ) )
{
var oldConf = Mod.Conf[ group.GroupName ];
if( groupName != group.GroupName )
{
Meta.Groups.Remove( group.GroupName );
Mod.Conf.Remove( group.GroupName );
}
if( groupName.Length > 0 )
{
Meta.Groups.Add( groupName,
new InstallerInfo() { GroupName = groupName, Options = group.Options, SelectionType = SelectType.Single } );
Mod.Conf[ groupName ] = oldConf;
}
return true;
}
return false;
}
private float DrawSingleSelectorEdit( InstallerInfo group )
{
var code = Mod.Conf[ group.GroupName ];
var selectionChanged = false;
var modChanged = false;
if( ImGuiCustom.RenameableCombo( $"##{group.GroupName}", ref code, out var newName,
group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) )
{
if( code == group.Options.Count )
{
if( newName.Length > 0 )
{
selectionChanged = true;
modChanged = true;
Mod.Conf[ group.GroupName ] = code;
group.Options.Add( new Option()
{ OptionName = newName, OptionDesc = "", OptionFiles = new Dictionary< string, HashSet< string > >() } );
}
}
else
{
if( newName.Length == 0 )
{
modChanged = true;
group.Options.RemoveAt( code );
if( code >= group.Options.Count )
{
code = 0;
}
}
else if( newName != group.Options[ code ].OptionName )
{
modChanged = true;
group.Options[ code ] = new Option()
{
OptionName = newName, OptionDesc = group.Options[ code ].OptionDesc,
OptionFiles = group.Options[ code ].OptionFiles
};
}
if( Mod.Conf[ group.GroupName ] != code )
{
selectionChanged = true;
Mod.Conf[ group.GroupName ] = code;
}
}
}
ImGui.SameLine();
var labelEditPos = ImGui.GetCursorPosX();
modChanged |= DrawSingleSelectorEditGroup( group );
if( modChanged )
{
_selector.SaveCurrentMod();
}
if( selectionChanged )
{
Save();
}
return labelEditPos;
}
private void AddNewGroup( string newGroup, SelectType selectType )
{
if( Meta.Groups.ContainsKey( newGroup ) || newGroup.Length <= 0 )
{
return;
}
Meta.Groups[ newGroup ] = new InstallerInfo()
{
GroupName = newGroup,
SelectionType = selectType,
Options = new List< Option >()
};
Mod.Conf[ newGroup ] = 0;
_selector.SaveCurrentMod();
Save();
}
private void DrawAddSingleGroupField( float labelEditPos )
{
var newGroup = "";
if( labelEditPos == CheckMarkSize )
{
ImGui.SetCursorPosX( CheckMarkSize );
ImGui.SetNextItemWidth( MultiEditBoxWidth );
if( ImGui.InputText( LabelNewSingleGroup, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{
AddNewGroup( newGroup, SelectType.Single );
}
}
else
{
ImGuiCustom.RightJustifiedLabel( labelEditPos, LabelNewSingleGroup );
if( ImGui.InputText( LabelNewSingleGroupEdit, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{
AddNewGroup( newGroup, SelectType.Single );
}
}
}
private void DrawAddMultiGroupField()
{
var newGroup = "";
ImGui.SetCursorPosX( CheckMarkSize );
ImGui.SetNextItemWidth( MultiEditBoxWidth );
if( ImGui.InputText( LabelNewMultiGroup, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{
AddNewGroup( newGroup, SelectType.Multi );
}
}
private void DrawGroupSelectorsEdit()
{
var labelEditPos = CheckMarkSize;
foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Single ) )
{
labelEditPos = DrawSingleSelectorEdit( g );
}
DrawAddSingleGroupField( labelEditPos );
foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Multi ) )
{
DrawMultiSelectorEdit( g );
}
DrawAddMultiGroupField();
}
}
}
}

View file

@ -1,8 +1,8 @@
using ImGuiNET;
using Dalamud.Plugin;
using System; using System;
using System.Numerics;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics;
using Dalamud.Plugin;
using ImGuiNET;
using Penumbra.Models; using Penumbra.Models;
namespace Penumbra.UI namespace Penumbra.UI
@ -16,26 +16,29 @@ namespace Penumbra.UI
private const string LabelEditVersion = "##editVersion"; private const string LabelEditVersion = "##editVersion";
private const string LabelEditAuthor = "##editAuthor"; private const string LabelEditAuthor = "##editAuthor";
private const string LabelEditWebsite = "##editWebsite"; private const string LabelEditWebsite = "##editWebsite";
private const string ButtonOpenWebsite = "Open Website";
private const string LabelModEnabled = "Enabled"; private const string LabelModEnabled = "Enabled";
private const string LabelEditingEnabled = "Enable Editing"; private const string LabelEditingEnabled = "Enable Editing";
private const string ButtonOpenWebsite = "Open Website";
private const string ButtonOpenModFolder = "Open Mod Folder"; private const string ButtonOpenModFolder = "Open Mod Folder";
private const string TooltipOpenModFolder = "Open the directory containing this mod in your default file explorer.";
private const string ButtonEditJson = "Edit JSON"; private const string ButtonEditJson = "Edit JSON";
private const string TooltipEditJson = "Open the JSON configuration file in your default application for .json.";
private const string ButtonReloadJson = "Reload JSON"; private const string ButtonReloadJson = "Reload JSON";
private const string TooltipReloadJson = "Reload the configuration of all mods.";
private const string ButtonDeduplicate = "Deduplicate"; private const string ButtonDeduplicate = "Deduplicate";
private const string TooltipDeduplicate = "Try to find identical files and remove duplicate occurences to reduce the mods disk size. Introduces an invisible single-option Group \"Duplicates\"."; private const string TooltipOpenModFolder = "Open the directory containing this mod in your default file explorer.";
private const string TooltipEditJson = "Open the JSON configuration file in your default application for .json.";
private const string TooltipReloadJson = "Reload the configuration of all mods.";
private const string TooltipDeduplicate =
"Try to find identical files and remove duplicate occurences to reduce the mods disk size.\n" +
"Introduces an invisible single-option Group \"Duplicates\".";
private const float HeaderLineDistance = 10f; private const float HeaderLineDistance = 10f;
private static readonly Vector4 GreyColor = new( 1f, 1f, 1f, 0.66f ); private static readonly Vector4 GreyColor = new( 1f, 1f, 1f, 0.66f );
private readonly SettingsInterface _base; private readonly SettingsInterface _base;
private readonly Selector _selector; private readonly Selector _selector;
public readonly PluginDetails _details; public readonly PluginDetails Details;
private bool _editMode = false; private bool _editMode;
private string _currentWebsite; private string _currentWebsite;
private bool _validWebsite; private bool _validWebsite;
@ -43,14 +46,13 @@ namespace Penumbra.UI
{ {
_base = ui; _base = ui;
_selector = s; _selector = s;
_details = new(_base, _selector); Details = new PluginDetails( _base, _selector );
_currentWebsite = Meta?.Website; _currentWebsite = Meta?.Website;
} }
private ModInfo Mod { get{ return _selector.Mod(); } } private ModInfo Mod => _selector.Mod();
private ModMeta Meta { get{ return Mod?.Mod.Meta; } } private ModMeta Meta => Mod?.Mod.Meta;
#region Header Line Functions
private void DrawName() private void DrawName()
{ {
var name = Meta.Name; var name = Meta.Name;
@ -103,6 +105,7 @@ namespace Penumbra.UI
Meta.Author = author.Length > 0 ? author : null; Meta.Author = author.Length > 0 ? author : null;
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
} }
ImGui.EndGroup(); ImGui.EndGroup();
} }
@ -129,6 +132,7 @@ namespace Penumbra.UI
_validWebsite = Uri.TryCreate( Meta.Website, UriKind.Absolute, out var uriResult ) _validWebsite = Uri.TryCreate( Meta.Website, UriKind.Absolute, out var uriResult )
&& ( uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp ); && ( uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp );
} }
if( _validWebsite ) if( _validWebsite )
{ {
if( ImGui.SmallButton( ButtonOpenWebsite ) ) if( ImGui.SmallButton( ButtonOpenWebsite ) )
@ -148,8 +152,10 @@ namespace Penumbra.UI
} }
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( Meta.Website ); ImGui.SetTooltip( Meta.Website );
} }
}
else else
{ {
ImGui.TextColored( GreyColor, "from" ); ImGui.TextColored( GreyColor, "from" );
@ -157,6 +163,7 @@ namespace Penumbra.UI
ImGui.Text( Meta.Website ); ImGui.Text( Meta.Website );
} }
} }
ImGui.EndGroup(); ImGui.EndGroup();
} }
@ -170,9 +177,7 @@ namespace Penumbra.UI
ImGui.SameLine(); ImGui.SameLine();
DrawWebsite(); DrawWebsite();
} }
#endregion
#region Enabled Checkmarks
private void DrawEnabledMark() private void DrawEnabledMark()
{ {
var enabled = Mod.Enabled; var enabled = Mod.Enabled;
@ -181,7 +186,7 @@ namespace Penumbra.UI
Mod.Enabled = enabled; Mod.Enabled = enabled;
_base._plugin.ModManager.Mods.Save(); _base._plugin.ModManager.Mods.Save();
_base._plugin.ModManager.CalculateEffectiveFileList(); _base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced); _base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
} }
} }
@ -189,18 +194,19 @@ namespace Penumbra.UI
{ {
ImGui.Checkbox( LabelEditingEnabled, ref _editMode ); ImGui.Checkbox( LabelEditingEnabled, ref _editMode );
} }
#endregion
#region Edit Line Functions
private void DrawOpenModFolderButton() private void DrawOpenModFolderButton()
{ {
if( ImGui.Button( ButtonOpenModFolder ) ) if( ImGui.Button( ButtonOpenModFolder ) )
{ {
Process.Start( Mod.Mod.ModBasePath.FullName ); Process.Start( Mod.Mod.ModBasePath.FullName );
} }
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipOpenModFolder ); ImGui.SetTooltip( TooltipOpenModFolder );
} }
}
private void DrawEditJsonButton() private void DrawEditJsonButton()
{ {
@ -208,9 +214,12 @@ namespace Penumbra.UI
{ {
Process.Start( _selector.SaveCurrentMod() ); Process.Start( _selector.SaveCurrentMod() );
} }
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipEditJson ); ImGui.SetTooltip( TooltipEditJson );
} }
}
private void DrawReloadJsonButton() private void DrawReloadJsonButton()
{ {
@ -218,9 +227,12 @@ namespace Penumbra.UI
{ {
_selector.ReloadCurrentMod(); _selector.ReloadCurrentMod();
} }
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipReloadJson ); ImGui.SetTooltip( TooltipReloadJson );
} }
}
private void DrawDeduplicateButton() private void DrawDeduplicateButton()
{ {
@ -230,11 +242,14 @@ namespace Penumbra.UI
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
Mod.Mod.RefreshModFiles(); Mod.Mod.RefreshModFiles();
_base._plugin.ModManager.CalculateEffectiveFileList(); _base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced); _base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
} }
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipDeduplicate ); ImGui.SetTooltip( TooltipDeduplicate );
} }
}
private void DrawEditLine() private void DrawEditLine()
{ {
@ -246,17 +261,21 @@ namespace Penumbra.UI
ImGui.SameLine(); ImGui.SameLine();
DrawDeduplicateButton(); DrawDeduplicateButton();
} }
#endregion
public void Draw() public void Draw()
{ {
if( Mod != null ) if( Mod == null )
{ {
return;
}
try try
{ {
var ret = ImGui.BeginChild( LabelModPanel, AutoFillSize, true ); var ret = ImGui.BeginChild( LabelModPanel, AutoFillSize, true );
if( !ret ) if( !ret )
{
return; return;
}
DrawHeaderLine(); DrawHeaderLine();
@ -272,9 +291,11 @@ namespace Penumbra.UI
// Next line, if editable. // Next line, if editable.
if( _editMode ) if( _editMode )
{
DrawEditLine(); DrawEditLine();
}
_details.Draw(_editMode); Details.Draw( _editMode );
ImGui.EndChild(); ImGui.EndChild();
} }
@ -286,4 +307,3 @@ namespace Penumbra.UI
} }
} }
} }
}

View file

@ -1,11 +1,11 @@
using System.Numerics;
using System.Linq;
using System.IO; using System.IO;
using Newtonsoft.Json; using System.Linq;
using ImGuiNET; using System.Numerics;
using Penumbra.Mods;
using Penumbra.Models;
using Dalamud.Interface; using Dalamud.Interface;
using ImGuiNET;
using Newtonsoft.Json;
using Penumbra.Models;
using Penumbra.Mods;
namespace Penumbra.UI namespace Penumbra.UI
{ {
@ -14,6 +14,8 @@ namespace Penumbra.UI
private class Selector private class Selector
{ {
private const string LabelSelectorList = "##availableModList"; private const string LabelSelectorList = "##availableModList";
private const string LabelModFilter = "##ModFilter";
private const string TooltipModFilter = "Filter mods for those containing the given substring.";
private const string TooltipMoveDown = "Move the selected mod down in priority"; private const string TooltipMoveDown = "Move the selected mod down in priority";
private const string TooltipMoveUp = "Move the selected mod up in priority"; private const string TooltipMoveUp = "Move the selected mod up in priority";
private const string TooltipDelete = "Delete the selected mod"; private const string TooltipDelete = "Delete the selected mod";
@ -30,15 +32,24 @@ namespace Penumbra.UI
private static readonly string ArrowDownString = FontAwesomeIcon.ArrowDown.ToIconString(); private static readonly string ArrowDownString = FontAwesomeIcon.ArrowDown.ToIconString();
private readonly SettingsInterface _base; private readonly SettingsInterface _base;
private ModCollection Mods{ get{ return _base._plugin.ModManager.Mods; } } private ModCollection Mods => _base._plugin.ModManager.Mods;
private ModInfo _mod;
private int _index;
private int? _deleteIndex;
private string _modFilter = "";
private string[] _modNamesLower;
private ModInfo _mod = null;
private int _index = 0;
private int? _deleteIndex = null;
public Selector( SettingsInterface ui ) public Selector( SettingsInterface ui )
{ {
_base = ui; _base = ui;
ResetModNamesLower();
}
public void ResetModNamesLower()
{
_modNamesLower = Mods.ModSettings.Select( I => I.Mod.Meta.Name.ToLowerInvariant() ).ToArray();
} }
private void DrawPriorityChangeButton( string iconString, bool up, int unavailableWhen ) private void DrawPriorityChangeButton( string iconString, bool up, int unavailableWhen )
@ -50,6 +61,7 @@ namespace Penumbra.UI
{ {
SetSelection( _index ); SetSelection( _index );
_base._plugin.ModManager.ChangeModPriority( _mod, up ); _base._plugin.ModManager.ChangeModPriority( _mod, up );
_modNamesLower.Swap( _index, _index + ( up ? 1 : -1 ) );
_index += up ? 1 : -1; _index += up ? 1 : -1;
} }
} }
@ -82,10 +94,12 @@ namespace Penumbra.UI
ImGui.PopFont(); ImGui.PopFont();
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipDelete ); ImGui.SetTooltip( TooltipDelete );
} }
}
private void DrawModAddButton() private static void DrawModAddButton()
{ {
ImGui.PushFont( UiBuilder.IconFont ); ImGui.PushFont( UiBuilder.IconFont );
@ -97,8 +111,25 @@ namespace Penumbra.UI
ImGui.PopFont(); ImGui.PopFont();
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipAdd ); ImGui.SetTooltip( TooltipAdd );
} }
}
private void DrawModsSelectorFilter()
{
ImGui.SetNextItemWidth( SelectorButtonSizes.X * 4 );
var tmp = _modFilter;
if( ImGui.InputText( LabelModFilter, ref tmp, 256 ) )
{
_modFilter = tmp.ToLowerInvariant();
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipModFilter );
}
}
private void DrawModsSelectorButtons() private void DrawModsSelectorButtons()
{ {
@ -117,19 +148,26 @@ namespace Penumbra.UI
ImGui.PopStyleVar( 3 ); ImGui.PopStyleVar( 3 );
} }
void DrawDeleteModal() private void DrawDeleteModal()
{ {
if( _deleteIndex != null ) if( _deleteIndex == null )
{
return;
}
ImGui.OpenPopup( DialogDeleteMod ); ImGui.OpenPopup( DialogDeleteMod );
var ret = ImGui.BeginPopupModal( DialogDeleteMod ); var ret = ImGui.BeginPopupModal( DialogDeleteMod );
if( !ret ) if( !ret )
{
return; return;
}
if( _mod?.Mod == null ) if( _mod?.Mod == null )
{ {
ImGui.CloseCurrentPopup(); ImGui.CloseCurrentPopup();
ImGui.EndPopup(); ImGui.EndPopup();
return;
} }
ImGui.Text( "Are you sure you want to delete the following mod:" ); ImGui.Text( "Are you sure you want to delete the following mod:" );
@ -159,18 +197,27 @@ namespace Penumbra.UI
public void Draw() public void Draw()
{ {
if( Mods == null ) if( Mods == null )
{
return; return;
}
// Selector pane // Selector pane
ImGui.BeginGroup(); ImGui.BeginGroup();
ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector ); ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
DrawModsSelectorFilter();
// Inlay selector list // Inlay selector list
ImGui.BeginChild( LabelSelectorList, new Vector2( SelectorPanelWidth, -ImGui.GetFrameHeightWithSpacing() ), true ); ImGui.BeginChild( LabelSelectorList, new Vector2( SelectorPanelWidth, -ImGui.GetFrameHeightWithSpacing() ), true );
for( var modIndex = 0; modIndex < Mods.ModSettings.Count; modIndex++ ) for( var modIndex = 0; modIndex < Mods.ModSettings.Count; modIndex++ )
{ {
var settings = Mods.ModSettings[ modIndex ]; var settings = Mods.ModSettings[ modIndex ];
var modName = settings.Mod.Meta.Name;
if( _modFilter.Length > 0 && !_modNamesLower[ modIndex ].Contains( _modFilter ) )
{
continue;
}
var changedColour = false; var changedColour = false;
if( !settings.Enabled ) if( !settings.Enabled )
@ -186,19 +233,23 @@ namespace Penumbra.UI
#if DEBUG #if DEBUG
var selected = ImGui.Selectable( var selected = ImGui.Selectable(
$"id={modIndex} {settings.Mod.Meta.Name}", $"id={modIndex} {modName}",
modIndex == _index modIndex == _index
); );
#else #else
var selected = ImGui.Selectable( settings.Mod.Meta.Name, modIndex == _index ); var selected = ImGui.Selectable( modName, modIndex == _index );
#endif #endif
if( changedColour ) if( changedColour )
{
ImGui.PopStyleColor(); ImGui.PopStyleColor();
}
if( selected ) if( selected )
{
SetSelection( modIndex, settings ); SetSelection( modIndex, settings );
} }
}
ImGui.EndChild(); ImGui.EndChild();
@ -214,20 +265,30 @@ namespace Penumbra.UI
{ {
_mod = info; _mod = info;
if( idx != _index ) if( idx != _index )
_base._menu._installedTab._modPanel._details.ResetState(); {
_base._menu.InstalledTab.ModPanel.Details.ResetState();
}
_index = idx; _index = idx;
_deleteIndex = null; _deleteIndex = null;
} }
public void SetSelection(int idx) private void SetSelection( int idx )
{ {
if( idx >= ( Mods?.ModSettings?.Count ?? 0 ) ) if( idx >= ( Mods?.ModSettings?.Count ?? 0 ) )
{
idx = -1; idx = -1;
}
if( idx < 0 ) if( idx < 0 )
{
SetSelection( 0, null ); SetSelection( 0, null );
}
else else
{
SetSelection( idx, Mods.ModSettings[ idx ] ); SetSelection( idx, Mods.ModSettings[ idx ] );
} }
}
public void ClearSelection() => SetSelection( -1 ); public void ClearSelection() => SetSelection( -1 );
@ -238,7 +299,9 @@ namespace Penumbra.UI
var mod = Mods.ModSettings[ modIndex ]; var mod = Mods.ModSettings[ modIndex ];
if( mod.Mod.Meta.Name != name ) if( mod.Mod.Meta.Name != name )
{
continue; continue;
}
SetSelection( modIndex, mod ); SetSelection( modIndex, mod );
return; return;
@ -246,11 +309,7 @@ namespace Penumbra.UI
} }
private string GetCurrentModMetaFile() private string GetCurrentModMetaFile()
{ => _mod == null ? "" : Path.Combine( _mod.Mod.ModBasePath.FullName, "meta.json" );
if( _mod == null )
return "";
return Path.Combine( _mod.Mod.ModBasePath.FullName, "meta.json" );
}
public void ReloadCurrentMod() public void ReloadCurrentMod()
{ {
@ -258,18 +317,24 @@ namespace Penumbra.UI
if( metaPath.Length > 0 && File.Exists( metaPath ) ) if( metaPath.Length > 0 && File.Exists( metaPath ) )
{ {
_mod.Mod.Meta = ModMeta.LoadFromFile( metaPath ) ?? _mod.Mod.Meta; _mod.Mod.Meta = ModMeta.LoadFromFile( metaPath ) ?? _mod.Mod.Meta;
_base._menu._installedTab._modPanel._details.ResetState(); _base._menu.InstalledTab.ModPanel.Details.ResetState();
} }
_mod.Mod.RefreshModFiles(); _mod.Mod.RefreshModFiles();
_base._plugin.ModManager.CalculateEffectiveFileList(); _base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
ResetModNamesLower();
} }
public string SaveCurrentMod() public string SaveCurrentMod()
{ {
var metaPath = GetCurrentModMetaFile(); var metaPath = GetCurrentModMetaFile();
if( metaPath.Length > 0 ) if( metaPath.Length > 0 )
{
File.WriteAllText( metaPath, JsonConvert.SerializeObject( _mod.Mod.Meta, Formatting.Indented ) ); File.WriteAllText( metaPath, JsonConvert.SerializeObject( _mod.Mod.Meta, Formatting.Indented ) );
_base._menu._installedTab._modPanel._details.ResetState(); }
_base._menu.InstalledTab.ModPanel.Details.ResetState();
return metaPath; return metaPath;
} }
} }

View file

@ -45,7 +45,7 @@ namespace Penumbra.UI
if( ImGui.Button( LabelRediscoverButton ) ) if( ImGui.Button( LabelRediscoverButton ) )
{ {
_base.ReloadMods(); _base.ReloadMods();
_base._menu._installedTab._selector.ClearSelection(); _base._menu.InstalledTab.Selector.ClearSelection();
} }
} }
@ -64,7 +64,7 @@ namespace Penumbra.UI
{ {
_config.IsEnabled = enabled; _config.IsEnabled = enabled;
_configChanged = true; _configChanged = true;
RefreshActors.RedrawAll(_base._plugin.PluginInterface.ClientState.Actors); Game.RefreshActors.RedrawAll( _base._plugin.PluginInterface.ClientState.Actors );
} }
} }
@ -86,15 +86,17 @@ namespace Penumbra.UI
{ {
_config.ShowAdvanced = showAdvanced; _config.ShowAdvanced = showAdvanced;
_configChanged = true; _configChanged = true;
_base._menu._effectiveTab.RebuildFileList(showAdvanced); _base._menu.EffectiveTab.RebuildFileList( showAdvanced );
} }
} }
private void DrawLogLoadedFilesBox() private void DrawLogLoadedFilesBox()
{ {
if( _base._plugin.ResourceLoader != null ) if( _base._plugin.ResourceLoader != null )
{
ImGui.Checkbox( LabelLogLoadedFiles, ref _base._plugin.ResourceLoader.LogAllFiles ); ImGui.Checkbox( LabelLogLoadedFiles, ref _base._plugin.ResourceLoader.LogAllFiles );
} }
}
private void DrawDisableNotificationsBox() private void DrawDisableNotificationsBox()
{ {
@ -112,9 +114,13 @@ namespace Penumbra.UI
if( ImGui.Checkbox( LabelEnableHttpApi, ref http ) ) if( ImGui.Checkbox( LabelEnableHttpApi, ref http ) )
{ {
if( http ) if( http )
{
_base._plugin.CreateWebServer(); _base._plugin.CreateWebServer();
}
else else
{
_base._plugin.ShutdownWebServer(); _base._plugin.ShutdownWebServer();
}
_config.EnableHttpApi = http; _config.EnableHttpApi = http;
_configChanged = true; _configChanged = true;
@ -141,7 +147,9 @@ namespace Penumbra.UI
{ {
var ret = ImGui.BeginTabItem( LabelTab ); var ret = ImGui.BeginTabItem( LabelTab );
if( !ret ) if( !ret )
{
return; return;
}
DrawRootFolder(); DrawRootFolder();
@ -159,7 +167,9 @@ namespace Penumbra.UI
DrawShowAdvancedBox(); DrawShowAdvancedBox();
if( _config.ShowAdvanced ) if( _config.ShowAdvanced )
{
DrawAdvancedSettings(); DrawAdvancedSettings();
}
if( _configChanged ) if( _configChanged )
{ {

View file

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
namespace Penumbra
{
public static class ArrayExtensions
{
public static void Swap< T >( this T[] array, int idx1, int idx2 )
{
var tmp = array[ idx1 ];
array[ idx1 ] = array[ idx2 ];
array[ idx2 ] = tmp;
}
public static void Swap< T >( this List< T > array, int idx1, int idx2 )
{
var tmp = array[ idx1 ];
array[ idx1 ] = array[ idx2 ];
array[ idx2 ] = tmp;
}
public static int IndexOf< T >( this T[] array, Predicate< T > match )
{
for( var i = 0; i < array.Length; ++i )
{
if( match( array[ i ] ) )
{
return i;
}
}
return -1;
}
public static void Swap< T >( this T[] array, T lhs, T rhs )
{
var idx1 = Array.IndexOf( array, lhs );
if( idx1 < 0 )
{
return;
}
var idx2 = Array.IndexOf( array, rhs );
if( idx2 < 0 )
{
return;
}
array.Swap( idx1, idx2 );
}
public static void Swap< T >( this List< T > array, T lhs, T rhs )
{
var idx1 = array.IndexOf( lhs );
if( idx1 < 0 )
{
return;
}
var idx2 = array.IndexOf( rhs );
if( idx2 < 0 )
{
return;
}
array.Swap( idx1, idx2 );
}
}
}

View file

@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Penumbra.Util namespace Penumbra.Util
@ -15,7 +15,9 @@ namespace Penumbra.Util
{ {
var k = ( uint )i; var k = ( uint )i;
for( var j = 0; j < 8; j++ ) for( var j = 0; j < 8; j++ )
{
k = ( k & 1 ) != 0 ? ( k >> 1 ) ^ Poly : k >> 1; k = ( k & 1 ) != 0 ? ( k >> 1 ) ^ Poly : k >> 1;
}
return k; return k;
} ).ToArray(); } ).ToArray();
@ -40,8 +42,10 @@ namespace Penumbra.Util
public void Update( byte[] data ) public void Update( byte[] data )
{ {
foreach( var b in data ) foreach( var b in data )
{
Update( b ); Update( b );
} }
}
[MethodImpl( MethodImplOptions.AggressiveInlining )] [MethodImpl( MethodImplOptions.AggressiveInlining )]
public void Update( byte b ) public void Update( byte b )

View file

@ -5,19 +5,14 @@ using Newtonsoft.Json.Linq;
public class SingleOrArrayConverter< T > : JsonConverter public class SingleOrArrayConverter< T > : JsonConverter
{ {
public override bool CanConvert( Type objectType ) public override bool CanConvert( Type objectType ) => objectType == typeof( HashSet< T > );
{
return (objectType == typeof(HashSet<T>));
}
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{ {
var token = JToken.Load( reader ); var token = JToken.Load( reader );
if (token.Type == JTokenType.Array) return token.Type == JTokenType.Array
{ ? token.ToObject< HashSet< T > >()
return token.ToObject<HashSet<T>>(); : new HashSet< T > { token.ToObject< T >() };
}
return new HashSet<T>{ token.ToObject<T>() };
} }
public override bool CanWrite => false; public override bool CanWrite => false;
@ -30,10 +25,7 @@ public class SingleOrArrayConverter<T> : JsonConverter
public class DictSingleOrArrayConverter< T, U > : JsonConverter public class DictSingleOrArrayConverter< T, U > : JsonConverter
{ {
public override bool CanConvert( Type objectType ) public override bool CanConvert( Type objectType ) => objectType == typeof( Dictionary< T, HashSet< U > > );
{
return (objectType == typeof(Dictionary<T, HashSet<U>>));
}
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{ {
@ -43,6 +35,7 @@ public class DictSingleOrArrayConverter<T,U> : JsonConverter
{ {
return token.ToObject< HashSet< T > >(); return token.ToObject< HashSet< T > >();
} }
return new HashSet< T > { token.ToObject< T >() }; return new HashSet< T > { token.ToObject< T >() };
} }

View file

@ -0,0 +1,15 @@
using System.IO;
namespace Penumbra
{
public static class StringPathExtensions
{
private static readonly char[] _invalid = Path.GetInvalidFileNameChars();
public static string ReplaceInvalidPathSymbols( this string s, string replacement = "_" )
=> string.Join( replacement, s.Split( _invalid ) );
public static string RemoveInvalidPathSymbols( this string s )
=> string.Concat( s.Split( _invalid ) );
}
}