Wow, I accidentally the whole UI

This commit is contained in:
Ottermandias 2023-03-18 21:39:59 +01:00
parent dd8c910597
commit 651c7410ac
87 changed files with 7571 additions and 7280 deletions

View file

@ -30,8 +30,11 @@ public static class Colors
public const uint FilterActive = 0x807070FF;
public const uint TutorialMarker = 0xFF20FFFF;
public const uint TutorialBorder = 0xD00000FF;
public const uint ReniColorButton = 0xFFCC648D;
public const uint ReniColorHovered = 0xFFB070B0;
public const uint ReniColorActive = 0xFF9070E0;
public static (uint DefaultColor, string Name, string Description) Data( this ColorId color )
public static (uint DefaultColor, string Name, string Description) Data(this ColorId color)
=> color switch
{
// @formatter:off
@ -53,6 +56,6 @@ public static class Colors
// @formatter:on
};
public static uint Value( this ColorId color )
=> Penumbra.Config.Colors.TryGetValue( color, out var value ) ? value : color.Data().DefaultColor;
}
public static uint Value(this ColorId color, Configuration config)
=> config.Colors.TryGetValue(color, out var value) ? value : color.Data().DefaultColor;
}

View file

@ -12,34 +12,34 @@ public static class Combos
=> Race( label, 100, current, out race );
public static bool Race( string label, float unscaledWidth, ModelRace current, out ModelRace race )
=> ImGuiUtil.GenericEnumCombo( label, unscaledWidth * ImGuiHelpers.GlobalScale, current, out race, RaceEnumExtensions.ToName, 1 );
=> ImGuiUtil.GenericEnumCombo( label, unscaledWidth * UiHelpers.Scale, current, out race, RaceEnumExtensions.ToName, 1 );
public static bool Gender( string label, Gender current, out Gender gender )
=> Gender( label, 120, current, out gender );
public static bool Gender( string label, float unscaledWidth, Gender current, out Gender gender )
=> ImGuiUtil.GenericEnumCombo( label, unscaledWidth * ImGuiHelpers.GlobalScale, current, out gender, RaceEnumExtensions.ToName, 1 );
=> ImGuiUtil.GenericEnumCombo( label, unscaledWidth * UiHelpers.Scale, current, out gender, RaceEnumExtensions.ToName, 1 );
public static bool EqdpEquipSlot( string label, EquipSlot current, out EquipSlot slot )
=> ImGuiUtil.GenericEnumCombo( label, 100 * ImGuiHelpers.GlobalScale, current, out slot, EquipSlotExtensions.EqdpSlots, EquipSlotExtensions.ToName );
=> ImGuiUtil.GenericEnumCombo( label, 100 * UiHelpers.Scale, current, out slot, EquipSlotExtensions.EqdpSlots, EquipSlotExtensions.ToName );
public static bool EqpEquipSlot( string label, float width, EquipSlot current, out EquipSlot slot )
=> ImGuiUtil.GenericEnumCombo( label, width * ImGuiHelpers.GlobalScale, current, out slot, EquipSlotExtensions.EquipmentSlots, EquipSlotExtensions.ToName );
=> ImGuiUtil.GenericEnumCombo( label, width * UiHelpers.Scale, current, out slot, EquipSlotExtensions.EquipmentSlots, EquipSlotExtensions.ToName );
public static bool AccessorySlot( string label, EquipSlot current, out EquipSlot slot )
=> ImGuiUtil.GenericEnumCombo( label, 100 * ImGuiHelpers.GlobalScale, current, out slot, EquipSlotExtensions.AccessorySlots, EquipSlotExtensions.ToName );
=> ImGuiUtil.GenericEnumCombo( label, 100 * UiHelpers.Scale, current, out slot, EquipSlotExtensions.AccessorySlots, EquipSlotExtensions.ToName );
public static bool SubRace( string label, SubRace current, out SubRace subRace )
=> ImGuiUtil.GenericEnumCombo( label, 150 * ImGuiHelpers.GlobalScale, current, out subRace, RaceEnumExtensions.ToName, 1 );
=> ImGuiUtil.GenericEnumCombo( label, 150 * UiHelpers.Scale, current, out subRace, RaceEnumExtensions.ToName, 1 );
public static bool RspAttribute( string label, RspAttribute current, out RspAttribute attribute )
=> ImGuiUtil.GenericEnumCombo( label, 200 * ImGuiHelpers.GlobalScale, current, out attribute,
=> ImGuiUtil.GenericEnumCombo( label, 200 * UiHelpers.Scale, current, out attribute,
RspAttributeExtensions.ToFullString, 0, 1 );
public static bool EstSlot( string label, EstManipulation.EstType current, out EstManipulation.EstType attribute )
=> ImGuiUtil.GenericEnumCombo( label, 200 * ImGuiHelpers.GlobalScale, current, out attribute );
=> ImGuiUtil.GenericEnumCombo( label, 200 * UiHelpers.Scale, current, out attribute );
public static bool ImcType( string label, ObjectType current, out ObjectType type )
=> ImGuiUtil.GenericEnumCombo( label, 110 * ImGuiHelpers.GlobalScale, current, out type, ObjectTypeExtensions.ValidImcTypes,
=> ImGuiUtil.GenericEnumCombo( label, 110 * UiHelpers.Scale, current, out type, ObjectTypeExtensions.ValidImcTypes,
ObjectTypeExtensions.ToName );
}

View file

@ -332,7 +332,7 @@ public class ItemSwapWindow : IDisposable
CreateMod();
ImGui.SameLine();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 20 * ImGuiHelpers.GlobalScale);
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 20 * UiHelpers.Scale);
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.");
@ -356,7 +356,7 @@ public class ItemSwapWindow : IDisposable
CreateOption();
ImGui.SameLine();
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 20 * ImGuiHelpers.GlobalScale);
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 20 * UiHelpers.Scale);
_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"
@ -415,7 +415,7 @@ public class ItemSwapWindow : IDisposable
ImGui.TextUnformatted( $"Take {article1}" );
ImGui.TableNextColumn();
ImGui.SetNextItemWidth( 100 * ImGuiHelpers.GlobalScale );
ImGui.SetNextItemWidth( 100 * UiHelpers.Scale );
using( var combo = ImRaii.Combo( "##fromType", _slotFrom is EquipSlot.Head ? "Hat" : _slotFrom.ToName() ) )
{
if( combo )
@ -444,7 +444,7 @@ public class ItemSwapWindow : IDisposable
ImGui.TextUnformatted( $"and put {article2} on {article1}" );
ImGui.TableNextColumn();
ImGui.SetNextItemWidth( 100 * ImGuiHelpers.GlobalScale );
ImGui.SetNextItemWidth( 100 * UiHelpers.Scale );
using( var combo = ImRaii.Combo( "##toType", _slotTo.ToName() ) )
{
if( combo )
@ -636,7 +636,7 @@ public class ItemSwapWindow : IDisposable
ImGui.TextUnformatted(text);
ImGui.TableNextColumn();
ImGui.SetNextItemWidth(InputWidth * ImGuiHelpers.GlobalScale);
ImGui.SetNextItemWidth(InputWidth * UiHelpers.Scale);
if (ImGui.InputInt("##targetId", ref _targetId, 0, 0))
_targetId = Math.Clamp(_targetId, 0, byte.MaxValue);
@ -650,7 +650,7 @@ public class ItemSwapWindow : IDisposable
ImGui.TextUnformatted(text);
ImGui.TableNextColumn();
ImGui.SetNextItemWidth(InputWidth * ImGuiHelpers.GlobalScale);
ImGui.SetNextItemWidth(InputWidth * UiHelpers.Scale);
if (ImGui.InputInt("##sourceId", ref _sourceId, 0, 0))
_sourceId = Math.Clamp(_sourceId, 0, byte.MaxValue);
@ -714,7 +714,7 @@ public class ItemSwapWindow : IDisposable
return;
ImGui.NewLine();
DrawHeaderLine(300 * ImGuiHelpers.GlobalScale);
DrawHeaderLine(300 * UiHelpers.Scale);
ImGui.NewLine();
DrawSwapBar();

View file

@ -5,26 +5,27 @@ using System.Numerics;
using System.Reflection;
using Dalamud.Interface;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Internal.Notifications;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Files;
using Penumbra.Mods;
using Penumbra.Services;
using Penumbra.Services;
using Penumbra.String.Classes;
namespace Penumbra.UI.Classes;
public partial class ModEditWindow
{
private class FileEditor< T > where T : class, IWritable
private class FileEditor<T> where T : class, IWritable
{
private readonly string _tabName;
private readonly string _fileType;
private readonly Func< IReadOnlyList< Mod.Editor.FileRegistry > > _getFiles;
private readonly Func< T, bool, bool > _drawEdit;
private readonly Func< string > _getInitialPath;
private readonly Func< byte[], T? > _parseFile;
private readonly string _tabName;
private readonly string _fileType;
private readonly Func<IReadOnlyList<Mod.Editor.FileRegistry>> _getFiles;
private readonly Func<T, bool, bool> _drawEdit;
private readonly Func<string> _getInitialPath;
private readonly Func<byte[], T?> _parseFile;
private Mod.Editor.FileRegistry? _currentPath;
private T? _currentFile;
@ -36,29 +37,28 @@ public partial class ModEditWindow
private T? _defaultFile;
private Exception? _defaultException;
private IReadOnlyList< Mod.Editor.FileRegistry > _list = null!;
private IReadOnlyList<Mod.Editor.FileRegistry> _list = null!;
private readonly FileDialogManager _fileDialog = ConfigWindow.SetupFileManager();
private readonly FileDialogService _fileDialog;
public FileEditor( string tabName, string fileType, Func< IReadOnlyList< Mod.Editor.FileRegistry > > getFiles,
Func< T, bool, bool > drawEdit, Func< string > getInitialPath, Func< byte[], T? >? parseFile )
public FileEditor(string tabName, string fileType, FileDialogService fileDialog, Func<IReadOnlyList<Mod.Editor.FileRegistry>> getFiles,
Func<T, bool, bool> drawEdit, Func<string> getInitialPath, Func<byte[], T?>? parseFile)
{
_tabName = tabName;
_fileType = fileType;
_getFiles = getFiles;
_drawEdit = drawEdit;
_getInitialPath = getInitialPath;
_fileDialog = fileDialog;
_parseFile = parseFile ?? DefaultParseFile;
}
public void Draw()
{
_list = _getFiles();
using var tab = ImRaii.TabItem( _tabName );
if( !tab )
{
using var tab = ImRaii.TabItem(_tabName);
if (!tab)
return;
}
ImGui.NewLine();
DrawFileSelectCombo();
@ -67,35 +67,35 @@ public partial class ModEditWindow
ResetButton();
ImGui.SameLine();
DefaultInput();
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
DrawFilePanel();
}
private void DefaultInput()
{
using var spacing = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = 3 * ImGuiHelpers.GlobalScale } );
ImGui.SetNextItemWidth( ImGui.GetContentRegionAvail().X - 3 * ImGuiHelpers.GlobalScale - ImGui.GetFrameHeight() );
ImGui.InputTextWithHint( "##defaultInput", "Input game path to compare...", ref _defaultPath, Utf8GamePath.MaxGamePathLength );
using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = 3 * UiHelpers.Scale });
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - 3 * UiHelpers.Scale - ImGui.GetFrameHeight());
ImGui.InputTextWithHint("##defaultInput", "Input game path to compare...", ref _defaultPath, Utf8GamePath.MaxGamePathLength);
_inInput = ImGui.IsItemActive();
if( ImGui.IsItemDeactivatedAfterEdit() && _defaultPath.Length > 0 )
if (ImGui.IsItemDeactivatedAfterEdit() && _defaultPath.Length > 0)
{
_fileDialog.Reset();
try
{
var file = DalamudServices.GameData.GetFile( _defaultPath );
if( file != null )
var file = DalamudServices.SGameData.GetFile(_defaultPath);
if (file != null)
{
_defaultException = null;
_defaultFile = _parseFile( file.Data );
_defaultFile = _parseFile(file.Data);
}
else
{
_defaultFile = null;
_defaultException = new Exception( "File does not exist." );
_defaultException = new Exception("File does not exist.");
}
}
catch( Exception e )
catch (Exception e)
{
_defaultFile = null;
_defaultException = e;
@ -103,25 +103,23 @@ public partial class ModEditWindow
}
ImGui.SameLine();
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Save.ToIconString(), new Vector2( ImGui.GetFrameHeight() ), "Export this file.", _defaultFile == null, true ) )
{
_fileDialog.SaveFileDialog( $"Export {_defaultPath} to...", _fileType, Path.GetFileNameWithoutExtension( _defaultPath ), _fileType, ( success, name ) =>
{
if( !success )
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Save.ToIconString(), new Vector2(ImGui.GetFrameHeight()), "Export this file.",
_defaultFile == null, true))
_fileDialog.OpenSavePicker($"Export {_defaultPath} to...", _fileType, Path.GetFileNameWithoutExtension(_defaultPath), _fileType,
(success, name) =>
{
return;
}
if (!success)
return;
try
{
File.WriteAllBytes( name, _defaultFile?.Write() ?? throw new Exception( "File invalid." ) );
}
catch( Exception e )
{
Penumbra.Log.Error( $"Could not export {_defaultPath}:\n{e}" );
}
}, _getInitialPath() );
}
try
{
File.WriteAllBytes(name, _defaultFile?.Write() ?? throw new Exception("File invalid."));
}
catch (Exception e)
{
Penumbra.ChatService.NotificationMessage($"Could not export {_defaultPath}:\n{e}", "Error", NotificationType.Error);
}
}, _getInitialPath(), false);
_fileDialog.Draw();
}
@ -136,64 +134,58 @@ public partial class ModEditWindow
private void DrawFileSelectCombo()
{
ImGui.SetNextItemWidth( ImGui.GetContentRegionAvail().X );
using var combo = ImRaii.Combo( "##fileSelect", _currentPath?.RelPath.ToString() ?? $"Select {_fileType} File..." );
if( !combo )
{
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
using var combo = ImRaii.Combo("##fileSelect", _currentPath?.RelPath.ToString() ?? $"Select {_fileType} File...");
if (!combo)
return;
}
foreach( var file in _list )
foreach (var file in _list)
{
if( ImGui.Selectable( file.RelPath.ToString(), ReferenceEquals( file, _currentPath ) ) )
{
UpdateCurrentFile( file );
}
if (ImGui.Selectable(file.RelPath.ToString(), ReferenceEquals(file, _currentPath)))
UpdateCurrentFile(file);
if( ImGui.IsItemHovered() )
if (ImGui.IsItemHovered())
{
using var tt = ImRaii.Tooltip();
ImGui.TextUnformatted( "All Game Paths" );
ImGui.TextUnformatted("All Game Paths");
ImGui.Separator();
using var t = ImRaii.Table( "##Tooltip", 2, ImGuiTableFlags.SizingFixedFit );
foreach( var (option, gamePath) in file.SubModUsage )
using var t = ImRaii.Table("##Tooltip", 2, ImGuiTableFlags.SizingFixedFit);
foreach (var (option, gamePath) in file.SubModUsage)
{
ImGui.TableNextColumn();
ConfigWindow.Text( gamePath.Path );
UiHelpers.Text(gamePath.Path);
ImGui.TableNextColumn();
using var color = ImRaii.PushColor( ImGuiCol.Text, ColorId.ItemId.Value() );
ImGui.TextUnformatted( option.FullName );
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ItemId.Value(Penumbra.Config));
ImGui.TextUnformatted(option.FullName);
}
}
if( file.SubModUsage.Count > 0 )
if (file.SubModUsage.Count > 0)
{
ImGui.SameLine();
using var color = ImRaii.PushColor( ImGuiCol.Text, ColorId.ItemId.Value() );
ImGuiUtil.RightAlign( file.SubModUsage[ 0 ].Item2.Path.ToString() );
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ItemId.Value(Penumbra.Config));
ImGuiUtil.RightAlign(file.SubModUsage[0].Item2.Path.ToString());
}
}
}
private static T? DefaultParseFile( byte[] bytes )
=> Activator.CreateInstance( typeof( T ), bytes ) as T;
private static T? DefaultParseFile(byte[] bytes)
=> Activator.CreateInstance(typeof(T), bytes) as T;
private void UpdateCurrentFile( Mod.Editor.FileRegistry path )
private void UpdateCurrentFile(Mod.Editor.FileRegistry path)
{
if( ReferenceEquals( _currentPath, path ) )
{
if (ReferenceEquals(_currentPath, path))
return;
}
_changed = false;
_currentPath = path;
_currentException = null;
try
{
var bytes = File.ReadAllBytes( _currentPath.File.FullName );
_currentFile = _parseFile( bytes );
var bytes = File.ReadAllBytes(_currentPath.File.FullName);
_currentFile = _parseFile(bytes);
}
catch( Exception e )
catch (Exception e)
{
_currentFile = null;
_currentException = e;
@ -202,76 +194,74 @@ public partial class ModEditWindow
private void SaveButton()
{
if( ImGuiUtil.DrawDisabledButton( "Save to File", Vector2.Zero,
$"Save the selected {_fileType} file with all changes applied. This is not revertible.", !_changed ) )
if (ImGuiUtil.DrawDisabledButton("Save to File", Vector2.Zero,
$"Save the selected {_fileType} file with all changes applied. This is not revertible.", !_changed))
{
File.WriteAllBytes( _currentPath!.File.FullName, _currentFile!.Write() );
File.WriteAllBytes(_currentPath!.File.FullName, _currentFile!.Write());
_changed = false;
}
}
private void ResetButton()
{
if( ImGuiUtil.DrawDisabledButton( "Reset Changes", Vector2.Zero,
$"Reset all changes made to the {_fileType} file.", !_changed ) )
if (ImGuiUtil.DrawDisabledButton("Reset Changes", Vector2.Zero,
$"Reset all changes made to the {_fileType} file.", !_changed))
{
var tmp = _currentPath;
_currentPath = null;
UpdateCurrentFile( tmp! );
UpdateCurrentFile(tmp!);
}
}
private void DrawFilePanel()
{
using var child = ImRaii.Child( "##filePanel", -Vector2.One, true );
if( !child )
{
using var child = ImRaii.Child("##filePanel", -Vector2.One, true);
if (!child)
return;
}
if( _currentPath != null )
if (_currentPath != null)
{
if( _currentFile == null )
if (_currentFile == null)
{
ImGui.TextUnformatted( $"Could not parse selected {_fileType} file." );
if( _currentException != null )
ImGui.TextUnformatted($"Could not parse selected {_fileType} file.");
if (_currentException != null)
{
using var tab = ImRaii.PushIndent();
ImGuiUtil.TextWrapped( _currentException.ToString() );
ImGuiUtil.TextWrapped(_currentException.ToString());
}
}
else
{
using var id = ImRaii.PushId( 0 );
_changed |= _drawEdit( _currentFile, false );
using var id = ImRaii.PushId(0);
_changed |= _drawEdit(_currentFile, false);
}
}
if( !_inInput && _defaultPath.Length > 0 )
if (!_inInput && _defaultPath.Length > 0)
{
if( _currentPath != null )
if (_currentPath != null)
{
ImGui.NewLine();
ImGui.NewLine();
ImGui.TextUnformatted( $"Preview of {_defaultPath}:" );
ImGui.TextUnformatted($"Preview of {_defaultPath}:");
ImGui.Separator();
}
if( _defaultFile == null )
if (_defaultFile == null)
{
ImGui.TextUnformatted( $"Could not parse provided {_fileType} game file:\n" );
if( _defaultException != null )
ImGui.TextUnformatted($"Could not parse provided {_fileType} game file:\n");
if (_defaultException != null)
{
using var tab = ImRaii.PushIndent();
ImGuiUtil.TextWrapped( _defaultException.ToString() );
ImGuiUtil.TextWrapped(_defaultException.ToString());
}
}
else
{
using var id = ImRaii.PushId( 1 );
_drawEdit( _defaultFile, true );
using var id = ImRaii.PushId(1);
_drawEdit(_defaultFile, true);
}
}
}
}
}
}

View file

@ -14,184 +14,156 @@ namespace Penumbra.UI.Classes;
public partial class ModEditWindow
{
private readonly HashSet< Mod.Editor.FileRegistry > _selectedFiles = new(256);
private LowerString _fileFilter = LowerString.Empty;
private bool _showGamePaths = true;
private string _gamePathEdit = string.Empty;
private int _fileIdx = -1;
private int _pathIdx = -1;
private int _folderSkip = 0;
private bool _overviewMode = false;
private LowerString _fileOverviewFilter1 = LowerString.Empty;
private LowerString _fileOverviewFilter2 = LowerString.Empty;
private LowerString _fileOverviewFilter3 = LowerString.Empty;
private readonly HashSet<Mod.Editor.FileRegistry> _selectedFiles = new(256);
private LowerString _fileFilter = LowerString.Empty;
private bool _showGamePaths = true;
private string _gamePathEdit = string.Empty;
private int _fileIdx = -1;
private int _pathIdx = -1;
private int _folderSkip = 0;
private bool _overviewMode = false;
private LowerString _fileOverviewFilter1 = LowerString.Empty;
private LowerString _fileOverviewFilter2 = LowerString.Empty;
private LowerString _fileOverviewFilter3 = LowerString.Empty;
private bool CheckFilter( Mod.Editor.FileRegistry registry )
=> _fileFilter.IsEmpty || registry.File.FullName.Contains( _fileFilter.Lower, StringComparison.OrdinalIgnoreCase );
private bool CheckFilter(Mod.Editor.FileRegistry registry)
=> _fileFilter.IsEmpty || registry.File.FullName.Contains(_fileFilter.Lower, StringComparison.OrdinalIgnoreCase);
private bool CheckFilter( (Mod.Editor.FileRegistry, int) p )
=> CheckFilter( p.Item1 );
private bool CheckFilter((Mod.Editor.FileRegistry, int) p)
=> CheckFilter(p.Item1);
private void DrawFileTab()
{
using var tab = ImRaii.TabItem( "File Redirections" );
if( !tab )
{
using var tab = ImRaii.TabItem("File Redirections");
if (!tab)
return;
}
DrawOptionSelectHeader();
DrawButtonHeader();
if( _overviewMode )
{
if (_overviewMode)
DrawFileManagementOverview();
}
else
{
DrawFileManagementNormal();
}
using var child = ImRaii.Child( "##files", -Vector2.One, true );
if( !child )
{
using var child = ImRaii.Child("##files", -Vector2.One, true);
if (!child)
return;
}
if( _overviewMode )
{
if (_overviewMode)
DrawFilesOverviewMode();
}
else
{
DrawFilesNormalMode();
}
}
private void DrawFilesOverviewMode()
{
var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y;
var skips = ImGuiClip.GetNecessarySkips( height );
var skips = ImGuiClip.GetNecessarySkips(height);
using var list = ImRaii.Table( "##table", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInnerV, -Vector2.One );
using var list = ImRaii.Table("##table", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInnerV, -Vector2.One);
if( !list )
{
if (!list)
return;
}
var width = ImGui.GetContentRegionAvail().X / 8;
ImGui.TableSetupColumn( "##file", ImGuiTableColumnFlags.WidthFixed, width * 3 );
ImGui.TableSetupColumn( "##path", ImGuiTableColumnFlags.WidthFixed, width * 3 + ImGui.GetStyle().FrameBorderSize );
ImGui.TableSetupColumn( "##option", ImGuiTableColumnFlags.WidthFixed, width * 2 );
ImGui.TableSetupColumn("##file", ImGuiTableColumnFlags.WidthFixed, width * 3);
ImGui.TableSetupColumn("##path", ImGuiTableColumnFlags.WidthFixed, width * 3 + ImGui.GetStyle().FrameBorderSize);
ImGui.TableSetupColumn("##option", ImGuiTableColumnFlags.WidthFixed, width * 2);
var idx = 0;
var files = _editor!.AvailableFiles.SelectMany( f =>
var files = _editor!.AvailableFiles.SelectMany(f =>
{
var file = f.RelPath.ToString();
return f.SubModUsage.Count == 0
? Enumerable.Repeat( ( file, "Unused", string.Empty, 0x40000080u ), 1 )
: f.SubModUsage.Select( s => ( file, s.Item2.ToString(), s.Item1.FullName,
_editor.CurrentOption == s.Item1 && _mod!.HasOptions ? 0x40008000u : 0u ) );
} );
? Enumerable.Repeat((file, "Unused", string.Empty, 0x40000080u), 1)
: f.SubModUsage.Select(s => (file, s.Item2.ToString(), s.Item1.FullName,
_editor.CurrentOption == s.Item1 && _mod!.HasOptions ? 0x40008000u : 0u));
});
void DrawLine( (string, string, string, uint) data )
void DrawLine((string, string, string, uint) data)
{
using var id = ImRaii.PushId( idx++ );
using var id = ImRaii.PushId(idx++);
ImGui.TableNextColumn();
if( data.Item4 != 0 )
{
ImGui.TableSetBgColor( ImGuiTableBgTarget.CellBg, data.Item4 );
}
if (data.Item4 != 0)
ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, data.Item4);
ImGuiUtil.CopyOnClickSelectable( data.Item1 );
ImGuiUtil.CopyOnClickSelectable(data.Item1);
ImGui.TableNextColumn();
if( data.Item4 != 0 )
{
ImGui.TableSetBgColor( ImGuiTableBgTarget.CellBg, data.Item4 );
}
if (data.Item4 != 0)
ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, data.Item4);
ImGuiUtil.CopyOnClickSelectable( data.Item2 );
ImGuiUtil.CopyOnClickSelectable(data.Item2);
ImGui.TableNextColumn();
if( data.Item4 != 0 )
{
ImGui.TableSetBgColor( ImGuiTableBgTarget.CellBg, data.Item4 );
}
if (data.Item4 != 0)
ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, data.Item4);
ImGuiUtil.CopyOnClickSelectable( data.Item3 );
ImGuiUtil.CopyOnClickSelectable(data.Item3);
}
bool Filter( (string, string, string, uint) data )
=> _fileOverviewFilter1.IsContained( data.Item1 )
&& _fileOverviewFilter2.IsContained( data.Item2 )
&& _fileOverviewFilter3.IsContained( data.Item3 );
bool Filter((string, string, string, uint) data)
=> _fileOverviewFilter1.IsContained(data.Item1)
&& _fileOverviewFilter2.IsContained(data.Item2)
&& _fileOverviewFilter3.IsContained(data.Item3);
var end = ImGuiClip.FilteredClippedDraw( files, skips, Filter, DrawLine );
ImGuiClip.DrawEndDummy( end, height );
var end = ImGuiClip.FilteredClippedDraw(files, skips, Filter, DrawLine);
ImGuiClip.DrawEndDummy(end, height);
}
private void DrawFilesNormalMode()
{
using var list = ImRaii.Table( "##table", 1 );
using var list = ImRaii.Table("##table", 1);
if( !list )
{
if (!list)
return;
}
foreach( var (registry, i) in _editor!.AvailableFiles.WithIndex().Where( CheckFilter ) )
foreach (var (registry, i) in _editor!.AvailableFiles.WithIndex().Where(CheckFilter))
{
using var id = ImRaii.PushId( i );
using var id = ImRaii.PushId(i);
ImGui.TableNextColumn();
DrawSelectable( registry );
DrawSelectable(registry);
if( !_showGamePaths )
{
if (!_showGamePaths)
continue;
}
using var indent = ImRaii.PushIndent( 50f );
for( var j = 0; j < registry.SubModUsage.Count; ++j )
using var indent = ImRaii.PushIndent(50f);
for (var j = 0; j < registry.SubModUsage.Count; ++j)
{
var (subMod, gamePath) = registry.SubModUsage[ j ];
if( subMod != _editor.CurrentOption )
{
var (subMod, gamePath) = registry.SubModUsage[j];
if (subMod != _editor.CurrentOption)
continue;
}
PrintGamePath( i, j, registry, subMod, gamePath );
PrintGamePath(i, j, registry, subMod, gamePath);
}
PrintNewGamePath( i, registry, _editor.CurrentOption );
PrintNewGamePath(i, registry, _editor.CurrentOption);
}
}
private static string DrawFileTooltip( Mod.Editor.FileRegistry registry, ColorId color )
private static string DrawFileTooltip(Mod.Editor.FileRegistry registry, ColorId color)
{
(string, int) GetMulti()
{
var groups = registry.SubModUsage.GroupBy( s => s.Item1 ).ToArray();
return ( string.Join( "\n", groups.Select( g => g.Key.Name ) ), groups.Length );
var groups = registry.SubModUsage.GroupBy(s => s.Item1).ToArray();
return (string.Join("\n", groups.Select(g => g.Key.Name)), groups.Length);
}
var (text, groupCount) = color switch
{
ColorId.ConflictingMod => ( string.Empty, 0 ),
ColorId.NewMod => ( registry.SubModUsage[ 0 ].Item1.Name, 1 ),
ColorId.ConflictingMod => (string.Empty, 0),
ColorId.NewMod => (registry.SubModUsage[0].Item1.Name, 1),
ColorId.InheritedMod => GetMulti(),
_ => ( string.Empty, 0 ),
_ => (string.Empty, 0),
};
if( text.Length > 0 && ImGui.IsItemHovered() )
{
ImGui.SetTooltip( text );
}
if (text.Length > 0 && ImGui.IsItemHovered())
ImGui.SetTooltip(text);
return ( groupCount, registry.SubModUsage.Count ) switch
return (groupCount, registry.SubModUsage.Count) switch
{
(0, 0) => "(unused)",
(1, 1) => "(used 1 time)",
@ -200,95 +172,91 @@ public partial class ModEditWindow
};
}
private void DrawSelectable( Mod.Editor.FileRegistry registry )
private void DrawSelectable(Mod.Editor.FileRegistry registry)
{
var selected = _selectedFiles.Contains( registry );
var color = registry.SubModUsage.Count == 0 ? ColorId.ConflictingMod :
registry.CurrentUsage == registry.SubModUsage.Count ? ColorId.NewMod : ColorId.InheritedMod;
using var c = ImRaii.PushColor( ImGuiCol.Text, color.Value() );
if( ConfigWindow.Selectable( registry.RelPath.Path, selected ) )
var selected = _selectedFiles.Contains(registry);
var color = registry.SubModUsage.Count == 0 ? ColorId.ConflictingMod :
registry.CurrentUsage == registry.SubModUsage.Count ? ColorId.NewMod : ColorId.InheritedMod;
using var c = ImRaii.PushColor(ImGuiCol.Text, color.Value(Penumbra.Config));
if (UiHelpers.Selectable(registry.RelPath.Path, selected))
{
if( selected )
{
_selectedFiles.Remove( registry );
}
if (selected)
_selectedFiles.Remove(registry);
else
{
_selectedFiles.Add( registry );
}
_selectedFiles.Add(registry);
}
var rightText = DrawFileTooltip( registry, color );
var rightText = DrawFileTooltip(registry, color);
ImGui.SameLine();
ImGuiUtil.RightAlign( rightText );
ImGuiUtil.RightAlign(rightText);
}
private void PrintGamePath( int i, int j, Mod.Editor.FileRegistry registry, ISubMod subMod, Utf8GamePath gamePath )
private void PrintGamePath(int i, int j, Mod.Editor.FileRegistry registry, ISubMod subMod, Utf8GamePath gamePath)
{
using var id = ImRaii.PushId( j );
using var id = ImRaii.PushId(j);
ImGui.TableNextColumn();
var tmp = _fileIdx == i && _pathIdx == j ? _gamePathEdit : gamePath.ToString();
var pos = ImGui.GetCursorPosX() - ImGui.GetFrameHeight();
ImGui.SetNextItemWidth( -1 );
if( ImGui.InputText( string.Empty, ref tmp, Utf8GamePath.MaxGamePathLength ) )
ImGui.SetNextItemWidth(-1);
if (ImGui.InputText(string.Empty, ref tmp, Utf8GamePath.MaxGamePathLength))
{
_fileIdx = i;
_pathIdx = j;
_gamePathEdit = tmp;
}
ImGuiUtil.HoverTooltip( "Clear completely to remove the path from this mod." );
ImGuiUtil.HoverTooltip("Clear completely to remove the path from this mod.");
if( ImGui.IsItemDeactivatedAfterEdit() )
if (ImGui.IsItemDeactivatedAfterEdit())
{
if( Utf8GamePath.FromString( _gamePathEdit, out var path, false ) )
{
_editor!.SetGamePath( _fileIdx, _pathIdx, path );
}
if (Utf8GamePath.FromString(_gamePathEdit, out var path, false))
_editor!.SetGamePath(_fileIdx, _pathIdx, path);
_fileIdx = -1;
_pathIdx = -1;
}
else if( _fileIdx == i && _pathIdx == j && ( !Utf8GamePath.FromString( _gamePathEdit, out var path, false )
|| !path.IsEmpty && !path.Equals( gamePath ) && !_editor!.CanAddGamePath( path )) )
else if (_fileIdx == i
&& _pathIdx == j
&& (!Utf8GamePath.FromString(_gamePathEdit, out var path, false)
|| !path.IsEmpty && !path.Equals(gamePath) && !_editor!.CanAddGamePath(path)))
{
ImGui.SameLine();
ImGui.SetCursorPosX( pos );
using var font = ImRaii.PushFont( UiBuilder.IconFont );
ImGuiUtil.TextColored( 0xFF0000FF, FontAwesomeIcon.TimesCircle.ToIconString() );
ImGui.SetCursorPosX(pos);
using var font = ImRaii.PushFont(UiBuilder.IconFont);
ImGuiUtil.TextColored(0xFF0000FF, FontAwesomeIcon.TimesCircle.ToIconString());
}
}
private void PrintNewGamePath( int i, Mod.Editor.FileRegistry registry, ISubMod subMod )
private void PrintNewGamePath(int i, Mod.Editor.FileRegistry registry, ISubMod subMod)
{
var tmp = _fileIdx == i && _pathIdx == -1 ? _gamePathEdit : string.Empty;
var pos = ImGui.GetCursorPosX() - ImGui.GetFrameHeight();
ImGui.SetNextItemWidth( -1 );
if( ImGui.InputTextWithHint( "##new", "Add New Path...", ref tmp, Utf8GamePath.MaxGamePathLength ) )
ImGui.SetNextItemWidth(-1);
if (ImGui.InputTextWithHint("##new", "Add New Path...", ref tmp, Utf8GamePath.MaxGamePathLength))
{
_fileIdx = i;
_pathIdx = -1;
_gamePathEdit = tmp;
}
if( ImGui.IsItemDeactivatedAfterEdit() )
if (ImGui.IsItemDeactivatedAfterEdit())
{
if( Utf8GamePath.FromString( _gamePathEdit, out var path, false ) && !path.IsEmpty )
{
_editor!.SetGamePath( _fileIdx, _pathIdx, path );
}
if (Utf8GamePath.FromString(_gamePathEdit, out var path, false) && !path.IsEmpty)
_editor!.SetGamePath(_fileIdx, _pathIdx, path);
_fileIdx = -1;
_pathIdx = -1;
}
else if( _fileIdx == i && _pathIdx == -1 && (!Utf8GamePath.FromString( _gamePathEdit, out var path, false )
|| !path.IsEmpty && !_editor!.CanAddGamePath( path )) )
else if (_fileIdx == i
&& _pathIdx == -1
&& (!Utf8GamePath.FromString(_gamePathEdit, out var path, false)
|| !path.IsEmpty && !_editor!.CanAddGamePath(path)))
{
ImGui.SameLine();
ImGui.SetCursorPosX( pos );
using var font = ImRaii.PushFont( UiBuilder.IconFont );
ImGuiUtil.TextColored( 0xFF0000FF, FontAwesomeIcon.TimesCircle.ToIconString() );
ImGui.SetCursorPosX(pos);
using var font = ImRaii.PushFont(UiBuilder.IconFont);
ImGuiUtil.TextColored(0xFF0000FF, FontAwesomeIcon.TimesCircle.ToIconString());
}
}
@ -296,115 +264,97 @@ public partial class ModEditWindow
{
ImGui.NewLine();
using var spacing = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, new Vector2( 3 * ImGuiHelpers.GlobalScale, 0 ) );
ImGui.SetNextItemWidth( 30 * ImGuiHelpers.GlobalScale );
ImGui.DragInt( "##skippedFolders", ref _folderSkip, 0.01f, 0, 10 );
ImGuiUtil.HoverTooltip( "Skip the first N folders when automatically constructing the game path from the file path." );
using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(3 * UiHelpers.Scale, 0));
ImGui.SetNextItemWidth(30 * UiHelpers.Scale);
ImGui.DragInt("##skippedFolders", ref _folderSkip, 0.01f, 0, 10);
ImGuiUtil.HoverTooltip("Skip the first N folders when automatically constructing the game path from the file path.");
ImGui.SameLine();
spacing.Pop();
if( ImGui.Button( "Add Paths" ) )
{
_editor!.AddPathsToSelected( _editor!.AvailableFiles.Where( _selectedFiles.Contains ), _folderSkip );
}
if (ImGui.Button("Add Paths"))
_editor!.AddPathsToSelected(_editor!.AvailableFiles.Where(_selectedFiles.Contains), _folderSkip);
ImGuiUtil.HoverTooltip(
"Add the file path converted to a game path to all selected files for the current option, optionally skipping the first N folders." );
"Add the file path converted to a game path to all selected files for the current option, optionally skipping the first N folders.");
ImGui.SameLine();
if( ImGui.Button( "Remove Paths" ) )
{
_editor!.RemovePathsFromSelected( _editor!.AvailableFiles.Where( _selectedFiles.Contains ) );
}
if (ImGui.Button("Remove Paths"))
_editor!.RemovePathsFromSelected(_editor!.AvailableFiles.Where(_selectedFiles.Contains));
ImGuiUtil.HoverTooltip( "Remove all game paths associated with the selected files in the current option." );
ImGuiUtil.HoverTooltip("Remove all game paths associated with the selected files in the current option.");
ImGui.SameLine();
if( ImGui.Button( "Delete Selected Files" ) )
{
_editor!.DeleteFiles( _editor!.AvailableFiles.Where( _selectedFiles.Contains ) );
}
if (ImGui.Button("Delete Selected Files"))
_editor!.DeleteFiles(_editor!.AvailableFiles.Where(_selectedFiles.Contains));
ImGuiUtil.HoverTooltip(
"Delete all selected files entirely from your filesystem, but not their file associations in the mod, if there are any.\n!!!This can not be reverted!!!" );
"Delete all selected files entirely from your filesystem, but not their file associations in the mod, if there are any.\n!!!This can not be reverted!!!");
ImGui.SameLine();
var changes = _editor!.FileChanges;
var tt = changes ? "Apply the current file setup to the currently selected option." : "No changes made.";
if( ImGuiUtil.DrawDisabledButton( "Apply Changes", Vector2.Zero, tt, !changes ) )
if (ImGuiUtil.DrawDisabledButton("Apply Changes", Vector2.Zero, tt, !changes))
{
var failedFiles = _editor!.ApplyFiles();
if( failedFiles > 0 )
{
Penumbra.Log.Information( $"Failed to apply {failedFiles} file redirections to {_editor.CurrentOption.FullName}." );
}
if (failedFiles > 0)
Penumbra.Log.Information($"Failed to apply {failedFiles} file redirections to {_editor.CurrentOption.FullName}.");
}
ImGui.SameLine();
var label = changes ? "Revert Changes" : "Reload Files";
var length = new Vector2( ImGui.CalcTextSize( "Revert Changes" ).X, 0 );
if( ImGui.Button( label, length ) )
{
var length = new Vector2(ImGui.CalcTextSize("Revert Changes").X, 0);
if (ImGui.Button(label, length))
_editor!.RevertFiles();
}
ImGuiUtil.HoverTooltip( "Revert all revertible changes since the last file or option reload or data refresh." );
ImGuiUtil.HoverTooltip("Revert all revertible changes since the last file or option reload or data refresh.");
ImGui.SameLine();
ImGui.Checkbox( "Overview Mode", ref _overviewMode );
ImGui.Checkbox("Overview Mode", ref _overviewMode);
}
private void DrawFileManagementNormal()
{
ImGui.SetNextItemWidth( 250 * ImGuiHelpers.GlobalScale );
LowerString.InputWithHint( "##filter", "Filter paths...", ref _fileFilter, Utf8GamePath.MaxGamePathLength );
ImGui.SetNextItemWidth(250 * UiHelpers.Scale);
LowerString.InputWithHint("##filter", "Filter paths...", ref _fileFilter, Utf8GamePath.MaxGamePathLength);
ImGui.SameLine();
ImGui.Checkbox( "Show Game Paths", ref _showGamePaths );
ImGui.Checkbox("Show Game Paths", ref _showGamePaths);
ImGui.SameLine();
if( ImGui.Button( "Unselect All" ) )
{
if (ImGui.Button("Unselect All"))
_selectedFiles.Clear();
}
ImGui.SameLine();
if( ImGui.Button( "Select Visible" ) )
{
_selectedFiles.UnionWith( _editor!.AvailableFiles.Where( CheckFilter ) );
}
if (ImGui.Button("Select Visible"))
_selectedFiles.UnionWith(_editor!.AvailableFiles.Where(CheckFilter));
ImGui.SameLine();
if( ImGui.Button( "Select Unused" ) )
{
_selectedFiles.UnionWith( _editor!.AvailableFiles.Where( f => f.SubModUsage.Count == 0 ) );
}
if (ImGui.Button("Select Unused"))
_selectedFiles.UnionWith(_editor!.AvailableFiles.Where(f => f.SubModUsage.Count == 0));
ImGui.SameLine();
if( ImGui.Button( "Select Used Here" ) )
{
_selectedFiles.UnionWith( _editor!.AvailableFiles.Where( f => f.CurrentUsage > 0 ) );
}
if (ImGui.Button("Select Used Here"))
_selectedFiles.UnionWith(_editor!.AvailableFiles.Where(f => f.CurrentUsage > 0));
ImGui.SameLine();
ImGuiUtil.RightAlign( $"{_selectedFiles.Count} / {_editor!.AvailableFiles.Count} Files Selected" );
ImGuiUtil.RightAlign($"{_selectedFiles.Count} / {_editor!.AvailableFiles.Count} Files Selected");
}
private void DrawFileManagementOverview()
{
using var style = ImRaii.PushStyle( ImGuiStyleVar.FrameRounding, 0 )
.Push( ImGuiStyleVar.ItemSpacing, Vector2.Zero )
.Push( ImGuiStyleVar.FrameBorderSize, ImGui.GetStyle().ChildBorderSize );
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 0)
.Push(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.Push(ImGuiStyleVar.FrameBorderSize, ImGui.GetStyle().ChildBorderSize);
var width = ImGui.GetContentRegionAvail().X / 8;
ImGui.SetNextItemWidth( width * 3 );
LowerString.InputWithHint( "##fileFilter", "Filter file...", ref _fileOverviewFilter1, Utf8GamePath.MaxGamePathLength );
ImGui.SetNextItemWidth(width * 3);
LowerString.InputWithHint("##fileFilter", "Filter file...", ref _fileOverviewFilter1, Utf8GamePath.MaxGamePathLength);
ImGui.SameLine();
ImGui.SetNextItemWidth( width * 3 );
LowerString.InputWithHint( "##pathFilter", "Filter path...", ref _fileOverviewFilter2, Utf8GamePath.MaxGamePathLength );
ImGui.SetNextItemWidth(width * 3);
LowerString.InputWithHint("##pathFilter", "Filter path...", ref _fileOverviewFilter2, Utf8GamePath.MaxGamePathLength);
ImGui.SameLine();
ImGui.SetNextItemWidth( width * 2 );
LowerString.InputWithHint( "##optionFilter", "Filter option...", ref _fileOverviewFilter3, Utf8GamePath.MaxGamePathLength );
ImGui.SetNextItemWidth(width * 2);
LowerString.InputWithHint("##optionFilter", "Filter option...", ref _fileOverviewFilter3, Utf8GamePath.MaxGamePathLength);
}
}
}

View file

@ -229,8 +229,8 @@ public partial class ModEditWindow
var row = file.ColorSets[ colorSetIdx ].Rows[ rowIdx ];
var hasDye = file.ColorDyeSets.Length > colorSetIdx;
var dye = hasDye ? file.ColorDyeSets[ colorSetIdx ].Rows[ rowIdx ] : new MtrlFile.ColorDyeSet.Row();
var floatSize = 70 * ImGuiHelpers.GlobalScale;
var intSize = 45 * ImGuiHelpers.GlobalScale;
var floatSize = 70 * UiHelpers.Scale;
var intSize = 45 * UiHelpers.Scale;
ImGui.TableNextColumn();
ColorSetCopyClipboardButton( row, dye );
ImGui.SameLine();

View file

@ -81,7 +81,7 @@ public partial class ModEditWindow
LoadedShpkPath = path;
var data = LoadedShpkPath.IsRooted
? File.ReadAllBytes( LoadedShpkPath.FullName )
: DalamudServices.GameData.GetFile( LoadedShpkPath.InternalName.ToString() )?.Data;
: DalamudServices.SGameData.GetFile( LoadedShpkPath.InternalName.ToString() )?.Data;
AssociatedShpk = data?.Length > 0 ? new ShpkFile( data ) : throw new Exception( "Failure to load file data." );
LoadedShpkPathName = path.ToPath();
}
@ -100,7 +100,7 @@ public partial class ModEditWindow
{
var samplers = Mtrl.GetSamplersByTexture( AssociatedShpk );
TextureLabels.Clear();
TextureLabelWidth = 50f * ImGuiHelpers.GlobalScale;
TextureLabelWidth = 50f * UiHelpers.Scale;
using( var _ = ImRaii.PushFont( UiBuilder.MonoFont ) )
{
for( var i = 0; i < Mtrl.Textures.Length; ++i )
@ -112,7 +112,7 @@ public partial class ModEditWindow
}
}
TextureLabelWidth = TextureLabelWidth / ImGuiHelpers.GlobalScale + 4;
TextureLabelWidth = TextureLabelWidth / UiHelpers.Scale + 4;
}
public void UpdateShaderKeyLabels()

View file

@ -17,36 +17,35 @@ namespace Penumbra.UI.Classes;
public partial class ModEditWindow
{
private readonly FileDialogManager _materialFileDialog = ConfigWindow.SetupFileManager();
private readonly FileDialogService _fileDialog;
private bool DrawPackageNameInput( MtrlTab tab, bool disabled )
private bool DrawPackageNameInput(MtrlTab tab, bool disabled)
{
var ret = false;
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f );
if( ImGui.InputText( "Shader Package Name", ref tab.Mtrl.ShaderPackage.Name, 63, disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None ) )
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
if (ImGui.InputText("Shader Package Name", ref tab.Mtrl.ShaderPackage.Name, 63,
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None))
{
ret = true;
tab.AssociatedShpk = null;
tab.LoadedShpkPath = FullPath.Empty;
}
if( ImGui.IsItemDeactivatedAfterEdit() )
{
tab.LoadShpk( tab.FindAssociatedShpk( out _, out _ ) );
}
if (ImGui.IsItemDeactivatedAfterEdit())
tab.LoadShpk(tab.FindAssociatedShpk(out _, out _));
return ret;
}
private static bool DrawShaderFlagsInput( MtrlFile file, bool disabled )
private static bool DrawShaderFlagsInput(MtrlFile file, bool disabled)
{
var ret = false;
var shpkFlags = ( int )file.ShaderPackage.Flags;
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f );
if( ImGui.InputInt( "Shader Package Flags", ref shpkFlags, 0, 0,
ImGuiInputTextFlags.CharsHexadecimal | ( disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None ) ) )
var shpkFlags = (int)file.ShaderPackage.Flags;
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
if (ImGui.InputInt("Shader Package Flags", ref shpkFlags, 0, 0,
ImGuiInputTextFlags.CharsHexadecimal | (disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None)))
{
file.ShaderPackage.Flags = ( uint )shpkFlags;
file.ShaderPackage.Flags = (uint)shpkFlags;
ret = true;
}
@ -57,86 +56,72 @@ public partial class ModEditWindow
/// Show the currently associated shpk file, if any, and the buttons to associate
/// a specific shpk from your drive, the modded shpk by path or the default shpk.
/// </summary>
private void DrawCustomAssociations( MtrlTab tab )
private void DrawCustomAssociations(MtrlTab tab)
{
var text = tab.AssociatedShpk == null
? "Associated .shpk file: None"
: $"Associated .shpk file: {tab.LoadedShpkPathName}";
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
if( ImGui.Selectable( text ) )
{
ImGui.SetClipboardText( tab.LoadedShpkPathName );
}
if (ImGui.Selectable(text))
ImGui.SetClipboardText(tab.LoadedShpkPathName);
ImGuiUtil.HoverTooltip( "Click to copy file path to clipboard." );
ImGuiUtil.HoverTooltip("Click to copy file path to clipboard.");
if( ImGui.Button( "Associate Custom .shpk File" ) )
{
_materialFileDialog.OpenFileDialog( "Associate Custom .shpk File...", ".shpk", ( success, name ) =>
if (ImGui.Button("Associate Custom .shpk File"))
_fileDialog.OpenFilePicker("Associate Custom .shpk File...", ".shpk", (success, name) =>
{
if( !success )
{
return;
}
if (success)
tab.LoadShpk(new FullPath(name[0]));
}, 1, _mod!.ModPath.FullName, false);
tab.LoadShpk( new FullPath( name ) );
} );
}
var moddedPath = tab.FindAssociatedShpk( out var defaultPath, out var gamePath );
var moddedPath = tab.FindAssociatedShpk(out var defaultPath, out var gamePath);
ImGui.SameLine();
if( ImGuiUtil.DrawDisabledButton( "Associate Default .shpk File", Vector2.Zero, moddedPath.ToPath(), moddedPath.Equals( tab.LoadedShpkPath ) ) )
{
tab.LoadShpk( moddedPath );
}
if (ImGuiUtil.DrawDisabledButton("Associate Default .shpk File", Vector2.Zero, moddedPath.ToPath(),
moddedPath.Equals(tab.LoadedShpkPath)))
tab.LoadShpk(moddedPath);
if( !gamePath.Path.Equals( moddedPath.InternalName ) )
if (!gamePath.Path.Equals(moddedPath.InternalName))
{
ImGui.SameLine();
if( ImGuiUtil.DrawDisabledButton( "Associate Unmodded .shpk File", Vector2.Zero, defaultPath, gamePath.Path.Equals( tab.LoadedShpkPath.InternalName ) ) )
{
tab.LoadShpk( new FullPath( gamePath ) );
}
if (ImGuiUtil.DrawDisabledButton("Associate Unmodded .shpk File", Vector2.Zero, defaultPath,
gamePath.Path.Equals(tab.LoadedShpkPath.InternalName)))
tab.LoadShpk(new FullPath(gamePath));
}
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
}
private static bool DrawShaderKey( MtrlTab tab, bool disabled, ref int idx )
private static bool DrawShaderKey(MtrlTab tab, bool disabled, ref int idx)
{
var ret = false;
using var t2 = ImRaii.TreeNode( tab.ShaderKeyLabels[ idx ], disabled ? ImGuiTreeNodeFlags.Leaf : 0 );
if( !t2 || disabled )
{
using var t2 = ImRaii.TreeNode(tab.ShaderKeyLabels[idx], disabled ? ImGuiTreeNodeFlags.Leaf : 0);
if (!t2 || disabled)
return ret;
}
var key = tab.Mtrl.ShaderPackage.ShaderKeys[ idx ];
var shpkKey = tab.AssociatedShpk?.GetMaterialKeyById( key.Category );
if( shpkKey.HasValue )
var key = tab.Mtrl.ShaderPackage.ShaderKeys[idx];
var shpkKey = tab.AssociatedShpk?.GetMaterialKeyById(key.Category);
if (shpkKey.HasValue)
{
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f );
using var c = ImRaii.Combo( "Value", $"0x{key.Value:X8}" );
if( c )
{
foreach( var value in shpkKey.Value.Values )
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
using var c = ImRaii.Combo("Value", $"0x{key.Value:X8}");
if (c)
foreach (var value in shpkKey.Value.Values)
{
if( ImGui.Selectable( $"0x{value:X8}", value == key.Value ) )
if (ImGui.Selectable($"0x{value:X8}", value == key.Value))
{
tab.Mtrl.ShaderPackage.ShaderKeys[ idx ].Value = value;
ret = true;
tab.Mtrl.ShaderPackage.ShaderKeys[idx].Value = value;
ret = true;
tab.UpdateShaderKeyLabels();
}
}
}
}
if( ImGui.Button( "Remove Key" ) )
if (ImGui.Button("Remove Key"))
{
tab.Mtrl.ShaderPackage.ShaderKeys = tab.Mtrl.ShaderPackage.ShaderKeys.RemoveItems( idx-- );
tab.Mtrl.ShaderPackage.ShaderKeys = tab.Mtrl.ShaderPackage.ShaderKeys.RemoveItems(idx--);
ret = true;
tab.UpdateShaderKeyLabels();
}
@ -144,19 +129,18 @@ public partial class ModEditWindow
return ret;
}
private static bool DrawNewShaderKey( MtrlTab tab )
private static bool DrawNewShaderKey(MtrlTab tab)
{
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f );
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
var ret = false;
using( var c = ImRaii.Combo( "##NewConstantId", $"ID: 0x{tab.NewKeyId:X8}" ) )
using (var c = ImRaii.Combo("##NewConstantId", $"ID: 0x{tab.NewKeyId:X8}"))
{
if( c )
{
foreach( var idx in tab.MissingShaderKeyIndices )
if (c)
foreach (var idx in tab.MissingShaderKeyIndices)
{
var key = tab.AssociatedShpk!.MaterialKeys[ idx ];
var key = tab.AssociatedShpk!.MaterialKeys[idx];
if( ImGui.Selectable( $"ID: 0x{key.Id:X8}", key.Id == tab.NewKeyId ) )
if (ImGui.Selectable($"ID: 0x{key.Id:X8}", key.Id == tab.NewKeyId))
{
tab.NewKeyDefault = key.DefaultValue;
tab.NewKeyId = key.Id;
@ -164,17 +148,16 @@ public partial class ModEditWindow
tab.UpdateShaderKeyLabels();
}
}
}
}
ImGui.SameLine();
if( ImGui.Button( "Add Key" ) )
if (ImGui.Button("Add Key"))
{
tab.Mtrl.ShaderPackage.ShaderKeys = tab.Mtrl.ShaderPackage.ShaderKeys.AddItem( new ShaderKey
tab.Mtrl.ShaderPackage.ShaderKeys = tab.Mtrl.ShaderPackage.ShaderKeys.AddItem(new ShaderKey
{
Category = tab.NewKeyId,
Value = tab.NewKeyDefault,
} );
});
ret = true;
tab.UpdateShaderKeyLabels();
}
@ -182,70 +165,59 @@ public partial class ModEditWindow
return ret;
}
private static bool DrawMaterialShaderKeys( MtrlTab tab, bool disabled )
private static bool DrawMaterialShaderKeys(MtrlTab tab, bool disabled)
{
if( tab.Mtrl.ShaderPackage.ShaderKeys.Length <= 0 && ( disabled || tab.AssociatedShpk == null || tab.AssociatedShpk.MaterialKeys.Length <= 0 ) )
{
if (tab.Mtrl.ShaderPackage.ShaderKeys.Length <= 0
&& (disabled || tab.AssociatedShpk == null || tab.AssociatedShpk.MaterialKeys.Length <= 0))
return false;
}
using var t = ImRaii.TreeNode( "Shader Keys" );
if( !t )
{
using var t = ImRaii.TreeNode("Shader Keys");
if (!t)
return false;
}
var ret = false;
for( var idx = 0; idx < tab.Mtrl.ShaderPackage.ShaderKeys.Length; ++idx )
{
ret |= DrawShaderKey( tab, disabled, ref idx );
}
for (var idx = 0; idx < tab.Mtrl.ShaderPackage.ShaderKeys.Length; ++idx)
ret |= DrawShaderKey(tab, disabled, ref idx);
if( !disabled && tab.AssociatedShpk != null && tab.MissingShaderKeyIndices.Count != 0 )
{
ret |= DrawNewShaderKey( tab );
}
if (!disabled && tab.AssociatedShpk != null && tab.MissingShaderKeyIndices.Count != 0)
ret |= DrawNewShaderKey(tab);
return ret;
}
private static void DrawMaterialShaders( MtrlTab tab )
private static void DrawMaterialShaders(MtrlTab tab)
{
if( tab.AssociatedShpk == null )
{
if (tab.AssociatedShpk == null)
return;
}
ImRaii.TreeNode( tab.VertexShaders, ImGuiTreeNodeFlags.Leaf ).Dispose();
ImRaii.TreeNode( tab.PixelShaders, ImGuiTreeNodeFlags.Leaf ).Dispose();
ImRaii.TreeNode(tab.VertexShaders, ImGuiTreeNodeFlags.Leaf).Dispose();
ImRaii.TreeNode(tab.PixelShaders, ImGuiTreeNodeFlags.Leaf).Dispose();
}
private static bool DrawMaterialConstantValues( MtrlTab tab, bool disabled, ref int idx )
private static bool DrawMaterialConstantValues(MtrlTab tab, bool disabled, ref int idx)
{
var (name, componentOnly, paramValueOffset) = tab.MaterialConstants[ idx ];
using var font = ImRaii.PushFont( UiBuilder.MonoFont );
using var t2 = ImRaii.TreeNode( name );
if( !t2 )
{
var (name, componentOnly, paramValueOffset) = tab.MaterialConstants[idx];
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
using var t2 = ImRaii.TreeNode(name);
if (!t2)
return false;
}
font.Dispose();
var constant = tab.Mtrl.ShaderPackage.Constants[ idx ];
var constant = tab.Mtrl.ShaderPackage.Constants[idx];
var ret = false;
var values = tab.Mtrl.GetConstantValues( constant );
if( values.Length > 0 )
var values = tab.Mtrl.GetConstantValues(constant);
if (values.Length > 0)
{
var valueOffset = constant.ByteOffset >> 2;
for( var valueIdx = 0; valueIdx < values.Length; ++valueIdx )
for (var valueIdx = 0; valueIdx < values.Length; ++valueIdx)
{
var paramName = MaterialParamName( componentOnly, paramValueOffset + valueIdx ) ?? $"#{valueIdx}";
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f );
if( ImGui.InputFloat( $"{paramName} (at 0x{( valueOffset + valueIdx ) << 2:X4})", ref values[ valueIdx ], 0.0f, 0.0f, "%.3f",
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None ) )
var paramName = MaterialParamName(componentOnly, paramValueOffset + valueIdx) ?? $"#{valueIdx}";
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
if (ImGui.InputFloat($"{paramName} (at 0x{(valueOffset + valueIdx) << 2:X4})", ref values[valueIdx], 0.0f, 0.0f, "%.3f",
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None))
{
ret = true;
tab.UpdateConstantLabels();
@ -254,24 +226,23 @@ public partial class ModEditWindow
}
else
{
ImRaii.TreeNode( $"Offset: 0x{constant.ByteOffset:X4}", ImGuiTreeNodeFlags.Leaf ).Dispose();
ImRaii.TreeNode( $"Size: 0x{constant.ByteSize:X4}", ImGuiTreeNodeFlags.Leaf ).Dispose();
ImRaii.TreeNode($"Offset: 0x{constant.ByteOffset:X4}", ImGuiTreeNodeFlags.Leaf).Dispose();
ImRaii.TreeNode($"Size: 0x{constant.ByteSize:X4}", ImGuiTreeNodeFlags.Leaf).Dispose();
}
if( !disabled
&& !tab.HasMalformedMaterialConstants
&& tab.OrphanedMaterialValues.Count == 0
&& tab.AliasedMaterialValueCount == 0
&& ImGui.Button( "Remove Constant" ) )
if (!disabled
&& !tab.HasMalformedMaterialConstants
&& tab.OrphanedMaterialValues.Count == 0
&& tab.AliasedMaterialValueCount == 0
&& ImGui.Button("Remove Constant"))
{
tab.Mtrl.ShaderPackage.ShaderValues = tab.Mtrl.ShaderPackage.ShaderValues.RemoveItems( constant.ByteOffset >> 2, constant.ByteSize >> 2 );
tab.Mtrl.ShaderPackage.Constants = tab.Mtrl.ShaderPackage.Constants.RemoveItems( idx-- );
for( var i = 0; i < tab.Mtrl.ShaderPackage.Constants.Length; ++i )
tab.Mtrl.ShaderPackage.ShaderValues =
tab.Mtrl.ShaderPackage.ShaderValues.RemoveItems(constant.ByteOffset >> 2, constant.ByteSize >> 2);
tab.Mtrl.ShaderPackage.Constants = tab.Mtrl.ShaderPackage.Constants.RemoveItems(idx--);
for (var i = 0; i < tab.Mtrl.ShaderPackage.Constants.Length; ++i)
{
if( tab.Mtrl.ShaderPackage.Constants[ i ].ByteOffset >= constant.ByteOffset )
{
tab.Mtrl.ShaderPackage.Constants[ i ].ByteOffset -= constant.ByteSize;
}
if (tab.Mtrl.ShaderPackage.Constants[i].ByteOffset >= constant.ByteOffset)
tab.Mtrl.ShaderPackage.Constants[i].ByteOffset -= constant.ByteSize;
}
ret = true;
@ -281,21 +252,19 @@ public partial class ModEditWindow
return ret;
}
private static bool DrawMaterialOrphans( MtrlTab tab, bool disabled )
private static bool DrawMaterialOrphans(MtrlTab tab, bool disabled)
{
using var t2 = ImRaii.TreeNode( $"Orphan Values ({tab.OrphanedMaterialValues.Count})" );
if( !t2 )
{
using var t2 = ImRaii.TreeNode($"Orphan Values ({tab.OrphanedMaterialValues.Count})");
if (!t2)
return false;
}
var ret = false;
foreach( var idx in tab.OrphanedMaterialValues )
foreach (var idx in tab.OrphanedMaterialValues)
{
ImGui.SetNextItemWidth( ImGui.GetFontSize() * 10.0f );
if( ImGui.InputFloat( $"#{idx} (at 0x{idx << 2:X4})",
ref tab.Mtrl.ShaderPackage.ShaderValues[ idx ], 0.0f, 0.0f, "%.3f",
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None ) )
ImGui.SetNextItemWidth(ImGui.GetFontSize() * 10.0f);
if (ImGui.InputFloat($"#{idx} (at 0x{idx << 2:X4})",
ref tab.Mtrl.ShaderPackage.ShaderValues[idx], 0.0f, 0.0f, "%.3f",
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None))
{
ret = true;
tab.UpdateConstantLabels();
@ -305,36 +274,34 @@ public partial class ModEditWindow
return ret;
}
private static bool DrawNewMaterialParam( MtrlTab tab )
private static bool DrawNewMaterialParam(MtrlTab tab)
{
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 450.0f );
using( var font = ImRaii.PushFont( UiBuilder.MonoFont ) )
ImGui.SetNextItemWidth(UiHelpers.Scale * 450.0f);
using (var font = ImRaii.PushFont(UiBuilder.MonoFont))
{
using var c = ImRaii.Combo( "##NewConstantId", tab.MissingMaterialConstants[ tab.NewConstantIdx ].Name );
if( c )
{
foreach( var (constant, idx) in tab.MissingMaterialConstants.WithIndex() )
using var c = ImRaii.Combo("##NewConstantId", tab.MissingMaterialConstants[tab.NewConstantIdx].Name);
if (c)
foreach (var (constant, idx) in tab.MissingMaterialConstants.WithIndex())
{
if( ImGui.Selectable( constant.Name, constant.Id == tab.NewConstantId ) )
if (ImGui.Selectable(constant.Name, constant.Id == tab.NewConstantId))
{
tab.NewConstantIdx = idx;
tab.NewConstantId = constant.Id;
}
}
}
}
ImGui.SameLine();
if( ImGui.Button( "Add Constant" ) )
if (ImGui.Button("Add Constant"))
{
var (_, _, byteSize) = tab.MissingMaterialConstants[ tab.NewConstantIdx ];
tab.Mtrl.ShaderPackage.Constants = tab.Mtrl.ShaderPackage.Constants.AddItem( new MtrlFile.Constant
var (_, _, byteSize) = tab.MissingMaterialConstants[tab.NewConstantIdx];
tab.Mtrl.ShaderPackage.Constants = tab.Mtrl.ShaderPackage.Constants.AddItem(new MtrlFile.Constant
{
Id = tab.NewConstantId,
ByteOffset = ( ushort )( tab.Mtrl.ShaderPackage.ShaderValues.Length << 2 ),
ByteOffset = (ushort)(tab.Mtrl.ShaderPackage.ShaderValues.Length << 2),
ByteSize = byteSize,
} );
tab.Mtrl.ShaderPackage.ShaderValues = tab.Mtrl.ShaderPackage.ShaderValues.AddItem( 0.0f, byteSize >> 2 );
});
tab.Mtrl.ShaderPackage.ShaderValues = tab.Mtrl.ShaderPackage.ShaderValues.AddItem(0.0f, byteSize >> 2);
tab.UpdateConstantLabels();
return true;
}
@ -342,92 +309,76 @@ public partial class ModEditWindow
return false;
}
private static bool DrawMaterialConstants( MtrlTab tab, bool disabled )
private static bool DrawMaterialConstants(MtrlTab tab, bool disabled)
{
if( tab.Mtrl.ShaderPackage.Constants.Length == 0
&& tab.Mtrl.ShaderPackage.ShaderValues.Length == 0
&& ( disabled || tab.AssociatedShpk == null || tab.AssociatedShpk.MaterialParams.Length == 0 ) )
{
if (tab.Mtrl.ShaderPackage.Constants.Length == 0
&& tab.Mtrl.ShaderPackage.ShaderValues.Length == 0
&& (disabled || tab.AssociatedShpk == null || tab.AssociatedShpk.MaterialParams.Length == 0))
return false;
}
using var font = ImRaii.PushFont( UiBuilder.MonoFont );
using var t = ImRaii.TreeNode( tab.MaterialConstantLabel );
if( !t )
{
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
using var t = ImRaii.TreeNode(tab.MaterialConstantLabel);
if (!t)
return false;
}
font.Dispose();
var ret = false;
for( var idx = 0; idx < tab.Mtrl.ShaderPackage.Constants.Length; ++idx )
{
ret |= DrawMaterialConstantValues( tab, disabled, ref idx );
}
for (var idx = 0; idx < tab.Mtrl.ShaderPackage.Constants.Length; ++idx)
ret |= DrawMaterialConstantValues(tab, disabled, ref idx);
if( tab.OrphanedMaterialValues.Count > 0 )
{
ret |= DrawMaterialOrphans( tab, disabled );
}
else if( !disabled && !tab.HasMalformedMaterialConstants && tab.MissingMaterialConstants.Count > 0 )
{
ret |= DrawNewMaterialParam( tab );
}
if (tab.OrphanedMaterialValues.Count > 0)
ret |= DrawMaterialOrphans(tab, disabled);
else if (!disabled && !tab.HasMalformedMaterialConstants && tab.MissingMaterialConstants.Count > 0)
ret |= DrawNewMaterialParam(tab);
return ret;
}
private static bool DrawMaterialSampler( MtrlTab tab, bool disabled, ref int idx )
private static bool DrawMaterialSampler(MtrlTab tab, bool disabled, ref int idx)
{
var (label, filename) = tab.Samplers[ idx ];
using var tree = ImRaii.TreeNode( label );
if( !tree )
{
var (label, filename) = tab.Samplers[idx];
using var tree = ImRaii.TreeNode(label);
if (!tree)
return false;
}
ImRaii.TreeNode( filename, ImGuiTreeNodeFlags.Leaf ).Dispose();
ImRaii.TreeNode(filename, ImGuiTreeNodeFlags.Leaf).Dispose();
var ret = false;
var sampler = tab.Mtrl.ShaderPackage.Samplers[ idx ];
var sampler = tab.Mtrl.ShaderPackage.Samplers[idx];
// FIXME this probably doesn't belong here
static unsafe bool InputHexUInt16( string label, ref ushort v, ImGuiInputTextFlags flags )
static unsafe bool InputHexUInt16(string label, ref ushort v, ImGuiInputTextFlags flags)
{
fixed( ushort* v2 = &v )
fixed (ushort* v2 = &v)
{
return ImGui.InputScalar( label, ImGuiDataType.U16, ( nint )v2, IntPtr.Zero, IntPtr.Zero, "%04X", flags );
return ImGui.InputScalar(label, ImGuiDataType.U16, (nint)v2, IntPtr.Zero, IntPtr.Zero, "%04X", flags);
}
}
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f );
if( InputHexUInt16( "Texture Flags", ref tab.Mtrl.Textures[ sampler.TextureIndex ].Flags,
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None ) )
{
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
if (InputHexUInt16("Texture Flags", ref tab.Mtrl.Textures[sampler.TextureIndex].Flags,
disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None))
ret = true;
var samplerFlags = (int)sampler.Flags;
ImGui.SetNextItemWidth(UiHelpers.Scale * 150.0f);
if (ImGui.InputInt("Sampler Flags", ref samplerFlags, 0, 0,
ImGuiInputTextFlags.CharsHexadecimal | (disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None)))
{
tab.Mtrl.ShaderPackage.Samplers[idx].Flags = (uint)samplerFlags;
ret = true;
}
var samplerFlags = ( int )sampler.Flags;
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f );
if( ImGui.InputInt( "Sampler Flags", ref samplerFlags, 0, 0,
ImGuiInputTextFlags.CharsHexadecimal | ( disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None ) ) )
if (!disabled
&& tab.OrphanedSamplers.Count == 0
&& tab.AliasedSamplerCount == 0
&& ImGui.Button("Remove Sampler"))
{
tab.Mtrl.ShaderPackage.Samplers[ idx ].Flags = ( uint )samplerFlags;
ret = true;
}
if( !disabled
&& tab.OrphanedSamplers.Count == 0
&& tab.AliasedSamplerCount == 0
&& ImGui.Button( "Remove Sampler" ) )
{
tab.Mtrl.Textures = tab.Mtrl.Textures.RemoveItems( sampler.TextureIndex );
tab.Mtrl.ShaderPackage.Samplers = tab.Mtrl.ShaderPackage.Samplers.RemoveItems( idx-- );
for( var i = 0; i < tab.Mtrl.ShaderPackage.Samplers.Length; ++i )
tab.Mtrl.Textures = tab.Mtrl.Textures.RemoveItems(sampler.TextureIndex);
tab.Mtrl.ShaderPackage.Samplers = tab.Mtrl.ShaderPackage.Samplers.RemoveItems(idx--);
for (var i = 0; i < tab.Mtrl.ShaderPackage.Samplers.Length; ++i)
{
if( tab.Mtrl.ShaderPackage.Samplers[ i ].TextureIndex >= sampler.TextureIndex )
{
--tab.Mtrl.ShaderPackage.Samplers[ i ].TextureIndex;
}
if (tab.Mtrl.ShaderPackage.Samplers[i].TextureIndex >= sampler.TextureIndex)
--tab.Mtrl.ShaderPackage.Samplers[i].TextureIndex;
}
ret = true;
@ -438,114 +389,99 @@ public partial class ModEditWindow
return ret;
}
private static bool DrawMaterialNewSampler( MtrlTab tab )
private static bool DrawMaterialNewSampler(MtrlTab tab)
{
var (name, id) = tab.MissingSamplers[ tab.NewSamplerIdx ];
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 450.0f );
using( var c = ImRaii.Combo( "##NewSamplerId", $"{name} (ID: 0x{id:X8})" ) )
var (name, id) = tab.MissingSamplers[tab.NewSamplerIdx];
ImGui.SetNextItemWidth(UiHelpers.Scale * 450.0f);
using (var c = ImRaii.Combo("##NewSamplerId", $"{name} (ID: 0x{id:X8})"))
{
if( c )
{
foreach( var (sampler, idx) in tab.MissingSamplers.WithIndex() )
if (c)
foreach (var (sampler, idx) in tab.MissingSamplers.WithIndex())
{
if( ImGui.Selectable( $"{sampler.Name} (ID: 0x{sampler.Id:X8})", sampler.Id == tab.NewSamplerId ) )
if (ImGui.Selectable($"{sampler.Name} (ID: 0x{sampler.Id:X8})", sampler.Id == tab.NewSamplerId))
{
tab.NewSamplerIdx = idx;
tab.NewSamplerId = sampler.Id;
}
}
}
}
ImGui.SameLine();
if( !ImGui.Button( "Add Sampler" ) )
{
if (!ImGui.Button("Add Sampler"))
return false;
}
tab.Mtrl.ShaderPackage.Samplers = tab.Mtrl.ShaderPackage.Samplers.AddItem( new Sampler
tab.Mtrl.ShaderPackage.Samplers = tab.Mtrl.ShaderPackage.Samplers.AddItem(new Sampler
{
SamplerId = tab.NewSamplerId,
TextureIndex = ( byte )tab.Mtrl.Textures.Length,
TextureIndex = (byte)tab.Mtrl.Textures.Length,
Flags = 0,
} );
tab.Mtrl.Textures = tab.Mtrl.Textures.AddItem( new MtrlFile.Texture
});
tab.Mtrl.Textures = tab.Mtrl.Textures.AddItem(new MtrlFile.Texture
{
Path = string.Empty,
Flags = 0,
} );
});
tab.UpdateSamplers();
tab.UpdateTextureLabels();
return true;
}
private static bool DrawMaterialSamplers( MtrlTab tab, bool disabled )
private static bool DrawMaterialSamplers(MtrlTab tab, bool disabled)
{
if( tab.Mtrl.ShaderPackage.Samplers.Length == 0
&& tab.Mtrl.Textures.Length == 0
&& ( disabled || ( tab.AssociatedShpk?.Samplers.All( sampler => sampler.Slot != 2 ) ?? false ) ) )
{
if (tab.Mtrl.ShaderPackage.Samplers.Length == 0
&& tab.Mtrl.Textures.Length == 0
&& (disabled || (tab.AssociatedShpk?.Samplers.All(sampler => sampler.Slot != 2) ?? false)))
return false;
}
using var t = ImRaii.TreeNode( "Samplers" );
if( !t )
{
using var t = ImRaii.TreeNode("Samplers");
if (!t)
return false;
}
var ret = false;
for( var idx = 0; idx < tab.Mtrl.ShaderPackage.Samplers.Length; ++idx )
{
ret |= DrawMaterialSampler( tab, disabled, ref idx );
}
for (var idx = 0; idx < tab.Mtrl.ShaderPackage.Samplers.Length; ++idx)
ret |= DrawMaterialSampler(tab, disabled, ref idx);
if( tab.OrphanedSamplers.Count > 0 )
if (tab.OrphanedSamplers.Count > 0)
{
using var t2 = ImRaii.TreeNode( $"Orphan Textures ({tab.OrphanedSamplers.Count})" );
if( t2 )
{
foreach( var idx in tab.OrphanedSamplers )
using var t2 = ImRaii.TreeNode($"Orphan Textures ({tab.OrphanedSamplers.Count})");
if (t2)
foreach (var idx in tab.OrphanedSamplers)
{
ImRaii.TreeNode( $"#{idx}: {Path.GetFileName( tab.Mtrl.Textures[ idx ].Path )} - {tab.Mtrl.Textures[ idx ].Flags:X4}", ImGuiTreeNodeFlags.Leaf )
.Dispose();
ImRaii.TreeNode($"#{idx}: {Path.GetFileName(tab.Mtrl.Textures[idx].Path)} - {tab.Mtrl.Textures[idx].Flags:X4}",
ImGuiTreeNodeFlags.Leaf)
.Dispose();
}
}
}
else if( !disabled && tab.MissingSamplers.Count > 0 && tab.AliasedSamplerCount == 0 && tab.Mtrl.Textures.Length < 255 )
else if (!disabled && tab.MissingSamplers.Count > 0 && tab.AliasedSamplerCount == 0 && tab.Mtrl.Textures.Length < 255)
{
ret |= DrawMaterialNewSampler( tab );
ret |= DrawMaterialNewSampler(tab);
}
return ret;
}
private bool DrawMaterialShaderResources( MtrlTab tab, bool disabled )
private bool DrawMaterialShaderResources(MtrlTab tab, bool disabled)
{
var ret = false;
if( !ImGui.CollapsingHeader( "Advanced Shader Resources" ) )
{
if (!ImGui.CollapsingHeader("Advanced Shader Resources"))
return ret;
}
ret |= DrawPackageNameInput( tab, disabled );
ret |= DrawShaderFlagsInput( tab.Mtrl, disabled );
DrawCustomAssociations( tab );
ret |= DrawMaterialShaderKeys( tab, disabled );
DrawMaterialShaders( tab );
ret |= DrawMaterialConstants( tab, disabled );
ret |= DrawMaterialSamplers( tab, disabled );
ret |= DrawPackageNameInput(tab, disabled);
ret |= DrawShaderFlagsInput(tab.Mtrl, disabled);
DrawCustomAssociations(tab);
ret |= DrawMaterialShaderKeys(tab, disabled);
DrawMaterialShaders(tab);
ret |= DrawMaterialConstants(tab, disabled);
ret |= DrawMaterialSamplers(tab, disabled);
return ret;
}
private static string? MaterialParamName( bool componentOnly, int offset )
private static string? MaterialParamName(bool componentOnly, int offset)
{
if( offset < 0 )
{
if (offset < 0)
return null;
}
return ( componentOnly, offset & 0x3 ) switch
return (componentOnly, offset & 0x3) switch
{
(true, 0) => "x",
(true, 1) => "y",
@ -559,10 +495,10 @@ public partial class ModEditWindow
};
}
private static (string? Name, bool ComponentOnly) MaterialParamRangeName( string prefix, int valueOffset, int valueLength )
private static (string? Name, bool ComponentOnly) MaterialParamRangeName(string prefix, int valueOffset, int valueLength)
{
static string VectorSwizzle( int firstComponent, int lastComponent )
=> ( firstComponent, lastComponent ) switch
static string VectorSwizzle(int firstComponent, int lastComponent)
=> (firstComponent, lastComponent) switch
{
(0, 4) => " ",
(0, 0) => ".x ",
@ -578,28 +514,22 @@ public partial class ModEditWindow
_ => string.Empty,
};
if( valueLength == 0 || valueOffset < 0 )
{
return ( null, false );
}
if (valueLength == 0 || valueOffset < 0)
return (null, false);
var firstVector = valueOffset >> 2;
var lastVector = ( valueOffset + valueLength - 1 ) >> 2;
var firstComponent = valueOffset & 0x3;
var lastComponent = ( valueOffset + valueLength - 1 ) & 0x3;
if( firstVector == lastVector )
{
return ( $"{prefix}[{firstVector}]{VectorSwizzle( firstComponent, lastComponent )}", true );
}
var firstVector = valueOffset >> 2;
var lastVector = (valueOffset + valueLength - 1) >> 2;
var firstComponent = valueOffset & 0x3;
var lastComponent = (valueOffset + valueLength - 1) & 0x3;
if (firstVector == lastVector)
return ($"{prefix}[{firstVector}]{VectorSwizzle(firstComponent, lastComponent)}", true);
var sb = new StringBuilder( 128 );
sb.Append( $"{prefix}[{firstVector}]{VectorSwizzle( firstComponent, 3 ).TrimEnd()}" );
for( var i = firstVector + 1; i < lastVector; ++i )
{
sb.Append( $", [{i}]" );
}
var sb = new StringBuilder(128);
sb.Append($"{prefix}[{firstVector}]{VectorSwizzle(firstComponent, 3).TrimEnd()}");
for (var i = firstVector + 1; i < lastVector; ++i)
sb.Append($", [{i}]");
sb.Append( $", [{lastVector}]{VectorSwizzle( 0, lastComponent )}" );
return ( sb.ToString(), false );
sb.Append($", [{lastVector}]{VectorSwizzle(0, lastComponent)}");
return (sb.ToString(), false);
}
}
}

View file

@ -29,8 +29,6 @@ public partial class ModEditWindow
ImGui.Dummy( new Vector2( ImGui.GetTextLineHeight() / 2 ) );
DrawOtherMaterialDetails( tab.Mtrl, disabled );
_materialFileDialog.Draw();
return !disabled && ret;
}
@ -39,7 +37,7 @@ public partial class ModEditWindow
var ret = false;
using var table = ImRaii.Table( "##Textures", 2 );
ImGui.TableSetupColumn( "Path", ImGuiTableColumnFlags.WidthStretch );
ImGui.TableSetupColumn( "Name", ImGuiTableColumnFlags.WidthFixed, tab.TextureLabelWidth * ImGuiHelpers.GlobalScale );
ImGui.TableSetupColumn( "Name", ImGuiTableColumnFlags.WidthFixed, tab.TextureLabelWidth * UiHelpers.Scale );
for( var i = 0; i < tab.Mtrl.Textures.Length; ++i )
{
using var _ = ImRaii.PushId( i );
@ -80,7 +78,7 @@ public partial class ModEditWindow
ret = true;
}
ImGui.SameLine( 200 * ImGuiHelpers.GlobalScale + ImGui.GetStyle().ItemSpacing.X + ImGui.GetStyle().WindowPadding.X );
ImGui.SameLine( 200 * UiHelpers.Scale + ImGui.GetStyle().ItemSpacing.X + ImGui.GetStyle().WindowPadding.X );
tmp = ( file.ShaderPackage.Flags & backfaceBit ) != 0;
if( ImGui.Checkbox( "Hide Backfaces", ref tmp ) )
{
@ -171,7 +169,7 @@ public partial class ModEditWindow
ImGui.TableNextColumn();
ImGui.TextUnformatted( info.Path.FullName[ ( _mod!.ModPath.FullName.Length + 1 ).. ] );
ImGui.TableNextColumn();
ImGui.SetNextItemWidth( 400 * ImGuiHelpers.GlobalScale );
ImGui.SetNextItemWidth( 400 * UiHelpers.Scale );
var tmp = info.CurrentMaterials[ 0 ];
if( ImGui.InputText( "##0", ref tmp, 64 ) )
{
@ -184,7 +182,7 @@ public partial class ModEditWindow
ImGui.TableNextColumn();
ImGui.TableNextColumn();
ImGui.TableNextColumn();
ImGui.SetNextItemWidth( 400 * ImGuiHelpers.GlobalScale );
ImGui.SetNextItemWidth( 400 * UiHelpers.Scale );
tmp = info.CurrentMaterials[ i ];
if( ImGui.InputText( $"##{i}", ref tmp, 64 ) )
{

View file

@ -117,7 +117,7 @@ public partial class ModEditWindow
private static EqpManipulation _new = new(Eqp.DefaultEntry, EquipSlot.Head, 1);
private static float IdWidth
=> 100 * ImGuiHelpers.GlobalScale;
=> 100 * UiHelpers.Scale;
public static void DrawNew( Mod.Editor editor, Vector2 iconSize )
{
@ -154,7 +154,7 @@ public partial class ModEditWindow
using var disabled = ImRaii.Disabled();
ImGui.TableNextColumn();
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing,
new Vector2( 3 * ImGuiHelpers.GlobalScale, ImGui.GetStyle().ItemSpacing.Y ) );
new Vector2( 3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y ) );
foreach( var flag in Eqp.EqpAttributes[ _new.Slot ] )
{
var value = defaultEntry.HasFlag( flag );
@ -184,7 +184,7 @@ public partial class ModEditWindow
// Values
ImGui.TableNextColumn();
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing,
new Vector2( 3 * ImGuiHelpers.GlobalScale, ImGui.GetStyle().ItemSpacing.Y ) );
new Vector2( 3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y ) );
var idx = 0;
foreach( var flag in Eqp.EqpAttributes[ meta.Slot ] )
{
@ -209,7 +209,7 @@ public partial class ModEditWindow
private static EqdpManipulation _new = new(EqdpEntry.Invalid, EquipSlot.Head, Gender.Male, ModelRace.Midlander, 1);
private static float IdWidth
=> 100 * ImGuiHelpers.GlobalScale;
=> 100 * UiHelpers.Scale;
public static void DrawNew( Mod.Editor editor, Vector2 iconSize )
{
@ -321,10 +321,10 @@ public partial class ModEditWindow
private static ImcManipulation _new = new(EquipSlot.Head, 1, 1, new ImcEntry());
private static float IdWidth
=> 80 * ImGuiHelpers.GlobalScale;
=> 80 * UiHelpers.Scale;
private static float SmallIdWidth
=> 45 * ImGuiHelpers.GlobalScale;
=> 45 * UiHelpers.Scale;
// Convert throwing to null-return if the file does not exist.
private static ImcEntry? GetDefault( ImcManipulation imc )
@ -380,7 +380,7 @@ public partial class ModEditWindow
ImGuiUtil.HoverTooltip( PrimaryIdTooltip );
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing,
new Vector2( 3 * ImGuiHelpers.GlobalScale, ImGui.GetStyle().ItemSpacing.Y ) );
new Vector2( 3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y ) );
ImGui.TableNextColumn();
// Equipment and accessories are slightly different imcs than other types.
@ -406,7 +406,7 @@ public partial class ModEditWindow
}
else
{
if( IdInput( "##imcId2", 100 * ImGuiHelpers.GlobalScale, _new.SecondaryId, out var setId2, 0, ushort.MaxValue, false ) )
if( IdInput( "##imcId2", 100 * UiHelpers.Scale, _new.SecondaryId, out var setId2, 0, ushort.MaxValue, false ) )
{
_new = new ImcManipulation( _new.ObjectType, _new.BodySlot, _new.PrimaryId, setId2, _new.Variant, _new.EquipSlot, _new.Entry ).Copy( GetDefault( _new )
?? new ImcEntry() );
@ -435,7 +435,7 @@ public partial class ModEditWindow
}
else
{
ImGui.Dummy( new Vector2( 70 * ImGuiHelpers.GlobalScale, 0 ) );
ImGui.Dummy( new Vector2( 70 * UiHelpers.Scale, 0 ) );
}
ImGuiUtil.HoverTooltip( VariantIdTooltip );
@ -511,7 +511,7 @@ public partial class ModEditWindow
// Values
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing,
new Vector2( 3 * ImGuiHelpers.GlobalScale, ImGui.GetStyle().ItemSpacing.Y ) );
new Vector2( 3 * UiHelpers.Scale, ImGui.GetStyle().ItemSpacing.Y ) );
ImGui.TableNextColumn();
var defaultEntry = GetDefault( meta ) ?? new ImcEntry();
if( IntDragInput( "##imcMaterialId", $"Material ID\nDefault Value: {defaultEntry.MaterialId}", SmallIdWidth, meta.Entry.MaterialId,
@ -572,7 +572,7 @@ public partial class ModEditWindow
private static EstManipulation _new = new(Gender.Male, ModelRace.Midlander, EstManipulation.EstType.Body, 1, 0);
private static float IdWidth
=> 100 * ImGuiHelpers.GlobalScale;
=> 100 * UiHelpers.Scale;
public static void DrawNew( Mod.Editor editor, Vector2 iconSize )
{
@ -669,13 +669,13 @@ public partial class ModEditWindow
private static GmpManipulation _new = new(GmpEntry.Default, 1);
private static float RotationWidth
=> 75 * ImGuiHelpers.GlobalScale;
=> 75 * UiHelpers.Scale;
private static float UnkWidth
=> 50 * ImGuiHelpers.GlobalScale;
=> 50 * UiHelpers.Scale;
private static float IdWidth
=> 100 * ImGuiHelpers.GlobalScale;
=> 100 * UiHelpers.Scale;
public static void DrawNew( Mod.Editor editor, Vector2 iconSize )
{
@ -787,7 +787,7 @@ public partial class ModEditWindow
private static RspManipulation _new = new(SubRace.Midlander, RspAttribute.MaleMinSize, 1f);
private static float FloatWidth
=> 150 * ImGuiHelpers.GlobalScale;
=> 150 * UiHelpers.Scale;
public static void DrawNew( Mod.Editor editor, Vector2 iconSize )
{
@ -847,7 +847,7 @@ public partial class ModEditWindow
var value = meta.Entry;
ImGui.SetNextItemWidth( FloatWidth );
using var color = ImRaii.PushColor( ImGuiCol.FrameBg,
def < value ? ColorId.IncreasedMetaValue.Value() : ColorId.DecreasedMetaValue.Value(),
def < value ? ColorId.IncreasedMetaValue.Value(Penumbra.Config) : ColorId.DecreasedMetaValue.Value(Penumbra.Config),
def != value );
if( ImGui.DragFloat( "##rspValue", ref value, 0.001f, 0.01f, 8f ) && value is >= 0.01f and <= 8f )
{
@ -864,7 +864,7 @@ public partial class ModEditWindow
{
int tmp = currentId;
ImGui.SetNextItemWidth( width );
using var style = ImRaii.PushStyle( ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale, border );
using var style = ImRaii.PushStyle( ImGuiStyleVar.FrameBorderSize, UiHelpers.Scale, border );
using var color = ImRaii.PushColor( ImGuiCol.Border, Colors.RegexWarningBorder, border );
if( ImGui.InputInt( label, ref tmp, 0 ) )
{
@ -880,7 +880,7 @@ public partial class ModEditWindow
private static bool Checkmark( string label, string tooltip, bool currentValue, bool defaultValue, out bool newValue )
{
using var color = ImRaii.PushColor( ImGuiCol.FrameBg,
defaultValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(), defaultValue != currentValue );
defaultValue ? ColorId.DecreasedMetaValue.Value(Penumbra.Config) : ColorId.IncreasedMetaValue.Value(Penumbra.Config), defaultValue != currentValue );
newValue = currentValue;
ImGui.Checkbox( label, ref newValue );
ImGuiUtil.HoverTooltip( tooltip, ImGuiHoveredFlags.AllowWhenDisabled );
@ -894,7 +894,7 @@ public partial class ModEditWindow
{
newValue = currentValue;
using var color = ImRaii.PushColor( ImGuiCol.FrameBg,
defaultValue > currentValue ? ColorId.DecreasedMetaValue.Value() : ColorId.IncreasedMetaValue.Value(),
defaultValue > currentValue ? ColorId.DecreasedMetaValue.Value(Penumbra.Config) : ColorId.IncreasedMetaValue.Value(Penumbra.Config),
defaultValue != currentValue );
ImGui.SetNextItemWidth( width );
if( ImGui.DragInt( label, ref newValue, speed, minValue, maxValue ) )

View file

@ -67,7 +67,7 @@ public partial class ModEditWindow
};
var blob = shader.Blob;
tab.FileDialog.SaveFileDialog( $"Export {objectName} #{idx} Program Blob to...", tab.Extension, defaultName, tab.Extension, ( success, name ) =>
tab.FileDialog.OpenSavePicker( $"Export {objectName} #{idx} Program Blob to...", tab.Extension, defaultName, tab.Extension, ( success, name ) =>
{
if( !success )
{
@ -87,7 +87,7 @@ public partial class ModEditWindow
Penumbra.ChatService.NotificationMessage( $"Shader Program Blob {defaultName}{tab.Extension} exported successfully to {Path.GetFileName( name )}",
"Penumbra Advanced Editing", NotificationType.Success );
} );
}, null, false );
}
private static void DrawShaderImportButton( ShpkTab tab, string objectName, Shader[] shaders, int idx )
@ -97,7 +97,7 @@ public partial class ModEditWindow
return;
}
tab.FileDialog.OpenFileDialog( $"Replace {objectName} #{idx} Program Blob...", "Shader Program Blobs{.o,.cso,.dxbc,.dxil}", ( success, name ) =>
tab.FileDialog.OpenFilePicker( $"Replace {objectName} #{idx} Program Blob...", "Shader Program Blobs{.o,.cso,.dxbc,.dxil}", ( success, name ) =>
{
if( !success )
{
@ -106,7 +106,7 @@ public partial class ModEditWindow
try
{
shaders[ idx ].Blob = File.ReadAllBytes( name );
shaders[ idx ].Blob = File.ReadAllBytes(name[0] );
}
catch( Exception e )
{
@ -128,7 +128,7 @@ public partial class ModEditWindow
}
tab.Shpk.SetChanged();
} );
}, 1, null, false );
}
private static unsafe void DrawRawDisassembly( Shader shader )
@ -193,7 +193,7 @@ public partial class ModEditWindow
var ret = false;
if( !disabled )
{
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 150.0f );
ImGui.SetNextItemWidth( UiHelpers.Scale * 150.0f );
if( ImGuiUtil.InputUInt16( $"{char.ToUpper( slotLabel[ 0 ] )}{slotLabel[ 1.. ].ToLower()}", ref resource.Slot, ImGuiInputTextFlags.None ) )
{
ret = true;
@ -285,11 +285,11 @@ public partial class ModEditWindow
return false;
}
ImGui.TableSetupColumn( string.Empty, ImGuiTableColumnFlags.WidthFixed, 25 * ImGuiHelpers.GlobalScale );
ImGui.TableSetupColumn( "x", ImGuiTableColumnFlags.WidthFixed, 100 * ImGuiHelpers.GlobalScale );
ImGui.TableSetupColumn( "y", ImGuiTableColumnFlags.WidthFixed, 100 * ImGuiHelpers.GlobalScale );
ImGui.TableSetupColumn( "z", ImGuiTableColumnFlags.WidthFixed, 100 * ImGuiHelpers.GlobalScale );
ImGui.TableSetupColumn( "w", ImGuiTableColumnFlags.WidthFixed, 100 * ImGuiHelpers.GlobalScale );
ImGui.TableSetupColumn( string.Empty, ImGuiTableColumnFlags.WidthFixed, 25 * UiHelpers.Scale );
ImGui.TableSetupColumn( "x", ImGuiTableColumnFlags.WidthFixed, 100 * UiHelpers.Scale );
ImGui.TableSetupColumn( "y", ImGuiTableColumnFlags.WidthFixed, 100 * UiHelpers.Scale );
ImGui.TableSetupColumn( "z", ImGuiTableColumnFlags.WidthFixed, 100 * UiHelpers.Scale );
ImGui.TableSetupColumn( "w", ImGuiTableColumnFlags.WidthFixed, 100 * UiHelpers.Scale );
ImGui.TableHeadersRow();
var textColorStart = ImGui.GetColorU32( ImGuiCol.Text );
@ -362,7 +362,7 @@ public partial class ModEditWindow
using var s = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing );
using( var _ = ImRaii.PushFont( UiBuilder.MonoFont ) )
{
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 400 );
ImGui.SetNextItemWidth( UiHelpers.Scale * 400 );
using var c = ImRaii.Combo( "##Start", tab.Orphans[ tab.NewMaterialParamStart ].Name );
if( c )
{
@ -385,7 +385,7 @@ public partial class ModEditWindow
using var s = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing );
using( var _ = ImRaii.PushFont( UiBuilder.MonoFont ) )
{
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 400 );
ImGui.SetNextItemWidth( UiHelpers.Scale * 400 );
using var c = ImRaii.Combo( "##End", tab.Orphans[ tab.NewMaterialParamEnd ].Name );
if( c )
{
@ -420,7 +420,7 @@ public partial class ModEditWindow
DrawShaderPackageStartCombo( tab );
DrawShaderPackageEndCombo( tab );
ImGui.SetNextItemWidth( ImGuiHelpers.GlobalScale * 400 );
ImGui.SetNextItemWidth( UiHelpers.Scale * 400 );
if( ImGui.InputText( "Name", ref tab.NewMaterialParamName, 63 ) )
{
tab.NewMaterialParamId = Crc32.Get( tab.NewMaterialParamName, 0xFFFFFFFFu );
@ -429,7 +429,7 @@ public partial class ModEditWindow
var tooltip = tab.UsedIds.Contains( tab.NewMaterialParamId )
? "The ID is already in use. Please choose a different name."
: string.Empty;
if( !ImGuiUtil.DrawDisabledButton( $"Add ID 0x{tab.NewMaterialParamId:X8}", new Vector2( 400 * ImGuiHelpers.GlobalScale, ImGui.GetFrameHeight() ), tooltip,
if( !ImGuiUtil.DrawDisabledButton( $"Add ID 0x{tab.NewMaterialParamId:X8}", new Vector2( 400 * UiHelpers.Scale, ImGui.GetFrameHeight() ), tooltip,
tooltip.Length > 0 ) )
{
return false;

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Utility;
using Lumina.Misc;
using OtterGui;
@ -16,19 +15,20 @@ public partial class ModEditWindow
public readonly ShpkFile Shpk;
public string NewMaterialParamName = string.Empty;
public uint NewMaterialParamId = Crc32.Get( string.Empty, 0xFFFFFFFFu );
public uint NewMaterialParamId = Crc32.Get(string.Empty, 0xFFFFFFFFu);
public short NewMaterialParamStart;
public short NewMaterialParamEnd;
public readonly FileDialogManager FileDialog = ConfigWindow.SetupFileManager();
public readonly FileDialogService FileDialog;
public readonly string Header;
public readonly string Extension;
public ShpkTab( byte[] bytes )
public ShpkTab(FileDialogService fileDialog, byte[] bytes)
{
Shpk = new ShpkFile( bytes, true );
Header = $"Shader Package for DirectX {( int )Shpk.DirectXVersion}";
FileDialog = fileDialog;
Shpk = new ShpkFile(bytes, true);
Header = $"Shader Package for DirectX {(int)Shpk.DirectXVersion}";
Extension = Shpk.DirectXVersion switch
{
ShpkFile.DxVersion.DirectX9 => ".cso",
@ -47,134 +47,130 @@ public partial class ModEditWindow
}
public (string Name, string Tooltip, short Index, ColorType Color)[,] Matrix = null!;
public readonly List< string > MalformedParameters = new();
public readonly HashSet< uint > UsedIds = new(16);
public readonly List< (string Name, short Index) > Orphans = new(16);
public readonly List<string> MalformedParameters = new();
public readonly HashSet<uint> UsedIds = new(16);
public readonly List<(string Name, short Index)> Orphans = new(16);
public void Update()
{
var materialParams = Shpk.GetConstantById( ShpkFile.MaterialParamsConstantId );
var numParameters = ( ( Shpk.MaterialParamsSize + 0xFu ) & ~0xFu ) >> 4;
var materialParams = Shpk.GetConstantById(ShpkFile.MaterialParamsConstantId);
var numParameters = ((Shpk.MaterialParamsSize + 0xFu) & ~0xFu) >> 4;
Matrix = new (string Name, string Tooltip, short Index, ColorType Color)[numParameters, 4];
MalformedParameters.Clear();
UsedIds.Clear();
foreach( var (param, idx) in Shpk.MaterialParams.WithIndex() )
foreach (var (param, idx) in Shpk.MaterialParams.WithIndex())
{
UsedIds.Add( param.Id );
UsedIds.Add(param.Id);
var iStart = param.ByteOffset >> 4;
var jStart = ( param.ByteOffset >> 2 ) & 3;
var iEnd = ( param.ByteOffset + param.ByteSize - 1 ) >> 4;
var jEnd = ( ( param.ByteOffset + param.ByteSize - 1 ) >> 2 ) & 3;
if( ( param.ByteOffset & 0x3 ) != 0 || ( param.ByteSize & 0x3 ) != 0 )
var jStart = (param.ByteOffset >> 2) & 3;
var iEnd = (param.ByteOffset + param.ByteSize - 1) >> 4;
var jEnd = ((param.ByteOffset + param.ByteSize - 1) >> 2) & 3;
if ((param.ByteOffset & 0x3) != 0 || (param.ByteSize & 0x3) != 0)
{
MalformedParameters.Add( $"ID: 0x{param.Id:X8}, offset: 0x{param.ByteOffset:X4}, size: 0x{param.ByteSize:X4}" );
MalformedParameters.Add($"ID: 0x{param.Id:X8}, offset: 0x{param.ByteOffset:X4}, size: 0x{param.ByteSize:X4}");
continue;
}
if( iEnd >= numParameters )
if (iEnd >= numParameters)
{
MalformedParameters.Add(
$"{MaterialParamRangeName( materialParams?.Name ?? string.Empty, param.ByteOffset >> 2, param.ByteSize >> 2 )} (ID: 0x{param.Id:X8})" );
$"{MaterialParamRangeName(materialParams?.Name ?? string.Empty, param.ByteOffset >> 2, param.ByteSize >> 2)} (ID: 0x{param.Id:X8})");
continue;
}
for( var i = iStart; i <= iEnd; ++i )
for (var i = iStart; i <= iEnd; ++i)
{
var end = i == iEnd ? jEnd : 3;
for( var j = i == iStart ? jStart : 0; j <= end; ++j )
for (var j = i == iStart ? jStart : 0; j <= end; ++j)
{
var tt = $"{MaterialParamRangeName( materialParams?.Name ?? string.Empty, param.ByteOffset >> 2, param.ByteSize >> 2 ).Item1} (ID: 0x{param.Id:X8})";
Matrix[ i, j ] = ( $"0x{param.Id:X8}", tt, ( short )idx, 0 );
var tt =
$"{MaterialParamRangeName(materialParams?.Name ?? string.Empty, param.ByteOffset >> 2, param.ByteSize >> 2).Item1} (ID: 0x{param.Id:X8})";
Matrix[i, j] = ($"0x{param.Id:X8}", tt, (short)idx, 0);
}
}
}
UpdateOrphans( materialParams );
UpdateColors( materialParams );
UpdateOrphans(materialParams);
UpdateColors(materialParams);
}
public void UpdateOrphanStart( int orphanStart )
public void UpdateOrphanStart(int orphanStart)
{
var oldEnd = Orphans.Count > 0 ? Orphans[ NewMaterialParamEnd ].Index : -1;
UpdateOrphanStart( orphanStart, oldEnd );
var oldEnd = Orphans.Count > 0 ? Orphans[NewMaterialParamEnd].Index : -1;
UpdateOrphanStart(orphanStart, oldEnd);
}
private void UpdateOrphanStart( int orphanStart, int oldEnd )
private void UpdateOrphanStart(int orphanStart, int oldEnd)
{
var count = Math.Min( NewMaterialParamEnd - NewMaterialParamStart + orphanStart + 1, Orphans.Count );
NewMaterialParamStart = ( short )orphanStart;
var current = Orphans[ NewMaterialParamStart ].Index;
for( var i = NewMaterialParamStart; i < count; ++i )
var count = Math.Min(NewMaterialParamEnd - NewMaterialParamStart + orphanStart + 1, Orphans.Count);
NewMaterialParamStart = (short)orphanStart;
var current = Orphans[NewMaterialParamStart].Index;
for (var i = NewMaterialParamStart; i < count; ++i)
{
var next = Orphans[ i ].Index;
if( current++ != next )
var next = Orphans[i].Index;
if (current++ != next)
{
NewMaterialParamEnd = ( short )( i - 1 );
NewMaterialParamEnd = (short)(i - 1);
return;
}
if( next == oldEnd )
if (next == oldEnd)
{
NewMaterialParamEnd = i;
return;
}
}
NewMaterialParamEnd = ( short )( count - 1 );
NewMaterialParamEnd = (short)(count - 1);
}
private void UpdateOrphans( ShpkFile.Resource? materialParams )
private void UpdateOrphans(ShpkFile.Resource? materialParams)
{
var oldStart = Orphans.Count > 0 ? Orphans[ NewMaterialParamStart ].Index : -1;
var oldEnd = Orphans.Count > 0 ? Orphans[ NewMaterialParamEnd ].Index : -1;
var oldStart = Orphans.Count > 0 ? Orphans[NewMaterialParamStart].Index : -1;
var oldEnd = Orphans.Count > 0 ? Orphans[NewMaterialParamEnd].Index : -1;
Orphans.Clear();
short newMaterialParamStart = 0;
for( var i = 0; i < Matrix.GetLength( 0 ); ++i )
for( var j = 0; j < 4; ++j )
for (var i = 0; i < Matrix.GetLength(0); ++i)
{
if( !Matrix[ i, j ].Name.IsNullOrEmpty() )
for (var j = 0; j < 4; ++j)
{
continue;
}
if (!Matrix[i, j].Name.IsNullOrEmpty())
continue;
Matrix[ i, j ] = ( "(none)", string.Empty, -1, 0 );
var linear = ( short )( 4 * i + j );
if( oldStart == linear )
{
newMaterialParamStart = ( short )Orphans.Count;
}
Matrix[i, j] = ("(none)", string.Empty, -1, 0);
var linear = (short)(4 * i + j);
if (oldStart == linear)
newMaterialParamStart = (short)Orphans.Count;
Orphans.Add( ( $"{materialParams?.Name ?? string.Empty}{MaterialParamName( false, linear )}", linear ) );
Orphans.Add(($"{materialParams?.Name ?? string.Empty}{MaterialParamName(false, linear)}", linear));
}
}
if( Orphans.Count == 0 )
{
if (Orphans.Count == 0)
return;
}
UpdateOrphanStart( newMaterialParamStart, oldEnd );
UpdateOrphanStart(newMaterialParamStart, oldEnd);
}
private void UpdateColors( ShpkFile.Resource? materialParams )
private void UpdateColors(ShpkFile.Resource? materialParams)
{
var lastIndex = -1;
for( var i = 0; i < Matrix.GetLength( 0 ); ++i )
for (var i = 0; i < Matrix.GetLength(0); ++i)
{
var usedComponents = ( materialParams?.Used?[ i ] ?? DisassembledShader.VectorComponents.All ) | ( materialParams?.UsedDynamically ?? 0 );
for( var j = 0; j < 4; ++j )
var usedComponents = (materialParams?.Used?[i] ?? DisassembledShader.VectorComponents.All)
| (materialParams?.UsedDynamically ?? 0);
for (var j = 0; j < 4; ++j)
{
var color = ( ( byte )usedComponents & ( 1 << j ) ) != 0
var color = ((byte)usedComponents & (1 << j)) != 0
? ColorType.Used
: 0;
if( Matrix[ i, j ].Index == lastIndex || Matrix[ i, j ].Index < 0 )
{
if (Matrix[i, j].Index == lastIndex || Matrix[i, j].Index < 0)
color |= ColorType.Continuation;
}
lastIndex = Matrix[ i, j ].Index;
Matrix[ i, j ].Color = color;
lastIndex = Matrix[i, j].Index;
Matrix[i, j].Color = color;
}
}
}
@ -185,4 +181,4 @@ public partial class ModEditWindow
public byte[] Write()
=> Shpk.Write();
}
}
}

View file

@ -2,12 +2,9 @@ using System;
using System.IO;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.ImGuiFileDialog;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using OtterTex;
using Penumbra.Import.Textures;
namespace Penumbra.UI.Classes;
@ -18,210 +15,181 @@ public partial class ModEditWindow
private readonly Texture _right = new();
private readonly CombinedTexture _center;
private readonly FileDialogManager _dialogManager = ConfigWindow.SetupFileManager();
private bool _overlayCollapsed = true;
private bool _overlayCollapsed = true;
private bool _addMipMaps = true;
private int _currentSaveAs = 0;
private bool _addMipMaps = true;
private int _currentSaveAs;
private static readonly (string, string)[] SaveAsStrings =
{
( "As Is", "Save the current texture with its own format without additional conversion or compression, if possible." ),
( "RGBA (Uncompressed)",
"Save the current texture as an uncompressed BGRA bitmap. This requires the most space but technically offers the best quality." ),
( "BC3 (Simple Compression)",
"Save the current texture compressed via BC3/DXT5 compression. This offers a 4:1 compression ratio and is quick with acceptable quality." ),
( "BC7 (Complex Compression)",
"Save the current texture compressed via BC7 compression. This offers a 4:1 compression ratio and has almost indistinguishable quality, but may take a while." ),
("As Is", "Save the current texture with its own format without additional conversion or compression, if possible."),
("RGBA (Uncompressed)",
"Save the current texture as an uncompressed BGRA bitmap. This requires the most space but technically offers the best quality."),
("BC3 (Simple Compression)",
"Save the current texture compressed via BC3/DXT5 compression. This offers a 4:1 compression ratio and is quick with acceptable quality."),
("BC7 (Complex Compression)",
"Save the current texture compressed via BC7 compression. This offers a 4:1 compression ratio and has almost indistinguishable quality, but may take a while."),
};
private void DrawInputChild( string label, Texture tex, Vector2 size, Vector2 imageSize )
private void DrawInputChild(string label, Texture tex, Vector2 size, Vector2 imageSize)
{
using var child = ImRaii.Child( label, size, true );
if( !child )
{
using var child = ImRaii.Child(label, size, true);
if (!child)
return;
}
using var id = ImRaii.PushId( label );
ImGuiUtil.DrawTextButton( label, new Vector2( -1, 0 ), ImGui.GetColorU32( ImGuiCol.FrameBg ) );
using var id = ImRaii.PushId(label);
ImGuiUtil.DrawTextButton(label, new Vector2(-1, 0), ImGui.GetColorU32(ImGuiCol.FrameBg));
ImGui.NewLine();
tex.PathInputBox( "##input", "Import Image...", "Can import game paths as well as your own files.", _mod!.ModPath.FullName,
_dialogManager );
var files = _editor!.TexFiles.SelectMany( f => f.SubModUsage.Select( p => (p.Item2.ToString(), true) )
.Prepend( (f.File.FullName, false )));
tex.PathSelectBox( "##combo", "Select the textures included in this mod on your drive or the ones they replace from the game files.",
files, _mod.ModPath.FullName.Length + 1 );
tex.PathInputBox("##input", "Import Image...", "Can import game paths as well as your own files.", _mod!.ModPath.FullName,
_fileDialog);
var files = _editor!.TexFiles.SelectMany(f => f.SubModUsage.Select(p => (p.Item2.ToString(), true))
.Prepend((f.File.FullName, false)));
tex.PathSelectBox("##combo", "Select the textures included in this mod on your drive or the ones they replace from the game files.",
files, _mod.ModPath.FullName.Length + 1);
if( tex == _left )
{
_center.DrawMatrixInputLeft( size.X );
}
if (tex == _left)
_center.DrawMatrixInputLeft(size.X);
else
{
_center.DrawMatrixInputRight( size.X );
}
_center.DrawMatrixInputRight(size.X);
ImGui.NewLine();
using var child2 = ImRaii.Child( "image" );
if( child2 )
{
tex.Draw( imageSize );
}
using var child2 = ImRaii.Child("image");
if (child2)
tex.Draw(imageSize);
}
private void SaveAsCombo()
{
var (text, desc) = SaveAsStrings[ _currentSaveAs ];
ImGui.SetNextItemWidth( -ImGui.GetFrameHeight() - ImGui.GetStyle().ItemSpacing.X );
using var combo = ImRaii.Combo( "##format", text );
ImGuiUtil.HoverTooltip( desc );
if( !combo )
{
var (text, desc) = SaveAsStrings[_currentSaveAs];
ImGui.SetNextItemWidth(-ImGui.GetFrameHeight() - ImGui.GetStyle().ItemSpacing.X);
using var combo = ImRaii.Combo("##format", text);
ImGuiUtil.HoverTooltip(desc);
if (!combo)
return;
}
foreach( var ((newText, newDesc), idx) in SaveAsStrings.WithIndex() )
foreach (var ((newText, newDesc), idx) in SaveAsStrings.WithIndex())
{
if( ImGui.Selectable( newText, idx == _currentSaveAs ) )
{
if (ImGui.Selectable(newText, idx == _currentSaveAs))
_currentSaveAs = idx;
}
ImGuiUtil.HoverTooltip( newDesc );
ImGuiUtil.HoverTooltip(newDesc);
}
}
private void MipMapInput()
{
ImGui.Checkbox( "##mipMaps", ref _addMipMaps );
ImGui.Checkbox("##mipMaps", ref _addMipMaps);
ImGuiUtil.HoverTooltip(
"Add the appropriate number of MipMaps to the file." );
"Add the appropriate number of MipMaps to the file.");
}
private void DrawOutputChild( Vector2 size, Vector2 imageSize )
private void DrawOutputChild(Vector2 size, Vector2 imageSize)
{
using var child = ImRaii.Child( "Output", size, true );
if( !child )
{
using var child = ImRaii.Child("Output", size, true);
if (!child)
return;
}
if( _center.IsLoaded )
if (_center.IsLoaded)
{
SaveAsCombo();
ImGui.SameLine();
MipMapInput();
if( ImGui.Button( "Save as TEX", -Vector2.UnitX ) )
if (ImGui.Button("Save as TEX", -Vector2.UnitX))
{
var fileName = Path.GetFileNameWithoutExtension( _left.Path.Length > 0 ? _left.Path : _right.Path );
_dialogManager.SaveFileDialog( "Save Texture as TEX...", ".tex", fileName, ".tex", ( a, b ) =>
var fileName = Path.GetFileNameWithoutExtension(_left.Path.Length > 0 ? _left.Path : _right.Path);
_fileDialog.OpenSavePicker("Save Texture as TEX...", ".tex", fileName, ".tex", (a, b) =>
{
if( a )
{
_center.SaveAsTex( b, ( CombinedTexture.TextureSaveType )_currentSaveAs, _addMipMaps );
}
}, _mod!.ModPath.FullName );
if (a)
_center.SaveAsTex(b, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps);
}, _mod!.ModPath.FullName, false);
}
if( ImGui.Button( "Save as DDS", -Vector2.UnitX ) )
if (ImGui.Button("Save as DDS", -Vector2.UnitX))
{
var fileName = Path.GetFileNameWithoutExtension( _right.Path.Length > 0 ? _right.Path : _left.Path );
_dialogManager.SaveFileDialog( "Save Texture as DDS...", ".dds", fileName, ".dds", ( a, b ) =>
var fileName = Path.GetFileNameWithoutExtension(_right.Path.Length > 0 ? _right.Path : _left.Path);
_fileDialog.OpenSavePicker("Save Texture as DDS...", ".dds", fileName, ".dds", (a, b) =>
{
if( a )
{
_center.SaveAsDds( b, ( CombinedTexture.TextureSaveType )_currentSaveAs, _addMipMaps );
}
}, _mod!.ModPath.FullName );
if (a)
_center.SaveAsDds(b, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps);
}, _mod!.ModPath.FullName, false);
}
ImGui.NewLine();
if( ImGui.Button( "Save as PNG", -Vector2.UnitX ) )
if (ImGui.Button("Save as PNG", -Vector2.UnitX))
{
var fileName = Path.GetFileNameWithoutExtension( _right.Path.Length > 0 ? _right.Path : _left.Path );
_dialogManager.SaveFileDialog( "Save Texture as PNG...", ".png", fileName, ".png", ( a, b ) =>
var fileName = Path.GetFileNameWithoutExtension(_right.Path.Length > 0 ? _right.Path : _left.Path);
_fileDialog.OpenSavePicker("Save Texture as PNG...", ".png", fileName, ".png", (a, b) =>
{
if( a )
{
_center.SaveAsPng( b );
}
}, _mod!.ModPath.FullName );
if (a)
_center.SaveAsPng(b);
}, _mod!.ModPath.FullName, false);
}
ImGui.NewLine();
}
if( _center.SaveException != null )
if (_center.SaveException != null)
{
ImGui.TextUnformatted( "Could not save file:" );
using var color = ImRaii.PushColor( ImGuiCol.Text, 0xFF0000FF );
ImGuiUtil.TextWrapped( _center.SaveException.ToString() );
ImGui.TextUnformatted("Could not save file:");
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF0000FF);
ImGuiUtil.TextWrapped(_center.SaveException.ToString());
}
using var child2 = ImRaii.Child( "image" );
if( child2 )
{
_center.Draw( imageSize );
}
using var child2 = ImRaii.Child("image");
if (child2)
_center.Draw(imageSize);
}
private Vector2 GetChildWidth()
{
var windowWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - ImGui.GetTextLineHeight();
if( _overlayCollapsed )
if (_overlayCollapsed)
{
var width = windowWidth - ImGui.GetStyle().FramePadding.X * 3;
return new Vector2( width / 2, -1 );
return new Vector2(width / 2, -1);
}
return new Vector2( ( windowWidth - ImGui.GetStyle().FramePadding.X * 5 ) / 3, -1 );
return new Vector2((windowWidth - ImGui.GetStyle().FramePadding.X * 5) / 3, -1);
}
private void DrawTextureTab()
{
_dialogManager.Draw();
using var tab = ImRaii.TabItem( "Texture Import/Export" );
if( !tab )
{
using var tab = ImRaii.TabItem("Texture Import/Export");
if (!tab)
return;
}
try
{
var childWidth = GetChildWidth();
var imageSize = new Vector2( childWidth.X - ImGui.GetStyle().FramePadding.X * 2 );
DrawInputChild( "Input Texture", _left, childWidth, imageSize );
var imageSize = new Vector2(childWidth.X - ImGui.GetStyle().FramePadding.X * 2);
DrawInputChild("Input Texture", _left, childWidth, imageSize);
ImGui.SameLine();
DrawOutputChild( childWidth, imageSize );
if( !_overlayCollapsed )
DrawOutputChild(childWidth, imageSize);
if (!_overlayCollapsed)
{
ImGui.SameLine();
DrawInputChild( "Overlay Texture", _right, childWidth, imageSize );
DrawInputChild("Overlay Texture", _right, childWidth, imageSize);
}
ImGui.SameLine();
DrawOverlayCollapseButton();
}
catch( Exception e )
catch (Exception e)
{
Penumbra.Log.Error( $"Unknown Error while drawing textures:\n{e}" );
Penumbra.Log.Error($"Unknown Error while drawing textures:\n{e}");
}
}
private void DrawOverlayCollapseButton()
{
var (label, tooltip) = _overlayCollapsed
? ( ">", "Show a third panel in which you can import an additional texture as an overlay for the primary texture." )
: ( "<", "Hide the overlay texture panel and clear the currently loaded overlay texture, if any." );
if( ImGui.Button( label, new Vector2( ImGui.GetTextLineHeight(), ImGui.GetContentRegionAvail().Y ) ) )
{
? (">", "Show a third panel in which you can import an additional texture as an overlay for the primary texture.")
: ("<", "Hide the overlay texture panel and clear the currently loaded overlay texture, if any.");
if (ImGui.Button(label, new Vector2(ImGui.GetTextLineHeight(), ImGui.GetContentRegionAvail().Y)))
_overlayCollapsed = !_overlayCollapsed;
}
ImGuiUtil.HoverTooltip( tooltip );
ImGuiUtil.HoverTooltip(tooltip);
}
}
}

View file

@ -477,21 +477,22 @@ public partial class ModEditWindow : Window, IDisposable
return new FullPath(path);
}
public ModEditWindow(CommunicatorService communicator)
public ModEditWindow(CommunicatorService communicator, FileDialogService fileDialog)
: base(WindowBaseLabel)
{
_fileDialog = fileDialog;
_swapWindow = new ItemSwapWindow(communicator);
_materialTab = new FileEditor<MtrlTab>("Materials", ".mtrl",
_materialTab = new FileEditor<MtrlTab>("Materials", ".mtrl", _fileDialog,
() => _editor?.MtrlFiles ?? Array.Empty<Editor.FileRegistry>(),
DrawMaterialPanel,
() => _mod?.ModPath.FullName ?? string.Empty,
bytes => new MtrlTab(this, new MtrlFile(bytes)));
_modelTab = new FileEditor<MdlFile>("Models", ".mdl",
_modelTab = new FileEditor<MdlFile>("Models", ".mdl", _fileDialog,
() => _editor?.MdlFiles ?? Array.Empty<Editor.FileRegistry>(),
DrawModelPanel,
() => _mod?.ModPath.FullName ?? string.Empty,
null);
_shaderPackageTab = new FileEditor<ShpkTab>("Shader Packages", ".shpk",
_shaderPackageTab = new FileEditor<ShpkTab>("Shader Packages", ".shpk", _fileDialog,
() => _editor?.ShpkFiles ?? Array.Empty<Editor.FileRegistry>(),
DrawShaderPackagePanel,
() => _mod?.ModPath.FullName ?? string.Empty,

View file

@ -1,313 +0,0 @@
using System;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using Dalamud.Interface;
using ImGuiNET;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Filesystem;
using OtterGui.Raii;
using Penumbra.Collections;
using Penumbra.Mods;
namespace Penumbra.UI.Classes;
public partial class ModFileSystemSelector
{
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public struct ModState
{
public ColorId Color;
}
private const StringComparison IgnoreCase = StringComparison.OrdinalIgnoreCase;
private LowerString _modFilter = LowerString.Empty;
private int _filterType = -1;
private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods;
private void SetFilterTooltip()
{
FilterTooltip = "Filter mods for those where their full paths or names contain the given substring.\n"
+ "Enter c:[string] to filter for mods changing specific items.\n"
+ "Enter t:[string] to filter for mods set to specific tags.\n"
+ "Enter n:[string] to filter only for mod names and no paths.\n"
+ "Enter a:[string] to filter for mods by specific authors.";
}
// Appropriately identify and set the string filter and its type.
protected override bool ChangeFilter( string filterValue )
{
( _modFilter, _filterType ) = filterValue.Length switch
{
0 => ( LowerString.Empty, -1 ),
> 1 when filterValue[ 1 ] == ':' =>
filterValue[ 0 ] switch
{
'n' => filterValue.Length == 2 ? ( LowerString.Empty, -1 ) : ( new LowerString( filterValue[ 2.. ] ), 1 ),
'N' => filterValue.Length == 2 ? ( LowerString.Empty, -1 ) : ( new LowerString( filterValue[ 2.. ] ), 1 ),
'a' => filterValue.Length == 2 ? ( LowerString.Empty, -1 ) : ( new LowerString( filterValue[ 2.. ] ), 2 ),
'A' => filterValue.Length == 2 ? ( LowerString.Empty, -1 ) : ( new LowerString( filterValue[ 2.. ] ), 2 ),
'c' => filterValue.Length == 2 ? ( LowerString.Empty, -1 ) : ( new LowerString( filterValue[ 2.. ] ), 3 ),
'C' => filterValue.Length == 2 ? ( LowerString.Empty, -1 ) : ( new LowerString( filterValue[ 2.. ] ), 3 ),
't' => filterValue.Length == 2 ? ( LowerString.Empty, -1 ) : ( new LowerString( filterValue[ 2.. ] ), 4 ),
'T' => filterValue.Length == 2 ? ( LowerString.Empty, -1 ) : ( new LowerString( filterValue[ 2.. ] ), 4 ),
_ => ( new LowerString( filterValue ), 0 ),
},
_ => ( new LowerString( filterValue ), 0 ),
};
return true;
}
// Check the state filter for a specific pair of has/has-not flags.
// Uses count == 0 to check for has-not and count != 0 for has.
// Returns true if it should be filtered and false if not.
private bool CheckFlags( int count, ModFilter hasNoFlag, ModFilter hasFlag )
{
return count switch
{
0 when _stateFilter.HasFlag( hasNoFlag ) => false,
0 => true,
_ when _stateFilter.HasFlag( hasFlag ) => false,
_ => true,
};
}
// The overwritten filter method also computes the state.
// 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< Mod >.IPath path, out ModState state )
{
if( path is ModFileSystem.Folder f )
{
state = default;
return ModFilterExtensions.UnfilteredStateMods != _stateFilter
|| FilterValue.Length > 0 && !f.FullName().Contains( FilterValue, IgnoreCase );
}
return ApplyFiltersAndState( ( ModFileSystem.Leaf )path, out state );
}
// Apply the string filters.
private bool ApplyStringFilters( ModFileSystem.Leaf leaf, Mod mod )
{
return _filterType switch
{
-1 => false,
0 => !( leaf.FullName().Contains( _modFilter.Lower, IgnoreCase ) || mod.Name.Contains( _modFilter ) ),
1 => !mod.Name.Contains( _modFilter ),
2 => !mod.Author.Contains( _modFilter ),
3 => !mod.LowerChangedItemsString.Contains( _modFilter.Lower ),
4 => !mod.AllTagsLower.Contains( _modFilter.Lower ),
_ => false, // Should never happen
};
}
// Only get the text color for a mod if no filters are set.
private static ColorId GetTextColor( Mod mod, ModSettings? settings, ModCollection collection )
{
if( Penumbra.ModManager.NewMods.Contains( mod ) )
{
return ColorId.NewMod;
}
if( settings == null )
{
return ColorId.UndefinedMod;
}
if( !settings.Enabled )
{
return collection != Penumbra.CollectionManager.Current ? ColorId.InheritedDisabledMod : ColorId.DisabledMod;
}
var conflicts = Penumbra.CollectionManager.Current.Conflicts( mod );
if( conflicts.Count == 0 )
{
return collection != Penumbra.CollectionManager.Current ? ColorId.InheritedMod : ColorId.EnabledMod;
}
return conflicts.Any( c => !c.Solved )
? ColorId.ConflictingMod
: ColorId.HandledConflictMod;
}
private bool CheckStateFilters( Mod mod, ModSettings? settings, ModCollection collection, ref ModState state )
{
var isNew = Penumbra.ModManager.NewMods.Contains( mod );
// Handle mod details.
if( CheckFlags( mod.TotalFileCount, ModFilter.HasNoFiles, ModFilter.HasFiles )
|| CheckFlags( mod.TotalSwapCount, ModFilter.HasNoFileSwaps, ModFilter.HasFileSwaps )
|| CheckFlags( mod.TotalManipulations, ModFilter.HasNoMetaManipulations, ModFilter.HasMetaManipulations )
|| CheckFlags( mod.HasOptions ? 1 : 0, ModFilter.HasNoConfig, ModFilter.HasConfig )
|| CheckFlags( isNew ? 1 : 0, ModFilter.NotNew, ModFilter.IsNew ) )
{
return true;
}
// Handle Favoritism
if( !_stateFilter.HasFlag( ModFilter.Favorite ) && mod.Favorite
|| !_stateFilter.HasFlag( ModFilter.NotFavorite ) && !mod.Favorite )
{
return true;
}
// Handle Inheritance
if( collection == Penumbra.CollectionManager.Current )
{
if( !_stateFilter.HasFlag( ModFilter.Uninherited ) )
{
return true;
}
}
else
{
state.Color = ColorId.InheritedMod;
if( !_stateFilter.HasFlag( ModFilter.Inherited ) )
{
return true;
}
}
// Handle settings.
if( settings == null )
{
state.Color = ColorId.UndefinedMod;
if( !_stateFilter.HasFlag( ModFilter.Undefined )
|| !_stateFilter.HasFlag( ModFilter.Disabled )
|| !_stateFilter.HasFlag( ModFilter.NoConflict ) )
{
return true;
}
}
else if( !settings.Enabled )
{
state.Color = collection == Penumbra.CollectionManager.Current ? ColorId.DisabledMod : ColorId.InheritedDisabledMod;
if( !_stateFilter.HasFlag( ModFilter.Disabled )
|| !_stateFilter.HasFlag( ModFilter.NoConflict ) )
{
return true;
}
}
else
{
if( !_stateFilter.HasFlag( ModFilter.Enabled ) )
{
return true;
}
// Conflicts can only be relevant if the mod is enabled.
var conflicts = Penumbra.CollectionManager.Current.Conflicts( mod );
if( conflicts.Count > 0 )
{
if( conflicts.Any( c => !c.Solved ) )
{
if( !_stateFilter.HasFlag( ModFilter.UnsolvedConflict ) )
{
return true;
}
state.Color = ColorId.ConflictingMod;
}
else
{
if( !_stateFilter.HasFlag( ModFilter.SolvedConflict ) )
{
return true;
}
state.Color = ColorId.HandledConflictMod;
}
}
else if( !_stateFilter.HasFlag( ModFilter.NoConflict ) )
{
return true;
}
}
// isNew color takes precedence before other colors.
if( isNew )
{
state.Color = ColorId.NewMod;
}
return false;
}
// Combined wrapper for handling all filters and setting state.
private bool ApplyFiltersAndState( ModFileSystem.Leaf leaf, out ModState state )
{
state = new ModState { Color = ColorId.EnabledMod };
var mod = leaf.Value;
var (settings, collection) = Penumbra.CollectionManager.Current[ mod.Index ];
if( ApplyStringFilters( leaf, mod ) )
{
return true;
}
if( _stateFilter != ModFilterExtensions.UnfilteredStateMods )
{
return CheckStateFilters( mod, settings, collection, ref state );
}
state.Color = GetTextColor( mod, settings, collection );
return false;
}
private void DrawFilterCombo( ref bool everything )
{
using var combo = ImRaii.Combo( "##filterCombo", string.Empty,
ImGuiComboFlags.NoPreview | ImGuiComboFlags.PopupAlignLeft | ImGuiComboFlags.HeightLargest );
if( combo )
{
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing,
ImGui.GetStyle().ItemSpacing with { Y = 3 * ImGuiHelpers.GlobalScale } );
var flags = ( int )_stateFilter;
if( ImGui.Checkbox( "Everything", ref everything ) )
{
_stateFilter = everything ? ModFilterExtensions.UnfilteredStateMods : 0;
SetFilterDirty();
}
ImGui.Dummy( new Vector2( 0, 5 * ImGuiHelpers.GlobalScale ) );
foreach( ModFilter flag in Enum.GetValues( typeof( ModFilter ) ) )
{
if( ImGui.CheckboxFlags( flag.ToName(), ref flags, ( int )flag ) )
{
_stateFilter = ( ModFilter )flags;
SetFilterDirty();
}
}
}
}
// Add the state filter combo-button to the right of the filter box.
protected override float CustomFilters( float width )
{
var pos = ImGui.GetCursorPos();
var remainingWidth = width - ImGui.GetFrameHeight();
var comboPos = new Vector2( pos.X + remainingWidth, pos.Y );
var everything = _stateFilter == ModFilterExtensions.UnfilteredStateMods;
ImGui.SetCursorPos( comboPos );
// Draw combo button
using var color = ImRaii.PushColor( ImGuiCol.Button, Colors.FilterActive, !everything );
DrawFilterCombo( ref everything );
ConfigWindow.OpenTutorial( ConfigWindow.BasicTutorialSteps.ModFilters );
if( ImGui.IsItemClicked( ImGuiMouseButton.Right ) )
{
_stateFilter = ModFilterExtensions.UnfilteredStateMods;
SetFilterDirty();
}
ImGuiUtil.HoverTooltip( "Filter mods for their activation status.\nRight-Click to clear all filters." );
ImGui.SetCursorPos( pos );
return remainingWidth;
}
}

View file

@ -1,478 +0,0 @@
using Dalamud.Interface;
using Dalamud.Interface.ImGuiFileDialog;
using ImGuiNET;
using OtterGui;
using OtterGui.Filesystem;
using OtterGui.FileSystem.Selector;
using OtterGui.Raii;
using Penumbra.Collections;
using Penumbra.Import;
using Penumbra.Mods;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Numerics;
using Penumbra.Api.Enums;
using Penumbra.Services;
namespace Penumbra.UI.Classes;
public sealed partial class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSystemSelector.ModState>
{
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(CommunicatorService communicator, ModFileSystem fileSystem)
: base(fileSystem, DalamudServices.KeyState)
{
_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;
_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, "");
}
public override void Dispose()
{
base.Dispose();
Penumbra.ModManager.ModDiscoveryStarted -= StoreCurrentSelection;
Penumbra.ModManager.ModDiscoveryFinished -= RestoreLastSelection;
Penumbra.ModManager.ModDataChanged -= OnModDataChange;
Penumbra.CollectionManager.Current.ModSettingChanged -= OnSettingChange;
Penumbra.CollectionManager.Current.InheritanceChanged -= OnInheritanceChange;
_communicator.CollectionChange.Event -= OnCollectionChange;
_import?.Dispose();
_import = null;
}
public new ModFileSystem.Leaf? SelectedLeaf
=> base.SelectedLeaf;
// Customization points.
public override ISortMode<Mod> SortMode
=> Penumbra.Config.SortMode;
protected override uint ExpandedFolderColor
=> ColorId.FolderExpanded.Value();
protected override uint CollapsedFolderColor
=> ColorId.FolderCollapsed.Value();
protected override uint FolderLineColor
=> ColorId.FolderLine.Value();
protected override bool FoldersDefaultOpen
=> Penumbra.Config.OpenFoldersByDefault;
protected override void DrawPopups()
{
_fileManager.Draw();
DrawHelpPopup();
DrawInfoPopup();
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);
_newModName = string.Empty;
}
catch (Exception e)
{
Penumbra.Log.Error($"Could not create directory for new Mod {_newModName}:\n{e}");
}
while (_modsToAdd.TryDequeue(out var dir))
{
Penumbra.ModManager.AddMod(dir);
var mod = Penumbra.ModManager.LastOrDefault();
if (mod != null)
{
MoveModToDefaultDirectory(mod);
SelectByValue(mod);
}
}
}
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();
}
// Add custom context menu items.
private static void EnableDescendants(ModFileSystem.Folder folder)
{
if (ImGui.MenuItem("Enable Descendants"))
SetDescendants(folder, true);
}
private static void DisableDescendants(ModFileSystem.Folder folder)
{
if (ImGui.MenuItem("Disable Descendants"))
SetDescendants(folder, false);
}
private static void InheritDescendants(ModFileSystem.Folder folder)
{
if (ImGui.MenuItem("Inherit Descendants"))
SetDescendants(folder, true, true);
}
private static void OwnDescendants(ModFileSystem.Folder folder)
{
if (ImGui.MenuItem("Stop Inheriting Descendants"))
SetDescendants(folder, false, true);
}
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);
}
private static void SetDefaultImportFolder(ModFileSystem.Folder folder)
{
if (ImGui.MenuItem("Set As Default Import Folder"))
{
var newName = folder.FullName();
if (newName != Penumbra.Config.DefaultImportFolder)
{
Penumbra.Config.DefaultImportFolder = newName;
Penumbra.Config.Save();
}
}
}
private static void ClearDefaultImportFolder()
{
if (ImGui.MenuItem("Clear Default Import Folder") && Penumbra.Config.DefaultImportFolder.Length > 0)
{
Penumbra.Config.DefaultImportFolder = string.Empty;
Penumbra.Config.Save();
}
}
// Add custom buttons.
private string _newModName = string.Empty;
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");
}
// Add an import mods button that opens a file selector.
// Only set the initial directory once.
private bool _hasSetFolder;
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)
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;
_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) =>
{
if (s)
{
_import = new TexToolsImporter(Penumbra.ModManager.BasePath, f.Count, f.Select(file => new FileInfo(file)),
AddNewMod);
ImGui.OpenPopup("Import Status");
}
}, 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 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)
return;
using (var child = ImRaii.Child("##import", new Vector2(-1, size.Y - ImGui.GetFrameHeight() * 2)))
{
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))
{
_import?.Dispose();
_import = null;
ImGui.CloseCurrentPopup();
}
}
// Mods need to be added thread-safely outside of iteration.
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)
{
if (error != null)
{
if (dir != null && Directory.Exists(dir.FullName))
try
{
Directory.Delete(dir.FullName, true);
}
catch (Exception 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}");
}
else if (dir != null)
{
_modsToAdd.Enqueue(dir);
}
}
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)
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);
}
private static void AddHelpButton(Vector2 size)
{
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.QuestionCircle.ToIconString(), size, "Open extended help.", false, true))
ImGui.OpenPopup("ExtendedHelp");
ConfigWindow.OpenTutorial(ConfigWindow.BasicTutorialSteps.AdvancedHelp);
}
// Helpers.
private static void SetDescendants(ModFileSystem.Folder folder, bool enabled, bool inherit = false)
{
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);
return l.Value;
});
if (inherit)
Penumbra.CollectionManager.Current.SetMultipleModInheritances(mods, enabled);
else
Penumbra.CollectionManager.Current.SetMultipleModStates(mods, enabled);
}
// Automatic cache update functions.
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);
}
private void OnModDataChange(ModDataChangeType type, Mod mod, string? oldName)
{
switch (type)
{
case ModDataChangeType.Name:
case ModDataChangeType.Author:
case ModDataChangeType.ModTags:
case ModDataChangeType.LocalTags:
case ModDataChangeType.Favorite:
SetFilterDirty();
break;
}
}
private void OnInheritanceChange(bool _)
{
SetFilterDirty();
OnSelectionChange(Selected, Selected, default);
}
private void OnCollectionChange(CollectionType collectionType, ModCollection? oldCollection, ModCollection? newCollection, string _)
{
if (collectionType != CollectionType.Current || oldCollection == newCollection)
return;
if (oldCollection != null)
{
oldCollection.ModSettingChanged -= OnSettingChange;
oldCollection.InheritanceChanged -= OnInheritanceChange;
}
if (newCollection != null)
{
newCollection.ModSettingChanged += OnSettingChange;
newCollection.InheritanceChanged += OnInheritanceChange;
}
SetFilterDirty();
OnSelectionChange(Selected, Selected, default);
}
private void OnSelectionChange(Mod? _1, Mod? newSelection, in ModState _2)
{
if (newSelection == null)
{
SelectedSettings = ModSettings.Empty;
SelectedSettingCollection = ModCollection.Empty;
}
else
{
(var settings, SelectedSettingCollection) = Penumbra.CollectionManager.Current[newSelection.Index];
SelectedSettings = settings ?? ModSettings.Empty;
}
}
// Keep selections across rediscoveries if possible.
private string _lastSelectedDirectory = string.Empty;
private void StoreCurrentSelection()
{
_lastSelectedDirectory = Selected?.ModPath.FullName ?? string.Empty;
ClearSelection();
}
private void RestoreLastSelection()
{
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);
_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)
{
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 folder = FileSystem.FindOrCreateAllFolders(Penumbra.Config.DefaultImportFolder);
FileSystem.Move(leaf, folder);
}
catch (Exception e)
{
Penumbra.Log.Warning(
$"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()), () =>
{
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(
"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.");
});
}
}

View file

@ -1,59 +0,0 @@
using System;
namespace Penumbra.UI.Classes;
[Flags]
public enum ModFilter
{
Enabled = 1 << 0,
Disabled = 1 << 1,
Favorite = 1 << 2,
NotFavorite = 1 << 3,
NoConflict = 1 << 4,
SolvedConflict = 1 << 5,
UnsolvedConflict = 1 << 6,
HasNoMetaManipulations = 1 << 7,
HasMetaManipulations = 1 << 8,
HasNoFileSwaps = 1 << 9,
HasFileSwaps = 1 << 10,
HasConfig = 1 << 11,
HasNoConfig = 1 << 12,
HasNoFiles = 1 << 13,
HasFiles = 1 << 14,
IsNew = 1 << 15,
NotNew = 1 << 16,
Inherited = 1 << 17,
Uninherited = 1 << 18,
Undefined = 1 << 19,
};
public static class ModFilterExtensions
{
public const ModFilter UnfilteredStateMods = ( ModFilter )( ( 1 << 20 ) - 1 );
public static string ToName( this ModFilter filter )
=> filter switch
{
ModFilter.Enabled => "Enabled",
ModFilter.Disabled => "Disabled",
ModFilter.Favorite => "Favorite",
ModFilter.NotFavorite => "No Favorite",
ModFilter.NoConflict => "No Conflicts",
ModFilter.SolvedConflict => "Solved Conflicts",
ModFilter.UnsolvedConflict => "Unsolved Conflicts",
ModFilter.HasNoMetaManipulations => "No Meta Manipulations",
ModFilter.HasMetaManipulations => "Meta Manipulations",
ModFilter.HasNoFileSwaps => "No File Swaps",
ModFilter.HasFileSwaps => "File Swaps",
ModFilter.HasNoConfig => "No Configuration",
ModFilter.HasConfig => "Configuration",
ModFilter.HasNoFiles => "No Files",
ModFilter.HasFiles => "Files",
ModFilter.IsNew => "Newly Imported",
ModFilter.NotNew => "Not Newly Imported",
ModFilter.Inherited => "Inherited Configuration",
ModFilter.Uninherited => "Own Configuration",
ModFilter.Undefined => "Not Configured",
_ => throw new ArgumentOutOfRangeException( nameof( filter ), filter, null ),
};
}