This is going rather well.

This commit is contained in:
Ottermandias 2023-03-11 17:50:32 +01:00
parent 73e2793da6
commit bdaff7b781
48 changed files with 2944 additions and 2952 deletions

View file

@ -17,6 +17,7 @@ using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Mods;
using Penumbra.Mods.ItemSwap;
using Penumbra.Services;
using Penumbra.Util;
namespace Penumbra.UI.Classes;
@ -42,49 +43,61 @@ public class ItemSwapWindow : IDisposable
Weapon,
}
private class ItemSelector : FilterComboCache< (string, Item) >
private class ItemSelector : FilterComboCache<(string, Item)>
{
public ItemSelector( FullEquipType type )
: base( () => Penumbra.ItemData[ type ].Select( i => ( i.Name.ToDalamudString().TextValue, i ) ).ToArray() )
public ItemSelector(FullEquipType type)
: base(() => Penumbra.ItemData[type].Select(i => (i.Name.ToDalamudString().TextValue, i)).ToArray())
{ }
protected override string ToString( (string, Item) obj )
protected override string ToString((string, Item) obj)
=> obj.Item1;
}
private class WeaponSelector : FilterComboCache< FullEquipType >
private class WeaponSelector : FilterComboCache<FullEquipType>
{
public WeaponSelector()
: base( FullEquipTypeExtensions.WeaponTypes.Concat( FullEquipTypeExtensions.ToolTypes ) )
: base(FullEquipTypeExtensions.WeaponTypes.Concat(FullEquipTypeExtensions.ToolTypes))
{ }
protected override string ToString( FullEquipType type )
protected override string ToString(FullEquipType type)
=> type.ToName();
}
public ItemSwapWindow()
private readonly CommunicatorService _communicator;
public ItemSwapWindow(CommunicatorService communicator)
{
Penumbra.CollectionManager.CollectionChanged += OnCollectionChange;
_communicator = communicator;
_communicator.CollectionChange.Event += OnCollectionChange;
Penumbra.CollectionManager.Current.ModSettingChanged += OnSettingChange;
}
public void Dispose()
{
Penumbra.CollectionManager.CollectionChanged += OnCollectionChange;
_communicator.CollectionChange.Event -= OnCollectionChange;
Penumbra.CollectionManager.Current.ModSettingChanged -= OnSettingChange;
}
private readonly Dictionary< SwapType, (ItemSelector Source, ItemSelector Target, string TextFrom, string TextTo) > _selectors = new()
private readonly Dictionary<SwapType, (ItemSelector Source, ItemSelector Target, string TextFrom, string TextTo)> _selectors = new()
{
[ SwapType.Hat ] = ( new ItemSelector( FullEquipType.Head ), new ItemSelector( FullEquipType.Head ), "Take this Hat", "and put it on this one" ),
[ SwapType.Top ] = ( new ItemSelector( FullEquipType.Body ), new ItemSelector( FullEquipType.Body ), "Take this Top", "and put it on this one" ),
[ SwapType.Gloves ] = ( new ItemSelector( FullEquipType.Hands ), new ItemSelector( FullEquipType.Hands ), "Take these Gloves", "and put them on these" ),
[ SwapType.Pants ] = ( new ItemSelector( FullEquipType.Legs ), new ItemSelector( FullEquipType.Legs ), "Take these Pants", "and put them on these" ),
[ SwapType.Shoes ] = ( new ItemSelector( FullEquipType.Feet ), new ItemSelector( FullEquipType.Feet ), "Take these Shoes", "and put them on these" ),
[ SwapType.Earrings ] = ( new ItemSelector( FullEquipType.Ears ), new ItemSelector( FullEquipType.Ears ), "Take these Earrings", "and put them on these" ),
[ SwapType.Necklace ] = ( new ItemSelector( FullEquipType.Neck ), new ItemSelector( FullEquipType.Neck ), "Take this Necklace", "and put it on this one" ),
[ SwapType.Bracelet ] = ( new ItemSelector( FullEquipType.Wrists ), new ItemSelector( FullEquipType.Wrists ), "Take these Bracelets", "and put them on these" ),
[ SwapType.Ring ] = ( new ItemSelector( FullEquipType.Finger ), new ItemSelector( FullEquipType.Finger ), "Take this Ring", "and put it on this one" ),
[SwapType.Hat] =
(new ItemSelector(FullEquipType.Head), new ItemSelector(FullEquipType.Head), "Take this Hat", "and put it on this one"),
[SwapType.Top] =
(new ItemSelector(FullEquipType.Body), new ItemSelector(FullEquipType.Body), "Take this Top", "and put it on this one"),
[SwapType.Gloves] =
(new ItemSelector(FullEquipType.Hands), new ItemSelector(FullEquipType.Hands), "Take these Gloves", "and put them on these"),
[SwapType.Pants] =
(new ItemSelector(FullEquipType.Legs), new ItemSelector(FullEquipType.Legs), "Take these Pants", "and put them on these"),
[SwapType.Shoes] =
(new ItemSelector(FullEquipType.Feet), new ItemSelector(FullEquipType.Feet), "Take these Shoes", "and put them on these"),
[SwapType.Earrings] =
(new ItemSelector(FullEquipType.Ears), new ItemSelector(FullEquipType.Ears), "Take these Earrings", "and put them on these"),
[SwapType.Necklace] =
(new ItemSelector(FullEquipType.Neck), new ItemSelector(FullEquipType.Neck), "Take this Necklace", "and put it on this one"),
[SwapType.Bracelet] =
(new ItemSelector(FullEquipType.Wrists), new ItemSelector(FullEquipType.Wrists), "Take these Bracelets", "and put them on these"),
[SwapType.Ring] = (new ItemSelector(FullEquipType.Finger), new ItemSelector(FullEquipType.Finger), "Take this Ring",
"and put it on this one"),
};
private ItemSelector? _weaponSource = null;
@ -117,39 +130,33 @@ public class ItemSwapWindow : IDisposable
private Item[]? _affectedItems;
public void UpdateMod( Mod mod, ModSettings? settings )
public void UpdateMod(Mod mod, ModSettings? settings)
{
if( mod == _mod && settings == _modSettings )
{
if (mod == _mod && settings == _modSettings)
return;
}
var oldDefaultName = $"{_mod?.Name.Text ?? "Unknown"} (Swapped)";
if( _newModName.Length == 0 || oldDefaultName == _newModName )
{
if (_newModName.Length == 0 || oldDefaultName == _newModName)
_newModName = $"{mod.Name.Text} (Swapped)";
}
_mod = mod;
_modSettings = settings;
_swapData.LoadMod( _mod, _modSettings );
_swapData.LoadMod(_mod, _modSettings);
UpdateOption();
_dirty = true;
}
private void UpdateState()
{
if( !_dirty )
{
if (!_dirty)
return;
}
_swapData.Clear();
_loadException = null;
_affectedItems = null;
try
{
switch( _lastTab )
switch (_lastTab)
{
case SwapType.Hat:
case SwapType.Top:
@ -178,27 +185,31 @@ public class ItemSwapWindow : IDisposable
}
break;
case SwapType.Hair when _targetId > 0 && _sourceId > 0:
_swapData.LoadCustomization( BodySlot.Hair, Names.CombinedRace( _currentGender, _currentRace ), ( SetId )_sourceId, ( SetId )_targetId,
_useCurrentCollection ? Penumbra.CollectionManager.Current : null );
_swapData.LoadCustomization(BodySlot.Hair, Names.CombinedRace(_currentGender, _currentRace), (SetId)_sourceId,
(SetId)_targetId,
_useCurrentCollection ? Penumbra.CollectionManager.Current : null);
break;
case SwapType.Face when _targetId > 0 && _sourceId > 0:
_swapData.LoadCustomization( BodySlot.Face, Names.CombinedRace( _currentGender, _currentRace ), ( SetId )_sourceId, ( SetId )_targetId,
_useCurrentCollection ? Penumbra.CollectionManager.Current : null );
_swapData.LoadCustomization(BodySlot.Face, Names.CombinedRace(_currentGender, _currentRace), (SetId)_sourceId,
(SetId)_targetId,
_useCurrentCollection ? Penumbra.CollectionManager.Current : null);
break;
case SwapType.Ears when _targetId > 0 && _sourceId > 0:
_swapData.LoadCustomization( BodySlot.Zear, Names.CombinedRace( _currentGender, ModelRace.Viera ), ( SetId )_sourceId, ( SetId )_targetId,
_useCurrentCollection ? Penumbra.CollectionManager.Current : null );
_swapData.LoadCustomization(BodySlot.Zear, Names.CombinedRace(_currentGender, ModelRace.Viera), (SetId)_sourceId,
(SetId)_targetId,
_useCurrentCollection ? Penumbra.CollectionManager.Current : null);
break;
case SwapType.Tail when _targetId > 0 && _sourceId > 0:
_swapData.LoadCustomization( BodySlot.Tail, Names.CombinedRace( _currentGender, _currentRace ), ( SetId )_sourceId, ( SetId )_targetId,
_useCurrentCollection ? Penumbra.CollectionManager.Current : null );
_swapData.LoadCustomization(BodySlot.Tail, Names.CombinedRace(_currentGender, _currentRace), (SetId)_sourceId,
(SetId)_targetId,
_useCurrentCollection ? Penumbra.CollectionManager.Current : null);
break;
case SwapType.Weapon: break;
}
}
catch( Exception e )
catch (Exception e)
{
Penumbra.Log.Error( $"Could not get Customization Data container for {_lastTab}:\n{e}" );
Penumbra.Log.Error($"Could not get Customization Data container for {_lastTab}:\n{e}");
_loadException = e;
_affectedItems = null;
_swapData.Clear();
@ -207,13 +218,14 @@ public class ItemSwapWindow : IDisposable
_dirty = false;
}
private static string SwapToString( Swap swap )
private static string SwapToString(Swap swap)
{
return swap switch
{
MetaSwap meta => $"{meta.SwapFrom}: {meta.SwapFrom.EntryToString()} -> {meta.SwapApplied.EntryToString()}",
FileSwap file => $"{file.Type}: {file.SwapFromRequestPath} -> {file.SwapToModded.FullName}{( file.DataWasChanged ? " (EDITED)" : string.Empty )}",
_ => string.Empty,
FileSwap file =>
$"{file.Type}: {file.SwapFromRequestPath} -> {file.SwapToModded.FullName}{(file.DataWasChanged ? " (EDITED)" : string.Empty)}",
_ => string.Empty,
};
}
@ -222,28 +234,28 @@ public class ItemSwapWindow : IDisposable
private void UpdateOption()
{
_selectedGroup = _mod?.Groups.FirstOrDefault( g => g.Name == _newGroupName );
_subModValid = _mod != null && _newGroupName.Length > 0 && _newOptionName.Length > 0 && ( _selectedGroup?.All( o => o.Name != _newOptionName ) ?? true );
_selectedGroup = _mod?.Groups.FirstOrDefault(g => g.Name == _newGroupName);
_subModValid = _mod != null
&& _newGroupName.Length > 0
&& _newOptionName.Length > 0
&& (_selectedGroup?.All(o => o.Name != _newOptionName) ?? true);
}
private void CreateMod()
{
var newDir = Mod.Creator.CreateModFolder( Penumbra.ModManager.BasePath, _newModName );
Mod.Creator.CreateMeta( newDir, _newModName, Penumbra.Config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty );
Mod.Creator.CreateDefaultFiles( newDir );
Penumbra.ModManager.AddMod( newDir );
if( !_swapData.WriteMod( Penumbra.ModManager.Last(), _useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps ) )
{
Penumbra.ModManager.DeleteMod( Penumbra.ModManager.Count - 1 );
}
var newDir = Mod.Creator.CreateModFolder(Penumbra.ModManager.BasePath, _newModName);
Mod.Creator.CreateMeta(newDir, _newModName, Penumbra.Config.DefaultModAuthor, CreateDescription(), "1.0", string.Empty);
Mod.Creator.CreateDefaultFiles(newDir);
Penumbra.ModManager.AddMod(newDir);
if (!_swapData.WriteMod(Penumbra.ModManager.Last(),
_useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps))
Penumbra.ModManager.DeleteMod(Penumbra.ModManager.Count - 1);
}
private void CreateOption()
{
if( _mod == null || !_subModValid )
{
if (_mod == null || !_subModValid)
return;
}
var groupCreated = false;
var dirCreated = false;
@ -251,52 +263,47 @@ public class ItemSwapWindow : IDisposable
DirectoryInfo? optionFolderName = null;
try
{
optionFolderName = Mod.Creator.NewSubFolderName( new DirectoryInfo( Path.Combine( _mod.ModPath.FullName, _selectedGroup?.Name ?? _newGroupName ) ), _newOptionName );
if( optionFolderName?.Exists == true )
{
throw new Exception( $"The folder {optionFolderName.FullName} for the option already exists." );
}
optionFolderName =
Mod.Creator.NewSubFolderName(new DirectoryInfo(Path.Combine(_mod.ModPath.FullName, _selectedGroup?.Name ?? _newGroupName)),
_newOptionName);
if (optionFolderName?.Exists == true)
throw new Exception($"The folder {optionFolderName.FullName} for the option already exists.");
if( optionFolderName != null )
if (optionFolderName != null)
{
if( _selectedGroup == null )
if (_selectedGroup == null)
{
Penumbra.ModManager.AddModGroup( _mod, GroupType.Multi, _newGroupName );
Penumbra.ModManager.AddModGroup(_mod, GroupType.Multi, _newGroupName);
_selectedGroup = _mod.Groups.Last();
groupCreated = true;
}
Penumbra.ModManager.AddOption( _mod, _mod.Groups.IndexOf( _selectedGroup ), _newOptionName );
Penumbra.ModManager.AddOption(_mod, _mod.Groups.IndexOf(_selectedGroup), _newOptionName);
optionCreated = true;
optionFolderName = Directory.CreateDirectory( optionFolderName.FullName );
optionFolderName = Directory.CreateDirectory(optionFolderName.FullName);
dirCreated = true;
if( !_swapData.WriteMod( _mod, _useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps, optionFolderName,
_mod.Groups.IndexOf( _selectedGroup ), _selectedGroup.Count - 1 ) )
{
throw new Exception( "Failure writing files for mod swap." );
}
if (!_swapData.WriteMod(_mod, _useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps,
optionFolderName,
_mod.Groups.IndexOf(_selectedGroup), _selectedGroup.Count - 1))
throw new Exception("Failure writing files for mod swap.");
}
}
catch( Exception e )
catch (Exception e)
{
ChatUtil.NotificationMessage( $"Could not create new Swap Option:\n{e}", "Error", NotificationType.Error );
ChatUtil.NotificationMessage($"Could not create new Swap Option:\n{e}", "Error", NotificationType.Error);
try
{
if( optionCreated && _selectedGroup != null )
{
Penumbra.ModManager.DeleteOption( _mod, _mod.Groups.IndexOf( _selectedGroup ), _selectedGroup.Count - 1 );
}
if (optionCreated && _selectedGroup != null)
Penumbra.ModManager.DeleteOption(_mod, _mod.Groups.IndexOf(_selectedGroup), _selectedGroup.Count - 1);
if( groupCreated )
if (groupCreated)
{
Penumbra.ModManager.DeleteModGroup( _mod, _mod.Groups.IndexOf( _selectedGroup! ) );
Penumbra.ModManager.DeleteModGroup(_mod, _mod.Groups.IndexOf(_selectedGroup!));
_selectedGroup = null;
}
if( dirCreated && optionFolderName != null )
{
Directory.Delete( optionFolderName.FullName, true );
}
if (dirCreated && optionFolderName != null)
Directory.Delete(optionFolderName.FullName, true);
}
catch
{
@ -307,12 +314,12 @@ public class ItemSwapWindow : IDisposable
UpdateOption();
}
private void DrawHeaderLine( float width )
private void DrawHeaderLine(float width)
{
var newModAvailable = _loadException == null && _swapData.Loaded;
ImGui.SetNextItemWidth( width );
if( ImGui.InputTextWithHint( "##newModName", "New Mod Name...", ref _newModName, 64 ) )
ImGui.SetNextItemWidth(width);
if (ImGui.InputTextWithHint("##newModName", "New Mod Name...", ref _newModName, 64))
{ }
ImGui.SameLine();
@ -321,29 +328,23 @@ public class ItemSwapWindow : IDisposable
: _newModName.Length == 0
? "Please enter a name for your mod."
: "Create a new mod of the given name containing only the swap.";
if( ImGuiUtil.DrawDisabledButton( "Create New Mod", new Vector2( width / 2, 0 ), tt, !newModAvailable || _newModName.Length == 0 ) )
{
if (ImGuiUtil.DrawDisabledButton("Create New Mod", new Vector2(width / 2, 0), tt, !newModAvailable || _newModName.Length == 0))
CreateMod();
}
ImGui.SameLine();
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + 20 * ImGuiHelpers.GlobalScale );
ImGui.Checkbox( "Use File Swaps", ref _useFileSwaps );
ImGuiUtil.HoverTooltip( "Instead of writing every single non-default file to the newly created mod or option,\n"
+ "even those available from game files, use File Swaps to default game files where possible." );
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 20 * ImGuiHelpers.GlobalScale);
ImGui.Checkbox("Use File Swaps", ref _useFileSwaps);
ImGuiUtil.HoverTooltip("Instead of writing every single non-default file to the newly created mod or option,\n"
+ "even those available from game files, use File Swaps to default game files where possible.");
ImGui.SetNextItemWidth( ( width - ImGui.GetStyle().ItemSpacing.X ) / 2 );
if( ImGui.InputTextWithHint( "##groupName", "Group Name...", ref _newGroupName, 32 ) )
{
ImGui.SetNextItemWidth((width - ImGui.GetStyle().ItemSpacing.X) / 2);
if (ImGui.InputTextWithHint("##groupName", "Group Name...", ref _newGroupName, 32))
UpdateOption();
}
ImGui.SameLine();
ImGui.SetNextItemWidth( ( width - ImGui.GetStyle().ItemSpacing.X ) / 2 );
if( ImGui.InputTextWithHint( "##optionName", "New Option Name...", ref _newOptionName, 32 ) )
{
ImGui.SetNextItemWidth((width - ImGui.GetStyle().ItemSpacing.X) / 2);
if (ImGui.InputTextWithHint("##optionName", "New Option Name...", ref _newOptionName, 32))
UpdateOption();
}
ImGui.SameLine();
tt = !_subModValid
@ -351,16 +352,15 @@ public class ItemSwapWindow : IDisposable
: !newModAvailable
? "Create a new option inside this mod containing only the swap."
: "Create a new option (and possibly Multi-Group) inside the currently selected mod containing the swap.";
if( ImGuiUtil.DrawDisabledButton( "Create New Option", new Vector2( width / 2, 0 ), tt, !newModAvailable || !_subModValid ) )
{
if (ImGuiUtil.DrawDisabledButton("Create New Option", new Vector2(width / 2, 0), tt, !newModAvailable || !_subModValid))
CreateOption();
}
ImGui.SameLine();
ImGui.SetCursorPosX( ImGui.GetCursorPosX() + 20 * ImGuiHelpers.GlobalScale );
_dirty |= ImGui.Checkbox( "Use Entire Collection", ref _useCurrentCollection );
ImGuiUtil.HoverTooltip( "Use all applied mods from the Selected Collection with their current settings and respecting the enabled state of mods and inheritance,\n"
+ "instead of using only the selected mod with its current settings in the Selected collection or the default settings, ignoring the enabled state and inheritance." );
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 20 * ImGuiHelpers.GlobalScale);
_dirty |= ImGui.Checkbox("Use Entire Collection", ref _useCurrentCollection);
ImGuiUtil.HoverTooltip(
"Use all applied mods from the Selected Collection with their current settings and respecting the enabled state of mods and inheritance,\n"
+ "instead of using only the selected mod with its current settings in the Selected collection or the default settings, ignoring the enabled state and inheritance.");
}
private void DrawSwapBar()
@ -491,61 +491,58 @@ public class ItemSwapWindow : IDisposable
return (article1, article2, source ? tuple.Source : tuple.Target);
}
private void DrawEquipmentSwap( SwapType type )
private void DrawEquipmentSwap(SwapType type)
{
using var tab = DrawTab( type );
if( !tab )
{
using var tab = DrawTab(type);
if (!tab)
return;
}
var (sourceSelector, targetSelector, text1, text2) = _selectors[ type ];
using var table = ImRaii.Table( "##settings", 2, ImGuiTableFlags.SizingFixedFit );
var (sourceSelector, targetSelector, text1, text2) = _selectors[type];
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted( text1 );
ImGui.TextUnformatted(text1);
ImGui.TableNextColumn();
_dirty |= sourceSelector.Draw( "##itemSource", sourceSelector.CurrentSelection.Item1 ?? string.Empty, string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() );
_dirty |= sourceSelector.Draw("##itemSource", sourceSelector.CurrentSelection.Item1 ?? string.Empty, string.Empty, InputWidth * 2,
ImGui.GetTextLineHeightWithSpacing());
if( type == SwapType.Ring )
if (type == SwapType.Ring)
{
ImGui.SameLine();
_dirty |= ImGui.Checkbox( "Swap Right Ring", ref _useRightRing );
_dirty |= ImGui.Checkbox("Swap Right Ring", ref _useRightRing);
}
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted( text2 );
ImGui.TextUnformatted(text2);
ImGui.TableNextColumn();
_dirty |= targetSelector.Draw( "##itemTarget", targetSelector.CurrentSelection.Item1 ?? string.Empty, string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() );
if( type == SwapType.Ring )
_dirty |= targetSelector.Draw("##itemTarget", targetSelector.CurrentSelection.Item1 ?? string.Empty, string.Empty, InputWidth * 2,
ImGui.GetTextLineHeightWithSpacing());
if (type == SwapType.Ring)
{
ImGui.SameLine();
_dirty |= ImGui.Checkbox( "Swap Left Ring", ref _useLeftRing );
_dirty |= ImGui.Checkbox("Swap Left Ring", ref _useLeftRing);
}
if( _affectedItems is { Length: > 1 } )
if (_affectedItems is { Length: > 1 })
{
ImGui.SameLine();
ImGuiUtil.DrawTextButton( $"which will also affect {_affectedItems.Length - 1} other Items.", Vector2.Zero, Colors.PressEnterWarningBg );
if( ImGui.IsItemHovered() )
{
ImGui.SetTooltip( string.Join( '\n', _affectedItems.Where( i => !ReferenceEquals( i, targetSelector.CurrentSelection.Item2 ) )
.Select( i => i.Name.ToDalamudString().TextValue ) ) );
}
ImGuiUtil.DrawTextButton($"which will also affect {_affectedItems.Length - 1} other Items.", Vector2.Zero,
Colors.PressEnterWarningBg);
if (ImGui.IsItemHovered())
ImGui.SetTooltip(string.Join('\n', _affectedItems.Where(i => !ReferenceEquals(i, targetSelector.CurrentSelection.Item2))
.Select(i => i.Name.ToDalamudString().TextValue)));
}
}
private void DrawHairSwap()
{
using var tab = DrawTab( SwapType.Hair );
if( !tab )
{
using var tab = DrawTab(SwapType.Hair);
if (!tab)
return;
}
using var table = ImRaii.Table( "##settings", 2, ImGuiTableFlags.SizingFixedFit );
DrawTargetIdInput( "Take this Hairstyle" );
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
DrawTargetIdInput("Take this Hairstyle");
DrawSourceIdInput();
DrawGenderInput();
}
@ -553,145 +550,139 @@ public class ItemSwapWindow : IDisposable
private void DrawFaceSwap()
{
using var disabled = ImRaii.Disabled();
using var tab = DrawTab( SwapType.Face );
if( !tab )
{
using var tab = DrawTab(SwapType.Face);
if (!tab)
return;
}
using var table = ImRaii.Table( "##settings", 2, ImGuiTableFlags.SizingFixedFit );
DrawTargetIdInput( "Take this Face Type" );
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
DrawTargetIdInput("Take this Face Type");
DrawSourceIdInput();
DrawGenderInput();
}
private void DrawTailSwap()
{
using var tab = DrawTab( SwapType.Tail );
if( !tab )
{
using var tab = DrawTab(SwapType.Tail);
if (!tab)
return;
}
using var table = ImRaii.Table( "##settings", 2, ImGuiTableFlags.SizingFixedFit );
DrawTargetIdInput( "Take this Tail Type" );
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
DrawTargetIdInput("Take this Tail Type");
DrawSourceIdInput();
DrawGenderInput( "for all", 2 );
DrawGenderInput("for all", 2);
}
private void DrawEarSwap()
{
using var tab = DrawTab( SwapType.Ears );
if( !tab )
{
using var tab = DrawTab(SwapType.Ears);
if (!tab)
return;
}
using var table = ImRaii.Table( "##settings", 2, ImGuiTableFlags.SizingFixedFit );
DrawTargetIdInput( "Take this Ear Type" );
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
DrawTargetIdInput("Take this Ear Type");
DrawSourceIdInput();
DrawGenderInput( "for all Viera", 0 );
DrawGenderInput("for all Viera", 0);
}
private void DrawWeaponSwap()
{
using var disabled = ImRaii.Disabled();
using var tab = DrawTab( SwapType.Weapon );
if( !tab )
{
using var tab = DrawTab(SwapType.Weapon);
if (!tab)
return;
}
using var table = ImRaii.Table( "##settings", 2, ImGuiTableFlags.SizingFixedFit );
using var table = ImRaii.Table("##settings", 2, ImGuiTableFlags.SizingFixedFit);
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted( "Select the weapon or tool you want" );
ImGui.TextUnformatted("Select the weapon or tool you want");
ImGui.TableNextColumn();
if( _slotSelector.Draw( "##weaponSlot", _slotSelector.CurrentSelection.ToName(), string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() ) )
if (_slotSelector.Draw("##weaponSlot", _slotSelector.CurrentSelection.ToName(), string.Empty, InputWidth * 2,
ImGui.GetTextLineHeightWithSpacing()))
{
_dirty = true;
_weaponSource = new ItemSelector( _slotSelector.CurrentSelection );
_weaponTarget = new ItemSelector( _slotSelector.CurrentSelection );
_weaponSource = new ItemSelector(_slotSelector.CurrentSelection);
_weaponTarget = new ItemSelector(_slotSelector.CurrentSelection);
}
else
{
_dirty = _weaponSource == null || _weaponTarget == null;
_weaponSource ??= new ItemSelector( _slotSelector.CurrentSelection );
_weaponTarget ??= new ItemSelector( _slotSelector.CurrentSelection );
_weaponSource ??= new ItemSelector(_slotSelector.CurrentSelection);
_weaponTarget ??= new ItemSelector(_slotSelector.CurrentSelection);
}
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted( "and put this variant of it" );
ImGui.TextUnformatted("and put this variant of it");
ImGui.TableNextColumn();
_dirty |= _weaponSource.Draw( "##weaponSource", _weaponSource.CurrentSelection.Item1 ?? string.Empty, string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() );
_dirty |= _weaponSource.Draw("##weaponSource", _weaponSource.CurrentSelection.Item1 ?? string.Empty, string.Empty, InputWidth * 2,
ImGui.GetTextLineHeightWithSpacing());
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted( "onto this one" );
ImGui.TextUnformatted("onto this one");
ImGui.TableNextColumn();
_dirty |= _weaponTarget.Draw( "##weaponTarget", _weaponTarget.CurrentSelection.Item1 ?? string.Empty, string.Empty, InputWidth * 2, ImGui.GetTextLineHeightWithSpacing() );
_dirty |= _weaponTarget.Draw("##weaponTarget", _weaponTarget.CurrentSelection.Item1 ?? string.Empty, string.Empty, InputWidth * 2,
ImGui.GetTextLineHeightWithSpacing());
}
private const float InputWidth = 120;
private void DrawTargetIdInput( string text = "Take this ID" )
private void DrawTargetIdInput(string text = "Take this ID")
{
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted( text );
ImGui.TextUnformatted(text);
ImGui.TableNextColumn();
ImGui.SetNextItemWidth( InputWidth * ImGuiHelpers.GlobalScale );
if( ImGui.InputInt( "##targetId", ref _targetId, 0, 0 ) )
{
_targetId = Math.Clamp( _targetId, 0, byte.MaxValue );
}
ImGui.SetNextItemWidth(InputWidth * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt("##targetId", ref _targetId, 0, 0))
_targetId = Math.Clamp(_targetId, 0, byte.MaxValue);
_dirty |= ImGui.IsItemDeactivatedAfterEdit();
}
private void DrawSourceIdInput( string text = "and put it on this one" )
private void DrawSourceIdInput(string text = "and put it on this one")
{
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted( text );
ImGui.TextUnformatted(text);
ImGui.TableNextColumn();
ImGui.SetNextItemWidth( InputWidth * ImGuiHelpers.GlobalScale );
if( ImGui.InputInt( "##sourceId", ref _sourceId, 0, 0 ) )
{
_sourceId = Math.Clamp( _sourceId, 0, byte.MaxValue );
}
ImGui.SetNextItemWidth(InputWidth * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt("##sourceId", ref _sourceId, 0, 0))
_sourceId = Math.Clamp(_sourceId, 0, byte.MaxValue);
_dirty |= ImGui.IsItemDeactivatedAfterEdit();
}
private void DrawGenderInput( string text = "for all", int drawRace = 1 )
private void DrawGenderInput(string text = "for all", int drawRace = 1)
{
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted( text );
ImGui.TextUnformatted(text);
ImGui.TableNextColumn();
_dirty |= Combos.Gender( "##Gender", InputWidth, _currentGender, out _currentGender );
if( drawRace == 1 )
_dirty |= Combos.Gender("##Gender", InputWidth, _currentGender, out _currentGender);
if (drawRace == 1)
{
ImGui.SameLine();
_dirty |= Combos.Race( "##Race", InputWidth, _currentRace, out _currentRace );
_dirty |= Combos.Race("##Race", InputWidth, _currentRace, out _currentRace);
}
else if( drawRace == 2 )
else if (drawRace == 2)
{
ImGui.SameLine();
if( _currentRace is not ModelRace.Miqote and not ModelRace.AuRa and not ModelRace.Hrothgar )
{
if (_currentRace is not ModelRace.Miqote and not ModelRace.AuRa and not ModelRace.Hrothgar)
_currentRace = ModelRace.Miqote;
}
_dirty |= ImGuiUtil.GenericEnumCombo( "##Race", InputWidth, _currentRace, out _currentRace, new[] { ModelRace.Miqote, ModelRace.AuRa, ModelRace.Hrothgar },
RaceEnumExtensions.ToName );
_dirty |= ImGuiUtil.GenericEnumCombo("##Race", InputWidth, _currentRace, out _currentRace, new[]
{
ModelRace.Miqote,
ModelRace.AuRa,
ModelRace.Hrothgar,
},
RaceEnumExtensions.ToName);
}
}
@ -718,72 +709,54 @@ public class ItemSwapWindow : IDisposable
public void DrawItemSwapPanel()
{
using var tab = ImRaii.TabItem( "Item Swap (WIP)" );
if( !tab )
{
using var tab = ImRaii.TabItem("Item Swap (WIP)");
if (!tab)
return;
}
ImGui.NewLine();
DrawHeaderLine( 300 * ImGuiHelpers.GlobalScale );
DrawHeaderLine(300 * ImGuiHelpers.GlobalScale);
ImGui.NewLine();
DrawSwapBar();
using var table = ImRaii.ListBox( "##swaps", -Vector2.One );
if( _loadException != null )
{
ImGuiUtil.TextWrapped( $"Could not load Customization Swap:\n{_loadException}" );
}
else if( _swapData.Loaded )
{
foreach( var swap in _swapData.Swaps )
{
DrawSwap( swap );
}
}
using var table = ImRaii.ListBox("##swaps", -Vector2.One);
if (_loadException != null)
ImGuiUtil.TextWrapped($"Could not load Customization Swap:\n{_loadException}");
else if (_swapData.Loaded)
foreach (var swap in _swapData.Swaps)
DrawSwap(swap);
else
{
ImGui.TextUnformatted( NonExistentText() );
}
ImGui.TextUnformatted(NonExistentText());
}
private static void DrawSwap( Swap swap )
private static void DrawSwap(Swap swap)
{
var flags = swap.ChildSwaps.Count == 0 ? ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf : ImGuiTreeNodeFlags.DefaultOpen;
using var tree = ImRaii.TreeNode( SwapToString( swap ), flags );
if( !tree )
{
using var tree = ImRaii.TreeNode(SwapToString(swap), flags);
if (!tree)
return;
}
foreach( var child in swap.ChildSwaps )
{
DrawSwap( child );
}
foreach (var child in swap.ChildSwaps)
DrawSwap(child);
}
private void OnCollectionChange( CollectionType collectionType, ModCollection? oldCollection,
ModCollection? newCollection, string _ )
private void OnCollectionChange(CollectionType collectionType, ModCollection? oldCollection,
ModCollection? newCollection, string _)
{
if( collectionType != CollectionType.Current || _mod == null || newCollection == null )
{
if (collectionType != CollectionType.Current || _mod == null || newCollection == null)
return;
}
UpdateMod( _mod, _mod.Index < newCollection.Settings.Count ? newCollection.Settings[ _mod.Index ] : null );
UpdateMod(_mod, _mod.Index < newCollection.Settings.Count ? newCollection.Settings[_mod.Index] : null);
newCollection.ModSettingChanged += OnSettingChange;
if( oldCollection != null )
{
if (oldCollection != null)
oldCollection.ModSettingChanged -= OnSettingChange;
}
}
private void OnSettingChange( ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool inherited )
private void OnSettingChange(ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool inherited)
{
if( modIdx == _mod?.Index )
if (modIdx == _mod?.Index)
{
_swapData.LoadMod( _mod, _modSettings );
_swapData.LoadMod(_mod, _modSettings);
_dirty = true;
}
}

View file

@ -97,7 +97,7 @@ public partial class ModEditWindow
private static bool DrawPreviewDye( MtrlFile file, bool disabled )
{
var (dyeId, (name, dyeColor, _)) = Penumbra.StainManager.StainCombo.CurrentSelection;
var (dyeId, (name, dyeColor, _)) = Penumbra.StainService.StainCombo.CurrentSelection;
var tt = dyeId == 0 ? "Select a preview dye first." : "Apply all preview values corresponding to the dye template and chosen dye where dyeing is enabled.";
if( ImGuiUtil.DrawDisabledButton( "Apply Preview Dye", Vector2.Zero, tt, disabled || dyeId == 0 ) )
{
@ -106,7 +106,7 @@ public partial class ModEditWindow
{
for( var i = 0; i < MtrlFile.ColorSet.RowArray.NumRows; ++i )
{
ret |= file.ApplyDyeTemplate( Penumbra.StainManager.StmFile, j, i, dyeId );
ret |= file.ApplyDyeTemplate( Penumbra.StainService.StmFile, j, i, dyeId );
}
}
@ -115,7 +115,7 @@ public partial class ModEditWindow
ImGui.SameLine();
var label = dyeId == 0 ? "Preview Dye###previewDye" : $"{name} (Preview)###previewDye";
Penumbra.StainManager.StainCombo.Draw( label, dyeColor, string.Empty, true );
Penumbra.StainService.StainCombo.Draw( label, dyeColor, string.Empty, true );
return false;
}
@ -355,10 +355,10 @@ public partial class ModEditWindow
ImGui.TableNextColumn();
if( hasDye )
{
if( Penumbra.StainManager.TemplateCombo.Draw( "##dyeTemplate", dye.Template.ToString(), string.Empty, intSize
if( Penumbra.StainService.TemplateCombo.Draw( "##dyeTemplate", dye.Template.ToString(), string.Empty, intSize
+ ImGui.GetStyle().ScrollbarSize / 2, ImGui.GetTextLineHeightWithSpacing(), ImGuiComboFlags.NoArrowButton ) )
{
file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Template = Penumbra.StainManager.TemplateCombo.CurrentSelection;
file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ].Template = Penumbra.StainService.TemplateCombo.CurrentSelection;
ret = true;
}
@ -378,8 +378,8 @@ public partial class ModEditWindow
private static bool DrawDyePreview( MtrlFile file, int colorSetIdx, int rowIdx, bool disabled, MtrlFile.ColorDyeSet.Row dye, float floatSize )
{
var stain = Penumbra.StainManager.StainCombo.CurrentSelection.Key;
if( stain == 0 || !Penumbra.StainManager.StmFile.Entries.TryGetValue( dye.Template, out var entry ) )
var stain = Penumbra.StainService.StainCombo.CurrentSelection.Key;
if( stain == 0 || !Penumbra.StainService.StmFile.Entries.TryGetValue( dye.Template, out var entry ) )
{
return false;
}
@ -390,7 +390,7 @@ public partial class ModEditWindow
var ret = ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.PaintBrush.ToIconString(), new Vector2( ImGui.GetFrameHeight() ),
"Apply the selected dye to this row.", disabled, true );
ret = ret && file.ApplyDyeTemplate( Penumbra.StainManager.StmFile, colorSetIdx, rowIdx, stain );
ret = ret && file.ApplyDyeTemplate( Penumbra.StainService.StmFile, colorSetIdx, rowIdx, stain );
ImGui.SameLine();
ColorPicker( "##diffusePreview", string.Empty, values.Diffuse, _ => { }, "D" );

View file

@ -13,6 +13,7 @@ using Penumbra.GameData.Enums;
using Penumbra.GameData.Files;
using Penumbra.Import.Textures;
using Penumbra.Mods;
using Penumbra.Services;
using Penumbra.String.Classes;
using Penumbra.Util;
using static Penumbra.Mods.Mod;
@ -22,7 +23,7 @@ namespace Penumbra.UI.Classes;
public partial class ModEditWindow : Window, IDisposable
{
private const string WindowBaseLabel = "###SubModEdit";
internal readonly ItemSwapWindow _swapWindow = new();
internal readonly ItemSwapWindow _swapWindow;
private Editor? _editor;
private Mod? _mod;
@ -567,9 +568,10 @@ public partial class ModEditWindow : Window, IDisposable
return new FullPath( path );
}
public ModEditWindow()
public ModEditWindow(CommunicatorService communicator)
: base( WindowBaseLabel )
{
{
_swapWindow = new ItemSwapWindow( communicator );
_materialTab = new FileEditor< MtrlTab >( "Materials", ".mtrl",
() => _editor?.MtrlFiles ?? Array.Empty< Editor.FileRegistry >(),
DrawMaterialPanel,

View file

@ -14,41 +14,43 @@ using System.IO;
using System.Linq;
using System.Numerics;
using Penumbra.Api.Enums;
using Penumbra.Services;
using Penumbra.Services;
namespace Penumbra.UI.Classes;
public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, ModFileSystemSelector.ModState >
public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSystemSelector.ModState>
{
private readonly FileDialogManager _fileManager = ConfigWindow.SetupFileManager();
private TexToolsImporter? _import;
public ModSettings SelectedSettings { get; private set; } = ModSettings.Empty;
public ModCollection SelectedSettingCollection { get; private set; } = ModCollection.Empty;
private readonly CommunicatorService _communicator;
private readonly FileDialogManager _fileManager = ConfigWindow.SetupFileManager();
private TexToolsImporter? _import;
public ModSettings SelectedSettings { get; private set; } = ModSettings.Empty;
public ModCollection SelectedSettingCollection { get; private set; } = ModCollection.Empty;
public ModFileSystemSelector( ModFileSystem fileSystem )
: base( fileSystem, DalamudServices.KeyState )
public ModFileSystemSelector(CommunicatorService communicator, ModFileSystem fileSystem)
: base(fileSystem, DalamudServices.KeyState)
{
SubscribeRightClickFolder( EnableDescendants, 10 );
SubscribeRightClickFolder( DisableDescendants, 10 );
SubscribeRightClickFolder( InheritDescendants, 15 );
SubscribeRightClickFolder( OwnDescendants, 15 );
SubscribeRightClickFolder( SetDefaultImportFolder, 100 );
SubscribeRightClickLeaf( ToggleLeafFavorite, 0 );
SubscribeRightClickMain( ClearDefaultImportFolder, 100 );
AddButton( AddNewModButton, 0 );
AddButton( AddImportModButton, 1 );
AddButton( AddHelpButton, 2 );
AddButton( DeleteModButton, 1000 );
_communicator = communicator;
SubscribeRightClickFolder(EnableDescendants, 10);
SubscribeRightClickFolder(DisableDescendants, 10);
SubscribeRightClickFolder(InheritDescendants, 15);
SubscribeRightClickFolder(OwnDescendants, 15);
SubscribeRightClickFolder(SetDefaultImportFolder, 100);
SubscribeRightClickLeaf(ToggleLeafFavorite, 0);
SubscribeRightClickMain(ClearDefaultImportFolder, 100);
AddButton(AddNewModButton, 0);
AddButton(AddImportModButton, 1);
AddButton(AddHelpButton, 2);
AddButton(DeleteModButton, 1000);
SetFilterTooltip();
SelectionChanged += OnSelectionChange;
Penumbra.CollectionManager.CollectionChanged += OnCollectionChange;
_communicator.CollectionChange.Event += OnCollectionChange;
Penumbra.CollectionManager.Current.ModSettingChanged += OnSettingChange;
Penumbra.CollectionManager.Current.InheritanceChanged += OnInheritanceChange;
Penumbra.ModManager.ModDataChanged += OnModDataChange;
Penumbra.ModManager.ModDiscoveryStarted += StoreCurrentSelection;
Penumbra.ModManager.ModDiscoveryFinished += RestoreLastSelection;
OnCollectionChange( CollectionType.Current, null, Penumbra.CollectionManager.Current, "" );
OnCollectionChange(CollectionType.Current, null, Penumbra.CollectionManager.Current, "");
}
public override void Dispose()
@ -59,7 +61,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
Penumbra.ModManager.ModDataChanged -= OnModDataChange;
Penumbra.CollectionManager.Current.ModSettingChanged -= OnSettingChange;
Penumbra.CollectionManager.Current.InheritanceChanged -= OnInheritanceChange;
Penumbra.CollectionManager.CollectionChanged -= OnCollectionChange;
_communicator.CollectionChange.Event -= OnCollectionChange;
_import?.Dispose();
_import = null;
}
@ -68,7 +70,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
=> base.SelectedLeaf;
// Customization points.
public override ISortMode< Mod > SortMode
public override ISortMode<Mod> SortMode
=> Penumbra.Config.SortMode;
protected override uint ExpandedFolderColor
@ -89,91 +91,79 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
DrawHelpPopup();
DrawInfoPopup();
if( ImGuiUtil.OpenNameField( "Create New Mod", ref _newModName ) )
{
if (ImGuiUtil.OpenNameField("Create New Mod", ref _newModName))
try
{
var newDir = Mod.Creator.CreateModFolder( Penumbra.ModManager.BasePath, _newModName );
Mod.Creator.CreateMeta( newDir, _newModName, Penumbra.Config.DefaultModAuthor, string.Empty, "1.0", string.Empty );
Mod.Creator.CreateDefaultFiles( newDir );
Penumbra.ModManager.AddMod( newDir );
var newDir = Mod.Creator.CreateModFolder(Penumbra.ModManager.BasePath, _newModName);
Mod.Creator.CreateMeta(newDir, _newModName, Penumbra.Config.DefaultModAuthor, string.Empty, "1.0", string.Empty);
Mod.Creator.CreateDefaultFiles(newDir);
Penumbra.ModManager.AddMod(newDir);
_newModName = string.Empty;
}
catch( Exception e )
catch (Exception e)
{
Penumbra.Log.Error( $"Could not create directory for new Mod {_newModName}:\n{e}" );
Penumbra.Log.Error($"Could not create directory for new Mod {_newModName}:\n{e}");
}
}
while( _modsToAdd.TryDequeue( out var dir ) )
while (_modsToAdd.TryDequeue(out var dir))
{
Penumbra.ModManager.AddMod( dir );
Penumbra.ModManager.AddMod(dir);
var mod = Penumbra.ModManager.LastOrDefault();
if( mod != null )
if (mod != null)
{
MoveModToDefaultDirectory( mod );
SelectByValue( mod );
MoveModToDefaultDirectory(mod);
SelectByValue(mod);
}
}
}
protected override void DrawLeafName( FileSystem< Mod >.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.Value() )
.Push( ImGuiCol.HeaderHovered, 0x4000FFFF, leaf.Value.Favorite );
using var id = ImRaii.PushId( leaf.Value.Index );
ImRaii.TreeNode( leaf.Value.Name, flags ).Dispose();
using var c = ImRaii.PushColor(ImGuiCol.Text, state.Color.Value())
.Push(ImGuiCol.HeaderHovered, 0x4000FFFF, leaf.Value.Favorite);
using var id = ImRaii.PushId(leaf.Value.Index);
ImRaii.TreeNode(leaf.Value.Name, flags).Dispose();
}
// Add custom context menu items.
private static void EnableDescendants( ModFileSystem.Folder folder )
private static void EnableDescendants(ModFileSystem.Folder folder)
{
if( ImGui.MenuItem( "Enable Descendants" ) )
{
SetDescendants( folder, true );
}
if (ImGui.MenuItem("Enable Descendants"))
SetDescendants(folder, true);
}
private static void DisableDescendants( ModFileSystem.Folder folder )
private static void DisableDescendants(ModFileSystem.Folder folder)
{
if( ImGui.MenuItem( "Disable Descendants" ) )
{
SetDescendants( folder, false );
}
if (ImGui.MenuItem("Disable Descendants"))
SetDescendants(folder, false);
}
private static void InheritDescendants( ModFileSystem.Folder folder )
private static void InheritDescendants(ModFileSystem.Folder folder)
{
if( ImGui.MenuItem( "Inherit Descendants" ) )
{
SetDescendants( folder, true, true );
}
if (ImGui.MenuItem("Inherit Descendants"))
SetDescendants(folder, true, true);
}
private static void OwnDescendants( ModFileSystem.Folder folder )
private static void OwnDescendants(ModFileSystem.Folder folder)
{
if( ImGui.MenuItem( "Stop Inheriting Descendants" ) )
{
SetDescendants( folder, false, true );
}
if (ImGui.MenuItem("Stop Inheriting Descendants"))
SetDescendants(folder, false, true);
}
private static void ToggleLeafFavorite( FileSystem< Mod >.Leaf mod )
private static void ToggleLeafFavorite(FileSystem<Mod>.Leaf mod)
{
if( ImGui.MenuItem( mod.Value.Favorite ? "Remove Favorite" : "Mark as Favorite" ) )
{
Penumbra.ModManager.ChangeModFavorite( mod.Value.Index, !mod.Value.Favorite );
}
if (ImGui.MenuItem(mod.Value.Favorite ? "Remove Favorite" : "Mark as Favorite"))
Penumbra.ModManager.ChangeModFavorite(mod.Value.Index, !mod.Value.Favorite);
}
private static void SetDefaultImportFolder( ModFileSystem.Folder folder )
private static void SetDefaultImportFolder(ModFileSystem.Folder folder)
{
if( ImGui.MenuItem( "Set As Default Import Folder" ) )
if (ImGui.MenuItem("Set As Default Import Folder"))
{
var newName = folder.FullName();
if( newName != Penumbra.Config.DefaultImportFolder )
if (newName != Penumbra.Config.DefaultImportFolder)
{
Penumbra.Config.DefaultImportFolder = newName;
Penumbra.Config.Save();
@ -183,7 +173,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
private static void ClearDefaultImportFolder()
{
if( ImGui.MenuItem( "Clear Default Import Folder" ) && Penumbra.Config.DefaultImportFolder.Length > 0 )
if (ImGui.MenuItem("Clear Default Import Folder") && Penumbra.Config.DefaultImportFolder.Length > 0)
{
Penumbra.Config.DefaultImportFolder = string.Empty;
Penumbra.Config.Save();
@ -194,71 +184,63 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
// Add custom buttons.
private string _newModName = string.Empty;
private static void AddNewModButton( Vector2 size )
private static void AddNewModButton(Vector2 size)
{
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.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size, "Create a new, empty mod of a given name.",
!Penumbra.ModManager.Valid, true))
ImGui.OpenPopup("Create New Mod");
}
// Add an import mods button that opens a file selector.
// Only set the initial directory once.
private bool _hasSetFolder;
private void AddImportModButton( Vector2 size )
private void AddImportModButton(Vector2 size)
{
var button = ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.FileImport.ToIconString(), size,
"Import one or multiple mods from Tex Tools Mod Pack Files or Penumbra Mod Pack Files.", !Penumbra.ModManager.Valid, true );
ConfigWindow.OpenTutorial( ConfigWindow.BasicTutorialSteps.ModImport );
if( !button )
{
var button = ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.FileImport.ToIconString(), size,
"Import one or multiple mods from Tex Tools Mod Pack Files or Penumbra Mod Pack Files.", !Penumbra.ModManager.Valid, true);
ConfigWindow.OpenTutorial(ConfigWindow.BasicTutorialSteps.ModImport);
if (!button)
return;
}
var modPath = _hasSetFolder && !Penumbra.Config.AlwaysOpenDefaultImport ? null
: Penumbra.Config.DefaultModImportPath.Length > 0 ? Penumbra.Config.DefaultModImportPath
: Penumbra.Config.ModDirectory.Length > 0 ? Penumbra.Config.ModDirectory : null;
: Penumbra.Config.ModDirectory.Length > 0 ? Penumbra.Config.ModDirectory : null;
_hasSetFolder = true;
_fileManager.OpenFileDialog( "Import Mod Pack",
"Mod Packs{.ttmp,.ttmp2,.pmp},TexTools Mod Packs{.ttmp,.ttmp2},Penumbra Mod Packs{.pmp},Archives{.zip,.7z,.rar}", ( s, f ) =>
_fileManager.OpenFileDialog("Import Mod Pack",
"Mod Packs{.ttmp,.ttmp2,.pmp},TexTools Mod Packs{.ttmp,.ttmp2},Penumbra Mod Packs{.pmp},Archives{.zip,.7z,.rar}", (s, f) =>
{
if( s )
if (s)
{
_import = new TexToolsImporter( Penumbra.ModManager.BasePath, f.Count, f.Select( file => new FileInfo( file ) ),
AddNewMod );
ImGui.OpenPopup( "Import Status" );
_import = new TexToolsImporter(Penumbra.ModManager.BasePath, f.Count, f.Select(file => new FileInfo(file)),
AddNewMod);
ImGui.OpenPopup("Import Status");
}
}, 0, modPath );
}, 0, modPath);
}
// Draw the progress information for import.
private void DrawInfoPopup()
{
var display = ImGui.GetIO().DisplaySize;
var height = Math.Max( display.Y / 4, 15 * ImGui.GetFrameHeightWithSpacing() );
var height = Math.Max(display.Y / 4, 15 * ImGui.GetFrameHeightWithSpacing());
var width = display.X / 8;
var size = new Vector2( width * 2, height );
ImGui.SetNextWindowPos( ImGui.GetMainViewport().GetCenter(), ImGuiCond.Always, Vector2.One / 2 );
ImGui.SetNextWindowSize( size );
using var popup = ImRaii.Popup( "Import Status", ImGuiWindowFlags.Modal );
if( _import == null || !popup.Success )
{
var size = new Vector2(width * 2, height);
ImGui.SetNextWindowPos(ImGui.GetMainViewport().GetCenter(), ImGuiCond.Always, Vector2.One / 2);
ImGui.SetNextWindowSize(size);
using var popup = ImRaii.Popup("Import Status", ImGuiWindowFlags.Modal);
if (_import == null || !popup.Success)
return;
}
using( var child = ImRaii.Child( "##import", new Vector2( -1, size.Y - ImGui.GetFrameHeight() * 2 ) ) )
using (var child = ImRaii.Child("##import", new Vector2(-1, size.Y - ImGui.GetFrameHeight() * 2)))
{
if( child )
{
_import.DrawProgressInfo( new Vector2( -1, ImGui.GetFrameHeight() ) );
}
if (child)
_import.DrawProgressInfo(new Vector2(-1, ImGui.GetFrameHeight()));
}
if( _import.State == ImporterState.Done && ImGui.Button( "Close", -Vector2.UnitX )
|| _import.State != ImporterState.Done && _import.DrawCancelButton( -Vector2.UnitX ) )
if (_import.State == ImporterState.Done && ImGui.Button("Close", -Vector2.UnitX)
|| _import.State != ImporterState.Done && _import.DrawCancelButton(-Vector2.UnitX))
{
_import?.Dispose();
_import = null;
@ -267,100 +249,84 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
}
// Mods need to be added thread-safely outside of iteration.
private readonly ConcurrentQueue< DirectoryInfo > _modsToAdd = new();
private readonly ConcurrentQueue<DirectoryInfo> _modsToAdd = new();
// Clean up invalid directory if necessary.
// Add successfully extracted mods.
private void AddNewMod( FileInfo file, DirectoryInfo? dir, Exception? error )
private void AddNewMod(FileInfo file, DirectoryInfo? dir, Exception? error)
{
if( error != null )
if (error != null)
{
if( dir != null && Directory.Exists( dir.FullName ) )
{
if (dir != null && Directory.Exists(dir.FullName))
try
{
Directory.Delete( dir.FullName, true );
Directory.Delete(dir.FullName, true);
}
catch( Exception e )
catch (Exception e)
{
Penumbra.Log.Error( $"Error cleaning up failed mod extraction of {file.FullName} to {dir.FullName}:\n{e}" );
Penumbra.Log.Error($"Error cleaning up failed mod extraction of {file.FullName} to {dir.FullName}:\n{e}");
}
}
if( error is not OperationCanceledException )
{
Penumbra.Log.Error( $"Error extracting {file.FullName}, mod skipped:\n{error}" );
}
if (error is not OperationCanceledException)
Penumbra.Log.Error($"Error extracting {file.FullName}, mod skipped:\n{error}");
}
else if( dir != null )
else if (dir != null)
{
_modsToAdd.Enqueue( dir );
_modsToAdd.Enqueue(dir);
}
}
private void DeleteModButton( Vector2 size )
private void DeleteModButton(Vector2 size)
{
var keys = Penumbra.Config.DeleteModModifier.IsActive();
var tt = SelectedLeaf == null
? "No mod selected."
: "Delete the currently selected mod entirely from your drive.\n"
+ "This can not be undone.";
if( !keys )
{
if (!keys)
tt += $"\nHold {Penumbra.Config.DeleteModModifier} 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 );
}
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), size, tt, SelectedLeaf == null || !keys, true)
&& Selected != null)
Penumbra.ModManager.DeleteMod(Selected.Index);
}
private static void AddHelpButton( Vector2 size )
private static void AddHelpButton(Vector2 size)
{
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.QuestionCircle.ToIconString(), size, "Open extended help.", false, true ) )
{
ImGui.OpenPopup( "ExtendedHelp" );
}
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.QuestionCircle.ToIconString(), size, "Open extended help.", false, true))
ImGui.OpenPopup("ExtendedHelp");
ConfigWindow.OpenTutorial( ConfigWindow.BasicTutorialSteps.AdvancedHelp );
ConfigWindow.OpenTutorial(ConfigWindow.BasicTutorialSteps.AdvancedHelp);
}
// Helpers.
private static void SetDescendants( ModFileSystem.Folder folder, bool enabled, bool inherit = false )
private static void SetDescendants(ModFileSystem.Folder folder, bool enabled, bool inherit = false)
{
var mods = folder.GetAllDescendants( ISortMode< Mod >.Lexicographical ).OfType< ModFileSystem.Leaf >().Select( l =>
var mods = folder.GetAllDescendants(ISortMode<Mod>.Lexicographical).OfType<ModFileSystem.Leaf>().Select(l =>
{
// Any mod handled here should not stay new.
Penumbra.ModManager.NewMods.Remove( l.Value );
Penumbra.ModManager.NewMods.Remove(l.Value);
return l.Value;
} );
});
if( inherit )
{
Penumbra.CollectionManager.Current.SetMultipleModInheritances( mods, enabled );
}
if (inherit)
Penumbra.CollectionManager.Current.SetMultipleModInheritances(mods, enabled);
else
{
Penumbra.CollectionManager.Current.SetMultipleModStates( mods, enabled );
}
Penumbra.CollectionManager.Current.SetMultipleModStates(mods, enabled);
}
// Automatic cache update functions.
private void OnSettingChange( ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool inherited )
private void OnSettingChange(ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool inherited)
{
// TODO: maybe make more efficient
SetFilterDirty();
if( modIdx == Selected?.Index )
{
OnSelectionChange( Selected, Selected, default );
}
if (modIdx == Selected?.Index)
OnSelectionChange(Selected, Selected, default);
}
private void OnModDataChange( ModDataChangeType type, Mod mod, string? oldName )
private void OnModDataChange(ModDataChangeType type, Mod mod, string? oldName)
{
switch( type )
switch (type)
{
case ModDataChangeType.Name:
case ModDataChangeType.Author:
@ -372,46 +338,44 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
}
}
private void OnInheritanceChange( bool _ )
private void OnInheritanceChange(bool _)
{
SetFilterDirty();
OnSelectionChange( Selected, Selected, default );
OnSelectionChange(Selected, Selected, default);
}
private void OnCollectionChange( CollectionType collectionType, ModCollection? oldCollection, ModCollection? newCollection, string _ )
private void OnCollectionChange(CollectionType collectionType, ModCollection? oldCollection, ModCollection? newCollection, string _)
{
if( collectionType != CollectionType.Current || oldCollection == newCollection )
{
if (collectionType != CollectionType.Current || oldCollection == newCollection)
return;
}
if( oldCollection != null )
if (oldCollection != null)
{
oldCollection.ModSettingChanged -= OnSettingChange;
oldCollection.InheritanceChanged -= OnInheritanceChange;
}
if( newCollection != null )
if (newCollection != null)
{
newCollection.ModSettingChanged += OnSettingChange;
newCollection.InheritanceChanged += OnInheritanceChange;
}
SetFilterDirty();
OnSelectionChange( Selected, Selected, default );
OnSelectionChange(Selected, Selected, default);
}
private void OnSelectionChange( Mod? _1, Mod? newSelection, in ModState _2 )
private void OnSelectionChange(Mod? _1, Mod? newSelection, in ModState _2)
{
if( newSelection == null )
if (newSelection == null)
{
SelectedSettings = ModSettings.Empty;
SelectedSettingCollection = ModCollection.Empty;
}
else
{
( var settings, SelectedSettingCollection ) = Penumbra.CollectionManager.Current[ newSelection.Index ];
SelectedSettings = settings ?? ModSettings.Empty;
(var settings, SelectedSettingCollection) = Penumbra.CollectionManager.Current[newSelection.Index];
SelectedSettings = settings ?? ModSettings.Empty;
}
}
@ -426,92 +390,89 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
private void RestoreLastSelection()
{
if( _lastSelectedDirectory.Length > 0 )
if (_lastSelectedDirectory.Length > 0)
{
var leaf = ( ModFileSystem.Leaf? )FileSystem.Root.GetAllDescendants( ISortMode< Mod >.Lexicographical )
.FirstOrDefault( l => l is ModFileSystem.Leaf m && m.Value.ModPath.FullName == _lastSelectedDirectory );
Select( leaf );
var leaf = (ModFileSystem.Leaf?)FileSystem.Root.GetAllDescendants(ISortMode<Mod>.Lexicographical)
.FirstOrDefault(l => l is ModFileSystem.Leaf m && m.Value.ModPath.FullName == _lastSelectedDirectory);
Select(leaf);
_lastSelectedDirectory = string.Empty;
}
}
// If a default import folder is setup, try to move the given mod in there.
// If the folder does not exist, create it if possible.
private void MoveModToDefaultDirectory( Mod mod )
private void MoveModToDefaultDirectory(Mod mod)
{
if( Penumbra.Config.DefaultImportFolder.Length == 0 )
{
if (Penumbra.Config.DefaultImportFolder.Length == 0)
return;
}
try
{
var leaf = FileSystem.Root.GetChildren( ISortMode< Mod >.Lexicographical )
.FirstOrDefault( f => f is FileSystem< Mod >.Leaf l && l.Value == mod );
if( leaf == null )
{
throw new Exception( "Mod was not found at root." );
}
var leaf = FileSystem.Root.GetChildren(ISortMode<Mod>.Lexicographical)
.FirstOrDefault(f => f is FileSystem<Mod>.Leaf l && l.Value == mod);
if (leaf == null)
throw new Exception("Mod was not found at root.");
var folder = FileSystem.FindOrCreateAllFolders( Penumbra.Config.DefaultImportFolder );
FileSystem.Move( leaf, folder );
var folder = FileSystem.FindOrCreateAllFolders(Penumbra.Config.DefaultImportFolder);
FileSystem.Move(leaf, folder);
}
catch( Exception e )
catch (Exception e)
{
Penumbra.Log.Warning(
$"Could not move newly imported mod {mod.Name} to default import folder {Penumbra.Config.DefaultImportFolder}:\n{e}" );
$"Could not move newly imported mod {mod.Name} to default import folder {Penumbra.Config.DefaultImportFolder}:\n{e}");
}
}
private static void DrawHelpPopup()
{
ImGuiUtil.HelpPopup( "ExtendedHelp", new Vector2( 1000 * ImGuiHelpers.GlobalScale, 34.5f * ImGui.GetTextLineHeightWithSpacing() ), () =>
ImGuiUtil.HelpPopup("ExtendedHelp", new Vector2(1000 * ImGuiHelpers.GlobalScale, 34.5f * ImGui.GetTextLineHeightWithSpacing()), () =>
{
ImGui.Dummy( Vector2.UnitY * ImGui.GetTextLineHeight() );
ImGui.TextUnformatted( "Mod Management" );
ImGui.BulletText( "You can create empty mods or import mods with the buttons in this row." );
ImGui.Dummy(Vector2.UnitY * ImGui.GetTextLineHeight());
ImGui.TextUnformatted("Mod Management");
ImGui.BulletText("You can create empty mods or import mods with the buttons in this row.");
using var indent = ImRaii.PushIndent();
ImGui.BulletText( "Supported formats for import are: .ttmp, .ttmp2, .pmp." );
ImGui.BulletText( "You can also support .zip, .7z or .rar archives, but only if they already contain Penumbra-styled mods with appropriate metadata." );
indent.Pop( 1 );
ImGui.BulletText( "You can also create empty mod folders and delete mods." );
ImGui.BulletText( "For further editing of mods, select them and use the Edit Mod tab in the panel or the Advanced Editing popup." );
ImGui.Dummy( Vector2.UnitY * ImGui.GetTextLineHeight() );
ImGui.TextUnformatted( "Mod Selector" );
ImGui.BulletText( "Select a mod to obtain more information or change settings." );
ImGui.BulletText( "Names are colored according to your config and their current state in the collection:" );
indent.Push();
ImGuiUtil.BulletTextColored( ColorId.EnabledMod.Value(), "enabled in the current collection." );
ImGuiUtil.BulletTextColored( ColorId.DisabledMod.Value(), "disabled in the current collection." );
ImGuiUtil.BulletTextColored( ColorId.InheritedMod.Value(), "enabled due to inheritance from another collection." );
ImGuiUtil.BulletTextColored( ColorId.InheritedDisabledMod.Value(), "disabled due to inheritance from another collection." );
ImGuiUtil.BulletTextColored( ColorId.UndefinedMod.Value(), "unconfigured in all inherited collections." );
ImGuiUtil.BulletTextColored( ColorId.NewMod.Value(),
"newly imported during this session. Will go away when first enabling a mod or when Penumbra is reloaded." );
ImGuiUtil.BulletTextColored( ColorId.HandledConflictMod.Value(),
"enabled and conflicting with another enabled Mod, but on different priorities (i.e. the conflict is solved)." );
ImGuiUtil.BulletTextColored( ColorId.ConflictingMod.Value(),
"enabled and conflicting with another enabled Mod on the same priority." );
ImGuiUtil.BulletTextColored( ColorId.FolderExpanded.Value(), "expanded mod folder." );
ImGuiUtil.BulletTextColored( ColorId.FolderCollapsed.Value(), "collapsed mod folder" );
indent.Pop( 1 );
ImGui.BulletText( "Right-click a mod to enter its sort order, which is its name by default, possibly with a duplicate number." );
indent.Push();
ImGui.BulletText( "A sort order differing from the mods name will not be displayed, it will just be used for ordering." );
ImGui.BulletText("Supported formats for import are: .ttmp, .ttmp2, .pmp.");
ImGui.BulletText(
"If the sort order string contains Forward-Slashes ('/'), the preceding substring will be turned into folders automatically." );
indent.Pop( 1 );
ImGui.BulletText(
"You can drag and drop mods and subfolders into existing folders. Dropping them onto mods is the same as dropping them onto the parent of the mod." );
ImGui.BulletText( "Right-clicking a folder opens a context menu." );
ImGui.BulletText( "Right-clicking empty space allows you to expand or collapse all folders at once." );
ImGui.BulletText( "Use the Filter Mods... input at the top to filter the list for mods whose name or path contain the text." );
"You can also support .zip, .7z or .rar archives, but only if they already contain Penumbra-styled mods with appropriate metadata.");
indent.Pop(1);
ImGui.BulletText("You can also create empty mod folders and delete mods.");
ImGui.BulletText("For further editing of mods, select them and use the Edit Mod tab in the panel or the Advanced Editing popup.");
ImGui.Dummy(Vector2.UnitY * ImGui.GetTextLineHeight());
ImGui.TextUnformatted("Mod Selector");
ImGui.BulletText("Select a mod to obtain more information or change settings.");
ImGui.BulletText("Names are colored according to your config and their current state in the collection:");
indent.Push();
ImGui.BulletText( "You can enter n:[string] to filter only for names, without path." );
ImGui.BulletText( "You can enter c:[string] to filter for Changed Items instead." );
ImGui.BulletText( "You can enter a:[string] to filter for Mod Authors instead." );
indent.Pop( 1 );
ImGui.BulletText( "Use the expandable menu beside the input to filter for mods fulfilling specific criteria." );
} );
ImGuiUtil.BulletTextColored(ColorId.EnabledMod.Value(), "enabled in the current collection.");
ImGuiUtil.BulletTextColored(ColorId.DisabledMod.Value(), "disabled in the current collection.");
ImGuiUtil.BulletTextColored(ColorId.InheritedMod.Value(), "enabled due to inheritance from another collection.");
ImGuiUtil.BulletTextColored(ColorId.InheritedDisabledMod.Value(), "disabled due to inheritance from another collection.");
ImGuiUtil.BulletTextColored(ColorId.UndefinedMod.Value(), "unconfigured in all inherited collections.");
ImGuiUtil.BulletTextColored(ColorId.NewMod.Value(),
"newly imported during this session. Will go away when first enabling a mod or when Penumbra is reloaded.");
ImGuiUtil.BulletTextColored(ColorId.HandledConflictMod.Value(),
"enabled and conflicting with another enabled Mod, but on different priorities (i.e. the conflict is solved).");
ImGuiUtil.BulletTextColored(ColorId.ConflictingMod.Value(),
"enabled and conflicting with another enabled Mod on the same priority.");
ImGuiUtil.BulletTextColored(ColorId.FolderExpanded.Value(), "expanded mod folder.");
ImGuiUtil.BulletTextColored(ColorId.FolderCollapsed.Value(), "collapsed mod folder");
indent.Pop(1);
ImGui.BulletText("Right-click a mod to enter its sort order, which is its name by default, possibly with a duplicate number.");
indent.Push();
ImGui.BulletText("A sort order differing from the mods name will not be displayed, it will just be used for ordering.");
ImGui.BulletText(
"If the sort order string contains Forward-Slashes ('/'), the preceding substring will be turned into folders automatically.");
indent.Pop(1);
ImGui.BulletText(
"You can drag and drop mods and subfolders into existing folders. Dropping them onto mods is the same as dropping them onto the parent of the mod.");
ImGui.BulletText("Right-clicking a folder opens a context menu.");
ImGui.BulletText("Right-clicking empty space allows you to expand or collapse all folders at once.");
ImGui.BulletText("Use the Filter Mods... input at the top to filter the list for mods whose name or path contain the text.");
indent.Push();
ImGui.BulletText("You can enter n:[string] to filter only for names, without path.");
ImGui.BulletText("You can enter c:[string] to filter for Changed Items instead.");
ImGui.BulletText("You can enter a:[string] to filter for Mod Authors instead.");
indent.Pop(1);
ImGui.BulletText("Use the expandable menu beside the input to filter for mods fulfilling specific criteria.");
});
}
}
}