mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Working on the selector.
This commit is contained in:
parent
8db54ef4f4
commit
069ae772a5
15 changed files with 558 additions and 358 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit baee502862a5e8cdfa407f703ce98abad5cc623b
|
||||
Subproject commit 0c32ec432d38093a402e0dae6dc5c62a883b163b
|
||||
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
43
Penumbra/UI/Classes/Colors.cs
Normal file
43
Penumbra/UI/Classes/Colors.cs
Normal 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;
|
||||
}
|
||||
277
Penumbra/UI/Classes/ModFileSystemSelector.Filters.cs
Normal file
277
Penumbra/UI/Classes/ModFileSystemSelector.Filters.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
181
Penumbra/UI/Classes/ModFileSystemSelector.cs
Normal file
181
Penumbra/UI/Classes/ModFileSystemSelector.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace Penumbra.UI;
|
||||
namespace Penumbra.UI.Classes;
|
||||
|
||||
[Flags]
|
||||
public enum ModFilter
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
@ -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 );
|
||||
|
|
|
|||
|
|
@ -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." );
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue