Complete refactoring of interface code. Addition of 'Enable Mods' checkmark, that just (de)activates all mods at once without changing settings. Allow editing of mod json inside the interface in advanced mode.

This commit is contained in:
Ottermandias 2021-01-27 14:14:35 +01:00
parent 5462bb6f05
commit 7e51e663ec
20 changed files with 2274 additions and 872 deletions

View file

@ -1,5 +1,8 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Linq;
using System.IO;
using System;
namespace Penumbra.Models
{
@ -22,5 +25,21 @@ namespace Penumbra.Models
[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
}
}
}
}

View file

@ -64,21 +64,7 @@ namespace Penumbra.Mods
continue;
}
ModMeta meta;
try
{
meta = JsonConvert.DeserializeObject< ModMeta >( File.ReadAllText( metaFile.FullName ) );
meta.HasGroupWithConfig = meta.Groups != null && meta.Groups.Count > 0
&& meta.Groups.Values.Any( G => G.SelectionType == SelectType.Multi || G.Options.Count > 1);
}
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 meta = ModMeta.LoadFromFile(metaFile.FullName);
var mod = new ResourceMod
{

View file

@ -109,6 +109,9 @@ namespace Penumbra.Mods
ResolvedFiles.Clear();
SwappedFiles.Clear();
if (!_plugin.Configuration.IsEnabled)
return;
var registeredFiles = new Dictionary< string, string >();
foreach( var (mod, settings) in Mods.GetOrderedAndEnabledModListWithSettings( _plugin.Configuration.InvertModListOrder ) )

View file

@ -124,7 +124,7 @@ namespace Penumbra
return;
}
SettingsInterface.Visible = !SettingsInterface.Visible;
SettingsInterface.FlipVisibility();
}
}
}

View 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();
}
}

View 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;
}
}
}

View 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
View 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);
}
}
}

View 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
View 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();
}
}
}
}
}

View file

@ -1,871 +1,46 @@
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
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;
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!;
private readonly LaunchButton _launchButton;
private readonly MenuBar _menuBar;
private readonly SettingsMenu _menu;
public SettingsInterface( Plugin plugin )
{
_plugin = plugin;
#if DEBUG
Visible = true;
ShowDebugBar = true;
#endif
_launchButton = new(this);
_menuBar = new(this);
_menu = new(this);
}
public void FlipVisibility() => _menu.Visible = !_menu.Visible;
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();
_menuBar.Draw();
_launchButton.Draw();
_menu.Draw();
}
private void ReloadMods()
{
_selectedMod = null;
_menu._installedTab._selector.ClearSelection();
// 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;
}
_menu._effectiveTab.RebuildFileList(_plugin.Configuration.ShowAdvanced);
}
}
}

View 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
View 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();
}
}
}
}

View 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();
}
}
}
}

126
Penumbra/UI/TabImport.cs Normal file
View file

@ -0,0 +1,126 @@
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 static readonly Vector2 ImportBarSize = new( -1, 0 );
private bool _isImportRunning = 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 )
{
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 ) );
}
catch( Exception ex )
{
PluginLog.LogError( ex, "Could not import one or more modpacks." );
}
PluginLog.Log( $"-> {fileName} OK!");
}
_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();
}
}
}
public void Draw()
{
var ret = ImGui.BeginTabItem( LabelTab );
if( !ret )
return;
if( !_isImportRunning )
DrawImportButton();
else
DrawImportProgress();
ImGui.EndTabItem();
}
}
}
}

View 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;
}
}
}
}

View 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();
}
}
}
}

View file

@ -0,0 +1,287 @@
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();
_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" );
}
}
}
}
}
}

View file

@ -0,0 +1,275 @@
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();
}
}
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
View 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;
_base.ReloadMods();
_configChanged = true;
}
}
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();
}
}
}
}