From 069ae772a5ae99efbf30c4ea17ac2e582cff59e2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 7 Apr 2022 21:16:04 +0200 Subject: [PATCH] Working on the selector. --- OtterGui | 2 +- Penumbra/Collections/ModCollection.Cache.cs | 3 +- .../Collections/ModCollection.Inheritance.cs | 3 +- Penumbra/Configuration.cs | 25 +- Penumbra/Mods/ModManager.cs | 1 - Penumbra/Mods/ModSettings.cs | 2 + Penumbra/UI/Classes/Colors.cs | 43 +++ .../Classes/ModFileSystemSelector.Filters.cs | 277 +++++++++++++++ Penumbra/UI/Classes/ModFileSystemSelector.cs | 181 ++++++++++ .../TabInstalled => Classes}/ModFilter.cs | 2 +- Penumbra/UI/Colors.cs | 10 - Penumbra/UI/MenuTabs/ModFileSystemSelector.cs | 330 ------------------ .../UI/MenuTabs/TabInstalled/ModListCache.cs | 9 +- .../TabInstalled/TabInstalledSelector.cs | 9 +- Penumbra/UI/MenuTabs/TabSettings.cs | 19 +- 15 files changed, 558 insertions(+), 358 deletions(-) create mode 100644 Penumbra/UI/Classes/Colors.cs create mode 100644 Penumbra/UI/Classes/ModFileSystemSelector.Filters.cs create mode 100644 Penumbra/UI/Classes/ModFileSystemSelector.cs rename Penumbra/UI/{MenuTabs/TabInstalled => Classes}/ModFilter.cs (98%) delete mode 100644 Penumbra/UI/Colors.cs delete mode 100644 Penumbra/UI/MenuTabs/ModFileSystemSelector.cs diff --git a/OtterGui b/OtterGui index baee5028..0c32ec43 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit baee502862a5e8cdfa407f703ce98abad5cc623b +Subproject commit 0c32ec432d38093a402e0dae6dc5c62a883b163b diff --git a/Penumbra/Collections/ModCollection.Cache.cs b/Penumbra/Collections/ModCollection.Cache.cs index 226f7b05..3e700cb5 100644 --- a/Penumbra/Collections/ModCollection.Cache.cs +++ b/Penumbra/Collections/ModCollection.Cache.cs @@ -231,7 +231,8 @@ public partial class ModCollection return; } - var hasMeta = Penumbra.ModManager[ modIdx ].Resources.MetaManipulations.Count > 0; + var hasMeta = type is ModSettingChange.MultiEnableState or ModSettingChange.MultiInheritance + || Penumbra.ModManager[ modIdx ].Resources.MetaManipulations.Count > 0; _collection.CalculateEffectiveFileList( hasMeta, Penumbra.CollectionManager.Default == _collection ); } diff --git a/Penumbra/Collections/ModCollection.Inheritance.cs b/Penumbra/Collections/ModCollection.Inheritance.cs index 390c9c87..e333e6e0 100644 --- a/Penumbra/Collections/ModCollection.Inheritance.cs +++ b/Penumbra/Collections/ModCollection.Inheritance.cs @@ -12,6 +12,7 @@ namespace Penumbra.Collections; public partial class ModCollection { // A change in inheritance usually requires complete recomputation. + // The bool signifies whether the change was in an already inherited collection. public event Action< bool > InheritanceChanged; private readonly List< ModCollection > _inheritance = new(); @@ -25,7 +26,7 @@ public partial class ModCollection { yield return this; - foreach( var collection in _inheritance.SelectMany( c => c._inheritance ) + foreach( var collection in _inheritance.SelectMany( c => c.GetFlattenedInheritance() ) .Where( c => !ReferenceEquals( this, c ) ) .Distinct() ) { diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index 6221d992..63c4b303 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -1,7 +1,11 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Windows.Forms; using Dalamud.Configuration; using Dalamud.Logging; +using Penumbra.UI; +using Penumbra.UI.Classes; namespace Penumbra; @@ -37,17 +41,21 @@ public partial class Configuration : IPluginConfiguration public bool SortFoldersFirst { get; set; } = false; public bool HasReadCharacterCollectionDesc { get; set; } = false; + public Dictionary< ColorId, uint > Colors { get; set; } + = Enum.GetValues< ColorId >().ToDictionary( c => c, c => c.Data().DefaultColor ); + public static Configuration Load() { var iConfiguration = Dalamud.PluginInterface.GetPluginConfig(); var configuration = iConfiguration as Configuration ?? new Configuration(); if( iConfiguration is { Version: CurrentVersion } ) { + configuration.AddColors( false ); return configuration; } MigrateConfiguration.Migrate( configuration ); - configuration.Save(); + configuration.AddColors( true ); return configuration; } @@ -63,4 +71,19 @@ public partial class Configuration : IPluginConfiguration PluginLog.Error( $"Could not save plugin configuration:\n{e}" ); } } + + // Add missing colors to the dictionary if necessary. + private void AddColors( bool forceSave ) + { + var save = false; + foreach( var color in Enum.GetValues< ColorId >() ) + { + save |= Colors.TryAdd( color, color.Data().DefaultColor ); + } + + if( save || forceSave ) + { + Save(); + } + } } \ No newline at end of file diff --git a/Penumbra/Mods/ModManager.cs b/Penumbra/Mods/ModManager.cs index 19458764..fc126dd4 100644 --- a/Penumbra/Mods/ModManager.cs +++ b/Penumbra/Mods/ModManager.cs @@ -7,7 +7,6 @@ using Dalamud.Logging; using Newtonsoft.Json.Linq; using Penumbra.GameData.ByteString; using Penumbra.Meta; -using Penumbra.Mods; using Penumbra.Util; namespace Penumbra.Mods; diff --git a/Penumbra/Mods/ModSettings.cs b/Penumbra/Mods/ModSettings.cs index aadc0242..d32c6d70 100644 --- a/Penumbra/Mods/ModSettings.cs +++ b/Penumbra/Mods/ModSettings.cs @@ -7,6 +7,8 @@ namespace Penumbra.Mods; // Contains the settings for a given mod. public class ModSettings { + public static readonly ModSettings Empty = new(); + public bool Enabled { get; set; } public int Priority { get; set; } public Dictionary< string, int > Settings { get; set; } = new(); diff --git a/Penumbra/UI/Classes/Colors.cs b/Penumbra/UI/Classes/Colors.cs new file mode 100644 index 00000000..9c95a9bc --- /dev/null +++ b/Penumbra/UI/Classes/Colors.cs @@ -0,0 +1,43 @@ +using System; + +namespace Penumbra.UI.Classes; + +public enum ColorId +{ + EnabledMod, + DisabledMod, + UndefinedMod, + InheritedMod, + InheritedDisabledMod, + NewMod, + ConflictingMod, + HandledConflictMod, + FolderExpanded, + FolderCollapsed, + FolderLine, +} + +public static class Colors +{ + public static (uint DefaultColor, string Name, string Description) Data( this ColorId color ) + => color switch + { + // @formatter:off + ColorId.EnabledMod => ( 0xFFFFFFFF, "Enabled Mod", "A mod that is enabled by the currently selected collection." ), + ColorId.DisabledMod => ( 0xFF686880, "Disabled Mod", "A mod that is disabled by the currently selected collection." ), + ColorId.UndefinedMod => ( 0xFF808080, "Mod With No Settings", "A mod that is not configured in the currently selected collection or any of the collections it inherits from, and thus implicitly disabled." ), + ColorId.InheritedMod => ( 0xFFD0FFFF, "Mod Enabled By Inheritance", "A mod that is not configured in the currently selected collection, but enabled in a collection it inherits from." ), + ColorId.InheritedDisabledMod => ( 0xFF688080, "Mod Disabled By Inheritance", "A mod that is not configured in the currently selected collection, but disabled in a collection it inherits from."), + ColorId.NewMod => ( 0xFF66DD66, "New Mod", "A mod that was newly imported or created during this session and has not been enabled yet." ), + ColorId.ConflictingMod => ( 0xFFAAAAFF, "Mod With Unresolved Conflicts", "An enabled mod that has conflicts with another enabled mod on the same priority level." ), + ColorId.HandledConflictMod => ( 0xFFD0FFD0, "Mod With Resolved Conflicts", "An enabled mod that has conflicts with another enabled mod on a different priority level." ), + ColorId.FolderExpanded => ( 0xFFFFF0C0, "Expanded Mod Folder", "A mod folder that is currently expanded." ), + ColorId.FolderCollapsed => ( 0xFFFFF0C0, "Collapsed Mod Folder", "A mod folder that is currently collapsed." ), + ColorId.FolderLine => ( 0xFFFFF0C0, "Expanded Mod Folder Line", "The line signifying which descendants belong to an expanded mod folder." ), + _ => throw new ArgumentOutOfRangeException( nameof( color ), color, null ), + // @formatter:on + }; + + public static uint Value( this ColorId color ) + => Penumbra.Config.Colors.TryGetValue( color, out var value ) ? value : color.Data().DefaultColor; +} \ No newline at end of file diff --git a/Penumbra/UI/Classes/ModFileSystemSelector.Filters.cs b/Penumbra/UI/Classes/ModFileSystemSelector.Filters.cs new file mode 100644 index 00000000..13c797d2 --- /dev/null +++ b/Penumbra/UI/Classes/ModFileSystemSelector.Filters.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Runtime.InteropServices; +using ImGuiNET; +using OtterGui; +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 uint Color; + } + + private const StringComparison IgnoreCase = StringComparison.InvariantCultureIgnoreCase; + private readonly IReadOnlySet< Mod > _newMods = new HashSet< Mod >(); + 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 n:[string] to filter only for mod names and no paths.\n" + + "Enter c:[string] to filter for mods changing specific items.\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 ), + _ => ( 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 ModFileSystemA.Folder f ) + { + state = default; + return ModFilterExtensions.UnfilteredStateMods != _stateFilter + || FilterValue.Length > 0 && !f.FullName().Contains( FilterValue, IgnoreCase ); + } + + return ApplyFiltersAndState( ( ModFileSystemA.Leaf )path, out state ); + } + + // Apply the string filters. + private bool ApplyStringFilters( ModFileSystemA.Leaf leaf, Mod mod ) + { + return _filterType switch + { + -1 => false, + 0 => !( leaf.FullName().Contains( _modFilter.Lower, IgnoreCase ) || mod.Meta.Name.Contains( _modFilter ) ), + 1 => !mod.Meta.Name.Contains( _modFilter ), + 2 => !mod.Meta.Author.Contains( _modFilter ), + 3 => !mod.LowerChangedItemsString.Contains( _modFilter ), + _ => false, // Should never happen + }; + } + + // Only get the text color for a mod if no filters are set. + private uint GetTextColor( Mod mod, ModSettings? settings, ModCollection collection ) + { + if( _newMods.Contains( mod ) ) + { + return ColorId.NewMod.Value(); + } + + if( settings == null ) + { + return ColorId.UndefinedMod.Value(); + } + + if( !settings.Enabled ) + { + return collection != Penumbra.CollectionManager.Current ? ColorId.InheritedDisabledMod.Value() : ColorId.DisabledMod.Value(); + } + + var conflicts = Penumbra.CollectionManager.Current.ModConflicts( mod.Index ).ToList(); + if( conflicts.Count == 0 ) + { + return collection != Penumbra.CollectionManager.Current ? ColorId.InheritedMod.Value() : ColorId.EnabledMod.Value(); + } + + return conflicts.Any( c => !c.Solved ) + ? ColorId.ConflictingMod.Value() + : ColorId.HandledConflictMod.Value(); + } + + private bool CheckStateFilters( Mod mod, ModSettings? settings, ModCollection collection, ref ModState state ) + { + var isNew = _newMods.Contains( mod ); + // Handle mod details. + 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.NotNew, ModFilter.IsNew ) ) + { + return true; + } + + // Handle Inheritance + if( collection == Penumbra.CollectionManager.Current ) + { + if( !_stateFilter.HasFlag( ModFilter.Uninherited ) ) + { + return true; + } + } + else + { + state.Color = ColorId.InheritedMod.Value(); + if( !_stateFilter.HasFlag( ModFilter.Inherited ) ) + { + return true; + } + } + + // Handle settings. + if( settings == null ) + { + state.Color = ColorId.UndefinedMod.Value(); + 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.Value() : ColorId.InheritedDisabledMod.Value(); + 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.ModConflicts( mod.Index ).ToList(); + if( conflicts.Count > 0 ) + { + if( conflicts.Any( c => !c.Solved ) ) + { + if( !_stateFilter.HasFlag( ModFilter.UnsolvedConflict ) ) + { + return true; + } + + state.Color = ColorId.ConflictingMod.Value(); + } + else + { + if( !_stateFilter.HasFlag( ModFilter.SolvedConflict ) ) + { + return true; + } + + state.Color = ColorId.HandledConflictMod.Value(); + } + } + else if( !_stateFilter.HasFlag( ModFilter.NoConflict ) ) + { + return true; + } + } + + // isNew color takes precedence before other colors. + if( isNew ) + { + state.Color = ColorId.NewMod.Value(); + } + + return false; + } + + // Combined wrapper for handling all filters and setting state. + private bool ApplyFiltersAndState( ModFileSystemA.Leaf leaf, out ModState state ) + { + state = new ModState { Color = ColorId.EnabledMod.Value() }; + 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; + } + + // 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 ); + ImGui.SetCursorPos( comboPos ); + using var combo = ImRaii.Combo( "##filterCombo", string.Empty, + ImGuiComboFlags.NoPreview | ImGuiComboFlags.PopupAlignLeft | ImGuiComboFlags.HeightLargest ); + + if( combo ) + { + var flags = ( int )_stateFilter; + foreach( ModFilter flag in Enum.GetValues( typeof( ModFilter ) ) ) + { + if( ImGui.CheckboxFlags( flag.ToName(), ref flags, ( int )flag ) ) + { + _stateFilter = ( ModFilter )flags; + SetFilterDirty(); + } + } + } + + combo.Dispose(); + ImGuiUtil.HoverTooltip( "Filter mods for their activation status." ); + ImGui.SetCursorPos( pos ); + return remainingWidth; + } +} \ No newline at end of file diff --git a/Penumbra/UI/Classes/ModFileSystemSelector.cs b/Penumbra/UI/Classes/ModFileSystemSelector.cs new file mode 100644 index 00000000..a34b3bd6 --- /dev/null +++ b/Penumbra/UI/Classes/ModFileSystemSelector.cs @@ -0,0 +1,181 @@ +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.Classes; + +public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, ModFileSystemSelector.ModState > +{ + public ModSettings SelectedSettings { get; private set; } = ModSettings.Empty; + public ModCollection SelectedSettingCollection { get; private set; } = ModCollection.Empty; + + public ModFileSystemSelector( ModFileSystemA fileSystem, IReadOnlySet newMods ) + : base( fileSystem ) + { + _newMods = newMods; + SubscribeRightClickFolder( EnableDescendants, 10 ); + SubscribeRightClickFolder( DisableDescendants, 10 ); + SubscribeRightClickFolder( InheritDescendants, 15 ); + SubscribeRightClickFolder( OwnDescendants, 15 ); + AddButton( AddNewModButton, 0 ); + AddButton( DeleteModButton, 1000 ); + SetFilterTooltip(); + + Penumbra.CollectionManager.CollectionChanged += OnCollectionChange; + OnCollectionChange( ModCollection.Type.Current, null, Penumbra.CollectionManager.Current, null ); + } + + public override void Dispose() + { + base.Dispose(); + Penumbra.CollectionManager.Current.ModSettingChanged -= OnSettingChange; + Penumbra.CollectionManager.Current.InheritanceChanged -= OnInheritanceChange; + Penumbra.CollectionManager.CollectionChanged -= OnCollectionChange; + } + + // Customization points. + public override SortMode SortMode + => Penumbra.Config.SortFoldersFirst ? SortMode.FoldersFirst : SortMode.Lexicographical; + + protected override uint ExpandedFolderColor + => ColorId.FolderExpanded.Value(); + + protected override uint CollapsedFolderColor + => ColorId.FolderCollapsed.Value(); + + protected override uint FolderLineColor + => ColorId.FolderLine.Value(); + + 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 ); + } + + + // Add custom context menu items. + private static void EnableDescendants( ModFileSystemA.Folder folder ) + { + if( ImGui.MenuItem( "Enable Descendants" ) ) + { + SetDescendants( folder, true ); + } + } + + private static void DisableDescendants( ModFileSystemA.Folder folder ) + { + if( ImGui.MenuItem( "Disable Descendants" ) ) + { + SetDescendants( folder, 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 ); + } + } + + + // Add custom buttons. + 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 ) ) + { } + } + + + // Helpers. + 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 ) + { + 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, string? optionName, bool inherited ) + { + // TODO: maybe make more efficient + SetFilterDirty(); + if( modIdx == Selected?.Index ) + { + OnSelectionChange( SelectedLeaf, SelectedLeaf, default ); + } + } + + private void OnInheritanceChange( bool _ ) + { + SetFilterDirty(); + OnSelectionChange( SelectedLeaf, SelectedLeaf, default ); + } + + private void OnCollectionChange( ModCollection.Type type, ModCollection? oldCollection, ModCollection? newCollection, string? _ ) + { + if( type != ModCollection.Type.Current || oldCollection == newCollection ) + { + return; + } + + if( oldCollection != null ) + { + oldCollection.ModSettingChanged -= OnSettingChange; + oldCollection.InheritanceChanged -= OnInheritanceChange; + } + + if( newCollection != null ) + { + newCollection.ModSettingChanged += OnSettingChange; + newCollection.InheritanceChanged += OnInheritanceChange; + } + + SetFilterDirty(); + OnSelectionChange( SelectedLeaf, SelectedLeaf, default ); + } + + private void OnSelectionChange( ModFileSystemA.Leaf? _1, ModFileSystemA.Leaf? newSelection, in ModState _2 ) + { + if( newSelection == null ) + { + SelectedSettings = ModSettings.Empty; + SelectedSettingCollection = ModCollection.Empty; + } + else + { + ( var settings, SelectedSettingCollection ) = Penumbra.CollectionManager.Current[ newSelection.Value.Index ]; + SelectedSettings = settings ?? ModSettings.Empty; + } + } +} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabInstalled/ModFilter.cs b/Penumbra/UI/Classes/ModFilter.cs similarity index 98% rename from Penumbra/UI/MenuTabs/TabInstalled/ModFilter.cs rename to Penumbra/UI/Classes/ModFilter.cs index a5a0b6d8..8812a203 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/ModFilter.cs +++ b/Penumbra/UI/Classes/ModFilter.cs @@ -1,6 +1,6 @@ using System; -namespace Penumbra.UI; +namespace Penumbra.UI.Classes; [Flags] public enum ModFilter diff --git a/Penumbra/UI/Colors.cs b/Penumbra/UI/Colors.cs deleted file mode 100644 index 013af594..00000000 --- a/Penumbra/UI/Colors.cs +++ /dev/null @@ -1,10 +0,0 @@ -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; -} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/ModFileSystemSelector.cs b/Penumbra/UI/MenuTabs/ModFileSystemSelector.cs deleted file mode 100644 index c80a0c47..00000000 --- a/Penumbra/UI/MenuTabs/ModFileSystemSelector.cs +++ /dev/null @@ -1,330 +0,0 @@ -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(); - 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 ); - } -} \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabInstalled/ModListCache.cs b/Penumbra/UI/MenuTabs/TabInstalled/ModListCache.cs index 1b9b48f9..69956e52 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/ModListCache.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/ModListCache.cs @@ -4,6 +4,7 @@ using System.Linq; using Dalamud.Logging; using OtterGui; using Penumbra.Mods; +using Penumbra.UI.Classes; using Penumbra.Util; namespace Penumbra.UI; @@ -278,7 +279,7 @@ public class ModListCache : IDisposable return ret; } - ret.Item2 = ret.Item2 == 0 ? Colors.DisabledModColor : ret.Item2; + ret.Item2 = ret.Item2 == 0 ? ColorId.DisabledMod.Value() : ret.Item2; } if( mod.Settings.Enabled && !StateFilter.HasFlag( ModFilter.Enabled ) ) @@ -296,7 +297,7 @@ public class ModListCache : IDisposable return ret; } - ret.Item2 = ret.Item2 == 0 ? Colors.ConflictingModColor : ret.Item2; + ret.Item2 = ret.Item2 == 0 ? ColorId.ConflictingMod.Value() : ret.Item2; } else { @@ -305,7 +306,7 @@ public class ModListCache : IDisposable return ret; } - ret.Item2 = ret.Item2 == 0 ? Colors.HandledConflictModColor : ret.Item2; + ret.Item2 = ret.Item2 == 0 ? ColorId.HandledConflictMod.Value() : ret.Item2; } } else if( !StateFilter.HasFlag( ModFilter.NoConflict ) ) @@ -316,7 +317,7 @@ public class ModListCache : IDisposable ret.Item1 = true; if( isNew ) { - ret.Item2 = Colors.NewModColor; + ret.Item2 = ColorId.NewMod.Value(); } SetFolderAndParentsVisible( mod.Data.Order.ParentFolder ); diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs index cc773a28..9b6f7279 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs @@ -11,6 +11,7 @@ using ImGuiNET; using Penumbra.Collections; using Penumbra.Importer; using Penumbra.Mods; +using Penumbra.UI.Classes; using Penumbra.UI.Custom; using Penumbra.Util; @@ -217,18 +218,18 @@ public partial class SettingsInterface ImGui.Text( "Enabled in the current collection." ); ImGui.Bullet(); ImGui.SameLine(); - ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( Colors.DisabledModColor ), "Disabled in the current collection." ); + ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( ColorId.DisabledMod.Value() ), "Disabled in the current collection." ); ImGui.Bullet(); ImGui.SameLine(); - ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( Colors.NewModColor ), + ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( ColorId.NewMod.Value() ), "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( Colors.HandledConflictModColor ), + ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( ColorId.HandledConflictMod.Value() ), "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( Colors.ConflictingModColor ), + ImGui.TextColored( ImGui.ColorConvertU32ToFloat4( ColorId.DisabledMod.Value() ), "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." ); diff --git a/Penumbra/UI/MenuTabs/TabSettings.cs b/Penumbra/UI/MenuTabs/TabSettings.cs index fe36cebb..0589faf6 100644 --- a/Penumbra/UI/MenuTabs/TabSettings.cs +++ b/Penumbra/UI/MenuTabs/TabSettings.cs @@ -3,15 +3,13 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Numerics; -using System.Text.RegularExpressions; using Dalamud.Interface; using Dalamud.Interface.Components; -using Dalamud.Logging; using ImGuiNET; +using OtterGui; using Penumbra.GameData.ByteString; -using Penumbra.Interop; +using Penumbra.UI.Classes; using Penumbra.UI.Custom; -using Penumbra.Util; namespace Penumbra.UI; @@ -360,6 +358,19 @@ public partial class SettingsInterface DrawAdvancedSettings(); } + if( ImGui.CollapsingHeader( "Colors" ) ) + { + foreach( var color in Enum.GetValues< ColorId >() ) + { + var (defaultColor, name, description) = color.Data(); + var currentColor = Penumbra.Config.Colors.TryGetValue( color, out var current ) ? current : defaultColor; + if( ImGuiUtil.ColorPicker( name, description, currentColor, c => Penumbra.Config.Colors[ color ] = c, defaultColor ) ) + { + _configChanged = true; + } + } + } + if( _configChanged ) { _config.Save();