This commit is contained in:
Ottermandias 2022-04-06 11:34:12 +02:00
parent 33db156544
commit 8db54ef4f4
17 changed files with 546 additions and 212 deletions

10
Penumbra/UI/Colors.cs Normal file
View file

@ -0,0 +1,10 @@
namespace Penumbra.UI;
public static class Colors
{
public const uint DefaultTextColor = 0xFFFFFFFFu;
public const uint NewModColor = 0xFF66DD66u;
public const uint DisabledModColor = 0xFF666666u;
public const uint ConflictingModColor = 0xFFAAAAFFu;
public const uint HandledConflictModColor = 0xFF88DDDDu;
}

View file

@ -0,0 +1,330 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using ImGuiNET;
using OtterGui;
using OtterGui.Filesystem;
using OtterGui.FileSystem.Selector;
using OtterGui.Raii;
using Penumbra.Collections;
using Penumbra.Mods;
namespace Penumbra.UI;
public sealed class ModFileSystemSelector : FileSystemSelector< Mod, ModState >
{
private readonly IReadOnlySet< Mod > _newMods = new HashSet<Mod>();
private LowerString _modFilter = LowerString.Empty;
private LowerString _modFilterAuthor = LowerString.Empty;
private LowerString _modFilterChanges = LowerString.Empty;
private LowerString _modFilterName = LowerString.Empty;
private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods;
public ModFilter StateFilter
{
get => _stateFilter;
set
{
var diff = _stateFilter != value;
_stateFilter = value;
if( diff )
{
SetFilterDirty();
}
}
}
protected override bool ChangeFilter( string filterValue )
{
if( filterValue.StartsWith( "c:", StringComparison.InvariantCultureIgnoreCase ) )
{
_modFilterChanges = new LowerString( filterValue[ 2.. ] );
_modFilter = LowerString.Empty;
_modFilterAuthor = LowerString.Empty;
_modFilterName = LowerString.Empty;
}
else if( filterValue.StartsWith( "a:", StringComparison.InvariantCultureIgnoreCase ) )
{
_modFilterAuthor = new LowerString( filterValue[ 2.. ] );
_modFilter = LowerString.Empty;
_modFilterChanges = LowerString.Empty;
_modFilterName = LowerString.Empty;
}
else if( filterValue.StartsWith( "n:", StringComparison.InvariantCultureIgnoreCase ) )
{
_modFilterName = new LowerString( filterValue[ 2.. ] );
_modFilter = LowerString.Empty;
_modFilterChanges = LowerString.Empty;
_modFilterAuthor = LowerString.Empty;
}
else
{
_modFilter = new LowerString( filterValue );
_modFilterAuthor = LowerString.Empty;
_modFilterChanges = LowerString.Empty;
_modFilterName = LowerString.Empty;
}
return true;
}
private bool CheckFlags( int count, ModFilter hasNoFlag, ModFilter hasFlag )
{
if( count == 0 )
{
if( StateFilter.HasFlag( hasNoFlag ) )
{
return false;
}
}
else if( StateFilter.HasFlag( hasFlag ) )
{
return false;
}
return true;
}
private ModState GetModState( Mod mod, ModSettings? settings )
{
if( settings?.Enabled != true )
{
return new ModState { Color = ImGui.GetColorU32( ImGuiCol.TextDisabled ) };
}
return new ModState { Color = ImGui.GetColorU32( ImGuiCol.Text ) };
}
protected override bool ApplyFiltersAndState( FileSystem< Mod >.IPath path, out ModState state )
{
if( path is ModFileSystemA.Folder f )
{
return base.ApplyFiltersAndState( f, out state );
}
return ApplyFiltersAndState( ( ModFileSystemA.Leaf )path, out state );
}
private bool CheckPath( string path, Mod mod )
=> _modFilter.IsEmpty
|| path.Contains( _modFilter.Lower, StringComparison.InvariantCultureIgnoreCase )
|| mod.Meta.Name.Contains( _modFilter );
private bool CheckName( Mod mod )
=> _modFilterName.IsEmpty || mod.Meta.Name.Contains( _modFilterName );
private bool CheckAuthor( Mod mod )
=> _modFilterAuthor.IsEmpty || mod.Meta.Author.Contains( _modFilterAuthor );
private bool CheckItems( Mod mod )
=> _modFilterChanges.IsEmpty || mod.LowerChangedItemsString.Contains( _modFilterChanges.Lower );
private bool ApplyFiltersAndState( ModFileSystemA.Leaf leaf, out ModState state )
{
state = new ModState { Color = Colors.DefaultTextColor };
var mod = leaf.Value;
var (settings, collection) = Current[ mod.Index ];
// Check string filters.
if( !( CheckPath( leaf.FullName(), mod )
&& CheckName( mod )
&& CheckAuthor( mod )
&& CheckItems( mod ) ) )
{
return true;
}
var isNew = _newMods.Contains( mod );
if( CheckFlags( mod.Resources.ModFiles.Count, ModFilter.HasNoFiles, ModFilter.HasFiles )
|| CheckFlags( mod.Meta.FileSwaps.Count, ModFilter.HasNoFileSwaps, ModFilter.HasFileSwaps )
|| CheckFlags( mod.Resources.MetaManipulations.Count, ModFilter.HasNoMetaManipulations, ModFilter.HasMetaManipulations )
|| CheckFlags( mod.Meta.HasGroupsWithConfig ? 1 : 0, ModFilter.HasNoConfig, ModFilter.HasConfig )
|| CheckFlags( isNew ? 1 : 0, ModFilter.IsNew, ModFilter.NotNew ) )
{
return true;
}
if( settings == null )
{
state.Color = Colors.DisabledModColor;
if( !StateFilter.HasFlag( ModFilter.Undefined ) )
{
return true;
}
settings = new ModSettings();
}
if( !settings.Enabled )
{
state.Color = Colors.DisabledModColor;
if( !StateFilter.HasFlag( ModFilter.Disabled ) )
{
return true;
}
}
else
{
if( !StateFilter.HasFlag( ModFilter.Enabled ) )
{
return true;
}
var conflicts = Penumbra.CollectionManager.Current.ModConflicts( mod.Index ).ToList();
if( conflicts.Count > 0 )
{
if( conflicts.Any( c => !c.Solved ) )
{
if( !StateFilter.HasFlag( ModFilter.UnsolvedConflict ) )
{
return true;
}
state.Color = Colors.ConflictingModColor;
}
else
{
if( !StateFilter.HasFlag( ModFilter.SolvedConflict ) )
{
return true;
}
state.Color = Colors.HandledConflictModColor;
}
}
else if( !StateFilter.HasFlag( ModFilter.NoConflict ) )
{
return true;
}
}
if( collection == Current )
{
if( !StateFilter.HasFlag( ModFilter.Uninherited ) )
{
return true;
}
}
else
{
if( !StateFilter.HasFlag( ModFilter.Inherited ) )
{
return true;
}
}
if( isNew )
{
state.Color = Colors.NewModColor;
}
return false;
}
protected override float CustomFilters( float width )
{
var pos = ImGui.GetCursorPos();
var remainingWidth = width - ImGui.GetFrameHeight();
var comboPos = new Vector2( pos.X + remainingWidth, pos.Y );
ImGui.SetCursorPos( comboPos );
using var combo = ImRaii.Combo( "##filterCombo", string.Empty,
ImGuiComboFlags.NoPreview | ImGuiComboFlags.PopupAlignLeft | ImGuiComboFlags.HeightLargest );
if( combo )
{
ImGui.Text( "A" );
ImGui.Text( "B" );
ImGui.Text( "C" );
}
combo.Dispose();
ImGui.SetCursorPos( pos );
return remainingWidth;
}
public ModFileSystemSelector( ModFileSystemA fileSystem )
: base( fileSystem )
{
SubscribeRightClickFolder( EnableDescendants, 10 );
SubscribeRightClickFolder( DisableDescendants, 10 );
SubscribeRightClickFolder( InheritDescendants, 15 );
SubscribeRightClickFolder( OwnDescendants, 15 );
AddButton( AddNewModButton, 0 );
AddButton( DeleteModButton, 1000 );
}
private static ModCollection Current
=> Penumbra.CollectionManager.Current;
private static void EnableDescendants( ModFileSystemA.Folder folder )
{
if( ImGui.MenuItem( "Enable Descendants" ) )
{
SetDescendants( folder, true, false );
}
}
private static void DisableDescendants( ModFileSystemA.Folder folder )
{
if( ImGui.MenuItem( "Disable Descendants" ) )
{
SetDescendants( folder, false, false );
}
}
private static void InheritDescendants( ModFileSystemA.Folder folder )
{
if( ImGui.MenuItem( "Inherit Descendants" ) )
{
SetDescendants( folder, true, true );
}
}
private static void OwnDescendants( ModFileSystemA.Folder folder )
{
if( ImGui.MenuItem( "Stop Inheriting Descendants" ) )
{
SetDescendants( folder, false, true );
}
}
private static void AddNewModButton( Vector2 size )
{
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Plus.ToIconString(), size, "Create a new, empty mod of a given name.", false, true ) )
{ }
}
private void DeleteModButton( Vector2 size )
{
if( ImGuiUtil.DrawDisabledButton( FontAwesomeIcon.Trash.ToIconString(), size,
"Delete the currently selected mod entirely from your drive.", SelectedLeaf == null, true ) )
{ }
}
private static void SetDescendants( ModFileSystemA.Folder folder, bool enabled, bool inherit = false )
{
var mods = folder.GetAllDescendants( SortMode.Lexicographical ).OfType< ModFileSystemA.Leaf >().Select( l => l.Value );
if( inherit )
{
Current.SetMultipleModInheritances( mods, enabled );
}
else
{
Current.SetMultipleModStates( mods, enabled );
}
}
public override SortMode SortMode
=> Penumbra.Config.SortFoldersFirst ? SortMode.FoldersFirst : SortMode.Lexicographical;
protected override void DrawLeafName( FileSystem< Mod >.Leaf leaf, in ModState state, bool selected )
{
var flags = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags;
using var c = ImRaii.PushColor( ImGuiCol.Text, state.Color );
using var _ = ImRaii.TreeNode( leaf.Value.Meta.Name, flags );
}
}

View file

@ -1,24 +1,37 @@
using System.Diagnostics;
using ImGuiNET;
using System.Runtime.InteropServices;
using OtterGui.Raii;
using Penumbra.Mods;
namespace Penumbra.UI
namespace Penumbra.UI;
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public struct ModState
{
public partial class SettingsInterface
{
private class TabBrowser
{
[Conditional( "LEAVEMEALONE" )]
public void Draw()
{
var ret = ImGui.BeginTabItem( "Available Mods" );
if( !ret )
{
return;
}
public uint Color;
}
ImGui.Text( "woah" );
ImGui.EndTabItem();
public partial class SettingsInterface
{
private class TabBrowser
{
private readonly ModFileSystemA _fileSystem;
private readonly ModFileSystemSelector _selector;
public TabBrowser()
{
_fileSystem = ModFileSystemA.Load();
_selector = new ModFileSystemSelector( _fileSystem );
}
public void Draw()
{
using var ret = ImRaii.TabItem( "Available Mods" );
if( !ret )
{
return;
}
_selector.Draw( 400 );
}
}
}

View file

@ -3,6 +3,7 @@ using System.IO;
using System.Linq;
using System.Numerics;
using ImGuiNET;
using OtterGui.Raii;
using Penumbra.Api;
using Penumbra.UI.Custom;
using CharacterUtility = Penumbra.Interop.CharacterUtility;
@ -12,6 +13,17 @@ namespace Penumbra.UI;
public partial class SettingsInterface
{
private string ImGuiIdTester = string.Empty;
private void DrawImGuiIdTester()
{
ImGui.SetNextItemWidth( 200 );
ImGui.InputText( "##abc1", ref ImGuiIdTester, 32 );
ImGui.SameLine();
ImGui.Text( ImGui.GetID( ImGuiIdTester ).ToString( "X" ) );
}
private static void PrintValue( string name, string value )
{
ImGui.TableNextRow();
@ -289,6 +301,16 @@ public partial class SettingsInterface
}
}
private void DrawDebugTabUtility()
{
if( !ImGui.CollapsingHeader( "Utilities##Debug" ) )
{
return;
}
DrawImGuiIdTester();
}
private void DrawDebugTab()
{
if( !ImGui.BeginTabItem( "Debug Tab" ) )
@ -324,5 +346,7 @@ public partial class SettingsInterface
ImGui.NewLine();
DrawDebugTabIpc();
ImGui.NewLine();
DrawDebugTabUtility();
ImGui.NewLine();
}
}

View file

@ -3,6 +3,7 @@ using System.IO;
using System.Linq;
using Dalamud.Interface;
using ImGuiNET;
using OtterGui;
using Penumbra.Collections;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Util;

View file

@ -1,50 +1,55 @@
using System;
namespace Penumbra.UI
namespace Penumbra.UI;
[Flags]
public enum ModFilter
{
[Flags]
public enum ModFilter
{
Enabled = 1 << 0,
Disabled = 1 << 1,
NoConflict = 1 << 2,
SolvedConflict = 1 << 3,
UnsolvedConflict = 1 << 4,
HasNoMetaManipulations = 1 << 5,
HasMetaManipulations = 1 << 6,
HasNoFileSwaps = 1 << 7,
HasFileSwaps = 1 << 8,
HasConfig = 1 << 9,
HasNoConfig = 1 << 10,
HasNoFiles = 1 << 11,
HasFiles = 1 << 12,
IsNew = 1 << 13,
NotNew = 1 << 14,
};
Enabled = 1 << 0,
Disabled = 1 << 1,
NoConflict = 1 << 2,
SolvedConflict = 1 << 3,
UnsolvedConflict = 1 << 4,
HasNoMetaManipulations = 1 << 5,
HasMetaManipulations = 1 << 6,
HasNoFileSwaps = 1 << 7,
HasFileSwaps = 1 << 8,
HasConfig = 1 << 9,
HasNoConfig = 1 << 10,
HasNoFiles = 1 << 11,
HasFiles = 1 << 12,
IsNew = 1 << 13,
NotNew = 1 << 14,
Inherited = 1 << 15,
Uninherited = 1 << 16,
Undefined = 1 << 17,
};
public static class ModFilterExtensions
{
public const ModFilter UnfilteredStateMods = ( ModFilter )( ( 1 << 15 ) - 1 );
public static class ModFilterExtensions
{
public const ModFilter UnfilteredStateMods = ( ModFilter )( ( 1 << 18 ) - 1 );
public static string ToName( this ModFilter filter )
=> filter switch
{
ModFilter.Enabled => "Enabled",
ModFilter.Disabled => "Disabled",
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",
_ => throw new ArgumentOutOfRangeException( nameof( filter ), filter, null ),
};
}
public static string ToName( this ModFilter filter )
=> filter switch
{
ModFilter.Enabled => "Enabled",
ModFilter.Disabled => "Disabled",
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 ),
};
}

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Logging;
using OtterGui;
using Penumbra.Mods;
using Penumbra.Util;
@ -9,12 +10,7 @@ namespace Penumbra.UI;
public class ModListCache : IDisposable
{
public const uint NewModColor = 0xFF66DD66u;
public const uint DisabledModColor = 0xFF666666u;
public const uint ConflictingModColor = 0xFFAAAAFFu;
public const uint HandledConflictModColor = 0xFF88DDDDu;
private readonly Mods.Mod.Manager _manager;
private readonly Mod.Manager _manager;
private readonly List< FullMod > _modsInOrder = new();
private readonly List< (bool visible, uint color) > _visibleMods = new();
@ -24,10 +20,11 @@ public class ModListCache : IDisposable
private LowerString _modFilter = LowerString.Empty;
private LowerString _modFilterAuthor = LowerString.Empty;
private LowerString _modFilterChanges = LowerString.Empty;
private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods;
private bool _listResetNecessary;
private bool _filterResetNecessary;
private bool _listResetNecessary;
private bool _filterResetNecessary;
private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods;
public ModFilter StateFilter
{
@ -43,7 +40,7 @@ public class ModListCache : IDisposable
}
}
public ModListCache( Mods.Mod.Manager manager, IReadOnlySet< string > newMods )
public ModListCache( Mod.Manager manager, IReadOnlySet< string > newMods )
{
_manager = manager;
_newMods = newMods;
@ -281,7 +278,7 @@ public class ModListCache : IDisposable
return ret;
}
ret.Item2 = ret.Item2 == 0 ? DisabledModColor : ret.Item2;
ret.Item2 = ret.Item2 == 0 ? Colors.DisabledModColor : ret.Item2;
}
if( mod.Settings.Enabled && !StateFilter.HasFlag( ModFilter.Enabled ) )
@ -299,7 +296,7 @@ public class ModListCache : IDisposable
return ret;
}
ret.Item2 = ret.Item2 == 0 ? ConflictingModColor : ret.Item2;
ret.Item2 = ret.Item2 == 0 ? Colors.ConflictingModColor : ret.Item2;
}
else
{
@ -308,7 +305,7 @@ public class ModListCache : IDisposable
return ret;
}
ret.Item2 = ret.Item2 == 0 ? HandledConflictModColor : ret.Item2;
ret.Item2 = ret.Item2 == 0 ? Colors.HandledConflictModColor : ret.Item2;
}
}
else if( !StateFilter.HasFlag( ModFilter.NoConflict ) )
@ -319,7 +316,7 @@ public class ModListCache : IDisposable
ret.Item1 = true;
if( isNew )
{
ret.Item2 = NewModColor;
ret.Item2 = Colors.NewModColor;
}
SetFolderAndParentsVisible( mod.Data.Order.ParentFolder );

View file

@ -217,18 +217,18 @@ public partial class SettingsInterface
ImGui.Text( "Enabled in the current collection." );
ImGui.Bullet();
ImGui.SameLine();
ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( ModListCache.DisabledModColor ), "Disabled in the current collection." );
ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( Colors.DisabledModColor ), "Disabled in the current collection." );
ImGui.Bullet();
ImGui.SameLine();
ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( ModListCache.NewModColor ),
ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( Colors.NewModColor ),
"Newly imported during this session. Will go away when first enabling a mod or when Penumbra is reloaded." );
ImGui.Bullet();
ImGui.SameLine();
ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( ModListCache.HandledConflictModColor ),
ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( Colors.HandledConflictModColor ),
"Enabled and conflicting with another enabled Mod, but on different priorities (i.e. the conflict is solved)." );
ImGui.Bullet();
ImGui.SameLine();
ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( ModListCache.ConflictingModColor ),
ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( Colors.ConflictingModColor ),
"Enabled and conflicting with another enabled Mod on the same priority." );
ImGui.Unindent();
ImGui.BulletText( "Right-click a mod to enter its sort order, which is its name by default." );

View file

@ -235,7 +235,8 @@ public partial class SettingsInterface
}
ImGui.SameLine();
ImGuiComponents.HelpMarker( "Enables other applications, e.g. Anamnesis, to use some Penumbra functions, like requesting redraws." );
ImGuiComponents.HelpMarker(
"Enables other applications, e.g. Anamnesis, to use some Penumbra functions, like requesting redraws." );
}
private static void DrawReloadResourceButton()
@ -342,7 +343,6 @@ public partial class SettingsInterface
}
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem );
DrawRootFolder();
DrawRediscoverButton();