Applied a slightly expanded .editorconfig to all files, checked the changes and did some simple refactoring-suggestions.

This commit is contained in:
Ottermandias 2021-02-16 15:44:05 +01:00
parent b307a787db
commit 801d9e24cf
38 changed files with 1438 additions and 1055 deletions

View file

@ -1,67 +1,85 @@
[*] [*]
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
# 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_space_after_cast=false csharp_prefer_braces=true:none
csharp_space_after_keywords_in_control_flow_statements=false csharp_space_after_cast=false
csharp_space_between_method_call_parameter_list_parentheses=true csharp_space_after_keywords_in_control_flow_statements=false
csharp_space_between_method_declaration_parameter_list_parentheses=true csharp_space_between_method_call_parameter_list_parentheses=true
csharp_space_between_parentheses=control_flow_statements,expressions,type_casts csharp_space_between_method_declaration_parameter_list_parentheses=true
csharp_style_var_elsewhere=true:suggestion csharp_space_between_parentheses=control_flow_statements,expressions,type_casts
csharp_style_var_for_built_in_types=true:suggestion csharp_style_var_elsewhere=true:suggestion
csharp_style_var_when_type_is_apparent=true:suggestion csharp_style_var_for_built_in_types=true:suggestion
dotnet_style_parentheses_in_arithmetic_binary_operators=never_if_unnecessary:none csharp_style_var_when_type_is_apparent=true:suggestion
dotnet_style_parentheses_in_other_binary_operators=never_if_unnecessary:none dotnet_style_parentheses_in_arithmetic_binary_operators=never_if_unnecessary:none
dotnet_style_parentheses_in_relational_binary_operators=never_if_unnecessary:none dotnet_style_parentheses_in_other_binary_operators=never_if_unnecessary:none
dotnet_style_predefined_type_for_locals_parameters_members=true:suggestion dotnet_style_parentheses_in_relational_binary_operators=never_if_unnecessary:none
dotnet_style_predefined_type_for_member_access=true:suggestion dotnet_style_predefined_type_for_locals_parameters_members=true:suggestion
dotnet_style_qualification_for_event=false:suggestion dotnet_style_predefined_type_for_member_access=true:suggestion
dotnet_style_qualification_for_field=false:suggestion dotnet_style_qualification_for_event=false:suggestion
dotnet_style_qualification_for_method=false:suggestion dotnet_style_qualification_for_field=false:suggestion
dotnet_style_qualification_for_property=false:suggestion dotnet_style_qualification_for_method=false:suggestion
dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion dotnet_style_qualification_for_property=false:suggestion
dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion
# ReSharper properties
resharper_autodetect_indent_settings=true # ReSharper properties
resharper_csharp_space_within_array_access_brackets=true resharper_align_multiline_binary_expressions_chain=false
resharper_enforce_line_ending_style=true resharper_align_multiline_calls_chain=false
resharper_place_attribute_on_same_line=false resharper_autodetect_indent_settings=true
resharper_space_after_cast=false resharper_braces_redundant=true
resharper_space_within_checked_parentheses=true resharper_constructor_or_destructor_body=expression_body
resharper_space_within_default_parentheses=true resharper_csharp_empty_block_style=together
resharper_space_within_nameof_parentheses=true resharper_csharp_max_line_length=144
resharper_space_within_single_line_array_initializer_braces=true resharper_csharp_space_within_array_access_brackets=true
resharper_space_within_sizeof_parentheses=true resharper_enforce_line_ending_style=true
resharper_space_within_typeof_parentheses=true resharper_int_align_assignments=true
resharper_space_within_type_argument_angles=true resharper_int_align_comments=true
resharper_space_within_type_parameter_angles=true resharper_int_align_fields=true
resharper_use_indent_from_vs=false resharper_int_align_invocations=false
resharper_wrap_lines=true resharper_int_align_nested_ternary=true
resharper_int_align_properties=false
# ReSharper inspection severities resharper_int_align_switch_expressions=true
resharper_arrange_redundant_parentheses_highlighting=hint resharper_int_align_switch_sections=true
resharper_arrange_this_qualifier_highlighting=hint resharper_int_align_variables=true
resharper_arrange_type_member_modifiers_highlighting=hint resharper_local_function_body=expression_body
resharper_arrange_type_modifiers_highlighting=hint resharper_method_or_operator_body=expression_body
resharper_built_in_type_reference_style_for_member_access_highlighting=hint resharper_place_attribute_on_same_line=false
resharper_built_in_type_reference_style_highlighting=hint resharper_space_after_cast=false
resharper_redundant_base_qualifier_highlighting=warning resharper_space_within_checked_parentheses=true
resharper_suggest_var_or_type_built_in_types_highlighting=hint resharper_space_within_default_parentheses=true
resharper_suggest_var_or_type_elsewhere_highlighting=hint resharper_space_within_nameof_parentheses=true
resharper_suggest_var_or_type_simple_types_highlighting=hint resharper_space_within_single_line_array_initializer_braces=true
resharper_web_config_module_not_resolved_highlighting=warning resharper_space_within_sizeof_parentheses=true
resharper_web_config_type_not_resolved_highlighting=warning resharper_space_within_typeof_parentheses=true
resharper_web_config_wrong_module_highlighting=warning resharper_space_within_type_argument_angles=true
resharper_space_within_type_parameter_angles=true
[*.{appxmanifest,asax,ascx,aspx,build,cg,cginc,compute,cs,cshtml,dtd,hlsl,hlsli,hlslinc,master,nuspec,razor,resw,resx,shader,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}] resharper_use_indent_from_vs=false
indent_style=space resharper_wrap_lines=true
indent_size=4
tab_width=4 # ReSharper inspection severities
resharper_arrange_redundant_parentheses_highlighting=hint
resharper_arrange_this_qualifier_highlighting=hint
resharper_arrange_type_member_modifiers_highlighting=hint
resharper_arrange_type_modifiers_highlighting=hint
resharper_built_in_type_reference_style_for_member_access_highlighting=hint
resharper_built_in_type_reference_style_highlighting=hint
resharper_redundant_base_qualifier_highlighting=warning
resharper_suggest_var_or_type_built_in_types_highlighting=hint
resharper_suggest_var_or_type_elsewhere_highlighting=hint
resharper_suggest_var_or_type_simple_types_highlighting=hint
resharper_web_config_module_not_resolved_highlighting=warning
resharper_web_config_type_not_resolved_highlighting=warning
resharper_web_config_wrong_module_highlighting=warning
[*.{appxmanifest,asax,ascx,aspx,build,cg,cginc,compute,cs,cshtml,dtd,hlsl,hlsli,hlslinc,master,nuspec,razor,resw,resx,shader,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}]
indent_style=space
indent_size=4
tab_width=4

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()
@ -24,7 +21,7 @@ namespace Penumbra.API
x.FolderName, x.FolderName,
x.Mod.Meta, x.Mod.Meta,
BasePath = x.Mod.ModBasePath.FullName, BasePath = x.Mod.ModBasePath.FullName,
Files = x.Mod.ModFiles.Select( fi => fi.FullName ) Files = x.Mod.ModFiles.Select( fi => fi.FullName )
} ); } );
} }

View file

@ -18,7 +18,7 @@ namespace Penumbra
public static Task< DialogResult > ShowDialogAsync( this CommonDialog form, IWin32Window owner ) public static Task< DialogResult > ShowDialogAsync( this CommonDialog form, IWin32Window owner )
{ {
var taskSource = new TaskCompletionSource< DialogResult >(); var taskSource = new TaskCompletionSource< DialogResult >();
var th = new Thread( () => DialogThread( form, owner, taskSource ) ); var th = new Thread( () => DialogThread( form, owner, taskSource ) );
th.Start(); th.Start();
return taskSource.Task; return taskSource.Task;
} }
@ -38,28 +38,25 @@ 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
{ {
private readonly CommonDialog _form; private readonly CommonDialog _form;
private readonly IWin32Window _owner; private readonly IWin32Window _owner;
private readonly TaskCompletionSource< DialogResult > _taskSource; private readonly TaskCompletionSource< DialogResult > _taskSource;
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;
ShowInTaskbar = false; ShowInTaskbar = false;
Size = new Size( 0, 0 ); Size = new Size( 0, 0 );
Shown += HiddenForm_Shown; Shown += HiddenForm_Shown;
} }

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,8 +14,8 @@ 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 readonly IntPtr _playerResourceManagerAddress; private readonly IntPtr _playerResourceManagerAddress;
@ -33,7 +33,7 @@ namespace Penumbra.Game
_playerResourceManagerAddress = scanner.GetStaticAddressFromSig( "0F 44 FE 48 8B 0D ?? ?? ?? ?? 48 85 C9 74 05" ); _playerResourceManagerAddress = scanner.GetStaticAddressFromSig( "0F 44 FE 48 8B 0D ?? ?? ?? ?? 48 85 C9 74 05" );
LoadPlayerResources = Marshal.GetDelegateForFunctionPointer< LoadPlayerResourcesPrototype >( loadPlayerResourcesAddress ); LoadPlayerResources = Marshal.GetDelegateForFunctionPointer< LoadPlayerResourcesPrototype >( loadPlayerResourcesAddress );
UnloadPlayerResources = Marshal.GetDelegateForFunctionPointer< UnloadPlayerResourcesPrototype >( unloadPlayerResourcesAddress ); UnloadPlayerResources = Marshal.GetDelegateForFunctionPointer< UnloadPlayerResourcesPrototype >( unloadPlayerResourcesAddress );
} }

View file

@ -1,9 +1,10 @@
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks; 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;
namespace Penumbra namespace Penumbra.Game
{ {
public static class RefreshActors public static class RefreshActors
{ {
@ -12,43 +13,49 @@ namespace Penumbra
private const int RenderTaskOtherDelay = 25; private const int RenderTaskOtherDelay = 25;
private const int ModelInvisibilityFlag = 0b10; private const int ModelInvisibilityFlag = 0b10;
private static async void Redraw(Actor actor) private static async void Redraw( Actor actor )
{ {
var ptr = actor.Address; var ptr = actor.Address;
var renderModePtr = ptr + RenderModeOffset; var renderModePtr = ptr + RenderModeOffset;
var renderStatus = Marshal.ReadInt32(renderModePtr); var renderStatus = Marshal.ReadInt32( renderModePtr );
async void DrawObject(int delay) async void DrawObject( int delay )
{ {
Marshal.WriteInt32(renderModePtr, renderStatus | ModelInvisibilityFlag); Marshal.WriteInt32( renderModePtr, renderStatus | ModelInvisibilityFlag );
await Task.Delay(delay); await Task.Delay( delay );
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

@ -16,7 +16,7 @@ namespace Penumbra.Importer
{ {
private readonly DirectoryInfo _outDirectory; private readonly DirectoryInfo _outDirectory;
private const string TempFileName = "textools-import"; private const string TempFileName = "textools-import";
private readonly string _resolvedTempFilePath; private readonly string _resolvedTempFilePath;
public ImporterState State { get; private set; } public ImporterState State { get; private set; }
@ -42,7 +42,7 @@ namespace Penumbra.Importer
public TexToolsImport( DirectoryInfo outDirectory ) public TexToolsImport( DirectoryInfo outDirectory )
{ {
_outDirectory = outDirectory; _outDirectory = outDirectory;
_resolvedTempFilePath = Path.Combine( _outDirectory.FullName, TempFileName ); _resolvedTempFilePath = Path.Combine( _outDirectory.FullName, TempFileName );
} }
@ -50,7 +50,7 @@ namespace Penumbra.Importer
{ {
CurrentModPack = modPackFile.Name; CurrentModPack = modPackFile.Name;
VerifyVersionAndImport(modPackFile); VerifyVersionAndImport( modPackFile );
State = ImporterState.Done; State = ImporterState.Done;
} }
@ -67,8 +67,8 @@ namespace Penumbra.Importer
State = ImporterState.WritingPackToDisk; State = ImporterState.WritingPackToDisk;
// write shitty zip garbage to disk // write shitty zip garbage to disk
var entry = file.GetEntry( entryName ); var entry = file.GetEntry( entryName );
using var s = file.GetInputStream( entry ); using var s = file.GetInputStream( entry );
WriteZipEntryToTempFile( s ); WriteZipEntryToTempFile( s );
@ -76,25 +76,31 @@ namespace Penumbra.Importer
return new MagicTempFileStreamManagerAndDeleterFuckery( fs ); return new MagicTempFileStreamManagerAndDeleterFuckery( fs );
} }
private void VerifyVersionAndImport(FileInfo modPackFile) private void VerifyVersionAndImport( FileInfo modPackFile )
{ {
using var zfs = modPackFile.OpenRead(); using var zfs = modPackFile.OpenRead();
using var extractedModPack = new ZipFile( zfs ); using var extractedModPack = new ZipFile( zfs );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" ); var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modRaw = GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 ); var modRaw = GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 );
// At least a better validation than going by the extension. // At least a better validation than going by the extension.
if (modRaw.Contains("\"TTMPVersion\":")) if( modRaw.Contains( "\"TTMPVersion\":" ) )
{ {
if (modPackFile.Extension != ".ttmp2") if( modPackFile.Extension != ".ttmp2" )
PluginLog.Warning($"File {modPackFile.FullName} seems to be a V2 TTMP, but has the wrong extension."); {
ImportV2ModPack(modPackFile, extractedModPack, modRaw); PluginLog.Warning( $"File {modPackFile.FullName} seems to be a V2 TTMP, but has the wrong extension." );
}
ImportV2ModPack( modPackFile, extractedModPack, modRaw );
} }
else else
{ {
if (modPackFile.Extension != ".ttmp") if( modPackFile.Extension != ".ttmp" )
PluginLog.Warning($"File {modPackFile.FullName} seems to be a V1 TTMP, but has the wrong extension."); {
ImportV1ModPack(modPackFile, extractedModPack, modRaw); PluginLog.Warning( $"File {modPackFile.FullName} seems to be a V1 TTMP, but has the wrong extension." );
}
ImportV1ModPack( modPackFile, extractedModPack, modRaw );
} }
} }
@ -112,8 +118,8 @@ namespace Penumbra.Importer
// 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
{ {
Author = "Unknown", Author = "Unknown",
Name = modPackFile.Name, Name = modPackFile.Name,
Description = "Mod imported from TexTools mod pack" Description = "Mod imported from TexTools mod pack"
}; };
@ -135,7 +141,7 @@ namespace Penumbra.Importer
ExtractSimpleModList( newModFolder, modList, modData ); ExtractSimpleModList( newModFolder, modList, modData );
} }
private void ImportV2ModPack( FileInfo modPackFile, ZipFile extractedModPack, string modRaw ) private void ImportV2ModPack( FileInfo modPackFile, ZipFile extractedModPack, string modRaw )
{ {
var modList = JsonConvert.DeserializeObject< SimpleModPack >( modRaw ); var modList = JsonConvert.DeserializeObject< SimpleModPack >( modRaw );
@ -159,7 +165,7 @@ namespace Penumbra.Importer
var modMeta = new ModMeta var modMeta = new ModMeta
{ {
Author = modList.Author, Author = modList.Author,
Name = modList.Name, Name = modList.Name,
Description = string.IsNullOrEmpty( modList.Description ) Description = string.IsNullOrEmpty( modList.Description )
? "Mod imported from TexTools mod pack" ? "Mod imported from TexTools mod pack"
: modList.Description : modList.Description
@ -188,7 +194,7 @@ namespace Penumbra.Importer
var modMeta = new ModMeta var modMeta = new ModMeta
{ {
Author = modList.Author, Author = modList.Author,
Name = modList.Name, Name = modList.Name,
Description = string.IsNullOrEmpty( modList.Description ) Description = string.IsNullOrEmpty( modList.Description )
? "Mod imported from TexTools mod pack" ? "Mod imported from TexTools mod pack"
: modList.Description, : modList.Description,
@ -206,24 +212,26 @@ namespace Penumbra.Importer
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() ) );
foreach( var option in group.OptionList )
{ {
var groupFolder = new DirectoryInfo(Path.Combine(newModFolder.FullName, group.GroupName.ReplaceInvalidPathSymbols())); var optionFolder = new DirectoryInfo( Path.Combine( groupFolder.FullName, option.Name.ReplaceInvalidPathSymbols() ) );
foreach(var option in group.OptionList) ExtractSimpleModList( optionFolder, option.ModsJsons, modData );
{
var optionFolder = new DirectoryInfo( Path.Combine( groupFolder.FullName, option.Name.ReplaceInvalidPathSymbols()) );
ExtractSimpleModList( optionFolder, option.ModsJsons, modData );
}
AddMeta(newModFolder, groupFolder, group, modMeta);
} }
AddMeta( newModFolder, groupFolder, group, modMeta );
} }
File.WriteAllText( File.WriteAllText(
@ -232,32 +240,35 @@ namespace Penumbra.Importer
); );
} }
private void AddMeta( DirectoryInfo baseFolder, DirectoryInfo groupFolder,ModGroup group, ModMeta meta) private void AddMeta( DirectoryInfo baseFolder, DirectoryInfo groupFolder, ModGroup group, ModMeta meta )
{ {
var Inf = new InstallerInfo var Inf = new InstallerInfo
{ {
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.ReplaceInvalidPathSymbols())); 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 );
} }
@ -276,14 +287,8 @@ namespace Penumbra.Importer
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,7 +306,9 @@ 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 );
} }
@ -317,26 +324,23 @@ namespace Penumbra.Importer
mdl[ 64 ] = 1; mdl[ 64 ] = 1;
// Model header LOD num // Model header LOD num
var stackSize = BitConverter.ToUInt32( mdl, 4 ); var stackSize = BitConverter.ToUInt32( mdl, 4 );
var runtimeBegin = stackSize + 0x44; var runtimeBegin = stackSize + 0x44;
var stringsLengthOffset = runtimeBegin + 4; var stringsLengthOffset = runtimeBegin + 4;
var stringsLength = BitConverter.ToUInt32( mdl, ( int )stringsLengthOffset ); var stringsLength = BitConverter.ToUInt32( mdl, ( int )stringsLengthOffset );
var modelHeaderStart = stringsLengthOffset + stringsLength + 4; var modelHeaderStart = stringsLengthOffset + stringsLength + 4;
var modelHeaderLodOffset = 22; var modelHeaderLodOffset = 22;
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 )
{ {
using var ms = new MemoryStream(); using var ms = new MemoryStream();
using var s = GetStreamFromZipEntry( file, entry ); using var s = GetStreamFromZipEntry( file, entry );
s.CopyTo( ms ); s.CopyTo( ms );
return encoding.GetString( ms.ToArray() ); return encoding.GetString( ms.ToArray() );
} }
} }
} }

View file

@ -12,18 +12,17 @@ namespace Penumbra.Models
private readonly DirectoryInfo _baseDir; private readonly DirectoryInfo _baseDir;
private readonly int _baseDirLength; private readonly int _baseDirLength;
private readonly ModMeta _mod; private readonly ModMeta _mod;
private SHA256 _hasher = null; private SHA256 _hasher;
private readonly Dictionary<long, List<FileInfo>> _filesBySize = new(); 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 )
{ {
_baseDir = baseDir; _baseDir = baseDir;
_baseDirLength = baseDir.FullName.Length; _baseDirLength = baseDir.FullName.Length;
@ -37,125 +36,134 @@ namespace Penumbra.Models
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) if( pair.Value.Count == 2 )
continue;
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;
for (var j = i + 1; j < pair.Value.Count; ++j)
{ {
if (deleted[j]) continue;
continue; }
if (!CompareHashes(hashes[i], hashes[j])) for( var j = i + 1; j < pair.Value.Count; ++j )
{
if( deleted[ j ] || !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;
} }
} }
} }
} }
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 )
{ {
if (option.OptionFiles.TryGetValue(relName2, out var values)) if( option.OptionFiles.TryGetValue( relName2, out var values ) )
{ {
inOption = true; inOption = true;
foreach (var value in values) foreach( var value in values )
option.AddFile(relName1, value); {
option.OptionFiles.Remove(relName2); option.AddFile( relName1, value );
}
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 )
{ {
var stream = File.OpenRead( f.FullName ); var stream = File.OpenRead( f.FullName );
var ret = Sha().ComputeHash(stream); var ret = Sha().ComputeHash( stream );
stream.Dispose(); stream.Dispose();
return ret; return ret;
} }
// Does not delete the base directory itself even if it is completely empty at the end. // Does not delete the base directory itself even if it is completely empty at the end.
public static void ClearEmptySubDirectories(DirectoryInfo baseDir) public static void ClearEmptySubDirectories( DirectoryInfo baseDir )
{ {
foreach (var subDir in baseDir.GetDirectories()) foreach( var subDir in baseDir.GetDirectories() )
{ {
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,30 +5,37 @@ 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;
public string OptionDesc; public string OptionDesc;
[JsonProperty(ItemConverterType = typeof(SingleOrArrayConverter<string>))] [JsonProperty( ItemConverterType = typeof( SingleOrArrayConverter< string > ) )]
public Dictionary<string, HashSet<string>> OptionFiles; public Dictionary< string, HashSet< string > > OptionFiles;
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); {
else return set.Add( gamePath );
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

@ -7,9 +7,9 @@ namespace Penumbra.Models
public class ModInfo public class ModInfo
{ {
public string FolderName { get; set; } public string FolderName { get; set; }
public bool Enabled { get; set; } public bool Enabled { get; set; }
public int Priority { get; set; } public int Priority { get; set; }
public Dictionary<string, int> Conf {get;set;} public Dictionary< string, int > Conf { get; set; }
[JsonIgnore] [JsonIgnore]
public ResourceMod Mod { get; set; } public ResourceMod Mod { get; set; }

View file

@ -21,21 +21,24 @@ namespace Penumbra.Models
public Dictionary< string, string > FileSwaps { get; } = new(); public Dictionary< string, string > FileSwaps { get; } = new();
public Dictionary<string, InstallerInfo> Groups { get; set; } = new(); public Dictionary< string, InstallerInfo > Groups { get; set; } = new();
[JsonIgnore] [JsonIgnore]
public bool HasGroupWithConfig { get; set; } = false; public bool HasGroupWithConfig { get; set; } = false;
public static ModMeta LoadFromFile(string filePath) public static ModMeta LoadFromFile( string filePath )
{ {
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.Values.Any( G => G.SelectionType == SelectType.Multi || G.Options.Count > 1); meta.Groups != null
&& meta.Groups.Count > 0
&& meta.Groups.Values.Any( G => G.SelectionType == SelectType.Multi || G.Options.Count > 1 );
return meta; return meta;
} }
catch( Exception) catch( Exception )
{ {
return null; return null;
// todo: handle broken mods properly // todo: handle broken mods properly

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() )
@ -73,7 +70,7 @@ namespace Penumbra.Mods
var mod = new ResourceMod var mod = new ResourceMod
{ {
Meta = meta, Meta = meta,
ModBasePath = modDir ModBasePath = modDir
}; };
@ -124,7 +121,7 @@ namespace Penumbra.Mods
{ {
// todo: certified fucked tier // todo: certified fucked tier
var prio = info.Priority; var prio = info.Priority;
var swapPrio = up ? prio + 1 : prio - 1; var swapPrio = up ? prio + 1 : prio - 1;
var swapMeta = ModSettings.FirstOrDefault( x => x.Priority == swapPrio ); var swapMeta = ModSettings.FirstOrDefault( x => x.Priority == swapPrio );
@ -133,7 +130,7 @@ namespace Penumbra.Mods
return; return;
} }
info.Priority = swapPrio; info.Priority = swapPrio;
swapMeta.Priority = prio; swapMeta.Priority = prio;
// reorder mods list // reorder mods list
@ -160,10 +157,10 @@ namespace Penumbra.Mods
{ {
var entry = new ModInfo var entry = new ModInfo
{ {
Priority = ModSettings.Count, Priority = ModSettings.Count,
FolderName = mod.ModBasePath.Name, FolderName = mod.ModBasePath.Name,
Enabled = true, Enabled = true,
Mod = mod Mod = mod
}; };
#if DEBUG #if DEBUG

View file

@ -9,18 +9,15 @@ namespace Penumbra.Mods
{ {
public class ModManager : IDisposable public class ModManager : IDisposable
{ {
private readonly Plugin _plugin; private readonly Plugin _plugin;
public readonly Dictionary< string, FileInfo > ResolvedFiles = new(); public readonly Dictionary< string, FileInfo > ResolvedFiles = new();
public readonly Dictionary< string, string > SwappedFiles = new(); public readonly Dictionary< string, string > SwappedFiles = new();
public ModCollection Mods { get; set; } public ModCollection Mods { get; set; }
private DirectoryInfo _basePath; private DirectoryInfo _basePath;
public ModManager( Plugin plugin ) public ModManager( Plugin plugin ) => _plugin = plugin;
{
_plugin = plugin;
}
public void DiscoverMods() public void DiscoverMods()
{ {
@ -114,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();
} }
@ -138,7 +135,7 @@ namespace Penumbra.Mods
if( !SwappedFiles.ContainsKey( swap.Value ) ) if( !SwappedFiles.ContainsKey( swap.Value ) )
{ {
SwappedFiles[ swap.Key.ToLowerInvariant() ] = swap.Value; SwappedFiles[ swap.Key.ToLowerInvariant() ] = swap.Value;
registeredFiles[ swap.Key ] = mod.Meta.Name; registeredFiles[ swap.Key ] = mod.Meta.Name;
} }
else if( registeredFiles.TryGetValue( swap.Key, out var modName ) ) else if( registeredFiles.TryGetValue( swap.Key, out var modName ) )
{ {
@ -155,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();
@ -169,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 )
{ {
@ -183,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 ) doNotAdd = true;
continue;
if( group.Options[ i ].OptionFiles.ContainsKey( relativeFilePath ) )
{
doNotAdd = true;
break;
}
} }
} }
@ -207,7 +204,9 @@ 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;
@ -216,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 );
} }
} }
} }
@ -230,7 +229,7 @@ namespace Penumbra.Mods
if( !ResolvedFiles.ContainsKey( gamePath ) ) if( !ResolvedFiles.ContainsKey( gamePath ) )
{ {
ResolvedFiles[ gamePath.ToLowerInvariant() ] = file; ResolvedFiles[ gamePath.ToLowerInvariant() ] = file;
registeredFiles[ gamePath ] = mod.Meta.Name; registeredFiles[ gamePath ] = mod.Meta.Name;
} }
else if( registeredFiles.TryGetValue( gamePath, out var modName ) ) else if( registeredFiles.TryGetValue( gamePath, out var modName ) )
{ {
@ -247,17 +246,18 @@ namespace Penumbra.Mods
public void DeleteMod( ResourceMod mod ) public void DeleteMod( ResourceMod mod )
{ {
if (mod?.ModBasePath?.Exists ?? false) if( mod?.ModBasePath?.Exists ?? false )
{ {
try try
{ {
Directory.Delete(mod.ModBasePath.FullName, true); Directory.Delete( mod.ModBasePath.FullName, true );
} }
catch( Exception e ) catch( Exception e )
{ {
PluginLog.Error($"Could not delete the mod {mod.ModBasePath.Name}:\n{e}"); PluginLog.Error( $"Could not delete the mod {mod.ModBasePath.Name}:\n{e}" );
} }
} }
DiscoverMods(); DiscoverMods();
} }
@ -278,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

@ -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";
@ -122,10 +123,15 @@ 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;
} }
} }
@ -136,4 +142,4 @@ namespace Penumbra
SettingsInterface.FlipVisibility(); SettingsInterface.FlipVisibility();
} }
} }
} }

View file

@ -51,7 +51,7 @@ namespace Penumbra
public ResourceLoader( Plugin plugin ) public ResourceLoader( Plugin plugin )
{ {
Plugin = plugin; Plugin = plugin;
Crc32 = new Crc32(); Crc32 = new Crc32();
} }
public unsafe void Init() public unsafe void Init()
@ -71,8 +71,8 @@ namespace Penumbra
scanner.ScanText( "E8 ?? ?? ?? 00 48 8B D8 EB ?? F0 FF 83 ?? ?? 00 00" ); scanner.ScanText( "E8 ?? ?? ?? 00 48 8B D8 EB ?? F0 FF 83 ?? ?? 00 00" );
ReadSqpackHook = new Hook< ReadSqpackPrototype >( ReadSqpackHandler, ( long )readSqpackAddress ); ReadSqpackHook = new Hook< ReadSqpackPrototype >( ReadSqpackHandler, ( long )readSqpackAddress );
GetResourceSyncHook = new Hook< GetResourceSyncPrototype >( GetResourceSyncHandler, ( long )getResourceSyncAddress ); GetResourceSyncHook = new Hook< GetResourceSyncPrototype >( GetResourceSyncHandler, ( long )getResourceSyncAddress );
GetResourceAsyncHook = new Hook< GetResourceAsyncPrototype >( GetResourceAsyncHandler, ( long )getResourceAsyncAddress ); GetResourceAsyncHook = new Hook< GetResourceAsyncPrototype >( GetResourceAsyncHandler, ( long )getResourceAsyncAddress );
ReadFile = Marshal.GetDelegateForFunctionPointer< ReadFilePrototype >( readFileAddress ); ReadFile = Marshal.GetDelegateForFunctionPointer< ReadFilePrototype >( readFileAddress );
@ -129,7 +129,7 @@ namespace Penumbra
PluginLog.Log( "[GetResourceHandler] {0}", gameFsPath ); PluginLog.Log( "[GetResourceHandler] {0}", gameFsPath );
} }
if (Plugin.Configuration.IsEnabled) if( Plugin.Configuration.IsEnabled )
{ {
var replacementPath = Plugin.ModManager.ResolveSwappedOrReplacementFilePath( gameFsPath ); var replacementPath = Plugin.ModManager.ResolveSwappedOrReplacementFilePath( gameFsPath );
@ -140,7 +140,7 @@ namespace Penumbra
} }
var cleanPath = replacementPath.Replace( '\\', '/' ); var cleanPath = replacementPath.Replace( '\\', '/' );
var path = Encoding.ASCII.GetBytes( cleanPath ); var path = Encoding.ASCII.GetBytes( cleanPath );
var bPath = stackalloc byte[path.Length + 1]; var bPath = stackalloc byte[path.Length + 1];
Marshal.Copy( path, 0, new IntPtr( bPath ), path.Length ); Marshal.Copy( path, 0, new IntPtr( bPath ), path.Length );
@ -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

@ -3,9 +3,10 @@ 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

@ -7,124 +7,134 @@ namespace Penumbra.UI
{ {
public static partial class ImGuiCustom public static partial class ImGuiCustom
{ {
public static void BeginFramedGroup(string label) => BeginFramedGroupInternal(ref label, ZeroVector, false); public static void BeginFramedGroup( string label ) => BeginFramedGroupInternal( ref label, ZeroVector, false );
public static void BeginFramedGroup(string label, Vector2 minSize) => BeginFramedGroupInternal(ref label, minSize, false); public static void BeginFramedGroup( string label, Vector2 minSize ) => BeginFramedGroupInternal( ref label, minSize, false );
public static bool BeginFramedGroupEdit(ref string label) => BeginFramedGroupInternal(ref label, ZeroVector, true); public static bool BeginFramedGroupEdit( ref string label ) => BeginFramedGroupInternal( ref label, ZeroVector, true );
public static bool BeginFramedGroupEdit(ref string label, Vector2 minSize) => BeginFramedGroupInternal(ref label, minSize, true); public static bool BeginFramedGroupEdit( ref string label, Vector2 minSize ) => BeginFramedGroupInternal( ref label, minSize, true );
private static bool BeginFramedGroupInternal(ref string label, Vector2 minSize, bool edit) private static bool BeginFramedGroupInternal( ref string label, Vector2 minSize, bool edit )
{ {
var itemSpacing = ImGui.GetStyle().ItemSpacing; var itemSpacing = ImGui.GetStyle().ItemSpacing;
var frameHeight = ImGui.GetFrameHeight(); var frameHeight = ImGui.GetFrameHeight();
var halfFrameHeight = new Vector2(ImGui.GetFrameHeight() / 2, 0); var halfFrameHeight = new Vector2( ImGui.GetFrameHeight() / 2, 0 );
ImGui.BeginGroup(); // First group ImGui.BeginGroup(); // First group
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, ZeroVector); ImGui.PushStyleVar( ImGuiStyleVar.FramePadding, ZeroVector );
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ZeroVector); ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
ImGui.BeginGroup(); // Second group ImGui.BeginGroup(); // Second group
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 );
ImGui.SameLine(); ImGui.SameLine();
ImGui.BeginGroup(); // Third group. ImGui.BeginGroup(); // Third group.
// Ensure right half of boundary width/distance // Ensure right half of boundary width/distance
ImGui.Dummy(halfFrameHeight); ImGui.Dummy( halfFrameHeight );
// Label block // Label block
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();
ImGui.SameLine(); ImGui.SameLine();
// Ensure height and distance to label. // Ensure height and distance to label.
ImGui.Dummy(new Vector2(0, frameHeight + itemSpacing.Y)); ImGui.Dummy( new Vector2( 0, frameHeight + itemSpacing.Y ) );
ImGui.BeginGroup(); // Fourth Group. ImGui.BeginGroup(); // Fourth Group.
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 ) );
var itemWidth = ImGui.CalcItemWidth(); var itemWidth = ImGui.CalcItemWidth();
ImGui.PushItemWidth(Math.Max(0f, itemWidth - frameHeight)); ImGui.PushItemWidth( Math.Max( 0f, itemWidth - frameHeight ) );
labelStack.Add((labelMin, labelMax)); labelStack.Add( ( labelMin, labelMax ) );
return ret; return ret;
} }
private static void DrawClippedRect(Vector2 clipMin, Vector2 clipMax, Vector2 drawMin, Vector2 drawMax, uint color, float thickness) private static void DrawClippedRect( Vector2 clipMin, Vector2 clipMax, Vector2 drawMin, Vector2 drawMax, uint color, float thickness )
{ {
ImGui.PushClipRect(clipMin, clipMax, true); ImGui.PushClipRect( clipMin, clipMax, true );
ImGui.GetWindowDrawList().AddRect(drawMin, drawMax, color, thickness); ImGui.GetWindowDrawList().AddRect( drawMin, drawMax, color, thickness );
ImGui.PopClipRect(); ImGui.PopClipRect();
} }
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();
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, ZeroVector); ImGui.PushStyleVar( ImGuiStyleVar.FramePadding, ZeroVector );
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ZeroVector); ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
ImGui.EndGroup(); // Close fourth group ImGui.EndGroup(); // Close fourth group
ImGui.EndGroup(); // Close third group ImGui.EndGroup(); // Close third group
ImGui.SameLine(); ImGui.SameLine();
// Ensure right distance. // Ensure right distance.
ImGui.Dummy(halfFrameHeight); ImGui.Dummy( halfFrameHeight );
// Ensure bottom distance // Ensure bottom distance
ImGui.Dummy(new Vector2(0, frameHeight/2 - itemSpacing.Y)); ImGui.Dummy( new Vector2( 0, frameHeight / 2 - itemSpacing.Y ) );
ImGui.EndGroup(); // Close second group ImGui.EndGroup(); // Close second group
var itemMin = ImGui.GetItemRectMin(); var itemMin = ImGui.GetItemRectMin();
var itemMax = ImGui.GetItemRectMax(); var itemMax = ImGui.GetItemRectMax();
var (currentLabelMin, currentLabelMax) = labelStack[labelStack.Count - 1]; var (currentLabelMin, currentLabelMax) = labelStack[ labelStack.Count - 1 ];
labelStack.RemoveAt(labelStack.Count - 1); labelStack.RemoveAt( labelStack.Count - 1 );
var halfFrame = new Vector2(frameHeight / 8, frameHeight / 2); var halfFrame = new Vector2( frameHeight / 8, frameHeight / 2 );
currentLabelMin.X -= itemSpacing.X; currentLabelMin.X -= itemSpacing.X;
currentLabelMax.X += itemSpacing.X; currentLabelMax.X += itemSpacing.X;
var frameMin = itemMin + halfFrame; var frameMin = itemMin + halfFrame;
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 ) );
ImGui.Dummy(ZeroVector); ImGui.Dummy( ZeroVector );
ImGui.EndGroup(); // Close first group ImGui.EndGroup(); // Close first group
} }
private static readonly Vector2 ZeroVector = new(0, 0); private static readonly Vector2 ZeroVector = new( 0, 0 );
private static readonly List<(Vector2, Vector2)> labelStack = new(); private static readonly List< (Vector2, Vector2) > labelStack = new();
} }
} }

View file

@ -4,39 +4,50 @@ 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 ) )
{ {
for (var i = 0; i < numItems; ++i) return false;
}
for( var i = 0; i < numItems; ++i )
{
var isSelected = i == currentItem;
ImGui.SetNextItemWidth( -1 );
if( ImGui.InputText( $"##{label}_{i}", ref items[ i ], 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{ {
var isSelected = i == currentItem; currentItem = i;
ImGui.SetNextItemWidth(-1); newName = items[ i ];
if (ImGui.InputText($"##{label}_{i}", ref items[i], 64, ImGuiInputTextFlags.EnterReturnsTrue)) ret = true;
{
currentItem = i;
newName = items[i];
ret = true;
ImGui.CloseCurrentPopup();
}
if (isSelected)
ImGui.SetItemDefaultFocus();
}
ImGui.SetNextItemWidth(-1);
if (ImGui.InputText($"##{label}_new", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue))
{
currentItem = numItems;
newName = newOption;
ret = true;
ImGui.CloseCurrentPopup(); ImGui.CloseCurrentPopup();
} }
if (numItems == 0)
if( isSelected )
{
ImGui.SetItemDefaultFocus(); ImGui.SetItemDefaultFocus();
ImGui.EndCombo(); }
} }
ImGui.SetNextItemWidth( -1 );
if( ImGui.InputText( $"##{label}_new", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{
currentItem = numItems;
newName = newOption;
ret = true;
ImGui.CloseCurrentPopup();
}
if( numItems == 0 )
{
ImGui.SetItemDefaultFocus();
}
ImGui.EndCombo();
return ret; return ret;
} }
} }

View file

@ -5,36 +5,45 @@ namespace Penumbra.UI
{ {
public static partial class ImGuiCustom public static partial class ImGuiCustom
{ {
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 ) =>
public static bool ResizingTextInput(ref string input, uint maxLength) ResizingTextInputIntern( label, ref input, maxLength ).Item1;
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

@ -4,23 +4,22 @@ namespace Penumbra.UI
{ {
public static partial class ImGuiCustom public static partial class ImGuiCustom
{ {
public static void VerticalDistance(float distance) public static void VerticalDistance( float distance )
{ {
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + distance); ImGui.SetCursorPosY( ImGui.GetCursorPosY() + distance );
} }
public static void RightJustifiedText(float pos, string text) public static void RightJustifiedText( float pos, string text )
{ {
ImGui.SetCursorPosX(pos - ImGui.CalcTextSize(text).X - 2 * ImGui.GetStyle().ItemSpacing.X); ImGui.SetCursorPosX( pos - ImGui.CalcTextSize( text ).X - 2 * ImGui.GetStyle().ItemSpacing.X );
ImGui.Text(text); ImGui.Text( text );
} }
public static void RightJustifiedLabel(float pos, string text) public static void RightJustifiedLabel( float pos, string text )
{ {
ImGui.SetCursorPosX(pos - ImGui.CalcTextSize(text).X - ImGui.GetStyle().ItemSpacing.X / 2); ImGui.SetCursorPosX( pos - ImGui.CalcTextSize( text ).X - ImGui.GetStyle().ItemSpacing.X / 2 );
ImGui.Text(text); ImGui.Text( text );
ImGui.SameLine(pos); ImGui.SameLine( pos );
} }
} }
} }

View file

@ -8,16 +8,17 @@ namespace Penumbra.UI
private class LaunchButton private class LaunchButton
{ {
// magic numbers // magic numbers
private const int Padding = 50; private const int Padding = 50;
private const int Width = 200; private const int Width = 200;
private const int Height = 45; private const int Height = 45;
private const string MenuButtonsName = "Penumbra Menu Buttons"; private const string MenuButtonsName = "Penumbra Menu Buttons";
private const string MenuButtonLabel = "Manage Mods"; private const string MenuButtonLabel = "Manage Mods";
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
@ -28,7 +29,7 @@ namespace Penumbra.UI
private readonly SettingsInterface _base; private readonly SettingsInterface _base;
private readonly Dalamud.Game.ClientState.Condition _condition; private readonly Dalamud.Game.ClientState.Condition _condition;
public LaunchButton(SettingsInterface ui) public LaunchButton( SettingsInterface ui )
{ {
_base = ui; _base = ui;
_condition = ui._plugin.PluginInterface.ClientState.Condition; _condition = ui._plugin.PluginInterface.ClientState.Condition;
@ -36,20 +37,26 @@ namespace Penumbra.UI
public void Draw() public void Draw()
{ {
if( !_condition.Any() && !_base._menu.Visible ) if( _condition.Any() || _base._menu.Visible )
{ {
var ss = ImGui.GetIO().DisplaySize; return;
ImGui.SetNextWindowPos( ss - WindowPosOffset, ImGuiCond.Always );
if( ImGui.Begin(MenuButtonsName, ButtonFlags) )
{
if( ImGui.Button( MenuButtonLabel, WindowSize ) )
_base.FlipVisibility();
ImGui.End();
}
} }
var ss = ImGui.GetIO().DisplaySize;
ImGui.SetNextWindowPos( ss - WindowPosOffset, ImGuiCond.Always );
if( !ImGui.Begin( MenuButtonsName, ButtonFlags ) )
{
return;
}
if( ImGui.Button( MenuButtonLabel, WindowSize ) )
{
_base.FlipVisibility();
}
ImGui.End();
} }
} }
} }

View file

@ -19,29 +19,37 @@ namespace Penumbra.UI
#endif #endif
private readonly SettingsInterface _base; private readonly SettingsInterface _base;
public MenuBar(SettingsInterface ui) => _base = ui; public MenuBar( SettingsInterface ui ) => _base = ui;
public void Draw() public void Draw()
{ {
if( _showDebugBar && ImGui.BeginMainMenuBar() ) if( !_showDebugBar || !ImGui.BeginMainMenuBar() )
{ {
if( ImGui.BeginMenu( MenuLabel ) ) return;
}
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 ) )
_base.ReloadMods();
#if DEBUG
if ( ImGui.MenuItem( MenuItemHide) )
_showDebugBar = false;
#endif
ImGui.EndMenu();
} }
ImGui.EndMainMenuBar(); if( ImGui.MenuItem( MenuItemRediscover ) )
{
_base.ReloadMods();
}
#if DEBUG
if( ImGui.MenuItem( MenuItemHide ) )
{
_showDebugBar = false;
}
#endif
ImGui.EndMenu();
} }
ImGui.EndMainMenuBar();
} }
} }
} }

View file

@ -7,8 +7,8 @@ namespace Penumbra.UI
{ {
private const float DefaultVerticalSpace = 20f; private const float DefaultVerticalSpace = 20f;
private static readonly Vector2 AutoFillSize = new(-1, -1); private static readonly Vector2 AutoFillSize = new( -1, -1 );
private static readonly Vector2 ZeroVector = new( 0, 0); private static readonly Vector2 ZeroVector = new( 0, 0 );
private readonly Plugin _plugin; private readonly Plugin _plugin;
@ -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,14 +35,14 @@ namespace Penumbra.UI
private void ReloadMods() private void ReloadMods()
{ {
_menu._installedTab._selector.ResetModNamesLower(); _menu.InstalledTab.Selector.ResetModNamesLower();
_menu._installedTab._selector.ClearSelection(); _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(); _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,22 +11,22 @@ 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 readonly 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 )
{ {
_mods = ui._plugin.ModManager; _mods = ui._plugin.ModManager;
RebuildFileList(ui._plugin.Configuration.ShowAdvanced); RebuildFileList( ui._plugin.Configuration.ShowAdvanced );
} }
public void RebuildFileList(bool advanced) public void RebuildFileList( bool advanced )
{ {
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
{ {
@ -35,26 +35,30 @@ namespace Penumbra.UI
} }
} }
private void DrawFileLine((string, string) file) private void DrawFileLine( (string, string) file )
{ {
ImGui.Selectable(file.Item2); ImGui.Selectable( file.Item2 );
ImGui.SameLine(); ImGui.SameLine();
ImGui.SetCursorPosX(_maxGamePath); ImGui.SetCursorPosX( _maxGamePath );
ImGui.TextUnformatted(" <-- "); ImGui.TextUnformatted( " <-- " );
ImGui.SameLine(); ImGui.SameLine();
ImGui.Selectable(file.Item1); ImGui.Selectable( file.Item1 );
} }
public void Draw() public void Draw()
{ {
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

@ -15,22 +15,22 @@ 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.";
private const uint ColorRed = 0xFF0000C8; private const uint ColorRed = 0xFF0000C8;
private static readonly Vector2 ImportBarSize = new( -1, 0 ); private static readonly Vector2 ImportBarSize = new( -1, 0 );
private bool _isImportRunning = false; private bool _isImportRunning = false;
private bool _hasError = false; private bool _hasError = false;
private TexToolsImport _texToolsImport = null!; private TexToolsImport _texToolsImport = null!;
private readonly SettingsInterface _base; private readonly SettingsInterface _base;
public TabImport(SettingsInterface ui) => _base = ui; public TabImport( SettingsInterface ui ) => _base = ui;
public bool IsImporting() => _isImportRunning; public bool IsImporting() => _isImportRunning;
@ -41,10 +41,10 @@ namespace Penumbra.UI
{ {
var picker = new OpenFileDialog var picker = new OpenFileDialog
{ {
Multiselect = true, Multiselect = true,
Filter = FileTypeFilter, Filter = FileTypeFilter,
CheckFileExists = true, CheckFileExists = true,
Title = LabelFileDialog Title = LabelFileDialog
}; };
var result = await picker.ShowDialogAsync(); var result = await picker.ShowDialogAsync();
@ -55,7 +55,7 @@ namespace Penumbra.UI
foreach( var fileName in picker.FileNames ) foreach( var fileName in picker.FileNames )
{ {
PluginLog.Log( $"-> {fileName} START"); PluginLog.Log( $"-> {fileName} START" );
try try
{ {
@ -74,6 +74,7 @@ namespace Penumbra.UI
_texToolsImport = null; _texToolsImport = null;
_base.ReloadMods(); _base.ReloadMods();
} }
_isImportRunning = false; _isImportRunning = false;
} ); } );
} }
@ -90,32 +91,34 @@ namespace Penumbra.UI
{ {
ImGui.Button( LabelFileImportRunning ); ImGui.Button( LabelFileImportRunning );
if( _texToolsImport != null ) if( _texToolsImport == null )
{ {
switch( _texToolsImport.State ) return;
{ }
case ImporterState.None:
break;
case ImporterState.WritingPackToDisk:
ImGui.Text( TooltipModpack1 );
break;
case ImporterState.ExtractingModFiles:
{
var str =
$"{_texToolsImport.CurrentModPack} - {_texToolsImport.CurrentProgress} of {_texToolsImport.TotalProgress} files";
ImGui.ProgressBar( _texToolsImport.Progress, ImportBarSize, str ); switch( _texToolsImport.State )
break; {
} case ImporterState.None:
case ImporterState.Done: break;
break; case ImporterState.WritingPackToDisk:
default: ImGui.Text( TooltipModpack1 );
throw new ArgumentOutOfRangeException(); break;
case ImporterState.ExtractingModFiles:
{
var str =
$"{_texToolsImport.CurrentModPack} - {_texToolsImport.CurrentProgress} of {_texToolsImport.TotalProgress} files";
ImGui.ProgressBar( _texToolsImport.Progress, ImportBarSize, str );
break;
} }
case ImporterState.Done:
break;
default:
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,25 +4,25 @@ 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 );
ImGui.Text( "You'll need to install them first by creating a folder close to the root of your drive (preferably an SSD)." ); ImGui.Text( "You'll need to install them first by creating a folder close to the root of your drive (preferably an SSD)." );
ImGui.Text( "For example: D:/ffxiv/mods/" ); ImGui.Text( "For example: D:/ffxiv/mods/" );
ImGui.Text( "And pasting that path into the settings tab and clicking the 'Rediscover Mods' button." ); ImGui.Text( "And pasting that path into the settings tab and clicking the 'Rediscover Mods' button." );
@ -33,20 +33,22 @@ namespace Penumbra.UI
{ {
var ret = ImGui.BeginTabItem( LabelTab ); var ret = ImGui.BeginTabItem( LabelTab );
if( !ret ) if( !ret )
return;
if (_base._plugin.ModManager.Mods != null)
{ {
_selector.Draw(); return;
}
if( _base._plugin.ModManager.Mods != null )
{
Selector.Draw();
ImGui.SameLine(); ImGui.SameLine();
_modPanel.Draw(); ModPanel.Draw();
} }
else else
{
DrawNoModsAvailable(); DrawNoModsAvailable();
}
ImGui.EndTabItem(); ImGui.EndTabItem();
return;
} }
} }
} }

View file

@ -8,12 +8,16 @@ namespace Penumbra.UI
internal static class Extension internal static class Extension
{ {
// Remove the entry at idx from the list if the new string is empty, otherwise replace it. // Remove the entry at idx from the list if the new string is empty, otherwise replace it.
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;
}
} }
} }
@ -33,117 +37,147 @@ 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 LabelGroupSelect = "##groupSelect"; private const string LabelGroupSelect = "##groupSelect";
private const string LabelOptionSelect = "##optionSelect"; private const string LabelOptionSelect = "##optionSelect";
private const string LabelConfigurationTab = "Configuration"; private const string LabelConfigurationTab = "Configuration";
private const float TextSizePadding = 5f; private const string TooltipFilesTab =
private const float OptionSelectionWidth = 140f; "Green files replace their standard game path counterpart (not in any option) or are in all options of a Single-Select option.\n" +
private const float CheckMarkSize = 50f; "Yellow files are restricted to some options.";
private const uint ColorGreen = 0xFF00C800;
private const uint ColorYellow = 0xFF00C8C8;
private const uint ColorRed = 0xFF0000C8;
private bool _editMode = false; private const float TextSizePadding = 5f;
private int _selectedGroupIndex = 0; private const float OptionSelectionWidth = 140f;
private InstallerInfo? _selectedGroup = null; private const float CheckMarkSize = 50f;
private int _selectedOptionIndex = 0; private const uint ColorGreen = 0xFF00C800;
private Option? _selectedOption = null; private const uint ColorYellow = 0xFF00C8C8;
private (string label, string name)[] _changedItemsList = null; private const uint ColorRed = 0xFF0000C8;
private float? _fileSwapOffset = null;
private string _currentGamePaths = ""; private bool _editMode = false;
private int _selectedGroupIndex = 0;
private InstallerInfo? _selectedGroup = null;
private int _selectedOptionIndex = 0;
private Option? _selectedOption = null;
private (string label, string name)[] _changedItemsList = null;
private float? _fileSwapOffset = null;
private string _currentGamePaths = "";
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) }
_selectedGroup = Meta.Groups.ElementAt(_selectedGroupIndex).Value;
else
_selectedGroup = null;
}
public void SelectGroup() => SelectGroup(_selectedGroupIndex);
public void SelectOption(int idx) if( Meta?.Groups?.Count > 0 )
{
_selectedGroup = Meta.Groups.ElementAt( _selectedGroupIndex ).Value;
}
else
{
_selectedGroup = null;
}
}
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) }
_selectedOption = ((InstallerInfo) _selectedGroup).Options[_selectedOptionIndex];
if( _selectedGroup?.Options.Count > 0 )
{
_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()
{ {
_changedItemsList = null; _changedItemsList = null;
_fileSwapOffset = null; _fileSwapOffset = null;
_fullFilenameList = null; _fullFilenameList = null;
SelectGroup(); SelectGroup();
SelectOption(); SelectOption();
} }
public PluginDetails( SettingsInterface ui, Selector s )
private readonly Selector _selector;
private readonly SettingsInterface _base;
public PluginDetails(SettingsInterface ui, Selector s)
{ {
_base = ui; _base = ui;
_selector = s; _selector = s;
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 );
} }
private void DrawAboutTab() private void DrawAboutTab()
{ {
if (!_editMode && Meta.Description?.Length == 0) if( !_editMode && Meta.Description?.Length == 0 )
return;
if(ImGui.BeginTabItem( LabelAboutTab ) )
{ {
var desc = Meta.Description; return;
var flags = _editMode
? ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CtrlEnterForNewLine
: ImGuiInputTextFlags.ReadOnly;
if( _editMode )
{
if (ImGui.InputTextMultiline(LabelDescEdit, ref desc, 1 << 16, AutoFillSize, flags))
{
Meta.Description = desc;
_selector.SaveCurrentMod();
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip( TooltipAboutEdit );
}
else
{
ImGui.TextWrapped( desc );
}
ImGui.EndTabItem();
} }
if( !ImGui.BeginTabItem( LabelAboutTab ) )
{
return;
}
var desc = Meta.Description;
var flags = _editMode
? ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CtrlEnterForNewLine
: ImGuiInputTextFlags.ReadOnly;
if( _editMode )
{
if( ImGui.InputTextMultiline( LabelDescEdit, ref desc, 1 << 16, AutoFillSize, flags ) )
{
Meta.Description = desc;
_selector.SaveCurrentMod();
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipAboutEdit );
}
}
else
{
ImGui.TextWrapped( desc );
}
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
@ -154,122 +188,164 @@ 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 );
if ( ImGui.InputText(_changedItemsList[i].label, ref _changedItemsList[i].name, 128, flags) ) if( ImGui.InputText( _changedItemsList[ i ].label, ref _changedItemsList[ i ].name, 128, flags ) )
{ {
Meta.ChangedItems.RemoveOrChange(_changedItemsList[i].name, i); Meta.ChangedItems.RemoveOrChange( _changedItemsList[ i ].name, i );
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
} }
} }
var newItem = ""; var newItem = "";
if ( _editMode ) if( _editMode )
{ {
ImGui.SetNextItemWidth(-1); ImGui.SetNextItemWidth( -1 );
if ( ImGui.InputText( LabelChangedItemNew, ref newItem, 128, flags) ) if( ImGui.InputText( LabelChangedItemNew, ref newItem, 128, flags ) )
{ {
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;
{ }
ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( LabelConflictsHeader, AutoFillSize ) )
{
foreach( var kv in Mod.Mod.FileConflicts )
{
var mod = kv.Key;
if( ImGui.Selectable( mod ) )
_selector.SelectModByName( mod );
ImGui.Indent( 15 ); if( !ImGui.BeginTabItem( LabelConflictsTab ) )
foreach( var file in kv.Value ) {
ImGui.Selectable( file ); return;
ImGui.Unindent( 15 ); }
}
ImGui.ListBoxFooter(); ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( LabelConflictsHeader, AutoFillSize ) )
{
foreach( var kv in Mod.Mod.FileConflicts )
{
var mod = kv.Key;
if( ImGui.Selectable( mod ) )
{
_selector.SelectModByName( mod );
} }
ImGui.EndTabItem(); ImGui.Indent( 15 );
foreach( var file in kv.Value )
{
ImGui.Selectable( file );
}
ImGui.Unindent( 15 );
} }
ImGui.ListBoxFooter();
} }
ImGui.EndTabItem();
} }
private void DrawFileSwapTab() private void DrawFileSwapTab()
{ {
if( Meta.FileSwaps.Any() ) if( !Meta.FileSwaps.Any() )
{ {
if( ImGui.BeginTabItem( LabelFileSwapTab ) ) return;
}
if( ImGui.BeginTabItem( LabelFileSwapTab ) )
{
_fileSwapOffset ??= Meta.FileSwaps.Max( P => ImGui.CalcTextSize( P.Key ).X ) + TextSizePadding;
ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( LabelFileSwapHeader, AutoFillSize ) )
{ {
if (_fileSwapOffset == null) foreach( var file in Meta.FileSwaps )
_fileSwapOffset = Meta.FileSwaps.Max( P => ImGui.CalcTextSize(P.Key).X) + TextSizePadding;
ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( LabelFileSwapHeader, AutoFillSize ) )
{ {
foreach( var file in Meta.FileSwaps ) ImGui.Selectable( file.Key );
{ ImGui.SameLine( _fileSwapOffset ?? 0 );
ImGui.Selectable(file.Key); ImGui.TextUnformatted( " -> " );
ImGui.SameLine(_fileSwapOffset ?? 0); ImGui.SameLine();
ImGui.TextUnformatted(" -> "); ImGui.Selectable( file.Value );
ImGui.SameLine();
ImGui.Selectable(file.Value);
}
ImGui.ListBoxFooter();
} }
ImGui.EndTabItem();
ImGui.ListBoxFooter();
} }
else
_fileSwapOffset = null; ImGui.EndTabItem();
}
else
{
_fileSwapOffset = null;
} }
} }
private void UpdateFilenameList() private void UpdateFilenameList()
{ {
if (_fullFilenameList == null) if( _fullFilenameList != null )
{ {
var len = Mod.Mod.ModBasePath.FullName.Length; return;
_fullFilenameList = Mod.Mod.ModFiles.Select( F => (F.FullName, false, ColorGreen, "") ).ToArray(); }
if(Meta.Groups?.Count == 0) var len = Mod.Mod.ModBasePath.FullName.Length;
return; _fullFilenameList = Mod.Mod.ModFiles.Select( F => ( F.FullName, false, ColorGreen, "" ) ).ToArray();
for (var i = 0; i < Mod.Mod.ModFiles.Count; ++i) if( Meta.Groups?.Count == 0 )
{
return;
}
for( var i = 0; i < Mod.Mod.ModFiles.Count; ++i )
{
_fullFilenameList[ i ].relName = _fullFilenameList[ i ].name.Substring( len ).TrimStart( '\\' );
if( Meta.Groups == null )
{ {
_fullFilenameList[i].relName = _fullFilenameList[i].name.Substring(len).TrimStart('\\'); continue;
foreach (var Group in Meta.Groups.Values) }
foreach( var group in Meta.Groups.Values )
{
var inAll = true;
foreach( var option in group.Options )
{ {
var inAll = true; if( option.OptionFiles.ContainsKey( _fullFilenameList[ i ].relName ) )
foreach (var Option in Group.Options)
{ {
if (Option.OptionFiles.ContainsKey(_fullFilenameList[i].relName)) _fullFilenameList[ i ].color = ColorYellow;
_fullFilenameList[i].color = ColorYellow;
else
inAll = false;
} }
if (inAll && Group.SelectionType == SelectType.Single) else
_fullFilenameList[i].color = ColorGreen; {
inAll = false;
}
}
if( inAll && group.SelectionType == SelectType.Single )
{
_fullFilenameList[ i ].color = ColorGreen;
} }
} }
} }
@ -277,111 +353,142 @@ namespace Penumbra.UI
private void DrawFileListTab() private void DrawFileListTab()
{ {
if( ImGui.BeginTabItem( LabelFileListTab ) ) if( !ImGui.BeginTabItem( LabelFileListTab ) )
{ {
if (ImGui.IsItemHovered()) return;
ImGui.SetTooltip( TooltipFilesTab );
ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( LabelFileListHeader, AutoFillSize ) )
{
UpdateFilenameList();
foreach(var file in _fullFilenameList)
{
ImGui.PushStyleColor(ImGuiCol.Text, file.color);
ImGui.Selectable(file.name);
ImGui.PopStyleColor();
}
ImGui.ListBoxFooter();
}
else
_fullFilenameList = null;
ImGui.EndTabItem();
} }
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipFilesTab );
}
ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( LabelFileListHeader, AutoFillSize ) )
{
UpdateFilenameList();
foreach( var file in _fullFilenameList )
{
ImGui.PushStyleColor( ImGuiCol.Text, file.color );
ImGui.Selectable( file.name );
ImGui.PopStyleColor();
}
ImGui.ListBoxFooter();
}
else
{
_fullFilenameList = null;
}
ImGui.EndTabItem();
} }
private void HandleSelectedFilesButton(bool remove) private void HandleSelectedFilesButton( bool remove )
{ {
if (_selectedOption == null) if( _selectedOption == null )
return;
var option = (Option) _selectedOption;
var gamePaths = _currentGamePaths.Split(';');
if (gamePaths.Length == 0 || gamePaths[0].Length == 0)
return;
int? defaultIndex = null;
for (var i = 0; i < gamePaths.Length; ++i)
{ {
if (gamePaths[i] == TextDefaultGamePath ) return;
{
defaultIndex = i;
break;
}
} }
var baseLength = Mod.Mod.ModBasePath.FullName.Length; var option = ( Option )_selectedOption;
var changed = false;
for (var i = 0; i < Mod.Mod.ModFiles.Count; ++i) var gamePaths = _currentGamePaths.Split( ';' );
if( gamePaths.Length == 0 || gamePaths[ 0 ].Length == 0 )
{ {
if (!_fullFilenameList[i].selected) return;
continue; }
var fileName = _fullFilenameList[i].relName; var defaultIndex = gamePaths.IndexOf( p => p == TextDefaultGamePath );
if (defaultIndex != null) var changed = false;
gamePaths[(int)defaultIndex] = fileName.Replace('\\', '/'); for( var i = 0; i < Mod.Mod.ModFiles.Count; ++i )
{
if (remove && option.OptionFiles.TryGetValue(fileName, out var setPaths)) if( !_fullFilenameList[ i ].selected )
{ {
if (setPaths.RemoveWhere( P => gamePaths.Contains(P)) > 0) continue;
}
var fileName = _fullFilenameList[ i ].relName;
if( defaultIndex >= 0 )
{
gamePaths[ ( int )defaultIndex ] = fileName.Replace( '\\', '/' );
}
if( remove && option.OptionFiles.TryGetValue( fileName, out var setPaths ) )
{
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 DrawGamePathInput() private void DrawGamePathInput()
{ {
ImGui.TextUnformatted( LabelGamePathsEdit ); ImGui.TextUnformatted( LabelGamePathsEdit );
ImGui.SameLine(); ImGui.SameLine();
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 (!DrawEditGroupSelector()) if( _selectedOption == null )
{
SelectOption();
}
if( !DrawEditGroupSelector() )
{
return; return;
}
ImGui.SameLine(); ImGui.SameLine();
if (!DrawEditOptionSelector()) if( !DrawEditOptionSelector() )
{
return; return;
}
ImGui.SameLine(); ImGui.SameLine();
DrawAddToGroupButton(); DrawAddToGroupButton();
ImGui.SameLine(); ImGui.SameLine();
@ -390,116 +497,142 @@ namespace Penumbra.UI
DrawGamePathInput(); DrawGamePathInput();
} }
private void DrawFileAndGamePaths(int idx) private void DrawFileAndGamePaths( int idx )
{ {
void Selectable(uint colorNormal, uint colorReplace) void Selectable( uint colorNormal, uint colorReplace )
{ {
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.Selectable( _fullFilenameList[idx].name, ref _fullFilenameList[idx].selected );
ImGui.PushStyleColor( ImGuiCol.Text, loc );
ImGui.Selectable( _fullFilenameList[ idx ].name, ref _fullFilenameList[ idx ].selected );
ImGui.PopStyleColor(); ImGui.PopStyleColor();
} }
const float indent = 30f; const float indent = 30f;
if (_selectedOption == null) if( _selectedOption == null )
{ {
Selectable(0, ColorGreen); Selectable( 0, ColorGreen );
return; return;
} }
var fileName = _fullFilenameList[idx].relName; var fileName = _fullFilenameList[ idx ].relName;
if (((Option) _selectedOption).OptionFiles.TryGetValue(fileName, out var gamePaths)) if( ( ( Option )_selectedOption ).OptionFiles.TryGetValue( fileName, out var gamePaths ) )
{ {
Selectable(0, ColorGreen); Selectable( 0, ColorGreen );
ImGui.Indent(indent); ImGui.Indent( indent );
foreach (var gamePath in gamePaths) foreach( var gamePath in gamePaths )
{ {
string tmp = gamePath; var tmp = gamePath;
if (ImGui.InputText($"##{fileName}_{gamePath}", ref tmp, 128, ImGuiInputTextFlags.EnterReturnsTrue)) if( ImGui.InputText( $"##{fileName}_{gamePath}", ref tmp, 128, ImGuiInputTextFlags.EnterReturnsTrue )
&& tmp != gamePath )
{ {
if (tmp != gamePath) gamePaths.Remove( gamePath );
if( tmp.Length > 0 )
{ {
gamePaths.Remove(gamePath); gamePaths.Add( tmp );
if (tmp.Length > 0)
gamePaths.Add(tmp);
_selector.SaveCurrentMod();
_selector.ReloadCurrentMod();
} }
_selector.SaveCurrentMod();
_selector.ReloadCurrentMod();
} }
} }
ImGui.Unindent(indent);
ImGui.Unindent( indent );
} }
else else
Selectable(ColorYellow, ColorRed);
}
private void DrawMultiSelectorCheckBox(InstallerInfo group, int idx, int flag, string label)
{
var opt = group.Options[idx];
var enabled = ( flag & (1 << idx)) != 0;
var oldEnabled = enabled;
if (ImGui.Checkbox(label, ref enabled))
{ {
if (oldEnabled != enabled) Selectable( ColorYellow, ColorRed );
{
Mod.Conf[group.GroupName] ^= (1 << idx);
Save();
}
} }
} }
private void DrawMultiSelector(InstallerInfo group) private void DrawMultiSelectorCheckBox( InstallerInfo group, int idx, int flag, string label )
{ {
if (group.Options.Count == 0) var opt = group.Options[ idx ];
return; var enabled = ( flag & ( 1 << idx ) ) != 0;
var oldEnabled = enabled;
if( ImGui.Checkbox( label, ref enabled ) && oldEnabled != enabled )
{
Mod.Conf[ group.GroupName ] ^= 1 << idx;
Save();
}
}
ImGuiCustom.BeginFramedGroup(group.GroupName); private void DrawMultiSelector( InstallerInfo group )
for(var i = 0; i < group.Options.Count; ++i) {
DrawMultiSelectorCheckBox(group, i, Mod.Conf[group.GroupName], $"{group.Options[i].OptionName}##{group.GroupName}"); if( group.Options.Count == 0 )
{
return;
}
ImGuiCustom.BeginFramedGroup( group.GroupName );
for( var i = 0; i < group.Options.Count; ++i )
{
DrawMultiSelectorCheckBox( group, i, Mod.Conf[ group.GroupName ],
$"{group.Options[ i ].OptionName}##{group.GroupName}" );
}
ImGuiCustom.EndFramedGroup(); ImGuiCustom.EndFramedGroup();
} }
private void DrawSingleSelector(InstallerInfo group) private void DrawSingleSelector( InstallerInfo group )
{ {
if (group.Options.Count < 2) if( group.Options.Count < 2 )
return;
var code = Mod.Conf[group.GroupName];
if( ImGui.Combo( group.GroupName, ref code, group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) )
{ {
Mod.Conf[group.GroupName] = code; return;
}
var code = Mod.Conf[ group.GroupName ];
if( ImGui.Combo( group.GroupName, ref code
, group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) )
{
Mod.Conf[ group.GroupName ] = code;
Save(); Save();
} }
} }
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); {
foreach(var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Multi )) DrawSingleSelector( g );
DrawMultiSelector(g); }
foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Multi ) )
{
DrawMultiSelector( g );
}
return; return;
} }
private void DrawConfigurationTab() private void DrawConfigurationTab()
{ {
if (!_editMode && !Meta.HasGroupWithConfig) if( !_editMode && !Meta.HasGroupWithConfig )
return;
if(ImGui.BeginTabItem( LabelConfigurationTab ) )
{ {
if (_editMode) return;
}
if( ImGui.BeginTabItem( LabelConfigurationTab ) )
{
if( _editMode )
{
DrawGroupSelectorsEdit(); DrawGroupSelectorsEdit();
}
else else
{
DrawGroupSelectors(); DrawGroupSelectors();
}
ImGui.EndTabItem(); ImGui.EndTabItem();
} }
} }
public void Draw(bool editMode) public void Draw( bool editMode )
{ {
_editMode = editMode; _editMode = editMode;
ImGui.BeginTabBar( LabelPluginDetails ); ImGui.BeginTabBar( LabelPluginDetails );
@ -507,10 +640,15 @@ namespace Penumbra.UI
DrawAboutTab(); DrawAboutTab();
DrawChangedItemsTab(); DrawChangedItemsTab();
DrawConfigurationTab(); DrawConfigurationTab();
if (_editMode) if( _editMode )
{
DrawFileListTabEdit(); DrawFileListTabEdit();
}
else else
{
DrawFileListTab(); DrawFileListTab();
}
DrawFileSwapTab(); DrawFileSwapTab();
DrawConflictTab(); DrawConflictTab();
@ -518,4 +656,4 @@ namespace Penumbra.UI
} }
} }
} }
} }

View file

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using ImGuiNET; using ImGuiNET;
@ -15,34 +16,40 @@ namespace Penumbra.UI
private const string LabelNewMultiGroup = "New Multi Group"; private const string LabelNewMultiGroup = "New Multi Group";
private const string LabelGamePathsEdit = "Game Paths"; private const string LabelGamePathsEdit = "Game Paths";
private const string LabelGamePathsEditBox = "##gamePathsEdit"; private const string LabelGamePathsEditBox = "##gamePathsEdit";
private const string TextNoOptionAvailable = "[Not Available]";
private const string ButtonAddToGroup = "Add to Group"; private const string ButtonAddToGroup = "Add to Group";
private const string ButtonRemoveFromGroup = "Remove from Group"; private const string ButtonRemoveFromGroup = "Remove from Group";
private const string TooltipAboutEdit = "Use Ctrl+Enter for newlines."; private const string TooltipAboutEdit = "Use Ctrl+Enter for newlines.";
private const string TextNoOptionAvailable = "[Not Available]";
private const string TextDefaultGamePath = "default"; private const string TextDefaultGamePath = "default";
private const char GamePathsSeparator = ';'; private const char GamePathsSeparator = ';';
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 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}\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 const float MultiEditBoxWidth = 300f;
private bool DrawEditGroupSelector() private bool DrawEditGroupSelector()
{ {
ImGui.SetNextItemWidth( OptionSelectionWidth ); ImGui.SetNextItemWidth( OptionSelectionWidth );
if (Meta.Groups.Count == 0) if( Meta.Groups.Count == 0 )
{ {
ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex, TextNoOptionAvailable, 1); ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex, TextNoOptionAvailable, 1 );
return false; return false;
} }
else
if( ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex
, Meta.Groups.Values.Select( G => G.GroupName ).ToArray()
, Meta.Groups.Count ) )
{ {
if (ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex, Meta.Groups.Values.Select( G => G.GroupName ).ToArray(), Meta.Groups.Count)) SelectGroup();
{ SelectOption( 0 );
SelectGroup();
SelectOption(0);
}
} }
return true; return true;
} }
@ -50,15 +57,19 @@ namespace Penumbra.UI
{ {
ImGui.SameLine(); ImGui.SameLine();
ImGui.SetNextItemWidth( OptionSelectionWidth ); ImGui.SetNextItemWidth( OptionSelectionWidth );
if ((_selectedGroup?.Options.Count ?? 0) == 0) if( ( _selectedGroup?.Options.Count ?? 0 ) == 0 )
{ {
ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, TextNoOptionAvailable, 1); ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, TextNoOptionAvailable, 1 );
return false; return false;
} }
var group = (InstallerInfo) _selectedGroup; var group = ( InstallerInfo )_selectedGroup;
if (ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, group.Options.Select(O => O.OptionName).ToArray(), group.Options.Count)) if( ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, group.Options.Select( O => O.OptionName ).ToArray(),
group.Options.Count ) )
{
SelectOption(); SelectOption();
}
return true; return true;
} }
@ -67,13 +78,19 @@ namespace Penumbra.UI
if( ImGui.BeginTabItem( LabelFileListTab ) ) if( ImGui.BeginTabItem( LabelFileListTab ) )
{ {
UpdateFilenameList(); UpdateFilenameList();
if (ImGui.IsItemHovered()) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( _editMode ? TooltipFilesTabEdit : TooltipFilesTab ); ImGui.SetTooltip( _editMode ? TooltipFilesTabEdit : TooltipFilesTab );
}
ImGui.SetNextItemWidth( -1 ); ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( LabelFileListHeader, AutoFillSize - new Vector2(0, 1.5f * ImGui.GetTextLineHeight()) ) ) if( ImGui.ListBoxHeader( LabelFileListHeader, AutoFillSize - new Vector2( 0, 1.5f * ImGui.GetTextLineHeight() ) ) )
for(var i = 0; i < Mod.Mod.ModFiles.Count; ++i) {
DrawFileAndGamePaths(i); for( var i = 0; i < Mod.Mod.ModFiles.Count; ++i )
{
DrawFileAndGamePaths( i );
}
}
ImGui.ListBoxFooter(); ImGui.ListBoxFooter();
@ -81,82 +98,90 @@ namespace Penumbra.UI
ImGui.EndTabItem(); ImGui.EndTabItem();
} }
else else
{
_fullFilenameList = null; _fullFilenameList = null;
}
} }
private bool DrawMultiSelectorEditBegin(InstallerInfo group) private bool DrawMultiSelectorEditBegin( InstallerInfo group )
{ {
var groupName = group.GroupName; var groupName = group.GroupName;
if (ImGuiCustom.BeginFramedGroupEdit(ref groupName) if( ImGuiCustom.BeginFramedGroupEdit( ref groupName )
&& groupName != group.GroupName && !Meta.Groups.ContainsKey(groupName)) && groupName != group.GroupName && !Meta.Groups.ContainsKey( groupName ) )
{ {
var oldConf = Mod.Conf[group.GroupName]; var oldConf = Mod.Conf[ group.GroupName ];
Meta.Groups.Remove(group.GroupName); Meta.Groups.Remove( group.GroupName );
Mod.Conf.Remove(group.GroupName); Mod.Conf.Remove( group.GroupName );
if (groupName.Length > 0) if( groupName.Length > 0 )
{ {
Meta.Groups[groupName] = new(){ GroupName = groupName, SelectionType = SelectType.Multi, Options = group.Options }; Meta.Groups[ groupName ] = new InstallerInfo()
Mod.Conf[groupName] = oldConf; { GroupName = groupName, SelectionType = SelectType.Multi, Options = group.Options };
Mod.Conf[ groupName ] = oldConf;
} }
return true; return true;
} }
return false; return false;
} }
private void DrawMultiSelectorEditAdd(InstallerInfo group, float nameBoxStart)
private void DrawMultiSelectorEditAdd( InstallerInfo group, float nameBoxStart )
{ {
var newOption = ""; var newOption = "";
ImGui.SetCursorPosX(nameBoxStart); ImGui.SetCursorPosX( nameBoxStart );
ImGui.SetNextItemWidth(MultiEditBoxWidth); ImGui.SetNextItemWidth( MultiEditBoxWidth );
if (ImGui.InputText($"##new_{group.GroupName}_l", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue)) if( ImGui.InputText( $"##new_{group.GroupName}_l", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue )
&& newOption.Length != 0 )
{ {
if (newOption.Length != 0) group.Options.Add( new Option()
{ { OptionName = newOption, OptionDesc = "", OptionFiles = new Dictionary< string, HashSet< string > >() } );
group.Options.Add(new(){ OptionName = newOption, OptionDesc = "", OptionFiles = new() }); _selector.SaveCurrentMod();
_selector.SaveCurrentMod();
}
} }
} }
private void DrawMultiSelectorEdit(InstallerInfo group) private void DrawMultiSelectorEdit( InstallerInfo group )
{ {
var nameBoxStart = CheckMarkSize; var nameBoxStart = CheckMarkSize;
var flag = Mod.Conf[group.GroupName]; var flag = Mod.Conf[ group.GroupName ];
var modChanged = DrawMultiSelectorEditBegin(group); var modChanged = DrawMultiSelectorEditBegin( group );
for (var i = 0; i < group.Options.Count; ++i) for( var i = 0; i < group.Options.Count; ++i )
{ {
var opt = group.Options[i]; var opt = group.Options[ i ];
var label = $"##{opt.OptionName}_{group.GroupName}"; var label = $"##{opt.OptionName}_{group.GroupName}";
DrawMultiSelectorCheckBox(group, i, flag, label); DrawMultiSelectorCheckBox( group, i, flag, label );
ImGui.SameLine(); ImGui.SameLine();
var newName = opt.OptionName; var newName = opt.OptionName;
if (nameBoxStart == CheckMarkSize) if( nameBoxStart == CheckMarkSize )
nameBoxStart = ImGui.GetCursorPosX();
ImGui.SetNextItemWidth(MultiEditBoxWidth);
if (ImGui.InputText($"{label}_l", ref newName, 64, ImGuiInputTextFlags.EnterReturnsTrue))
{ {
if (newName.Length == 0) nameBoxStart = ImGui.GetCursorPosX();
}
ImGui.SetNextItemWidth( MultiEditBoxWidth );
if( ImGui.InputText( $"{label}_l", ref newName, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
{
if( newName.Length == 0 )
{ {
group.Options.RemoveAt(i); group.Options.RemoveAt( i );
var bitmaskFront = (1 << i) - 1; var bitmaskFront = ( 1 << i ) - 1;
Mod.Conf[group.GroupName] = (flag & bitmaskFront) | ((flag & ~bitmaskFront) >> 1); Mod.Conf[ group.GroupName ] = ( flag & bitmaskFront ) | ( ( flag & ~bitmaskFront ) >> 1 );
modChanged = true; modChanged = true;
} }
else if (newName != opt.OptionName) else if( newName != opt.OptionName )
{ {
group.Options[i] = new(){ OptionName = newName, OptionDesc = opt.OptionDesc, OptionFiles = opt.OptionFiles }; group.Options[ i ] = new Option()
{ OptionName = newName, OptionDesc = opt.OptionDesc, OptionFiles = opt.OptionFiles };
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
} }
} }
} }
DrawMultiSelectorEditAdd(group, nameBoxStart); DrawMultiSelectorEditAdd( group, nameBoxStart );
if (modChanged) if( modChanged )
{ {
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
Save(); Save();
@ -165,134 +190,164 @@ namespace Penumbra.UI
ImGuiCustom.EndFramedGroup(); ImGuiCustom.EndFramedGroup();
} }
private bool DrawSingleSelectorEditGroup(InstallerInfo group) private bool DrawSingleSelectorEditGroup( InstallerInfo group )
{ {
var groupName = group.GroupName; var groupName = group.GroupName;
if (ImGui.InputText($"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue) if( ImGui.InputText( $"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue )
&& !Meta.Groups.ContainsKey(groupName)) && !Meta.Groups.ContainsKey( groupName ) )
{ {
var oldConf = Mod.Conf[group.GroupName]; var oldConf = Mod.Conf[ group.GroupName ];
if (groupName != group.GroupName) if( groupName != group.GroupName )
{ {
Meta.Groups.Remove(group.GroupName); Meta.Groups.Remove( group.GroupName );
Mod.Conf.Remove(group.GroupName); Mod.Conf.Remove( group.GroupName );
} }
if (groupName.Length > 0)
if( groupName.Length > 0 )
{ {
Meta.Groups.Add(groupName, new InstallerInfo(){ GroupName = groupName, Options = group.Options, SelectionType = SelectType.Single } ); Meta.Groups.Add( groupName,
Mod.Conf[groupName] = oldConf; new InstallerInfo() { GroupName = groupName, Options = group.Options, SelectionType = SelectType.Single } );
Mod.Conf[ groupName ] = oldConf;
} }
return true; return true;
} }
return false; return false;
} }
private float DrawSingleSelectorEdit(InstallerInfo group) private float DrawSingleSelectorEdit( InstallerInfo group )
{ {
var code = Mod.Conf[group.GroupName]; var code = Mod.Conf[ group.GroupName ];
var selectionChanged = false; var selectionChanged = false;
var modChanged = false; var modChanged = false;
var newName = ""; if( ImGuiCustom.RenameableCombo( $"##{group.GroupName}", ref code, out var newName,
if (ImGuiCustom.RenameableCombo($"##{group.GroupName}", ref code, ref newName, group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count)) group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) )
{ {
if (code == group.Options.Count) if( code == group.Options.Count )
{ {
if (newName.Length > 0) if( newName.Length > 0 )
{ {
selectionChanged = true; selectionChanged = true;
modChanged = true; modChanged = true;
Mod.Conf[group.GroupName] = code; Mod.Conf[ group.GroupName ] = code;
group.Options.Add(new(){ OptionName = newName, OptionDesc = "", OptionFiles = new()}); group.Options.Add( new Option()
{ OptionName = newName, OptionDesc = "", OptionFiles = new Dictionary< string, HashSet< string > >() } );
} }
} }
else else
{ {
if (newName.Length == 0) if( newName.Length == 0 )
{ {
modChanged = true; modChanged = true;
group.Options.RemoveAt(code); group.Options.RemoveAt( code );
if (code >= group.Options.Count) if( code >= group.Options.Count )
{
code = 0; code = 0;
}
} }
else if (newName != group.Options[code].OptionName) else if( newName != group.Options[ code ].OptionName )
{ {
modChanged = true; modChanged = true;
group.Options[code] = new Option(){ OptionName = newName, OptionDesc = group.Options[code].OptionDesc, OptionFiles = group.Options[code].OptionFiles}; group.Options[ code ] = new Option()
{
OptionName = newName, OptionDesc = group.Options[ code ].OptionDesc,
OptionFiles = group.Options[ code ].OptionFiles
};
} }
if (Mod.Conf[group.GroupName] != code)
if( Mod.Conf[ group.GroupName ] != code )
{ {
selectionChanged = true; selectionChanged = true;
Mod.Conf[group.GroupName] = code; Mod.Conf[ group.GroupName ] = code;
} }
} }
} }
ImGui.SameLine(); ImGui.SameLine();
var labelEditPos = ImGui.GetCursorPosX(); var labelEditPos = ImGui.GetCursorPosX();
modChanged |= DrawSingleSelectorEditGroup(group); modChanged |= DrawSingleSelectorEditGroup( group );
if (modChanged) if( modChanged )
{
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
}
if (selectionChanged) if( selectionChanged )
{
Save(); Save();
}
return labelEditPos; return labelEditPos;
} }
private void AddNewGroup(string newGroup, SelectType selectType) private void AddNewGroup( string newGroup, SelectType selectType )
{ {
if (!Meta.Groups.ContainsKey(newGroup) && newGroup.Length > 0) if( Meta.Groups.ContainsKey( newGroup ) || newGroup.Length <= 0 )
{ {
Meta.Groups[newGroup] = new () return;
{
GroupName = newGroup,
SelectionType = selectType,
Options = new()
} ;
Mod.Conf[newGroup] = 0;
_selector.SaveCurrentMod();
Save();
} }
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) private void DrawAddSingleGroupField( float labelEditPos )
{ {
var newGroup = ""; var newGroup = "";
if(labelEditPos == CheckMarkSize) if( labelEditPos == CheckMarkSize )
{ {
ImGui.SetCursorPosX(CheckMarkSize); ImGui.SetCursorPosX( CheckMarkSize );
ImGui.SetNextItemWidth(MultiEditBoxWidth); ImGui.SetNextItemWidth( MultiEditBoxWidth );
if (ImGui.InputText(LabelNewSingleGroup, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue)) if( ImGui.InputText( LabelNewSingleGroup, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
AddNewGroup(newGroup, SelectType.Single); {
AddNewGroup( newGroup, SelectType.Single );
}
} }
else else
{ {
ImGuiCustom.RightJustifiedLabel(labelEditPos, LabelNewSingleGroup ); ImGuiCustom.RightJustifiedLabel( labelEditPos, LabelNewSingleGroup );
if (ImGui.InputText(LabelNewSingleGroupEdit, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue)) if( ImGui.InputText( LabelNewSingleGroupEdit, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
AddNewGroup(newGroup, SelectType.Single); {
AddNewGroup( newGroup, SelectType.Single );
}
} }
} }
private void DrawAddMultiGroupField() private void DrawAddMultiGroupField()
{ {
var newGroup = ""; var newGroup = "";
ImGui.SetCursorPosX(CheckMarkSize); ImGui.SetCursorPosX( CheckMarkSize );
ImGui.SetNextItemWidth(MultiEditBoxWidth); ImGui.SetNextItemWidth( MultiEditBoxWidth );
if (ImGui.InputText(LabelNewMultiGroup, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue)) if( ImGui.InputText( LabelNewMultiGroup, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue ) )
AddNewGroup(newGroup, SelectType.Multi); {
AddNewGroup( newGroup, SelectType.Multi );
}
} }
private void DrawGroupSelectorsEdit() private void DrawGroupSelectorsEdit()
{ {
var labelEditPos = CheckMarkSize; var labelEditPos = CheckMarkSize;
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 ) )
labelEditPos = DrawSingleSelectorEdit(g); {
DrawAddSingleGroupField(labelEditPos); labelEditPos = DrawSingleSelectorEdit( g );
}
DrawAddSingleGroupField( labelEditPos );
foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Multi ) )
{
DrawMultiSelectorEdit( g );
}
foreach(var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Multi ))
DrawMultiSelectorEdit(g);
DrawAddMultiGroupField(); DrawAddMultiGroupField();
} }
} }

View file

@ -16,45 +16,48 @@ 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 float HeaderLineDistance = 10f; private const string TooltipDeduplicate =
private static readonly Vector4 GreyColor = new( 1f, 1f, 1f, 0.66f ); "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 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;
public ModPanel(SettingsInterface ui, Selector s) public ModPanel( SettingsInterface ui, Selector s )
{ {
_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;
private void DrawName() private void DrawName()
{ {
var name = Meta.Name; var name = Meta.Name;
if (ImGuiCustom.InputOrText(_editMode, LabelEditName, ref name, 64) if( ImGuiCustom.InputOrText( _editMode, LabelEditName, ref name, 64 )
&& name.Length > 0 && name != Meta.Name) && name.Length > 0 && name != Meta.Name )
{ {
Meta.Name = name; Meta.Name = name;
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
@ -63,27 +66,27 @@ namespace Penumbra.UI
private void DrawVersion() private void DrawVersion()
{ {
if (_editMode) if( _editMode )
{ {
ImGui.BeginGroup(); ImGui.BeginGroup();
ImGui.Text("(Version "); ImGui.Text( "(Version " );
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ZeroVector); ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
ImGui.SameLine(); ImGui.SameLine();
var version = Meta.Version ?? ""; var version = Meta.Version ?? "";
if (ImGuiCustom.ResizingTextInput( LabelEditVersion, ref version, 16) if( ImGuiCustom.ResizingTextInput( LabelEditVersion, ref version, 16 )
&& version != Meta.Version) && version != Meta.Version )
{ {
Meta.Version = version.Length > 0 ? version : null; Meta.Version = version.Length > 0 ? version : null;
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
} }
ImGui.SameLine(); ImGui.SameLine();
ImGui.Text(")"); ImGui.Text( ")" );
ImGui.PopStyleVar(); ImGui.PopStyleVar();
ImGui.EndGroup(); ImGui.EndGroup();
} }
else if ((Meta.Version?.Length ?? 0) > 0) else if( ( Meta.Version?.Length ?? 0 ) > 0 )
{ {
ImGui.Text( $"(Version {Meta.Version})" ); ImGui.Text( $"(Version {Meta.Version})" );
} }
@ -96,58 +99,62 @@ namespace Penumbra.UI
ImGui.SameLine(); ImGui.SameLine();
var author = Meta.Author ?? ""; var author = Meta.Author ?? "";
if (ImGuiCustom.InputOrText(_editMode, LabelEditAuthor, ref author, 64) if( ImGuiCustom.InputOrText( _editMode, LabelEditAuthor, ref author, 64 )
&& author != Meta.Author) && author != Meta.Author )
{ {
Meta.Author = author.Length > 0 ? author : null; Meta.Author = author.Length > 0 ? author : null;
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
} }
ImGui.EndGroup(); ImGui.EndGroup();
} }
private void DrawWebsite() private void DrawWebsite()
{ {
ImGui.BeginGroup(); ImGui.BeginGroup();
if (_editMode) if( _editMode )
{ {
ImGui.TextColored( GreyColor, "from" ); ImGui.TextColored( GreyColor, "from" );
ImGui.SameLine(); ImGui.SameLine();
var website = Meta.Website ?? ""; var website = Meta.Website ?? "";
if (ImGuiCustom.ResizingTextInput(LabelEditWebsite, ref website, 512) if( ImGuiCustom.ResizingTextInput( LabelEditWebsite, ref website, 512 )
&& website != Meta.Website) && website != Meta.Website )
{ {
Meta.Website = website.Length > 0 ? website : null; Meta.Website = website.Length > 0 ? website : null;
_selector.SaveCurrentMod(); _selector.SaveCurrentMod();
} }
} }
else if (( Meta.Website?.Length ?? 0 ) > 0) else if( ( Meta.Website?.Length ?? 0 ) > 0 )
{ {
if (_currentWebsite != Meta.Website) if( _currentWebsite != Meta.Website )
{ {
_currentWebsite = Meta.Website; _currentWebsite = Meta.Website;
_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 ) )
{ {
try try
{ {
var process = new ProcessStartInfo(Meta.Website) var process = new ProcessStartInfo( Meta.Website )
{ {
UseShellExecute = true UseShellExecute = true
}; };
Process.Start(process); Process.Start( process );
} }
catch(System.ComponentModel.Win32Exception) catch( System.ComponentModel.Win32Exception )
{ {
// Do nothing. // Do nothing.
} }
} }
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( Meta.Website ); ImGui.SetTooltip( Meta.Website );
}
} }
else else
{ {
@ -156,6 +163,7 @@ namespace Penumbra.UI
ImGui.Text( Meta.Website ); ImGui.Text( Meta.Website );
} }
} }
ImGui.EndGroup(); ImGui.EndGroup();
} }
@ -178,13 +186,13 @@ 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 );
} }
} }
private void DrawEditableMark() private void DrawEditableMark()
{ {
ImGui.Checkbox( LabelEditingEnabled, ref _editMode); ImGui.Checkbox( LabelEditingEnabled, ref _editMode );
} }
private void DrawOpenModFolderButton() private void DrawOpenModFolderButton()
@ -193,8 +201,11 @@ namespace Penumbra.UI
{ {
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()
@ -203,8 +214,11 @@ 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()
@ -213,22 +227,28 @@ 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()
{ {
if( ImGui.Button( ButtonDeduplicate ) ) if( ImGui.Button( ButtonDeduplicate ) )
{ {
new Deduplicator(Mod.Mod.ModBasePath, Meta).Run(); new Deduplicator( Mod.Mod.ModBasePath, Meta ).Run();
_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()
@ -244,38 +264,44 @@ namespace Penumbra.UI
public void Draw() public void Draw()
{ {
if( Mod != null ) if( Mod == null )
{ {
try return;
}
try
{
var ret = ImGui.BeginChild( LabelModPanel, AutoFillSize, true );
if( !ret )
{ {
var ret = ImGui.BeginChild( LabelModPanel, AutoFillSize, true ); return;
if (!ret)
return;
DrawHeaderLine();
// Next line with fixed distance.
ImGuiCustom.VerticalDistance(HeaderLineDistance);
DrawEnabledMark();
if (_base._plugin.Configuration.ShowAdvanced)
{
ImGui.SameLine();
DrawEditableMark();
}
// Next line, if editable.
if (_editMode)
DrawEditLine();
_details.Draw(_editMode);
ImGui.EndChild();
} }
catch( Exception ex )
DrawHeaderLine();
// Next line with fixed distance.
ImGuiCustom.VerticalDistance( HeaderLineDistance );
DrawEnabledMark();
if( _base._plugin.Configuration.ShowAdvanced )
{ {
PluginLog.LogError( ex, "fuck" ); ImGui.SameLine();
DrawEditableMark();
} }
// Next line, if editable.
if( _editMode )
{
DrawEditLine();
}
Details.Draw( _editMode );
ImGui.EndChild();
}
catch( Exception ex )
{
PluginLog.LogError( ex, "fuck" );
} }
} }
} }

View file

@ -27,21 +27,21 @@ namespace Penumbra.UI
private const uint DisabledModColor = 0xFF666666; private const uint DisabledModColor = 0xFF666666;
private const uint ConflictingModColor = 0xFFAAAAFF; private const uint ConflictingModColor = 0xFFAAAAFF;
private static readonly Vector2 SelectorButtonSizes = new(60, 0); private static readonly Vector2 SelectorButtonSizes = new( 60, 0 );
private static readonly string ArrowUpString = FontAwesomeIcon.ArrowUp.ToIconString(); private static readonly string ArrowUpString = FontAwesomeIcon.ArrowUp.ToIconString();
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 = null; private ModInfo _mod;
private int _index = 0; private int _index;
private int? _deleteIndex = null; private int? _deleteIndex;
private string _modFilter = ""; private string _modFilter = "";
private string[] _modNamesLower = null; private string[] _modNamesLower;
public Selector(SettingsInterface ui) public Selector( SettingsInterface ui )
{ {
_base = ui; _base = ui;
ResetModNamesLower(); ResetModNamesLower();
@ -52,16 +52,16 @@ namespace Penumbra.UI
_modNamesLower = Mods.ModSettings.Select( I => I.Mod.Meta.Name.ToLowerInvariant() ).ToArray(); _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 )
{ {
ImGui.PushFont( UiBuilder.IconFont ); ImGui.PushFont( UiBuilder.IconFont );
if( _index != unavailableWhen ) if( _index != unavailableWhen )
{ {
if( ImGui.Button( iconString, SelectorButtonSizes ) ) if( ImGui.Button( iconString, SelectorButtonSizes ) )
{ {
SetSelection(_index); SetSelection( _index );
_base._plugin.ModManager.ChangeModPriority( _mod, up ); _base._plugin.ModManager.ChangeModPriority( _mod, up );
_modNamesLower.Swap(_index, _index + (up ? 1 : -1)); _modNamesLower.Swap( _index, _index + ( up ? 1 : -1 ) );
_index += up ? 1 : -1; _index += up ? 1 : -1;
} }
} }
@ -94,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 );
@ -109,19 +111,24 @@ namespace Penumbra.UI
ImGui.PopFont(); ImGui.PopFont();
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipAdd ); ImGui.SetTooltip( TooltipAdd );
}
} }
private void DrawModsSelectorFilter() private void DrawModsSelectorFilter()
{ {
ImGui.SetNextItemWidth( SelectorButtonSizes.X * 4 ); ImGui.SetNextItemWidth( SelectorButtonSizes.X * 4 );
string tmp = _modFilter; var tmp = _modFilter;
if (ImGui.InputText(LabelModFilter, ref tmp, 256)) if( ImGui.InputText( LabelModFilter, ref tmp, 256 ) )
{ {
_modFilter = tmp.ToLowerInvariant(); _modFilter = tmp.ToLowerInvariant();
} }
if( ImGui.IsItemHovered() ) if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipModFilter ); ImGui.SetTooltip( TooltipModFilter );
}
} }
private void DrawModsSelectorButtons() private void DrawModsSelectorButtons()
@ -130,9 +137,9 @@ namespace Penumbra.UI
ImGui.PushStyleVar( ImGuiStyleVar.WindowPadding, ZeroVector ); ImGui.PushStyleVar( ImGuiStyleVar.WindowPadding, ZeroVector );
ImGui.PushStyleVar( ImGuiStyleVar.FrameRounding, 0 ); ImGui.PushStyleVar( ImGuiStyleVar.FrameRounding, 0 );
DrawPriorityChangeButton(ArrowUpString, false, 0); DrawPriorityChangeButton( ArrowUpString, false, 0 );
ImGui.SameLine(); ImGui.SameLine();
DrawPriorityChangeButton(ArrowDownString, true, Mods?.ModSettings.Count - 1 ?? 0); DrawPriorityChangeButton( ArrowDownString, true, Mods?.ModSettings.Count - 1 ?? 0 );
ImGui.SameLine(); ImGui.SameLine();
DrawModTrashButton(); DrawModTrashButton();
ImGui.SameLine(); ImGui.SameLine();
@ -141,16 +148,20 @@ namespace Penumbra.UI
ImGui.PopStyleVar( 3 ); ImGui.PopStyleVar( 3 );
} }
void DrawDeleteModal() private void DrawDeleteModal()
{ {
if( _deleteIndex == null ) if( _deleteIndex == null )
{
return; 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 )
{ {
@ -185,8 +196,10 @@ 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();
@ -195,14 +208,16 @@ namespace Penumbra.UI
DrawModsSelectorFilter(); 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; var modName = settings.Mod.Meta.Name;
if (_modFilter.Length > 0 && !_modNamesLower[modIndex].Contains(_modFilter) ) if( _modFilter.Length > 0 && !_modNamesLower[ modIndex ].Contains( _modFilter ) )
{
continue; continue;
}
var changedColour = false; var changedColour = false;
if( !settings.Enabled ) if( !settings.Enabled )
@ -226,10 +241,14 @@ namespace Penumbra.UI
#endif #endif
if( changedColour ) if( changedColour )
{
ImGui.PopStyleColor(); ImGui.PopStyleColor();
}
if( selected ) if( selected )
SetSelection(modIndex, settings); {
SetSelection( modIndex, settings );
}
} }
ImGui.EndChild(); ImGui.EndChild();
@ -242,26 +261,36 @@ namespace Penumbra.UI
public ModInfo Mod() => _mod; public ModInfo Mod() => _mod;
private void SetSelection(int idx, ModInfo info) private void SetSelection( int idx, ModInfo info )
{ {
_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) }
SetSelection(0, null);
if( idx < 0 )
{
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 );
public void SelectModByName( string name ) public void SelectModByName( string name )
{ {
@ -270,40 +299,42 @@ 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;
} }
} }
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()
{ {
var metaPath = GetCurrentModMetaFile(); var metaPath = GetCurrentModMetaFile();
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); _base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
ResetModNamesLower(); 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

@ -21,9 +21,9 @@ namespace Penumbra.UI
private readonly SettingsInterface _base; private readonly SettingsInterface _base;
private readonly Configuration _config; private readonly Configuration _config;
private bool _configChanged; private bool _configChanged;
public TabSettings(SettingsInterface ui) public TabSettings( SettingsInterface ui )
{ {
_base = ui; _base = ui;
_config = _base._plugin.Configuration; _config = _base._plugin.Configuration;
@ -36,7 +36,7 @@ namespace Penumbra.UI
if( ImGui.InputText( LabelRootFolder, ref basePath, 255 ) && _config.CurrentCollection != basePath ) if( ImGui.InputText( LabelRootFolder, ref basePath, 255 ) && _config.CurrentCollection != basePath )
{ {
_config.CurrentCollection = basePath; _config.CurrentCollection = basePath;
_configChanged = true; _configChanged = true;
} }
} }
@ -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();
} }
} }
@ -63,8 +63,8 @@ namespace Penumbra.UI
if( ImGui.Checkbox( LabelEnabled, ref enabled ) ) if( ImGui.Checkbox( LabelEnabled, ref enabled ) )
{ {
_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 );
} }
} }
@ -85,15 +85,17 @@ namespace Penumbra.UI
if( ImGui.Checkbox( LabelShowAdvanced, ref showAdvanced ) ) if( ImGui.Checkbox( LabelShowAdvanced, ref showAdvanced ) )
{ {
_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()
@ -102,7 +104,7 @@ namespace Penumbra.UI
if( ImGui.Checkbox( LabelDisableNotifications, ref fswatch ) ) if( ImGui.Checkbox( LabelDisableNotifications, ref fswatch ) )
{ {
_config.DisableFileSystemNotifications = fswatch; _config.DisableFileSystemNotifications = fswatch;
_configChanged = true; _configChanged = true;
} }
} }
@ -112,12 +114,16 @@ 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();
@ -149,17 +157,19 @@ namespace Penumbra.UI
ImGui.SameLine(); ImGui.SameLine();
DrawOpenModsButton(); DrawOpenModsButton();
ImGuiCustom.VerticalDistance(DefaultVerticalSpace); ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawEnabledBox(); DrawEnabledBox();
ImGuiCustom.VerticalDistance(DefaultVerticalSpace); ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawInvertModOrderBox(); DrawInvertModOrderBox();
ImGuiCustom.VerticalDistance(DefaultVerticalSpace); ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawShowAdvancedBox(); DrawShowAdvancedBox();
if( _config.ShowAdvanced ) if( _config.ShowAdvanced )
{
DrawAdvancedSettings(); DrawAdvancedSettings();
}
if( _configChanged ) if( _configChanged )
{ {

View file

@ -5,21 +5,34 @@ namespace Penumbra
{ {
public static class ArrayExtensions public static class ArrayExtensions
{ {
public static void Swap<T>( this T[] array, int idx1, int idx2 ) public static void Swap< T >( this T[] array, int idx1, int idx2 )
{ {
var tmp = array[idx1]; var tmp = array[ idx1 ];
array[idx1] = array[idx2]; array[ idx1 ] = array[ idx2 ];
array[idx2] = tmp; array[ idx2 ] = tmp;
} }
public static void Swap<T>( this List<T> array, int idx1, int idx2 ) public static void Swap< T >( this List< T > array, int idx1, int idx2 )
{ {
var tmp = array[idx1]; var tmp = array[ idx1 ];
array[idx1] = array[idx2]; array[ idx1 ] = array[ idx2 ];
array[idx2] = tmp; array[ idx2 ] = tmp;
} }
public static void Swap<T>( this T[] array, T lhs, T rhs ) 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 ); var idx1 = Array.IndexOf( array, lhs );
if( idx1 < 0 ) if( idx1 < 0 )
@ -36,7 +49,7 @@ namespace Penumbra
array.Swap( idx1, idx2 ); array.Swap( idx1, idx2 );
} }
public static void Swap<T>( this List<T> array, T lhs, T rhs ) public static void Swap< T >( this List< T > array, T lhs, T rhs )
{ {
var idx1 = array.IndexOf( lhs ); var idx1 = array.IndexOf( lhs );
if( idx1 < 0 ) if( idx1 < 0 )
@ -53,4 +66,4 @@ namespace Penumbra
array.Swap( idx1, idx2 ); array.Swap( idx1, idx2 );
} }
} }
} }

View file

@ -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,14 +42,16 @@ 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 )
{ {
_crc32 = CrcArray[ ( _crc32 ^ b ) & 0xFF ] ^ _crc32 = CrcArray[ ( _crc32 ^ b ) & 0xFF ] ^
( ( _crc32 >> 8 ) & 0x00FFFFFF ); ( ( _crc32 >> 8 ) & 0x00FFFFFF );
} }
} }
} }

View file

@ -3,21 +3,16 @@ using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; 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;
@ -28,22 +23,20 @@ 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 )
{ {
var token = JToken.Load(reader); var token = JToken.Load( reader );
if (token.Type == JTokenType.Array) if( token.Type == JTokenType.Array )
{ {
return token.ToObject<HashSet<T>>(); return token.ToObject< HashSet< T > >();
} }
return new HashSet<T>{ token.ToObject<T>() };
return new HashSet< T > { token.ToObject< T >() };
} }
public override bool CanWrite => false; public override bool CanWrite => false;
@ -52,4 +45,4 @@ public class DictSingleOrArrayConverter<T,U> : JsonConverter
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }

View file

@ -5,14 +5,11 @@ namespace Penumbra
public static class StringPathExtensions public static class StringPathExtensions
{ {
private static readonly char[] _invalid = Path.GetInvalidFileNameChars(); private static readonly char[] _invalid = Path.GetInvalidFileNameChars();
public static string ReplaceInvalidPathSymbols( this string s, string replacement = "_" ) public static string ReplaceInvalidPathSymbols( this string s, string replacement = "_" )
{ => string.Join( replacement, s.Split( _invalid ) );
return string.Join( replacement, s.Split( _invalid ) );
}
public static string RemoveInvalidPathSymbols( this string s ) public static string RemoveInvalidPathSymbols( this string s )
{ => string.Concat( s.Split( _invalid ) );
return string.Concat( s.Split( _invalid ) );
}
} }
} }