Working on the selector.

This commit is contained in:
Ottermandias 2022-04-07 21:16:04 +02:00
parent 8db54ef4f4
commit 069ae772a5
15 changed files with 558 additions and 358 deletions

@ -1 +1 @@
Subproject commit baee502862a5e8cdfa407f703ce98abad5cc623b
Subproject commit 0c32ec432d38093a402e0dae6dc5c62a883b163b

View file

@ -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 );
}

View file

@ -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() )
{

View file

@ -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();
}
}
}

View file

@ -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;

View file

@ -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();

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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<Mod> 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;
}
}
}

View file

@ -1,6 +1,6 @@
using System;
namespace Penumbra.UI;
namespace Penumbra.UI.Classes;
[Flags]
public enum ModFilter

View file

@ -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;
}

View file

@ -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<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

@ -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 );

View file

@ -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." );

View file

@ -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();