Penumbra/Penumbra/UI/SettingsInterface.cs

871 lines
No EOL
30 KiB
C#

using System;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.IO;
using System.Linq;
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
{
public class SettingsInterface
{
private readonly Plugin _plugin;
public bool Visible;
public bool ShowDebugBar;
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 )
{
_plugin = plugin;
#if DEBUG
Visible = true;
ShowDebugBar = true;
#endif
}
public void Draw()
{
if( ShowDebugBar && ImGui.BeginMainMenuBar() )
{
if( ImGui.BeginMenu( "Penumbra" ) )
{
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.SetTooltip( _selectedMod.Mod.Meta.Website );
}
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 );
}
if( ImGui.IsItemHovered() )
ImGui.SetTooltip( "Open the directory containing this mod in your default file explorer." );
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 );
}
if( ImGui.IsItemHovered() )
ImGui.SetTooltip( "Open the JSON configuration file in your default application for .json." );
ImGui.SameLine();
if( ImGui.Button( "Reload JSON" ) )
{
ReloadMods();
}
if( ImGui.IsItemHovered() )
ImGui.SetTooltip( "Reload the configuration of all mods." );
ImGui.SameLine();
if( ImGui.Button( "Deduplicate" ) )
{
new Deduplicator(_selectedMod.Mod.ModBasePath, _selectedMod.Mod.Meta).Run();
var metaPath = Path.Combine( _selectedMod.Mod.ModBasePath.FullName, "meta.json" );
File.WriteAllText( metaPath, JsonConvert.SerializeObject( _selectedMod.Mod.Meta, Formatting.Indented ) );
ReloadMods();
}
if( ImGui.IsItemHovered() )
ImGui.SetTooltip( "Try to find identical files and remove duplicate occurences to reduce the mods disk size." );
}
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.HasGroupWithConfig) {
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()
{
_selectedMod = null;
// create the directory if it doesn't exist
Directory.CreateDirectory( _plugin.Configuration.CurrentCollection );
_plugin.ModManager.DiscoverMods( _plugin.Configuration.CurrentCollection );
// 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;
}
}
}
}