mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-01-03 14:23:43 +01:00
A lot of interface stuff, some more cleanup and fixes. Main functionality should be mostly fine, importing works. Missing a lot of mod edit options.
This commit is contained in:
parent
8dd681bdda
commit
dbb9931189
77 changed files with 3332 additions and 2066 deletions
|
|
@ -5,6 +5,7 @@ using System.Numerics;
|
|||
using System.Runtime.InteropServices;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Filesystem;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.Collections;
|
||||
|
|
@ -21,7 +22,7 @@ public partial class ModFileSystemSelector
|
|||
}
|
||||
|
||||
private const StringComparison IgnoreCase = StringComparison.InvariantCultureIgnoreCase;
|
||||
private readonly IReadOnlySet< Mod2 > _newMods = new HashSet< Mod2 >();
|
||||
private readonly IReadOnlySet< Mod > _newMods = new HashSet< Mod >();
|
||||
private LowerString _modFilter = LowerString.Empty;
|
||||
private int _filterType = -1;
|
||||
private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods;
|
||||
|
|
@ -75,7 +76,7 @@ public partial class ModFileSystemSelector
|
|||
// Folders have default state and are filtered out on the direct string instead of the other options.
|
||||
// If any filter is set, they should be hidden by default unless their children are visible,
|
||||
// or they contain the path search string.
|
||||
protected override bool ApplyFiltersAndState( FileSystem< Mod2 >.IPath path, out ModState state )
|
||||
protected override bool ApplyFiltersAndState( FileSystem< Mod >.IPath path, out ModState state )
|
||||
{
|
||||
if( path is ModFileSystem.Folder f )
|
||||
{
|
||||
|
|
@ -88,7 +89,7 @@ public partial class ModFileSystemSelector
|
|||
}
|
||||
|
||||
// Apply the string filters.
|
||||
private bool ApplyStringFilters( ModFileSystem.Leaf leaf, Mod2 mod )
|
||||
private bool ApplyStringFilters( ModFileSystem.Leaf leaf, Mod mod )
|
||||
{
|
||||
return _filterType switch
|
||||
{
|
||||
|
|
@ -102,7 +103,7 @@ public partial class ModFileSystemSelector
|
|||
}
|
||||
|
||||
// Only get the text color for a mod if no filters are set.
|
||||
private uint GetTextColor( Mod2 mod, ModSettings2? settings, ModCollection collection )
|
||||
private uint GetTextColor( Mod mod, ModSettings? settings, ModCollection collection )
|
||||
{
|
||||
if( _newMods.Contains( mod ) )
|
||||
{
|
||||
|
|
@ -119,7 +120,7 @@ public partial class ModFileSystemSelector
|
|||
return collection != Penumbra.CollectionManager.Current ? ColorId.InheritedDisabledMod.Value() : ColorId.DisabledMod.Value();
|
||||
}
|
||||
|
||||
var conflicts = Penumbra.CollectionManager.Current.ModConflicts( mod.Index ).ToList();
|
||||
var conflicts = Penumbra.CollectionManager.Current.ModConflicts( mod.Index );
|
||||
if( conflicts.Count == 0 )
|
||||
{
|
||||
return collection != Penumbra.CollectionManager.Current ? ColorId.InheritedMod.Value() : ColorId.EnabledMod.Value();
|
||||
|
|
@ -130,7 +131,7 @@ public partial class ModFileSystemSelector
|
|||
: ColorId.HandledConflictMod.Value();
|
||||
}
|
||||
|
||||
private bool CheckStateFilters( Mod2 mod, ModSettings2? settings, ModCollection collection, ref ModState state )
|
||||
private bool CheckStateFilters( Mod mod, ModSettings? settings, ModCollection collection, ref ModState state )
|
||||
{
|
||||
var isNew = _newMods.Contains( mod );
|
||||
// Handle mod details.
|
||||
|
|
@ -188,7 +189,7 @@ public partial class ModFileSystemSelector
|
|||
}
|
||||
|
||||
// Conflicts can only be relevant if the mod is enabled.
|
||||
var conflicts = Penumbra.CollectionManager.Current.ModConflicts( mod.Index ).ToList();
|
||||
var conflicts = Penumbra.CollectionManager.Current.ModConflicts( mod.Index );
|
||||
if( conflicts.Count > 0 )
|
||||
{
|
||||
if( conflicts.Any( c => !c.Solved ) )
|
||||
|
|
|
|||
|
|
@ -1,23 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.ImGuiFileDialog;
|
||||
using Dalamud.Logging;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Filesystem;
|
||||
using OtterGui.FileSystem.Selector;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Import;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.UI.Classes;
|
||||
|
||||
public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, ModFileSystemSelector.ModState >
|
||||
public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, ModFileSystemSelector.ModState >
|
||||
{
|
||||
public ModSettings2 SelectedSettings { get; private set; } = ModSettings2.Empty;
|
||||
private readonly FileDialogManager _fileManager = new();
|
||||
private TexToolsImporter? _import;
|
||||
public ModSettings SelectedSettings { get; private set; } = ModSettings.Empty;
|
||||
public ModCollection SelectedSettingCollection { get; private set; } = ModCollection.Empty;
|
||||
|
||||
public ModFileSystemSelector( ModFileSystem fileSystem, IReadOnlySet< Mod2 > newMods )
|
||||
public ModFileSystemSelector( ModFileSystem fileSystem, IReadOnlySet< Mod > newMods )
|
||||
: base( fileSystem )
|
||||
{
|
||||
_newMods = newMods;
|
||||
|
|
@ -26,6 +33,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo
|
|||
SubscribeRightClickFolder( InheritDescendants, 15 );
|
||||
SubscribeRightClickFolder( OwnDescendants, 15 );
|
||||
AddButton( AddNewModButton, 0 );
|
||||
AddButton( AddImportModButton, 1 );
|
||||
AddButton( DeleteModButton, 1000 );
|
||||
SetFilterTooltip();
|
||||
|
||||
|
|
@ -33,6 +41,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo
|
|||
Penumbra.CollectionManager.CollectionChanged += OnCollectionChange;
|
||||
Penumbra.CollectionManager.Current.ModSettingChanged += OnSettingChange;
|
||||
Penumbra.CollectionManager.Current.InheritanceChanged += OnInheritanceChange;
|
||||
Penumbra.ModManager.ModMetaChanged += OnModMetaChange;
|
||||
Penumbra.ModManager.ModDiscoveryStarted += StoreCurrentSelection;
|
||||
Penumbra.ModManager.ModDiscoveryFinished += RestoreLastSelection;
|
||||
OnCollectionChange( ModCollection.Type.Current, null, Penumbra.CollectionManager.Current, null );
|
||||
|
|
@ -43,6 +52,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo
|
|||
base.Dispose();
|
||||
Penumbra.ModManager.ModDiscoveryStarted -= StoreCurrentSelection;
|
||||
Penumbra.ModManager.ModDiscoveryFinished -= RestoreLastSelection;
|
||||
Penumbra.ModManager.ModMetaChanged -= OnModMetaChange;
|
||||
Penumbra.CollectionManager.Current.ModSettingChanged -= OnSettingChange;
|
||||
Penumbra.CollectionManager.Current.InheritanceChanged -= OnInheritanceChange;
|
||||
Penumbra.CollectionManager.CollectionChanged -= OnCollectionChange;
|
||||
|
|
@ -64,10 +74,11 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo
|
|||
protected override uint FolderLineColor
|
||||
=> ColorId.FolderLine.Value();
|
||||
|
||||
protected override void DrawLeafName( FileSystem< Mod2 >.Leaf leaf, in ModState state, bool selected )
|
||||
protected override void DrawLeafName( FileSystem< Mod >.Leaf leaf, in ModState state, bool selected )
|
||||
{
|
||||
var flags = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags;
|
||||
using var c = ImRaii.PushColor( ImGuiCol.Text, state.Color );
|
||||
using var id = ImRaii.PushId( leaf.Value.Index );
|
||||
using var _ = ImRaii.TreeNode( leaf.Value.Name, flags );
|
||||
}
|
||||
|
||||
|
|
@ -107,17 +118,90 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo
|
|||
|
||||
|
||||
// Add custom buttons.
|
||||
private static void AddNewModButton( Vector2 size )
|
||||
private string _newModName = string.Empty;
|
||||
|
||||
private void AddNewModButton( Vector2 size )
|
||||
{
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Plus.ToIconString(), size, "Create a new, empty mod of a given name.", false, true ) )
|
||||
{ }
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Plus.ToIconString(), size, "Create a new, empty mod of a given name.", !Penumbra.ModManager.Valid, true ) )
|
||||
{
|
||||
ImGui.OpenPopup( "Create New Mod" );
|
||||
}
|
||||
|
||||
if( ImGuiUtil.OpenNameField( "Create New Mod", ref _newModName ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
var newDir = Mod.CreateModFolder( Penumbra.ModManager.BasePath, _newModName );
|
||||
Mod.CreateMeta( newDir, _newModName, string.Empty, string.Empty, "1.0", string.Empty );
|
||||
Penumbra.ModManager.AddMod( newDir );
|
||||
_newModName = string.Empty;
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not create directory for new Mod {_newModName}:\n{e}" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add an import mods button that opens a file selector.
|
||||
private void AddImportModButton( Vector2 size )
|
||||
{
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.FileImport.ToIconString(), size,
|
||||
"Import one or multiple mods from Tex Tools Mod Pack Files.", !Penumbra.ModManager.Valid, true ) )
|
||||
{
|
||||
_fileManager.OpenFileDialog( "Import Mod Pack", "TexTools Mod Packs{.ttmp,.ttmp2}", ( s, f ) =>
|
||||
{
|
||||
if( s )
|
||||
{
|
||||
_import = new TexToolsImporter( Penumbra.ModManager.BasePath, f.Count, f.Select( file => new FileInfo( file ) ) );
|
||||
ImGui.OpenPopup( "Import Status" );
|
||||
}
|
||||
}, 0, Penumbra.Config.ModDirectory );
|
||||
}
|
||||
|
||||
_fileManager.Draw();
|
||||
DrawInfoPopup();
|
||||
}
|
||||
|
||||
// Draw the progress information for import.
|
||||
private void DrawInfoPopup()
|
||||
{
|
||||
var display = ImGui.GetIO().DisplaySize;
|
||||
ImGui.SetNextWindowSize( display / 4 );
|
||||
ImGui.SetNextWindowPos( 3 * display / 8 );
|
||||
using var popup = ImRaii.Popup( "Import Status", ImGuiWindowFlags.Modal );
|
||||
if( _import != null && popup.Success )
|
||||
{
|
||||
_import.DrawProgressInfo( ImGuiHelpers.ScaledVector2( -1, ImGui.GetFrameHeight() ) );
|
||||
if( _import.State == ImporterState.Done )
|
||||
{
|
||||
ImGui.SetCursorPosY( ImGui.GetWindowHeight() - ImGui.GetFrameHeight() * 2 );
|
||||
if( ImGui.Button( "Close", -Vector2.UnitX ) )
|
||||
{
|
||||
_import = null;
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteModButton( Vector2 size )
|
||||
{
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), size,
|
||||
"Delete the currently selected mod entirely from your drive.", SelectedLeaf == null, true ) )
|
||||
{ }
|
||||
var keys = ImGui.GetIO().KeyCtrl && ImGui.GetIO().KeyShift;
|
||||
var tt = SelectedLeaf == null
|
||||
? "No mod selected."
|
||||
: "Delete the currently selected mod entirely from your drive.\n"
|
||||
+ "This can not be undone.";
|
||||
if( !keys )
|
||||
{
|
||||
tt += "\nHold Control and Shift while clicking to delete the mod.";
|
||||
}
|
||||
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), size, tt, SelectedLeaf == null || !keys, true )
|
||||
&& Selected != null )
|
||||
{
|
||||
Penumbra.ModManager.DeleteMod( Selected.Index );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -146,6 +230,17 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo
|
|||
}
|
||||
}
|
||||
|
||||
private void OnModMetaChange( MetaChangeType type, Mod mod, string? oldName )
|
||||
{
|
||||
switch( type )
|
||||
{
|
||||
case MetaChangeType.Name:
|
||||
case MetaChangeType.Author:
|
||||
SetFilterDirty();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInheritanceChange( bool _ )
|
||||
{
|
||||
SetFilterDirty();
|
||||
|
|
@ -175,17 +270,17 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod2, Mo
|
|||
OnSelectionChange( Selected, Selected, default );
|
||||
}
|
||||
|
||||
private void OnSelectionChange( Mod2? _1, Mod2? newSelection, in ModState _2 )
|
||||
private void OnSelectionChange( Mod? _1, Mod? newSelection, in ModState _2 )
|
||||
{
|
||||
if( newSelection == null )
|
||||
{
|
||||
SelectedSettings = ModSettings2.Empty;
|
||||
SelectedSettings = ModSettings.Empty;
|
||||
SelectedSettingCollection = ModCollection.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
( var settings, SelectedSettingCollection ) = Penumbra.CollectionManager.Current[ newSelection.Index ];
|
||||
SelectedSettings = settings ?? ModSettings2.Empty;
|
||||
SelectedSettings = settings ?? ModSettings.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
225
Penumbra/UI/Classes/SubModEditWindow.cs
Normal file
225
Penumbra/UI/Classes/SubModEditWindow.cs
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.UI.Classes;
|
||||
|
||||
public class SubModEditWindow : Window
|
||||
{
|
||||
private const string WindowBaseLabel = "###SubModEdit";
|
||||
private Mod? _mod;
|
||||
private int _groupIdx = -1;
|
||||
private int _optionIdx = -1;
|
||||
private IModGroup? _group;
|
||||
private ISubMod? _subMod;
|
||||
private readonly List< FilePathInfo > _availableFiles = new();
|
||||
|
||||
private readonly struct FilePathInfo
|
||||
{
|
||||
public readonly FullPath File;
|
||||
public readonly Utf8RelPath RelFile;
|
||||
public readonly long Size;
|
||||
public readonly List< (int, int, Utf8GamePath) > SubMods;
|
||||
|
||||
public FilePathInfo( FileInfo file, Mod mod )
|
||||
{
|
||||
File = new FullPath( file );
|
||||
RelFile = Utf8RelPath.FromFile( File, mod.BasePath, out var f ) ? f : Utf8RelPath.Empty;
|
||||
Size = file.Length;
|
||||
SubMods = new List< (int, int, Utf8GamePath) >();
|
||||
var path = File;
|
||||
foreach( var (group, groupIdx) in mod.Groups.WithIndex() )
|
||||
{
|
||||
foreach( var (subMod, optionIdx) in group.WithIndex() )
|
||||
{
|
||||
SubMods.AddRange( subMod.Files.Where( kvp => kvp.Value.Equals( path ) ).Select( kvp => ( groupIdx, optionIdx, kvp.Key ) ) );
|
||||
}
|
||||
}
|
||||
SubMods.AddRange( mod.Default.Files.Where( kvp => kvp.Value.Equals( path ) ).Select( kvp => (-1, 0, kvp.Key) ) );
|
||||
}
|
||||
}
|
||||
|
||||
private readonly HashSet< MetaManipulation > _manipulations = new();
|
||||
private readonly Dictionary< Utf8GamePath, FullPath > _files = new();
|
||||
private readonly Dictionary< Utf8GamePath, FullPath > _fileSwaps = new();
|
||||
|
||||
public void Activate( Mod mod, int groupIdx, int optionIdx )
|
||||
{
|
||||
IsOpen = true;
|
||||
_mod = mod;
|
||||
_groupIdx = groupIdx;
|
||||
_group = groupIdx >= 0 ? mod.Groups[ groupIdx ] : null;
|
||||
_optionIdx = optionIdx;
|
||||
_subMod = groupIdx >= 0 ? _group![ optionIdx ] : _mod.Default;
|
||||
_availableFiles.Clear();
|
||||
_availableFiles.AddRange( mod.BasePath.EnumerateDirectories()
|
||||
.SelectMany( d => d.EnumerateFiles( "*.*", SearchOption.AllDirectories ) )
|
||||
.Select( f => new FilePathInfo( f, _mod ) ) );
|
||||
|
||||
_manipulations.Clear();
|
||||
_manipulations.UnionWith( _subMod.Manipulations );
|
||||
_files.SetTo( _subMod.Files );
|
||||
_fileSwaps.SetTo( _subMod.FileSwaps );
|
||||
|
||||
WindowName = $"{_mod.Name}: {(_group != null ? $"{_group.Name} - " : string.Empty)}{_subMod.Name}";
|
||||
}
|
||||
|
||||
public override bool DrawConditions()
|
||||
=> _subMod != null;
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
using var tabBar = ImRaii.TabBar( "##tabs" );
|
||||
if( !tabBar )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawFileTab();
|
||||
DrawMetaTab();
|
||||
DrawSwapTab();
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
if( _mod != null )
|
||||
{
|
||||
Penumbra.ModManager.OptionUpdate( _mod, _groupIdx, _optionIdx, _files, _manipulations, _fileSwaps );
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnClose()
|
||||
{
|
||||
_subMod = null;
|
||||
}
|
||||
|
||||
private void DrawFileTab()
|
||||
{
|
||||
using var tab = ImRaii.TabItem( "File Redirections" );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var list = ImRaii.Table( "##files", 3 );
|
||||
if( !list )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach( var file in _availableFiles )
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ConfigWindow.Text( file.RelFile.Path );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( file.Size.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
if( file.SubMods.Count == 0 )
|
||||
{
|
||||
ImGui.Text( "Unused" );
|
||||
}
|
||||
|
||||
foreach( var (groupIdx, optionIdx, gamePath) in file.SubMods )
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
var group = groupIdx >= 0 ? _mod!.Groups[ groupIdx ] : null;
|
||||
var option = groupIdx >= 0 ? group![ optionIdx ] : _mod!.Default;
|
||||
var text = groupIdx >= 0
|
||||
? $"{group!.Name} - {option.Name}"
|
||||
: option.Name;
|
||||
ImGui.Text( text );
|
||||
ImGui.TableNextColumn();
|
||||
ConfigWindow.Text( gamePath.Path );
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TableNextRow();
|
||||
foreach( var (gamePath, fullPath) in _files )
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ConfigWindow.Text( gamePath.Path );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( fullPath.FullName );
|
||||
ImGui.TableNextColumn();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMetaTab()
|
||||
{
|
||||
using var tab = ImRaii.TabItem( "Meta Manipulations" );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var list = ImRaii.Table( "##meta", 3 );
|
||||
if( !list )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach( var manip in _manipulations )
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( manip.ManipulationType.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( manip.ManipulationType switch
|
||||
{
|
||||
MetaManipulation.Type.Imc => manip.Imc.ToString(),
|
||||
MetaManipulation.Type.Eqdp => manip.Eqdp.ToString(),
|
||||
MetaManipulation.Type.Eqp => manip.Eqp.ToString(),
|
||||
MetaManipulation.Type.Est => manip.Est.ToString(),
|
||||
MetaManipulation.Type.Gmp => manip.Gmp.ToString(),
|
||||
MetaManipulation.Type.Rsp => manip.Rsp.ToString(),
|
||||
_ => string.Empty,
|
||||
} );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( manip.ManipulationType switch
|
||||
{
|
||||
MetaManipulation.Type.Imc => manip.Imc.Entry.ToString(),
|
||||
MetaManipulation.Type.Eqdp => manip.Eqdp.Entry.ToString(),
|
||||
MetaManipulation.Type.Eqp => manip.Eqp.Entry.ToString(),
|
||||
MetaManipulation.Type.Est => manip.Est.Entry.ToString(),
|
||||
MetaManipulation.Type.Gmp => manip.Gmp.Entry.ToString(),
|
||||
MetaManipulation.Type.Rsp => manip.Rsp.Entry.ToString(),
|
||||
_ => string.Empty,
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSwapTab()
|
||||
{
|
||||
using var tab = ImRaii.TabItem( "File Swaps" );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var list = ImRaii.Table( "##swaps", 3 );
|
||||
if( !list )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach( var (from, to) in _fileSwaps )
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ConfigWindow.Text( from.Path );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( to.FullName );
|
||||
ImGui.TableNextColumn();
|
||||
}
|
||||
}
|
||||
|
||||
public SubModEditWindow()
|
||||
: base( WindowBaseLabel )
|
||||
{ }
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Numerics;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Numerics;
|
|||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.ByteString;
|
||||
|
|
|
|||
444
Penumbra/UI/ConfigWindow.ModPanel.Edit.cs
Normal file
444
Penumbra/UI/ConfigWindow.ModPanel.Edit.cs
Normal file
|
|
@ -0,0 +1,444 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.Mods;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public partial class ConfigWindow
|
||||
{
|
||||
private partial class ModPanel
|
||||
{
|
||||
public readonly Queue< Action > _delayedActions = new();
|
||||
|
||||
private void DrawAddOptionGroupInput()
|
||||
{
|
||||
ImGui.SetNextItemWidth( _window._inputTextWidth.X );
|
||||
ImGui.InputTextWithHint( "##newGroup", "Add new option group...", ref _newGroupName, 256 );
|
||||
ImGui.SameLine();
|
||||
|
||||
var nameValid = Mod.Manager.VerifyFileName( _mod, null, _newGroupName, false );
|
||||
var tt = nameValid ? "Add new option group to the mod." : "Can not add a group of this name.";
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Plus.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
tt, !nameValid, true ) )
|
||||
{
|
||||
Penumbra.ModManager.AddModGroup( _mod, SelectType.Single, _newGroupName );
|
||||
_newGroupName = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 _cellPadding = Vector2.Zero;
|
||||
private Vector2 _itemSpacing = Vector2.Zero;
|
||||
|
||||
private void DrawEditModTab()
|
||||
{
|
||||
using var tab = DrawTab( EditModTabHeader, Tabs.Edit );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var child = ImRaii.Child( "##editChild", -Vector2.One );
|
||||
if( !child )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_cellPadding = ImGui.GetStyle().CellPadding with { X = 2 * ImGuiHelpers.GlobalScale };
|
||||
_itemSpacing = ImGui.GetStyle().CellPadding with { X = 4 * ImGuiHelpers.GlobalScale };
|
||||
|
||||
EditRegularMeta();
|
||||
ImGui.Dummy( _window._defaultSpace );
|
||||
|
||||
if( TextInput( "Mod Path", PathFieldIdx, NoFieldIdx, _leaf.FullName(), out var newPath, 256, _window._inputTextWidth.X ) )
|
||||
{
|
||||
_window._penumbra.ModFileSystem.RenameAndMove( _leaf, newPath );
|
||||
}
|
||||
|
||||
ImGui.Dummy( _window._defaultSpace );
|
||||
DrawAddOptionGroupInput();
|
||||
ImGui.Dummy( _window._defaultSpace );
|
||||
|
||||
for( var groupIdx = 0; groupIdx < _mod.Groups.Count; ++groupIdx )
|
||||
{
|
||||
EditGroup( groupIdx );
|
||||
}
|
||||
|
||||
EndActions();
|
||||
EditDescriptionPopup();
|
||||
}
|
||||
|
||||
|
||||
// Special field indices to reuse the same string buffer.
|
||||
private const int NoFieldIdx = -1;
|
||||
private const int NameFieldIdx = -2;
|
||||
private const int AuthorFieldIdx = -3;
|
||||
private const int VersionFieldIdx = -4;
|
||||
private const int WebsiteFieldIdx = -5;
|
||||
private const int PathFieldIdx = -6;
|
||||
private const int DescriptionFieldIdx = -7;
|
||||
|
||||
private void EditRegularMeta()
|
||||
{
|
||||
if( TextInput( "Name", NameFieldIdx, NoFieldIdx, _mod.Name, out var newName, 256, _window._inputTextWidth.X ) )
|
||||
{
|
||||
Penumbra.ModManager.ChangeModName( _mod.Index, newName );
|
||||
}
|
||||
|
||||
if( TextInput( "Author", AuthorFieldIdx, NoFieldIdx, _mod.Author, out var newAuthor, 256, _window._inputTextWidth.X ) )
|
||||
{
|
||||
Penumbra.ModManager.ChangeModAuthor( _mod.Index, newAuthor );
|
||||
}
|
||||
|
||||
if( TextInput( "Version", VersionFieldIdx, NoFieldIdx, _mod.Version, out var newVersion, 32, _window._inputTextWidth.X ) )
|
||||
{
|
||||
Penumbra.ModManager.ChangeModVersion( _mod.Index, newVersion );
|
||||
}
|
||||
|
||||
if( TextInput( "Website", WebsiteFieldIdx, NoFieldIdx, _mod.Website, out var newWebsite, 256, _window._inputTextWidth.X ) )
|
||||
{
|
||||
Penumbra.ModManager.ChangeModWebsite( _mod.Index, newWebsite );
|
||||
}
|
||||
|
||||
if( ImGui.Button( "Edit Description", _window._inputTextWidth ) )
|
||||
{
|
||||
_delayedActions.Enqueue( () => OpenEditDescriptionPopup( DescriptionFieldIdx ) );
|
||||
}
|
||||
|
||||
if( ImGui.Button( "Edit Default Mod", _window._inputTextWidth ) )
|
||||
{
|
||||
_window.SubModPopup.Activate( _mod, -1, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Temporary strings
|
||||
private string? _currentEdit;
|
||||
private int? _currentGroupPriority;
|
||||
private int _currentField = -1;
|
||||
private int _optionIndex = -1;
|
||||
|
||||
private string _newGroupName = string.Empty;
|
||||
private string _newOptionName = string.Empty;
|
||||
private string _newDescription = string.Empty;
|
||||
private int _newDescriptionIdx = -1;
|
||||
|
||||
private void EditGroup( int groupIdx )
|
||||
{
|
||||
var group = _mod.Groups[ groupIdx ];
|
||||
using var id = ImRaii.PushId( groupIdx );
|
||||
using var frame = ImRaii.FramedGroup( $"Group #{groupIdx + 1}" );
|
||||
|
||||
using var style = ImRaii.PushStyle( ImGuiStyleVar.CellPadding, _cellPadding )
|
||||
.Push( ImGuiStyleVar.ItemSpacing, _itemSpacing );
|
||||
|
||||
if( TextInput( "##Name", groupIdx, NoFieldIdx, group.Name, out var newGroupName, 256, _window._inputTextWidth.X ) )
|
||||
{
|
||||
Penumbra.ModManager.RenameModGroup( _mod, groupIdx, newGroupName );
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Group Name" );
|
||||
ImGui.SameLine();
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
"Delete this option group.\nHold Control while clicking to delete.", !ImGui.GetIO().KeyCtrl, true ) )
|
||||
{
|
||||
_delayedActions.Enqueue( () => Penumbra.ModManager.DeleteModGroup( _mod, groupIdx ) );
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Edit.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
"Edit group description.", false, true ) )
|
||||
{
|
||||
_delayedActions.Enqueue( () => OpenEditDescriptionPopup( groupIdx ) );
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if( PriorityInput( "##Priority", groupIdx, NoFieldIdx, group.Priority, out var priority, 50 * ImGuiHelpers.GlobalScale ) )
|
||||
{
|
||||
Penumbra.ModManager.ChangeGroupPriority( _mod, groupIdx, priority );
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Group Priority" );
|
||||
|
||||
ImGui.SetNextItemWidth( _window._inputTextWidth.X - 2 * ImGui.GetFrameHeight() - 8 * ImGuiHelpers.GlobalScale );
|
||||
using( var combo = ImRaii.Combo( "##GroupType", GroupTypeName( group.Type ) ) )
|
||||
{
|
||||
if( combo )
|
||||
{
|
||||
foreach( var type in new[] { SelectType.Single, SelectType.Multi } )
|
||||
{
|
||||
if( ImGui.Selectable( GroupTypeName( type ), group.Type == type ) )
|
||||
{
|
||||
Penumbra.ModManager.ChangeModGroupType( _mod, groupIdx, type );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
var tt = groupIdx == 0 ? "Can not move this group further upwards." : $"Move this group up to group {groupIdx}.";
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.ArrowUp.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
tt, groupIdx == 0, true ) )
|
||||
{
|
||||
_delayedActions.Enqueue( () => Penumbra.ModManager.MoveModGroup( _mod, groupIdx, groupIdx - 1 ) );
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
tt = groupIdx == _mod.Groups.Count - 1
|
||||
? "Can not move this group further downwards."
|
||||
: $"Move this group down to group {groupIdx + 2}.";
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.ArrowDown.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
tt, groupIdx == _mod.Groups.Count - 1, true ) )
|
||||
{
|
||||
_delayedActions.Enqueue( () => Penumbra.ModManager.MoveModGroup( _mod, groupIdx, groupIdx + 1 ) );
|
||||
}
|
||||
|
||||
ImGui.Dummy( _window._defaultSpace );
|
||||
|
||||
using var table = ImRaii.Table( string.Empty, 5, ImGuiTableFlags.SizingFixedFit );
|
||||
ImGui.TableSetupColumn( "idx", ImGuiTableColumnFlags.WidthFixed, 60 * ImGuiHelpers.GlobalScale );
|
||||
ImGui.TableSetupColumn( "name", ImGuiTableColumnFlags.WidthFixed, _window._inputTextWidth.X - 62 * ImGuiHelpers.GlobalScale );
|
||||
ImGui.TableSetupColumn( "delete", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight() );
|
||||
ImGui.TableSetupColumn( "edit", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight() );
|
||||
ImGui.TableSetupColumn( "priority", ImGuiTableColumnFlags.WidthFixed, 50 * ImGuiHelpers.GlobalScale );
|
||||
if( table )
|
||||
{
|
||||
for( var optionIdx = 0; optionIdx < group.Count; ++optionIdx )
|
||||
{
|
||||
EditOption( group, groupIdx, optionIdx );
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth( -1 );
|
||||
ImGui.InputTextWithHint( "##newOption", "Add new option...", ref _newOptionName, 256 );
|
||||
ImGui.TableNextColumn();
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Plus.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
"Add a new option to this group.", _newOptionName.Length == 0, true ) )
|
||||
{
|
||||
Penumbra.ModManager.AddOption( _mod, groupIdx, _newOptionName );
|
||||
_newOptionName = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GroupTypeName( SelectType type )
|
||||
=> type switch
|
||||
{
|
||||
SelectType.Single => "Single Group",
|
||||
SelectType.Multi => "Multi Group",
|
||||
_ => "Unknown",
|
||||
};
|
||||
|
||||
private int _dragDropGroupIdx = -1;
|
||||
private int _dragDropOptionIdx = -1;
|
||||
|
||||
private void OptionDragDrop( IModGroup group, int groupIdx, int optionIdx )
|
||||
{
|
||||
const string label = "##DragOption";
|
||||
using( var source = ImRaii.DragDropSource() )
|
||||
{
|
||||
if( source )
|
||||
{
|
||||
if( ImGui.SetDragDropPayload( label, IntPtr.Zero, 0 ) )
|
||||
{
|
||||
_dragDropGroupIdx = groupIdx;
|
||||
_dragDropOptionIdx = optionIdx;
|
||||
}
|
||||
|
||||
ImGui.Text( $"Dragging option {group[ optionIdx ].Name} from group {group.Name}..." );
|
||||
}
|
||||
}
|
||||
|
||||
using( var target = ImRaii.DragDropTarget() )
|
||||
{
|
||||
if( target.Success && ImGuiUtil.IsDropping( label ) )
|
||||
{
|
||||
if( _dragDropGroupIdx >= 0 && _dragDropOptionIdx >= 0 )
|
||||
{
|
||||
if( _dragDropGroupIdx == groupIdx )
|
||||
{
|
||||
// TODO
|
||||
Dalamud.Chat.Print(
|
||||
$"Dropped {_mod.Groups[ _dragDropGroupIdx ][ _dragDropOptionIdx ].Name} onto {_mod.Groups[ groupIdx ][ optionIdx ].Name}" );
|
||||
}
|
||||
else
|
||||
{
|
||||
Dalamud.Chat.Print(
|
||||
$"Dropped {_mod.Groups[ _dragDropGroupIdx ][ _dragDropOptionIdx ].Name} onto {_mod.Groups[ groupIdx ][ optionIdx ].Name}" );
|
||||
}
|
||||
}
|
||||
|
||||
_dragDropGroupIdx = -1;
|
||||
_dragDropOptionIdx = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EditOption( IModGroup group, int groupIdx, int optionIdx )
|
||||
{
|
||||
var option = group[ optionIdx ];
|
||||
using var id = ImRaii.PushId( optionIdx );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Selectable( $"Option #{optionIdx + 1}" );
|
||||
OptionDragDrop( group, groupIdx, optionIdx );
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if( TextInput( "##Name", groupIdx, optionIdx, option.Name, out var newOptionName, 256, -1 ) )
|
||||
{
|
||||
Penumbra.ModManager.RenameOption( _mod, groupIdx, optionIdx, newOptionName );
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
"Delete this option.\nHold Control while clicking to delete.", !ImGui.GetIO().KeyCtrl, true ) )
|
||||
{
|
||||
_delayedActions.Enqueue( () => Penumbra.ModManager.DeleteOption( _mod, groupIdx, optionIdx ) );
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Edit.ToIconString(), ImGui.GetFrameHeight() * Vector2.One,
|
||||
"Edit this option.", false, true ) )
|
||||
{
|
||||
_window.SubModPopup.Activate( _mod, groupIdx, optionIdx );
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if( group.Type == SelectType.Multi )
|
||||
{
|
||||
if( PriorityInput( "##Priority", groupIdx, optionIdx, group.OptionPriority( optionIdx ), out var priority,
|
||||
50 * ImGuiHelpers.GlobalScale ) )
|
||||
{
|
||||
Penumbra.ModManager.ChangeOptionPriority( _mod, groupIdx, optionIdx, priority );
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Option priority." );
|
||||
}
|
||||
}
|
||||
|
||||
private bool TextInput( string label, int field, int option, string oldValue, out string value, uint maxLength, float width )
|
||||
{
|
||||
var tmp = field == _currentField && option == _optionIndex ? _currentEdit ?? oldValue : oldValue;
|
||||
ImGui.SetNextItemWidth( width );
|
||||
if( ImGui.InputText( label, ref tmp, maxLength ) )
|
||||
{
|
||||
_currentEdit = tmp;
|
||||
_optionIndex = option;
|
||||
_currentField = field;
|
||||
}
|
||||
|
||||
if( ImGui.IsItemDeactivatedAfterEdit() && _currentEdit != null )
|
||||
{
|
||||
var ret = _currentEdit != oldValue;
|
||||
value = _currentEdit;
|
||||
_currentEdit = null;
|
||||
_currentField = NoFieldIdx;
|
||||
_optionIndex = NoFieldIdx;
|
||||
return ret;
|
||||
}
|
||||
|
||||
value = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool PriorityInput( string label, int field, int option, int oldValue, out int value, float width )
|
||||
{
|
||||
var tmp = field == _currentField && option == _optionIndex ? _currentGroupPriority ?? oldValue : oldValue;
|
||||
ImGui.SetNextItemWidth( width );
|
||||
if( ImGui.InputInt( label, ref tmp, 0, 0 ) )
|
||||
{
|
||||
_currentGroupPriority = tmp;
|
||||
_optionIndex = option;
|
||||
_currentField = field;
|
||||
}
|
||||
|
||||
if( ImGui.IsItemDeactivatedAfterEdit() && _currentGroupPriority != null )
|
||||
{
|
||||
var ret = _currentGroupPriority != oldValue;
|
||||
value = _currentGroupPriority.Value;
|
||||
_currentGroupPriority = null;
|
||||
_currentField = NoFieldIdx;
|
||||
_optionIndex = NoFieldIdx;
|
||||
return ret;
|
||||
}
|
||||
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete a marked group or option outside of iteration.
|
||||
private void EndActions()
|
||||
{
|
||||
while( _delayedActions.TryDequeue( out var action ) )
|
||||
{
|
||||
action.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenEditDescriptionPopup( int groupIdx )
|
||||
{
|
||||
_newDescriptionIdx = groupIdx;
|
||||
_newDescription = groupIdx < 0 ? _mod.Description : _mod.Groups[ groupIdx ].Description;
|
||||
ImGui.OpenPopup( "Edit Description" );
|
||||
}
|
||||
|
||||
private void EditDescriptionPopup()
|
||||
{
|
||||
using var popup = ImRaii.Popup( "Edit Description" );
|
||||
if( popup )
|
||||
{
|
||||
if( ImGui.IsWindowAppearing() )
|
||||
{
|
||||
ImGui.SetKeyboardFocusHere();
|
||||
}
|
||||
|
||||
ImGui.InputTextMultiline( "##editDescription", ref _newDescription, 4096, ImGuiHelpers.ScaledVector2( 800, 800 ) );
|
||||
ImGui.Dummy( _window._defaultSpace );
|
||||
|
||||
var buttonSize = ImGuiHelpers.ScaledVector2( 100, 0 );
|
||||
var width = 2 * buttonSize.X
|
||||
+ 4 * ImGui.GetStyle().FramePadding.X
|
||||
+ ImGui.GetStyle().ItemSpacing.X;
|
||||
ImGui.SetCursorPosX( ( 800 * ImGuiHelpers.GlobalScale - width ) / 2 );
|
||||
|
||||
var oldDescription = _newDescriptionIdx == DescriptionFieldIdx
|
||||
? _mod.Description
|
||||
: _mod.Groups[ _newDescriptionIdx ].Description;
|
||||
|
||||
var tooltip = _newDescription != oldDescription ? string.Empty : "No changes made yet.";
|
||||
|
||||
if( ImGuiUtil.DrawDisabledButton( "Save", buttonSize, tooltip, tooltip.Length > 0 ) )
|
||||
{
|
||||
if( _newDescriptionIdx == DescriptionFieldIdx )
|
||||
{
|
||||
Penumbra.ModManager.ChangeModDescription( _mod.Index, _newDescription );
|
||||
}
|
||||
else if( _newDescriptionIdx >= 0 )
|
||||
{
|
||||
Penumbra.ModManager.ChangeGroupDescription( _mod, _newDescriptionIdx, _newDescription );
|
||||
}
|
||||
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if( ImGui.Button( "Cancel", buttonSize )
|
||||
|| ImGui.IsKeyPressed( ImGui.GetKeyIndex( ImGuiKey.Escape ) ) )
|
||||
{
|
||||
_newDescriptionIdx = NoFieldIdx;
|
||||
_newDescription = string.Empty;
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
214
Penumbra/UI/ConfigWindow.ModPanel.Header.cs
Normal file
214
Penumbra/UI/ConfigWindow.ModPanel.Header.cs
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.UI.Classes;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public partial class ConfigWindow
|
||||
{
|
||||
private partial class ModPanel : IDisposable
|
||||
{
|
||||
// We use a big, nice game font for the title.
|
||||
private readonly GameFontHandle _nameFont =
|
||||
Dalamud.PluginInterface.UiBuilder.GetGameFontHandle( new GameFontStyle( GameFontFamilyAndSize.Jupiter23 ) );
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_nameFont.Dispose();
|
||||
}
|
||||
|
||||
// Header data.
|
||||
private string _modName = string.Empty;
|
||||
private string _modAuthor = string.Empty;
|
||||
private string _modVersion = string.Empty;
|
||||
private string _modWebsite = string.Empty;
|
||||
private string _modWebsiteButton = string.Empty;
|
||||
private bool _websiteValid;
|
||||
|
||||
private float _modNameWidth;
|
||||
private float _modAuthorWidth;
|
||||
private float _modVersionWidth;
|
||||
private float _modWebsiteButtonWidth;
|
||||
private float _secondRowWidth;
|
||||
|
||||
// Draw the header for the current mod,
|
||||
// consisting of its name, version, author and website, if they exist.
|
||||
private void DrawModHeader()
|
||||
{
|
||||
var offset = DrawModName();
|
||||
DrawVersion( offset );
|
||||
DrawSecondRow( offset );
|
||||
}
|
||||
|
||||
// Draw the mod name in the game font with a 2px border, centered,
|
||||
// with at least the width of the version space to each side.
|
||||
private float DrawModName()
|
||||
{
|
||||
var decidingWidth = Math.Max( _secondRowWidth, ImGui.GetWindowWidth() );
|
||||
var offsetWidth = ( decidingWidth - _modNameWidth ) / 2;
|
||||
var offsetVersion = _modVersion.Length > 0
|
||||
? _modVersionWidth + ImGui.GetStyle().ItemSpacing.X + ImGui.GetStyle().WindowPadding.X
|
||||
: 0;
|
||||
var offset = Math.Max( offsetWidth, offsetVersion );
|
||||
if( offset > 0 )
|
||||
{
|
||||
ImGui.SetCursorPosX( offset );
|
||||
}
|
||||
|
||||
using var color = ImRaii.PushColor( ImGuiCol.Border, Colors.MetaInfoText );
|
||||
using var style = ImRaii.PushStyle( ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale );
|
||||
using var font = ImRaii.PushFont( _nameFont.ImFont, _nameFont.Available );
|
||||
ImGuiUtil.DrawTextButton( _modName, Vector2.Zero, 0 );
|
||||
return offset;
|
||||
}
|
||||
|
||||
// Draw the version in the top-right corner.
|
||||
private void DrawVersion( float offset )
|
||||
{
|
||||
var oldPos = ImGui.GetCursorPos();
|
||||
ImGui.SetCursorPos( new Vector2( 2 * offset + _modNameWidth - _modVersionWidth - ImGui.GetStyle().WindowPadding.X,
|
||||
ImGui.GetStyle().FramePadding.Y ) );
|
||||
ImGuiUtil.TextColored( Colors.MetaInfoText, _modVersion );
|
||||
ImGui.SetCursorPos( oldPos );
|
||||
}
|
||||
|
||||
// Draw author and website if they exist. The website is a button if it is valid.
|
||||
// Usually, author begins at the left boundary of the name,
|
||||
// and website ends at the right boundary of the name.
|
||||
// If their combined width is larger than the name, they are combined-centered.
|
||||
private void DrawSecondRow( float offset )
|
||||
{
|
||||
if( _modAuthor.Length == 0 )
|
||||
{
|
||||
if( _modWebsiteButton.Length == 0 )
|
||||
{
|
||||
ImGui.NewLine();
|
||||
return;
|
||||
}
|
||||
|
||||
offset += ( _modNameWidth - _modWebsiteButtonWidth ) / 2;
|
||||
ImGui.SetCursorPosX( offset );
|
||||
DrawWebsite();
|
||||
}
|
||||
else if( _modWebsiteButton.Length == 0 )
|
||||
{
|
||||
offset += ( _modNameWidth - _modAuthorWidth ) / 2;
|
||||
ImGui.SetCursorPosX( offset );
|
||||
DrawAuthor();
|
||||
}
|
||||
else if( _secondRowWidth < _modNameWidth )
|
||||
{
|
||||
ImGui.SetCursorPosX( offset );
|
||||
DrawAuthor();
|
||||
ImGui.SameLine( offset + _modNameWidth - _modWebsiteButtonWidth );
|
||||
DrawWebsite();
|
||||
}
|
||||
else
|
||||
{
|
||||
offset -= ( _secondRowWidth - _modNameWidth ) / 2;
|
||||
if( offset > 0 )
|
||||
{
|
||||
ImGui.SetCursorPosX( offset );
|
||||
}
|
||||
|
||||
DrawAuthor();
|
||||
ImGui.SameLine();
|
||||
DrawWebsite();
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the author text.
|
||||
private void DrawAuthor()
|
||||
{
|
||||
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, Vector2.Zero );
|
||||
ImGuiUtil.TextColored( Colors.MetaInfoText, "by " );
|
||||
ImGui.SameLine();
|
||||
style.Pop();
|
||||
ImGui.Text( _mod.Author );
|
||||
}
|
||||
|
||||
// Draw either a website button if the source is a valid website address,
|
||||
// or a source text if it is not.
|
||||
private void DrawWebsite()
|
||||
{
|
||||
if( _websiteValid )
|
||||
{
|
||||
if( ImGui.SmallButton( _modWebsiteButton ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
var process = new ProcessStartInfo( _modWebsite )
|
||||
{
|
||||
UseShellExecute = true,
|
||||
};
|
||||
Process.Start( process );
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( _modWebsite );
|
||||
}
|
||||
else
|
||||
{
|
||||
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, Vector2.Zero );
|
||||
ImGuiUtil.TextColored( Colors.MetaInfoText, "from " );
|
||||
ImGui.SameLine();
|
||||
style.Pop();
|
||||
ImGui.Text( _mod.Website );
|
||||
}
|
||||
}
|
||||
|
||||
// Update all mod header data. Should someone change frame padding or item spacing,
|
||||
// or his default font, this will break, but he will just have to select a different mod to restore.
|
||||
private void UpdateModData()
|
||||
{
|
||||
// Name
|
||||
var name = $" {_mod.Name} ";
|
||||
if( name != _modName )
|
||||
{
|
||||
using var font = ImRaii.PushFont( _nameFont.ImFont, _nameFont.Available );
|
||||
_modName = name;
|
||||
_modNameWidth = ImGui.CalcTextSize( name ).X + 2 * ( ImGui.GetStyle().FramePadding.X + 2 * ImGuiHelpers.GlobalScale );
|
||||
}
|
||||
|
||||
// Author
|
||||
var author = _mod.Author.IsEmpty ? string.Empty : $"by {_mod.Author}";
|
||||
if( author != _modAuthor )
|
||||
{
|
||||
_modAuthor = author;
|
||||
_modAuthorWidth = ImGui.CalcTextSize( author ).X;
|
||||
_secondRowWidth = _modAuthorWidth + _modWebsiteButtonWidth + ImGui.GetStyle().ItemSpacing.X;
|
||||
}
|
||||
|
||||
// Version
|
||||
var version = _mod.Version.Length > 0 ? $"({_mod.Version})" : string.Empty;
|
||||
if( version != _modVersion )
|
||||
{
|
||||
_modVersion = version;
|
||||
_modVersionWidth = ImGui.CalcTextSize( version ).X;
|
||||
}
|
||||
|
||||
// Website
|
||||
if( _modWebsite != _mod.Website )
|
||||
{
|
||||
_modWebsite = _mod.Website;
|
||||
_websiteValid = Uri.TryCreate( _modWebsite, UriKind.Absolute, out var uriResult )
|
||||
&& ( uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp );
|
||||
_modWebsiteButton = _websiteValid ? "Open Website" : _modWebsite.Length == 0 ? string.Empty : $"from {_modWebsite}";
|
||||
_modWebsiteButtonWidth = _websiteValid
|
||||
? ImGui.CalcTextSize( _modWebsiteButton ).X + 2 * ImGui.GetStyle().FramePadding.X
|
||||
: ImGui.CalcTextSize( _modWebsiteButton ).X;
|
||||
_secondRowWidth = _modAuthorWidth + _modWebsiteButtonWidth + ImGui.GetStyle().ItemSpacing.X;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
207
Penumbra/UI/ConfigWindow.ModPanel.Settings.cs
Normal file
207
Penumbra/UI/ConfigWindow.ModPanel.Settings.cs
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.UI.Classes;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public partial class ConfigWindow
|
||||
{
|
||||
private partial class ModPanel
|
||||
{
|
||||
private ModSettings _settings = null!;
|
||||
private ModCollection _collection = null!;
|
||||
private bool _emptySetting;
|
||||
private bool _inherited;
|
||||
private SubList< ConflictCache.Conflict > _conflicts = SubList< ConflictCache.Conflict >.Empty;
|
||||
|
||||
private int? _currentPriority;
|
||||
|
||||
private void UpdateSettingsData( ModFileSystemSelector selector )
|
||||
{
|
||||
_settings = selector.SelectedSettings;
|
||||
_collection = selector.SelectedSettingCollection;
|
||||
_emptySetting = _settings == ModSettings.Empty;
|
||||
_inherited = _collection != Penumbra.CollectionManager.Current;
|
||||
_conflicts = Penumbra.CollectionManager.Current.ModConflicts( _mod.Index );
|
||||
}
|
||||
|
||||
// Draw the whole settings tab as well as its contents.
|
||||
private void DrawSettingsTab()
|
||||
{
|
||||
using var tab = DrawTab( SettingsTabHeader, Tabs.Settings );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var child = ImRaii.Child( "##settings" );
|
||||
if( !child )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawInheritedWarning();
|
||||
ImGui.Dummy( _window._defaultSpace );
|
||||
DrawEnabledInput();
|
||||
ImGui.SameLine();
|
||||
DrawPriorityInput();
|
||||
DrawRemoveSettings();
|
||||
ImGui.Dummy( _window._defaultSpace );
|
||||
for( var idx = 0; idx < _mod.Groups.Count; ++idx )
|
||||
{
|
||||
DrawSingleGroup( _mod.Groups[ idx ], idx );
|
||||
}
|
||||
|
||||
for( var idx = 0; idx < _mod.Groups.Count; ++idx )
|
||||
{
|
||||
DrawMultiGroup( _mod.Groups[ idx ], idx );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draw a big red bar if the current setting is inherited.
|
||||
private void DrawInheritedWarning()
|
||||
{
|
||||
if( !_inherited )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var color = ImRaii.PushColor( ImGuiCol.Button, Colors.PressEnterWarningBg );
|
||||
var width = new Vector2( ImGui.GetContentRegionAvail().X, 0 );
|
||||
if( ImGui.Button( $"These settings are inherited from {_collection.Name}.", width ) )
|
||||
{
|
||||
Penumbra.CollectionManager.Current.SetModInheritance( _mod.Index, false );
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "You can click this button to copy the current settings to the current selection.\n"
|
||||
+ "You can also just change any setting, which will copy the settings with the single setting changed to the current selection." );
|
||||
}
|
||||
|
||||
// Draw a checkbox for the enabled status of the mod.
|
||||
private void DrawEnabledInput()
|
||||
{
|
||||
var enabled = _settings.Enabled;
|
||||
if( ImGui.Checkbox( "Enabled", ref enabled ) )
|
||||
{
|
||||
Penumbra.CollectionManager.Current.SetModState( _mod.Index, enabled );
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a priority input.
|
||||
// Priority is changed on deactivation of the input box.
|
||||
private void DrawPriorityInput()
|
||||
{
|
||||
var priority = _currentPriority ?? _settings.Priority;
|
||||
ImGui.SetNextItemWidth( 50 * ImGuiHelpers.GlobalScale );
|
||||
if( ImGui.InputInt( "##Priority", ref priority, 0, 0 ) )
|
||||
{
|
||||
_currentPriority = priority;
|
||||
}
|
||||
|
||||
if( ImGui.IsItemDeactivatedAfterEdit() && _currentPriority.HasValue )
|
||||
{
|
||||
if( _currentPriority != _settings.Priority )
|
||||
{
|
||||
Penumbra.CollectionManager.Current.SetModPriority( _mod.Index, _currentPriority.Value );
|
||||
}
|
||||
|
||||
_currentPriority = null;
|
||||
}
|
||||
|
||||
ImGuiUtil.LabeledHelpMarker( "Priority", "Mods with higher priority take precedence before Mods with lower priority.\n"
|
||||
+ "That means, if Mod A should overwrite changes from Mod B, Mod A should have higher priority than Mod B." );
|
||||
}
|
||||
|
||||
// Draw a button to remove the current settings and inherit them instead
|
||||
// on the top-right corner of the window/tab.
|
||||
private void DrawRemoveSettings()
|
||||
{
|
||||
const string text = "Remove Settings";
|
||||
if( _inherited || _emptySetting )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scroll = ImGui.GetScrollMaxY() > 0 ? ImGui.GetStyle().ScrollbarSize : 0;
|
||||
ImGui.SameLine( ImGui.GetWindowWidth() - ImGui.CalcTextSize( text ).X - ImGui.GetStyle().FramePadding.X * 2 - scroll);
|
||||
if( ImGui.Button( text ) )
|
||||
{
|
||||
Penumbra.CollectionManager.Current.SetModInheritance( _mod.Index, true );
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Remove current settings from this collection so that it can inherit them.\n"
|
||||
+ "If no inherited collection has settings for this mod, it will be disabled." );
|
||||
}
|
||||
|
||||
// Draw a single group selector as a combo box.
|
||||
// If a description is provided, add a help marker besides it.
|
||||
private void DrawSingleGroup( IModGroup group, int groupIdx )
|
||||
{
|
||||
if( group.Type != SelectType.Single || !group.IsOption )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var id = ImRaii.PushId( groupIdx );
|
||||
var selectedOption = _emptySetting ? 0 : ( int )_settings.Settings[ groupIdx ];
|
||||
ImGui.SetNextItemWidth( _window._inputTextWidth.X * 3 / 4 );
|
||||
using var combo = ImRaii.Combo( string.Empty, group[ selectedOption ].Name );
|
||||
if( combo )
|
||||
{
|
||||
for( var idx2 = 0; idx2 < group.Count; ++idx2 )
|
||||
{
|
||||
if( ImGui.Selectable( group[ idx2 ].Name, idx2 == selectedOption ) )
|
||||
{
|
||||
Penumbra.CollectionManager.Current.SetModSetting( _mod.Index, groupIdx, ( uint )idx2 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
combo.Dispose();
|
||||
ImGui.SameLine();
|
||||
if( group.Description.Length > 0 )
|
||||
{
|
||||
ImGuiUtil.LabeledHelpMarker( group.Name, group.Description );
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text( group.Name );
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a multi group selector as a bordered set of checkboxes.
|
||||
// If a description is provided, add a help marker in the title.
|
||||
private void DrawMultiGroup( IModGroup group, int groupIdx )
|
||||
{
|
||||
if( group.Type != SelectType.Multi || !group.IsOption )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var id = ImRaii.PushId( groupIdx );
|
||||
var flags = _emptySetting ? 0u : _settings.Settings[ groupIdx ];
|
||||
Widget.BeginFramedGroup( group.Name, group.Description );
|
||||
for( var idx2 = 0; idx2 < group.Count; ++idx2 )
|
||||
{
|
||||
var flag = 1u << idx2;
|
||||
var setting = ( flags & flag ) != 0;
|
||||
if( ImGui.Checkbox( group[ idx2 ].Name, ref setting ) )
|
||||
{
|
||||
flags = setting ? flags | flag : flags & ~flag;
|
||||
Penumbra.CollectionManager.Current.SetModSetting( _mod.Index, groupIdx, flags );
|
||||
}
|
||||
}
|
||||
|
||||
Widget.EndFramedGroup();
|
||||
}
|
||||
}
|
||||
}
|
||||
178
Penumbra/UI/ConfigWindow.ModPanel.Tabs.cs
Normal file
178
Penumbra/UI/ConfigWindow.ModPanel.Tabs.cs
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
using System;
|
||||
using System.ComponentModel.Design;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.UI.Classes;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public partial class ConfigWindow
|
||||
{
|
||||
private partial class ModPanel
|
||||
{
|
||||
[Flags]
|
||||
private enum Tabs
|
||||
{
|
||||
Description = 0x01,
|
||||
Settings = 0x02,
|
||||
ChangedItems = 0x04,
|
||||
Conflicts = 0x08,
|
||||
Edit = 0x10,
|
||||
};
|
||||
|
||||
// We want to keep the preferred tab selected even if switching through mods.
|
||||
private Tabs _preferredTab = Tabs.Settings;
|
||||
private Tabs _availableTabs = 0;
|
||||
|
||||
// Required to use tabs that can not be closed but have a flag to set them open.
|
||||
private static readonly Utf8String ConflictTabHeader = Utf8String.FromStringUnsafe( "Conflicts", false );
|
||||
private static readonly Utf8String DescriptionTabHeader = Utf8String.FromStringUnsafe( "Description", false );
|
||||
private static readonly Utf8String SettingsTabHeader = Utf8String.FromStringUnsafe( "Settings", false );
|
||||
private static readonly Utf8String ChangedItemsTabHeader = Utf8String.FromStringUnsafe( "Changed Items", false );
|
||||
private static readonly Utf8String EditModTabHeader = Utf8String.FromStringUnsafe( "Edit Mod", false );
|
||||
|
||||
private void DrawTabBar()
|
||||
{
|
||||
ImGui.Dummy( _window._defaultSpace );
|
||||
using var tabBar = ImRaii.TabBar( "##ModTabs" );
|
||||
if( !tabBar )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_availableTabs = Tabs.Settings
|
||||
| ( _mod.ChangedItems.Count > 0 ? Tabs.ChangedItems : 0 )
|
||||
| ( _mod.Description.Length > 0 ? Tabs.Description : 0 )
|
||||
| ( _conflicts.Count > 0 ? Tabs.Conflicts : 0 )
|
||||
| ( Penumbra.Config.ShowAdvanced ? Tabs.Edit : 0 );
|
||||
|
||||
DrawSettingsTab();
|
||||
DrawDescriptionTab();
|
||||
DrawChangedItemsTab();
|
||||
DrawConflictsTab();
|
||||
DrawEditModTab();
|
||||
}
|
||||
|
||||
// Just a simple text box with the wrapped description, if it exists.
|
||||
private void DrawDescriptionTab()
|
||||
{
|
||||
using var tab = DrawTab( DescriptionTabHeader, Tabs.Description );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var child = ImRaii.Child( "##description" );
|
||||
if( !child )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.TextWrapped( _mod.Description );
|
||||
}
|
||||
|
||||
// A simple clipped list of changed items.
|
||||
private void DrawChangedItemsTab()
|
||||
{
|
||||
using var tab = DrawTab( ChangedItemsTabHeader, Tabs.ChangedItems );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var list = ImRaii.ListBox( "##changedItems", -Vector2.One );
|
||||
if( !list )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var zipList = ZipList.FromSortedList( _mod.ChangedItems );
|
||||
var height = ImGui.GetTextLineHeight();
|
||||
ImGuiClip.ClippedDraw( zipList, kvp => _window.DrawChangedItem( kvp.Item1, kvp.Item2 ), height );
|
||||
}
|
||||
|
||||
// If any conflicts exist, show them in this tab.
|
||||
private void DrawConflictsTab()
|
||||
{
|
||||
using var tab = DrawTab( ConflictTabHeader, Tabs.Conflicts );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var box = ImRaii.ListBox( "##conflicts" );
|
||||
if( !box )
|
||||
{
|
||||
return;
|
||||
}
|
||||
var conflicts = Penumbra.CollectionManager.Current.ModConflicts( _mod.Index );
|
||||
Mod? oldBadMod = null;
|
||||
using var indent = ImRaii.PushIndent( 0f );
|
||||
foreach( var conflict in conflicts )
|
||||
{
|
||||
var badMod = Penumbra.ModManager[ conflict.Mod2 ];
|
||||
if( badMod != oldBadMod )
|
||||
{
|
||||
if( oldBadMod != null )
|
||||
{
|
||||
indent.Pop( 30f );
|
||||
}
|
||||
|
||||
if( ImGui.Selectable( badMod.Name ) )
|
||||
{
|
||||
_window._selector.SelectByValue( badMod );
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using var color = ImRaii.PushColor( ImGuiCol.Text, conflict.Mod1Priority ? ColorId.HandledConflictMod.Value() : ColorId.ConflictingMod.Value() );
|
||||
ImGui.Text( $"(Priority {Penumbra.CollectionManager.Current[ conflict.Mod2 ].Settings!.Priority})" );
|
||||
|
||||
indent.Push( 30f );
|
||||
}
|
||||
|
||||
if( conflict.Data is Utf8GamePath p )
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
ImGuiNative.igSelectable_Bool( p.Path.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero );
|
||||
}
|
||||
}
|
||||
else if( conflict.Data is MetaManipulation m )
|
||||
{
|
||||
ImGui.Selectable( m.Manipulation?.ToString() ?? string.Empty );
|
||||
}
|
||||
|
||||
oldBadMod = badMod;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draw a tab by given name if it is available, and deal with changing the preferred tab.
|
||||
private ImRaii.IEndObject DrawTab( Utf8String name, Tabs flag )
|
||||
{
|
||||
if( !_availableTabs.HasFlag( flag ) )
|
||||
{
|
||||
return ImRaii.IEndObject.Empty;
|
||||
}
|
||||
|
||||
var flags = _preferredTab == flag ? ImGuiTabItemFlags.SetSelected : ImGuiTabItemFlags.None;
|
||||
unsafe
|
||||
{
|
||||
var tab = ImRaii.TabItem( name.Path, flags );
|
||||
if( ImGui.IsItemClicked() )
|
||||
{
|
||||
_preferredTab = flag;
|
||||
}
|
||||
|
||||
return tab;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,386 +1,15 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.UI.Classes;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
|
||||
public partial class ConfigWindow
|
||||
{
|
||||
private class ModPanel
|
||||
{
|
||||
private readonly ConfigWindow _window;
|
||||
private bool _valid;
|
||||
private bool _emptySetting;
|
||||
private bool _inherited;
|
||||
private ModFileSystem.Leaf _leaf = null!;
|
||||
private Mod2 _mod = null!;
|
||||
private ModSettings2 _settings = null!;
|
||||
private ModCollection _collection = null!;
|
||||
private string _lastWebsite = string.Empty;
|
||||
private bool _websiteValid;
|
||||
|
||||
private string? _currentSortOrderPath;
|
||||
private int? _currentPriority;
|
||||
|
||||
public ModPanel( ConfigWindow window )
|
||||
=> _window = window;
|
||||
|
||||
private void Init( ModFileSystemSelector selector )
|
||||
{
|
||||
_valid = selector.Selected != null;
|
||||
if( !_valid )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_leaf = selector.SelectedLeaf!;
|
||||
_mod = selector.Selected!;
|
||||
_settings = selector.SelectedSettings;
|
||||
_collection = selector.SelectedSettingCollection;
|
||||
_emptySetting = _settings == ModSettings2.Empty;
|
||||
_inherited = _collection != Penumbra.CollectionManager.Current;
|
||||
}
|
||||
|
||||
public void Draw( ModFileSystemSelector selector )
|
||||
{
|
||||
Init( selector );
|
||||
if( !_valid )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawInheritedWarning();
|
||||
DrawHeaderLine();
|
||||
DrawFilesystemPath();
|
||||
DrawEnabledInput();
|
||||
ImGui.SameLine();
|
||||
DrawPriorityInput();
|
||||
DrawRemoveSettings();
|
||||
DrawTabBar();
|
||||
}
|
||||
|
||||
private void DrawDescriptionTab()
|
||||
{
|
||||
if( _mod.Description.Length == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var tab = ImRaii.TabItem( "Description" );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var child = ImRaii.Child( "##tab" );
|
||||
if( !child )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.TextWrapped( _mod.Description );
|
||||
}
|
||||
|
||||
private void DrawSettingsTab()
|
||||
{
|
||||
if( !_mod.HasOptions )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var tab = ImRaii.TabItem( "Settings" );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var child = ImRaii.Child( "##tab" );
|
||||
if( !child )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for( var idx = 0; idx < _mod.Groups.Count; ++idx )
|
||||
{
|
||||
var group = _mod.Groups[ idx ];
|
||||
if( group.Type == SelectType.Single && group.IsOption )
|
||||
{
|
||||
using var id = ImRaii.PushId( idx );
|
||||
var selectedOption = _emptySetting ? 0 : ( int )_settings.Settings[ idx ];
|
||||
ImGui.SetNextItemWidth( _window._inputTextWidth.X );
|
||||
using var combo = ImRaii.Combo( string.Empty, group[ selectedOption ].Name );
|
||||
if( combo )
|
||||
{
|
||||
for( var idx2 = 0; idx2 < group.Count; ++idx2 )
|
||||
{
|
||||
if( ImGui.Selectable( group[ idx2 ].Name, idx2 == selectedOption ) )
|
||||
{
|
||||
Penumbra.CollectionManager.Current.SetModSetting( _mod.Index, idx, ( uint )idx2 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
combo.Dispose();
|
||||
ImGui.SameLine();
|
||||
if( group.Description.Length > 0 )
|
||||
{
|
||||
ImGuiUtil.LabeledHelpMarker( group.Name, group.Description );
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text( group.Name );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO add description
|
||||
for( var idx = 0; idx < _mod.Groups.Count; ++idx )
|
||||
{
|
||||
var group = _mod.Groups[ idx ];
|
||||
if( group.Type == SelectType.Multi && group.IsOption )
|
||||
{
|
||||
using var id = ImRaii.PushId( idx );
|
||||
var flags = _emptySetting ? 0u : _settings.Settings[ idx ];
|
||||
Widget.BeginFramedGroup( group.Name );
|
||||
for( var idx2 = 0; idx2 < group.Count; ++idx2 )
|
||||
{
|
||||
var flag = 1u << idx2;
|
||||
var setting = ( flags & flag ) != 0;
|
||||
if( ImGui.Checkbox( group[ idx2 ].Name, ref setting ) )
|
||||
{
|
||||
flags = setting ? flags | flag : flags & ~flag;
|
||||
Penumbra.CollectionManager.Current.SetModSetting( _mod.Index, idx, flags );
|
||||
}
|
||||
}
|
||||
|
||||
Widget.EndFramedGroup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawChangedItemsTab()
|
||||
{
|
||||
if( _mod.ChangedItems.Count == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var tab = ImRaii.TabItem( "Changed Items" );
|
||||
if( !tab )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var list = ImRaii.ListBox( "##changedItems", -Vector2.One );
|
||||
if( !list )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach( var (name, data) in _mod.ChangedItems )
|
||||
{
|
||||
_window.DrawChangedItem( name, data );
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTabBar()
|
||||
{
|
||||
using var tabBar = ImRaii.TabBar( "##ModTabs" );
|
||||
if( !tabBar )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawDescriptionTab();
|
||||
DrawSettingsTab();
|
||||
DrawChangedItemsTab();
|
||||
}
|
||||
|
||||
private void DrawInheritedWarning()
|
||||
{
|
||||
if( _inherited )
|
||||
{
|
||||
using var color = ImRaii.PushColor( ImGuiCol.Button, Colors.PressEnterWarningBg );
|
||||
var w = new Vector2( ImGui.GetContentRegionAvail().X, 0 );
|
||||
if( ImGui.Button( $"These settings are inherited from {_collection.Name}.", w ) )
|
||||
{
|
||||
Penumbra.CollectionManager.Current.SetModInheritance( _mod.Index, false );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPriorityInput()
|
||||
{
|
||||
var priority = _currentPriority ?? _settings.Priority;
|
||||
ImGui.SetNextItemWidth( 50 * ImGuiHelpers.GlobalScale );
|
||||
if( ImGui.InputInt( "Priority", ref priority, 0, 0 ) )
|
||||
{
|
||||
_currentPriority = priority;
|
||||
}
|
||||
|
||||
if( ImGui.IsItemDeactivatedAfterEdit() && _currentPriority.HasValue )
|
||||
{
|
||||
if( _currentPriority != _settings.Priority )
|
||||
{
|
||||
Penumbra.CollectionManager.Current.SetModPriority( _mod.Index, _currentPriority.Value );
|
||||
}
|
||||
|
||||
_currentPriority = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawRemoveSettings()
|
||||
{
|
||||
if( _inherited )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if( ImGui.Button( "Remove Settings" ) )
|
||||
{
|
||||
Penumbra.CollectionManager.Current.SetModInheritance( _mod.Index, true );
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Remove current settings from this collection so that it can inherit them.\n"
|
||||
+ "If no inherited collection has settings for this mod, it will be disabled." );
|
||||
}
|
||||
|
||||
private void DrawEnabledInput()
|
||||
{
|
||||
var enabled = _settings.Enabled;
|
||||
if( ImGui.Checkbox( "Enabled", ref enabled ) )
|
||||
{
|
||||
Penumbra.CollectionManager.Current.SetModState( _mod.Index, enabled );
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawFilesystemPath()
|
||||
{
|
||||
var fullName = _leaf.FullName();
|
||||
var path = _currentSortOrderPath ?? fullName;
|
||||
ImGui.SetNextItemWidth( 300 * ImGuiHelpers.GlobalScale );
|
||||
if( ImGui.InputText( "Sort Order", ref path, 256 ) )
|
||||
{
|
||||
_currentSortOrderPath = path;
|
||||
}
|
||||
|
||||
if( ImGui.IsItemDeactivatedAfterEdit() && _currentSortOrderPath != null )
|
||||
{
|
||||
if( _currentSortOrderPath != fullName )
|
||||
{
|
||||
_window._penumbra.ModFileSystem.RenameAndMove( _leaf, _currentSortOrderPath );
|
||||
}
|
||||
|
||||
_currentSortOrderPath = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draw the first info line for the mod panel,
|
||||
// containing all basic meta information.
|
||||
private void DrawHeaderLine()
|
||||
{
|
||||
DrawName();
|
||||
ImGui.SameLine();
|
||||
DrawVersion();
|
||||
ImGui.SameLine();
|
||||
DrawAuthor();
|
||||
ImGui.SameLine();
|
||||
DrawWebsite();
|
||||
}
|
||||
|
||||
// Draw the mod name.
|
||||
private void DrawName()
|
||||
{
|
||||
ImGui.Text( _mod.Name.Text );
|
||||
}
|
||||
|
||||
// Draw the author of the mod, if any.
|
||||
private void DrawAuthor()
|
||||
{
|
||||
using var group = ImRaii.Group();
|
||||
ImGuiUtil.TextColored( Colors.MetaInfoText, "by" );
|
||||
ImGui.SameLine();
|
||||
ImGui.Text( _mod.Author.IsEmpty ? "Unknown" : _mod.Author.Text );
|
||||
}
|
||||
|
||||
// Draw the mod version, if any.
|
||||
private void DrawVersion()
|
||||
{
|
||||
if( _mod.Version.Length > 0 )
|
||||
{
|
||||
ImGui.Text( $"(Version {_mod.Version})" );
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Dummy( Vector2.Zero );
|
||||
}
|
||||
}
|
||||
|
||||
// Update the last seen website and check for validity.
|
||||
private void UpdateWebsite( string newWebsite )
|
||||
{
|
||||
if( _lastWebsite == newWebsite )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastWebsite = newWebsite;
|
||||
_websiteValid = Uri.TryCreate( _lastWebsite, UriKind.Absolute, out var uriResult )
|
||||
&& ( uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp );
|
||||
}
|
||||
|
||||
// Draw the website source either as a button to open the site,
|
||||
// if it is a valid http website, or as pure text.
|
||||
private void DrawWebsite()
|
||||
{
|
||||
UpdateWebsite( _mod.Website );
|
||||
if( _lastWebsite.Length == 0 )
|
||||
{
|
||||
ImGui.Dummy( Vector2.Zero );
|
||||
return;
|
||||
}
|
||||
|
||||
using var group = ImRaii.Group();
|
||||
if( _websiteValid )
|
||||
{
|
||||
if( ImGui.Button( "Open Website" ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
var process = new ProcessStartInfo( _lastWebsite )
|
||||
{
|
||||
UseShellExecute = true,
|
||||
};
|
||||
Process.Start( process );
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( _lastWebsite );
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGuiUtil.TextColored( Colors.MetaInfoText, "from" );
|
||||
ImGui.SameLine();
|
||||
ImGui.Text( _lastWebsite );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ConfigWindow
|
||||
{
|
||||
public void DrawModsTab()
|
||||
|
|
@ -401,14 +30,13 @@ public partial class ConfigWindow
|
|||
using var group = ImRaii.Group();
|
||||
DrawHeaderLine();
|
||||
|
||||
using var child = ImRaii.Child( "##ModsTabMod", -Vector2.One, true );
|
||||
using var child = ImRaii.Child( "##ModsTabMod", -Vector2.One, true, ImGuiWindowFlags.HorizontalScrollbar );
|
||||
if( child )
|
||||
{
|
||||
_modPanel.Draw( _selector );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draw the header line that can quick switch between collections.
|
||||
private void DrawHeaderLine()
|
||||
{
|
||||
|
|
@ -466,4 +94,46 @@ public partial class ConfigWindow
|
|||
? absoluteSize
|
||||
: Math.Max( absoluteSize, relativeSize * ImGui.GetContentRegionAvail().X / 100 );
|
||||
}
|
||||
|
||||
// The basic setup for the mod panel.
|
||||
// Details are in other files.
|
||||
private partial class ModPanel
|
||||
{
|
||||
private readonly ConfigWindow _window;
|
||||
|
||||
private bool _valid;
|
||||
private ModFileSystem.Leaf _leaf = null!;
|
||||
private Mod _mod = null!;
|
||||
|
||||
public ModPanel( ConfigWindow window )
|
||||
{
|
||||
_window = window;
|
||||
}
|
||||
|
||||
public void Draw( ModFileSystemSelector selector )
|
||||
{
|
||||
Init( selector );
|
||||
if( !_valid )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawModHeader();
|
||||
DrawTabBar();
|
||||
}
|
||||
|
||||
private void Init( ModFileSystemSelector selector )
|
||||
{
|
||||
_valid = selector.Selected != null;
|
||||
if( !_valid )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_leaf = selector.SelectedLeaf!;
|
||||
_mod = selector.Selected!;
|
||||
UpdateSettingsData( selector );
|
||||
UpdateModData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ public partial class ConfigWindow
|
|||
DrawAdvancedSettings();
|
||||
}
|
||||
|
||||
private string? _settingsNewModDirectory;
|
||||
private string? _newModDirectory;
|
||||
private readonly FileDialogManager _dialogManager = new();
|
||||
private bool _dialogOpen;
|
||||
|
||||
|
|
@ -70,12 +70,18 @@ public partial class ConfigWindow
|
|||
}
|
||||
else
|
||||
{
|
||||
// TODO
|
||||
//_dialogManager.OpenFolderDialog( "Choose Mod Directory", ( b, s ) =>
|
||||
//{
|
||||
// _newModDirectory = b ? s : _newModDirectory;
|
||||
// _dialogOpen = false;
|
||||
//}, _newModDirectory, false);
|
||||
_newModDirectory ??= Penumbra.Config.ModDirectory;
|
||||
var startDir = Directory.Exists( _newModDirectory )
|
||||
? _newModDirectory
|
||||
: Directory.Exists( Penumbra.Config.ModDirectory )
|
||||
? Penumbra.Config.ModDirectory
|
||||
: ".";
|
||||
|
||||
_dialogManager.OpenFolderDialog( "Choose Mod Directory", ( b, s ) =>
|
||||
{
|
||||
_newModDirectory = b ? s : _newModDirectory;
|
||||
_dialogOpen = false;
|
||||
}, startDir );
|
||||
_dialogOpen = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -99,12 +105,12 @@ public partial class ConfigWindow
|
|||
|
||||
private void DrawRootFolder()
|
||||
{
|
||||
_settingsNewModDirectory ??= Penumbra.Config.ModDirectory;
|
||||
_newModDirectory ??= Penumbra.Config.ModDirectory;
|
||||
|
||||
var spacing = 3 * ImGuiHelpers.GlobalScale;
|
||||
using var group = ImRaii.Group();
|
||||
ImGui.SetNextItemWidth( _window._inputTextWidth.X - spacing - ImGui.GetFrameHeight() );
|
||||
var save = ImGui.InputText( "##rootDirectory", ref _settingsNewModDirectory, 255, ImGuiInputTextFlags.EnterReturnsTrue );
|
||||
var save = ImGui.InputText( "##rootDirectory", ref _newModDirectory, 255, ImGuiInputTextFlags.EnterReturnsTrue );
|
||||
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, new Vector2( spacing, 0 ) );
|
||||
ImGui.SameLine();
|
||||
DrawDirectoryPickerButton();
|
||||
|
|
@ -121,14 +127,14 @@ public partial class ConfigWindow
|
|||
var pos = ImGui.GetCursorPosX();
|
||||
ImGui.NewLine();
|
||||
|
||||
if( Penumbra.Config.ModDirectory == _settingsNewModDirectory || _settingsNewModDirectory.Length == 0 )
|
||||
if( Penumbra.Config.ModDirectory == _newModDirectory || _newModDirectory.Length == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if( save || DrawPressEnterWarning( Penumbra.Config.ModDirectory, pos ) )
|
||||
{
|
||||
Penumbra.ModManager.DiscoverMods( _settingsNewModDirectory );
|
||||
Penumbra.ModManager.DiscoverMods( _newModDirectory );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -137,12 +143,13 @@ public partial class ConfigWindow
|
|||
{
|
||||
DrawOpenDirectoryButton( 0, Penumbra.ModManager.BasePath, Penumbra.ModManager.Valid );
|
||||
ImGui.SameLine();
|
||||
if( ImGui.Button( "Rediscover Mods" ) )
|
||||
var tt = Penumbra.ModManager.Valid
|
||||
? "Force Penumbra to completely re-scan your root directory as if it was restarted."
|
||||
: "The currently selected folder is not valid. Please select a different folder.";
|
||||
if( ImGuiUtil.DrawDisabledButton( "Rediscover Mods", Vector2.Zero, tt, !Penumbra.ModManager.Valid ) )
|
||||
{
|
||||
Penumbra.ModManager.DiscoverMods();
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip( "Force Penumbra to completely re-scan your root directory as if it was restarted." );
|
||||
}
|
||||
|
||||
private void DrawEnabledBox()
|
||||
|
|
|
|||
|
|
@ -21,13 +21,14 @@ public sealed partial class ConfigWindow : Window, IDisposable
|
|||
private readonly EffectiveTab _effectiveTab;
|
||||
private readonly DebugTab _debugTab;
|
||||
private readonly ResourceTab _resourceTab;
|
||||
public readonly SubModEditWindow SubModPopup = new();
|
||||
|
||||
public ConfigWindow( Penumbra penumbra )
|
||||
: base( GetLabel() )
|
||||
{
|
||||
_penumbra = penumbra;
|
||||
_settingsTab = new SettingsTab( this );
|
||||
_selector = new ModFileSystemSelector( _penumbra.ModFileSystem, new HashSet< Mod2 >() ); // TODO
|
||||
_selector = new ModFileSystemSelector( _penumbra.ModFileSystem, new HashSet< Mod >() ); // TODO
|
||||
_modPanel = new ModPanel( this );
|
||||
_collectionsTab = new CollectionsTab( this );
|
||||
_effectiveTab = new EffectiveTab();
|
||||
|
|
@ -61,6 +62,7 @@ public sealed partial class ConfigWindow : Window, IDisposable
|
|||
public void Dispose()
|
||||
{
|
||||
_selector.Dispose();
|
||||
_modPanel.Dispose();
|
||||
}
|
||||
|
||||
private static string GetLabel()
|
||||
|
|
@ -70,10 +72,12 @@ public sealed partial class ConfigWindow : Window, IDisposable
|
|||
|
||||
private Vector2 _defaultSpace;
|
||||
private Vector2 _inputTextWidth;
|
||||
private Vector2 _iconButtonSize;
|
||||
|
||||
private void SetupSizes()
|
||||
{
|
||||
_defaultSpace = new Vector2( 0, 10 * ImGuiHelpers.GlobalScale );
|
||||
_inputTextWidth = new Vector2( 350f * ImGuiHelpers.GlobalScale, 0 );
|
||||
_iconButtonSize = new Vector2( ImGui.GetFrameHeight() );
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ namespace Penumbra.UI;
|
|||
// using the Dalamud-provided collapsible submenu.
|
||||
public class LaunchButton : IDisposable
|
||||
{
|
||||
private readonly ConfigWindow _configWindow;
|
||||
private readonly ConfigWindow _configWindow;
|
||||
private readonly TextureWrap? _icon;
|
||||
private readonly TitleScreenMenu.TitleScreenMenuEntry? _entry;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue