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
end_of_line=lf
trim_trailing_whitespace=false
insert_final_newline=false
indent_style=space
indent_size=4
# Microsoft .NET properties
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_space_after_cast=false
csharp_space_after_keywords_in_control_flow_statements=false
csharp_space_between_method_call_parameter_list_parentheses=true
csharp_space_between_method_declaration_parameter_list_parentheses=true
csharp_space_between_parentheses=control_flow_statements,expressions,type_casts
csharp_style_var_elsewhere=true:suggestion
csharp_style_var_for_built_in_types=true:suggestion
csharp_style_var_when_type_is_apparent=true:suggestion
dotnet_style_parentheses_in_arithmetic_binary_operators=never_if_unnecessary:none
dotnet_style_parentheses_in_other_binary_operators=never_if_unnecessary:none
dotnet_style_parentheses_in_relational_binary_operators=never_if_unnecessary:none
dotnet_style_predefined_type_for_locals_parameters_members=true:suggestion
dotnet_style_predefined_type_for_member_access=true:suggestion
dotnet_style_qualification_for_event=false:suggestion
dotnet_style_qualification_for_field=false:suggestion
dotnet_style_qualification_for_method=false: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_csharp_space_within_array_access_brackets=true
resharper_enforce_line_ending_style=true
resharper_place_attribute_on_same_line=false
resharper_space_after_cast=false
resharper_space_within_checked_parentheses=true
resharper_space_within_default_parentheses=true
resharper_space_within_nameof_parentheses=true
resharper_space_within_single_line_array_initializer_braces=true
resharper_space_within_sizeof_parentheses=true
resharper_space_within_typeof_parentheses=true
resharper_space_within_type_argument_angles=true
resharper_space_within_type_parameter_angles=true
resharper_use_indent_from_vs=false
resharper_wrap_lines=true
# 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
[*]
charset=utf-8
end_of_line=lf
trim_trailing_whitespace=true
insert_final_newline=false
indent_style=space
indent_size=4
# Microsoft .NET properties
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_prefer_braces=true:none
csharp_space_after_cast=false
csharp_space_after_keywords_in_control_flow_statements=false
csharp_space_between_method_call_parameter_list_parentheses=true
csharp_space_between_method_declaration_parameter_list_parentheses=true
csharp_space_between_parentheses=control_flow_statements,expressions,type_casts
csharp_style_var_elsewhere=true:suggestion
csharp_style_var_for_built_in_types=true:suggestion
csharp_style_var_when_type_is_apparent=true:suggestion
dotnet_style_parentheses_in_arithmetic_binary_operators=never_if_unnecessary:none
dotnet_style_parentheses_in_other_binary_operators=never_if_unnecessary:none
dotnet_style_parentheses_in_relational_binary_operators=never_if_unnecessary:none
dotnet_style_predefined_type_for_locals_parameters_members=true:suggestion
dotnet_style_predefined_type_for_member_access=true:suggestion
dotnet_style_qualification_for_event=false:suggestion
dotnet_style_qualification_for_field=false:suggestion
dotnet_style_qualification_for_method=false:suggestion
dotnet_style_qualification_for_property=false:suggestion
dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion
# ReSharper properties
resharper_align_multiline_binary_expressions_chain=false
resharper_align_multiline_calls_chain=false
resharper_autodetect_indent_settings=true
resharper_braces_redundant=true
resharper_constructor_or_destructor_body=expression_body
resharper_csharp_empty_block_style=together
resharper_csharp_max_line_length=144
resharper_csharp_space_within_array_access_brackets=true
resharper_enforce_line_ending_style=true
resharper_int_align_assignments=true
resharper_int_align_comments=true
resharper_int_align_fields=true
resharper_int_align_invocations=false
resharper_int_align_nested_ternary=true
resharper_int_align_properties=false
resharper_int_align_switch_expressions=true
resharper_int_align_switch_sections=true
resharper_int_align_variables=true
resharper_local_function_body=expression_body
resharper_method_or_operator_body=expression_body
resharper_place_attribute_on_same_line=false
resharper_space_after_cast=false
resharper_space_within_checked_parentheses=true
resharper_space_within_default_parentheses=true
resharper_space_within_nameof_parentheses=true
resharper_space_within_single_line_array_initializer_braces=true
resharper_space_within_sizeof_parentheses=true
resharper_space_within_typeof_parentheses=true
resharper_space_within_type_argument_angles=true
resharper_space_within_type_parameter_angles=true
resharper_use_indent_from_vs=false
resharper_wrap_lines=true
# 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;
public ModsController( Plugin plugin )
{
_plugin = plugin;
}
public ModsController( Plugin plugin ) => _plugin = plugin;
[Route( HttpVerbs.Get, "/mods" )]
public object GetMods()
@ -24,7 +21,7 @@ namespace Penumbra.API
x.FolderName,
x.Mod.Meta,
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 )
{
var taskSource = new TaskCompletionSource< DialogResult >();
var th = new Thread( () => DialogThread( form, owner, taskSource ) );
var th = new Thread( () => DialogThread( form, owner, taskSource ) );
th.Start();
return taskSource.Task;
}
@ -38,28 +38,25 @@ namespace Penumbra
{
public IntPtr Handle { get; set; }
public DialogHandle( IntPtr handle )
{
Handle = handle;
}
public DialogHandle( IntPtr handle ) => Handle = handle;
}
public class HiddenForm : Form
{
private readonly CommonDialog _form;
private readonly IWin32Window _owner;
private readonly CommonDialog _form;
private readonly IWin32Window _owner;
private readonly TaskCompletionSource< DialogResult > _taskSource;
public HiddenForm( CommonDialog form, IWin32Window owner, TaskCompletionSource< DialogResult > taskSource )
{
this._form = form;
this._owner = owner;
this._taskSource = taskSource;
_form = form;
_owner = owner;
_taskSource = taskSource;
Opacity = 0;
Opacity = 0;
FormBorderStyle = FormBorderStyle.None;
ShowInTaskbar = false;
Size = new Size( 0, 0 );
ShowInTaskbar = false;
Size = new Size( 0, 0 );
Shown += HiddenForm_Shown;
}

View file

@ -25,7 +25,8 @@ namespace Penumbra.Extensions
/// <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>
/// <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;

View file

@ -14,8 +14,8 @@ namespace Penumbra.Game
public unsafe delegate void* UnloadPlayerResourcesPrototype( IntPtr pResourceManager );
public LoadPlayerResourcesPrototype LoadPlayerResources { get; private set; }
public UnloadPlayerResourcesPrototype UnloadPlayerResources { get; private set; }
public LoadPlayerResourcesPrototype LoadPlayerResources { get; }
public UnloadPlayerResourcesPrototype UnloadPlayerResources { get; }
// Object addresses
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" );
LoadPlayerResources = Marshal.GetDelegateForFunctionPointer< LoadPlayerResourcesPrototype >( loadPlayerResourcesAddress );
LoadPlayerResources = Marshal.GetDelegateForFunctionPointer< LoadPlayerResourcesPrototype >( loadPlayerResourcesAddress );
UnloadPlayerResources = Marshal.GetDelegateForFunctionPointer< UnloadPlayerResourcesPrototype >( unloadPlayerResourcesAddress );
}

View file

@ -1,9 +1,10 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types;
namespace Penumbra
namespace Penumbra.Game
{
public static class RefreshActors
{
@ -12,43 +13,49 @@ namespace Penumbra
private const int RenderTaskOtherDelay = 25;
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 renderStatus = Marshal.ReadInt32(renderModePtr);
var renderStatus = Marshal.ReadInt32( renderModePtr );
async void DrawObject(int delay)
async void DrawObject( int delay )
{
Marshal.WriteInt32(renderModePtr, renderStatus | ModelInvisibilityFlag);
await Task.Delay(delay);
Marshal.WriteInt32(renderModePtr, renderStatus & ~ModelInvisibilityFlag);
Marshal.WriteInt32( renderModePtr, renderStatus | ModelInvisibilityFlag );
await Task.Delay( delay );
Marshal.WriteInt32( renderModePtr, renderStatus & ~ModelInvisibilityFlag );
}
if (actor.ObjectKind == Dalamud.Game.ClientState.Actors.ObjectKind.Player)
if( actor.ObjectKind == ObjectKind.Player )
{
DrawObject(RenderTaskPlayerDelay);
await Task.Delay(RenderTaskPlayerDelay);
DrawObject( RenderTaskPlayerDelay );
await Task.Delay( RenderTaskPlayerDelay );
}
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)
RedrawAll(actors);
if( name?.Length == 0 )
{
RedrawAll( actors );
}
foreach (var actor in actors)
if (actor.Name == name)
Redraw(actor);
foreach( var actor in actors.Where( A => A.Name == name ) )
{
Redraw( actor );
}
}
public static void RedrawAll(ActorTable actors)
public static void RedrawAll( ActorTable actors )
{
foreach (var actor in actors)
Redraw(actor);
foreach( var actor in actors )
{
Redraw( actor );
}
}
}
}
}

View file

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

View file

@ -16,7 +16,7 @@ namespace Penumbra.Importer
{
private readonly DirectoryInfo _outDirectory;
private const string TempFileName = "textools-import";
private const string TempFileName = "textools-import";
private readonly string _resolvedTempFilePath;
public ImporterState State { get; private set; }
@ -42,7 +42,7 @@ namespace Penumbra.Importer
public TexToolsImport( DirectoryInfo outDirectory )
{
_outDirectory = outDirectory;
_outDirectory = outDirectory;
_resolvedTempFilePath = Path.Combine( _outDirectory.FullName, TempFileName );
}
@ -50,7 +50,7 @@ namespace Penumbra.Importer
{
CurrentModPack = modPackFile.Name;
VerifyVersionAndImport(modPackFile);
VerifyVersionAndImport( modPackFile );
State = ImporterState.Done;
}
@ -67,8 +67,8 @@ namespace Penumbra.Importer
State = ImporterState.WritingPackToDisk;
// write shitty zip garbage to disk
var entry = file.GetEntry( entryName );
using var s = file.GetInputStream( entry );
var entry = file.GetEntry( entryName );
using var s = file.GetInputStream( entry );
WriteZipEntryToTempFile( s );
@ -76,25 +76,31 @@ namespace Penumbra.Importer
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 );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modRaw = GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 );
var mpl = extractedModPack.GetEntry( "TTMPL.mpl" );
var modRaw = GetStringFromZipEntry( extractedModPack, mpl, Encoding.UTF8 );
// At least a better validation than going by the extension.
if (modRaw.Contains("\"TTMPVersion\":"))
if( modRaw.Contains( "\"TTMPVersion\":" ) )
{
if (modPackFile.Extension != ".ttmp2")
PluginLog.Warning($"File {modPackFile.FullName} seems to be a V2 TTMP, but has the wrong extension.");
ImportV2ModPack(modPackFile, extractedModPack, modRaw);
if( modPackFile.Extension != ".ttmp2" )
{
PluginLog.Warning( $"File {modPackFile.FullName} seems to be a V2 TTMP, but has the wrong extension." );
}
ImportV2ModPack( modPackFile, extractedModPack, modRaw );
}
else
{
if (modPackFile.Extension != ".ttmp")
PluginLog.Warning($"File {modPackFile.FullName} seems to be a V1 TTMP, but has the wrong extension.");
ImportV1ModPack(modPackFile, extractedModPack, modRaw);
if( modPackFile.Extension != ".ttmp" )
{
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
var modMeta = new ModMeta
{
Author = "Unknown",
Name = modPackFile.Name,
Author = "Unknown",
Name = modPackFile.Name,
Description = "Mod imported from TexTools mod pack"
};
@ -135,7 +141,7 @@ namespace Penumbra.Importer
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 );
@ -159,7 +165,7 @@ namespace Penumbra.Importer
var modMeta = new ModMeta
{
Author = modList.Author,
Name = modList.Name,
Name = modList.Name,
Description = string.IsNullOrEmpty( modList.Description )
? "Mod imported from TexTools mod pack"
: modList.Description
@ -188,7 +194,7 @@ namespace Penumbra.Importer
var modMeta = new ModMeta
{
Author = modList.Author,
Name = modList.Name,
Name = modList.Name,
Description = string.IsNullOrEmpty( modList.Description )
? "Mod imported from TexTools mod pack"
: modList.Description,
@ -206,24 +212,26 @@ namespace Penumbra.Importer
newModFolder.Create();
if( modList.SimpleModsList != null )
{
ExtractSimpleModList( newModFolder, modList.SimpleModsList, modData );
}
if( modList.ModPackPages == null )
{
return;
}
// 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()));
foreach(var option in group.OptionList)
{
var optionFolder = new DirectoryInfo( Path.Combine( groupFolder.FullName, option.Name.ReplaceInvalidPathSymbols()) );
ExtractSimpleModList( optionFolder, option.ModsJsons, modData );
}
AddMeta(newModFolder, groupFolder, group, modMeta);
var optionFolder = new DirectoryInfo( Path.Combine( groupFolder.FullName, option.Name.ReplaceInvalidPathSymbols() ) );
ExtractSimpleModList( optionFolder, option.ModsJsons, modData );
}
AddMeta( newModFolder, groupFolder, group, modMeta );
}
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
{
SelectionType = group.SelectionType,
GroupName = group.GroupName,
Options = new List<Option>(),
GroupName = group.GroupName,
Options = new List< Option >()
};
foreach( var opt in group.OptionList )
{
var optio = new Option
var option = new Option
{
OptionName = opt.Name,
OptionDesc = String.IsNullOrEmpty(opt.Description) ? "" : opt.Description,
OptionFiles = new Dictionary<string, HashSet<string>>()
OptionName = opt.Name,
OptionDesc = string.IsNullOrEmpty( opt.Description ) ? "" : opt.Description,
OptionFiles = new Dictionary< string, HashSet< string > >()
};
var optDir = new DirectoryInfo(Path.Combine( groupFolder.FullName, opt.Name.ReplaceInvalidPathSymbols()));
if (optDir.Exists)
var optDir = new DirectoryInfo( Path.Combine( groupFolder.FullName, opt.Name.ReplaceInvalidPathSymbols() ) );
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 );
}
@ -276,14 +287,8 @@ namespace Penumbra.Importer
TotalProgress += wtf.LongCount();
// 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 );
CurrentProgress++;
}
@ -301,7 +306,9 @@ namespace Penumbra.Importer
extractedFile.Directory?.Create();
if( extractedFile.FullName.EndsWith( "mdl" ) )
{
ProcessMdl( data.Data );
}
File.WriteAllBytes( extractedFile.FullName, data.Data );
}
@ -317,26 +324,23 @@ namespace Penumbra.Importer
mdl[ 64 ] = 1;
// Model header LOD num
var stackSize = BitConverter.ToUInt32( mdl, 4 );
var runtimeBegin = stackSize + 0x44;
var stringsLengthOffset = runtimeBegin + 4;
var stringsLength = BitConverter.ToUInt32( mdl, ( int )stringsLengthOffset );
var modelHeaderStart = stringsLengthOffset + stringsLength + 4;
var stackSize = BitConverter.ToUInt32( mdl, 4 );
var runtimeBegin = stackSize + 0x44;
var stringsLengthOffset = runtimeBegin + 4;
var stringsLength = BitConverter.ToUInt32( mdl, ( int )stringsLengthOffset );
var modelHeaderStart = stringsLengthOffset + stringsLength + 4;
var modelHeaderLodOffset = 22;
mdl[ modelHeaderStart + modelHeaderLodOffset ] = 1;
}
private static Stream GetStreamFromZipEntry( ZipFile file, ZipEntry entry )
{
return file.GetInputStream( entry );
}
private static Stream GetStreamFromZipEntry( ZipFile file, ZipEntry entry ) => file.GetInputStream( entry );
private static string GetStringFromZipEntry( ZipFile file, ZipEntry entry, Encoding encoding )
{
using var ms = new MemoryStream();
using var s = GetStreamFromZipEntry( file, entry );
using var s = GetStreamFromZipEntry( file, entry );
s.CopyTo( ms );
return encoding.GetString( ms.ToArray() );
}
}
}
}

View file

@ -12,18 +12,17 @@ namespace Penumbra.Models
private readonly DirectoryInfo _baseDir;
private readonly int _baseDirLength;
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()
{
if (_hasher == null)
_hasher = SHA256.Create();
_hasher ??= SHA256.Create();
return ref _hasher;
}
public Deduplicator(DirectoryInfo baseDir, ModMeta mod)
public Deduplicator( DirectoryInfo baseDir, ModMeta mod )
{
_baseDir = baseDir;
_baseDirLength = baseDir.FullName.Length;
@ -37,125 +36,134 @@ namespace Penumbra.Models
foreach( var file in _baseDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
{
var fileLength = file.Length;
if (_filesBySize.TryGetValue(fileLength, out var files))
files.Add(file);
if( _filesBySize.TryGetValue( fileLength, out var files ) )
{
files.Add( file );
}
else
_filesBySize[fileLength] = new(){ file };
{
_filesBySize[ fileLength ] = new List< FileInfo >() { file };
}
}
}
public void Run()
{
foreach (var pair in _filesBySize)
foreach( var pair in _filesBySize.Where( pair => pair.Value.Count >= 2 ) )
{
if (pair.Value.Count < 2)
continue;
if (pair.Value.Count == 2)
if( pair.Value.Count == 2 )
{
if (CompareFilesDirectly(pair.Value[0], pair.Value[1]))
ReplaceFile(pair.Value[0], pair.Value[1]);
if( CompareFilesDirectly( pair.Value[ 0 ], pair.Value[ 1 ] ) )
{
ReplaceFile( pair.Value[ 0 ], pair.Value[ 1 ] );
}
}
else
{
var deleted = Enumerable.Repeat(false, pair.Value.Count).ToArray();
var hashes = pair.Value.Select( F => ComputeHash(F)).ToArray();
var deleted = Enumerable.Repeat( false, pair.Value.Count ).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])
continue;
for (var j = i + 1; j < pair.Value.Count; ++j)
if( deleted[ i ] )
{
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;
}
ReplaceFile(pair.Value[i], pair.Value[j]);
deleted[j] = true;
ReplaceFile( pair.Value[ i ], pair.Value[ j ] );
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 relName2 = f2.FullName.Substring(_baseDirLength).TrimStart('\\');
var relName1 = f1.FullName.Substring( _baseDirLength ).TrimStart( '\\' );
var relName2 = f2.FullName.Substring( _baseDirLength ).TrimStart( '\\' );
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;
foreach (var value in values)
option.AddFile(relName1, value);
option.OptionFiles.Remove(relName2);
foreach( var value in values )
{
option.AddFile( relName1, value );
}
option.OptionFiles.Remove( relName2 );
}
}
}
if (!inOption)
if( !inOption )
{
const string duplicates = "Duplicates";
if (!_mod.Groups.ContainsKey(duplicates))
if( !_mod.Groups.ContainsKey( duplicates ) )
{
InstallerInfo info = new()
{
GroupName = duplicates,
GroupName = duplicates,
SelectionType = SelectType.Single,
Options = new()
Options = new List< Option >()
{
new()
{
OptionName = "Required",
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();
}
public static bool CompareFilesDirectly(FileInfo f1, FileInfo f2)
{
return File.ReadAllBytes(f1.FullName).SequenceEqual(File.ReadAllBytes(f2.FullName));
}
public static bool CompareFilesDirectly( FileInfo f1, FileInfo f2 )
=> File.ReadAllBytes( f1.FullName ).SequenceEqual( File.ReadAllBytes( f2.FullName ) );
public static bool CompareHashes(byte[] f1, byte[] f2)
{
return StructuralComparisons.StructuralEqualityComparer.Equals(f1, f2);
}
public static bool CompareHashes( byte[] f1, byte[] f2 )
=> StructuralComparisons.StructuralEqualityComparer.Equals( f1, f2 );
public byte[] ComputeHash(FileInfo f)
public byte[] ComputeHash( FileInfo f )
{
var stream = File.OpenRead( f.FullName );
var ret = Sha().ComputeHash(stream);
var ret = Sha().ComputeHash( stream );
stream.Dispose();
return ret;
}
// 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);
if (subDir.GetFiles().Length == 0 && subDir.GetDirectories().Length == 0)
ClearEmptySubDirectories( subDir );
if( subDir.GetFiles().Length == 0 && subDir.GetDirectories().Length == 0 )
{
subDir.Delete();
}
}
}
}

View file

@ -5,30 +5,37 @@ namespace Penumbra.Models
{
public enum SelectType
{
Single, Multi
Single,
Multi
}
public struct Option
{
public string OptionName;
public string OptionDesc;
[JsonProperty(ItemConverterType = typeof(SingleOrArrayConverter<string>))]
public Dictionary<string, HashSet<string>> OptionFiles;
[JsonProperty( ItemConverterType = typeof( SingleOrArrayConverter< string > ) )]
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))
return set.Add(gamePath);
else
OptionFiles[filePath] = new(){ gamePath };
if( OptionFiles.TryGetValue( filePath, out var set ) )
{
return set.Add( gamePath );
}
OptionFiles[ filePath ] = new HashSet< string >() { gamePath };
return true;
}
}
public struct InstallerInfo {
public struct InstallerInfo
{
public string GroupName;
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
[JsonConverter( typeof( Newtonsoft.Json.Converters.StringEnumConverter ) )]
public SelectType SelectionType;
public List<Option> Options;
public List< Option > Options;
}
}
}

View file

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

View file

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

View file

@ -16,10 +16,7 @@ namespace Penumbra.Mods
public ResourceMod[] EnabledMods { get; set; }
public ModCollection( DirectoryInfo basePath )
{
_basePath = basePath;
}
public ModCollection( DirectoryInfo basePath ) => _basePath = basePath;
public void Load( bool invertOrder = false )
{
@ -51,7 +48,7 @@ namespace Penumbra.Mods
}
#endif
ModSettings ??= new();
ModSettings ??= new List< ModInfo >();
var foundMods = new List< string >();
foreach( var modDir in _basePath.EnumerateDirectories() )
@ -73,7 +70,7 @@ namespace Penumbra.Mods
var mod = new ResourceMod
{
Meta = meta,
Meta = meta,
ModBasePath = modDir
};
@ -124,7 +121,7 @@ namespace Penumbra.Mods
{
// todo: certified fucked tier
var prio = info.Priority;
var prio = info.Priority;
var swapPrio = up ? prio + 1 : prio - 1;
var swapMeta = ModSettings.FirstOrDefault( x => x.Priority == swapPrio );
@ -133,7 +130,7 @@ namespace Penumbra.Mods
return;
}
info.Priority = swapPrio;
info.Priority = swapPrio;
swapMeta.Priority = prio;
// reorder mods list
@ -160,10 +157,10 @@ namespace Penumbra.Mods
{
var entry = new ModInfo
{
Priority = ModSettings.Count,
Priority = ModSettings.Count,
FolderName = mod.ModBasePath.Name,
Enabled = true,
Mod = mod
Enabled = true,
Mod = mod
};
#if DEBUG

View file

@ -9,18 +9,15 @@ namespace Penumbra.Mods
{
public class ModManager : IDisposable
{
private readonly Plugin _plugin;
public readonly Dictionary< string, FileInfo > ResolvedFiles = new();
public readonly Dictionary< string, string > SwappedFiles = new();
private readonly Plugin _plugin;
public readonly Dictionary< string, FileInfo > ResolvedFiles = new();
public readonly Dictionary< string, string > SwappedFiles = new();
public ModCollection Mods { get; set; }
private DirectoryInfo _basePath;
public ModManager( Plugin plugin )
{
_plugin = plugin;
}
public ModManager( Plugin plugin ) => _plugin = plugin;
public void DiscoverMods()
{
@ -114,7 +111,7 @@ namespace Penumbra.Mods
mod.FileConflicts?.Clear();
if( settings.Conf == null )
{
settings.Conf = new();
settings.Conf = new Dictionary< string, int >();
_plugin.ModManager.Mods.Save();
}
@ -138,7 +135,7 @@ namespace Penumbra.Mods
if( !SwappedFiles.ContainsKey( 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 ) )
{
@ -155,13 +152,14 @@ namespace Penumbra.Mods
{
var relativeFilePath = file.FullName.Substring( baseDir.Length ).TrimStart( '\\' );
bool doNotAdd = false;
var doNotAdd = false;
HashSet< string > paths;
foreach( var group in mod.Meta.Groups.Select( G => G.Value ) )
{
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;
_plugin.ModManager.Mods.Save();
@ -169,10 +167,14 @@ namespace Penumbra.Mods
}
if( group.Options.Count == 0 )
{
continue;
}
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 )
{
@ -183,15 +185,10 @@ namespace Penumbra.Mods
}
else
{
for( var i = 0; i < group.Options.Count; ++i )
if( group.Options.Where( ( o, i ) => i != setting )
.Any( option => option.OptionFiles.ContainsKey( relativeFilePath ) ) )
{
if( i == setting )
continue;
if( group.Options[ i ].OptionFiles.ContainsKey( relativeFilePath ) )
{
doNotAdd = true;
break;
}
doNotAdd = true;
}
}
@ -207,7 +204,9 @@ namespace Penumbra.Mods
}
}
else if( group.Options[ i ].OptionFiles.ContainsKey( relativeFilePath ) )
{
doNotAdd = true;
}
}
break;
@ -216,7 +215,7 @@ namespace Penumbra.Mods
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 ) )
{
ResolvedFiles[ gamePath.ToLowerInvariant() ] = file;
registeredFiles[ gamePath ] = mod.Meta.Name;
registeredFiles[ gamePath ] = mod.Meta.Name;
}
else if( registeredFiles.TryGetValue( gamePath, out var modName ) )
{
@ -247,17 +246,18 @@ namespace Penumbra.Mods
public void DeleteMod( ResourceMod mod )
{
if (mod?.ModBasePath?.Exists ?? false)
if( mod?.ModBasePath?.Exists ?? false )
{
try
{
Directory.Delete(mod.ModBasePath.FullName, true);
Directory.Delete( mod.ModBasePath.FullName, true );
}
catch( Exception e )
{
PluginLog.Error($"Could not delete the mod {mod.ModBasePath.Name}:\n{e}");
PluginLog.Error( $"Could not delete the mod {mod.ModBasePath.Name}:\n{e}" );
}
}
DiscoverMods();
}
@ -278,9 +278,7 @@ namespace Penumbra.Mods
}
public string GetSwappedFilePath( string gameResourcePath )
{
return SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null;
}
=> SwappedFiles.TryGetValue( gameResourcePath, out var swappedPath ) ? swappedPath : null;
public string ResolveSwappedOrReplacementFilePath( string gameResourcePath )
{

View file

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

View file

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

View file

@ -3,9 +3,10 @@ namespace Penumbra.Structs
public enum FileMode : uint
{
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?
LoadIndexResource = 0xA, // load index/index2
LoadIndexResource = 0xA, // load index/index2
LoadSqPackResource = 0xB
}
}

View file

@ -7,124 +7,134 @@ namespace Penumbra.UI
{
public static partial class ImGuiCustom
{
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 ) => BeginFramedGroupInternal( ref label, ZeroVector, 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, Vector2 minSize) => BeginFramedGroupInternal(ref label, minSize, 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 );
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 frameHeight = ImGui.GetFrameHeight();
var halfFrameHeight = new Vector2(ImGui.GetFrameHeight() / 2, 0);
var halfFrameHeight = new Vector2( ImGui.GetFrameHeight() / 2, 0 );
ImGui.BeginGroup(); // First group
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, ZeroVector);
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ZeroVector);
ImGui.PushStyleVar( ImGuiStyleVar.FramePadding, ZeroVector );
ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
ImGui.BeginGroup(); // Second group
var effectiveSize = minSize;
if (effectiveSize.X < 0)
if( effectiveSize.X < 0 )
{
effectiveSize.X = ImGui.GetContentRegionAvail().X;
}
// Ensure width.
ImGui.Dummy(new(effectiveSize.X, 0));
ImGui.Dummy( new Vector2( effectiveSize.X, 0 ) );
// Ensure left half boundary width/distance.
ImGui.Dummy(halfFrameHeight);
ImGui.Dummy( halfFrameHeight );
ImGui.SameLine();
ImGui.BeginGroup(); // Third group.
// Ensure right half of boundary width/distance
ImGui.Dummy(halfFrameHeight);
ImGui.Dummy( halfFrameHeight );
// Label block
ImGui.SameLine();
var ret = false;
if (edit)
ret = ImGuiCustom.ResizingTextInput(ref label, 1024);
if( edit )
{
ret = ResizingTextInput( ref label, 1024 );
}
else
ImGui.TextUnformatted(label);
{
ImGui.TextUnformatted( label );
}
var labelMin = ImGui.GetItemRectMin();
var labelMax = ImGui.GetItemRectMax();
ImGui.SameLine();
// 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.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();
ImGui.PushItemWidth(Math.Max(0f, itemWidth - frameHeight));
ImGui.PushItemWidth( Math.Max( 0f, itemWidth - frameHeight ) );
labelStack.Add((labelMin, labelMax));
labelStack.Add( ( labelMin, labelMax ) );
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.GetWindowDrawList().AddRect(drawMin, drawMax, color, thickness);
ImGui.PushClipRect( clipMin, clipMax, true );
ImGui.GetWindowDrawList().AddRect( drawMin, drawMax, color, thickness );
ImGui.PopClipRect();
}
public static void EndFramedGroup()
{
uint borderColor = ImGui.ColorConvertFloat4ToU32(ImGui.GetStyle().Colors[(int)ImGuiCol.Border]);
Vector2 itemSpacing = ImGui.GetStyle().ItemSpacing;
float frameHeight = ImGui.GetFrameHeight();
Vector2 halfFrameHeight = new(ImGui.GetFrameHeight() / 2, 0);
var borderColor = ImGui.ColorConvertFloat4ToU32( ImGui.GetStyle().Colors[ ( int )ImGuiCol.Border ] );
var itemSpacing = ImGui.GetStyle().ItemSpacing;
var frameHeight = ImGui.GetFrameHeight();
var halfFrameHeight = new Vector2( ImGui.GetFrameHeight() / 2, 0 );
ImGui.PopItemWidth();
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, ZeroVector);
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ZeroVector);
ImGui.PushStyleVar( ImGuiStyleVar.FramePadding, ZeroVector );
ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
ImGui.EndGroup(); // Close fourth group
ImGui.EndGroup(); // Close third group
ImGui.SameLine();
// Ensure right distance.
ImGui.Dummy(halfFrameHeight);
ImGui.Dummy( halfFrameHeight );
// 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
var itemMin = ImGui.GetItemRectMin();
var itemMax = ImGui.GetItemRectMax();
var (currentLabelMin, currentLabelMax) = labelStack[labelStack.Count - 1];
labelStack.RemoveAt(labelStack.Count - 1);
var (currentLabelMin, currentLabelMax) = labelStack[ 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;
currentLabelMax.X += itemSpacing.X;
var frameMin = itemMin + halfFrame;
var frameMax = itemMax - new Vector2(halfFrame.X, 0);
var frameMax = itemMax - new Vector2( halfFrame.X, 0 );
// 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
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
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
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.SetWindowSize(new Vector2(ImGui.GetWindowSize().X + frameHeight, ImGui.GetWindowSize().Y));
ImGui.Dummy(ZeroVector);
ImGui.PopStyleVar( 2 );
ImGui.SetWindowSize( new Vector2( ImGui.GetWindowSize().X + frameHeight, ImGui.GetWindowSize().Y ) );
ImGui.Dummy( ZeroVector );
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 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;
newName = "";
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;
ImGui.SetNextItemWidth(-1);
if (ImGui.InputText($"##{label}_{i}", ref items[i], 64, ImGuiInputTextFlags.EnterReturnsTrue))
{
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;
currentItem = i;
newName = items[ i ];
ret = true;
ImGui.CloseCurrentPopup();
}
if (numItems == 0)
if( isSelected )
{
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;
}
}

View file

@ -5,36 +5,45 @@ namespace Penumbra.UI
{
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)
return ResizingTextInput(label, ref text, maxLength);
if( editable )
{
return ResizingTextInput( label, ref text, maxLength );
}
ImGui.Text(text);
ImGui.Text( text );
return false;
}
public static bool ResizingTextInput(string label, ref string input, uint maxLength) => ResizingTextInputIntern(label, ref input, maxLength).Item1;
public static bool ResizingTextInput(ref string input, uint maxLength)
public static bool ResizingTextInput( string label, 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);
if (ret)
_textInputWidths.Remove(id);
var (ret, id) = ResizingTextInputIntern( $"##{input}", ref input, maxLength );
if( ret )
{
TextInputWidths.Remove( id );
}
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);
if (!_textInputWidths.TryGetValue(id, out var width))
width = ImGui.CalcTextSize(input).X + 10;
var id = ImGui.GetID( label );
if( !TextInputWidths.TryGetValue( id, out var width ) )
{
width = ImGui.CalcTextSize( input ).X + 10;
}
ImGui.SetNextItemWidth(width);
var ret = ImGui.InputText(label, ref input, maxLength, ImGuiInputTextFlags.EnterReturnsTrue);
_textInputWidths[id] = ImGui.CalcTextSize(input).X + 10;
return (ret, id);
ImGui.SetNextItemWidth( width );
var ret = ImGui.InputText( label, ref input, maxLength, ImGuiInputTextFlags.EnterReturnsTrue );
TextInputWidths[ id ] = ImGui.CalcTextSize( input ).X + 10;
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 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.Text(text);
ImGui.SetCursorPosX( pos - ImGui.CalcTextSize( text ).X - 2 * ImGui.GetStyle().ItemSpacing.X );
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.Text(text);
ImGui.SameLine(pos);
ImGui.SetCursorPosX( pos - ImGui.CalcTextSize( text ).X - ImGui.GetStyle().ItemSpacing.X / 2 );
ImGui.Text( text );
ImGui.SameLine( pos );
}
}
}

View file

@ -8,16 +8,17 @@ namespace Penumbra.UI
private class LaunchButton
{
// magic numbers
private const int Padding = 50;
private const int Width = 200;
private const int Height = 45;
private const string MenuButtonsName = "Penumbra Menu Buttons";
private const string MenuButtonLabel = "Manage Mods";
private const int Padding = 50;
private const int Width = 200;
private const int Height = 45;
private const string MenuButtonsName = "Penumbra Menu Buttons";
private const string MenuButtonLabel = "Manage Mods";
private static readonly Vector2 WindowSize = new(Width, Height);
private static readonly Vector2 WindowPosOffset = new(Padding + Width, Padding + Height);
private static readonly Vector2 WindowSize = new( Width, 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.NoDecoration
| ImGuiWindowFlags.NoMove
@ -28,7 +29,7 @@ namespace Penumbra.UI
private readonly SettingsInterface _base;
private readonly Dalamud.Game.ClientState.Condition _condition;
public LaunchButton(SettingsInterface ui)
public LaunchButton( SettingsInterface ui )
{
_base = ui;
_condition = ui._plugin.PluginInterface.ClientState.Condition;
@ -36,20 +37,26 @@ namespace Penumbra.UI
public void Draw()
{
if( !_condition.Any() && !_base._menu.Visible )
if( _condition.Any() || _base._menu.Visible )
{
var ss = ImGui.GetIO().DisplaySize;
ImGui.SetNextWindowPos( ss - WindowPosOffset, ImGuiCond.Always );
if( ImGui.Begin(MenuButtonsName, ButtonFlags) )
{
if( ImGui.Button( MenuButtonLabel, WindowSize ) )
_base.FlipVisibility();
ImGui.End();
}
return;
}
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
private readonly SettingsInterface _base;
public MenuBar(SettingsInterface ui) => _base = ui;
public MenuBar( SettingsInterface ui ) => _base = ui;
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();
if( ImGui.MenuItem( MenuItemRediscover ) )
_base.ReloadMods();
#if DEBUG
if ( ImGui.MenuItem( MenuItemHide) )
_showDebugBar = false;
#endif
ImGui.EndMenu();
_base.FlipVisibility();
}
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 static readonly Vector2 AutoFillSize = new(-1, -1);
private static readonly Vector2 ZeroVector = new( 0, 0);
private static readonly Vector2 AutoFillSize = new( -1, -1 );
private static readonly Vector2 ZeroVector = new( 0, 0 );
private readonly Plugin _plugin;
@ -19,9 +19,9 @@ namespace Penumbra.UI
public SettingsInterface( Plugin plugin )
{
_plugin = plugin;
_launchButton = new(this);
_menuBar = new(this);
_menu = new(this);
_launchButton = new LaunchButton( this );
_menuBar = new MenuBar( this );
_menu = new SettingsMenu( this );
}
public void FlipVisibility() => _menu.Visible = !_menu.Visible;
@ -35,14 +35,14 @@ namespace Penumbra.UI
private void ReloadMods()
{
_menu._installedTab._selector.ResetModNamesLower();
_menu._installedTab._selector.ClearSelection();
_menu.InstalledTab.Selector.ResetModNamesLower();
_menu.InstalledTab.Selector.ClearSelection();
// create the directory if it doesn't exist
Directory.CreateDirectory( _plugin.Configuration.CurrentCollection );
_plugin.ModManager.DiscoverMods( _plugin.Configuration.CurrentCollection );
_menu._effectiveTab.RebuildFileList(_plugin.Configuration.ShowAdvanced);
_menu._installedTab._selector.ResetModNamesLower();
_menu.EffectiveTab.RebuildFileList( _plugin.Configuration.ShowAdvanced );
_menu.InstalledTab.Selector.ResetModNamesLower();
}
}
}

View file

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

View file

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

View file

@ -11,22 +11,22 @@ namespace Penumbra.UI
private const string LabelTab = "Effective File List";
private const float TextSizePadding = 5f;
private readonly ModManager _mods;
private (string, string)[] _fileList = null;
private float _maxGamePath = 0f;
private readonly ModManager _mods;
private (string, string)[] _fileList;
private float _maxGamePath;
public TabEffective(SettingsInterface ui)
public TabEffective( SettingsInterface ui )
{
_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();
_maxGamePath = ((_fileList.Length > 0) ? _fileList.Max( P => ImGui.CalcTextSize(P.Item2).X ) : 0f) + TextSizePadding;
_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;
}
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.SetCursorPosX(_maxGamePath);
ImGui.TextUnformatted(" <-- ");
ImGui.SetCursorPosX( _maxGamePath );
ImGui.TextUnformatted( " <-- " );
ImGui.SameLine();
ImGui.Selectable(file.Item1);
ImGui.Selectable( file.Item1 );
}
public void Draw()
{
var ret = ImGui.BeginTabItem( LabelTab );
if( !ret )
{
return;
}
if( ImGui.ListBoxHeader( "##effective_files", AutoFillSize ) )
{
foreach( var file in _fileList )
DrawFileLine(file);
{
DrawFileLine( file );
}
ImGui.ListBoxFooter();
}

View file

@ -15,22 +15,22 @@ namespace Penumbra.UI
{
private const string LabelTab = "Import Mods";
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 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 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 bool _isImportRunning = false;
private bool _hasError = false;
private TexToolsImport _texToolsImport = null!;
private bool _isImportRunning = false;
private bool _hasError = false;
private TexToolsImport _texToolsImport = null!;
private readonly SettingsInterface _base;
public TabImport(SettingsInterface ui) => _base = ui;
public TabImport( SettingsInterface ui ) => _base = ui;
public bool IsImporting() => _isImportRunning;
@ -41,10 +41,10 @@ namespace Penumbra.UI
{
var picker = new OpenFileDialog
{
Multiselect = true,
Filter = FileTypeFilter,
Multiselect = true,
Filter = FileTypeFilter,
CheckFileExists = true,
Title = LabelFileDialog
Title = LabelFileDialog
};
var result = await picker.ShowDialogAsync();
@ -55,7 +55,7 @@ namespace Penumbra.UI
foreach( var fileName in picker.FileNames )
{
PluginLog.Log( $"-> {fileName} START");
PluginLog.Log( $"-> {fileName} START" );
try
{
@ -74,6 +74,7 @@ namespace Penumbra.UI
_texToolsImport = null;
_base.ReloadMods();
}
_isImportRunning = false;
} );
}
@ -90,32 +91,34 @@ namespace Penumbra.UI
{
ImGui.Button( LabelFileImportRunning );
if( _texToolsImport != null )
if( _texToolsImport == null )
{
switch( _texToolsImport.State )
{
case ImporterState.None:
break;
case ImporterState.WritingPackToDisk:
ImGui.Text( TooltipModpack1 );
break;
case ImporterState.ExtractingModFiles:
{
var str =
$"{_texToolsImport.CurrentModPack} - {_texToolsImport.CurrentProgress} of {_texToolsImport.TotalProgress} files";
return;
}
ImGui.ProgressBar( _texToolsImport.Progress, ImportBarSize, str );
break;
}
case ImporterState.Done:
break;
default:
throw new ArgumentOutOfRangeException();
switch( _texToolsImport.State )
{
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 );
break;
}
case ImporterState.Done:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void DrawFailedImportMessage()
private static void DrawFailedImportMessage()
{
ImGui.PushStyleColor( ImGuiCol.Text, ColorRed );
ImGui.Text( FailedImport );
@ -126,15 +129,23 @@ namespace Penumbra.UI
{
var ret = ImGui.BeginTabItem( LabelTab );
if( !ret )
{
return;
}
if( !_isImportRunning )
{
DrawImportButton();
}
else
{
DrawImportProgress();
}
if (_hasError)
if( _hasError )
{
DrawFailedImportMessage();
}
ImGui.EndTabItem();
}

View file

@ -4,25 +4,25 @@ namespace Penumbra.UI
{
public partial class SettingsInterface
{
private partial class TabInstalled
private class TabInstalled
{
private const string LabelTab = "Installed Mods";
private readonly SettingsInterface _base;
public readonly Selector _selector;
public readonly ModPanel _modPanel;
public readonly Selector Selector;
public readonly ModPanel ModPanel;
public TabInstalled(SettingsInterface ui)
public TabInstalled( SettingsInterface ui )
{
_base = ui;
_selector = new(_base);
_modPanel = new(_base, _selector);
_base = ui;
Selector = new Selector( _base );
ModPanel = new ModPanel( _base, Selector );
}
private void DrawNoModsAvailable()
private static void DrawNoModsAvailable()
{
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( "For example: D:/ffxiv/mods/" );
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 );
if( !ret )
return;
if (_base._plugin.ModManager.Mods != null)
{
_selector.Draw();
return;
}
if( _base._plugin.ModManager.Mods != null )
{
Selector.Draw();
ImGui.SameLine();
_modPanel.Draw();
ModPanel.Draw();
}
else
{
DrawNoModsAvailable();
}
ImGui.EndTabItem();
return;
}
}
}

View file

@ -8,12 +8,16 @@ namespace Penumbra.UI
internal static class Extension
{
// 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)
list.RemoveAt(idx);
if( newString?.Length == 0 )
{
list.RemoveAt( idx );
}
else
list[idx] = newString;
{
list[ idx ] = newString;
}
}
}
@ -33,117 +37,147 @@ namespace Penumbra.UI
private const string LabelFileSwapHeader = "##fileSwaps";
private const string LabelFileListTab = "Files";
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 LabelOptionSelect = "##optionSelect";
private const string LabelConfigurationTab = "Configuration";
private const float TextSizePadding = 5f;
private const float OptionSelectionWidth = 140f;
private const float CheckMarkSize = 50f;
private const uint ColorGreen = 0xFF00C800;
private const uint ColorYellow = 0xFF00C8C8;
private const uint ColorRed = 0xFF0000C8;
private const string TooltipFilesTab =
"Green files replace their standard game path counterpart (not in any option) or are in all options of a Single-Select option.\n" +
"Yellow files are restricted to some options.";
private 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 const float TextSizePadding = 5f;
private const float OptionSelectionWidth = 140f;
private const float CheckMarkSize = 50f;
private const uint ColorGreen = 0xFF00C800;
private const uint ColorYellow = 0xFF00C8C8;
private const uint ColorRed = 0xFF0000C8;
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;
public void SelectGroup(int idx)
private readonly Selector _selector;
private readonly SettingsInterface _base;
private void SelectGroup( int idx )
{
_selectedGroupIndex = idx;
if (_selectedGroupIndex >= Meta?.Groups?.Count)
if( _selectedGroupIndex >= Meta?.Groups?.Count )
{
_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;
if (_selectedOptionIndex >= _selectedGroup?.Options.Count)
if( _selectedOptionIndex >= _selectedGroup?.Options.Count )
{
_selectedOptionIndex = 0;
if (_selectedGroup?.Options.Count > 0)
_selectedOption = ((InstallerInfo) _selectedGroup).Options[_selectedOptionIndex];
}
if( _selectedGroup?.Options.Count > 0 )
{
_selectedOption = ( ( InstallerInfo )_selectedGroup ).Options[ _selectedOptionIndex ];
}
else
{
_selectedOption = null;
}
}
public void SelectOption() => SelectOption(_selectedOptionIndex);
private void SelectOption() => SelectOption( _selectedOptionIndex );
public void ResetState()
{
_changedItemsList = null;
_fileSwapOffset = null;
_fullFilenameList = null;
_changedItemsList = null;
_fileSwapOffset = null;
_fullFilenameList = null;
SelectGroup();
SelectOption();
}
private readonly Selector _selector;
private readonly SettingsInterface _base;
public PluginDetails(SettingsInterface ui, Selector s)
public PluginDetails( SettingsInterface ui, Selector s )
{
_base = ui;
_selector = s;
ResetState();
}
private ModInfo Mod { get{ return _selector.Mod(); } }
private ModMeta Meta { get{ return Mod?.Mod?.Meta; } }
private ModInfo Mod => _selector.Mod();
private ModMeta Meta => Mod?.Mod?.Meta;
private void Save()
{
_base._plugin.ModManager.Mods.Save();
_base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced);
_base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
}
private void DrawAboutTab()
{
if (!_editMode && Meta.Description?.Length == 0)
return;
if(ImGui.BeginTabItem( LabelAboutTab ) )
if( !_editMode && Meta.Description?.Length == 0 )
{
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();
return;
}
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()
{
if (!_editMode && Meta.ChangedItems?.Count == 0)
if( !_editMode && ( Meta.ChangedItems?.Count ?? 0 ) == 0 )
{
return;
}
Meta.ChangedItems ??= new List< string >();
var flags = _editMode
? ImGuiInputTextFlags.EnterReturnsTrue
@ -154,122 +188,164 @@ namespace Penumbra.UI
ImGui.SetNextItemWidth( -1 );
if( ImGui.ListBoxHeader( LabelChangedItemsHeader, AutoFillSize ) )
{
if (_changedItemsList == null)
_changedItemsList = Meta.ChangedItems.Select( (I, index) => ($"{LabelChangedItemIdx}{index}", I) ).ToArray();
for (var i = 0; i < Meta.ChangedItems.Count; ++i)
_changedItemsList ??= Meta.ChangedItems.Select( ( I, index ) => ( $"{LabelChangedItemIdx}{index}", I ) ).ToArray();
for( var i = 0; i < Meta.ChangedItems.Count; ++i )
{
ImGui.SetNextItemWidth(-1);
if ( ImGui.InputText(_changedItemsList[i].label, ref _changedItemsList[i].name, 128, flags) )
ImGui.SetNextItemWidth( -1 );
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();
}
}
var newItem = "";
if ( _editMode )
if( _editMode )
{
ImGui.SetNextItemWidth(-1);
if ( ImGui.InputText( LabelChangedItemNew, ref newItem, 128, flags) )
ImGui.SetNextItemWidth( -1 );
if( ImGui.InputText( LabelChangedItemNew, ref newItem, 128, flags ) )
{
if (newItem.Length > 0)
if( newItem.Length > 0 )
{
if (Meta.ChangedItems == null)
Meta.ChangedItems = new(){ newItem };
if( Meta.ChangedItems == null )
{
Meta.ChangedItems = new List< string >() { newItem };
}
else
Meta.ChangedItems.Add(newItem);
{
Meta.ChangedItems.Add( newItem );
}
_selector.SaveCurrentMod();
}
}
}
ImGui.ListBoxFooter();
}
ImGui.EndTabItem();
}
else
{
_changedItemsList = null;
}
}
private void DrawConflictTab()
{
if( Mod.Mod.FileConflicts.Any() )
if( !Mod.Mod.FileConflicts.Any() )
{
if( ImGui.BeginTabItem( LabelConflictsTab ) )
{
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 );
return;
}
ImGui.Indent( 15 );
foreach( var file in kv.Value )
ImGui.Selectable( file );
ImGui.Unindent( 15 );
}
ImGui.ListBoxFooter();
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.EndTabItem();
ImGui.Indent( 15 );
foreach( var file in kv.Value )
{
ImGui.Selectable( file );
}
ImGui.Unindent( 15 );
}
ImGui.ListBoxFooter();
}
ImGui.EndTabItem();
}
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)
_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 )
{
foreach( var file in Meta.FileSwaps )
{
ImGui.Selectable(file.Key);
ImGui.SameLine(_fileSwapOffset ?? 0);
ImGui.TextUnformatted(" -> ");
ImGui.SameLine();
ImGui.Selectable(file.Value);
}
ImGui.ListBoxFooter();
ImGui.Selectable( file.Key );
ImGui.SameLine( _fileSwapOffset ?? 0 );
ImGui.TextUnformatted( " -> " );
ImGui.SameLine();
ImGui.Selectable( file.Value );
}
ImGui.EndTabItem();
ImGui.ListBoxFooter();
}
else
_fileSwapOffset = null;
ImGui.EndTabItem();
}
else
{
_fileSwapOffset = null;
}
}
private void UpdateFilenameList()
{
if (_fullFilenameList == null)
if( _fullFilenameList != null )
{
var len = Mod.Mod.ModBasePath.FullName.Length;
_fullFilenameList = Mod.Mod.ModFiles.Select( F => (F.FullName, false, ColorGreen, "") ).ToArray();
return;
}
if(Meta.Groups?.Count == 0)
return;
var len = Mod.Mod.ModBasePath.FullName.Length;
_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('\\');
foreach (var Group in Meta.Groups.Values)
continue;
}
foreach( var group in Meta.Groups.Values )
{
var inAll = true;
foreach( var option in group.Options )
{
var inAll = true;
foreach (var Option in Group.Options)
if( option.OptionFiles.ContainsKey( _fullFilenameList[ i ].relName ) )
{
if (Option.OptionFiles.ContainsKey(_fullFilenameList[i].relName))
_fullFilenameList[i].color = ColorYellow;
else
inAll = false;
_fullFilenameList[ i ].color = ColorYellow;
}
if (inAll && Group.SelectionType == SelectType.Single)
_fullFilenameList[i].color = ColorGreen;
else
{
inAll = false;
}
}
if( inAll && group.SelectionType == SelectType.Single )
{
_fullFilenameList[ i ].color = ColorGreen;
}
}
}
@ -277,111 +353,142 @@ namespace Penumbra.UI
private void DrawFileListTab()
{
if( ImGui.BeginTabItem( LabelFileListTab ) )
if( !ImGui.BeginTabItem( LabelFileListTab ) )
{
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();
return;
}
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)
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( _selectedOption == null )
{
if (gamePaths[i] == TextDefaultGamePath )
{
defaultIndex = i;
break;
}
return;
}
var baseLength = Mod.Mod.ModBasePath.FullName.Length;
var changed = false;
for (var i = 0; i < Mod.Mod.ModFiles.Count; ++i)
var option = ( Option )_selectedOption;
var gamePaths = _currentGamePaths.Split( ';' );
if( gamePaths.Length == 0 || gamePaths[ 0 ].Length == 0 )
{
if (!_fullFilenameList[i].selected)
continue;
return;
}
var fileName = _fullFilenameList[i].relName;
if (defaultIndex != null)
gamePaths[(int)defaultIndex] = fileName.Replace('\\', '/');
if (remove && option.OptionFiles.TryGetValue(fileName, out var setPaths))
var defaultIndex = gamePaths.IndexOf( p => p == TextDefaultGamePath );
var changed = false;
for( var i = 0; i < Mod.Mod.ModFiles.Count; ++i )
{
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;
if (setPaths.Count == 0 && option.OptionFiles.Remove(fileName))
}
if( setPaths.Count == 0 && option.OptionFiles.Remove( fileName ) )
{
changed = true;
}
}
else
{
foreach(var gamePath in gamePaths)
changed |= option.AddFile(fileName, gamePath);
changed = gamePaths.Aggregate( changed, ( current, gamePath ) => current | option.AddFile( fileName, gamePath ) );
}
}
if (changed)
if( changed )
{
_selector.SaveCurrentMod();
}
}
private void DrawAddToGroupButton()
{
if (ImGui.Button( ButtonAddToGroup ) )
HandleSelectedFilesButton(false);
if( ImGui.Button( ButtonAddToGroup ) )
{
HandleSelectedFilesButton( false );
}
}
private void DrawRemoveFromGroupButton()
{
if (ImGui.Button( ButtonRemoveFromGroup ) )
HandleSelectedFilesButton(true);
if( ImGui.Button( ButtonRemoveFromGroup ) )
{
HandleSelectedFilesButton( true );
}
}
private void DrawGamePathInput()
{
ImGui.TextUnformatted( LabelGamePathsEdit );
ImGui.SameLine();
ImGui.SetNextItemWidth(-1);
ImGui.InputText(LabelGamePathsEditBox, ref _currentGamePaths, 128);
if (ImGui.IsItemHovered())
ImGui.SetTooltip(TooltipGamePathsEdit);
ImGui.SetNextItemWidth( -1 );
ImGui.InputText( LabelGamePathsEditBox, ref _currentGamePaths, 128 );
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipGamePathsEdit );
}
}
private void DrawGroupRow()
{
if (_selectedGroup == null)
if( _selectedGroup == null )
{
SelectGroup();
if (_selectedOption == null)
SelectOption();
}
if (!DrawEditGroupSelector())
if( _selectedOption == null )
{
SelectOption();
}
if( !DrawEditGroupSelector() )
{
return;
}
ImGui.SameLine();
if (!DrawEditOptionSelector())
if( !DrawEditOptionSelector() )
{
return;
}
ImGui.SameLine();
DrawAddToGroupButton();
ImGui.SameLine();
@ -390,116 +497,142 @@ namespace Penumbra.UI
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;
if (loc == colorNormal)
var loc = _fullFilenameList[ idx ].color;
if( loc == colorNormal )
{
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();
}
const float indent = 30f;
if (_selectedOption == null)
if( _selectedOption == null )
{
Selectable(0, ColorGreen);
Selectable( 0, ColorGreen );
return;
}
var fileName = _fullFilenameList[idx].relName;
if (((Option) _selectedOption).OptionFiles.TryGetValue(fileName, out var gamePaths))
var fileName = _fullFilenameList[ idx ].relName;
if( ( ( Option )_selectedOption ).OptionFiles.TryGetValue( fileName, out var gamePaths ) )
{
Selectable(0, ColorGreen);
Selectable( 0, ColorGreen );
ImGui.Indent(indent);
foreach (var gamePath in gamePaths)
ImGui.Indent( indent );
foreach( var gamePath in gamePaths )
{
string tmp = gamePath;
if (ImGui.InputText($"##{fileName}_{gamePath}", ref tmp, 128, ImGuiInputTextFlags.EnterReturnsTrue))
var tmp = gamePath;
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);
if (tmp.Length > 0)
gamePaths.Add(tmp);
_selector.SaveCurrentMod();
_selector.ReloadCurrentMod();
gamePaths.Add( tmp );
}
_selector.SaveCurrentMod();
_selector.ReloadCurrentMod();
}
}
ImGui.Unindent(indent);
ImGui.Unindent( indent );
}
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)
{
Mod.Conf[group.GroupName] ^= (1 << idx);
Save();
}
Selectable( ColorYellow, ColorRed );
}
}
private void DrawMultiSelector(InstallerInfo group)
private void DrawMultiSelectorCheckBox( InstallerInfo group, int idx, int flag, string label )
{
if (group.Options.Count == 0)
return;
var opt = group.Options[ idx ];
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);
for(var i = 0; i < group.Options.Count; ++i)
DrawMultiSelectorCheckBox(group, i, Mod.Conf[group.GroupName], $"{group.Options[i].OptionName}##{group.GroupName}");
private void DrawMultiSelector( InstallerInfo group )
{
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();
}
private void DrawSingleSelector(InstallerInfo group)
private void DrawSingleSelector( InstallerInfo group )
{
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 ) )
if( group.Options.Count < 2 )
{
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();
}
}
private void DrawGroupSelectors()
{
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 ))
DrawMultiSelector(g);
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 ) )
{
DrawMultiSelector( g );
}
return;
}
private void DrawConfigurationTab()
{
if (!_editMode && !Meta.HasGroupWithConfig)
return;
if(ImGui.BeginTabItem( LabelConfigurationTab ) )
if( !_editMode && !Meta.HasGroupWithConfig )
{
if (_editMode)
return;
}
if( ImGui.BeginTabItem( LabelConfigurationTab ) )
{
if( _editMode )
{
DrawGroupSelectorsEdit();
}
else
{
DrawGroupSelectors();
}
ImGui.EndTabItem();
}
}
public void Draw(bool editMode)
public void Draw( bool editMode )
{
_editMode = editMode;
ImGui.BeginTabBar( LabelPluginDetails );
@ -507,10 +640,15 @@ namespace Penumbra.UI
DrawAboutTab();
DrawChangedItemsTab();
DrawConfigurationTab();
if (_editMode)
if( _editMode )
{
DrawFileListTabEdit();
}
else
{
DrawFileListTab();
}
DrawFileSwapTab();
DrawConflictTab();
@ -518,4 +656,4 @@ namespace Penumbra.UI
}
}
}
}
}

View file

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

View file

@ -16,45 +16,48 @@ namespace Penumbra.UI
private const string LabelEditVersion = "##editVersion";
private const string LabelEditAuthor = "##editAuthor";
private const string LabelEditWebsite = "##editWebsite";
private const string ButtonOpenWebsite = "Open Website";
private const string LabelModEnabled = "Enabled";
private const string LabelEditingEnabled = "Enable Editing";
private const string ButtonOpenWebsite = "Open Website";
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 TooltipEditJson = "Open the JSON configuration file in your default application for .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 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 static readonly Vector4 GreyColor = new( 1f, 1f, 1f, 0.66f );
private const string TooltipDeduplicate =
"Try to find identical files and remove duplicate occurences to reduce the mods disk size.\n" +
"Introduces an invisible single-option Group \"Duplicates\".";
private const float HeaderLineDistance = 10f;
private static readonly Vector4 GreyColor = new( 1f, 1f, 1f, 0.66f );
private readonly SettingsInterface _base;
private readonly Selector _selector;
public readonly PluginDetails _details;
public readonly PluginDetails Details;
private bool _editMode = false;
private bool _editMode;
private string _currentWebsite;
private bool _validWebsite;
public ModPanel(SettingsInterface ui, Selector s)
public ModPanel( SettingsInterface ui, Selector s )
{
_base = ui;
_selector = s;
_details = new(_base, _selector);
Details = new PluginDetails( _base, _selector );
_currentWebsite = Meta?.Website;
}
private ModInfo Mod { get{ return _selector.Mod(); } }
private ModMeta Meta { get{ return Mod?.Mod.Meta; } }
private ModInfo Mod => _selector.Mod();
private ModMeta Meta => Mod?.Mod.Meta;
private void DrawName()
{
var name = Meta.Name;
if (ImGuiCustom.InputOrText(_editMode, LabelEditName, ref name, 64)
&& name.Length > 0 && name != Meta.Name)
if( ImGuiCustom.InputOrText( _editMode, LabelEditName, ref name, 64 )
&& name.Length > 0 && name != Meta.Name )
{
Meta.Name = name;
_selector.SaveCurrentMod();
@ -63,27 +66,27 @@ namespace Penumbra.UI
private void DrawVersion()
{
if (_editMode)
if( _editMode )
{
ImGui.BeginGroup();
ImGui.Text("(Version ");
ImGui.Text( "(Version " );
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ZeroVector);
ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
ImGui.SameLine();
var version = Meta.Version ?? "";
if (ImGuiCustom.ResizingTextInput( LabelEditVersion, ref version, 16)
&& version != Meta.Version)
if( ImGuiCustom.ResizingTextInput( LabelEditVersion, ref version, 16 )
&& version != Meta.Version )
{
Meta.Version = version.Length > 0 ? version : null;
_selector.SaveCurrentMod();
}
ImGui.SameLine();
ImGui.Text(")");
ImGui.Text( ")" );
ImGui.PopStyleVar();
ImGui.EndGroup();
}
else if ((Meta.Version?.Length ?? 0) > 0)
else if( ( Meta.Version?.Length ?? 0 ) > 0 )
{
ImGui.Text( $"(Version {Meta.Version})" );
}
@ -96,58 +99,62 @@ namespace Penumbra.UI
ImGui.SameLine();
var author = Meta.Author ?? "";
if (ImGuiCustom.InputOrText(_editMode, LabelEditAuthor, ref author, 64)
&& author != Meta.Author)
if( ImGuiCustom.InputOrText( _editMode, LabelEditAuthor, ref author, 64 )
&& author != Meta.Author )
{
Meta.Author = author.Length > 0 ? author : null;
_selector.SaveCurrentMod();
}
ImGui.EndGroup();
}
private void DrawWebsite()
{
ImGui.BeginGroup();
if (_editMode)
if( _editMode )
{
ImGui.TextColored( GreyColor, "from" );
ImGui.SameLine();
var website = Meta.Website ?? "";
if (ImGuiCustom.ResizingTextInput(LabelEditWebsite, ref website, 512)
&& website != Meta.Website)
if( ImGuiCustom.ResizingTextInput( LabelEditWebsite, ref website, 512 )
&& website != Meta.Website )
{
Meta.Website = website.Length > 0 ? website : null;
_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;
_validWebsite = Uri.TryCreate( Meta.Website, UriKind.Absolute, out var uriResult )
&& ( uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp );
}
if( _validWebsite )
{
if( ImGui.SmallButton( ButtonOpenWebsite ) )
{
try
{
var process = new ProcessStartInfo(Meta.Website)
var process = new ProcessStartInfo( Meta.Website )
{
UseShellExecute = true
};
Process.Start(process);
Process.Start( process );
}
catch(System.ComponentModel.Win32Exception)
catch( System.ComponentModel.Win32Exception )
{
// Do nothing.
}
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( Meta.Website );
}
}
else
{
@ -156,6 +163,7 @@ namespace Penumbra.UI
ImGui.Text( Meta.Website );
}
}
ImGui.EndGroup();
}
@ -178,13 +186,13 @@ namespace Penumbra.UI
Mod.Enabled = enabled;
_base._plugin.ModManager.Mods.Save();
_base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced);
_base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
}
}
private void DrawEditableMark()
{
ImGui.Checkbox( LabelEditingEnabled, ref _editMode);
ImGui.Checkbox( LabelEditingEnabled, ref _editMode );
}
private void DrawOpenModFolderButton()
@ -193,8 +201,11 @@ namespace Penumbra.UI
{
Process.Start( Mod.Mod.ModBasePath.FullName );
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipOpenModFolder );
}
}
private void DrawEditJsonButton()
@ -203,8 +214,11 @@ namespace Penumbra.UI
{
Process.Start( _selector.SaveCurrentMod() );
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipEditJson );
}
}
private void DrawReloadJsonButton()
@ -213,22 +227,28 @@ namespace Penumbra.UI
{
_selector.ReloadCurrentMod();
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipReloadJson );
}
}
private void DrawDeduplicateButton()
{
if( ImGui.Button( ButtonDeduplicate ) )
{
new Deduplicator(Mod.Mod.ModBasePath, Meta).Run();
new Deduplicator( Mod.Mod.ModBasePath, Meta ).Run();
_selector.SaveCurrentMod();
Mod.Mod.RefreshModFiles();
_base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced);
_base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipDeduplicate );
}
}
private void DrawEditLine()
@ -244,38 +264,44 @@ namespace Penumbra.UI
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 );
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();
return;
}
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 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 ArrowDownString = FontAwesomeIcon.ArrowDown.ToIconString();
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 int _index = 0;
private int? _deleteIndex = null;
private string _modFilter = "";
private string[] _modNamesLower = null;
private ModInfo _mod;
private int _index;
private int? _deleteIndex;
private string _modFilter = "";
private string[] _modNamesLower;
public Selector(SettingsInterface ui)
public Selector( SettingsInterface ui )
{
_base = ui;
ResetModNamesLower();
@ -52,16 +52,16 @@ namespace Penumbra.UI
_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 );
if( _index != unavailableWhen )
{
if( ImGui.Button( iconString, SelectorButtonSizes ) )
{
SetSelection(_index);
SetSelection( _index );
_base._plugin.ModManager.ChangeModPriority( _mod, up );
_modNamesLower.Swap(_index, _index + (up ? 1 : -1));
_modNamesLower.Swap( _index, _index + ( up ? 1 : -1 ) );
_index += up ? 1 : -1;
}
}
@ -94,10 +94,12 @@ namespace Penumbra.UI
ImGui.PopFont();
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipDelete );
}
}
private void DrawModAddButton()
private static void DrawModAddButton()
{
ImGui.PushFont( UiBuilder.IconFont );
@ -109,19 +111,24 @@ namespace Penumbra.UI
ImGui.PopFont();
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipAdd );
}
}
private void DrawModsSelectorFilter()
{
ImGui.SetNextItemWidth( SelectorButtonSizes.X * 4 );
string tmp = _modFilter;
if (ImGui.InputText(LabelModFilter, ref tmp, 256))
var tmp = _modFilter;
if( ImGui.InputText( LabelModFilter, ref tmp, 256 ) )
{
_modFilter = tmp.ToLowerInvariant();
}
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( TooltipModFilter );
}
}
private void DrawModsSelectorButtons()
@ -130,9 +137,9 @@ namespace Penumbra.UI
ImGui.PushStyleVar( ImGuiStyleVar.WindowPadding, ZeroVector );
ImGui.PushStyleVar( ImGuiStyleVar.FrameRounding, 0 );
DrawPriorityChangeButton(ArrowUpString, false, 0);
DrawPriorityChangeButton( ArrowUpString, false, 0 );
ImGui.SameLine();
DrawPriorityChangeButton(ArrowDownString, true, Mods?.ModSettings.Count - 1 ?? 0);
DrawPriorityChangeButton( ArrowDownString, true, Mods?.ModSettings.Count - 1 ?? 0 );
ImGui.SameLine();
DrawModTrashButton();
ImGui.SameLine();
@ -141,16 +148,20 @@ namespace Penumbra.UI
ImGui.PopStyleVar( 3 );
}
void DrawDeleteModal()
private void DrawDeleteModal()
{
if( _deleteIndex == null )
{
return;
}
ImGui.OpenPopup( DialogDeleteMod );
var ret = ImGui.BeginPopupModal( DialogDeleteMod );
if( !ret )
{
return;
}
if( _mod?.Mod == null )
{
@ -185,8 +196,10 @@ namespace Penumbra.UI
public void Draw()
{
if (Mods == null)
if( Mods == null )
{
return;
}
// Selector pane
ImGui.BeginGroup();
@ -195,14 +208,16 @@ namespace Penumbra.UI
DrawModsSelectorFilter();
// 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++ )
{
var settings = Mods.ModSettings[ modIndex ];
var modName = settings.Mod.Meta.Name;
if (_modFilter.Length > 0 && !_modNamesLower[modIndex].Contains(_modFilter) )
var modName = settings.Mod.Meta.Name;
if( _modFilter.Length > 0 && !_modNamesLower[ modIndex ].Contains( _modFilter ) )
{
continue;
}
var changedColour = false;
if( !settings.Enabled )
@ -226,10 +241,14 @@ namespace Penumbra.UI
#endif
if( changedColour )
{
ImGui.PopStyleColor();
}
if( selected )
SetSelection(modIndex, settings);
{
SetSelection( modIndex, settings );
}
}
ImGui.EndChild();
@ -242,26 +261,36 @@ namespace Penumbra.UI
public ModInfo Mod() => _mod;
private void SetSelection(int idx, ModInfo info)
private void SetSelection( int idx, ModInfo info )
{
_mod = info;
if (idx != _index)
_base._menu._installedTab._modPanel._details.ResetState();
_mod = info;
if( idx != _index )
{
_base._menu.InstalledTab.ModPanel.Details.ResetState();
}
_index = idx;
_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;
if (idx < 0)
SetSelection(0, null);
}
if( idx < 0 )
{
SetSelection( 0, null );
}
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 )
{
@ -270,40 +299,42 @@ namespace Penumbra.UI
var mod = Mods.ModSettings[ modIndex ];
if( mod.Mod.Meta.Name != name )
{
continue;
}
SetSelection(modIndex, mod);
SetSelection( modIndex, mod );
return;
}
}
private string GetCurrentModMetaFile()
{
if( _mod == null )
return "";
return Path.Combine( _mod.Mod.ModBasePath.FullName, "meta.json" );
}
=> _mod == null ? "" : Path.Combine( _mod.Mod.ModBasePath.FullName, "meta.json" );
public void ReloadCurrentMod()
{
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;
_base._menu._installedTab._modPanel._details.ResetState();
_mod.Mod.Meta = ModMeta.LoadFromFile( metaPath ) ?? _mod.Mod.Meta;
_base._menu.InstalledTab.ModPanel.Details.ResetState();
}
_mod.Mod.RefreshModFiles();
_base._plugin.ModManager.CalculateEffectiveFileList();
_base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced);
_base._menu.EffectiveTab.RebuildFileList( _base._plugin.Configuration.ShowAdvanced );
ResetModNamesLower();
}
public string SaveCurrentMod()
{
var metaPath = GetCurrentModMetaFile();
if (metaPath.Length > 0)
if( metaPath.Length > 0 )
{
File.WriteAllText( metaPath, JsonConvert.SerializeObject( _mod.Mod.Meta, Formatting.Indented ) );
_base._menu._installedTab._modPanel._details.ResetState();
}
_base._menu.InstalledTab.ModPanel.Details.ResetState();
return metaPath;
}
}

View file

@ -21,9 +21,9 @@ namespace Penumbra.UI
private readonly SettingsInterface _base;
private readonly Configuration _config;
private bool _configChanged;
private bool _configChanged;
public TabSettings(SettingsInterface ui)
public TabSettings( SettingsInterface ui )
{
_base = ui;
_config = _base._plugin.Configuration;
@ -36,7 +36,7 @@ namespace Penumbra.UI
if( ImGui.InputText( LabelRootFolder, ref basePath, 255 ) && _config.CurrentCollection != basePath )
{
_config.CurrentCollection = basePath;
_configChanged = true;
_configChanged = true;
}
}
@ -45,7 +45,7 @@ namespace Penumbra.UI
if( ImGui.Button( LabelRediscoverButton ) )
{
_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 ) )
{
_config.IsEnabled = enabled;
_configChanged = true;
RefreshActors.RedrawAll(_base._plugin.PluginInterface.ClientState.Actors);
_configChanged = true;
Game.RefreshActors.RedrawAll( _base._plugin.PluginInterface.ClientState.Actors );
}
}
@ -85,15 +85,17 @@ namespace Penumbra.UI
if( ImGui.Checkbox( LabelShowAdvanced, ref showAdvanced ) )
{
_config.ShowAdvanced = showAdvanced;
_configChanged = true;
_base._menu._effectiveTab.RebuildFileList(showAdvanced);
_configChanged = true;
_base._menu.EffectiveTab.RebuildFileList( showAdvanced );
}
}
private void DrawLogLoadedFilesBox()
{
if( _base._plugin.ResourceLoader != null )
{
ImGui.Checkbox( LabelLogLoadedFiles, ref _base._plugin.ResourceLoader.LogAllFiles );
}
}
private void DrawDisableNotificationsBox()
@ -102,7 +104,7 @@ namespace Penumbra.UI
if( ImGui.Checkbox( LabelDisableNotifications, ref fswatch ) )
{
_config.DisableFileSystemNotifications = fswatch;
_configChanged = true;
_configChanged = true;
}
}
@ -112,12 +114,16 @@ namespace Penumbra.UI
if( ImGui.Checkbox( LabelEnableHttpApi, ref http ) )
{
if( http )
{
_base._plugin.CreateWebServer();
}
else
{
_base._plugin.ShutdownWebServer();
}
_config.EnableHttpApi = http;
_configChanged = true;
_configChanged = true;
}
}
@ -141,7 +147,9 @@ namespace Penumbra.UI
{
var ret = ImGui.BeginTabItem( LabelTab );
if( !ret )
{
return;
}
DrawRootFolder();
@ -149,17 +157,19 @@ namespace Penumbra.UI
ImGui.SameLine();
DrawOpenModsButton();
ImGuiCustom.VerticalDistance(DefaultVerticalSpace);
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawEnabledBox();
ImGuiCustom.VerticalDistance(DefaultVerticalSpace);
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawInvertModOrderBox();
ImGuiCustom.VerticalDistance(DefaultVerticalSpace);
ImGuiCustom.VerticalDistance( DefaultVerticalSpace );
DrawShowAdvancedBox();
if( _config.ShowAdvanced )
{
DrawAdvancedSettings();
}
if( _configChanged )
{

View file

@ -5,21 +5,34 @@ namespace Penumbra
{
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];
array[idx1] = array[idx2];
array[idx2] = tmp;
var tmp = array[ idx1 ];
array[ idx1 ] = array[ idx2 ];
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];
array[idx1] = array[idx2];
array[idx2] = tmp;
var tmp = array[ idx1 ];
array[ idx1 ] = array[ idx2 ];
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 );
if( idx1 < 0 )
@ -36,7 +49,7 @@ namespace Penumbra
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 );
if( idx1 < 0 )
@ -53,4 +66,4 @@ namespace Penumbra
array.Swap( idx1, idx2 );
}
}
}
}

View file

@ -15,7 +15,9 @@ namespace Penumbra.Util
{
var k = ( uint )i;
for( var j = 0; j < 8; j++ )
{
k = ( k & 1 ) != 0 ? ( k >> 1 ) ^ Poly : k >> 1;
}
return k;
} ).ToArray();
@ -40,14 +42,16 @@ namespace Penumbra.Util
public void Update( byte[] data )
{
foreach( var b in data )
{
Update( b );
}
}
[MethodImpl( MethodImplOptions.AggressiveInlining )]
public void Update( byte b )
{
_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.Linq;
public class SingleOrArrayConverter<T> : JsonConverter
public class SingleOrArrayConverter< T > : JsonConverter
{
public override bool CanConvert( Type objectType )
{
return (objectType == typeof(HashSet<T>));
}
public override bool CanConvert( Type objectType ) => objectType == typeof( HashSet< T > );
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{
var token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<HashSet<T>>();
}
return new HashSet<T>{ token.ToObject<T>() };
var token = JToken.Load( reader );
return token.Type == JTokenType.Array
? token.ToObject< HashSet< T > >()
: new HashSet< T > { token.ToObject< T >() };
}
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 )
{
return (objectType == typeof(Dictionary<T, HashSet<U>>));
}
public override bool CanConvert( Type objectType ) => objectType == typeof( Dictionary< T, HashSet< U > > );
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;
@ -52,4 +45,4 @@ public class DictSingleOrArrayConverter<T,U> : JsonConverter
{
throw new NotImplementedException();
}
}
}

View file

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