mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-14 20:54:16 +01:00
Merge pull request #15 from Ottermandias/deduplicate
Deduplicate & Edit Meta
This commit is contained in:
commit
1aa31132a3
27 changed files with 2710 additions and 956 deletions
54
Penumbra/Game/RefreshActors.cs
Normal file
54
Penumbra/Game/RefreshActors.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Dalamud.Game.ClientState.Actors;
|
||||||
|
using Dalamud.Game.ClientState.Actors.Types;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Penumbra
|
||||||
|
{
|
||||||
|
public static class RefreshActors
|
||||||
|
{
|
||||||
|
private const int RenderModeOffset = 0x0104;
|
||||||
|
private const int RenderTaskPlayerDelay = 75;
|
||||||
|
private const int RenderTaskOtherDelay = 25;
|
||||||
|
private const int ModelInvisibilityFlag = 0b10;
|
||||||
|
|
||||||
|
private static async void Redraw(Actor actor)
|
||||||
|
{
|
||||||
|
var ptr = actor.Address;
|
||||||
|
var renderModePtr = ptr + RenderModeOffset;
|
||||||
|
var renderStatus = Marshal.ReadInt32(renderModePtr);
|
||||||
|
|
||||||
|
async void DrawObject(int delay)
|
||||||
|
{
|
||||||
|
Marshal.WriteInt32(renderModePtr, renderStatus | ModelInvisibilityFlag);
|
||||||
|
await Task.Delay(delay);
|
||||||
|
Marshal.WriteInt32(renderModePtr, renderStatus & ~ModelInvisibilityFlag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actor.ObjectKind == Dalamud.Game.ClientState.Actors.ObjectKind.Player)
|
||||||
|
{
|
||||||
|
DrawObject(RenderTaskPlayerDelay);
|
||||||
|
await Task.Delay(RenderTaskPlayerDelay);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
DrawObject(RenderTaskOtherDelay);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RedrawSpecific(ActorTable actors, string name)
|
||||||
|
{
|
||||||
|
if (name?.Length == 0)
|
||||||
|
RedrawAll(actors);
|
||||||
|
|
||||||
|
foreach (var actor in actors)
|
||||||
|
if (actor.Name == name)
|
||||||
|
Redraw(actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RedrawAll(ActorTable actors)
|
||||||
|
{
|
||||||
|
foreach (var actor in actors)
|
||||||
|
Redraw(actor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -59,6 +59,9 @@ namespace Penumbra.Importer
|
||||||
case ".ttmp2":
|
case ".ttmp2":
|
||||||
ImportV2ModPack( modPackFile );
|
ImportV2ModPack( modPackFile );
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentException( $"Unrecognized modpack format: {modPackFile.Extension}", nameof(modPackFile) );
|
||||||
}
|
}
|
||||||
|
|
||||||
State = ImporterState.Done;
|
State = ImporterState.Done;
|
||||||
|
|
@ -242,13 +245,16 @@ namespace Penumbra.Importer
|
||||||
var optio = new Option
|
var optio = new Option
|
||||||
{
|
{
|
||||||
OptionName = opt.Name,
|
OptionName = opt.Name,
|
||||||
OptionDesc = String.IsNullOrEmpty( opt.Description ) ? "" : opt.Description,
|
OptionDesc = String.IsNullOrEmpty(opt.Description) ? "" : opt.Description,
|
||||||
OptionFiles = new Dictionary<string, string>()
|
OptionFiles = new Dictionary<string, HashSet<string>>()
|
||||||
};
|
};
|
||||||
var optDir = new DirectoryInfo( Path.Combine( groupFolder.FullName, opt.Name ) );
|
var optDir = new DirectoryInfo(Path.Combine( groupFolder.FullName, opt.Name));
|
||||||
foreach( var file in optDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
|
if (optDir.Exists)
|
||||||
{
|
{
|
||||||
optio.OptionFiles[file.FullName.Substring( baseFolder.FullName.Length ).TrimStart( '\\' )] = file.FullName.Substring( optDir.FullName.Length ).TrimStart( '\\' ).Replace( '\\', '/' );
|
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('\\','/'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Inf.Options.Add( optio );
|
Inf.Options.Add( optio );
|
||||||
}
|
}
|
||||||
|
|
|
||||||
163
Penumbra/Models/Deduplicator.cs
Normal file
163
Penumbra/Models/Deduplicator.cs
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
|
||||||
|
namespace Penumbra.Models
|
||||||
|
{
|
||||||
|
public class Deduplicator
|
||||||
|
{
|
||||||
|
private DirectoryInfo baseDir;
|
||||||
|
private int baseDirLength;
|
||||||
|
private ModMeta mod;
|
||||||
|
private SHA256 hasher = null;
|
||||||
|
|
||||||
|
private Dictionary<long, List<FileInfo>> filesBySize;
|
||||||
|
|
||||||
|
private ref SHA256 Sha()
|
||||||
|
{
|
||||||
|
if (hasher == null)
|
||||||
|
hasher = SHA256.Create();
|
||||||
|
return ref hasher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Deduplicator(DirectoryInfo baseDir, ModMeta mod)
|
||||||
|
{
|
||||||
|
this.baseDir = baseDir;
|
||||||
|
this.baseDirLength = baseDir.FullName.Length;
|
||||||
|
this.mod = mod;
|
||||||
|
filesBySize = new();
|
||||||
|
|
||||||
|
BuildDict();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildDict()
|
||||||
|
{
|
||||||
|
foreach( var file in baseDir.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
|
||||||
|
{
|
||||||
|
var fileLength = file.Length;
|
||||||
|
if (filesBySize.TryGetValue(fileLength, out var files))
|
||||||
|
files.Add(file);
|
||||||
|
else
|
||||||
|
filesBySize[fileLength] = new(){ file };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
foreach (var pair in filesBySize)
|
||||||
|
{
|
||||||
|
if (pair.Value.Count < 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (pair.Value.Count == 2)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
|
||||||
|
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[j])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!CompareHashes(hashes[i], hashes[j]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ReplaceFile(pair.Value[i], pair.Value[j]);
|
||||||
|
deleted[j] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClearEmptySubDirectories(baseDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReplaceFile(FileInfo f1, FileInfo f2)
|
||||||
|
{
|
||||||
|
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 option in group)
|
||||||
|
{
|
||||||
|
if (option.OptionFiles.TryGetValue(relName2, out var values))
|
||||||
|
{
|
||||||
|
inOption = true;
|
||||||
|
foreach (var value in values)
|
||||||
|
option.AddFile(relName1, value);
|
||||||
|
option.OptionFiles.Remove(relName2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!inOption)
|
||||||
|
{
|
||||||
|
const string duplicates = "Duplicates";
|
||||||
|
if (!mod.Groups.ContainsKey(duplicates))
|
||||||
|
{
|
||||||
|
InstallerInfo info = new()
|
||||||
|
{
|
||||||
|
GroupName = duplicates,
|
||||||
|
SelectionType = SelectType.Single,
|
||||||
|
Options = new()
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
OptionName = "Required",
|
||||||
|
OptionDesc = "",
|
||||||
|
OptionFiles = new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mod.Groups.Add(duplicates, info);
|
||||||
|
}
|
||||||
|
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.");
|
||||||
|
f2.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool CompareFilesDirectly(FileInfo f1, FileInfo f2)
|
||||||
|
{
|
||||||
|
return File.ReadAllBytes(f1.FullName).SequenceEqual(File.ReadAllBytes(f2.FullName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool CompareHashes(byte[] f1, byte[] f2)
|
||||||
|
{
|
||||||
|
return StructuralComparisons.StructuralEqualityComparer.Equals(f1, f2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ComputeHash(FileInfo f)
|
||||||
|
{
|
||||||
|
var stream = File.OpenRead( f.FullName );
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
foreach (var subDir in baseDir.GetDirectories())
|
||||||
|
{
|
||||||
|
ClearEmptySubDirectories(subDir);
|
||||||
|
if (subDir.GetFiles().Length == 0 && subDir.GetDirectories().Length == 0)
|
||||||
|
subDir.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Penumbra.Models
|
namespace Penumbra.Models
|
||||||
{
|
{
|
||||||
|
|
@ -10,11 +11,23 @@ namespace Penumbra.Models
|
||||||
{
|
{
|
||||||
public string OptionName;
|
public string OptionName;
|
||||||
public string OptionDesc;
|
public string OptionDesc;
|
||||||
public Dictionary<string, string> OptionFiles;
|
|
||||||
|
[JsonProperty(ItemConverterType = typeof(SingleOrArrayConverter<string>))]
|
||||||
|
public Dictionary<string, HashSet<string>> OptionFiles;
|
||||||
|
|
||||||
|
public bool AddFile(string filePath, string gamePath)
|
||||||
|
{
|
||||||
|
if (OptionFiles.TryGetValue(filePath, out var set))
|
||||||
|
return set.Add(gamePath);
|
||||||
|
else
|
||||||
|
OptionFiles[filePath] = new(){ gamePath };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public struct InstallerInfo
|
|
||||||
{
|
public struct InstallerInfo {
|
||||||
public string GroupName;
|
public string GroupName;
|
||||||
|
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||||
public SelectType SelectionType;
|
public SelectType SelectionType;
|
||||||
public List<Option> Options;
|
public List<Option> Options;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Linq;
|
||||||
|
using System.IO;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Penumbra.Models
|
namespace Penumbra.Models
|
||||||
{
|
{
|
||||||
|
|
@ -18,5 +22,24 @@ namespace Penumbra.Models
|
||||||
public Dictionary< string, string > FileSwaps { get; } = new();
|
public Dictionary< string, string > FileSwaps { get; } = new();
|
||||||
|
|
||||||
public Dictionary<string, InstallerInfo> Groups { get; set; } = new();
|
public Dictionary<string, InstallerInfo> Groups { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool HasGroupWithConfig { get; set; } = false;
|
||||||
|
|
||||||
|
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);
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
catch( Exception)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
// todo: handle broken mods properly
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -64,19 +64,7 @@ namespace Penumbra.Mods
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModMeta meta;
|
var meta = ModMeta.LoadFromFile(metaFile.FullName);
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
meta = JsonConvert.DeserializeObject< ModMeta >( File.ReadAllText( metaFile.FullName ) );
|
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
|
||||||
PluginLog.Error( e, "failed to parse mod info, attempting basic load for: {FilePath}", metaFile.FullName );
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// todo: handle broken mods properly
|
|
||||||
}
|
|
||||||
|
|
||||||
var mod = new ResourceMod
|
var mod = new ResourceMod
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using Penumbra.Models;
|
using Penumbra.Models;
|
||||||
|
|
||||||
namespace Penumbra.Mods
|
namespace Penumbra.Mods
|
||||||
|
|
@ -102,21 +103,7 @@ namespace Penumbra.Mods
|
||||||
// Needed to reload body textures with mods
|
// Needed to reload body textures with mods
|
||||||
//_plugin.GameUtils.ReloadPlayerResources();
|
//_plugin.GameUtils.ReloadPlayerResources();
|
||||||
}
|
}
|
||||||
private (InstallerInfo, Option, string) GlobalPosition( string rel, Dictionary<string, InstallerInfo> gps )
|
|
||||||
{
|
|
||||||
string filePath = null;
|
|
||||||
foreach( var g in gps )
|
|
||||||
{
|
|
||||||
foreach( var opt in g.Value.Options )
|
|
||||||
{
|
|
||||||
if( opt.OptionFiles.TryGetValue( rel, out filePath ) )
|
|
||||||
{
|
|
||||||
return (g.Value, opt, filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (default( InstallerInfo ), default( Option ), null);
|
|
||||||
}
|
|
||||||
public void CalculateEffectiveFileList()
|
public void CalculateEffectiveFileList()
|
||||||
{
|
{
|
||||||
ResolvedFiles.Clear();
|
ResolvedFiles.Clear();
|
||||||
|
|
@ -131,65 +118,87 @@ namespace Penumbra.Mods
|
||||||
// fixup path
|
// fixup path
|
||||||
var baseDir = mod.ModBasePath.FullName;
|
var baseDir = mod.ModBasePath.FullName;
|
||||||
|
|
||||||
|
if(settings.Conf == null) {
|
||||||
|
settings.Conf = new();
|
||||||
|
_plugin.ModManager.Mods.Save();
|
||||||
|
}
|
||||||
|
|
||||||
foreach( var file in mod.ModFiles )
|
foreach( var file in mod.ModFiles )
|
||||||
{
|
{
|
||||||
var relativeFilePath = file.FullName.Substring( baseDir.Length ).TrimStart( '\\' );
|
var relativeFilePath = file.FullName.Substring( baseDir.Length ).TrimStart( '\\' );
|
||||||
|
|
||||||
string gamePath;
|
bool doNotAdd = false;
|
||||||
bool addFile = true;
|
void AddFiles(HashSet<string> gamePaths)
|
||||||
var gps = mod.Meta.Groups;
|
|
||||||
if( gps.Count >= 1 )
|
|
||||||
{
|
{
|
||||||
var negivtron = GlobalPosition( relativeFilePath, gps );
|
doNotAdd = true;
|
||||||
if( negivtron.Item3 != null )
|
foreach (var gamePath in gamePaths)
|
||||||
{
|
{
|
||||||
if( settings.Conf == null )
|
if( !ResolvedFiles.ContainsKey( gamePath ) ) {
|
||||||
{
|
ResolvedFiles[ gamePath.ToLowerInvariant() ] = file;
|
||||||
settings.Conf = new();
|
registeredFiles[ gamePath ] = mod.Meta.Name;
|
||||||
_plugin.ModManager.Mods.Save();
|
|
||||||
}
|
}
|
||||||
if( !settings.Conf.ContainsKey( negivtron.Item1.GroupName ) )
|
else if( registeredFiles.TryGetValue( gamePath, out var modName ) )
|
||||||
{
|
{
|
||||||
settings.Conf[negivtron.Item1.GroupName] = 0;
|
mod.AddConflict( modName, gamePath );
|
||||||
_plugin.ModManager.Mods.Save();
|
|
||||||
}
|
}
|
||||||
var current = settings.Conf[negivtron.Item1.GroupName];
|
|
||||||
var flag = negivtron.Item1.Options.IndexOf( negivtron.Item2 );
|
|
||||||
switch( negivtron.Item1.SelectionType )
|
|
||||||
{
|
|
||||||
case SelectType.Single:
|
|
||||||
{
|
|
||||||
addFile = current == flag;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SelectType.Multi:
|
|
||||||
{
|
|
||||||
flag = 1 << negivtron.Item1.Options.IndexOf( negivtron.Item2 );
|
|
||||||
addFile = ( flag & current ) != 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gamePath = negivtron.Item3;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gamePath = relativeFilePath.Replace( '\\', '/' );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
gamePath = relativeFilePath.Replace( '\\', '/' );
|
HashSet<string> paths;
|
||||||
if( addFile )
|
foreach (var group in mod.Meta.Groups.Select( G => G.Value))
|
||||||
{
|
{
|
||||||
if( !ResolvedFiles.ContainsKey( gamePath ) ) {
|
if (!settings.Conf.TryGetValue(group.GroupName, out var setting)
|
||||||
ResolvedFiles[ gamePath.ToLowerInvariant() ] = file;
|
|| (group.SelectionType == SelectType.Single && settings.Conf[group.GroupName] >= group.Options.Count))
|
||||||
registeredFiles[ gamePath ] = mod.Meta.Name;
|
|
||||||
}
|
|
||||||
else if( registeredFiles.TryGetValue( gamePath, out var modName ) )
|
|
||||||
{
|
{
|
||||||
mod.AddConflict( modName, gamePath );
|
settings.Conf[group.GroupName] = 0;
|
||||||
|
_plugin.ModManager.Mods.Save();
|
||||||
|
setting = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group.Options.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (group.SelectionType == SelectType.Multi)
|
||||||
|
settings.Conf[group.GroupName] &= ((1 << group.Options.Count) - 1);
|
||||||
|
|
||||||
|
switch(group.SelectionType)
|
||||||
|
{
|
||||||
|
case SelectType.Single:
|
||||||
|
if (group.Options[setting].OptionFiles.TryGetValue(relativeFilePath, out paths))
|
||||||
|
AddFiles(paths);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for(var i = 0; i < group.Options.Count; ++i)
|
||||||
|
{
|
||||||
|
if (i == setting)
|
||||||
|
continue;
|
||||||
|
if(group.Options[i].OptionFiles.ContainsKey(relativeFilePath))
|
||||||
|
{
|
||||||
|
doNotAdd = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SelectType.Multi:
|
||||||
|
for(var i = 0; i < group.Options.Count; ++i)
|
||||||
|
{
|
||||||
|
if ((setting & (1 << i)) != 0)
|
||||||
|
{
|
||||||
|
if (group.Options[i].OptionFiles.TryGetValue(relativeFilePath, out paths))
|
||||||
|
AddFiles(paths);
|
||||||
|
}
|
||||||
|
else if (group.Options[i].OptionFiles.ContainsKey(relativeFilePath))
|
||||||
|
doNotAdd = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!doNotAdd)
|
||||||
|
AddFiles(new() { relativeFilePath.Replace( '\\', '/' ) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
foreach( var swap in mod.Meta.FileSwaps )
|
foreach( var swap in mod.Meta.FileSwaps )
|
||||||
{
|
{
|
||||||
// just assume people put not fucked paths in here lol
|
// just assume people put not fucked paths in here lol
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ namespace Penumbra.Mods
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModFiles.Clear();
|
||||||
// we don't care about any _files_ in the root dir, but any folders should be a game folder/file combo
|
// we don't care about any _files_ in the root dir, but any folders should be a game folder/file combo
|
||||||
foreach( var dir in ModBasePath.EnumerateDirectories() )
|
foreach( var dir in ModBasePath.EnumerateDirectories() )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.Linq;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using EmbedIO;
|
using EmbedIO;
|
||||||
|
|
@ -119,12 +120,20 @@ namespace Penumbra
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "redraw":
|
||||||
|
{
|
||||||
|
if (args.Length > 1)
|
||||||
|
RefreshActors.RedrawSpecific(PluginInterface.ClientState.Actors, string.Join(" ", args.Skip(1)));
|
||||||
|
else
|
||||||
|
RefreshActors.RedrawAll(PluginInterface.ClientState.Actors);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsInterface.Visible = !SettingsInterface.Visible;
|
SettingsInterface.FlipVisibility();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,28 +129,31 @@ namespace Penumbra
|
||||||
PluginLog.Log( "[GetResourceHandler] {0}", gameFsPath );
|
PluginLog.Log( "[GetResourceHandler] {0}", gameFsPath );
|
||||||
}
|
}
|
||||||
|
|
||||||
var replacementPath = Plugin.ModManager.ResolveSwappedOrReplacementFilePath( gameFsPath );
|
if (Plugin.Configuration.IsEnabled)
|
||||||
|
|
||||||
// path must be < 260 because statically defined array length :(
|
|
||||||
if( replacementPath == null || replacementPath.Length >= 260 )
|
|
||||||
{
|
{
|
||||||
return CallOriginalHandler( isSync, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, isUnknown );
|
var replacementPath = Plugin.ModManager.ResolveSwappedOrReplacementFilePath( gameFsPath );
|
||||||
}
|
|
||||||
|
|
||||||
var cleanPath = replacementPath.Replace( '\\', '/' );
|
// path must be < 260 because statically defined array length :(
|
||||||
var path = Encoding.ASCII.GetBytes( cleanPath );
|
if( replacementPath == null || replacementPath.Length >= 260 )
|
||||||
|
{
|
||||||
|
return CallOriginalHandler( isSync, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, isUnknown );
|
||||||
|
}
|
||||||
|
|
||||||
var bPath = stackalloc byte[path.Length + 1];
|
var cleanPath = replacementPath.Replace( '\\', '/' );
|
||||||
Marshal.Copy( path, 0, new IntPtr( bPath ), path.Length );
|
var path = Encoding.ASCII.GetBytes( cleanPath );
|
||||||
pPath = ( char* )bPath;
|
|
||||||
|
|
||||||
Crc32.Init();
|
var bPath = stackalloc byte[path.Length + 1];
|
||||||
Crc32.Update( path );
|
Marshal.Copy( path, 0, new IntPtr( bPath ), path.Length );
|
||||||
*pResourceHash = Crc32.Checksum;
|
pPath = ( char* )bPath;
|
||||||
|
|
||||||
|
Crc32.Init();
|
||||||
|
Crc32.Update( path );
|
||||||
|
*pResourceHash = Crc32.Checksum;
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
PluginLog.Log( "[GetResourceHandler] resolved {GamePath} to {NewPath}", gameFsPath, replacementPath );
|
PluginLog.Log( "[GetResourceHandler] resolved {GamePath} to {NewPath}", gameFsPath, replacementPath );
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
return CallOriginalHandler( isSync, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, isUnknown );
|
return CallOriginalHandler( isSync, pFileManager, pCategoryId, pResourceType, pResourceHash, pPath, pUnknown, isUnknown );
|
||||||
}
|
}
|
||||||
|
|
|
||||||
130
Penumbra/UI/ImGuiFramedGroup.cs
Normal file
130
Penumbra/UI/ImGuiFramedGroup.cs
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
{
|
||||||
|
var itemSpacing = ImGui.GetStyle().ItemSpacing;
|
||||||
|
var frameHeight = ImGui.GetFrameHeight();
|
||||||
|
var halfFrameHeight = new Vector2(ImGui.GetFrameHeight() / 2, 0);
|
||||||
|
|
||||||
|
ImGui.BeginGroup(); // First group
|
||||||
|
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, ZeroVector);
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ZeroVector);
|
||||||
|
|
||||||
|
ImGui.BeginGroup(); // Second group
|
||||||
|
|
||||||
|
var effectiveSize = minSize;
|
||||||
|
if (effectiveSize.X < 0)
|
||||||
|
effectiveSize.X = ImGui.GetContentRegionAvail().X;
|
||||||
|
|
||||||
|
// Ensure width.
|
||||||
|
ImGui.Dummy(new(effectiveSize.X, 0));
|
||||||
|
// Ensure left half boundary width/distance.
|
||||||
|
ImGui.Dummy(halfFrameHeight);
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.BeginGroup(); // Third group.
|
||||||
|
// Ensure right half of boundary width/distance
|
||||||
|
ImGui.Dummy(halfFrameHeight);
|
||||||
|
|
||||||
|
// Label block
|
||||||
|
ImGui.SameLine();
|
||||||
|
var ret = false;
|
||||||
|
if (edit)
|
||||||
|
ret = ImGuiCustom.ResizingTextInput(ref label, 1024);
|
||||||
|
else
|
||||||
|
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.BeginGroup(); // Fourth Group.
|
||||||
|
|
||||||
|
ImGui.PopStyleVar(2);
|
||||||
|
|
||||||
|
ImGui.SetWindowSize(new Vector2(ImGui.GetWindowSize().X - frameHeight, ImGui.GetWindowSize().Y));
|
||||||
|
|
||||||
|
var itemWidth = ImGui.CalcItemWidth();
|
||||||
|
ImGui.PushItemWidth(Math.Max(0f, itemWidth - frameHeight));
|
||||||
|
|
||||||
|
labelStack.Add((labelMin, labelMax));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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);
|
||||||
|
|
||||||
|
ImGui.PopItemWidth();
|
||||||
|
|
||||||
|
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);
|
||||||
|
// Ensure bottom distance
|
||||||
|
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 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);
|
||||||
|
|
||||||
|
// Left
|
||||||
|
DrawClippedRect(new(-float.MaxValue , -float.MaxValue ), new(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);
|
||||||
|
// Top
|
||||||
|
DrawClippedRect(new(currentLabelMin.X, -float.MaxValue ), new(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);
|
||||||
|
|
||||||
|
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 List<(Vector2, Vector2)> labelStack = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Penumbra/UI/ImGuiRenameableCombo.cs
Normal file
43
Penumbra/UI/ImGuiRenameableCombo.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
namespace Penumbra.UI
|
||||||
|
{
|
||||||
|
public static partial class ImGuiCustom
|
||||||
|
{
|
||||||
|
public static bool RenameableCombo(string label, ref int currentItem, ref string newName, string[] items, int numItems)
|
||||||
|
{
|
||||||
|
var ret = false;
|
||||||
|
newName = "";
|
||||||
|
var newOption = "";
|
||||||
|
if (ImGui.BeginCombo(label, (numItems > 0) ? items[currentItem] : newOption))
|
||||||
|
{
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
currentItem = i;
|
||||||
|
newName = items[i];
|
||||||
|
ret = true;
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (isSelected)
|
||||||
|
ImGui.SetItemDefaultFocus();
|
||||||
|
}
|
||||||
|
ImGui.SetNextItemWidth(-1);
|
||||||
|
if (ImGui.InputText($"##{label}_new", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||||
|
{
|
||||||
|
currentItem = numItems;
|
||||||
|
newName = newOption;
|
||||||
|
ret = true;
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (numItems == 0)
|
||||||
|
ImGui.SetItemDefaultFocus();
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
Penumbra/UI/ImGuiResizingTextInput.cs
Normal file
40
Penumbra/UI/ImGuiResizingTextInput.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
namespace Penumbra.UI
|
||||||
|
{
|
||||||
|
public static partial class ImGuiCustom
|
||||||
|
{
|
||||||
|
public static bool InputOrText(bool editable, string label, ref string text, uint maxLength)
|
||||||
|
{
|
||||||
|
if (editable)
|
||||||
|
return ResizingTextInput(label, ref text, maxLength);
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Dictionary<uint, float> _textInputWidths = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
26
Penumbra/UI/ImGuiUtil.cs
Normal file
26
Penumbra/UI/ImGuiUtil.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
namespace Penumbra.UI
|
||||||
|
{
|
||||||
|
public static partial class ImGuiCustom
|
||||||
|
{
|
||||||
|
public static void VerticalDistance(float distance)
|
||||||
|
{
|
||||||
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RightJustifiedText(float pos, string text)
|
||||||
|
{
|
||||||
|
ImGui.SetCursorPosX(pos - ImGui.CalcTextSize(text).X - 2 * ImGui.GetStyle().ItemSpacing.X);
|
||||||
|
ImGui.Text(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
56
Penumbra/UI/LaunchButton.cs
Normal file
56
Penumbra/UI/LaunchButton.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
namespace Penumbra.UI
|
||||||
|
{
|
||||||
|
public partial class SettingsInterface
|
||||||
|
{
|
||||||
|
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 static readonly Vector2 WindowSize = new(Width, Height);
|
||||||
|
private static readonly Vector2 WindowPosOffset = new(Padding + Width, Padding + Height);
|
||||||
|
|
||||||
|
private readonly ImGuiWindowFlags ButtonFlags = ImGuiWindowFlags.AlwaysAutoResize
|
||||||
|
| ImGuiWindowFlags.NoBackground
|
||||||
|
| ImGuiWindowFlags.NoDecoration
|
||||||
|
| ImGuiWindowFlags.NoMove
|
||||||
|
| ImGuiWindowFlags.NoScrollbar
|
||||||
|
| ImGuiWindowFlags.NoResize
|
||||||
|
| ImGuiWindowFlags.NoSavedSettings;
|
||||||
|
|
||||||
|
private readonly SettingsInterface _base;
|
||||||
|
private readonly Dalamud.Game.ClientState.Condition _condition;
|
||||||
|
|
||||||
|
public LaunchButton(SettingsInterface ui)
|
||||||
|
{
|
||||||
|
_base = ui;
|
||||||
|
_condition = ui._plugin.PluginInterface.ClientState.Condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Penumbra/UI/MenuBar.cs
Normal file
43
Penumbra/UI/MenuBar.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
namespace Penumbra.UI
|
||||||
|
{
|
||||||
|
public partial class SettingsInterface
|
||||||
|
{
|
||||||
|
private class MenuBar
|
||||||
|
{
|
||||||
|
private const string MenuLabel = "Penumbra";
|
||||||
|
private const string MenuItemToggle = "Toggle UI";
|
||||||
|
private const string SlashCommand = "/penumbra";
|
||||||
|
private const string MenuItemRediscover = "Rediscover Mods";
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
private const bool _showDebugBar = true;
|
||||||
|
#else
|
||||||
|
private const bool _showDebugBar = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private readonly SettingsInterface _base;
|
||||||
|
public MenuBar(SettingsInterface ui) => _base = ui;
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
if( _showDebugBar && ImGui.BeginMainMenuBar() )
|
||||||
|
{
|
||||||
|
if( ImGui.BeginMenu( MenuLabel ) )
|
||||||
|
{
|
||||||
|
if( ImGui.MenuItem( MenuItemToggle, SlashCommand, _base._menu.Visible ) )
|
||||||
|
_base.FlipVisibility();
|
||||||
|
|
||||||
|
if( ImGui.MenuItem( MenuItemRediscover ) )
|
||||||
|
_base.ReloadMods();
|
||||||
|
|
||||||
|
ImGui.EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndMainMenuBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,856 +1,46 @@
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using Dalamud.Interface;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using ImGuiNET;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Penumbra.Importer;
|
|
||||||
using Penumbra.Models;
|
|
||||||
|
|
||||||
namespace Penumbra.UI
|
namespace Penumbra.UI
|
||||||
{
|
{
|
||||||
public class SettingsInterface
|
public partial class SettingsInterface
|
||||||
{
|
{
|
||||||
|
private const float DefaultVerticalSpace = 20f;
|
||||||
|
|
||||||
|
private static readonly Vector2 AutoFillSize = new(-1, -1);
|
||||||
|
private static readonly Vector2 ZeroVector = new( 0, 0);
|
||||||
|
|
||||||
private readonly Plugin _plugin;
|
private readonly Plugin _plugin;
|
||||||
|
|
||||||
public bool Visible;
|
private readonly LaunchButton _launchButton;
|
||||||
public bool ShowDebugBar;
|
private readonly MenuBar _menuBar;
|
||||||
|
private readonly SettingsMenu _menu;
|
||||||
private static readonly Vector2 AutoFillSize = new Vector2( -1, -1 );
|
|
||||||
private static readonly Vector2 ModListSize = new Vector2( 200, -1 );
|
|
||||||
|
|
||||||
private static readonly Vector2 MinSettingsSize = new Vector2( 800, 450 );
|
|
||||||
private static readonly Vector2 MaxSettingsSize = new Vector2( 69420, 42069 );
|
|
||||||
|
|
||||||
private const string DialogDeleteMod = "PenumbraDeleteMod";
|
|
||||||
|
|
||||||
private int _selectedModIndex;
|
|
||||||
private int? _selectedModDeleteIndex;
|
|
||||||
private ModInfo _selectedMod;
|
|
||||||
|
|
||||||
private bool _isImportRunning;
|
|
||||||
private TexToolsImport _texToolsImport = null!;
|
|
||||||
|
|
||||||
public SettingsInterface( Plugin plugin )
|
public SettingsInterface( Plugin plugin )
|
||||||
{
|
{
|
||||||
_plugin = plugin;
|
_plugin = plugin;
|
||||||
#if DEBUG
|
_launchButton = new(this);
|
||||||
Visible = true;
|
_menuBar = new(this);
|
||||||
ShowDebugBar = true;
|
_menu = new(this);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void FlipVisibility() => _menu.Visible = !_menu.Visible;
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
{
|
{
|
||||||
if( ShowDebugBar && ImGui.BeginMainMenuBar() )
|
_menuBar.Draw();
|
||||||
{
|
_launchButton.Draw();
|
||||||
if( ImGui.BeginMenu( "Penumbra" ) )
|
_menu.Draw();
|
||||||
{
|
|
||||||
if( ImGui.MenuItem( "Toggle UI", "/penumbra", Visible ) )
|
|
||||||
{
|
|
||||||
Visible = !Visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ImGui.MenuItem( "Rediscover Mods" ) )
|
|
||||||
{
|
|
||||||
ReloadMods();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImGui.Separator();
|
|
||||||
// #if DEBUG
|
|
||||||
// ImGui.Text( _plugin.PluginDebugTitleStr );
|
|
||||||
// #else
|
|
||||||
// ImGui.Text( _plugin.Name );
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
ImGui.EndMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndMainMenuBar();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( !_plugin.PluginInterface.ClientState.Condition.Any() && !Visible )
|
|
||||||
{
|
|
||||||
// draw mods button on da menu :DDD
|
|
||||||
var ss = ImGui.GetIO().DisplaySize;
|
|
||||||
var padding = 50;
|
|
||||||
var width = 200;
|
|
||||||
var height = 45;
|
|
||||||
|
|
||||||
// magic numbers
|
|
||||||
ImGui.SetNextWindowPos( new Vector2( ss.X - padding - width, ss.Y - padding - height ), ImGuiCond.Always );
|
|
||||||
|
|
||||||
if(
|
|
||||||
ImGui.Begin(
|
|
||||||
"Penumbra Menu Buttons",
|
|
||||||
ImGuiWindowFlags.AlwaysAutoResize |
|
|
||||||
ImGuiWindowFlags.NoBackground |
|
|
||||||
ImGuiWindowFlags.NoDecoration |
|
|
||||||
ImGuiWindowFlags.NoMove |
|
|
||||||
ImGuiWindowFlags.NoScrollbar |
|
|
||||||
ImGuiWindowFlags.NoResize |
|
|
||||||
ImGuiWindowFlags.NoSavedSettings
|
|
||||||
)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if( ImGui.Button( "Manage Mods", new Vector2( width, height ) ) )
|
|
||||||
{
|
|
||||||
Visible = !Visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.End();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( !Visible )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SetNextWindowSizeConstraints( MinSettingsSize, MaxSettingsSize );
|
|
||||||
#if DEBUG
|
|
||||||
var ret = ImGui.Begin( _plugin.PluginDebugTitleStr, ref Visible );
|
|
||||||
#else
|
|
||||||
var ret = ImGui.Begin( _plugin.Name, ref Visible );
|
|
||||||
#endif
|
|
||||||
if( !ret )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.BeginTabBar( "PenumbraSettings" );
|
|
||||||
|
|
||||||
DrawSettingsTab();
|
|
||||||
DrawImportTab();
|
|
||||||
|
|
||||||
|
|
||||||
if( !_isImportRunning )
|
|
||||||
{
|
|
||||||
DrawModBrowser();
|
|
||||||
|
|
||||||
DrawInstalledMods();
|
|
||||||
|
|
||||||
if( _plugin.Configuration.ShowAdvanced )
|
|
||||||
{
|
|
||||||
DrawEffectiveFileList();
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawDeleteModal();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabBar();
|
|
||||||
|
|
||||||
ImGui.End();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawImportTab()
|
|
||||||
{
|
|
||||||
var ret = ImGui.BeginTabItem( "Import Mods" );
|
|
||||||
if( !ret )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( !_isImportRunning )
|
|
||||||
{
|
|
||||||
if( ImGui.Button( "Import TexTools Modpacks" ) )
|
|
||||||
{
|
|
||||||
_isImportRunning = true;
|
|
||||||
|
|
||||||
Task.Run( async () =>
|
|
||||||
{
|
|
||||||
var picker = new OpenFileDialog
|
|
||||||
{
|
|
||||||
Multiselect = true,
|
|
||||||
Filter = "TexTools TTMP Modpack (*.ttmp2)|*.ttmp*|All files (*.*)|*.*",
|
|
||||||
CheckFileExists = true,
|
|
||||||
Title = "Pick one or more modpacks."
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await picker.ShowDialogAsync();
|
|
||||||
|
|
||||||
if( result == DialogResult.OK )
|
|
||||||
{
|
|
||||||
foreach( var fileName in picker.FileNames )
|
|
||||||
{
|
|
||||||
PluginLog.Log( "-> {0} START", fileName );
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_texToolsImport = new TexToolsImport( new DirectoryInfo( _plugin.Configuration.CurrentCollection ) );
|
|
||||||
_texToolsImport.ImportModPack( new FileInfo( fileName ) );
|
|
||||||
}
|
|
||||||
catch( Exception ex )
|
|
||||||
{
|
|
||||||
PluginLog.LogError( ex, "Could not import one or more modpacks." );
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginLog.Log( "-> {0} OK!", fileName );
|
|
||||||
}
|
|
||||||
|
|
||||||
_texToolsImport = null;
|
|
||||||
ReloadMods();
|
|
||||||
}
|
|
||||||
|
|
||||||
_isImportRunning = false;
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.Button( "Import in progress..." );
|
|
||||||
|
|
||||||
if( _texToolsImport != null )
|
|
||||||
{
|
|
||||||
switch( _texToolsImport.State )
|
|
||||||
{
|
|
||||||
case ImporterState.None:
|
|
||||||
break;
|
|
||||||
case ImporterState.WritingPackToDisk:
|
|
||||||
ImGui.Text( "Writing modpack to disk before extracting..." );
|
|
||||||
break;
|
|
||||||
case ImporterState.ExtractingModFiles:
|
|
||||||
{
|
|
||||||
var str =
|
|
||||||
$"{_texToolsImport.CurrentModPack} - {_texToolsImport.CurrentProgress} of {_texToolsImport.TotalProgress} files";
|
|
||||||
|
|
||||||
ImGui.ProgressBar( _texToolsImport.Progress, new Vector2( -1, 0 ), str );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ImporterState.Done:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Conditional( "DEBUG" )]
|
|
||||||
void DrawModBrowser()
|
|
||||||
{
|
|
||||||
var ret = ImGui.BeginTabItem( "Available Mods" );
|
|
||||||
if( !ret )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Text( "woah" );
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawSettingsTab()
|
|
||||||
{
|
|
||||||
var ret = ImGui.BeginTabItem( "Settings" );
|
|
||||||
if( !ret )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool dirty = false;
|
|
||||||
|
|
||||||
// FUCKKKKK
|
|
||||||
var basePath = _plugin.Configuration.CurrentCollection;
|
|
||||||
if( ImGui.InputText( "Root Folder", ref basePath, 255 ) )
|
|
||||||
{
|
|
||||||
_plugin.Configuration.CurrentCollection = basePath;
|
|
||||||
dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ImGui.Button( "Rediscover Mods" ) )
|
|
||||||
{
|
|
||||||
ReloadMods();
|
|
||||||
_selectedModIndex = 0;
|
|
||||||
_selectedMod = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
if( ImGui.Button( "Open Mods Folder" ) )
|
|
||||||
{
|
|
||||||
Process.Start( _plugin.Configuration.CurrentCollection );
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SetCursorPosY( ImGui.GetCursorPosY() + 20 );
|
|
||||||
|
|
||||||
var invertOrder = _plugin.Configuration.InvertModListOrder;
|
|
||||||
if( ImGui.Checkbox( "Invert mod load order (mods are loaded bottom up)", ref invertOrder ) )
|
|
||||||
{
|
|
||||||
_plugin.Configuration.InvertModListOrder = invertOrder;
|
|
||||||
dirty = true;
|
|
||||||
|
|
||||||
ReloadMods();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SetCursorPosY( ImGui.GetCursorPosY() + 20 );
|
|
||||||
|
|
||||||
var showAdvanced = _plugin.Configuration.ShowAdvanced;
|
|
||||||
if( ImGui.Checkbox( "Show Advanced Settings", ref showAdvanced ) )
|
|
||||||
{
|
|
||||||
_plugin.Configuration.ShowAdvanced = showAdvanced;
|
|
||||||
dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( _plugin.Configuration.ShowAdvanced )
|
|
||||||
{
|
|
||||||
if( _plugin.ResourceLoader != null )
|
|
||||||
{
|
|
||||||
ImGui.Checkbox( "Log all loaded files", ref _plugin.ResourceLoader.LogAllFiles );
|
|
||||||
}
|
|
||||||
|
|
||||||
var fswatch = _plugin.Configuration.DisableFileSystemNotifications;
|
|
||||||
if( ImGui.Checkbox( "Disable filesystem change notifications", ref fswatch ) )
|
|
||||||
{
|
|
||||||
_plugin.Configuration.DisableFileSystemNotifications = fswatch;
|
|
||||||
dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var http = _plugin.Configuration.EnableHttpApi;
|
|
||||||
if( ImGui.Checkbox( "Enable HTTP API", ref http ) )
|
|
||||||
{
|
|
||||||
if( http )
|
|
||||||
{
|
|
||||||
_plugin.CreateWebServer();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_plugin.ShutdownWebServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
_plugin.Configuration.EnableHttpApi = http;
|
|
||||||
dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ImGui.Button( "Reload Player Resource" ) )
|
|
||||||
{
|
|
||||||
_plugin.GameUtils.ReloadPlayerResources();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( dirty )
|
|
||||||
{
|
|
||||||
_plugin.Configuration.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawModsSelector()
|
|
||||||
{
|
|
||||||
// Selector pane
|
|
||||||
ImGui.BeginGroup();
|
|
||||||
ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, new Vector2( 0, 0 ) );
|
|
||||||
|
|
||||||
// Inlay selector list
|
|
||||||
ImGui.BeginChild( "availableModList", new Vector2( 240, -ImGui.GetFrameHeightWithSpacing() ), true );
|
|
||||||
|
|
||||||
if( _plugin.ModManager.Mods != null )
|
|
||||||
{
|
|
||||||
for( var modIndex = 0; modIndex < _plugin.ModManager.Mods.ModSettings.Count; modIndex++ )
|
|
||||||
{
|
|
||||||
var settings = _plugin.ModManager.Mods.ModSettings[ modIndex ];
|
|
||||||
|
|
||||||
var changedColour = false;
|
|
||||||
if( !settings.Enabled )
|
|
||||||
{
|
|
||||||
ImGui.PushStyleColor( ImGuiCol.Text, 0xFF666666 );
|
|
||||||
changedColour = true;
|
|
||||||
}
|
|
||||||
else if( settings.Mod.FileConflicts.Any() )
|
|
||||||
{
|
|
||||||
ImGui.PushStyleColor( ImGuiCol.Text, 0xFFAAAAFF );
|
|
||||||
changedColour = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
var selected = ImGui.Selectable(
|
|
||||||
$"id={modIndex} {settings.Mod.Meta.Name}",
|
|
||||||
modIndex == _selectedModIndex
|
|
||||||
);
|
|
||||||
#else
|
|
||||||
var selected = ImGui.Selectable( settings.Mod.Meta.Name, modIndex == _selectedModIndex );
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if( changedColour )
|
|
||||||
{
|
|
||||||
ImGui.PopStyleColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( selected )
|
|
||||||
{
|
|
||||||
_selectedModIndex = modIndex;
|
|
||||||
_selectedMod = settings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndChild();
|
|
||||||
|
|
||||||
// Selector controls
|
|
||||||
ImGui.PushStyleVar( ImGuiStyleVar.WindowPadding, new Vector2( 0, 0 ) );
|
|
||||||
ImGui.PushStyleVar( ImGuiStyleVar.FrameRounding, 0 );
|
|
||||||
ImGui.PushFont( UiBuilder.IconFont );
|
|
||||||
if( _selectedModIndex != 0 )
|
|
||||||
{
|
|
||||||
if( ImGui.Button( FontAwesomeIcon.ArrowUp.ToIconString(), new Vector2( 60, 0 ) ) )
|
|
||||||
{
|
|
||||||
_plugin.ModManager.ChangeModPriority( _selectedMod );
|
|
||||||
_selectedModIndex -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.PushStyleVar( ImGuiStyleVar.Alpha, 0.5f );
|
|
||||||
ImGui.Button( FontAwesomeIcon.ArrowUp.ToIconString(), new Vector2( 60, 0 ) );
|
|
||||||
ImGui.PopStyleVar();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.PopFont();
|
|
||||||
|
|
||||||
if( ImGui.IsItemHovered() )
|
|
||||||
{
|
|
||||||
ImGui.SetTooltip(
|
|
||||||
_plugin.Configuration.InvertModListOrder
|
|
||||||
? "Move the selected mod down in priority"
|
|
||||||
: "Move the selected mod up in priority"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.PushFont( UiBuilder.IconFont );
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
if( _selectedModIndex != _plugin.ModManager.Mods?.ModSettings.Count - 1 )
|
|
||||||
{
|
|
||||||
if( ImGui.Button( FontAwesomeIcon.ArrowDown.ToIconString(), new Vector2( 60, 0 ) ) )
|
|
||||||
{
|
|
||||||
_plugin.ModManager.ChangeModPriority( _selectedMod, true );
|
|
||||||
_selectedModIndex += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.PushStyleVar( ImGuiStyleVar.Alpha, 0.5f );
|
|
||||||
ImGui.Button( FontAwesomeIcon.ArrowDown.ToIconString(), new Vector2( 60, 0 ) );
|
|
||||||
ImGui.PopStyleVar();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ImGui.PopFont();
|
|
||||||
|
|
||||||
if( ImGui.IsItemHovered() )
|
|
||||||
{
|
|
||||||
ImGui.SetTooltip(
|
|
||||||
_plugin.Configuration.InvertModListOrder
|
|
||||||
? "Move the selected mod up in priority"
|
|
||||||
: "Move the selected mod down in priority"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.PushFont( UiBuilder.IconFont );
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
if( ImGui.Button( FontAwesomeIcon.Trash.ToIconString(), new Vector2( 60, 0 ) ) )
|
|
||||||
{
|
|
||||||
_selectedModDeleteIndex = _selectedModIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.PopFont();
|
|
||||||
|
|
||||||
if( ImGui.IsItemHovered() )
|
|
||||||
ImGui.SetTooltip( "Delete the selected mod" );
|
|
||||||
|
|
||||||
ImGui.PushFont( UiBuilder.IconFont );
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
if( ImGui.Button( FontAwesomeIcon.Plus.ToIconString(), new Vector2( 60, 0 ) ) )
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.PopFont();
|
|
||||||
|
|
||||||
if( ImGui.IsItemHovered() )
|
|
||||||
ImGui.SetTooltip( "Add an empty mod" );
|
|
||||||
|
|
||||||
ImGui.PopStyleVar( 3 );
|
|
||||||
|
|
||||||
ImGui.EndGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawDeleteModal()
|
|
||||||
{
|
|
||||||
if( _selectedModDeleteIndex != null )
|
|
||||||
ImGui.OpenPopup( DialogDeleteMod );
|
|
||||||
|
|
||||||
var ret = ImGui.BeginPopupModal( DialogDeleteMod );
|
|
||||||
if( !ret )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( _selectedMod?.Mod == null )
|
|
||||||
{
|
|
||||||
ImGui.CloseCurrentPopup();
|
|
||||||
ImGui.EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Text( "Are you sure you want to delete the following mod:" );
|
|
||||||
// todo: why the fuck does this become null??????
|
|
||||||
ImGui.Text( _selectedMod?.Mod?.Meta?.Name );
|
|
||||||
|
|
||||||
if( ImGui.Button( "Yes, delete it" ) )
|
|
||||||
{
|
|
||||||
ImGui.CloseCurrentPopup();
|
|
||||||
_plugin.ModManager.DeleteMod( _selectedMod.Mod );
|
|
||||||
_selectedMod = null;
|
|
||||||
_selectedModIndex = 0;
|
|
||||||
_selectedModDeleteIndex = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
if( ImGui.Button( "No, keep it" ) )
|
|
||||||
{
|
|
||||||
ImGui.CloseCurrentPopup();
|
|
||||||
_selectedModDeleteIndex = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Website button with On-Hover address if valid http(s), otherwise text.
|
|
||||||
private void DrawWebsiteText()
|
|
||||||
{
|
|
||||||
if( ( _selectedMod.Mod.Meta.Website?.Length ?? 0 ) <= 0 )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var validUrl = Uri.TryCreate( _selectedMod.Mod.Meta.Website, UriKind.Absolute, out Uri uriResult )
|
|
||||||
&& ( uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp );
|
|
||||||
ImGui.SameLine();
|
|
||||||
if( validUrl )
|
|
||||||
{
|
|
||||||
if( ImGui.SmallButton( "Open Website" ) )
|
|
||||||
{
|
|
||||||
Process.Start( _selectedMod.Mod.Meta.Website );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ImGui.IsItemHovered() )
|
|
||||||
{
|
|
||||||
ImGui.BeginTooltip();
|
|
||||||
ImGui.Text( _selectedMod.Mod.Meta.Website );
|
|
||||||
ImGui.EndTooltip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.TextColored( new Vector4( 1f, 1f, 1f, 0.66f ), "from" );
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.Text( _selectedMod.Mod.Meta.Website );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Mod-Handling buttons.
|
|
||||||
private void DrawEditButtons()
|
|
||||||
{
|
|
||||||
ImGui.SameLine();
|
|
||||||
if( ImGui.Button( "Open Mod Folder" ) )
|
|
||||||
{
|
|
||||||
Process.Start( _selectedMod.Mod.ModBasePath.FullName );
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if( ImGui.Button( "Edit JSON" ) )
|
|
||||||
{
|
|
||||||
var metaPath = Path.Combine( _selectedMod.Mod.ModBasePath.FullName, "meta.json" );
|
|
||||||
File.WriteAllText( metaPath, JsonConvert.SerializeObject( _selectedMod.Mod.Meta, Formatting.Indented ) );
|
|
||||||
Process.Start( metaPath );
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if( ImGui.Button( "Reload JSON" ) )
|
|
||||||
{
|
|
||||||
ReloadMods();
|
|
||||||
|
|
||||||
// May select a different mod than before if mods were added or deleted, but will not crash.
|
|
||||||
if( _selectedModIndex < _plugin.ModManager.Mods.ModSettings.Count )
|
|
||||||
{
|
|
||||||
_selectedMod = _plugin.ModManager.Mods.ModSettings[ _selectedModIndex ];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_selectedModIndex = 0;
|
|
||||||
_selectedMod = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawGroupSelectors()
|
|
||||||
{
|
|
||||||
//Spahetti code time
|
|
||||||
var mod = _plugin.SettingsInterface._selectedMod;
|
|
||||||
var conf = mod.Conf;
|
|
||||||
var settings = mod.Mod.Meta.Groups;
|
|
||||||
foreach( var g in settings )
|
|
||||||
{
|
|
||||||
switch( g.Value.SelectionType )
|
|
||||||
{
|
|
||||||
case SelectType.Multi:
|
|
||||||
{
|
|
||||||
var i = 0;
|
|
||||||
var flag = conf[g.Key];
|
|
||||||
foreach( var opt in g.Value.Options )
|
|
||||||
{
|
|
||||||
var enab = ( flag & 1 << i ) != 0;
|
|
||||||
if( ImGui.Checkbox( g.Value.GroupName + " - " + opt.OptionName, ref enab ) )
|
|
||||||
{
|
|
||||||
flag = flag ^= 1 << i;
|
|
||||||
conf[g.Key] = flag;
|
|
||||||
_plugin.ModManager.Mods.Save();
|
|
||||||
_plugin.ModManager.CalculateEffectiveFileList();
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SelectType.Single:
|
|
||||||
{
|
|
||||||
var code = conf[g.Key];
|
|
||||||
if( g.Value.Options.Count > 1 )
|
|
||||||
{
|
|
||||||
if( ImGui.Combo( g.Value.GroupName, ref code, g.Value.Options.Select( x => x.OptionName ).ToArray(), g.Value.Options.ToArray().Length ) )
|
|
||||||
{
|
|
||||||
conf[g.Key] = code;
|
|
||||||
_plugin.ModManager.Mods.Save();
|
|
||||||
_plugin.ModManager.CalculateEffectiveFileList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawInstalledMods()
|
|
||||||
{
|
|
||||||
var ret = ImGui.BeginTabItem( "Installed Mods" );
|
|
||||||
if( !ret )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( _plugin.ModManager.Mods == null )
|
|
||||||
{
|
|
||||||
ImGui.Text( "You don't have any mods :(" );
|
|
||||||
ImGui.SetCursorPosY( ImGui.GetCursorPosY() + 20 );
|
|
||||||
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." );
|
|
||||||
ImGui.Text( "You can return to this tab once you've done that." );
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawModsSelector();
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
if( _selectedMod != null )
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ImGui.BeginChild( "selectedModInfo", AutoFillSize, true );
|
|
||||||
|
|
||||||
ImGui.Text( _selectedMod.Mod.Meta.Name );
|
|
||||||
|
|
||||||
// (Version ...) or nothing.
|
|
||||||
if( ( _selectedMod.Mod.Meta.Version?.Length ?? 0 ) > 0 )
|
|
||||||
{
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.Text( $"(Version {_selectedMod.Mod.Meta.Version})" );
|
|
||||||
}
|
|
||||||
|
|
||||||
// by Author or Unknown.
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.TextColored( new Vector4( 1f, 1f, 1f, 0.66f ), "by" );
|
|
||||||
ImGui.SameLine();
|
|
||||||
if( ( _selectedMod.Mod.Meta.Author?.Length ?? 0 ) > 0 )
|
|
||||||
ImGui.Text( _selectedMod.Mod.Meta.Author );
|
|
||||||
else
|
|
||||||
ImGui.Text( "Unknown" );
|
|
||||||
|
|
||||||
DrawWebsiteText();
|
|
||||||
|
|
||||||
ImGui.SetCursorPosY( ImGui.GetCursorPosY() + 10 );
|
|
||||||
|
|
||||||
var enabled = _selectedMod.Enabled;
|
|
||||||
if( ImGui.Checkbox( "Enabled", ref enabled ) )
|
|
||||||
{
|
|
||||||
_selectedMod.Enabled = enabled;
|
|
||||||
_plugin.ModManager.Mods.Save();
|
|
||||||
_plugin.ModManager.CalculateEffectiveFileList();
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawEditButtons();
|
|
||||||
|
|
||||||
ImGui.BeginTabBar( "PenumbraPluginDetails" );
|
|
||||||
|
|
||||||
if( _selectedMod.Mod.Meta.Description?.Length > 0 && ImGui.BeginTabItem( "About" ) )
|
|
||||||
{
|
|
||||||
ImGui.TextWrapped( _selectedMod.Mod.Meta.Description );
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ( _selectedMod.Mod.Meta.ChangedItems?.Count ?? 0 ) > 0 )
|
|
||||||
{
|
|
||||||
if( ImGui.BeginTabItem( "Changed Items" ) )
|
|
||||||
{
|
|
||||||
ImGui.SetNextItemWidth( -1 );
|
|
||||||
if( ImGui.ListBoxHeader( "###", AutoFillSize ) )
|
|
||||||
foreach( var item in _selectedMod.Mod.Meta.ChangedItems )
|
|
||||||
ImGui.Selectable( item );
|
|
||||||
ImGui.ListBoxFooter();
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(_selectedMod.Mod.Meta.Groups.Count >=1) {
|
|
||||||
if(ImGui.BeginTabItem( "Configuration" )) {
|
|
||||||
DrawGroupSelectors();
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if( ImGui.BeginTabItem( "Files" ) )
|
|
||||||
{
|
|
||||||
ImGui.SetNextItemWidth( -1 );
|
|
||||||
if( ImGui.ListBoxHeader( "##", AutoFillSize ) )
|
|
||||||
foreach( var file in _selectedMod.Mod.ModFiles )
|
|
||||||
ImGui.Selectable( file.FullName );
|
|
||||||
|
|
||||||
ImGui.ListBoxFooter();
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( _selectedMod.Mod.Meta.FileSwaps.Any() )
|
|
||||||
{
|
|
||||||
if( ImGui.BeginTabItem( "File Swaps" ) )
|
|
||||||
{
|
|
||||||
ImGui.SetNextItemWidth( -1 );
|
|
||||||
if( ImGui.ListBoxHeader( "##", AutoFillSize ) )
|
|
||||||
{
|
|
||||||
foreach( var file in _selectedMod.Mod.Meta.FileSwaps )
|
|
||||||
{
|
|
||||||
// todo: fucking gross alloc every frame * items
|
|
||||||
ImGui.Selectable( $"{file.Key} -> {file.Value}" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.ListBoxFooter();
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( _selectedMod.Mod.FileConflicts.Any() )
|
|
||||||
{
|
|
||||||
if( ImGui.BeginTabItem( "File Conflicts" ) )
|
|
||||||
{
|
|
||||||
ImGui.SetNextItemWidth( -1 );
|
|
||||||
if( ImGui.ListBoxHeader( "##", AutoFillSize ) )
|
|
||||||
{
|
|
||||||
foreach( var kv in _selectedMod.Mod.FileConflicts )
|
|
||||||
{
|
|
||||||
var mod = kv.Key;
|
|
||||||
var files = kv.Value;
|
|
||||||
|
|
||||||
if( ImGui.Selectable( mod ) )
|
|
||||||
{
|
|
||||||
SelectModByName( mod );
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Indent( 15 );
|
|
||||||
foreach( var file in files )
|
|
||||||
{
|
|
||||||
ImGui.Selectable( file );
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Unindent( 15 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.ListBoxFooter();
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabBar();
|
|
||||||
ImGui.EndChild();
|
|
||||||
}
|
|
||||||
catch( Exception ex )
|
|
||||||
{
|
|
||||||
PluginLog.LogError( ex, "fuck" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SelectModByName( string name )
|
|
||||||
{
|
|
||||||
for( var modIndex = 0; modIndex < _plugin.ModManager.Mods.ModSettings.Count; modIndex++ )
|
|
||||||
{
|
|
||||||
var mod = _plugin.ModManager.Mods.ModSettings[ modIndex ];
|
|
||||||
|
|
||||||
if( mod.Mod.Meta.Name != name )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_selectedMod = mod;
|
|
||||||
_selectedModIndex = modIndex;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawEffectiveFileList()
|
|
||||||
{
|
|
||||||
var ret = ImGui.BeginTabItem( "Effective File List" );
|
|
||||||
if( !ret )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ImGui.ListBoxHeader( "##", AutoFillSize ) )
|
|
||||||
{
|
|
||||||
// todo: virtualise this
|
|
||||||
foreach( var file in _plugin.ModManager.ResolvedFiles )
|
|
||||||
{
|
|
||||||
ImGui.Selectable( file.Value.FullName + " -> " + file.Key );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.ListBoxFooter();
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReloadMods()
|
private void ReloadMods()
|
||||||
{
|
{
|
||||||
_selectedMod = null;
|
_menu._installedTab._selector.ClearSelection();
|
||||||
|
|
||||||
// create the directory if it doesn't exist
|
// create the directory if it doesn't exist
|
||||||
Directory.CreateDirectory( _plugin.Configuration.CurrentCollection );
|
Directory.CreateDirectory( _plugin.Configuration.CurrentCollection );
|
||||||
|
|
||||||
_plugin.ModManager.DiscoverMods( _plugin.Configuration.CurrentCollection );
|
_plugin.ModManager.DiscoverMods( _plugin.Configuration.CurrentCollection );
|
||||||
|
_menu._effectiveTab.RebuildFileList(_plugin.Configuration.ShowAdvanced);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
72
Penumbra/UI/SettingsMenu.cs
Normal file
72
Penumbra/UI/SettingsMenu.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
namespace Penumbra.UI
|
||||||
|
{
|
||||||
|
public partial class SettingsInterface
|
||||||
|
{
|
||||||
|
private partial class SettingsMenu
|
||||||
|
{
|
||||||
|
private const string PenumbraSettingsLabel = "PenumbraSettings";
|
||||||
|
|
||||||
|
private static readonly Vector2 MinSettingsSize = new( 800, 450 );
|
||||||
|
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;
|
||||||
|
|
||||||
|
public SettingsMenu(SettingsInterface ui)
|
||||||
|
{
|
||||||
|
_base = ui;
|
||||||
|
_settingsTab = new(_base);
|
||||||
|
_importTab = new(_base);
|
||||||
|
_browserTab = new();
|
||||||
|
_installedTab = new(_base);
|
||||||
|
_effectiveTab = new(_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
private const bool DefaultVisibility = true;
|
||||||
|
#else
|
||||||
|
private const bool DefaultVisibility = false;
|
||||||
|
#endif
|
||||||
|
public bool Visible = DefaultVisibility;
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
if( !Visible )
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGui.SetNextWindowSizeConstraints( MinSettingsSize, MaxSettingsSize );
|
||||||
|
#if DEBUG
|
||||||
|
var ret = ImGui.Begin( _base._plugin.PluginDebugTitleStr, ref Visible );
|
||||||
|
#else
|
||||||
|
var ret = ImGui.Begin( _base._plugin.Name, ref Visible );
|
||||||
|
#endif
|
||||||
|
if( !ret )
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGui.BeginTabBar( PenumbraSettingsLabel );
|
||||||
|
|
||||||
|
_settingsTab.Draw();
|
||||||
|
_importTab.Draw();
|
||||||
|
|
||||||
|
if( !_importTab.IsImporting() )
|
||||||
|
{
|
||||||
|
_browserTab.Draw();
|
||||||
|
_installedTab.Draw();
|
||||||
|
|
||||||
|
if( _base._plugin.Configuration.ShowAdvanced )
|
||||||
|
_effectiveTab.Draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabBar();
|
||||||
|
ImGui.End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
Penumbra/UI/TabBrowser.cs
Normal file
22
Penumbra/UI/TabBrowser.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
namespace Penumbra.UI
|
||||||
|
{
|
||||||
|
public partial class SettingsInterface
|
||||||
|
{
|
||||||
|
private class TabBrowser
|
||||||
|
{
|
||||||
|
[Conditional( "DEBUG" )]
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
var ret = ImGui.BeginTabItem( "Available Mods" );
|
||||||
|
if( !ret )
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGui.Text( "woah" );
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
Penumbra/UI/TabEffective.cs
Normal file
66
Penumbra/UI/TabEffective.cs
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
using System.Linq;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Penumbra.Mods;
|
||||||
|
|
||||||
|
namespace Penumbra.UI
|
||||||
|
{
|
||||||
|
public partial class SettingsInterface
|
||||||
|
{
|
||||||
|
private class TabEffective
|
||||||
|
{
|
||||||
|
private const string LabelTab = "Effective File List";
|
||||||
|
private const float TextSizePadding = 5f;
|
||||||
|
|
||||||
|
private ModManager _mods;
|
||||||
|
private (string, string)[] _fileList = null;
|
||||||
|
private float _maxGamePath = 0f;
|
||||||
|
|
||||||
|
public TabEffective(SettingsInterface ui)
|
||||||
|
{
|
||||||
|
_mods = ui._plugin.ModManager;
|
||||||
|
RebuildFileList(ui._plugin.Configuration.ShowAdvanced);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RebuildFileList(bool 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;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_fileList = null;
|
||||||
|
_maxGamePath = 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFileLine((string, string) file)
|
||||||
|
{
|
||||||
|
ImGui.Selectable(file.Item2);
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetCursorPosX(_maxGamePath);
|
||||||
|
ImGui.TextUnformatted(" <-- ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
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);
|
||||||
|
|
||||||
|
ImGui.ListBoxFooter();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
143
Penumbra/UI/TabImport.cs
Normal file
143
Penumbra/UI/TabImport.cs
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
using ImGuiNET;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using System.IO;
|
||||||
|
using System;
|
||||||
|
using Penumbra.Importer;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Penumbra.UI
|
||||||
|
{
|
||||||
|
public partial class SettingsInterface
|
||||||
|
{
|
||||||
|
private class TabImport
|
||||||
|
{
|
||||||
|
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 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 static readonly Vector2 ImportBarSize = new( -1, 0 );
|
||||||
|
|
||||||
|
private bool _isImportRunning = false;
|
||||||
|
private bool _hasError = false;
|
||||||
|
private TexToolsImport _texToolsImport = null!;
|
||||||
|
private readonly SettingsInterface _base;
|
||||||
|
|
||||||
|
public TabImport(SettingsInterface ui) => _base = ui;
|
||||||
|
|
||||||
|
public bool IsImporting() => _isImportRunning;
|
||||||
|
|
||||||
|
private void RunImportTask()
|
||||||
|
{
|
||||||
|
_isImportRunning = true;
|
||||||
|
Task.Run( async () =>
|
||||||
|
{
|
||||||
|
var picker = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Multiselect = true,
|
||||||
|
Filter = FileTypeFilter,
|
||||||
|
CheckFileExists = true,
|
||||||
|
Title = LabelFileDialog
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await picker.ShowDialogAsync();
|
||||||
|
|
||||||
|
if( result == DialogResult.OK )
|
||||||
|
{
|
||||||
|
_hasError = false;
|
||||||
|
|
||||||
|
foreach( var fileName in picker.FileNames )
|
||||||
|
{
|
||||||
|
PluginLog.Log( $"-> {fileName} START");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_texToolsImport = new TexToolsImport( new DirectoryInfo( _base._plugin.Configuration.CurrentCollection ) );
|
||||||
|
_texToolsImport.ImportModPack( new FileInfo( fileName ) );
|
||||||
|
|
||||||
|
PluginLog.Log( $"-> {fileName} OK!" );
|
||||||
|
}
|
||||||
|
catch( Exception ex )
|
||||||
|
{
|
||||||
|
PluginLog.LogError( ex, "Failed to import modpack at {0}", fileName );
|
||||||
|
_hasError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_texToolsImport = null;
|
||||||
|
_base.ReloadMods();
|
||||||
|
}
|
||||||
|
_isImportRunning = false;
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawImportButton()
|
||||||
|
{
|
||||||
|
if( ImGui.Button( LabelImportButton ) )
|
||||||
|
{
|
||||||
|
RunImportTask();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawImportProgress()
|
||||||
|
{
|
||||||
|
ImGui.Button( LabelFileImportRunning );
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
ImGui.ProgressBar( _texToolsImport.Progress, ImportBarSize, str );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ImporterState.Done:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFailedImportMessage()
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor( ImGuiCol.Text, ColorRed );
|
||||||
|
ImGui.Text( FailedImport );
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
var ret = ImGui.BeginTabItem( LabelTab );
|
||||||
|
if( !ret )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if( !_isImportRunning )
|
||||||
|
DrawImportButton();
|
||||||
|
else
|
||||||
|
DrawImportProgress();
|
||||||
|
|
||||||
|
if (_hasError)
|
||||||
|
DrawFailedImportMessage();
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
Penumbra/UI/TabInstalled.cs
Normal file
53
Penumbra/UI/TabInstalled.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
namespace Penumbra.UI
|
||||||
|
{
|
||||||
|
public partial class SettingsInterface
|
||||||
|
{
|
||||||
|
private partial class TabInstalled
|
||||||
|
{
|
||||||
|
private const string LabelTab = "Installed Mods";
|
||||||
|
|
||||||
|
private readonly SettingsInterface _base;
|
||||||
|
public readonly Selector _selector;
|
||||||
|
public readonly ModPanel _modPanel;
|
||||||
|
|
||||||
|
public TabInstalled(SettingsInterface ui)
|
||||||
|
{
|
||||||
|
_base = ui;
|
||||||
|
_selector = new(_base);
|
||||||
|
_modPanel = new(_base, _selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawNoModsAvailable()
|
||||||
|
{
|
||||||
|
ImGui.Text( "You don't have any mods :(" );
|
||||||
|
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." );
|
||||||
|
ImGui.Text( "You can return to this tab once you've done that." );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
var ret = ImGui.BeginTabItem( LabelTab );
|
||||||
|
if( !ret )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_base._plugin.ModManager.Mods != null)
|
||||||
|
{
|
||||||
|
_selector.Draw();
|
||||||
|
ImGui.SameLine();
|
||||||
|
_modPanel.Draw();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
DrawNoModsAvailable();
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
806
Penumbra/UI/TabInstalledDetails.cs
Normal file
806
Penumbra/UI/TabInstalledDetails.cs
Normal file
|
|
@ -0,0 +1,806 @@
|
||||||
|
using Penumbra.Models;
|
||||||
|
using ImGuiNET;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (newString?.Length == 0)
|
||||||
|
list.RemoveAt(idx);
|
||||||
|
else
|
||||||
|
list[idx] = newString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class SettingsInterface
|
||||||
|
{
|
||||||
|
private class PluginDetails
|
||||||
|
{
|
||||||
|
#region ========== Literals ===============
|
||||||
|
private const string LabelPluginDetails = "PenumbraPluginDetails";
|
||||||
|
private const string LabelAboutTab = "About";
|
||||||
|
private const string TooltipAboutEdit = "Use Ctrl+Enter for newlines.";
|
||||||
|
private const string LabelDescEdit = "##descedit";
|
||||||
|
private const string LabelChangedItemsTab = "Changed Items";
|
||||||
|
private const string LabelChangedItemsHeader = "##changedItems";
|
||||||
|
private const string LabelChangedItemIdx = "##citem_";
|
||||||
|
private const string LabelChangedItemNew = "##citem_new";
|
||||||
|
private const string LabelConflictsTab = "File Conflicts";
|
||||||
|
private const string LabelConflictsHeader = "##conflicts";
|
||||||
|
private const string LabelFileSwapTab = "File Swaps";
|
||||||
|
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 ButtonAddToGroup = "Add to Group";
|
||||||
|
private const string ButtonRemoveFromGroup = "Remove from Group";
|
||||||
|
private const string LabelGroupSelect = "##groupSelect";
|
||||||
|
private const string LabelOptionSelect = "##optionSelect";
|
||||||
|
private const string TextNoOptionAvailable = "[No Option Available]";
|
||||||
|
private const string LabelConfigurationTab = "Configuration";
|
||||||
|
private const string LabelNewSingleGroup = "New Single Group";
|
||||||
|
private const string LabelNewSingleGroupEdit = "##newSingleGroup";
|
||||||
|
private const string LabelNewMultiGroup = "New Multi Group";
|
||||||
|
private const string TextDefaultGamePath = "default";
|
||||||
|
private const string LabelGamePathsEdit = "Game Paths";
|
||||||
|
private const string LabelGamePathsEditBox = "##gamePathsEdit";
|
||||||
|
private const string TooltipGamePathText = "Click to copy to clipboard.";
|
||||||
|
private static readonly string TooltipGamePathsEdit = $"Enter all game paths to add or remove, separated by '{GamePathsSeparator}'.\nUse '{TextDefaultGamePath}' to add the original file path.";
|
||||||
|
private static readonly string TooltipFilesTabEdit = $"{TooltipFilesTab}\nRed Files are replaced in another group or a different option in this group, but not contained in the current option.";
|
||||||
|
|
||||||
|
private const char GamePathsSeparator = ';';
|
||||||
|
private const float TextSizePadding = 5f;
|
||||||
|
private const float OptionSelectionWidth = 140f;
|
||||||
|
private const float CheckMarkSize = 50f;
|
||||||
|
private const float MultiEditBoxWidth = 300f;
|
||||||
|
private const uint ColorGreen = 0xFF00C800;
|
||||||
|
private const uint ColorYellow = 0xFF00C8C8;
|
||||||
|
private const uint ColorRed = 0xFF0000C8;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ========== State ==================
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
_selectedGroupIndex = idx;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
_selectedOptionIndex = idx;
|
||||||
|
if (_selectedOptionIndex >= _selectedGroup?.Options.Count)
|
||||||
|
_selectedOptionIndex = 0;
|
||||||
|
if (_selectedGroup?.Options.Count > 0)
|
||||||
|
_selectedOption = ((InstallerInfo) _selectedGroup).Options[_selectedOptionIndex];
|
||||||
|
else
|
||||||
|
_selectedOption = null;
|
||||||
|
}
|
||||||
|
public void SelectOption() => SelectOption(_selectedOptionIndex);
|
||||||
|
|
||||||
|
public void ResetState()
|
||||||
|
{
|
||||||
|
_changedItemsList = null;
|
||||||
|
_fileSwapOffset = null;
|
||||||
|
_fullFilenameList = null;
|
||||||
|
SelectGroup();
|
||||||
|
SelectOption();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private readonly Selector _selector;
|
||||||
|
private readonly SettingsInterface _base;
|
||||||
|
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 void Save()
|
||||||
|
{
|
||||||
|
_base._plugin.ModManager.Mods.Save();
|
||||||
|
_base._plugin.ModManager.CalculateEffectiveFileList();
|
||||||
|
_base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ========== Tabs ===================
|
||||||
|
private void DrawAboutTab()
|
||||||
|
{
|
||||||
|
if (!_editMode && Meta.Description?.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(ImGui.BeginTabItem( LabelAboutTab ) )
|
||||||
|
{
|
||||||
|
var desc = Meta.Description;
|
||||||
|
var flags = _editMode
|
||||||
|
? ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CtrlEnterForNewLine
|
||||||
|
: ImGuiInputTextFlags.ReadOnly;
|
||||||
|
|
||||||
|
if (ImGui.InputTextMultiline(LabelDescEdit, ref desc, 1 << 16, AutoFillSize, flags))
|
||||||
|
{
|
||||||
|
Meta.Description = desc;
|
||||||
|
_selector.SaveCurrentMod();
|
||||||
|
}
|
||||||
|
if (_editMode && ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip( TooltipAboutEdit );
|
||||||
|
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawChangedItemsTab()
|
||||||
|
{
|
||||||
|
if (!_editMode && Meta.ChangedItems?.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var flags = _editMode
|
||||||
|
? ImGuiInputTextFlags.EnterReturnsTrue
|
||||||
|
: ImGuiInputTextFlags.ReadOnly;
|
||||||
|
|
||||||
|
if( ImGui.BeginTabItem( LabelChangedItemsTab ) )
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
ImGui.SetNextItemWidth(-1);
|
||||||
|
if ( ImGui.InputText(_changedItemsList[i].label, ref _changedItemsList[i].name, 128, flags) )
|
||||||
|
{
|
||||||
|
Meta.ChangedItems.RemoveOrChange(_changedItemsList[i].name, i);
|
||||||
|
_selector.SaveCurrentMod();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var newItem = "";
|
||||||
|
if ( _editMode )
|
||||||
|
{
|
||||||
|
ImGui.SetNextItemWidth(-1);
|
||||||
|
if ( ImGui.InputText( LabelChangedItemNew, ref newItem, 128, flags) )
|
||||||
|
{
|
||||||
|
if (newItem.Length > 0)
|
||||||
|
{
|
||||||
|
if (Meta.ChangedItems == null)
|
||||||
|
Meta.ChangedItems = new(){ newItem };
|
||||||
|
else
|
||||||
|
Meta.ChangedItems.Add(newItem);
|
||||||
|
_selector.SaveCurrentMod();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui.ListBoxFooter();
|
||||||
|
}
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_changedItemsList = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawConflictTab()
|
||||||
|
{
|
||||||
|
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 );
|
||||||
|
|
||||||
|
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( ImGui.BeginTabItem( LabelFileSwapTab ) )
|
||||||
|
{
|
||||||
|
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 )
|
||||||
|
{
|
||||||
|
ImGui.Selectable(file.Key);
|
||||||
|
ImGui.SameLine(_fileSwapOffset ?? 0);
|
||||||
|
ImGui.TextUnformatted(" -> ");
|
||||||
|
ImGui.Selectable(file.Value);
|
||||||
|
}
|
||||||
|
ImGui.ListBoxFooter();
|
||||||
|
}
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_fileSwapOffset = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ========== FileList ===============
|
||||||
|
private void UpdateFilenameList()
|
||||||
|
{
|
||||||
|
if (_fullFilenameList == null)
|
||||||
|
{
|
||||||
|
var len = Mod.Mod.ModBasePath.FullName.Length;
|
||||||
|
_fullFilenameList = Mod.Mod.ModFiles.Select( F => (F.FullName, false, ColorGreen, "") ).ToArray();
|
||||||
|
|
||||||
|
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('\\');
|
||||||
|
foreach (var Group in Meta.Groups.Values)
|
||||||
|
{
|
||||||
|
var inAll = true;
|
||||||
|
foreach (var Option in Group.Options)
|
||||||
|
{
|
||||||
|
if (Option.OptionFiles.ContainsKey(_fullFilenameList[i].relName))
|
||||||
|
_fullFilenameList[i].color = ColorYellow;
|
||||||
|
else
|
||||||
|
inAll = false;
|
||||||
|
}
|
||||||
|
if (inAll && Group.SelectionType == SelectType.Single)
|
||||||
|
_fullFilenameList[i].color = ColorGreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFileListTab()
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (gamePaths[i] == TextDefaultGamePath )
|
||||||
|
{
|
||||||
|
defaultIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseLength = Mod.Mod.ModBasePath.FullName.Length;
|
||||||
|
var changed = false;
|
||||||
|
for (var i = 0; i < Mod.Mod.ModFiles.Count; ++i)
|
||||||
|
{
|
||||||
|
if (!_fullFilenameList[i].selected)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var fileName = _fullFilenameList[i].relName;
|
||||||
|
if (defaultIndex != null)
|
||||||
|
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))
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach(var gamePath in gamePaths)
|
||||||
|
changed |= option.AddFile(fileName, gamePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changed)
|
||||||
|
_selector.SaveCurrentMod();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawAddToGroupButton()
|
||||||
|
{
|
||||||
|
if (ImGui.Button( ButtonAddToGroup ) )
|
||||||
|
HandleSelectedFilesButton(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRemoveFromGroupButton()
|
||||||
|
{
|
||||||
|
if (ImGui.Button( ButtonRemoveFromGroup ) )
|
||||||
|
HandleSelectedFilesButton(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawEditGroupSelector()
|
||||||
|
{
|
||||||
|
ImGui.SetNextItemWidth( OptionSelectionWidth );
|
||||||
|
if (Meta.Groups.Count == 0)
|
||||||
|
{
|
||||||
|
ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex, TextNoOptionAvailable, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ImGui.Combo( LabelGroupSelect, ref _selectedGroupIndex, Meta.Groups.Values.Select( G => G.GroupName ).ToArray(), Meta.Groups.Count))
|
||||||
|
{
|
||||||
|
SelectGroup();
|
||||||
|
SelectOption(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawEditOptionSelector()
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetNextItemWidth( OptionSelectionWidth );
|
||||||
|
if (_selectedGroup?.Options.Count == 0)
|
||||||
|
{
|
||||||
|
ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, TextNoOptionAvailable, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var group = (InstallerInfo) _selectedGroup;
|
||||||
|
if (ImGui.Combo( LabelOptionSelect, ref _selectedOptionIndex, group.Options.Select(O => O.OptionName).ToArray(), group.Options.Count))
|
||||||
|
SelectOption();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawGamePathInput()
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted( LabelGamePathsEdit );
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetNextItemWidth(-1);
|
||||||
|
ImGui.InputText(LabelGamePathsEditBox, ref _currentGamePaths, 128);
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip(TooltipGamePathsEdit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawGroupRow()
|
||||||
|
{
|
||||||
|
if (_selectedGroup == null)
|
||||||
|
SelectGroup();
|
||||||
|
if (_selectedOption == null)
|
||||||
|
SelectOption();
|
||||||
|
|
||||||
|
DrawEditGroupSelector();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawEditOptionSelector();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawAddToGroupButton();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawRemoveFromGroupButton();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawGamePathInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFileAndGamePaths(int idx)
|
||||||
|
{
|
||||||
|
void Selectable(uint colorNormal, uint colorReplace)
|
||||||
|
{
|
||||||
|
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.PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
const float indent = 30f;
|
||||||
|
if (_selectedOption == null)
|
||||||
|
{
|
||||||
|
Selectable(0, ColorGreen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileName = _fullFilenameList[idx].relName;
|
||||||
|
if (((Option) _selectedOption).OptionFiles.TryGetValue(fileName, out var gamePaths))
|
||||||
|
{
|
||||||
|
Selectable(0, ColorGreen);
|
||||||
|
|
||||||
|
ImGui.Indent(indent);
|
||||||
|
foreach (var gamePath in gamePaths)
|
||||||
|
{
|
||||||
|
ImGui.Text(gamePath);
|
||||||
|
if (ImGui.IsItemClicked())
|
||||||
|
ImGui.SetClipboardText(gamePath);
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip( TooltipGamePathText );
|
||||||
|
}
|
||||||
|
ImGui.Unindent(indent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Selectable(ColorYellow, ColorRed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFileListTabEdit()
|
||||||
|
{
|
||||||
|
if( ImGui.BeginTabItem( LabelFileListTab ) )
|
||||||
|
{
|
||||||
|
UpdateFilenameList();
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip( _editMode ? TooltipFilesTabEdit : TooltipFilesTab );
|
||||||
|
|
||||||
|
ImGui.SetNextItemWidth( -1 );
|
||||||
|
if( ImGui.ListBoxHeader( LabelFileListHeader, AutoFillSize - new Vector2(0, 1.5f * ImGui.GetTextLineHeight()) ) )
|
||||||
|
for(var i = 0; i < Mod.Mod.ModFiles.Count; ++i)
|
||||||
|
DrawFileAndGamePaths(i);
|
||||||
|
|
||||||
|
ImGui.ListBoxFooter();
|
||||||
|
|
||||||
|
DrawGroupRow();
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_fullFilenameList = null;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ========== Configuration ==========
|
||||||
|
#region ========== MultiSelectorEdit ==========
|
||||||
|
private bool DrawMultiSelectorEditBegin(InstallerInfo group)
|
||||||
|
{
|
||||||
|
var groupName = group.GroupName;
|
||||||
|
if (ImGuiCustom.BeginFramedGroupEdit(ref groupName)
|
||||||
|
&& groupName != group.GroupName && !Meta.Groups.ContainsKey(groupName))
|
||||||
|
{
|
||||||
|
var oldConf = Mod.Conf[group.GroupName];
|
||||||
|
Meta.Groups.Remove(group.GroupName);
|
||||||
|
Mod.Conf.Remove(group.GroupName);
|
||||||
|
if (groupName.Length > 0)
|
||||||
|
{
|
||||||
|
Meta.Groups[groupName] = new(){ GroupName = groupName, SelectionType = SelectType.Multi, Options = group.Options };
|
||||||
|
Mod.Conf[groupName] = oldConf;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
private void DrawMultiSelectorEditAdd(InstallerInfo group, float nameBoxStart)
|
||||||
|
{
|
||||||
|
var newOption = "";
|
||||||
|
ImGui.SetCursorPosX(nameBoxStart);
|
||||||
|
ImGui.SetNextItemWidth(MultiEditBoxWidth);
|
||||||
|
if (ImGui.InputText($"##new_{group.GroupName}_l", ref newOption, 64, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||||
|
{
|
||||||
|
if (newOption.Length != 0)
|
||||||
|
{
|
||||||
|
group.Options.Add(new(){ OptionName = newOption, OptionDesc = "", OptionFiles = new() });
|
||||||
|
_selector.SaveCurrentMod();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMultiSelectorEdit(InstallerInfo group)
|
||||||
|
{
|
||||||
|
var nameBoxStart = CheckMarkSize;
|
||||||
|
var flag = Mod.Conf[group.GroupName];
|
||||||
|
|
||||||
|
var modChanged = DrawMultiSelectorEditBegin(group);
|
||||||
|
|
||||||
|
for (var i = 0; i < group.Options.Count; ++i)
|
||||||
|
{
|
||||||
|
var opt = group.Options[i];
|
||||||
|
var label = $"##{opt.OptionName}_{group.GroupName}";
|
||||||
|
DrawMultiSelectorCheckBox(group, i, flag, label);
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
var newName = opt.OptionName;
|
||||||
|
|
||||||
|
if (nameBoxStart == CheckMarkSize)
|
||||||
|
nameBoxStart = ImGui.GetCursorPosX();
|
||||||
|
|
||||||
|
ImGui.SetNextItemWidth(MultiEditBoxWidth);
|
||||||
|
if (ImGui.InputText($"{label}_l", ref newName, 64, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||||
|
{
|
||||||
|
if (newName.Length == 0)
|
||||||
|
{
|
||||||
|
group.Options.RemoveAt(i);
|
||||||
|
var bitmaskFront = (1 << i) - 1;
|
||||||
|
Mod.Conf[group.GroupName] = (flag & bitmaskFront) | ((flag & ~bitmaskFront) >> 1);
|
||||||
|
modChanged = true;
|
||||||
|
}
|
||||||
|
else if (newName != opt.OptionName)
|
||||||
|
{
|
||||||
|
group.Options[i] = new(){ OptionName = newName, OptionDesc = opt.OptionDesc, OptionFiles = opt.OptionFiles };
|
||||||
|
_selector.SaveCurrentMod();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawMultiSelectorEditAdd(group, nameBoxStart);
|
||||||
|
|
||||||
|
if (modChanged)
|
||||||
|
{
|
||||||
|
_selector.SaveCurrentMod();
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiCustom.EndFramedGroup();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ========== SingleSelectorEdit ==========
|
||||||
|
private bool DrawSingleSelectorEditGroup(InstallerInfo group)
|
||||||
|
{
|
||||||
|
var groupName = group.GroupName;
|
||||||
|
if (ImGui.InputText($"##{groupName}_add", ref groupName, 64, ImGuiInputTextFlags.EnterReturnsTrue)
|
||||||
|
&& !Meta.Groups.ContainsKey(groupName))
|
||||||
|
{
|
||||||
|
var oldConf = Mod.Conf[group.GroupName];
|
||||||
|
if (groupName != group.GroupName)
|
||||||
|
{
|
||||||
|
Meta.Groups.Remove(group.GroupName);
|
||||||
|
Mod.Conf.Remove(group.GroupName);
|
||||||
|
}
|
||||||
|
if (groupName.Length > 0)
|
||||||
|
{
|
||||||
|
Meta.Groups.Add(groupName, new InstallerInfo(){ GroupName = groupName, Options = group.Options, SelectionType = SelectType.Single } );
|
||||||
|
Mod.Conf[groupName] = oldConf;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float DrawSingleSelectorEdit(InstallerInfo group)
|
||||||
|
{
|
||||||
|
var code = Mod.Conf[group.GroupName];
|
||||||
|
var selectionChanged = false;
|
||||||
|
var modChanged = false;
|
||||||
|
var newName = "";
|
||||||
|
if (ImGuiCustom.RenameableCombo($"##{group.GroupName}", ref code, ref newName, group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count))
|
||||||
|
{
|
||||||
|
if (code == group.Options.Count)
|
||||||
|
{
|
||||||
|
if (newName.Length > 0)
|
||||||
|
{
|
||||||
|
selectionChanged = true;
|
||||||
|
modChanged = true;
|
||||||
|
Mod.Conf[group.GroupName] = code;
|
||||||
|
group.Options.Add(new(){ OptionName = newName, OptionDesc = "", OptionFiles = new()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (newName.Length == 0)
|
||||||
|
{
|
||||||
|
modChanged = true;
|
||||||
|
group.Options.RemoveAt(code);
|
||||||
|
if (code >= group.Options.Count)
|
||||||
|
code = 0;
|
||||||
|
}
|
||||||
|
else if (newName != group.Options[code].OptionName)
|
||||||
|
{
|
||||||
|
modChanged = true;
|
||||||
|
group.Options[code] = new Option(){ OptionName = newName, OptionDesc = group.Options[code].OptionDesc, OptionFiles = group.Options[code].OptionFiles};
|
||||||
|
}
|
||||||
|
if (Mod.Conf[group.GroupName] != code)
|
||||||
|
{
|
||||||
|
selectionChanged = true;
|
||||||
|
Mod.Conf[group.GroupName] = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
var labelEditPos = ImGui.GetCursorPosX();
|
||||||
|
modChanged |= DrawSingleSelectorEditGroup(group);
|
||||||
|
|
||||||
|
if (modChanged)
|
||||||
|
_selector.SaveCurrentMod();
|
||||||
|
|
||||||
|
if (selectionChanged)
|
||||||
|
Save();
|
||||||
|
|
||||||
|
return labelEditPos;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
private void AddNewGroup(string newGroup, SelectType selectType)
|
||||||
|
{
|
||||||
|
if (!Meta.Groups.ContainsKey(newGroup) && newGroup.Length > 0)
|
||||||
|
{
|
||||||
|
Meta.Groups[newGroup] = new ()
|
||||||
|
{
|
||||||
|
GroupName = newGroup,
|
||||||
|
SelectionType = selectType,
|
||||||
|
Options = new()
|
||||||
|
} ;
|
||||||
|
|
||||||
|
Mod.Conf[newGroup] = 0;
|
||||||
|
_selector.SaveCurrentMod();
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawAddSingleGroupField(float labelEditPos)
|
||||||
|
{
|
||||||
|
var newGroup = "";
|
||||||
|
if(labelEditPos == CheckMarkSize)
|
||||||
|
{
|
||||||
|
ImGui.SetCursorPosX(CheckMarkSize);
|
||||||
|
ImGui.SetNextItemWidth(MultiEditBoxWidth);
|
||||||
|
if (ImGui.InputText(LabelNewSingleGroup, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||||
|
AddNewGroup(newGroup, SelectType.Single);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGuiCustom.RightJustifiedLabel(labelEditPos, LabelNewSingleGroup );
|
||||||
|
if (ImGui.InputText(LabelNewSingleGroupEdit, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||||
|
AddNewGroup(newGroup, SelectType.Single);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawAddMultiGroupField()
|
||||||
|
{
|
||||||
|
var newGroup = "";
|
||||||
|
ImGui.SetCursorPosX(CheckMarkSize);
|
||||||
|
ImGui.SetNextItemWidth(MultiEditBoxWidth);
|
||||||
|
if (ImGui.InputText(LabelNewMultiGroup, ref newGroup, 64, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||||
|
AddNewGroup(newGroup, SelectType.Multi);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawGroupSelectorsEdit()
|
||||||
|
{
|
||||||
|
var labelEditPos = CheckMarkSize;
|
||||||
|
foreach( var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Single ) )
|
||||||
|
labelEditPos = DrawSingleSelectorEdit(g);
|
||||||
|
DrawAddSingleGroupField(labelEditPos);
|
||||||
|
|
||||||
|
foreach(var g in Meta.Groups.Values.Where( g => g.SelectionType == SelectType.Multi ))
|
||||||
|
DrawMultiSelectorEdit(g);
|
||||||
|
DrawAddMultiGroupField();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Non-Edit
|
||||||
|
|
||||||
|
private void DrawMultiSelectorCheckBox(InstallerInfo group, int idx, int flag, string label)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (group.Options.Count < 2)
|
||||||
|
return;
|
||||||
|
var code = Mod.Conf[group.GroupName];
|
||||||
|
if( ImGui.Combo( group.GroupName, ref code, group.Options.Select( x => x.OptionName ).ToArray(), group.Options.Count ) )
|
||||||
|
{
|
||||||
|
Mod.Conf[group.GroupName] = code;
|
||||||
|
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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
private void DrawConfigurationTab()
|
||||||
|
{
|
||||||
|
if (!_editMode && !Meta.HasGroupWithConfig)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(ImGui.BeginTabItem( LabelConfigurationTab ) )
|
||||||
|
{
|
||||||
|
if (_editMode)
|
||||||
|
DrawGroupSelectorsEdit();
|
||||||
|
else
|
||||||
|
DrawGroupSelectors();
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public void Draw(bool editMode)
|
||||||
|
{
|
||||||
|
_editMode = editMode;
|
||||||
|
ImGui.BeginTabBar( LabelPluginDetails );
|
||||||
|
|
||||||
|
DrawAboutTab();
|
||||||
|
DrawChangedItemsTab();
|
||||||
|
DrawConfigurationTab();
|
||||||
|
if (_editMode)
|
||||||
|
DrawFileListTabEdit();
|
||||||
|
else
|
||||||
|
DrawFileListTab();
|
||||||
|
DrawFileSwapTab();
|
||||||
|
DrawConflictTab();
|
||||||
|
|
||||||
|
ImGui.EndTabBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
289
Penumbra/UI/TabInstalledModPanel.cs
Normal file
289
Penumbra/UI/TabInstalledModPanel.cs
Normal file
|
|
@ -0,0 +1,289 @@
|
||||||
|
using ImGuiNET;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Penumbra.Models;
|
||||||
|
|
||||||
|
namespace Penumbra.UI
|
||||||
|
{
|
||||||
|
public partial class SettingsInterface
|
||||||
|
{
|
||||||
|
private class ModPanel
|
||||||
|
{
|
||||||
|
private const string LabelModPanel = "selectedModInfo";
|
||||||
|
private const string LabelEditName = "##editName";
|
||||||
|
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 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 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;
|
||||||
|
|
||||||
|
private bool _editMode = false;
|
||||||
|
private string _currentWebsite;
|
||||||
|
private bool _validWebsite;
|
||||||
|
|
||||||
|
public ModPanel(SettingsInterface ui, Selector s)
|
||||||
|
{
|
||||||
|
_base = ui;
|
||||||
|
_selector = s;
|
||||||
|
_details = new(_base, _selector);
|
||||||
|
_currentWebsite = Meta?.Website;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModInfo Mod { get{ return _selector.Mod(); } }
|
||||||
|
private ModMeta Meta { get{ return Mod?.Mod.Meta; } }
|
||||||
|
|
||||||
|
#region Header Line Functions
|
||||||
|
private void DrawName()
|
||||||
|
{
|
||||||
|
var name = Meta.Name;
|
||||||
|
if (ImGuiCustom.InputOrText(_editMode, LabelEditName, ref name, 64)
|
||||||
|
&& name.Length > 0 && name != Meta.Name)
|
||||||
|
{
|
||||||
|
Meta.Name = name;
|
||||||
|
_selector.SaveCurrentMod();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawVersion()
|
||||||
|
{
|
||||||
|
if (_editMode)
|
||||||
|
{
|
||||||
|
ImGui.BeginGroup();
|
||||||
|
ImGui.Text("(Version ");
|
||||||
|
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ZeroVector);
|
||||||
|
ImGui.SameLine();
|
||||||
|
var 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.PopStyleVar();
|
||||||
|
ImGui.EndGroup();
|
||||||
|
}
|
||||||
|
else if ((Meta.Version?.Length ?? 0) > 0)
|
||||||
|
{
|
||||||
|
ImGui.Text( $"(Version {Meta.Version})" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawAuthor()
|
||||||
|
{
|
||||||
|
ImGui.BeginGroup();
|
||||||
|
ImGui.TextColored( GreyColor, "by" );
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
var 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)
|
||||||
|
{
|
||||||
|
ImGui.TextColored( GreyColor, "from" );
|
||||||
|
ImGui.SameLine();
|
||||||
|
var 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)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
UseShellExecute = true
|
||||||
|
};
|
||||||
|
Process.Start(process);
|
||||||
|
}
|
||||||
|
catch(System.ComponentModel.Win32Exception)
|
||||||
|
{
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( ImGui.IsItemHovered() )
|
||||||
|
ImGui.SetTooltip( Meta.Website );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.TextColored( GreyColor, "from" );
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text( Meta.Website );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui.EndGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawHeaderLine()
|
||||||
|
{
|
||||||
|
DrawName();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawVersion();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawAuthor();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawWebsite();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Enabled Checkmarks
|
||||||
|
private void DrawEnabledMark()
|
||||||
|
{
|
||||||
|
var enabled = Mod.Enabled;
|
||||||
|
if( ImGui.Checkbox( LabelModEnabled, ref enabled ) )
|
||||||
|
{
|
||||||
|
Mod.Enabled = enabled;
|
||||||
|
_base._plugin.ModManager.Mods.Save();
|
||||||
|
_base._plugin.ModManager.CalculateEffectiveFileList();
|
||||||
|
_base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawEditableMark()
|
||||||
|
{
|
||||||
|
ImGui.Checkbox( LabelEditingEnabled, ref _editMode);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Edit Line Functions
|
||||||
|
private void DrawOpenModFolderButton()
|
||||||
|
{
|
||||||
|
if( ImGui.Button( ButtonOpenModFolder ) )
|
||||||
|
{
|
||||||
|
Process.Start( Mod.Mod.ModBasePath.FullName );
|
||||||
|
}
|
||||||
|
if( ImGui.IsItemHovered() )
|
||||||
|
ImGui.SetTooltip( TooltipOpenModFolder );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawEditJsonButton()
|
||||||
|
{
|
||||||
|
if( ImGui.Button( ButtonEditJson ) )
|
||||||
|
{
|
||||||
|
Process.Start( _selector.SaveCurrentMod() );
|
||||||
|
}
|
||||||
|
if( ImGui.IsItemHovered() )
|
||||||
|
ImGui.SetTooltip( TooltipEditJson );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawReloadJsonButton()
|
||||||
|
{
|
||||||
|
if( ImGui.Button( ButtonReloadJson ) )
|
||||||
|
{
|
||||||
|
_selector.ReloadCurrentMod();
|
||||||
|
}
|
||||||
|
if( ImGui.IsItemHovered() )
|
||||||
|
ImGui.SetTooltip( TooltipReloadJson );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDeduplicateButton()
|
||||||
|
{
|
||||||
|
if( ImGui.Button( ButtonDeduplicate ) )
|
||||||
|
{
|
||||||
|
new Deduplicator(Mod.Mod.ModBasePath, Meta).Run();
|
||||||
|
_selector.SaveCurrentMod();
|
||||||
|
Mod.Mod.RefreshModFiles();
|
||||||
|
_base._plugin.ModManager.CalculateEffectiveFileList();
|
||||||
|
_base._menu._effectiveTab.RebuildFileList(_base._plugin.Configuration.ShowAdvanced);
|
||||||
|
}
|
||||||
|
if( ImGui.IsItemHovered() )
|
||||||
|
ImGui.SetTooltip( TooltipDeduplicate );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawEditLine()
|
||||||
|
{
|
||||||
|
DrawOpenModFolderButton();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawEditJsonButton();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawReloadJsonButton();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawDeduplicateButton();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
if( Mod != null )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
catch( Exception ex )
|
||||||
|
{
|
||||||
|
PluginLog.LogError( ex, "fuck" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
277
Penumbra/UI/TabInstalledSelector.cs
Normal file
277
Penumbra/UI/TabInstalledSelector.cs
Normal file
|
|
@ -0,0 +1,277 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.IO;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.Models;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
|
||||||
|
namespace Penumbra.UI
|
||||||
|
{
|
||||||
|
public partial class SettingsInterface
|
||||||
|
{
|
||||||
|
private class Selector
|
||||||
|
{
|
||||||
|
private const string LabelSelectorList = "##availableModList";
|
||||||
|
private const string TooltipMoveDown = "Move the selected mod down in priority";
|
||||||
|
private const string TooltipMoveUp = "Move the selected mod up in priority";
|
||||||
|
private const string TooltipDelete = "Delete the selected mod";
|
||||||
|
private const string TooltipAdd = "Add an empty mod";
|
||||||
|
private const string DialogDeleteMod = "PenumbraDeleteMod";
|
||||||
|
private const string ButtonYesDelete = "Yes, delete it";
|
||||||
|
private const string ButtonNoDelete = "No, keep it";
|
||||||
|
private const float SelectorPanelWidth = 240f;
|
||||||
|
private const uint DisabledModColor = 0xFF666666;
|
||||||
|
private const uint ConflictingModColor = 0xFFAAAAFF;
|
||||||
|
|
||||||
|
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 ModInfo _mod = null;
|
||||||
|
private int _index = 0;
|
||||||
|
private int? _deleteIndex = null;
|
||||||
|
|
||||||
|
public Selector(SettingsInterface ui)
|
||||||
|
{
|
||||||
|
_base = ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPriorityChangeButton(string iconString, bool up, int unavailableWhen)
|
||||||
|
{
|
||||||
|
ImGui.PushFont( UiBuilder.IconFont );
|
||||||
|
if( _index != unavailableWhen )
|
||||||
|
{
|
||||||
|
if( ImGui.Button( iconString, SelectorButtonSizes ) )
|
||||||
|
{
|
||||||
|
SetSelection(_index);
|
||||||
|
_base._plugin.ModManager.ChangeModPriority( _mod, up );
|
||||||
|
_index += up ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.PushStyleVar( ImGuiStyleVar.Alpha, 0.5f );
|
||||||
|
ImGui.Button( iconString, SelectorButtonSizes );
|
||||||
|
ImGui.PopStyleVar();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopFont();
|
||||||
|
|
||||||
|
if( ImGui.IsItemHovered() )
|
||||||
|
{
|
||||||
|
ImGui.SetTooltip(
|
||||||
|
_base._plugin.Configuration.InvertModListOrder ^ up ? TooltipMoveDown : TooltipMoveUp
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawModTrashButton()
|
||||||
|
{
|
||||||
|
ImGui.PushFont( UiBuilder.IconFont );
|
||||||
|
|
||||||
|
if( ImGui.Button( FontAwesomeIcon.Trash.ToIconString(), SelectorButtonSizes ) )
|
||||||
|
{
|
||||||
|
_deleteIndex = _index;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopFont();
|
||||||
|
|
||||||
|
if( ImGui.IsItemHovered() )
|
||||||
|
ImGui.SetTooltip( TooltipDelete );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawModAddButton()
|
||||||
|
{
|
||||||
|
ImGui.PushFont( UiBuilder.IconFont );
|
||||||
|
|
||||||
|
if( ImGui.Button( FontAwesomeIcon.Plus.ToIconString(), SelectorButtonSizes ) )
|
||||||
|
{
|
||||||
|
// Do nothing. YEAH. #TODO.
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopFont();
|
||||||
|
|
||||||
|
if( ImGui.IsItemHovered() )
|
||||||
|
ImGui.SetTooltip( TooltipAdd );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawModsSelectorButtons()
|
||||||
|
{
|
||||||
|
// Selector controls
|
||||||
|
ImGui.PushStyleVar( ImGuiStyleVar.WindowPadding, ZeroVector );
|
||||||
|
ImGui.PushStyleVar( ImGuiStyleVar.FrameRounding, 0 );
|
||||||
|
|
||||||
|
DrawPriorityChangeButton(ArrowUpString, false, 0);
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawPriorityChangeButton(ArrowDownString, true, Mods?.ModSettings.Count - 1 ?? 0);
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawModTrashButton();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawModAddButton();
|
||||||
|
|
||||||
|
ImGui.PopStyleVar( 3 );
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawDeleteModal()
|
||||||
|
{
|
||||||
|
if( _deleteIndex != null )
|
||||||
|
ImGui.OpenPopup( DialogDeleteMod );
|
||||||
|
|
||||||
|
var ret = ImGui.BeginPopupModal( DialogDeleteMod );
|
||||||
|
if( !ret )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if( _mod?.Mod == null )
|
||||||
|
{
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Text( "Are you sure you want to delete the following mod:" );
|
||||||
|
// todo: why the fuck does this become null??????
|
||||||
|
ImGui.Text( _mod?.Mod?.Meta?.Name );
|
||||||
|
|
||||||
|
if( ImGui.Button( ButtonYesDelete ) )
|
||||||
|
{
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
_base._plugin.ModManager.DeleteMod( _mod.Mod );
|
||||||
|
ClearSelection();
|
||||||
|
_base.ReloadMods();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if( ImGui.Button( ButtonNoDelete ) )
|
||||||
|
{
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
_deleteIndex = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
if (Mods == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Selector pane
|
||||||
|
ImGui.BeginGroup();
|
||||||
|
ImGui.PushStyleVar( ImGuiStyleVar.ItemSpacing, ZeroVector );
|
||||||
|
|
||||||
|
// Inlay selector list
|
||||||
|
ImGui.BeginChild( LabelSelectorList, new Vector2(SelectorPanelWidth, -ImGui.GetFrameHeightWithSpacing() ), true );
|
||||||
|
|
||||||
|
for( var modIndex = 0; modIndex < Mods.ModSettings.Count; modIndex++ )
|
||||||
|
{
|
||||||
|
var settings = Mods.ModSettings[ modIndex ];
|
||||||
|
|
||||||
|
var changedColour = false;
|
||||||
|
if( !settings.Enabled )
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor( ImGuiCol.Text, DisabledModColor );
|
||||||
|
changedColour = true;
|
||||||
|
}
|
||||||
|
else if( settings.Mod.FileConflicts.Any() )
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor( ImGuiCol.Text, ConflictingModColor );
|
||||||
|
changedColour = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
var selected = ImGui.Selectable(
|
||||||
|
$"id={modIndex} {settings.Mod.Meta.Name}",
|
||||||
|
modIndex == _index
|
||||||
|
);
|
||||||
|
#else
|
||||||
|
var selected = ImGui.Selectable( settings.Mod.Meta.Name, modIndex == _index );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if( changedColour )
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
|
if( selected )
|
||||||
|
SetSelection(modIndex, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
DrawModsSelectorButtons();
|
||||||
|
ImGui.EndGroup();
|
||||||
|
|
||||||
|
DrawDeleteModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModInfo Mod() => _mod;
|
||||||
|
|
||||||
|
private void SetSelection(int idx, ModInfo info)
|
||||||
|
{
|
||||||
|
_mod = info;
|
||||||
|
if (idx != _index)
|
||||||
|
_base._menu._installedTab._modPanel._details.ResetState();
|
||||||
|
_index = idx;
|
||||||
|
_deleteIndex = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetSelection(int idx)
|
||||||
|
{
|
||||||
|
if (idx >= (Mods?.ModSettings?.Count ?? 0))
|
||||||
|
idx = -1;
|
||||||
|
if (idx < 0)
|
||||||
|
SetSelection(0, null);
|
||||||
|
else
|
||||||
|
SetSelection(idx, Mods.ModSettings[idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearSelection() => SetSelection(-1);
|
||||||
|
|
||||||
|
public void SelectModByName( string name )
|
||||||
|
{
|
||||||
|
for( var modIndex = 0; modIndex < Mods.ModSettings.Count; modIndex++ )
|
||||||
|
{
|
||||||
|
var mod = Mods.ModSettings[ modIndex ];
|
||||||
|
|
||||||
|
if( mod.Mod.Meta.Name != name )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
SetSelection(modIndex, mod);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCurrentModMetaFile()
|
||||||
|
{
|
||||||
|
if( _mod == null )
|
||||||
|
return "";
|
||||||
|
return Path.Combine( _mod.Mod.ModBasePath.FullName, "meta.json" );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReloadCurrentMod()
|
||||||
|
{
|
||||||
|
var metaPath = GetCurrentModMetaFile();
|
||||||
|
if (metaPath.Length > 0 && File.Exists(metaPath))
|
||||||
|
{
|
||||||
|
_mod.Mod.Meta = ModMeta.LoadFromFile(metaPath) ?? _mod.Mod.Meta;
|
||||||
|
_base._menu._installedTab._modPanel._details.ResetState();
|
||||||
|
}
|
||||||
|
_mod.Mod.RefreshModFiles();
|
||||||
|
_base._plugin.ModManager.CalculateEffectiveFileList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SaveCurrentMod()
|
||||||
|
{
|
||||||
|
var metaPath = GetCurrentModMetaFile();
|
||||||
|
if (metaPath.Length > 0)
|
||||||
|
File.WriteAllText( metaPath, JsonConvert.SerializeObject( _mod.Mod.Meta, Formatting.Indented ) );
|
||||||
|
_base._menu._installedTab._modPanel._details.ResetState();
|
||||||
|
return metaPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
174
Penumbra/UI/TabSettings.cs
Normal file
174
Penumbra/UI/TabSettings.cs
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
namespace Penumbra.UI
|
||||||
|
{
|
||||||
|
public partial class SettingsInterface
|
||||||
|
{
|
||||||
|
private class TabSettings
|
||||||
|
{
|
||||||
|
private const string LabelTab = "Settings";
|
||||||
|
private const string LabelRootFolder = "Root Folder";
|
||||||
|
private const string LabelRediscoverButton = "Rediscover Mods";
|
||||||
|
private const string LabelOpenFolder = "Open Mods Folder";
|
||||||
|
private const string LabelEnabled = "Enable Mods";
|
||||||
|
private const string LabelInvertModOrder = "Invert mod load order (mods are loaded bottom up)";
|
||||||
|
private const string LabelShowAdvanced = "Show Advanced Settings";
|
||||||
|
private const string LabelLogLoadedFiles = "Log all loaded files";
|
||||||
|
private const string LabelDisableNotifications = "Disable filesystem change notifications";
|
||||||
|
private const string LabelEnableHttpApi = "Enable HTTP API";
|
||||||
|
private const string LabelReloadResource = "Reload Player Resource";
|
||||||
|
|
||||||
|
private readonly SettingsInterface _base;
|
||||||
|
private readonly Configuration _config;
|
||||||
|
private bool _configChanged;
|
||||||
|
|
||||||
|
public TabSettings(SettingsInterface ui)
|
||||||
|
{
|
||||||
|
_base = ui;
|
||||||
|
_config = _base._plugin.Configuration;
|
||||||
|
_configChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRootFolder()
|
||||||
|
{
|
||||||
|
var basePath = _config.CurrentCollection;
|
||||||
|
if( ImGui.InputText( LabelRootFolder, ref basePath, 255 ) && _config.CurrentCollection != basePath )
|
||||||
|
{
|
||||||
|
_config.CurrentCollection = basePath;
|
||||||
|
_configChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRediscoverButton()
|
||||||
|
{
|
||||||
|
if( ImGui.Button( LabelRediscoverButton ) )
|
||||||
|
{
|
||||||
|
_base.ReloadMods();
|
||||||
|
_base._menu._installedTab._selector.ClearSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawOpenModsButton()
|
||||||
|
{
|
||||||
|
if( ImGui.Button( LabelOpenFolder ) )
|
||||||
|
{
|
||||||
|
Process.Start( _config.CurrentCollection );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawEnabledBox()
|
||||||
|
{
|
||||||
|
var enabled = _config.IsEnabled;
|
||||||
|
if( ImGui.Checkbox( LabelEnabled, ref enabled ) )
|
||||||
|
{
|
||||||
|
_config.IsEnabled = enabled;
|
||||||
|
_configChanged = true;
|
||||||
|
RefreshActors.RedrawAll(_base._plugin.PluginInterface.ClientState.Actors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawInvertModOrderBox()
|
||||||
|
{
|
||||||
|
var invertOrder = _config.InvertModListOrder;
|
||||||
|
if( ImGui.Checkbox( LabelInvertModOrder, ref invertOrder ) )
|
||||||
|
{
|
||||||
|
_config.InvertModListOrder = invertOrder;
|
||||||
|
_base.ReloadMods();
|
||||||
|
_configChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawShowAdvancedBox()
|
||||||
|
{
|
||||||
|
var showAdvanced = _config.ShowAdvanced;
|
||||||
|
if( ImGui.Checkbox( LabelShowAdvanced, ref showAdvanced ) )
|
||||||
|
{
|
||||||
|
_config.ShowAdvanced = 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()
|
||||||
|
{
|
||||||
|
var fswatch = _config.DisableFileSystemNotifications;
|
||||||
|
if( ImGui.Checkbox( LabelDisableNotifications, ref fswatch ) )
|
||||||
|
{
|
||||||
|
_config.DisableFileSystemNotifications = fswatch;
|
||||||
|
_configChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawEnableHttpApiBox()
|
||||||
|
{
|
||||||
|
var http = _config.EnableHttpApi;
|
||||||
|
if( ImGui.Checkbox( LabelEnableHttpApi, ref http ) )
|
||||||
|
{
|
||||||
|
if( http )
|
||||||
|
_base._plugin.CreateWebServer();
|
||||||
|
else
|
||||||
|
_base._plugin.ShutdownWebServer();
|
||||||
|
|
||||||
|
_config.EnableHttpApi = http;
|
||||||
|
_configChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawReloadResourceButton()
|
||||||
|
{
|
||||||
|
if( ImGui.Button( LabelReloadResource ) )
|
||||||
|
{
|
||||||
|
_base._plugin.GameUtils.ReloadPlayerResources();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawAdvancedSettings()
|
||||||
|
{
|
||||||
|
DrawLogLoadedFilesBox();
|
||||||
|
DrawDisableNotificationsBox();
|
||||||
|
DrawEnableHttpApiBox();
|
||||||
|
DrawReloadResourceButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
var ret = ImGui.BeginTabItem( LabelTab );
|
||||||
|
if( !ret )
|
||||||
|
return;
|
||||||
|
|
||||||
|
DrawRootFolder();
|
||||||
|
|
||||||
|
DrawRediscoverButton();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawOpenModsButton();
|
||||||
|
|
||||||
|
ImGuiCustom.VerticalDistance(DefaultVerticalSpace);
|
||||||
|
DrawEnabledBox();
|
||||||
|
|
||||||
|
ImGuiCustom.VerticalDistance(DefaultVerticalSpace);
|
||||||
|
DrawInvertModOrderBox();
|
||||||
|
|
||||||
|
ImGuiCustom.VerticalDistance(DefaultVerticalSpace);
|
||||||
|
DrawShowAdvancedBox();
|
||||||
|
|
||||||
|
if( _config.ShowAdvanced )
|
||||||
|
DrawAdvancedSettings();
|
||||||
|
|
||||||
|
if( _configChanged )
|
||||||
|
{
|
||||||
|
_config.Save();
|
||||||
|
_configChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
Penumbra/Util/SingleOrArrayConverter.cs
Normal file
55
Penumbra/Util/SingleOrArrayConverter.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
public class SingleOrArrayConverter<T> : JsonConverter
|
||||||
|
{
|
||||||
|
public override bool CanConvert( Type objectType )
|
||||||
|
{
|
||||||
|
return (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>() };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanWrite => false;
|
||||||
|
|
||||||
|
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DictSingleOrArrayConverter<T,U> : JsonConverter
|
||||||
|
{
|
||||||
|
public override bool CanConvert( Type objectType )
|
||||||
|
{
|
||||||
|
return (objectType == typeof(Dictionary<T, HashSet<U>>));
|
||||||
|
}
|
||||||
|
|
||||||
|
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>() };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanWrite => false;
|
||||||
|
|
||||||
|
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue