diff --git a/Luna b/Luna
index d81c7881..91a282ca 160000
--- a/Luna
+++ b/Luna
@@ -1 +1 @@
-Subproject commit d81c788133b8b557febbad0bf74baee9588215eb
+Subproject commit 91a282ca7aa716f1774975a7e286c4b603bcd6aa
diff --git a/Penumbra.GameData b/Penumbra.GameData
index a6b170cb..bab75501 160000
--- a/Penumbra.GameData
+++ b/Penumbra.GameData
@@ -1 +1 @@
-Subproject commit a6b170cbd5ef2936466a8c85ba4a4ad495d4f3c9
+Subproject commit bab75501cd499de7d1c28883fe87dde98c772a3c
diff --git a/Penumbra.sln b/Penumbra.sln
index 2acdf570..be81b9d2 100644
--- a/Penumbra.sln
+++ b/Penumbra.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.2.32210.308
+# Visual Studio Version 18
+VisualStudioVersion = 18.3.11312.210 d18.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra", "Penumbra\Penumbra.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}"
EndProject
@@ -17,8 +17,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "Penumbra.GameData\Penumbra.GameData.csproj", "{EE551E87-FDB3-4612-B500-DC870C07C605}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "OtterGui\OtterGui.csproj", "{87750518-1A20-40B4-9FC1-22F906EFB290}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.Api", "Penumbra.Api\Penumbra.Api.csproj", "{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra.String\Penumbra.String.csproj", "{5549BAFD-6357-4B1A-800C-75AC36E5B76D}"
@@ -73,10 +71,6 @@ Global
{EE551E87-FDB3-4612-B500-DC870C07C605}.Debug|x64.Build.0 = Debug|x64
{EE551E87-FDB3-4612-B500-DC870C07C605}.Release|x64.ActiveCfg = Release|x64
{EE551E87-FDB3-4612-B500-DC870C07C605}.Release|x64.Build.0 = Release|x64
- {87750518-1A20-40B4-9FC1-22F906EFB290}.Debug|x64.ActiveCfg = Debug|x64
- {87750518-1A20-40B4-9FC1-22F906EFB290}.Debug|x64.Build.0 = Debug|x64
- {87750518-1A20-40B4-9FC1-22F906EFB290}.Release|x64.ActiveCfg = Release|x64
- {87750518-1A20-40B4-9FC1-22F906EFB290}.Release|x64.Build.0 = Release|x64
{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Debug|x64.ActiveCfg = Debug|x64
{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Debug|x64.Build.0 = Debug|x64
{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Release|x64.ActiveCfg = Release|x64
diff --git a/Penumbra/Api/Api/ModsApi.cs b/Penumbra/Api/Api/ModsApi.cs
index 348dc250..5d4e2392 100644
--- a/Penumbra/Api/Api/ModsApi.cs
+++ b/Penumbra/Api/Api/ModsApi.cs
@@ -122,13 +122,12 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable
public (PenumbraApiEc, string, bool, bool) GetModPath(string modDirectory, string modName)
{
- if (!_modManager.TryGetMod(modDirectory, modName, out var mod)
- || !_modFileSystem.TryGetValue(mod, out var leaf))
+ if (!_modManager.TryGetMod(modDirectory, modName, out var mod) || mod.Node is not { } node)
return (PenumbraApiEc.ModMissing, string.Empty, false, false);
- var fullPath = leaf.FullName();
- var isDefault = ModFileSystem.ModHasDefaultPath(mod, fullPath);
- var isNameDefault = isDefault || ModFileSystem.ModHasDefaultPath(mod, leaf.Name);
+ var fullPath = node.FullPath;
+ var isDefault = mod.Path.IsDefault;
+ var isNameDefault = mod.Path.SortName is null;
return (PenumbraApiEc.Success, fullPath, !isDefault, !isNameDefault);
}
@@ -137,13 +136,12 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable
if (newPath.Length == 0)
return PenumbraApiEc.InvalidArgument;
- if (!_modManager.TryGetMod(modDirectory, modName, out var mod)
- || !_modFileSystem.TryGetValue(mod, out var leaf))
+ if (!_modManager.TryGetMod(modDirectory, modName, out var mod) || mod.Node is not { } node)
return PenumbraApiEc.ModMissing;
try
{
- _modFileSystem.RenameAndMove(leaf, newPath);
+ _modFileSystem.RenameAndMove(node, newPath);
return PenumbraApiEc.Success;
}
catch
diff --git a/Penumbra/Communication/CollectionChange.cs b/Penumbra/Communication/CollectionChange.cs
index e6aa8ac2..8ed978f6 100644
--- a/Penumbra/Communication/CollectionChange.cs
+++ b/Penumbra/Communication/CollectionChange.cs
@@ -46,7 +46,7 @@ public sealed class CollectionChange(Logger log)
IndividualAssignmentUi = 0,
///
- ModFileSystemSelector = 0,
+ ModFileSystemCache = 0,
///
ModSelection = 10,
diff --git a/Penumbra/Communication/CollectionInheritanceChanged.cs b/Penumbra/Communication/CollectionInheritanceChanged.cs
index c917875d..5de5414f 100644
--- a/Penumbra/Communication/CollectionInheritanceChanged.cs
+++ b/Penumbra/Communication/CollectionInheritanceChanged.cs
@@ -22,7 +22,7 @@ public sealed class CollectionInheritanceChanged(Logger log)
ItemSwapTab = 0,
///
- ModFileSystemSelector = 0,
+ ModFileSystemCache = 0,
///
ModSelection = 10,
diff --git a/Penumbra/Communication/ModDataChanged.cs b/Penumbra/Communication/ModDataChanged.cs
index 325247f5..af870870 100644
--- a/Penumbra/Communication/ModDataChanged.cs
+++ b/Penumbra/Communication/ModDataChanged.cs
@@ -10,7 +10,7 @@ public sealed class ModDataChanged(Logger log) : EventBase
- ModFileSystemSelector = -10,
+ ModFileSystemCache = -10,
///
ModCacheManager = 0,
diff --git a/Penumbra/Communication/ModSettingChanged.cs b/Penumbra/Communication/ModSettingChanged.cs
index a41658a9..7cf13db5 100644
--- a/Penumbra/Communication/ModSettingChanged.cs
+++ b/Penumbra/Communication/ModSettingChanged.cs
@@ -27,7 +27,7 @@ public sealed class ModSettingChanged(Logger log)
ItemSwapTab = 0,
///
- ModFileSystemSelector = 0,
+ ModFileSystemCache = 0,
///
ModSelection = 10,
diff --git a/Penumbra/Configuration.cs b/Penumbra/Config/Configuration.cs
similarity index 95%
rename from Penumbra/Configuration.cs
rename to Penumbra/Config/Configuration.cs
index 828d03bb..16dde140 100644
--- a/Penumbra/Configuration.cs
+++ b/Penumbra/Config/Configuration.cs
@@ -1,6 +1,5 @@
using Dalamud.Configuration;
using Dalamud.Interface.ImGuiNotification;
-using ImSharp;
using Luna;
using Newtonsoft.Json;
using Penumbra.Import.Structs;
@@ -14,16 +13,6 @@ using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
namespace Penumbra;
-public record PcpSettings
-{
- public bool CreateCollection { get; set; } = true;
- public bool AssignCollection { get; set; } = true;
- public bool AllowIpc { get; set; } = true;
- public bool DisableHandling { get; set; } = false;
- public string FolderName { get; set; } = "PCP";
- public string PcpExtension { get; set; } = ".pcp";
-}
-
[Serializable]
public class Configuration : IPluginConfiguration, ISavable, IService
{
@@ -33,6 +22,9 @@ public class Configuration : IPluginConfiguration, ISavable, IService
[JsonIgnore]
public readonly EphemeralConfig Ephemeral;
+ [JsonIgnore]
+ public readonly UiConfig Ui;
+
public int Version { get; set; } = Constants.CurrentVersion;
public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New;
@@ -120,7 +112,7 @@ public class Configuration : IPluginConfiguration, ISavable, IService
public bool EditRawTileTransforms { get; set; } = false;
public bool WholePairSelectorAlwaysHighlights { get; set; } = false;
- public bool HdrRenderTargets { get; set; } = true;
+ public bool HdrRenderTargets { get; set; } = true;
public Dictionary Colors { get; set; }
= Enum.GetValues().ToDictionary(c => c, c => c.Data().DefaultColor);
@@ -129,10 +121,12 @@ public class Configuration : IPluginConfiguration, ISavable, IService
/// Load the current configuration.
/// Includes adding new colors and migrating from old versions.
///
- public Configuration(CharacterUtility utility, ConfigMigrationService migrator, SaveService saveService, EphemeralConfig ephemeral)
+ public Configuration(CharacterUtility utility, ConfigMigrationService migrator, SaveService saveService, EphemeralConfig ephemeral,
+ UiConfig ui)
{
_saveService = saveService;
Ephemeral = ephemeral;
+ Ui = ui;
Load(utility, migrator);
}
diff --git a/Penumbra/EphemeralConfig.cs b/Penumbra/Config/EphemeralConfig.cs
similarity index 77%
rename from Penumbra/EphemeralConfig.cs
rename to Penumbra/Config/EphemeralConfig.cs
index 4cd2516e..2eb8d038 100644
--- a/Penumbra/EphemeralConfig.cs
+++ b/Penumbra/Config/EphemeralConfig.cs
@@ -1,9 +1,7 @@
using Dalamud.Interface.ImGuiNotification;
using Luna;
using Newtonsoft.Json;
-using Penumbra.Communication;
using Penumbra.Enums;
-using Penumbra.Mods.Manager;
using Penumbra.Services;
using Penumbra.UI;
using Penumbra.UI.Classes;
@@ -14,18 +12,11 @@ using TabType = Penumbra.Api.Enums.TabType;
namespace Penumbra;
-public class EphemeralConfig : ISavable, IDisposable, IService
+public class EphemeralConfig : ISavable, IService
{
[JsonIgnore]
private readonly SaveService _saveService;
- [JsonIgnore]
- private readonly ModPathChanged _modPathChanged;
-
- public float CurrentModSelectorWidth { get; set; } = 200f;
- public float ModSelectorMinimumScale { get; set; } = 0.1f;
- public float ModSelectorMaximumScale { get; set; } = 0.5f;
-
public int Version { get; set; } = Configuration.Constants.CurrentVersion;
public int LastSeenVersion { get; set; } = PenumbraChangelog.LastChangelogVersion;
public bool DebugSeparateWindow { get; set; } = false;
@@ -41,7 +32,6 @@ public class EphemeralConfig : ISavable, IDisposable, IService
public TabType SelectedTab { get; set; } = TabType.Settings;
public ChangedItemIconFlag ChangedItemFilter { get; set; } = ChangedItemFlagExtensions.DefaultFlags;
public bool FixMainWindow { get; set; } = false;
- public string LastModPath { get; set; } = string.Empty;
public HashSet AdvancedEditingOpenForModPaths { get; set; } = [];
public bool ForceRedrawOnFileChange { get; set; } = false;
public bool IncognitoMode { get; set; } = false;
@@ -50,17 +40,12 @@ public class EphemeralConfig : ISavable, IDisposable, IService
/// Load the current configuration.
/// Includes adding new colors and migrating from old versions.
///
- public EphemeralConfig(SaveService saveService, ModPathChanged modPathChanged)
+ public EphemeralConfig(SaveService saveService)
{
_saveService = saveService;
- _modPathChanged = modPathChanged;
Load();
- _modPathChanged.Subscribe(OnModPathChanged, ModPathChanged.Priority.EphemeralConfig);
}
- public void Dispose()
- => _modPathChanged.Unsubscribe(OnModPathChanged);
-
private void Load()
{
static void HandleDeserializationError(object? sender, ErrorEventArgs errorArgs)
@@ -104,15 +89,4 @@ public class EphemeralConfig : ISavable, IDisposable, IService
var serializer = new JsonSerializer { Formatting = Formatting.Indented };
serializer.Serialize(jWriter, this);
}
-
- /// Overwrite the last saved mod path if it changes.
- private void OnModPathChanged(in ModPathChanged.Arguments arguments)
- {
- if (arguments.Type is not ModPathChangeType.Moved
- || !string.Equals(arguments.OldDirectory?.Name, LastModPath, StringComparison.OrdinalIgnoreCase))
- return;
-
- LastModPath = arguments.Mod.Identifier;
- Save();
- }
}
diff --git a/Penumbra/Config/PcpSettings.cs b/Penumbra/Config/PcpSettings.cs
new file mode 100644
index 00000000..8b4ee9d9
--- /dev/null
+++ b/Penumbra/Config/PcpSettings.cs
@@ -0,0 +1,11 @@
+namespace Penumbra;
+
+public record PcpSettings
+{
+ public bool CreateCollection { get; set; } = true;
+ public bool AssignCollection { get; set; } = true;
+ public bool AllowIpc { get; set; } = true;
+ public bool DisableHandling { get; set; } = false;
+ public string FolderName { get; set; } = "PCP";
+ public string PcpExtension { get; set; } = ".pcp";
+}
diff --git a/Penumbra/Config/UiConfig.cs b/Penumbra/Config/UiConfig.cs
new file mode 100644
index 00000000..7233b1ab
--- /dev/null
+++ b/Penumbra/Config/UiConfig.cs
@@ -0,0 +1,108 @@
+using Dalamud.Interface.ImGuiNotification;
+using Luna;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Penumbra.Services;
+
+namespace Penumbra;
+
+public class UiConfig : ISavable, IService
+{
+ public const int CurrentVersion = 1;
+
+ [JsonIgnore]
+ private readonly SaveService _saveService;
+
+ public UiConfig(SaveService saveService)
+ {
+ _saveService = saveService;
+ Load();
+ }
+
+ private TwoPanelWidth _collectionsTabScale = new(0.25f, ScalingMode.Percentage);
+
+ public TwoPanelWidth CollectionTabScale
+ {
+ get => _collectionsTabScale;
+ set
+ {
+ if (value == _collectionsTabScale)
+ return;
+
+ _collectionsTabScale = value;
+ Save();
+ }
+ }
+
+ private TwoPanelWidth _modTabScale = new(0.3f, ScalingMode.Percentage);
+
+ public TwoPanelWidth ModTabScale
+ {
+ get => _modTabScale;
+ set
+ {
+ if (value == _modTabScale)
+ return;
+
+ _modTabScale = value;
+ Save();
+ }
+ }
+
+ public string ToFilePath(FilenameService fileNames)
+ => fileNames.UiConfigFile;
+
+ public void Save()
+ => _saveService.DelaySave(this);
+
+ public void Save(StreamWriter writer)
+ {
+ using var j = new JsonTextWriter(writer);
+ j.Formatting = Formatting.Indented;
+ j.WriteStartObject();
+ j.WritePropertyName("Version");
+ j.WriteValue(CurrentVersion);
+ j.WritePropertyName("CollectionsTab");
+ j.WriteStartObject();
+ j.WritePropertyName("Mode");
+ j.WriteValue(CollectionTabScale.Mode.ToString());
+ j.WritePropertyName("Width");
+ j.WriteValue(CollectionTabScale.Width);
+ j.WriteEndObject();
+ j.WritePropertyName("ModsTab");
+ j.WriteStartObject();
+ j.WritePropertyName("Mode");
+ j.WriteValue(ModTabScale.Mode.ToString());
+ j.WritePropertyName("Width");
+ j.WriteValue(ModTabScale.Width);
+ j.WriteEndObject();
+ j.WriteEndObject();
+ }
+
+ private void Load()
+ {
+ if (!File.Exists(_saveService.FileNames.UiConfigFile))
+ return;
+
+ try
+ {
+ var text = File.ReadAllText(_saveService.FileNames.UiConfigFile);
+ var jObj = JObject.Parse(text);
+ if (jObj["Version"]?.Value() is not CurrentVersion)
+ throw new Exception("Unsupported version.");
+
+ if (jObj["CollectionsTab"] is JObject collections)
+ _collectionsTabScale = new TwoPanelWidth(collections["Width"].ValueOr(float.NaN),
+ collections["Mode"].TextEnum(ScalingMode.Percentage));
+
+ if (jObj["ModsTab"] is JObject mods)
+ _modTabScale = new TwoPanelWidth(mods["Width"].ValueOr(float.NaN), mods["Mode"].TextEnum(ScalingMode.Percentage));
+ }
+ catch (Exception ex)
+ {
+ Penumbra.Messager.NotificationMessage(ex,
+ "Error reading UI Configuration, reverting to default.",
+ "Error reading UI Configuration", NotificationType.Error);
+ }
+ }
+}
diff --git a/Penumbra/Import/Textures/TexFileParser.cs b/Penumbra/Import/Textures/TexFileParser.cs
index 04bbf5d8..ca061472 100644
--- a/Penumbra/Import/Textures/TexFileParser.cs
+++ b/Penumbra/Import/Textures/TexFileParser.cs
@@ -44,8 +44,8 @@ public static class TexFileParser
if (offset == 0)
return i;
- var Size = width * height * bits / 8;
- if (offset + Size > data.Length)
+ var size = width * height * bits / 8;
+ if (offset + size > data.Length)
return i;
var diff = offset - lastOffset;
@@ -55,7 +55,7 @@ public static class TexFileParser
width = Math.Max(width / 2, minSize);
height = Math.Max(height / 2, minSize);
lastOffset = offset;
- lastSize = Size;
+ lastSize = size;
}
return 13;
diff --git a/Penumbra/Interop/GameState.cs b/Penumbra/Interop/GameState.cs
index 409dc363..4b766fef 100644
--- a/Penumbra/Interop/GameState.cs
+++ b/Penumbra/Interop/GameState.cs
@@ -74,8 +74,9 @@ public class GameState : Luna.IService
#region Subfiles
- public readonly ThreadLocal MtrlData = new(() => ResolveData.Invalid);
- public readonly ThreadLocal AvfxData = new(() => ResolveData.Invalid);
+ public readonly ThreadLocal MtrlData = new(() => ResolveData.Invalid);
+ public readonly ThreadLocal AvfxData = new(() => ResolveData.Invalid);
+ public ResolveData ApricotDocumentAvfx = ResolveData.Invalid;
public readonly ConcurrentDictionary SubFileCollection = new();
diff --git a/Penumbra/Interop/Hooks/Animation/ApricotListenerSoundPlay.cs b/Penumbra/Interop/Hooks/Animation/ApricotListenerSoundPlay.cs
index de76b6d9..0e909cda 100644
--- a/Penumbra/Interop/Hooks/Animation/ApricotListenerSoundPlay.cs
+++ b/Penumbra/Interop/Hooks/Animation/ApricotListenerSoundPlay.cs
@@ -16,13 +16,15 @@ public sealed unsafe class ApricotListenerSoundPlayCaller : FastHook("Apricot Listener Sound Play Caller", Sigs.ApricotListenerSoundPlayCaller, Detour,
!HookOverrides.Instance.Animation.ApricotListenerSoundPlayCaller);
}
@@ -52,8 +54,10 @@ public sealed unsafe class ApricotListenerSoundPlayCaller : FastHook**)apricotIInstanceListenner)[VolatileOffsets.ApricotListenerSoundPlayCaller.CasterVFunc](apricotIInstanceListenner);
+ var newData = ResolveData.Invalid;
+ var gameObject =
+ (*(delegate* unmanaged**)apricotIInstanceListenner)[VolatileOffsets.ApricotListenerSoundPlayCaller.CasterVFunc](
+ apricotIInstanceListenner);
if (gameObject != null)
{
newData = _collectionResolver.IdentifyCollection(gameObject, true);
@@ -64,8 +68,10 @@ public sealed unsafe class ApricotListenerSoundPlayCaller : FastHook
+{
+ public CreateApricotDocumentHook(CollectionManager collections, GameState state, HookManager hooks)
+ : this(collections, state)
+ {
+ Task = hooks.CreateHook("Create Apricot Document", Sigs.CreateApricotDocument, Detour,
+ !HookOverrides.Instance.Animation.CreateApricotDocument);
+ }
+
+ public delegate nint Delegate(nint singleton, nint vfxOwner, byte* avfxPath, byte* avfxData, uint avfxSize, ApricotResourceHandle* resource,
+ nint document);
+
+ private nint Detour(nint singleton, nint vfxOwner, byte* avfxPath, byte* avfxData, uint avfxSize, ApricotResourceHandle* resource,
+ nint document)
+ {
+ var fullPath = new CiByteString(avfxPath);
+ state.ApricotDocumentAvfx = state.AvfxData.Value.Valid
+ ? state.AvfxData.Value
+ : new ResolveData(avfxPath is not null
+ && PathDataHandler.Split(fullPath.Span, out var path, out var additionalData)
+ && PathDataHandler.Read(additionalData, out var data)
+ ? collections.Storage.ByLocalId(data.Collection)
+ : collections.Active.Default, nint.Zero);
+ var ret = Task!.Result.Original(singleton, vfxOwner, avfxPath, avfxData, avfxSize, resource, document);
+ Penumbra.Log.Excessive(
+ $"[CreateApricotDocument] Results with 0x{singleton:X}, 0x{vfxOwner:X}, 0x{(nint)avfxPath:X} ({fullPath}, {state.ApricotDocumentAvfx.ModCollection}) 0x{(nint)avfxData:X} ({avfxSize} bytes) 0x{(long)resource:X} 0x{document:X} -> 0x{ret:X}.");
+ return ret;
+ }
+}
+#endif
diff --git a/Penumbra/Interop/Hooks/DebugHook.cs b/Penumbra/Interop/Hooks/DebugHook.cs
index cf3264c6..c4840591 100644
--- a/Penumbra/Interop/Hooks/DebugHook.cs
+++ b/Penumbra/Interop/Hooks/DebugHook.cs
@@ -1,15 +1,20 @@
using Dalamud.Hooking;
using Luna;
-using Penumbra.Interop.Structs;
+using Penumbra.Collections;
+using Penumbra.Collections.Manager;
+using Penumbra.Interop.PathResolving;
+using Penumbra.String;
namespace Penumbra.Interop.Hooks;
#if DEBUG
-public sealed unsafe class DebugHook : Luna.IHookService
+
+public sealed unsafe class DebugHook(CollectionStorage collections) : Luna.IHookService
{
public const string Signature = "";
- public DebugHook(HookManager hooks)
+ public DebugHook(CollectionStorage collections, HookManager hooks)
+ : this(collections)
{
if (Signature.Length > 0)
_task = hooks.CreateHook("Debug Hook", Signature, Detour, true);
@@ -32,12 +37,19 @@ public sealed unsafe class DebugHook : Luna.IHookService
public bool Finished
=> _task?.IsCompletedSuccessfully ?? true;
- private delegate nint Delegate(ResourceHandle* a, int b, int c);
+ private delegate nint Delegate(nint a, nint b, nint c, nint d, uint e, nint f, nint g);
- private nint Detour(ResourceHandle* a, int b, int c)
+ private nint Detour(nint a, nint b, nint c, nint d, uint e, nint f, nint g)
{
- var ret = _task!.Result.Original(a, b, c);
- Penumbra.Log.Information($"[Debug Hook] Results with 0x{(nint)a:X}, {b}, {c} -> 0x{ret:X}.");
+ var path = new CiByteString((byte*)c);
+ var collection = PathDataHandler.Split(path.Span, out _, out var additionalData) && PathDataHandler.Read(additionalData, out var data)
+ ? collections.ByLocalId(data.Collection)
+ : ModCollection.Empty;
+
+ var ret = _task!.Result.Original(a, b, c, d, e, f, g);
+
+
+ Penumbra.Log.Information($"[Debug Hook] Results with 0x{a:X}, 0x{b:X}, 0x{c:X} ({path}, {collection}) 0x{d:X} {e} 0x{f:X} {g} -> 0x{ret:X}.");
return ret;
}
}
diff --git a/Penumbra/Interop/Hooks/HookSettings.cs b/Penumbra/Interop/Hooks/HookSettings.cs
index bcff25d2..afef00f8 100644
--- a/Penumbra/Interop/Hooks/HookSettings.cs
+++ b/Penumbra/Interop/Hooks/HookSettings.cs
@@ -31,6 +31,7 @@ public class HookOverrides
public struct AnimationHooks
{
public bool ApricotListenerSoundPlayCaller;
+ public bool CreateApricotDocument;
public bool CharacterBaseLoadAnimation;
public bool Dismount;
public bool LoadAreaVfx;
diff --git a/Penumbra/Interop/Processing/AvfxPathPreProcessor.cs b/Penumbra/Interop/Processing/AvfxPathPreProcessor.cs
index 2194354a..1beeecc5 100644
--- a/Penumbra/Interop/Processing/AvfxPathPreProcessor.cs
+++ b/Penumbra/Interop/Processing/AvfxPathPreProcessor.cs
@@ -6,11 +6,14 @@ using Penumbra.String.Classes;
namespace Penumbra.Interop.Processing;
-public sealed class AvfxPathPreProcessor : IPathPreProcessor
+public sealed class AvfxPathPreProcessor(GameState state) : IPathPreProcessor
{
public ResourceType Type
=> ResourceType.Avfx;
public FullPath? PreProcess(ResolveData resolveData, CiByteString path, Utf8GamePath _, bool nonDefault, FullPath? resolved)
- => nonDefault ? PathDataHandler.CreateAvfx(path, resolveData.ModCollection) : resolved;
+ {
+ state.ApricotDocumentAvfx = resolveData;
+ return nonDefault ? PathDataHandler.CreateAvfx(path, resolveData.ModCollection) : resolved;
+ }
}
diff --git a/Penumbra/Mods/Manager/ModFileSystem.cs b/Penumbra/Mods/Manager/ModFileSystem.cs
index 8be970ae..810bc731 100644
--- a/Penumbra/Mods/Manager/ModFileSystem.cs
+++ b/Penumbra/Mods/Manager/ModFileSystem.cs
@@ -1,69 +1,36 @@
using Dalamud.Interface.ImGuiNotification;
-using ImSharp;
using Luna;
-using OtterGui.Filesystem;
-using Penumbra.Api.Enums;
using Penumbra.Communication;
using Penumbra.Services;
-using Penumbra.UI.Classes;
-using Penumbra.UI.ModsTab;
-using Penumbra.UI.ModsTab.Selector;
-using FileSystemChangeType = OtterGui.Filesystem.FileSystemChangeType;
namespace Penumbra.Mods.Manager;
-public sealed class ModTab : TwoPanelLayout, ITab
-{
- public override ReadOnlySpan Label
- => "Mods2"u8;
-
- public ModTab(ModFileSystemDrawer drawer, ModPanel panel, CollectionSelectHeader collectionHeader, RedrawFooter redrawFooter)
- {
- LeftHeader = drawer.Header;
- LeftFooter = drawer.Footer;
- LeftPanel = drawer;
- RightPanel = panel;
- RightHeader = collectionHeader;
- RightFooter = redrawFooter;
- }
-
- public void DrawContent()
- => Draw();
-
- public TabType Identifier
- => TabType.Mods;
-
- protected override void SetSize(Vector2 newSize)
- {
- base.SetSize(newSize);
- ((ModFileSystemDrawer)LeftPanel).Config.Ephemeral.CurrentModSelectorWidth = newSize.X / Im.Style.GlobalScale;
- }
-
- protected override float MinimumWidth
- => ((ModFileSystemDrawer)LeftPanel).Footer.Buttons.Count * Im.Style.FrameHeight;
-
- protected override float MaximumWidth
- => Im.Window.Width - 500 * Im.Style.GlobalScale;
-}
-
-public sealed class ModFileSystem2 : BaseFileSystem, IDisposable, IRequiredService
+public sealed class ModFileSystem : BaseFileSystem, IDisposable, IRequiredService
{
private readonly Configuration _config;
private readonly CommunicatorService _communicator;
private readonly ModFileSystemSaver _saver;
+ private readonly ModSelection _selection;
- public ModFileSystem2(Configuration config, CommunicatorService communicator, SaveService saveService, Logger log, ModStorage modStorage)
+ public ModFileSystem(Configuration config, CommunicatorService communicator, SaveService saveService, Logger log, ModStorage modStorage,
+ ModSelection selection)
: base("ModFileSystem", log, true)
{
_config = config;
_communicator = communicator;
+ _selection = selection;
_saver = new ModFileSystemSaver(log, this, saveService, modStorage);
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModFileSystem);
_communicator.ModDiscoveryFinished.Subscribe(_saver.Load, ModDiscoveryFinished.Priority.ModFileSystem);
_communicator.ModDataChanged.Subscribe(OnModDataChange, ModDataChanged.Priority.ModFileSystem);
_saver.Load();
+ Selection.Changed += OnSelectionChanged;
+ _selection.SelectMod(Selection.Selection?.GetValue());
}
+ private void OnSelectionChanged()
+ => _selection.SelectMod(Selection.Selection?.GetValue());
+
public void Dispose()
{
_communicator.ModPathChanged.Unsubscribe(OnModPathChange);
@@ -113,151 +80,3 @@ public sealed class ModFileSystem2 : BaseFileSystem, IDisposable, IRequiredServi
}
}
}
-
-public sealed class ModFileSystem : FileSystem, IDisposable, ISavable, IService
-{
- private readonly ModManager _modManager;
- private readonly CommunicatorService _communicator;
- private readonly SaveService _saveService;
- private readonly Configuration _config;
-
- // Create a new ModFileSystem from the currently loaded mods and the current sort order file.
- public ModFileSystem(ModManager modManager, CommunicatorService communicator, SaveService saveService, Configuration config)
- {
- _modManager = modManager;
- _communicator = communicator;
- _saveService = saveService;
- _config = config;
- Reload();
- Changed += OnChange;
- _communicator.ModDiscoveryFinished.Subscribe(Reload, ModDiscoveryFinished.Priority.ModFileSystem);
- _communicator.ModDataChanged.Subscribe(OnModDataChange, ModDataChanged.Priority.ModFileSystem);
- _communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModFileSystem);
- }
-
- public void Dispose()
- {
- _communicator.ModPathChanged.Unsubscribe(OnModPathChange);
- _communicator.ModDiscoveryFinished.Unsubscribe(Reload);
- _communicator.ModDataChanged.Unsubscribe(OnModDataChange);
- }
-
- public struct ImportDate : ISortMode
- {
- public ReadOnlySpan Name
- => "Import Date (Older First)"u8;
-
- public ReadOnlySpan Description
- => "In each folder, sort all subfolders lexicographically, then sort all leaves using their import date."u8;
-
- public IEnumerable GetChildren(Folder f)
- => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderBy(l => l.Value.ImportDate));
- }
-
- public struct InverseImportDate : ISortMode
- {
- public ReadOnlySpan Name
- => "Import Date (Newer First)"u8;
-
- public ReadOnlySpan Description
- => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse import date."u8;
-
- public IEnumerable GetChildren(Folder f)
- => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderByDescending(l => l.Value.ImportDate));
- }
-
- // Reload the whole filesystem from currently loaded mods and the current sort order file.
- // Used on construction and on mod rediscoveries.
- private void Reload()
- {
- var jObj = BackupService.GetJObjectForFile(_saveService.FileNames, _saveService.FileNames.OldFilesystemFile);
- if (Load(jObj, _modManager, ModToIdentifier, ModToName))
- _saveService.ImmediateSave(this);
-
- Penumbra.Log.Debug("Reloaded mod filesystem.");
- }
-
- // Save the filesystem on every filesystem change except full reloading.
- private void OnChange(FileSystemChangeType type, IPath _1, IPath? _2, IPath? _3)
- {
- if (type != FileSystemChangeType.Reload)
- _saveService.DelaySave(this);
- }
-
- // Update sort order when defaulted mod names change.
- private void OnModDataChange(in ModDataChanged.Arguments arguments)
- {
- if (!arguments.Type.HasFlag(ModDataChangeType.Name) || arguments.OldName == null || !TryGetValue(arguments.Mod, out var leaf))
- return;
-
- var old = Extensions.FixName(arguments.OldName);
- if (old == leaf.Name || Extensions.IsDuplicateName(leaf.Name, out var baseName, out _) && baseName == old)
- RenameWithDuplicates(leaf, arguments.Mod.Name);
- }
-
- // Update the filesystem if a mod has been added or removed.
- // Save it, if the mod directory has been moved, since this will change the save format.
- private void OnModPathChange(in ModPathChanged.Arguments arguments)
- {
- switch (arguments.Type)
- {
- case ModPathChangeType.Added:
- var parent = Root;
- if (_config.DefaultImportFolder.Length != 0)
- try
- {
- parent = FindOrCreateAllFolders(_config.DefaultImportFolder);
- }
- catch (Exception e)
- {
- Penumbra.Messager.NotificationMessage(e,
- $"Could not move newly imported mod {arguments.Mod.Name} to default import folder {_config.DefaultImportFolder}.",
- NotificationType.Warning);
- }
-
- CreateDuplicateLeaf(parent, arguments.Mod.Name, arguments.Mod);
- break;
- case ModPathChangeType.Deleted:
- if (TryGetValue(arguments.Mod, out var leaf))
- Delete(leaf);
-
- break;
- case ModPathChangeType.Moved: _saveService.DelaySave(this); break;
- case ModPathChangeType.Reloaded:
- // Nothing
- break;
- }
- }
-
- // Used for saving and loading.
- private static string ModToIdentifier(Mod mod)
- => mod.ModPath.Name;
-
- private static string ModToName(Mod mod)
- => Extensions.FixName(mod.Name);
-
- // Return whether a mod has a custom path or is just a numbered default path.
- public static bool ModHasDefaultPath(Mod mod, string fullPath)
- {
- var regex = new Regex($@"^{Regex.Escape(ModToName(mod))}( \(\d+\))?$");
- return regex.IsMatch(fullPath);
- }
-
- private static (string, bool) SaveMod(Mod mod, string fullPath)
- // Only save pairs with non-default paths.
- => ModHasDefaultPath(mod, fullPath)
- ? (string.Empty, false)
- : (ModToIdentifier(mod), true);
-
- public string ToFilePath(FilenameService fileNames)
- => fileNames.OldFilesystemFile;
-
- public void Save(StreamWriter writer)
- => SaveToFile(writer, SaveMod, true);
-
- public string TypeName
- => "Mod File System";
-
- public string LogName(string _)
- => "to file";
-}
diff --git a/Penumbra/Mods/Manager/ModManager.cs b/Penumbra/Mods/Manager/ModManager.cs
index 58e9fe12..6cac3c79 100644
--- a/Penumbra/Mods/Manager/ModManager.cs
+++ b/Penumbra/Mods/Manager/ModManager.cs
@@ -332,9 +332,16 @@ public sealed class ModManager : ModStorage, IDisposable, Luna.IService
var queue = new ConcurrentQueue();
Parallel.ForEach(BasePath.EnumerateDirectories(), options, dir =>
{
- var mod = Creator.LoadMod(dir, false, false);
- if (mod != null)
- queue.Enqueue(mod);
+ try
+ {
+ var mod = Creator.LoadMod(dir, false, false);
+ if (mod != null)
+ queue.Enqueue(mod);
+ }
+ catch (Exception ex)
+ {
+ Penumbra.Log.Warning($"Failed to load mod at {dir}:\n{ex}");
+ }
});
foreach (var mod in queue)
diff --git a/Penumbra/Mods/ModSelection.cs b/Penumbra/Mods/ModSelection.cs
index 294d8244..9c535731 100644
--- a/Penumbra/Mods/ModSelection.cs
+++ b/Penumbra/Mods/ModSelection.cs
@@ -18,7 +18,6 @@ namespace Penumbra.Mods;
public class ModSelection : EventBase
{
private readonly ActiveCollections _collections;
- private readonly EphemeralConfig _config;
private readonly CommunicatorService _communicator;
public ModSelection(Logger log, CommunicatorService communicator, ModManager mods, ActiveCollections collections, EphemeralConfig config)
@@ -26,10 +25,6 @@ public class ModSelection : EventBase 0 && mods.TryGetMod(config.LastModPath, out var mod))
- SelectMod(mod);
-
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.ModSelection);
_communicator.CollectionInheritanceChanged.Subscribe(OnInheritanceChange, CollectionInheritanceChanged.Priority.ModSelection);
_communicator.ModSettingChanged.Subscribe(OnSettingChange, ModSettingChanged.Priority.ModSelection);
@@ -50,8 +45,6 @@ public class ModSelection : EventBase();
Messager = _services.GetService();
Dynamis = _services.GetService();
_validityChecker = _services.GetService();
_services.EnsureRequiredServices();
- _services.EnsureRequiredServices();
var startup = _services.GetService()
.GetDalamudConfig(DalamudConfigService.WaitingForPluginsOption, out bool s)
diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj
index e0f9de69..47fb58dc 100644
--- a/Penumbra/Penumbra.csproj
+++ b/Penumbra/Penumbra.csproj
@@ -13,6 +13,7 @@
PROFILING;
false
+ false
@@ -58,7 +59,6 @@
-
diff --git a/Penumbra/Penumbra.csproj.DotSettings b/Penumbra/Penumbra.csproj.DotSettings
index 21db684f..a0b39c16 100644
--- a/Penumbra/Penumbra.csproj.DotSettings
+++ b/Penumbra/Penumbra.csproj.DotSettings
@@ -1,4 +1,5 @@
+ True
True
True
True
\ No newline at end of file
diff --git a/Penumbra/Services/FilenameService.cs b/Penumbra/Services/FilenameService.cs
index ba3ef826..54b7e5b5 100644
--- a/Penumbra/Services/FilenameService.cs
+++ b/Penumbra/Services/FilenameService.cs
@@ -10,6 +10,7 @@ public sealed class FilenameService(IDalamudPluginInterface pi) : BaseFilePathPr
public readonly string CollectionDirectory = Path.Combine(pi.ConfigDirectory.FullName, "collections");
public readonly string LocalDataDirectory = Path.Combine(pi.ConfigDirectory.FullName, "mod_data");
public readonly string EphemeralConfigFile = Path.Combine(pi.ConfigDirectory.FullName, "ephemeral_config.json");
+ public readonly string UiConfigFile = Path.Combine(pi.ConfigDirectory.FullName, "ui_config.json");
public readonly string OldFilesystemFile = Path.Combine(pi.ConfigDirectory.FullName, "sort_order.json");
public readonly string ActiveCollectionsFile = Path.Combine(pi.ConfigDirectory.FullName, "active_collections.json");
public readonly string PredefinedTagFile = Path.Combine(pi.ConfigDirectory.FullName, "predefined_tags.json");
diff --git a/Penumbra/Services/PcpService.cs b/Penumbra/Services/PcpService.cs
index d0aa4630..74401065 100644
--- a/Penumbra/Services/PcpService.cs
+++ b/Penumbra/Services/PcpService.cs
@@ -135,12 +135,12 @@ public class PcpService : IApiService, IDisposable
}
// Move to folder.
- if (_fileSystem.TryGetValue(arguments.Mod, out var leaf))
+ if (arguments.Mod.Node is {} node)
{
try
{
var folder = _fileSystem.FindOrCreateAllFolders(_config.PcpSettings.FolderName);
- _fileSystem.Move(leaf, folder);
+ _fileSystem.Move(node, folder);
}
catch
{
diff --git a/Penumbra/Services/StainService.cs b/Penumbra/Services/StainService.cs
index 51a84619..2e9b4ec5 100644
--- a/Penumbra/Services/StainService.cs
+++ b/Penumbra/Services/StainService.cs
@@ -1,6 +1,6 @@
using Dalamud.Plugin.Services;
using ImSharp;
-using OtterGui.Widgets;
+using Luna;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Files;
using Penumbra.GameData.Files.StainMapStructs;
@@ -24,7 +24,7 @@ public sealed class TemplateFilter : TextFilterBase
=> item.Id;
}
-public sealed class StainTemplateCombo : ImSharp.FilterComboBase
+public sealed class StainTemplateCombo : FilterComboBase
where TDyePack : unmanaged, IDyePack
{
private readonly StainService.StainCombo[] _stainCombos;
@@ -70,7 +70,7 @@ public sealed class StainTemplateCombo : ImSharp.FilterComboBase : ImSharp.FilterComboBase(dataManager);
}
- public sealed class StainCombo(DictStain stainData) : Luna.FilterComboColors
+ public sealed class StainCombo(DictStain stainData) : FilterComboColors
{
protected override IEnumerable- GetItems()
=> stainData.Value.Select(t => new Item(new StringPair(t.Value.Name), t.Value.Dye, t.Key, t.Value.Gloss)).Prepend(None);
diff --git a/Penumbra/Services/StaticServiceManager.cs b/Penumbra/Services/StaticServiceManager.cs
index e6b8d703..4511882a 100644
--- a/Penumbra/Services/StaticServiceManager.cs
+++ b/Penumbra/Services/StaticServiceManager.cs
@@ -1,11 +1,8 @@
-using Dalamud.Game;
-using Dalamud.Game.ClientState.Objects;
using Dalamud.Interface.DragDrop;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Luna;
using Microsoft.Extensions.DependencyInjection;
-using OtterGui;
using Penumbra.Api.Api;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Structs;
@@ -26,12 +23,8 @@ public static class StaticServiceManager
.AddDalamudServices(pi)
.AddExistingService(log)
.AddExistingService(penumbra);
- // TODO Remove this when migration done
- services.AddExistingService(Penumbra.Log);
services.AddIServices(typeof(EquipItem).Assembly);
services.AddIServices(typeof(Penumbra).Assembly);
- services.AddIServices(typeof(Penumbra).Assembly);
- services.AddIServices(typeof(ImGuiUtil).Assembly);
services.AddIServices(typeof(IService).Assembly);
services.AddSingleton(p =>
diff --git a/Penumbra/UI/AdvancedWindow/ItemSelector.cs b/Penumbra/UI/AdvancedWindow/ItemSelector.cs
index d659fc1b..358837f0 100644
--- a/Penumbra/UI/AdvancedWindow/ItemSelector.cs
+++ b/Penumbra/UI/AdvancedWindow/ItemSelector.cs
@@ -6,11 +6,10 @@ using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Mods;
using Penumbra.UI.Classes;
-using Penumbra.UI.ModsTab;
namespace Penumbra.UI.AdvancedWindow;
-public sealed class ItemSelector(ActiveCollections collections, ItemData data, ModFileSystemSelector? selector, FullEquipType type)
+public sealed class ItemSelector(ActiveCollections collections, ItemData data, ModSelection? selection, FullEquipType type)
: FilterComboBase(new ItemFilter())
{
public EquipItem CurrentSelection = new(string.Empty, default, default, default, default, default, FullEquipType.Unknown, default, default,
@@ -59,11 +58,11 @@ public sealed class ItemSelector(ActiveCollections collections, ItemData data, M
protected override IEnumerable GetItems()
{
var list = data.ByType[type];
- if (selector?.Selected is { } currentMod && currentMod.ChangedItems.Values.Any(c => c is IdentifiedItem i && i.Item.Type == type))
+ if (selection?.Mod is { } currentMod && currentMod.ChangedItems.Values.Any(c => c is IdentifiedItem i && i.Item.Type == type))
return list.Select(item => new CacheItem(item, currentMod, collections.Current)).OrderByDescending(i => i.InCurrentMod)
.ThenByDescending(i => i.CollectionMods.Length);
- if (selector is null)
+ if (selection is null)
return list.Select(item => new CacheItem(item, collections.Current)).OrderBy(i => i.CollectionMods.Length);
return list.Select(item => new CacheItem(item, collections.Current)).OrderByDescending(i => i.CollectionMods.Length);
diff --git a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs
index db94ac30..ea1f9dba 100644
--- a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs
+++ b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs
@@ -19,7 +19,6 @@ using Penumbra.Mods.Settings;
using Penumbra.Mods.SubMods;
using Penumbra.Services;
using Penumbra.UI.Classes;
-using Penumbra.UI.ModsTab;
namespace Penumbra.UI.AdvancedWindow;
@@ -69,7 +68,7 @@ public class ItemSwapTab : IDisposable, ITab
private readonly MetaFileManager _metaFileManager;
public ItemSwapTab(CommunicatorService communicator, ItemData itemService, CollectionManager collectionManager,
- ModManager modManager, ModFileSystemSelector selector, ObjectIdentification identifier, MetaFileManager metaFileManager,
+ ModManager modManager, ModSelection selection, ObjectIdentification identifier, MetaFileManager metaFileManager,
Configuration config)
{
_communicator = communicator;
@@ -83,16 +82,16 @@ public class ItemSwapTab : IDisposable, ITab
_selectors = new Dictionary
{
// @formatter:off
- [SwapType.Hat] = (new ItemSelector(a, itemService, selector, FullEquipType.Head), new ItemSelector(a, itemService, null, FullEquipType.Head), new StringU8("Take this Hat"u8), new StringU8("and put it on this one"u8) ),
- [SwapType.Top] = (new ItemSelector(a, itemService, selector, FullEquipType.Body), new ItemSelector(a, itemService, null, FullEquipType.Body), new StringU8("Take this Top"u8), new StringU8("and put it on this one"u8) ),
- [SwapType.Gloves] = (new ItemSelector(a, itemService, selector, FullEquipType.Hands), new ItemSelector(a, itemService, null, FullEquipType.Hands), new StringU8("Take these Gloves"u8), new StringU8("and put them on these"u8) ),
- [SwapType.Pants] = (new ItemSelector(a, itemService, selector, FullEquipType.Legs), new ItemSelector(a, itemService, null, FullEquipType.Legs), new StringU8("Take these Pants"u8), new StringU8("and put them on these"u8) ),
- [SwapType.Shoes] = (new ItemSelector(a, itemService, selector, FullEquipType.Feet), new ItemSelector(a, itemService, null, FullEquipType.Feet), new StringU8("Take these Shoes"u8), new StringU8("and put them on these"u8) ),
- [SwapType.Earrings] = (new ItemSelector(a, itemService, selector, FullEquipType.Ears), new ItemSelector(a, itemService, null, FullEquipType.Ears), new StringU8("Take these Earrings"u8), new StringU8("and put them on these"u8) ),
- [SwapType.Necklace] = (new ItemSelector(a, itemService, selector, FullEquipType.Neck), new ItemSelector(a, itemService, null, FullEquipType.Neck), new StringU8("Take this Necklace"u8), new StringU8("and put it on this one"u8) ),
- [SwapType.Bracelet] = (new ItemSelector(a, itemService, selector, FullEquipType.Wrists), new ItemSelector(a, itemService, null, FullEquipType.Wrists), new StringU8("Take these Bracelets"u8), new StringU8("and put them on these"u8) ),
- [SwapType.Ring] = (new ItemSelector(a, itemService, selector, FullEquipType.Finger), new ItemSelector(a, itemService, null, FullEquipType.Finger), new StringU8("Take this Ring"u8), new StringU8("and put it on this one"u8) ),
- [SwapType.Glasses] = (new ItemSelector(a, itemService, selector, FullEquipType.Glasses), new ItemSelector(a, itemService, null, FullEquipType.Glasses), new StringU8("Take these Glasses"u8), new StringU8("and put them on these"u8) ),
+ [SwapType.Hat] = (new ItemSelector(a, itemService, selection, FullEquipType.Head), new ItemSelector(a, itemService, null, FullEquipType.Head), new StringU8("Take this Hat"u8), new StringU8("and put it on this one"u8) ),
+ [SwapType.Top] = (new ItemSelector(a, itemService, selection, FullEquipType.Body), new ItemSelector(a, itemService, null, FullEquipType.Body), new StringU8("Take this Top"u8), new StringU8("and put it on this one"u8) ),
+ [SwapType.Gloves] = (new ItemSelector(a, itemService, selection, FullEquipType.Hands), new ItemSelector(a, itemService, null, FullEquipType.Hands), new StringU8("Take these Gloves"u8), new StringU8("and put them on these"u8) ),
+ [SwapType.Pants] = (new ItemSelector(a, itemService, selection, FullEquipType.Legs), new ItemSelector(a, itemService, null, FullEquipType.Legs), new StringU8("Take these Pants"u8), new StringU8("and put them on these"u8) ),
+ [SwapType.Shoes] = (new ItemSelector(a, itemService, selection, FullEquipType.Feet), new ItemSelector(a, itemService, null, FullEquipType.Feet), new StringU8("Take these Shoes"u8), new StringU8("and put them on these"u8) ),
+ [SwapType.Earrings] = (new ItemSelector(a, itemService, selection, FullEquipType.Ears), new ItemSelector(a, itemService, null, FullEquipType.Ears), new StringU8("Take these Earrings"u8), new StringU8("and put them on these"u8) ),
+ [SwapType.Necklace] = (new ItemSelector(a, itemService, selection, FullEquipType.Neck), new ItemSelector(a, itemService, null, FullEquipType.Neck), new StringU8("Take this Necklace"u8), new StringU8("and put it on this one"u8) ),
+ [SwapType.Bracelet] = (new ItemSelector(a, itemService, selection, FullEquipType.Wrists), new ItemSelector(a, itemService, null, FullEquipType.Wrists), new StringU8("Take these Bracelets"u8), new StringU8("and put them on these"u8) ),
+ [SwapType.Ring] = (new ItemSelector(a, itemService, selection, FullEquipType.Finger), new ItemSelector(a, itemService, null, FullEquipType.Finger), new StringU8("Take this Ring"u8), new StringU8("and put it on this one"u8) ),
+ [SwapType.Glasses] = (new ItemSelector(a, itemService, selection, FullEquipType.Glasses), new ItemSelector(a, itemService, null, FullEquipType.Glasses), new StringU8("Take these Glasses"u8), new StringU8("and put them on these"u8) ),
// @formatter:on
};
diff --git a/Penumbra/UI/AdvancedWindow/ItemSwapTabFactory.cs b/Penumbra/UI/AdvancedWindow/ItemSwapTabFactory.cs
index ba06807d..3b9087c5 100644
--- a/Penumbra/UI/AdvancedWindow/ItemSwapTabFactory.cs
+++ b/Penumbra/UI/AdvancedWindow/ItemSwapTabFactory.cs
@@ -2,16 +2,22 @@ using Luna;
using Penumbra.Collections.Manager;
using Penumbra.GameData.Data;
using Penumbra.Meta;
+using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Services;
-using Penumbra.UI.ModsTab;
namespace Penumbra.UI.AdvancedWindow;
-public class ItemSwapTabFactory(CommunicatorService communicator, ItemData itemService, CollectionManager collectionManager,
- ModManager modManager, ModFileSystemSelector selector, ObjectIdentification identifier, MetaFileManager metaFileManager,
+public class ItemSwapTabFactory(
+ CommunicatorService communicator,
+ ItemData itemService,
+ CollectionManager collectionManager,
+ ModManager modManager,
+ ModSelection selection,
+ ObjectIdentification identifier,
+ MetaFileManager metaFileManager,
Configuration config) : IUiService
{
public ItemSwapTab Create()
- => new(communicator, itemService, collectionManager, modManager, selector, identifier, metaFileManager, config);
+ => new(communicator, itemService, collectionManager, modManager, selection, identifier, metaFileManager, config);
}
diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ColorTable.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ColorTable.cs
index d9ca832c..7fbccaa9 100644
--- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ColorTable.cs
+++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ColorTable.cs
@@ -27,22 +27,18 @@ public partial class MtrlTab
var frameHeight = Im.Style.FrameHeight;
var highlighterSize = ImEx.Icon.CalculateSize(LunaStyle.OnHoverIcon) + framePadding * 2.0f;
- using var font = Im.Font.PushMono();
using var alignment = ImStyleDouble.ButtonTextAlign.Push(new Vector2(0, 0.5f));
- // This depends on the font being pushed for "proper" alignment of the pair indices in the buttons.
- var spaceWidth = Im.Font.Mono.GetCharacterAdvance(' ');
- var spacePadding = (int)MathF.Ceiling((highlighterSize.X + framePadding.X + itemInnerSpacing) / spaceWidth);
-
+ var buttonSize = new Vector2(buttonWidth, Im.Style.FrameHeightWithSpacing + frameHeight);
for (var i = 0; i < ColorTable.NumRows >> 1; i += 8)
{
for (var j = 0; j < 8; ++j)
{
- var pairIndex = i + j;
+ var pairIndex = i + j;
+ using var id = Im.Id.Push(pairIndex);
using (ImGuiColor.Button.Push(Im.Style[ImGuiColor.ButtonActive], pairIndex == _colorTableSelectedPair))
{
- if (Im.Button($"#{pairIndex + 1}".PadLeft(3 + spacePadding),
- new Vector2(buttonWidth, Im.Style.FrameHeightWithSpacing + frameHeight)))
+ if (Im.Button(StringU8.Empty, buttonSize))
_colorTableSelectedPair = pairIndex;
}
@@ -68,11 +64,13 @@ public partial class MtrlTab
if (j < 7)
Im.Line.Same();
- var cursor = Im.Cursor.ScreenPosition;
- Im.Cursor.ScreenPosition = rcMin with { Y = float.Lerp(rcMin.Y, rcMax.Y, 0.5f) - highlighterSize.Y * 0.5f };
- font.Pop();
+ var cursor = Im.Cursor.ScreenPosition;
+ var buttonPos = rcMin with { Y = float.Lerp(rcMin.Y, rcMax.Y, 0.5f) - highlighterSize.Y * 0.5f };
+ Im.Cursor.ScreenPosition = buttonPos;
ColorTablePairHighlightButton(pairIndex, disabled);
- font.Push(Im.Font.Mono);
+ Im.Cursor.ScreenPosition = buttonPos + new Vector2(Im.Style.FrameHeight + Im.Style.ItemInnerSpacing.X, Im.Style.FramePadding.Y);
+ using var font = Im.Font.PushMono();
+ Im.Text($"#{pairIndex + 1:D2}");
Im.Cursor.ScreenPosition = cursor;
}
}
diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.CommonColorTable.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.CommonColorTable.cs
index a271426c..f60fa376 100644
--- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.CommonColorTable.cs
+++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.CommonColorTable.cs
@@ -316,7 +316,7 @@ public partial class MtrlTab
topColor, topColor, bottomColor, bottomColor);
drawList.RectangleFilled(
rcMin with { Y = MathF.Ceiling(float.Lerp(rcMin.Y, rcMax.Y, 2.0f / 3)) }, rcMax,
- bottomColor, frameRounding, ImDrawFlagsRectangle.RoundCornersTop);
+ bottomColor, frameRounding, ImDrawFlagsRectangle.RoundCornersBottom);
}
drawList.Rectangle(rcMin, rcMax, borderColor.Color, frameRounding, default, frameThickness);
diff --git a/Penumbra/UI/Classes/FileDialogService.cs b/Penumbra/UI/Classes/FileDialogService.cs
index 0a6cb6d9..14fd0045 100644
--- a/Penumbra/UI/Classes/FileDialogService.cs
+++ b/Penumbra/UI/Classes/FileDialogService.cs
@@ -118,11 +118,7 @@ public class FileDialogService : IDisposable, IUiService
/// Set up the file selector with the right flags and custom side bar items.
private static FileDialogManager SetupFileManager(string modDirectory)
{
- var fileManager = new FileDialogManager
- {
- AddedWindowFlags = (WindowFlags.NoCollapse | WindowFlags.NoDocking).ToDalamudWindowFlags(),
- };
-
+ var fileManager = DalamudExtensions.CreateFileDialog(WindowFlags.NoCollapse | WindowFlags.NoDocking);
if (WindowsFunctions.GetDownloadsFolder(out var downloadsFolder))
fileManager.CustomSideBarItems.Add(("Downloads", downloadsFolder, FontAwesomeIcon.Download, -1));
diff --git a/Penumbra/UI/MainWindow/ChangedItemsTab.cs b/Penumbra/UI/MainWindow/ChangedItemsTab.cs
index 71499ced..f226f134 100644
--- a/Penumbra/UI/MainWindow/ChangedItemsTab.cs
+++ b/Penumbra/UI/MainWindow/ChangedItemsTab.cs
@@ -74,6 +74,11 @@ public sealed class ChangedItemsTab(
uiState.ChangedItemTabCategoryFilter = ChangedItemFlagExtensions.DefaultFlags;
FilterChanged?.Invoke();
}
+
+ public bool IsEmpty
+ => uiState.ChangedItemTabModFilter.Length is 0
+ && uiState.ChangedItemTabNameFilter.Length is 0
+ && uiState.ChangedItemTabCategoryFilter is ChangedItemFlagExtensions.DefaultFlags;
}
private readonly record struct Item(string Label, IIdentifiedObjectData Data, SingleArray Mods)
@@ -102,7 +107,6 @@ public sealed class ChangedItemsTab(
_collectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.ChangedItemsTabCache);
}
-
private void OnCollectionChange(in CollectionChange.Arguments arguments)
=> FilterDirty = true;
diff --git a/Penumbra/UI/MainWindow/MainTabBar.cs b/Penumbra/UI/MainWindow/MainTabBar.cs
index 1bf73669..139c8945 100644
--- a/Penumbra/UI/MainWindow/MainTabBar.cs
+++ b/Penumbra/UI/MainWindow/MainTabBar.cs
@@ -3,6 +3,7 @@ using Penumbra.Api.Enums;
using Penumbra.Communication;
using Penumbra.Mods.Manager;
using Penumbra.Services;
+using Penumbra.UI.ModsTab;
using Penumbra.UI.Tabs;
using Penumbra.UI.Tabs.Debug;
using Watcher = Penumbra.UI.ResourceWatcher.ResourceWatcher;
@@ -11,14 +12,13 @@ namespace Penumbra.UI.MainWindow;
public sealed class MainTabBar : TabBar, IDisposable
{
- public readonly Tabs.ModsTab Mods;
+ private readonly ModFileSystem _modFileSystem;
private readonly EphemeralConfig _config;
private readonly SelectTab _selectTab;
public MainTabBar(Logger log,
SettingsTab settings,
- Tabs.ModsTab mods,
- ModTab mods2,
+ ModTab mods,
CollectionsTab collections,
ChangedItemsTab changedItems,
EffectiveTab effectiveChanges,
@@ -26,13 +26,13 @@ public sealed class MainTabBar : TabBar, IDisposable
ResourceTab resources,
Watcher watcher,
OnScreenTab onScreen,
- MessagesTab messages, EphemeralConfig config, CommunicatorService communicator)
- : base(nameof(MainTabBar), log, settings, collections, mods, mods2, changedItems, effectiveChanges, onScreen,
+ MessagesTab messages, EphemeralConfig config, CommunicatorService communicator, ModFileSystem modFileSystem)
+ : base(nameof(MainTabBar), log, settings, collections, mods, changedItems, effectiveChanges, onScreen,
resources, watcher, debug, messages)
{
- Mods = mods;
- _config = config;
- _selectTab = communicator.SelectTab;
+ _config = config;
+ _modFileSystem = modFileSystem;
+ _selectTab = communicator.SelectTab;
_selectTab.Subscribe(OnSelectTab, SelectTab.Priority.MainTabBar);
TabSelected.Subscribe(OnTabSelected, 0);
@@ -41,8 +41,8 @@ public sealed class MainTabBar : TabBar, IDisposable
private void OnSelectTab(in SelectTab.Arguments arguments)
{
NextTab = arguments.Tab;
- if (arguments.Mod is not null)
- Mods.SelectMod = arguments.Mod;
+ if (arguments.Mod?.Node is { } node)
+ _modFileSystem.Selection.Select(node);
}
public void Dispose()
@@ -52,6 +52,9 @@ public sealed class MainTabBar : TabBar, IDisposable
private void OnTabSelected(in TabType type)
{
+ if (_config.SelectedTab == type)
+ return;
+
_config.SelectedTab = type;
_config.Save();
}
diff --git a/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs
index d53410b1..ba99af9e 100644
--- a/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs
+++ b/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs
@@ -155,7 +155,7 @@ public readonly struct ImcModGroupEditDrawer(ModGroupEditDrawer editor, ImcModGr
var inDefault = defaultMask.HasValue && (defaultMask & flag) is not 0;
using (Im.Disabled(defaultMask is not null && !cache.CanChange(i)))
{
- if (inDefault ? NegativeCheckbox.Instance.Draw(""u8, ref value) : Im.Checkbox(""u8, ref value))
+ if (inDefault ? ImEx.XCheckbox(""u8, ref value) : Im.Checkbox(""u8, ref value))
{
if (data is ImcModGroup g)
editor.ChangeDefaultAttribute(g, cache, i, value);
@@ -169,21 +169,4 @@ public readonly struct ImcModGroupEditDrawer(ModGroupEditDrawer editor, ImcModGr
Im.Line.SameInner();
}
}
-
- private sealed class NegativeCheckbox : OtterGui.Text.Widget.MultiStateCheckbox
- {
- public static readonly NegativeCheckbox Instance = new();
-
- protected override void RenderSymbol(bool value, Vector2 position, float size)
- {
- if (value)
- Im.Render.Cross(Im.Window.DrawList, position, ImGuiColor.CheckMark.Get(), size);
- }
-
- protected override bool NextValue(bool value)
- => !value;
-
- protected override bool PreviousValue(bool value)
- => !value;
- }
}
diff --git a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs
deleted file mode 100644
index 41727c73..00000000
--- a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs
+++ /dev/null
@@ -1,856 +0,0 @@
-using Dalamud.Interface;
-using Dalamud.Interface.DragDrop;
-using Dalamud.Interface.ImGuiNotification;
-using Dalamud.Plugin.Services;
-using Dalamud.Bindings.ImGui;
-using ImSharp;
-using Luna;
-using OtterGui;
-using OtterGui.Filesystem;
-using OtterGui.FileSystem.Selector;
-using OtterGui.Raii;
-using OtterGui.Text;
-using OtterGui.Text.Widget;
-using Penumbra.Collections;
-using Penumbra.Collections.Manager;
-using Penumbra.Communication;
-using Penumbra.Mods;
-using Penumbra.Mods.Manager;
-using Penumbra.Mods.Settings;
-using Penumbra.Services;
-using Penumbra.UI.Classes;
-using Penumbra.UI.ModsTab.Selector;
-using MessageService = Penumbra.Services.MessageService;
-
-namespace Penumbra.UI.ModsTab;
-
-public sealed class ModFileSystemSelector : FileSystemSelector, IUiService
-{
- private readonly CommunicatorService _communicator;
- private readonly Configuration _config;
- private readonly FileDialogService _fileDialog;
- private readonly ModManager _modManager;
- private readonly CollectionManager _collectionManager;
- private readonly TutorialService _tutorial;
- private readonly ModImportManager _modImportManager;
- private readonly IDragDropManager _dragDrop;
- private readonly ModSearchStringSplitter _filter = new();
- private readonly ModSelection _selection;
-
- public ModFileSystemSelector(IKeyState keyState, CommunicatorService communicator, ModFileSystem fileSystem, ModManager modManager,
- CollectionManager collectionManager, Configuration config, TutorialService tutorial, FileDialogService fileDialog,
- MessageService messager, ModImportManager modImportManager, IDragDropManager dragDrop, ModSelection selection)
- : base(fileSystem, keyState, Penumbra.Log, HandleException, allowMultipleSelection: true)
- {
- _communicator = communicator;
- _modManager = modManager;
- _collectionManager = collectionManager;
- _config = config;
- _tutorial = tutorial;
- _fileDialog = fileDialog;
- _modImportManager = modImportManager;
- _dragDrop = dragDrop;
- _selection = selection;
-
- // @formatter:off
- SubscribeRightClickFolder(EnableDescendants, 10);
- SubscribeRightClickFolder(DisableDescendants, 10);
- SubscribeRightClickFolder(InheritDescendants, 15);
- SubscribeRightClickFolder(OwnDescendants, 15);
- SubscribeRightClickFolder(SetDefaultImportFolder, 100);
- SubscribeRightClickFolder(f => SetQuickMove(f, 0, _config.QuickMoveFolder1, s => { _config.QuickMoveFolder1 = s; _config.Save(); }), 110);
- SubscribeRightClickFolder(f => SetQuickMove(f, 1, _config.QuickMoveFolder2, s => { _config.QuickMoveFolder2 = s; _config.Save(); }), 120);
- SubscribeRightClickFolder(f => SetQuickMove(f, 2, _config.QuickMoveFolder3, s => { _config.QuickMoveFolder3 = s; _config.Save(); }), 130);
- SubscribeRightClickLeaf(ToggleLeafFavorite);
- SubscribeRightClickLeaf(DrawTemporaryOptions);
- SubscribeRightClickLeaf(l => QuickMove(l, _config.QuickMoveFolder1, _config.QuickMoveFolder2, _config.QuickMoveFolder3));
- SubscribeRightClickMain(ClearTemporarySettings, 105);
- SubscribeRightClickMain(ClearDefaultImportFolder, 100);
- SubscribeRightClickMain(() => ClearQuickMove(0, _config.QuickMoveFolder1, () => {_config.QuickMoveFolder1 = string.Empty; _config.Save();}), 110);
- SubscribeRightClickMain(() => ClearQuickMove(1, _config.QuickMoveFolder2, () => {_config.QuickMoveFolder2 = string.Empty; _config.Save();}), 120);
- SubscribeRightClickMain(() => ClearQuickMove(2, _config.QuickMoveFolder3, () => {_config.QuickMoveFolder3 = string.Empty; _config.Save();}), 130);
- UnsubscribeRightClickLeaf(RenameLeaf);
- SetRenameSearchPath(_config.ShowRename);
- AddButton(AddNewModButton, 0);
- AddButton(AddImportModButton, 1);
- AddButton(AddHelpButton, 2);
- AddButton(DeleteModButton, 1000);
- // @formatter:on
- SetFilterTooltip();
-
- if (_selection.Mod != null)
- SelectByValue(_selection.Mod);
- _communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.ModFileSystemSelector);
- _communicator.ModSettingChanged.Subscribe(OnSettingChange, ModSettingChanged.Priority.ModFileSystemSelector);
- _communicator.CollectionInheritanceChanged.Subscribe(OnInheritanceChange, CollectionInheritanceChanged.Priority.ModFileSystemSelector);
- _communicator.ModDataChanged.Subscribe(OnModDataChange, ModDataChanged.Priority.ModFileSystemSelector);
- _communicator.ModDiscoveryStarted.Subscribe(StoreCurrentSelection, ModDiscoveryStarted.Priority.ModFileSystemSelector);
- _communicator.ModDiscoveryFinished.Subscribe(RestoreLastSelection, ModDiscoveryFinished.Priority.ModFileSystemSelector);
- SetFilterDirty();
- SelectionChanged += OnSelectionChanged;
- }
-
- public void SetRenameSearchPath(RenameField value)
- {
- switch (value)
- {
- case RenameField.RenameSearchPath:
- SubscribeRightClickLeaf(RenameLeafMod, 1000);
- UnsubscribeRightClickLeaf(RenameMod);
- break;
- case RenameField.RenameData:
- UnsubscribeRightClickLeaf(RenameLeafMod);
- SubscribeRightClickLeaf(RenameMod, 1000);
- break;
- case RenameField.BothSearchPathPrio:
- UnsubscribeRightClickLeaf(RenameLeafMod);
- UnsubscribeRightClickLeaf(RenameMod);
- SubscribeRightClickLeaf(RenameLeafMod, 1001);
- SubscribeRightClickLeaf(RenameMod, 1000);
- break;
- case RenameField.BothDataPrio:
- UnsubscribeRightClickLeaf(RenameLeafMod);
- UnsubscribeRightClickLeaf(RenameMod);
- SubscribeRightClickLeaf(RenameLeafMod, 1000);
- SubscribeRightClickLeaf(RenameMod, 1001);
- break;
- default:
- UnsubscribeRightClickLeaf(RenameLeafMod);
- UnsubscribeRightClickLeaf(RenameMod);
- break;
- }
- }
-
- private static readonly string[] ValidModExtensions =
- [
- ".ttmp",
- ".ttmp2",
- ".pmp",
- ".pcp",
- ".zip",
- ".rar",
- ".7z",
- ];
-
- public new void Draw()
- {
- _dragDrop.CreateImGuiSource("ModDragDrop", m => m.Extensions.Any(e => ValidModExtensions.Contains(e.ToLowerInvariant())), m =>
- {
- ImUtf8.Text($"Dragging mods for import:\n\t{string.Join("\n\t", m.Files.Select(Path.GetFileName))}");
- return true;
- });
- base.Draw();
- if (_dragDrop.CreateImGuiTarget("ModDragDrop", out var files, out _))
- _modImportManager.AddUnpack(files.Where(f => ValidModExtensions.Contains(Path.GetExtension(f.ToLowerInvariant()))));
- }
-
- protected override float CurrentWidth
- => _config.Ephemeral.CurrentModSelectorWidth * Im.Style.GlobalScale;
-
- protected override float MinimumAbsoluteRemainder
- => 550 * Im.Style.GlobalScale;
-
- protected override float MinimumScaling
- => _config.Ephemeral.ModSelectorMinimumScale;
-
- protected override float MaximumScaling
- => _config.Ephemeral.ModSelectorMaximumScale;
-
- protected override void SetSize(Vector2 size)
- {
- base.SetSize(size);
- var adaptedSize = MathF.Round(size.X / Im.Style.GlobalScale);
- if (adaptedSize == _config.Ephemeral.CurrentModSelectorWidth)
- return;
-
- _config.Ephemeral.CurrentModSelectorWidth = adaptedSize;
- _config.Ephemeral.Save();
- }
-
- public override void Dispose()
- {
- base.Dispose();
- _communicator.ModDiscoveryStarted.Unsubscribe(StoreCurrentSelection);
- _communicator.ModDiscoveryFinished.Unsubscribe(RestoreLastSelection);
- _communicator.ModDataChanged.Unsubscribe(OnModDataChange);
- _communicator.ModSettingChanged.Unsubscribe(OnSettingChange);
- _communicator.CollectionInheritanceChanged.Unsubscribe(OnInheritanceChange);
- _communicator.CollectionChange.Unsubscribe(OnCollectionChange);
- }
-
- public new ModFileSystem.Leaf? SelectedLeaf
- => base.SelectedLeaf;
-
- #region Interface
-
- // Customization points.
- public override ISortMode SortMode
- => ISortMode.FoldersFirst;
-
- protected override uint ExpandedFolderColor
- => ColorId.FolderExpanded.Value().Color;
-
- protected override uint CollapsedFolderColor
- => ColorId.FolderCollapsed.Value().Color;
-
- protected override uint FolderLineColor
- => ColorId.FolderLine.Value().Color;
-
- protected override bool FoldersDefaultOpen
- => _config.OpenFoldersByDefault;
-
- protected override void DrawPopups()
- {
- DrawHelpPopup();
-
- if (ImGuiUtil.OpenNameField("Create New Mod", ref _newModName))
- {
- var newDir = _modManager.Creator.CreateEmptyMod(_modManager.BasePath, _newModName);
- if (newDir != null)
- {
- _modManager.AddMod(newDir, false);
- _newModName = string.Empty;
- }
- }
-
- while (_modImportManager.AddUnpackedMod(out var mod))
- SelectByValue(mod);
- }
-
- protected override void DrawLeafName(FileSystem.Leaf leaf, in ModState state, bool selected)
- {
- var flags = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags;
- using var c = ImGuiColor.Text.Push(state.Color.Value().Tinted(state.Tint.Value()))
- .Push(ImGuiColor.HeaderHovered, 0x4000FFFF, leaf.Value.Favorite);
- using var id = ImUtf8.PushId(leaf.Value.Index);
- ImUtf8.TreeNode(leaf.Value.Name, flags).Dispose();
- if (Im.Item.MiddleClicked())
- {
- _modManager.SetKnown(leaf.Value);
- var (setting, collection) = _collectionManager.Active.Current.GetActualSettings(leaf.Value.Index);
- if (_config.DeleteModModifier.ForcedModifier(new DoubleModifier(ModifierHotkey.Control, ModifierHotkey.Shift)).IsActive())
- {
- // Delete temporary settings if they exist, regardless of mode, or set to inheriting if none exist.
- if (_collectionManager.Active.Current.GetTempSettings(leaf.Value.Index) is not null)
- _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, leaf.Value, null);
- else
- _collectionManager.Editor.SetModInheritance(_collectionManager.Active.Current, leaf.Value, true);
- }
- else
- {
- if (_config.DefaultTemporaryMode)
- {
- var settings = new TemporaryModSettings(leaf.Value, setting) { ForceInherit = false };
- settings.Enabled = !settings.Enabled;
- _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, leaf.Value, settings);
- }
- else
- {
- var inherited = collection != _collectionManager.Active.Current;
- if (inherited)
- _collectionManager.Editor.SetModInheritance(_collectionManager.Active.Current, leaf.Value, false);
- _collectionManager.Editor.SetModState(_collectionManager.Active.Current, leaf.Value, setting is not { Enabled: true });
- }
- }
- }
-
- if (!state.Priority.IsDefault && !_config.HidePrioritiesInSelector)
- {
- var line = ImGui.GetItemRectMin().Y;
- var itemPos = ImGui.GetItemRectMax().X;
- var maxWidth = ImGui.GetWindowPos().X + Im.Window.MaximumContentRegion.X;
- var priorityString = $"[{state.Priority}]";
- var size = ImGui.CalcTextSize(priorityString).X;
- var remainingSpace = maxWidth - itemPos;
- var offset = remainingSpace - size;
- if (ImGui.GetScrollMaxY() == 0)
- offset -= Im.Style.ItemInnerSpacing.X;
-
- if (offset > Im.Style.ItemSpacing.X)
- ImGui.GetWindowDrawList().AddText(new Vector2(itemPos + offset, line), ColorId.SelectorPriority.Value().Color, priorityString);
- }
- }
-
-
- // Add custom context menu items.
- private void EnableDescendants(ModFileSystem.Folder folder)
- {
- if (ImUtf8.MenuItem("Enable Descendants"u8))
- SetDescendants(folder, true);
- }
-
- private void ClearTemporarySettings()
- {
- if (ImUtf8.MenuItem("Clear Temporary Settings"u8))
- _collectionManager.Editor.ClearTemporarySettings(_collectionManager.Active.Current);
- }
-
- private void DisableDescendants(ModFileSystem.Folder folder)
- {
- if (ImUtf8.MenuItem("Disable Descendants"u8))
- SetDescendants(folder, false);
- }
-
- private void InheritDescendants(ModFileSystem.Folder folder)
- {
- if (ImUtf8.MenuItem("Inherit Descendants"u8))
- SetDescendants(folder, true, true);
- }
-
- private void OwnDescendants(ModFileSystem.Folder folder)
- {
- if (ImUtf8.MenuItem("Stop Inheriting Descendants"u8))
- SetDescendants(folder, false, true);
- }
-
- private void ToggleLeafFavorite(FileSystem.Leaf mod)
- {
- if (ImUtf8.MenuItem(mod.Value.Favorite ? "Remove Favorite"u8 : "Mark as Favorite"u8))
- _modManager.DataEditor.ChangeModFavorite(mod.Value, !mod.Value.Favorite);
- }
-
- private void DrawTemporaryOptions(FileSystem.Leaf mod)
- {
- var tempSettings = _collectionManager.Active.Current.GetTempSettings(mod.Value.Index);
- if (tempSettings is { Lock: > 0 })
- return;
-
- if (tempSettings is { Lock: <= 0 } && ImUtf8.MenuItem("Remove Temporary Settings"u8))
- _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, mod.Value, null);
- var actual = _collectionManager.Active.Current.GetActualSettings(mod.Value.Index).Settings;
- if (actual?.Enabled is true && ImUtf8.MenuItem("Disable Temporarily"u8))
- _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, mod.Value,
- new TemporaryModSettings(mod.Value, actual) { Enabled = false });
-
- if (actual is not { Enabled: true } && ImUtf8.MenuItem("Enable Temporarily"u8))
- {
- var newSettings = actual is null
- ? TemporaryModSettings.DefaultSettings(mod.Value, TemporaryModSettings.OwnSource, true)
- : new TemporaryModSettings(mod.Value, actual) { Enabled = true };
- _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, mod.Value, newSettings);
- }
-
- if (tempSettings is null && ImUtf8.MenuItem("Turn Temporary"u8))
- _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, mod.Value,
- new TemporaryModSettings(mod.Value, actual));
- }
-
- private void SetDefaultImportFolder(ModFileSystem.Folder folder)
- {
- if (!ImUtf8.MenuItem("Set As Default Import Folder"u8))
- return;
-
- var newName = folder.FullName();
- if (newName == _config.DefaultImportFolder)
- return;
-
- _config.DefaultImportFolder = newName;
- _config.Save();
- }
-
- private void ClearDefaultImportFolder()
- {
- if (!ImUtf8.MenuItem("Clear Default Import Folder"u8) || _config.DefaultImportFolder.Length <= 0)
- return;
-
- _config.DefaultImportFolder = string.Empty;
- _config.Save();
- }
-
- private string _newModName = string.Empty;
-
- private void AddNewModButton(Vector2 size)
- {
- if (ImUtf8.IconButton(FontAwesomeIcon.Plus, "Create a new, empty mod of a given name."u8, size, !_modManager.Valid))
- ImUtf8.OpenPopup("Create New Mod"u8);
- }
-
- /// Add an import mods button that opens a file selector.
- private void AddImportModButton(Vector2 size)
- {
- var button = ImUtf8.IconButton(FontAwesomeIcon.FileImport,
- "Import one or multiple mods from Tex Tools Mod Pack Files or Penumbra Mod Pack Files."u8, size, !_modManager.Valid);
- _tutorial.OpenTutorial(BasicTutorialSteps.ModImport);
- if (!button)
- return;
-
- var modPath = _config.DefaultModImportPath.Length > 0
- ? _config.DefaultModImportPath
- : _config.ModDirectory.Length > 0
- ? _config.ModDirectory
- : null;
-
- _fileDialog.OpenFilePicker("Import Mod Pack",
- "Mod Packs{.ttmp,.ttmp2,.pmp,.pcp},TexTools Mod Packs{.ttmp,.ttmp2},Penumbra Mod Packs{.pmp,.pcp},Archives{.zip,.7z,.rar},Penumbra Character Packs{.pcp}",
- (s, f) =>
- {
- if (!s)
- return;
-
- _modImportManager.AddUnpack(f);
- }, 0, modPath, _config.AlwaysOpenDefaultImport);
- }
-
- private void RenameLeafMod(ModFileSystem.Leaf leaf)
- {
- Im.Separator();
- RenameLeaf(leaf);
- }
-
- private void RenameMod(ModFileSystem.Leaf leaf)
- {
- Im.Separator();
- var currentName = leaf.Value.Name;
- if (ImGui.IsWindowAppearing())
- ImGui.SetKeyboardFocusHere(0);
- ImUtf8.Text("Rename Mod:"u8);
- if (ImUtf8.InputText("##RenameMod"u8, ref currentName, flags: ImGuiInputTextFlags.EnterReturnsTrue))
- {
- _modManager.DataEditor.ChangeModName(leaf.Value, currentName);
- Im.Popup.CloseCurrent();
- }
-
- Im.Tooltip.OnHover("Enter a new name here to rename the changed mod."u8);
- }
-
- private void DeleteModButton(Vector2 size)
- => DeleteSelectionButton(size, Unsafe.BitCast(_config.DeleteModModifier), "mod",
- "mods", _modManager.DeleteMod);
-
- private void AddHelpButton(Vector2 size)
- {
- if (ImUtf8.IconButton(FontAwesomeIcon.QuestionCircle, "Open extended help."u8, size, false))
- ImUtf8.OpenPopup("ExtendedHelp"u8);
-
- _tutorial.OpenTutorial(BasicTutorialSteps.AdvancedHelp);
- }
-
- private void SetDescendants(ModFileSystem.Folder folder, bool enabled, bool inherit = false)
- {
- var mods = folder.GetAllDescendants(ISortMode.Lexicographical).OfType().Select(l =>
- {
- // Any mod handled here should not stay new.
- _modManager.SetKnown(l.Value);
- return l.Value;
- });
-
- if (inherit)
- _collectionManager.Editor.SetMultipleModInheritances(_collectionManager.Active.Current, mods, enabled);
- else
- _collectionManager.Editor.SetMultipleModStates(_collectionManager.Active.Current, mods, enabled);
- }
-
- private void DrawHelpPopup()
- {
- ImGuiUtil.HelpPopup("ExtendedHelp", new Vector2(1000 * Im.Style.GlobalScale, 38.5f * Im.Style.TextHeightWithSpacing), () =>
- {
- ImGui.Dummy(Vector2.UnitY * Im.Style.TextHeight);
- ImUtf8.Text("Mod Management"u8);
- ImUtf8.BulletText("You can create empty mods or import mods with the buttons in this row."u8);
- using var indent = ImRaii.PushIndent();
- ImUtf8.BulletText("Supported formats for import are: .ttmp, .ttmp2, .pmp, .pcp."u8);
- ImUtf8.BulletText(
- "You can also support .zip, .7z or .rar archives, but only if they already contain Penumbra-styled mods with appropriate metadata."u8);
- indent.Pop(1);
- ImUtf8.BulletText("You can also create empty mod folders and delete mods."u8);
- ImUtf8.BulletText(
- "For further editing of mods, select them and use the Edit Mod tab in the panel or the Advanced Editing popup."u8);
- ImGui.Dummy(Vector2.UnitY * Im.Style.TextHeight);
- ImUtf8.Text("Mod Selector"u8);
- ImUtf8.BulletText("Select a mod to obtain more information or change settings."u8);
- ImUtf8.BulletText("Names are colored according to your config and their current state in the collection:"u8);
- indent.Push();
- ImUtf8.BulletTextColored(ColorId.EnabledMod.Value().Color, "enabled in the current collection."u8);
- ImUtf8.BulletTextColored(ColorId.DisabledMod.Value().Color, "disabled in the current collection."u8);
- ImUtf8.BulletTextColored(ColorId.InheritedMod.Value().Color, "enabled due to inheritance from another collection."u8);
- ImUtf8.BulletTextColored(ColorId.InheritedDisabledMod.Value().Color, "disabled due to inheritance from another collection."u8);
- ImUtf8.BulletTextColored(ColorId.UndefinedMod.Value().Color, "unconfigured in all inherited collections."u8);
- ImUtf8.BulletTextColored(ColorId.HandledConflictMod.Value().Color,
- "enabled and conflicting with another enabled Mod, but on different priorities (i.e. the conflict is solved)."u8);
- ImUtf8.BulletTextColored(ColorId.ConflictingMod.Value().Color,
- "enabled and conflicting with another enabled Mod on the same priority."u8);
- ImUtf8.BulletTextColored(ColorId.FolderExpanded.Value().Color, "expanded mod folder."u8);
- ImUtf8.BulletTextColored(ColorId.FolderCollapsed.Value().Color, "collapsed mod folder"u8);
- indent.Pop(1);
- ImUtf8.BulletText("Middle-click a mod to disable it if it is enabled or enable it if it is disabled."u8);
- indent.Push();
- ImUtf8.BulletText(
- $"Holding {_config.DeleteModModifier.ForcedModifier(new DoubleModifier(ModifierHotkey.Control, ModifierHotkey.Shift))} while middle-clicking lets it inherit, discarding settings.");
- indent.Pop(1);
- ImUtf8.BulletText("Right-click a mod to enter its sort order, which is its name by default, possibly with a duplicate number."u8);
- indent.Push();
- ImUtf8.BulletText("A sort order differing from the mods name will not be displayed, it will just be used for ordering."u8);
- ImUtf8.BulletText(
- "If the sort order string contains Forward-Slashes ('/'), the preceding substring will be turned into folders automatically."u8);
- indent.Pop(1);
- ImUtf8.BulletText(
- "You can drag and drop mods and subfolders into existing folders. Dropping them onto mods is the same as dropping them onto the parent of the mod."u8);
- indent.Push();
- ImUtf8.BulletText(
- "You can select multiple mods and folders by holding Control while clicking them, and then drag all of them at once."u8);
- ImUtf8.BulletText(
- "Selected mods inside an also selected folder will be ignored when dragging and move inside their folder instead of directly into the target."u8);
- indent.Pop(1);
- ImUtf8.BulletText("Right-clicking a folder opens a context menu."u8);
- ImUtf8.BulletText("Right-clicking empty space allows you to expand or collapse all folders at once."u8);
- ImUtf8.BulletText("Use the Filter Mods... input at the top to filter the list for mods whose name or path contain the text."u8);
- indent.Push();
- ImUtf8.BulletText("You can enter n:[string] to filter only for names, without path."u8);
- ImUtf8.BulletText("You can enter c:[string] to filter for Changed Items instead."u8);
- ImUtf8.BulletText("You can enter a:[string] to filter for Mod Authors instead."u8);
- indent.Pop(1);
- ImUtf8.BulletText("Use the expandable menu beside the input to filter for mods fulfilling specific criteria."u8);
- });
- }
-
- private static void HandleException(Exception e)
- => Penumbra.Messager.NotificationMessage(e, e.Message, NotificationType.Warning);
-
- #endregion
-
- #region Automatic cache update functions.
-
- private void OnSettingChange(in ModSettingChanged.Arguments arguments)
- {
- if (arguments.Collection == _collectionManager.Active.Current)
- SetFilterDirty();
- }
-
- private void OnModDataChange(in ModDataChanged.Arguments arguments)
- {
- const ModDataChangeType relevantFlags =
- ModDataChangeType.Name
- | ModDataChangeType.Author
- | ModDataChangeType.ModTags
- | ModDataChangeType.LocalTags
- | ModDataChangeType.Favorite
- | ModDataChangeType.ImportDate;
- if ((arguments.Type & relevantFlags) is not 0)
- SetFilterDirty();
- }
-
- private void OnInheritanceChange(in CollectionInheritanceChanged.Arguments arguments)
- {
- if (arguments.Collection == _collectionManager.Active.Current)
- SetFilterDirty();
- }
-
- private void OnCollectionChange(in CollectionChange.Arguments arguments)
- {
- if (arguments.Type is CollectionType.Current && arguments.OldCollection != arguments.NewCollection)
- SetFilterDirty();
- }
-
- // Keep selections across rediscoveries if possible.
- private string _lastSelectedDirectory = string.Empty;
-
- private void StoreCurrentSelection()
- {
- _lastSelectedDirectory = Selected?.ModPath.FullName ?? string.Empty;
- ClearSelection();
- }
-
- private void RestoreLastSelection()
- {
- if (_lastSelectedDirectory.Length <= 0)
- return;
-
- var leaf = (ModFileSystem.Leaf?)FileSystem.Root.GetAllDescendants(ISortMode.Lexicographical)
- .FirstOrDefault(l => l is ModFileSystem.Leaf m && m.Value.ModPath.FullName == _lastSelectedDirectory);
- Select(leaf, AllowMultipleSelection);
- _lastSelectedDirectory = string.Empty;
- }
-
- private void OnSelectionChanged(Mod? oldSelection, Mod? newSelection, in ModState state)
- => _selection.SelectMod(newSelection);
-
- #endregion
-
- #region Filters
-
- [StructLayout(LayoutKind.Sequential, Pack = 1)]
- public struct ModState
- {
- public ColorId Color;
- public ColorId Tint;
- public ModPriority Priority;
- }
-
- private ModTypeFilter _stateTypeFilter = ModTypeFilterExtensions.UnfilteredStateMods;
-
- private void SetFilterTooltip()
- {
- FilterTooltip = "Filter mods for those where their full paths or names contain the given strings, split by spaces.\n"
- + "Enter c:[string] to filter for mods changing specific items.\n"
- + "Enter t:[string] to filter for mods set to specific tags.\n"
- + "Enter n:[string] to filter only for mod names and no paths.\n"
- + "Enter a:[string] to filter for mods by specific authors.\n"
- + $"Enter s:[string] to filter for mods by the categories of the items they change (1-{ChangedItemFlagExtensions.NumCategories + 1} or partial category name).\n\n"
- + "Use None as a placeholder value that only matches empty lists or names.\n"
- + "Regularly, a mod has to match all supplied criteria separately.\n"
- + "Put a - in front of a search token to search only for mods not matching the criterion.\n"
- + "Put a ? in front of a search token to search for mods matching at least one of the '?'-criteria.\n"
- + "Wrap spaces in \"[string with space]\" to match this exact combination of words.\n\n"
- + "Example: 't:Tag1 t:\"Tag 2\" -t:Tag3 -a:None s:Body -c:Hempen ?c:Camise ?n:Top' will match any mod that\n"
- + " - contains the tags 'tag1' and 'tag 2'\n"
- + " - does not contain the tag 'tag3'\n"
- + " - has any author set (negating None means Any)\n"
- + " - changes an item of the 'Body' category\n"
- + " - and either contains a changed item with 'camise' in it's name, or has 'top' in the mod's name.";
- }
-
- /// Appropriately identify and set the string filter and its type.
- protected override bool ChangeFilter(string filterValue)
- {
- _filter.Parse(filterValue);
- 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, ModTypeFilter hasNoFlag, ModTypeFilter hasFlag)
- => count switch
- {
- 0 when _stateTypeFilter.HasFlag(hasNoFlag) => false,
- 0 => true,
- _ when _stateTypeFilter.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.IPath path, out ModState state)
- {
- if (path is ModFileSystem.Folder f)
- {
- state = default;
- return ModTypeFilterExtensions.UnfilteredStateMods != _stateTypeFilter
- || !_filter.IsVisible(f);
- }
-
- return ApplyFiltersAndState((ModFileSystem.Leaf)path, out state);
- }
-
- /// Apply the string filters.
- private bool ApplyStringFilters(ModFileSystem.Leaf leaf, Mod _)
- => !_filter.IsVisible(leaf);
-
- /// Only get the text color for a mod if no filters are set.
- private (ColorId Color, ColorId Tint) GetTextColor(Mod mod, ModSettings? settings, ModCollection collection)
- {
- var tint = settings.IsTemporary()
- ? ColorId.TemporaryModSettingsTint
- : _modManager.IsNew(mod)
- ? ColorId.NewModTint
- : ColorId.NoTint;
- if (settings.IsTemporary())
- tint = ColorId.TemporaryModSettingsTint;
-
- if (settings == null)
- return (ColorId.UndefinedMod, tint);
-
- if (!settings.Enabled)
- return (collection != _collectionManager.Active.Current
- ? ColorId.InheritedDisabledMod
- : ColorId.DisabledMod, tint);
-
- var conflicts = _collectionManager.Active.Current.Conflicts(mod);
- if (conflicts.Count == 0)
- return (collection != _collectionManager.Active.Current ? ColorId.InheritedMod : ColorId.EnabledMod, tint);
-
- return (conflicts.Any(c => !c.Solved)
- ? ColorId.ConflictingMod
- : ColorId.HandledConflictMod, tint);
- }
-
- private bool CheckStateFilters(Mod mod, ModSettings? settings, ModCollection collection, ref ModState state)
- {
- var isNew = _modManager.IsNew(mod);
- // Handle mod details.
- if (CheckFlags(mod.TotalFileCount, ModTypeFilter.HasNoFiles, ModTypeFilter.HasFiles)
- || CheckFlags(mod.TotalSwapCount, ModTypeFilter.HasNoFileSwaps, ModTypeFilter.HasFileSwaps)
- || CheckFlags(mod.TotalManipulations, ModTypeFilter.HasNoMetaManipulations, ModTypeFilter.HasMetaManipulations)
- || CheckFlags(mod.HasOptions ? 1 : 0, ModTypeFilter.HasNoConfig, ModTypeFilter.HasConfig)
- || CheckFlags(isNew ? 1 : 0, ModTypeFilter.NotNew, ModTypeFilter.IsNew))
- return true;
-
- // Handle Favoritism
- if (!_stateTypeFilter.HasFlag(ModTypeFilter.Favorite) && mod.Favorite
- || !_stateTypeFilter.HasFlag(ModTypeFilter.NotFavorite) && !mod.Favorite)
- return true;
-
- // Handle Temporary
- if (!_stateTypeFilter.HasFlag(ModTypeFilter.Temporary) || !_stateTypeFilter.HasFlag(ModTypeFilter.NotTemporary))
- {
- if (settings == null && _stateTypeFilter.HasFlag(ModTypeFilter.Temporary))
- return true;
-
- if (settings != null && settings.IsTemporary() != _stateTypeFilter.HasFlag(ModTypeFilter.Temporary))
- return true;
- }
-
- // Handle Inheritance
- if (collection == _collectionManager.Active.Current)
- {
- if (!_stateTypeFilter.HasFlag(ModTypeFilter.Uninherited))
- return true;
- }
- else
- {
- state.Color = ColorId.InheritedMod;
- if (!_stateTypeFilter.HasFlag(ModTypeFilter.Inherited))
- return true;
- }
-
- // isNew color takes precedence before other colors.
- if (settings.IsTemporary())
- state.Tint = ColorId.TemporaryModSettingsTint;
- else if (isNew)
- state.Tint = ColorId.NewModTint;
- else
- state.Tint = ColorId.NoTint;
-
-
- // Handle settings.
- if (settings == null)
- {
- state.Color = ColorId.UndefinedMod;
- if (!_stateTypeFilter.HasFlag(ModTypeFilter.Undefined)
- || !_stateTypeFilter.HasFlag(ModTypeFilter.Disabled)
- || !_stateTypeFilter.HasFlag(ModTypeFilter.NoConflict))
- return true;
- }
- else if (!settings.Enabled)
- {
- state.Color = collection != _collectionManager.Active.Current
- ? ColorId.InheritedDisabledMod
- : ColorId.DisabledMod;
- if (!_stateTypeFilter.HasFlag(ModTypeFilter.Disabled)
- || !_stateTypeFilter.HasFlag(ModTypeFilter.NoConflict))
- return true;
- }
- else
- {
- if (!_stateTypeFilter.HasFlag(ModTypeFilter.Enabled))
- return true;
-
- // Conflicts can only be relevant if the mod is enabled.
- var conflicts = _collectionManager.Active.Current.Conflicts(mod);
- if (conflicts.Count > 0)
- {
- if (conflicts.Any(c => !c.Solved))
- {
- if (!_stateTypeFilter.HasFlag(ModTypeFilter.UnsolvedConflict))
- return true;
-
- state.Color = ColorId.ConflictingMod;
- }
- else
- {
- if (!_stateTypeFilter.HasFlag(ModTypeFilter.SolvedConflict))
- return true;
-
- state.Color = ColorId.HandledConflictMod;
- }
- }
- else if (!_stateTypeFilter.HasFlag(ModTypeFilter.NoConflict))
- {
- return true;
- }
- }
-
-
- return false;
- }
-
- /// Combined wrapper for handling all filters and setting state.
- private bool ApplyFiltersAndState(ModFileSystem.Leaf leaf, out ModState state)
- {
- var mod = leaf.Value;
- var (settings, collection) = _collectionManager.Active.Current.GetActualSettings(mod.Index);
-
- state = new ModState
- {
- Color = ColorId.EnabledMod,
- Priority = settings?.Priority ?? ModPriority.Default,
- };
- if (ApplyStringFilters(leaf, mod))
- return true;
-
- if (_stateTypeFilter != ModTypeFilterExtensions.UnfilteredStateMods)
- return CheckStateFilters(mod, settings, collection, ref state);
-
- (state.Color, state.Tint) = GetTextColor(mod, settings, collection);
- return false;
- }
-
- private bool DrawFilterCombo(ref bool everything)
- {
- using var combo = ImUtf8.Combo("##filterCombo"u8, ""u8,
- ImGuiComboFlags.NoPreview | ImGuiComboFlags.PopupAlignLeft | ImGuiComboFlags.HeightLargest);
- var ret = Im.Item.RightClicked();
- if (!combo)
- return ret;
-
- using var style = ImStyleDouble.ItemSpacing.PushY(3 * Im.Style.GlobalScale);
-
- if (ImUtf8.Checkbox("Everything"u8, ref everything))
- {
- _stateTypeFilter = everything ? ModTypeFilterExtensions.UnfilteredStateMods : 0;
- SetFilterDirty();
- }
-
- ImGui.Dummy(new Vector2(0, 5 * Im.Style.GlobalScale));
- foreach (var (onFlag, offFlag, name) in ModTypeFilterExtensions.TriStatePairs)
- {
- if (TriStateCheckbox.Instance.Draw(name, ref _stateTypeFilter, onFlag, offFlag))
- SetFilterDirty();
- }
-
- foreach (var group in ModTypeFilterExtensions.Groups)
- {
- Im.Separator();
- foreach (var (flag, name) in group)
- {
- if (ImUtf8.Checkbox(name, ref _stateTypeFilter, flag))
- SetFilterDirty();
- }
- }
-
- return ret;
- }
-
- /// Add the state filter combo-button to the right of the filter box.
- protected override (float, bool) CustomFilters(float width)
- {
- var pos = ImGui.GetCursorPos();
- var remainingWidth = width - Im.Style.FrameHeight;
- var comboPos = new Vector2(pos.X + remainingWidth, pos.Y);
-
- var everything = _stateTypeFilter == ModTypeFilterExtensions.UnfilteredStateMods;
-
- ImGui.SetCursorPos(comboPos);
- // Draw combo button
- using var color = ImGuiColor.Button.Push(Colors.FilterActive, !everything);
- var rightClick = DrawFilterCombo(ref everything);
- _tutorial.OpenTutorial(BasicTutorialSteps.ModFilters);
- if (rightClick)
- {
- _stateTypeFilter = ModTypeFilterExtensions.UnfilteredStateMods;
- SetFilterDirty();
- }
-
- Im.Tooltip.OnHover("Filter mods for their activation status.\nRight-Click to clear all filters."u8);
- ImGui.SetCursorPos(pos);
- return (remainingWidth, rightClick);
- }
-
- #endregion
-}
diff --git a/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs b/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs
index 0d5b2016..201caf44 100644
--- a/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs
+++ b/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs
@@ -1,7 +1,5 @@
using ImSharp;
using Luna;
-using OtterGui;
-using OtterGui.Services;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@@ -9,20 +7,18 @@ using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.String;
using Penumbra.UI.Classes;
+using ImGuiId = ImSharp.ImGuiId;
namespace Penumbra.UI.ModsTab;
public class ModPanelChangedItemsTab(
- ModFileSystemSelector selector,
+ ModFileSystem fileSystem,
ChangedItemDrawer drawer,
- ImGuiCacheService cacheService,
Configuration config,
ModDataEditor dataEditor)
: ITab
{
- private readonly ImGuiCacheService.CacheId _cacheId = cacheService.GetNewId();
-
- private class ChangedItemsCache
+ private class ChangedItemsCache : BasicCache
{
private Mod? _lastSelected;
private ushort _lastUpdate;
@@ -199,6 +195,10 @@ public class ModPanelChangedItemsTab(
}
}
}
+
+ /// Handled differently.
+ public override void Update()
+ { }
}
public ReadOnlySpan Label
@@ -208,19 +208,22 @@ public class ModPanelChangedItemsTab(
=> ModPanelTab.ChangedItems;
public bool IsVisible
- => selector.Selected!.ChangedItems.Count > 0;
+ => fileSystem.Selection.Selection?.GetValue()?.ChangedItems.Count > 0;
+
+ private Mod _mod = null!;
private Vector2 _buttonSize;
private Rgba32 _starColor;
+ private ImGuiId _id;
public void DrawContent()
{
- if (cacheService.Cache(_cacheId, () => (new ChangedItemsCache(), "ModPanelChangedItemsCache")) is not { } cache)
- return;
-
+ _id = Im.Id.Current;
+ _mod = fileSystem.Selection.Selection!.GetValue()!;
+ var cache = CacheManager.Instance.GetOrCreateCache(_id, () => new ChangedItemsCache());
drawer.DrawTypeFilter();
- cache.Update(selector.Selected, drawer, config.Ephemeral.ChangedItemFilter, config.ChangedItemDisplay);
+ cache.Update(_mod, drawer, config.Ephemeral.ChangedItemFilter, config.ChangedItemDisplay);
Im.Separator();
_buttonSize = new Vector2(Im.Style.ItemSpacing.Y + Im.Style.FrameHeight);
using var style = ImStyleDouble.CellPadding.Push(Vector2.Zero)
@@ -232,7 +235,7 @@ public class ModPanelChangedItemsTab(
.Push(ImGuiColor.ButtonHovered, Rgba32.Transparent);
using var table = Im.Table.Begin("##changedItems"u8, cache.AnyExpandable ? 2 : 1, TableFlags.RowBackground | TableFlags.ScrollY,
- new Vector2(Im.ContentRegion.Available.X, -1));
+ Im.ContentRegion.Available);
if (!table)
return;
@@ -241,11 +244,11 @@ public class ModPanelChangedItemsTab(
{
table.SetupColumn("##exp"u8, TableColumnFlags.WidthFixed, _buttonSize.Y);
table.SetupColumn("##text"u8, TableColumnFlags.WidthStretch);
- ImGuiClip.ClippedDraw(cache.Data, DrawContainerExpandable, _buttonSize.Y);
+ Im.ListClipper.Draw(cache.Data, DrawContainerExpandable, _buttonSize.Y);
}
else
{
- ImGuiClip.ClippedDraw(cache.Data, DrawContainer, _buttonSize.Y);
+ Im.ListClipper.Draw(cache.Data, DrawContainer, _buttonSize.Y);
}
}
@@ -262,7 +265,7 @@ public class ModPanelChangedItemsTab(
_buttonSize))
{
Im.State.Storage.SetBool(obj.Id, !obj.Expanded);
- if (cacheService.TryGetCache(_cacheId, out var cache))
+ if (CacheManager.Instance.TryGetCache(_id, out var cache))
cache.Reset();
}
}
@@ -293,12 +296,12 @@ public class ModPanelChangedItemsTab(
if (ImEx.Icon.Button(LunaStyle.FavoriteIcon,
"Prefer displaying this item instead of the current primary item.\n\nRight-click for more options."u8,
textColor: textColor, size: _buttonSize))
- dataEditor.AddPreferredItem(selector.Selected!, item.Item.Id, false, true);
+ dataEditor.AddPreferredItem(_mod, item.Item.Id, false, true);
using var context = Im.Popup.BeginContextItem("StarContext"u8);
if (!context)
return;
- if (cacheService.TryGetCache(_cacheId, out var cache))
+ if (CacheManager.Instance.TryGetCache(_id, out var cache))
for (--idx; idx >= 0; --idx)
{
if (!cache.Data[idx].Expanded)
@@ -306,34 +309,34 @@ public class ModPanelChangedItemsTab(
if (cache.Data[idx].Data is IdentifiedItem it)
{
- if (selector.Selected!.PreferredChangedItems.Contains(it.Item.Id)
+ if (_mod.PreferredChangedItems.Contains(it.Item.Id)
&& Im.Menu.Item("Remove Parent from Local Preferred Items"u8))
- dataEditor.RemovePreferredItem(selector.Selected!, it.Item.Id, false);
- if (selector.Selected!.DefaultPreferredItems.Contains(it.Item.Id)
+ dataEditor.RemovePreferredItem(_mod, it.Item.Id, false);
+ if (_mod.DefaultPreferredItems.Contains(it.Item.Id)
&& Im.Menu.Item("Remove Parent from Default Preferred Items"u8))
- dataEditor.RemovePreferredItem(selector.Selected!, it.Item.Id, true);
+ dataEditor.RemovePreferredItem(_mod, it.Item.Id, true);
}
break;
}
- var enabled = !selector.Selected!.DefaultPreferredItems.Contains(item.Item.Id);
+ var enabled = !_mod.DefaultPreferredItems.Contains(item.Item.Id);
if (enabled)
{
if (Im.Menu.Item("Add to Local and Default Preferred Changed Items"u8))
- dataEditor.AddPreferredItem(selector.Selected!, item.Item.Id, true, true);
+ dataEditor.AddPreferredItem(_mod, item.Item.Id, true, true);
}
else
{
if (Im.Menu.Item("Remove from Default Preferred Changed Items"u8))
- dataEditor.RemovePreferredItem(selector.Selected!, item.Item.Id, true);
+ dataEditor.RemovePreferredItem(_mod, item.Item.Id, true);
}
if (Im.Menu.Item("Reset Local Preferred Items to Default"u8))
- dataEditor.ResetPreferredItems(selector.Selected!);
+ dataEditor.ResetPreferredItems(_mod);
if (Im.Menu.Item("Clear Local and Default Preferred Items not Changed by the Mod"u8))
- dataEditor.ClearInvalidPreferredItems(selector.Selected!);
+ dataEditor.ClearInvalidPreferredItems(_mod);
}
diff --git a/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs b/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs
index fcb5b956..41faed8a 100644
--- a/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs
+++ b/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs
@@ -7,7 +7,7 @@ using Penumbra.UI.Classes;
namespace Penumbra.UI.ModsTab;
-public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSelector selector) : ITab
+public class ModPanelCollectionsTab(CollectionManager manager, ModSelection selection) : ITab
{
private enum ModState
{
@@ -26,7 +26,7 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele
public void DrawContent()
{
- var (direct, inherited) = CountUsage(selector.Selected!);
+ var (direct, inherited) = CountUsage(selection.Mod!);
Im.Line.New();
switch (direct)
{
@@ -70,8 +70,8 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele
if (Im.Menu.Item("Enable"u8))
{
if (parent != collection)
- manager.Editor.SetModInheritance(collection, selector.Selected!, false);
- manager.Editor.SetModState(collection, selector.Selected!, true);
+ manager.Editor.SetModInheritance(collection, selection.Mod!, false);
+ manager.Editor.SetModState(collection, selection.Mod!, true);
}
}
@@ -80,15 +80,15 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele
if (Im.Menu.Item("Disable"u8))
{
if (parent != collection)
- manager.Editor.SetModInheritance(collection, selector.Selected!, false);
- manager.Editor.SetModState(collection, selector.Selected!, false);
+ manager.Editor.SetModInheritance(collection, selection.Mod!, false);
+ manager.Editor.SetModState(collection, selection.Mod!, false);
}
}
using (Im.Disabled(parent != collection))
{
if (Im.Menu.Item("Inherit"u8))
- manager.Editor.SetModInheritance(collection, selector.Selected!, true);
+ manager.Editor.SetModInheritance(collection, selection.Mod!, true);
}
}
}
diff --git a/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs b/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs
index c477aae7..ce7b750d 100644
--- a/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs
+++ b/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs
@@ -12,7 +12,7 @@ using Penumbra.UI.Classes;
namespace Penumbra.UI.ModsTab;
-public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSystemSelector selector) : ITab
+public class ModPanelConflictsTab(CollectionManager collectionManager, ModSelection selection) : ITab
{
public ReadOnlySpan Label
=> "Conflicts"u8;
@@ -21,7 +21,7 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy
=> ModPanelTab.Conflicts;
public bool IsVisible
- => collectionManager.Active.Current.Conflicts(selector.Selected!).Any(c => !GetPriority(c).IsHidden);
+ => collectionManager.Active.Current.Conflicts(selection.Mod!).Any(c => !GetPriority(c).IsHidden);
private readonly ConditionalWeakTable _expandedMods = [];
@@ -53,7 +53,7 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy
DrawCurrentRow(table, priorityWidth);
// Can not be null because otherwise the tab bar is never drawn.
- var mod = selector.Selected!;
+ var mod = selection.Mod!;
foreach (var (index, conflict) in collectionManager.Active.Current.Conflicts(mod).Where(c => !c.Mod2.Priority.IsHidden)
.OrderByDescending(GetPriority)
.ThenBy(c => c.Mod2.Name, StringComparer.OrdinalIgnoreCase).Index())
@@ -66,16 +66,16 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy
private void DrawCurrentRow(in Im.TableDisposable table, float priorityWidth)
{
using var c = ImGuiColor.Text.Push(ColorId.FolderLine.Value());
- table.DrawFrameColumn(selector.Selected!.Name);
+ table.DrawFrameColumn(selection.Mod!.Name);
table.NextColumn();
- var actualSettings = collectionManager.Active.Current.GetActualSettings(selector.Selected!.Index).Settings!;
+ var actualSettings = collectionManager.Active.Current.GetActualSettings(selection.Mod!.Index).Settings!;
var priority = actualSettings.Priority.Value;
// TODO
using (Im.Disabled(actualSettings is TemporaryModSettings))
{
if (ImEx.InputOnDeactivation.Scalar("##priority"u8, ref priority))
if (priority != actualSettings.Priority.Value)
- collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selector.Selected!,
+ collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selection.Mod!,
new ModPriority(priority));
}
@@ -86,7 +86,7 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy
{
Im.Cursor.FrameAlign();
if (Im.Selectable(conflict.Mod2.Name) && conflict.Mod2 is Mod otherMod)
- selector.SelectByValue(otherMod);
+ selection.SelectMod(otherMod);
var hovered = Im.Item.Hovered();
var rightClicked = Im.Item.RightClicked();
if (conflict.Mod2 is Mod otherMod2)
@@ -125,7 +125,7 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy
table.NextColumn();
var conflictPriority = DrawPriorityInput(conflict, priorityWidth);
Im.Line.Same();
- var selectedPriority = collectionManager.Active.Current.GetActualSettings(selector.Selected!.Index).Settings!.Priority.Value;
+ var selectedPriority = collectionManager.Active.Current.GetActualSettings(selection.Mod!.Index).Settings!.Priority.Value;
DrawPriorityButtons(conflict.Mod2 as Mod, conflictPriority, selectedPriority, buttonSize);
table.NextColumn();
DrawExpandButton(conflict.Mod2, expanded, buttonSize);
@@ -164,10 +164,9 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy
if (ImEx.Icon.Button(FontAwesomeIcon.SortNumericUpAlt.Icon(),
$"Set the priority of the currently selected mod to this mods priority plus one. ({selectedPriority} -> {conflictPriority + 1})",
selectedPriority > conflictPriority, buttonSize))
- collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selector.Selected!,
- new ModPriority(conflictPriority + 1));
+ collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selection.Mod!, new ModPriority(conflictPriority + 1));
Im.Line.Same();
- if (ImEx.Icon.Button(FontAwesomeIcon.SortNumericDownAlt.Icon(),
+ if (ImEx.Icon.Button(FontAwesomeIcon.SortNumericDownAlt.Icon(),
$"Set the priority of this mod to the currently selected mods priority minus one. ({conflictPriority} -> {selectedPriority - 1})",
selectedPriority > conflictPriority || conflict == null, buttonSize))
collectionManager.Editor.SetModPriority(collectionManager.Active.Current, conflict!, new ModPriority(selectedPriority - 1));
diff --git a/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs b/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs
index 9166b2ac..be705941 100644
--- a/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs
+++ b/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs
@@ -1,18 +1,18 @@
using ImSharp;
using Luna;
+using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.UI.Classes;
namespace Penumbra.UI.ModsTab;
public class ModPanelDescriptionTab(
- ModFileSystemSelector selector,
+ ModSelection selection,
TutorialService tutorial,
ModManager modManager,
PredefinedTagManager predefinedTagsConfig)
: ITab
{
-
public ReadOnlySpan Label
=> "Description"u8;
@@ -32,24 +32,22 @@ public class ModPanelDescriptionTab(
: (false, 0);
var tagIdx = TagButtons.Draw("Local Tags: "u8,
"Custom tags you can set personally that will not be exported to the mod data but only set for you.\n"u8
- + "If the mod already contains a local tag in its own tags, the local tag will be ignored."u8, selector.Selected!.LocalTags,
+ + "If the mod already contains a local tag in its own tags, the local tag will be ignored."u8, selection.Mod!.LocalTags,
out var editedTag, rightEndOffset: predefinedTagButtonOffset);
tutorial.OpenTutorial(BasicTutorialSteps.Tags);
if (tagIdx >= 0)
- modManager.DataEditor.ChangeLocalTag(selector.Selected!, tagIdx, editedTag);
+ modManager.DataEditor.ChangeLocalTag(selection.Mod!, tagIdx, editedTag);
if (predefinedTagsEnabled)
- predefinedTagsConfig.DrawAddFromSharedTagsAndUpdateTags(selector.Selected!.LocalTags, selector.Selected!.ModTags, true,
- selector.Selected!);
+ predefinedTagsConfig.DrawAddFromSharedTagsAndUpdateTags(selection.Mod!.LocalTags, selection.Mod!.ModTags, true, selection.Mod!);
- if (selector.Selected!.ModTags.Count > 0)
+ if (selection.Mod!.ModTags.Count > 0)
TagButtons.Draw("Mod Tags: "u8, "Tags assigned by the mod creator and saved with the mod data. To edit these, look at Edit Mod."u8,
- selector.Selected!.ModTags, out _, false,
- Im.Font.CalculateSize("Local "u8).X - Im.Font.CalculateSize("Mod "u8).X);
+ selection.Mod!.ModTags, out _, false, Im.Font.CalculateSize("Local "u8).X - Im.Font.CalculateSize("Mod "u8).X);
Im.ScaledDummy(2, 2);
Im.Separator();
- Im.TextWrapped(selector.Selected!.Description);
+ Im.TextWrapped(selection.Mod!.Description);
}
}
diff --git a/Penumbra/UI/ModsTab/ModPanelEditTab.cs b/Penumbra/UI/ModsTab/ModPanelEditTab.cs
index ca833aa0..1917e81d 100644
--- a/Penumbra/UI/ModsTab/ModPanelEditTab.cs
+++ b/Penumbra/UI/ModsTab/ModPanelEditTab.cs
@@ -12,7 +12,6 @@ namespace Penumbra.UI.ModsTab;
public class ModPanelEditTab(
ModManager modManager,
- ModFileSystemSelector selector,
ModFileSystem fileSystem,
Services.MessageService messager,
FilenameService filenames,
@@ -24,8 +23,8 @@ public class ModPanelEditTab(
AddGroupDrawer addGroupDrawer)
: ITab
{
- private ModFileSystem.Leaf _leaf = null!;
- private Mod _mod = null!;
+ private IFileSystemData _leaf = null!;
+ private Mod _mod = null!;
public ReadOnlySpan Label
=> "Edit Mod"u8;
@@ -39,8 +38,8 @@ public class ModPanelEditTab(
if (!child)
return;
- _leaf = selector.SelectedLeaf!;
- _mod = selector.Selected!;
+ _leaf = (IFileSystemData)fileSystem.Selection.Selection!;
+ _mod = _leaf.Value;
EditButtons();
EditRegularMeta();
@@ -48,7 +47,7 @@ public class ModPanelEditTab(
EditLocalData();
UiHelpers.DefaultLineSpace();
- if (Input.Text("Mod Path"u8, Input.Path, Input.None, _leaf.FullName(), out var newPath, UiHelpers.InputTextWidth.X))
+ if (Input.Text("Mod Path"u8, Input.Path, Input.None, _leaf.FullPath, out var newPath, UiHelpers.InputTextWidth.X))
try
{
fileSystem.RenameAndMove(_leaf, newPath);
@@ -71,8 +70,7 @@ public class ModPanelEditTab(
modManager.DataEditor.ChangeModTag(_mod, tagIdx, editedTag);
if (sharedTagsEnabled)
- predefinedTagManager.DrawAddFromSharedTagsAndUpdateTags(selector.Selected!.LocalTags, selector.Selected!.ModTags, false,
- selector.Selected!);
+ predefinedTagManager.DrawAddFromSharedTagsAndUpdateTags(_mod.LocalTags, _mod.ModTags, false, _mod);
UiHelpers.DefaultLineSpace();
diff --git a/Penumbra/UI/ModsTab/ModSearchStringSplitter.cs b/Penumbra/UI/ModsTab/ModSearchStringSplitter.cs
deleted file mode 100644
index d122122f..00000000
--- a/Penumbra/UI/ModsTab/ModSearchStringSplitter.cs
+++ /dev/null
@@ -1,138 +0,0 @@
-using OtterGui.Filesystem;
-using OtterGui.Filesystem.Selector;
-using Penumbra.Mods;
-using Penumbra.Mods.Manager;
-using Penumbra.UI.Classes;
-
-namespace Penumbra.UI.ModsTab;
-
-public enum ModSearchType : byte
-{
- Default = 0,
- ChangedItem,
- Tag,
- Name,
- Author,
- Category,
-}
-
-public sealed class ModSearchStringSplitter : SearchStringSplitter.Leaf, ModSearchStringSplitter.Entry>
-{
- public readonly struct Entry : ISplitterEntry
- {
- public string Needle { get; init; }
- public ModSearchType Type { get; init; }
- public ChangedItemIconFlag IconFlagFilter { get; init; }
-
- public bool Contains(Entry other)
- {
- if (Type != other.Type)
- return false;
- if (Type is ModSearchType.Category)
- return IconFlagFilter == other.IconFlagFilter;
-
- return Needle.Contains(other.Needle);
- }
- }
-
- protected override bool ConvertToken(char token, out ModSearchType val)
- {
- val = token switch
- {
- 'c' or 'C' => ModSearchType.ChangedItem,
- 't' or 'T' => ModSearchType.Tag,
- 'n' or 'N' => ModSearchType.Name,
- 'a' or 'A' => ModSearchType.Author,
- 's' or 'S' => ModSearchType.Category,
- _ => ModSearchType.Default,
- };
- return val is not ModSearchType.Default;
- }
-
- protected override bool AllowsNone(ModSearchType val)
- => val switch
- {
- ModSearchType.Author => true,
- ModSearchType.ChangedItem => true,
- ModSearchType.Tag => true,
- ModSearchType.Category => true,
- _ => false,
- };
-
- protected override void PostProcessing()
- {
- base.PostProcessing();
- HandleList(General);
- HandleList(Forced);
- HandleList(Negated);
- return;
-
- static void HandleList(List list)
- {
- for (var i = 0; i < list.Count; ++i)
- {
- var entry = list[i];
- if (entry.Type is not ModSearchType.Category)
- continue;
-
- if (ChangedItemDrawer.TryParsePartial(entry.Needle, out var icon))
- list[i] = entry with
- {
- IconFlagFilter = icon,
- Needle = string.Empty,
- };
- else
- list.RemoveAt(i--);
- }
- }
- }
-
- public bool IsVisible(ModFileSystem.Folder folder)
- {
- switch (State)
- {
- case FilterState.NoFilters: return true;
- case FilterState.NoMatches: return false;
- }
-
- var fullName = folder.FullName();
- return Forced.All(i => MatchesName(i, folder.Name, fullName, false))
- && !Negated.Any(i => MatchesName(i, folder.Name, fullName, true))
- && (General.Count is 0 || General.Any(i => MatchesName(i, folder.Name, fullName, false)));
- }
-
- protected override bool Matches(Entry entry, ModFileSystem.Leaf leaf)
- => entry.Type switch
- {
- ModSearchType.Default => leaf.FullName().AsSpan().Contains(entry.Needle, StringComparison.OrdinalIgnoreCase)
- || leaf.Value.Name.AsSpan().Contains(entry.Needle, StringComparison.OrdinalIgnoreCase),
- ModSearchType.ChangedItem => leaf.Value.LowerChangedItemsString.AsSpan().Contains(entry.Needle, StringComparison.Ordinal),
- ModSearchType.Tag => leaf.Value.AllTagsLower.AsSpan().Contains(entry.Needle, StringComparison.Ordinal),
- ModSearchType.Name => leaf.Value.Name.AsSpan().Contains(entry.Needle, StringComparison.OrdinalIgnoreCase),
- ModSearchType.Author => leaf.Value.Author.AsSpan().Contains(entry.Needle, StringComparison.OrdinalIgnoreCase),
- ModSearchType.Category => leaf.Value.ChangedItems.Any(p => (p.Value.Icon.ToFlag() & entry.IconFlagFilter) is not 0),
- _ => true,
- };
-
- protected override bool MatchesNone(ModSearchType type, bool negated, ModFileSystem.Leaf haystack)
- => type switch
- {
- ModSearchType.Author when negated => haystack.Value.Author.Length > 0,
- ModSearchType.Author => haystack.Value.Author.Length is 0,
- ModSearchType.ChangedItem when negated => haystack.Value.LowerChangedItemsString.Length > 0,
- ModSearchType.ChangedItem => haystack.Value.LowerChangedItemsString.Length is 0,
- ModSearchType.Tag when negated => haystack.Value.AllTagsLower.Length > 0,
- ModSearchType.Tag => haystack.Value.AllTagsLower.Length is 0,
- ModSearchType.Category when negated => haystack.Value.ChangedItems.Count > 0,
- ModSearchType.Category => haystack.Value.ChangedItems.Count is 0,
- _ => true,
- };
-
- private static bool MatchesName(Entry entry, ReadOnlySpan name, ReadOnlySpan fullName, bool defaultValue)
- => entry.Type switch
- {
- ModSearchType.Default => fullName.Contains(entry.Needle, StringComparison.OrdinalIgnoreCase),
- ModSearchType.Name => name.Contains(entry.Needle, StringComparison.OrdinalIgnoreCase),
- _ => defaultValue,
- };
-}
diff --git a/Penumbra/UI/ModsTab/ModTab.cs b/Penumbra/UI/ModsTab/ModTab.cs
new file mode 100644
index 00000000..7750e628
--- /dev/null
+++ b/Penumbra/UI/ModsTab/ModTab.cs
@@ -0,0 +1,42 @@
+using ImSharp;
+using Luna;
+using Penumbra.Api.Enums;
+using Penumbra.UI.Classes;
+using Penumbra.UI.ModsTab.Selector;
+
+namespace Penumbra.UI.ModsTab;
+
+public sealed class ModTab : TwoPanelLayout, ITab
+{
+ private readonly UiConfig _uiConfig;
+
+ public override ReadOnlySpan Label
+ => "Mods"u8;
+
+ public ModTab(ModFileSystemDrawer drawer, ModPanel panel, CollectionSelectHeader collectionHeader, RedrawFooter redrawFooter,
+ UiConfig uiConfig)
+ {
+ _uiConfig = uiConfig;
+ LeftHeader = drawer.Header;
+ LeftFooter = drawer.Footer;
+ LeftPanel = drawer;
+ RightPanel = panel;
+ RightHeader = collectionHeader;
+ RightFooter = redrawFooter;
+ }
+
+ public void DrawContent()
+ => Draw(_uiConfig.ModTabScale);
+
+ public TabType Identifier
+ => TabType.Mods;
+
+ protected override void SetWidth(float width, ScalingMode mode)
+ => _uiConfig.ModTabScale = new TwoPanelWidth(width, mode);
+
+ protected override float MinimumWidth
+ => LeftFooter.MinimumWidth;
+
+ protected override float MaximumWidth
+ => Im.Window.Width - 500 * Im.Style.GlobalScale;
+}
diff --git a/Penumbra/UI/ModsTab/MultiModPanel.cs b/Penumbra/UI/ModsTab/MultiModPanel.cs
index 52f1eef1..508c7cfa 100644
--- a/Penumbra/UI/ModsTab/MultiModPanel.cs
+++ b/Penumbra/UI/ModsTab/MultiModPanel.cs
@@ -1,157 +1,153 @@
-using Dalamud.Interface;
-using ImSharp;
-using Luna;
-using Penumbra.Mods;
-using Penumbra.Mods.Manager;
-
-namespace Penumbra.UI.ModsTab;
-
-public class MultiModPanel(ModFileSystemSelector selector, ModDataEditor editor, PredefinedTagManager tagManager) : IUiService
-{
- public void Draw()
- {
- if (selector.SelectedPaths.Count == 0)
- return;
-
- Im.Line.New();
- var treeNodePos = Im.Cursor.Position;
- var numLeaves = DrawModList();
- DrawCounts(treeNodePos, numLeaves);
- DrawMultiTagger();
- }
-
- private void DrawCounts(Vector2 treeNodePos, int numLeaves)
- {
- var startPos = Im.Cursor.Position;
- var numFolders = selector.SelectedPaths.Count - numLeaves;
- Utf8StringHandler text = (numLeaves, numFolders) switch
- {
- (0, 0) => StringU8.Empty, // should not happen
- (> 0, 0) => $"{numLeaves} Mods",
- (0, > 0) => $"{numFolders} Folders",
- _ => $"{numLeaves} Mods, {numFolders} Folders",
- };
- Im.Cursor.Position = treeNodePos;
- ImEx.TextRightAligned(ref text);
- Im.Cursor.Position = startPos;
- }
-
- private int DrawModList()
- {
- using var tree = Im.Tree.Node("Currently Selected Objects###Selected"u8,
- TreeNodeFlags.DefaultOpen | TreeNodeFlags.NoTreePushOnOpen);
- Im.Separator();
-
-
- if (!tree)
- return selector.SelectedPaths.Count(l => l is ModFileSystem.Leaf);
-
- var sizeType = new Vector2(Im.Style.FrameHeight);
- var availableSizePercent = (Im.ContentRegion.Available.X - sizeType.X - 4 * Im.Style.CellPadding.X) / 100;
- var sizeMods = availableSizePercent * 35;
- var sizeFolders = availableSizePercent * 65;
-
- var leaves = 0;
- using (var table = Im.Table.Begin("mods"u8, 3, TableFlags.RowBackground))
- {
- if (!table)
- return selector.SelectedPaths.Count(l => l is ModFileSystem.Leaf);
-
- table.SetupColumn("type"u8, TableColumnFlags.WidthFixed, sizeType.X);
- table.SetupColumn("mod"u8, TableColumnFlags.WidthFixed, sizeMods);
- table.SetupColumn("path"u8, TableColumnFlags.WidthFixed, sizeFolders);
-
- var i = 0;
- foreach (var (fullName, path) in selector.SelectedPaths.Select(p => (p.FullName(), p))
- .OrderBy(p => p.Item1, StringComparer.OrdinalIgnoreCase))
- {
- using var id = Im.Id.Push(i++);
- var (icon, text) = path is ModFileSystem.Leaf l
- ? (FontAwesomeIcon.FileCircleMinus.Icon(), l.Value.Name)
- : (FontAwesomeIcon.FolderMinus.Icon(), string.Empty);
- table.NextColumn();
- if (ImEx.Icon.Button(icon, "Remove from selection."u8, false, sizeType))
- selector.RemovePathFromMultiSelection(path);
+using Dalamud.Interface;
+using ImSharp;
+using Luna;
+using Penumbra.Mods;
+using Penumbra.Mods.Manager;
- table.DrawFrameColumn(text);
- table.DrawFrameColumn(fullName);
- if (path is ModFileSystem.Leaf)
- ++leaves;
- }
- }
-
- Im.Separator();
- return leaves;
- }
-
- private string _tag = string.Empty;
- private readonly List _addMods = [];
- private readonly List<(Mod, int)> _removeMods = [];
-
- private void DrawMultiTagger()
- {
- var width = ImEx.ScaledVector(150, 0);
- ImEx.TextFrameAligned("Multi Tagger:"u8);
- Im.Line.Same();
-
- var predefinedTagsEnabled = tagManager.Enabled;
- var inputWidth = predefinedTagsEnabled
- ? Im.ContentRegion.Available.X - 2 * width.X - 3 * Im.Style.ItemInnerSpacing.X - Im.Style.FrameHeight
- : Im.ContentRegion.Available.X - 2 * (width.X + Im.Style.ItemInnerSpacing.X);
- Im.Item.SetNextWidth(inputWidth);
- Im.Input.Text("##tag"u8, ref _tag, "Local Tag Name..."u8);
-
- UpdateTagCache();
- Utf8StringHandler label = _addMods.Count > 0
- ? $"Add to {_addMods.Count} Mods"
- : "Add";
- Utf8StringHandler tooltip = _addMods.Count is 0
- ? _tag.Length is 0
- ? "No tag specified."
- : $"All mods selected already contain the tag \"{_tag}\", either locally or as mod data."
- : $"Add the tag \"{_tag}\" to {_addMods.Count} mods as a local tag:\n\n\t{string.Join("\n\t", _addMods.Select(m => m.Name))}";
- Im.Line.SameInner();
- if (ImEx.Button(label, width, tooltip, _addMods.Count is 0))
- foreach (var mod in _addMods)
- editor.ChangeLocalTag(mod, mod.LocalTags.Count, _tag);
-
- label = _removeMods.Count > 0
- ? $"Remove from {_removeMods.Count} Mods"
- : "Remove";
- tooltip = _removeMods.Count is 0
- ? _tag.Length is 0
- ? "No tag specified."
- : $"No selected mod contains the tag \"{_tag}\" locally."
- : $"Remove the local tag \"{_tag}\" from {_removeMods.Count} mods:\n\n\t{string.Join("\n\t", _removeMods.Select(m => m.Item1.Name))}";
- Im.Line.SameInner();
- if (ImEx.Button(label, width, tooltip, _removeMods.Count is 0))
- foreach (var (mod, index) in _removeMods)
- editor.ChangeLocalTag(mod, index, string.Empty);
-
- if (predefinedTagsEnabled)
- {
- Im.Line.SameInner();
- tagManager.DrawToggleButton();
- tagManager.DrawListMulti(selector.SelectedPaths.OfType().Select(l => l.Value));
- }
-
- Im.Separator();
- }
-
- private void UpdateTagCache()
- {
- _addMods.Clear();
- _removeMods.Clear();
- if (_tag.Length == 0)
- return;
-
- foreach (var leaf in selector.SelectedPaths.OfType())
- {
- var index = leaf.Value.LocalTags.IndexOf(_tag);
- if (index >= 0)
- _removeMods.Add((leaf.Value, index));
- else if (!leaf.Value.ModTags.Contains(_tag))
- _addMods.Add(leaf.Value);
- }
- }
-}
+namespace Penumbra.UI.ModsTab;
+
+public class MultiModPanel(ModFileSystem fileSystem, ModDataEditor editor, PredefinedTagManager tagManager) : IUiService
+{
+ public void Draw()
+ {
+ if (fileSystem.Selection.OrderedNodes.Count is 0)
+ return;
+
+ Im.Line.New();
+ var treeNodePos = Im.Cursor.Position;
+ DrawModList();
+ DrawCounts(treeNodePos);
+ DrawMultiTagger();
+ }
+
+ private void DrawCounts(Vector2 treeNodePos)
+ {
+ var startPos = Im.Cursor.Position;
+ var numLeaves = fileSystem.Selection.DataNodes.Count;
+ var numFolders = fileSystem.Selection.Folders.Count;
+ Utf8StringHandler text = (numLeaves, numFolders) switch
+ {
+ (0, 0) => StringU8.Empty, // should not happen
+ (> 0, 0) => $"{numLeaves} Mods",
+ (0, > 0) => $"{numFolders} Folders",
+ _ => $"{numLeaves} Mods, {numFolders} Folders",
+ };
+ Im.Cursor.Position = treeNodePos;
+ ImEx.TextRightAligned(ref text);
+ Im.Cursor.Position = startPos;
+ }
+
+ private void DrawModList()
+ {
+ using var tree = Im.Tree.Node("Currently Selected Objects###Selected"u8,
+ TreeNodeFlags.DefaultOpen | TreeNodeFlags.NoTreePushOnOpen);
+ Im.Separator();
+
+ if (!tree)
+ return;
+
+ var sizeType = new Vector2(Im.Style.FrameHeight);
+ var availableSizePercent = (Im.ContentRegion.Available.X - sizeType.X - 4 * Im.Style.CellPadding.X) / 100;
+ var sizeMods = availableSizePercent * 35;
+ var sizeFolders = availableSizePercent * 65;
+
+ using (var table = Im.Table.Begin("mods"u8, 3, TableFlags.RowBackground))
+ {
+ if (!table)
+ return;
+
+ table.SetupColumn("type"u8, TableColumnFlags.WidthFixed, sizeType.X);
+ table.SetupColumn("mod"u8, TableColumnFlags.WidthFixed, sizeMods);
+ table.SetupColumn("path"u8, TableColumnFlags.WidthFixed, sizeFolders);
+
+ var i = 0;
+ foreach (var node in fileSystem.Selection.OrderedNodes.OrderBy(p => p.FullPath, StringComparer.OrdinalIgnoreCase))
+ {
+ using var id = Im.Id.Push(i++);
+ var (icon, text) = node is IFileSystemData l
+ ? (FontAwesomeIcon.FileCircleMinus.Icon(), l.Value.Name)
+ : (FontAwesomeIcon.FolderMinus.Icon(), string.Empty);
+ table.NextColumn();
+ if (ImEx.Icon.Button(icon, "Remove from selection."u8, false, sizeType))
+ fileSystem.Selection.RemoveFromSelection(node);
+
+ table.DrawFrameColumn(text);
+ table.DrawFrameColumn(node.FullPath);
+ }
+ }
+
+ Im.Separator();
+ }
+
+ private string _tag = string.Empty;
+ private readonly List _addMods = [];
+ private readonly List<(Mod, int)> _removeMods = [];
+
+ private void DrawMultiTagger()
+ {
+ var width = ImEx.ScaledVector(150, 0);
+ ImEx.TextFrameAligned("Multi Tagger:"u8);
+ Im.Line.Same();
+
+ var predefinedTagsEnabled = tagManager.Enabled;
+ var inputWidth = predefinedTagsEnabled
+ ? Im.ContentRegion.Available.X - 2 * width.X - 3 * Im.Style.ItemInnerSpacing.X - Im.Style.FrameHeight
+ : Im.ContentRegion.Available.X - 2 * (width.X + Im.Style.ItemInnerSpacing.X);
+ Im.Item.SetNextWidth(inputWidth);
+ Im.Input.Text("##tag"u8, ref _tag, "Local Tag Name..."u8);
+
+ UpdateTagCache();
+ Utf8StringHandler label = _addMods.Count > 0
+ ? $"Add to {_addMods.Count} Mods"
+ : "Add";
+ Utf8StringHandler tooltip = _addMods.Count is 0
+ ? _tag.Length is 0
+ ? "No tag specified."
+ : $"All mods selected already contain the tag \"{_tag}\", either locally or as mod data."
+ : $"Add the tag \"{_tag}\" to {_addMods.Count} mods as a local tag:\n\n\t{string.Join("\n\t", _addMods.Select(m => m.Name))}";
+ Im.Line.SameInner();
+ if (ImEx.Button(label, width, tooltip, _addMods.Count is 0))
+ foreach (var mod in _addMods)
+ editor.ChangeLocalTag(mod, mod.LocalTags.Count, _tag);
+
+ label = _removeMods.Count > 0
+ ? $"Remove from {_removeMods.Count} Mods"
+ : "Remove";
+ tooltip = _removeMods.Count is 0
+ ? _tag.Length is 0
+ ? "No tag specified."
+ : $"No selected mod contains the tag \"{_tag}\" locally."
+ : $"Remove the local tag \"{_tag}\" from {_removeMods.Count} mods:\n\n\t{string.Join("\n\t", _removeMods.Select(m => m.Item1.Name))}";
+ Im.Line.SameInner();
+ if (ImEx.Button(label, width, tooltip, _removeMods.Count is 0))
+ foreach (var (mod, index) in _removeMods)
+ editor.ChangeLocalTag(mod, index, string.Empty);
+
+ if (predefinedTagsEnabled)
+ {
+ Im.Line.SameInner();
+ tagManager.DrawToggleButton();
+ tagManager.DrawListMulti(fileSystem.Selection.DataNodes.Select(l => (Mod)l.Value));
+ }
+
+ Im.Separator();
+ }
+
+ private void UpdateTagCache()
+ {
+ _addMods.Clear();
+ _removeMods.Clear();
+ if (_tag.Length is 0)
+ return;
+
+ foreach (var leaf in fileSystem.Selection.DataNodes)
+ {
+ var mod = (Mod)leaf.Value;
+ var index = mod.LocalTags.IndexOf(_tag);
+ if (index >= 0)
+ _removeMods.Add((mod, index));
+ else if (!mod.ModTags.Contains(_tag))
+ _addMods.Add(mod);
+ }
+ }
+}
diff --git a/Penumbra/UI/ModsTab/Selector/Buttons/AddNewModButton.cs b/Penumbra/UI/ModsTab/Selector/Buttons/AddNewModButton.cs
index 1b6e8404..78e7cc3a 100644
--- a/Penumbra/UI/ModsTab/Selector/Buttons/AddNewModButton.cs
+++ b/Penumbra/UI/ModsTab/Selector/Buttons/AddNewModButton.cs
@@ -21,74 +21,12 @@ public sealed class AddNewModButton(ModFileSystemDrawer drawer) : BaseIconButton
///
public override void OnClick()
- => Im.Popup.Open("Create New Mod"u8);
+ => Im.Popup.Open("newMod"u8);
///
protected override void PostDraw()
{
- if (!InputPopup.OpenName("Create New Mod"u8, out var newModName))
- return;
-
- if (drawer.ModManager.Creator.CreateEmptyMod(drawer.ModManager.BasePath, newModName) is { } directory)
- drawer.ModManager.AddMod(directory, false);
- }
-}
-
-/// The button to import a mod.
-/// The file system drawer.
-public sealed class ImportModButton(ModFileSystemDrawer drawer) : BaseIconButton
-{
- ///
- public override AwesomeIcon Icon
- => LunaStyle.AddObjectIcon;
-
- ///
- public override bool HasTooltip
- => true;
-
- ///
- public override void DrawTooltip()
- => Im.Text("Create a new, empty mod of a given name."u8);
-
- ///
- public override void OnClick()
- => Im.Popup.Open("Create New Mod"u8);
-
- ///
- protected override void PostDraw()
- {
- if (!InputPopup.OpenName("Create New Mod"u8, out var newModName))
- return;
-
- if (drawer.ModManager.Creator.CreateEmptyMod(drawer.ModManager.BasePath, newModName) is { } directory)
- drawer.ModManager.AddMod(directory, false);
- }
-}
-
-/// The button to import a mod.
-/// The file system drawer.
-public sealed class DeleteSelectionButton(ModFileSystemDrawer drawer) : BaseIconButton
-{
- ///
- public override AwesomeIcon Icon
- => LunaStyle.AddObjectIcon;
-
- ///
- public override bool HasTooltip
- => true;
-
- ///
- public override void DrawTooltip()
- => Im.Text("Create a new, empty mod of a given name."u8);
-
- ///
- public override void OnClick()
- => Im.Popup.Open("Create New Mod"u8);
-
- ///
- protected override void PostDraw()
- {
- if (!InputPopup.OpenName("Create New Mod"u8, out var newModName))
+ if (!InputPopup.OpenName("newMod"u8, out var newModName))
return;
if (drawer.ModManager.Creator.CreateEmptyMod(drawer.ModManager.BasePath, newModName) is { } directory)
diff --git a/Penumbra/UI/ModsTab/Selector/Buttons/ClearQuickMoveFoldersButtons.cs b/Penumbra/UI/ModsTab/Selector/Buttons/ClearQuickMoveFoldersButtons.cs
new file mode 100644
index 00000000..880eb76b
--- /dev/null
+++ b/Penumbra/UI/ModsTab/Selector/Buttons/ClearQuickMoveFoldersButtons.cs
@@ -0,0 +1,50 @@
+using ImSharp;
+using Luna;
+
+namespace Penumbra.UI.ModsTab.Selector;
+
+public sealed class ClearQuickMoveFoldersButtons(ModFileSystemDrawer drawer) : BaseButton
+{
+ public override ReadOnlySpan Label
+ => throw new NotImplementedException();
+
+ public override bool DrawMenuItem()
+ {
+ if (drawer.Config.QuickMoveFolder1.Length > 0)
+ {
+ if (Im.Menu.Item("Clear Quick Move Folder #1"u8))
+ {
+ drawer.Config.QuickMoveFolder1 = string.Empty;
+ drawer.Config.Save();
+ }
+
+ Im.Tooltip.OnHover($"Clear the current quick move assignment of {drawer.Config.QuickMoveFolder1}.");
+ }
+
+
+ if (drawer.Config.QuickMoveFolder2.Length > 0)
+ {
+ if (Im.Menu.Item("Clear Quick Move Folder #2"u8))
+ {
+ drawer.Config.QuickMoveFolder2 = string.Empty;
+ drawer.Config.Save();
+ }
+
+ Im.Tooltip.OnHover($"Clear the current quick move assignment of {drawer.Config.QuickMoveFolder2}.");
+ }
+
+
+ if (drawer.Config.QuickMoveFolder3.Length > 0)
+ {
+ if (Im.Menu.Item("Clear Quick Move Folder #3"u8))
+ {
+ drawer.Config.QuickMoveFolder3 = string.Empty;
+ drawer.Config.Save();
+ }
+
+ Im.Tooltip.OnHover($"Clear the current quick move assignment of {drawer.Config.QuickMoveFolder3}.");
+ }
+
+ return false;
+ }
+}
diff --git a/Penumbra/UI/ModsTab/Selector/Buttons/DeleteSelectionButton.cs b/Penumbra/UI/ModsTab/Selector/Buttons/DeleteSelectionButton.cs
new file mode 100644
index 00000000..dfba8e61
--- /dev/null
+++ b/Penumbra/UI/ModsTab/Selector/Buttons/DeleteSelectionButton.cs
@@ -0,0 +1,53 @@
+using ImSharp;
+using Luna;
+using Penumbra.Mods;
+
+namespace Penumbra.UI.ModsTab.Selector;
+
+/// The button to import a mod.
+/// The file system drawer.
+public sealed class DeleteSelectionButton(ModFileSystemDrawer drawer) : BaseIconButton
+{
+ ///
+ public override AwesomeIcon Icon
+ => LunaStyle.DeleteIcon;
+
+ ///
+ public override bool HasTooltip
+ => true;
+
+ ///
+ public override void DrawTooltip()
+ {
+ var anySelected = drawer.FileSystem.Selection.DataNodes.Count > 0;
+ var modifier = Enabled;
+
+ Im.Text(anySelected ? "Delete the currently selected mods entirely from your drive\nThis can not be undone."u8 : "No mods selected."u8);
+ if (!modifier)
+ Im.Text($"Hold {drawer.Config.DeleteModModifier} while clicking to delete the mods.");
+ }
+
+ ///
+ public override bool Enabled
+ => drawer.Config.DeleteModModifier.IsActive() && drawer.FileSystem.Selection.DataNodes.Count > 0;
+
+ ///
+ public override void OnClick()
+ {
+ foreach (var node in drawer.FileSystem.Selection.DataNodes.ToArray())
+ {
+ if (node.GetValue() is { } mod)
+ drawer.ModManager.DeleteMod(mod);
+ }
+ }
+
+ ///
+ protected override void PostDraw()
+ {
+ if (!InputPopup.OpenName("Create New Mod"u8, out var newModName))
+ return;
+
+ if (drawer.ModManager.Creator.CreateEmptyMod(drawer.ModManager.BasePath, newModName) is { } directory)
+ drawer.ModManager.AddMod(directory, false);
+ }
+}
diff --git a/Penumbra/UI/ModsTab/Selector/Buttons/HelpButton.cs b/Penumbra/UI/ModsTab/Selector/Buttons/HelpButton.cs
new file mode 100644
index 00000000..db76721f
--- /dev/null
+++ b/Penumbra/UI/ModsTab/Selector/Buttons/HelpButton.cs
@@ -0,0 +1,91 @@
+using ImSharp;
+using Luna;
+using Penumbra.UI.Classes;
+
+namespace Penumbra.UI.ModsTab.Selector;
+
+/// The button to open the help popup.
+public sealed class HelpButton(ModFileSystemDrawer drawer) : BaseIconButton
+{
+ ///
+ public override AwesomeIcon Icon
+ => LunaStyle.InfoIcon;
+
+ ///
+ public override bool HasTooltip
+ => true;
+
+ ///
+ public override void DrawTooltip()
+ => Im.Text("Open extended help."u8);
+
+ ///
+ public override void OnClick()
+ => Im.Popup.Open("ExHelp"u8);
+
+ ///
+ protected override void PostDraw()
+ {
+ drawer.Tutorial.OpenTutorial(BasicTutorialSteps.AdvancedHelp);
+ ImEx.HelpPopup("ExtendedHelp"u8, new Vector2(1000 * Im.Style.GlobalScale, 38.5f * Im.Style.TextHeightWithSpacing), PopupContent);
+ }
+
+ private void PopupContent()
+ {
+ Im.Line.New();
+ Im.Text("Mod Management"u8);
+ Im.BulletText("You can create empty mods or import mods with the buttons in this row."u8);
+ using var indent = Im.Indent();
+ Im.BulletText("Supported formats for import are: .ttmp, .ttmp2, .pmp, .pcp."u8);
+ Im.BulletText(
+ "You can also support .zip, .7z or .rar archives, but only if they already contain Penumbra-styled mods with appropriate metadata."u8);
+ indent.Unindent();
+ Im.BulletText("You can also create empty mod folders and delete mods."u8);
+ Im.BulletText(
+ "For further editing of mods, select them and use the Edit Mod tab in the panel or the Advanced Editing popup."u8);
+ Im.Line.New();
+ Im.Text("Mod Selector"u8);
+ Im.BulletText("Select a mod to obtain more information or change settings."u8);
+ Im.BulletText("Names are colored according to your config and their current state in the collection:"u8);
+ indent.Indent();
+ Im.BulletText("enabled in the current collection."u8, ColorId.EnabledMod.Value());
+ Im.BulletText("disabled in the current collection."u8, ColorId.DisabledMod.Value());
+ Im.BulletText("enabled due to inheritance from another collection."u8, ColorId.InheritedMod.Value());
+ Im.BulletText("disabled due to inheritance from another collection."u8, ColorId.InheritedDisabledMod.Value());
+ Im.BulletText("unconfigured in all inherited collections."u8, ColorId.UndefinedMod.Value());
+ Im.BulletText("enabled and conflicting with another enabled Mod, but on different priorities (i.e. the conflict is solved)."u8,
+ ColorId.HandledConflictMod.Value());
+ Im.BulletText("enabled and conflicting with another enabled Mod on the same priority."u8, ColorId.ConflictingMod.Value());
+ Im.BulletText("expanded mod folder."u8, ColorId.FolderExpanded.Value());
+ Im.BulletText("collapsed mod folder"u8, ColorId.FolderCollapsed.Value());
+ indent.Unindent();
+ Im.BulletText("Middle-click a mod to disable it if it is enabled or enable it if it is disabled."u8);
+ indent.Indent();
+ Im.BulletText(
+ $"Holding {drawer.Config.DeleteModModifier.ForcedModifier(new DoubleModifier(ModifierHotkey.Control, ModifierHotkey.Shift))} while middle-clicking lets it inherit, discarding settings.");
+ indent.Unindent();
+ Im.BulletText("Right-click a mod to enter its sort order, which is its name by default, possibly with a duplicate number."u8);
+ indent.Indent();
+ Im.BulletText("A sort order differing from the mods name will not be displayed, it will just be used for ordering."u8);
+ Im.BulletText(
+ "If the sort order string contains Forward-Slashes ('/'), the preceding substring will be turned into folders automatically."u8);
+ indent.Unindent();
+ Im.BulletText(
+ "You can drag and drop mods and subfolders into existing folders. Dropping them onto mods is the same as dropping them onto the parent of the mod."u8);
+ indent.Indent();
+ Im.BulletText(
+ "You can select multiple mods and folders by holding Control while clicking them, and then drag all of them at once."u8);
+ Im.BulletText(
+ "Selected mods inside an also selected folder will be ignored when dragging and move inside their folder instead of directly into the target."u8);
+ indent.Unindent();
+ Im.BulletText("Right-clicking a folder opens a context menu."u8);
+ Im.BulletText("Right-clicking empty space allows you to expand or collapse all folders at once."u8);
+ Im.BulletText("Use the Filter Mods... input at the top to filter the list for mods whose name or path contain the text."u8);
+ indent.Indent();
+ Im.BulletText("You can enter n:[string] to filter only for names, without path."u8);
+ Im.BulletText("You can enter c:[string] to filter for Changed Items instead."u8);
+ Im.BulletText("You can enter a:[string] to filter for Mod Authors instead."u8);
+ indent.Unindent();
+ Im.BulletText("Use the expandable menu beside the input to filter for mods fulfilling specific criteria."u8);
+ }
+}
diff --git a/Penumbra/UI/ModsTab/Selector/Buttons/ImportModButton.cs b/Penumbra/UI/ModsTab/Selector/Buttons/ImportModButton.cs
new file mode 100644
index 00000000..1ce9e5f4
--- /dev/null
+++ b/Penumbra/UI/ModsTab/Selector/Buttons/ImportModButton.cs
@@ -0,0 +1,45 @@
+using ImSharp;
+using Luna;
+using Penumbra.UI.Classes;
+
+namespace Penumbra.UI.ModsTab.Selector;
+
+/// The button to import a mod.
+public sealed class ImportModButton(ModFileSystemDrawer drawer) : BaseIconButton
+{
+ ///
+ public override AwesomeIcon Icon
+ => LunaStyle.ImportIcon;
+
+ ///
+ public override bool HasTooltip
+ => true;
+
+ ///
+ public override void DrawTooltip()
+ => Im.Text("Import one or multiple mods from Tex Tools Mod Pack Files or Penumbra Mod Pack Files."u8);
+
+ ///
+ public override void OnClick()
+ {
+ var modPath = drawer.Config.DefaultModImportPath.Length > 0
+ ? drawer.Config.DefaultModImportPath
+ : drawer.Config.ModDirectory.Length > 0
+ ? drawer.Config.ModDirectory
+ : null;
+
+ drawer.FileService.OpenFilePicker("Import Mod Pack",
+ "Mod Packs{.ttmp,.ttmp2,.pmp,.pcp},TexTools Mod Packs{.ttmp,.ttmp2},Penumbra Mod Packs{.pmp,.pcp},Archives{.zip,.7z,.rar},Penumbra Character Packs{.pcp}",
+ (s, f) =>
+ {
+ if (!s)
+ return;
+
+ drawer.ModImport.AddUnpack(f);
+ }, 0, modPath, drawer.Config.AlwaysOpenDefaultImport);
+ }
+
+ ///
+ protected override void PostDraw()
+ => drawer.Tutorial.OpenTutorial(BasicTutorialSteps.ModImport);
+}
diff --git a/Penumbra/UI/ModsTab/Selector/Buttons/MoveToQuickMoveFoldersButtons.cs b/Penumbra/UI/ModsTab/Selector/Buttons/MoveToQuickMoveFoldersButtons.cs
new file mode 100644
index 00000000..edd6e004
--- /dev/null
+++ b/Penumbra/UI/ModsTab/Selector/Buttons/MoveToQuickMoveFoldersButtons.cs
@@ -0,0 +1,84 @@
+using ImSharp;
+using Luna;
+
+namespace Penumbra.UI.ModsTab.Selector;
+
+public sealed class MoveToQuickMoveFoldersButtons(ModFileSystemDrawer drawer) : BaseButton
+{
+ public override ReadOnlySpan Label(in IFileSystemData data)
+ => throw new NotImplementedException();
+
+ public override bool DrawMenuItem(in IFileSystemData data)
+ {
+ var currentName = data.Name;
+ var currentPath = data.FullPath;
+ using var id = new Im.IdDisposable();
+ if (drawer.Config.QuickMoveFolder1.Length > 0)
+ {
+ id.Push(0);
+ var targetPath = $"{drawer.Config.QuickMoveFolder1}/{currentName}";
+ if (!drawer.FileSystem.Equal(currentPath, targetPath))
+ {
+ if (Im.Menu.Item($"Move to {drawer.Config.QuickMoveFolder1}"))
+ {
+ foreach (var path in drawer.FileSystem.Selection.OrderedNodes)
+ {
+ if (path != data)
+ drawer.FileSystem.RenameAndMoveWithDuplicates(path, $"{drawer.Config.QuickMoveFolder1}/{path.Name}");
+ }
+
+ drawer.FileSystem.RenameAndMoveWithDuplicates(data, targetPath);
+ }
+ Im.Tooltip.OnHover("Move the selected objects to a previously set-up quick move location, if possible."u8);
+ }
+
+ id.Pop();
+ }
+
+ if (drawer.Config.QuickMoveFolder2.Length > 0)
+ {
+ id.Push(1);
+ var targetPath = $"{drawer.Config.QuickMoveFolder2}/{currentName}";
+ if (!drawer.FileSystem.Equal(currentPath, targetPath))
+ {
+ if (Im.Menu.Item($"Move to {drawer.Config.QuickMoveFolder2}"))
+ {
+ foreach (var path in drawer.FileSystem.Selection.OrderedNodes)
+ {
+ if (path != data)
+ drawer.FileSystem.RenameAndMoveWithDuplicates(path, $"{drawer.Config.QuickMoveFolder2}/{path.Name}");
+ }
+
+ drawer.FileSystem.RenameAndMoveWithDuplicates(data, targetPath);
+ }
+ Im.Tooltip.OnHover("Move the selected objects to a previously set-up quick move location, if possible."u8);
+ }
+
+ id.Pop();
+ }
+
+ if (drawer.Config.QuickMoveFolder3.Length > 0)
+ {
+ id.Push(2);
+ var targetPath = $"{drawer.Config.QuickMoveFolder3}/{currentName}";
+ if (!drawer.FileSystem.Equal(currentPath, targetPath))
+ {
+ if (Im.Menu.Item($"Move to {drawer.Config.QuickMoveFolder3}"))
+ {
+ foreach (var path in drawer.FileSystem.Selection.OrderedNodes)
+ {
+ if (path != data)
+ drawer.FileSystem.RenameAndMoveWithDuplicates(path, $"{drawer.Config.QuickMoveFolder3}/{path.Name}");
+ }
+
+ drawer.FileSystem.RenameAndMoveWithDuplicates(data, targetPath);
+ }
+ Im.Tooltip.OnHover("Move the selected objects to a previously set-up quick move location, if possible."u8);
+ }
+
+ id.Pop();
+ }
+
+ return false;
+ }
+}
diff --git a/Penumbra/UI/ModsTab/Selector/Buttons/SetQuickMoveButtons.cs b/Penumbra/UI/ModsTab/Selector/Buttons/SetQuickMoveButtons.cs
new file mode 100644
index 00000000..533dc670
--- /dev/null
+++ b/Penumbra/UI/ModsTab/Selector/Buttons/SetQuickMoveButtons.cs
@@ -0,0 +1,35 @@
+using ImSharp;
+using Luna;
+
+namespace Penumbra.UI.ModsTab.Selector;
+
+public sealed class SetQuickMoveFoldersButtons(ModFileSystemDrawer drawer) : BaseButton
+{
+ public override ReadOnlySpan Label(in IFileSystemFolder data)
+ => throw new NotImplementedException();
+
+ public override bool DrawMenuItem(in IFileSystemFolder data)
+ {
+ if (Im.Menu.Item("Set as Quick Move Folder #1"u8))
+ {
+ drawer.Config.QuickMoveFolder1 = data.FullPath;
+ drawer.Config.Save();
+ }
+ Im.Tooltip.OnHover(drawer.Config.QuickMoveFolder1.Length is 0 ? "Set this folder as a quick move location."u8 : $"Set this folder as a quick move location instead of {drawer.Config.QuickMoveFolder1}.");
+
+ if (Im.Menu.Item("Set as Quick Move Folder #2"u8))
+ {
+ drawer.Config.QuickMoveFolder2 = data.FullPath;
+ drawer.Config.Save();
+ }
+ Im.Tooltip.OnHover(drawer.Config.QuickMoveFolder2.Length is 0 ? "Set this folder as a quick move location."u8 : $"Set this folder as a quick move location instead of {drawer.Config.QuickMoveFolder2}.");
+
+ if (Im.Menu.Item("Set as Quick Move Folder #3"u8))
+ {
+ drawer.Config.QuickMoveFolder3 = data.FullPath;
+ drawer.Config.Save();
+ }
+ Im.Tooltip.OnHover(drawer.Config.QuickMoveFolder3.Length is 0 ? "Set this folder as a quick move location."u8 : $"Set this folder as a quick move location instead of {drawer.Config.QuickMoveFolder3}.");
+ return false;
+ }
+}
diff --git a/Penumbra/UI/ModsTab/Selector/Buttons/TemporaryButtons.cs b/Penumbra/UI/ModsTab/Selector/Buttons/TemporaryButtons.cs
new file mode 100644
index 00000000..98e2ec45
--- /dev/null
+++ b/Penumbra/UI/ModsTab/Selector/Buttons/TemporaryButtons.cs
@@ -0,0 +1,42 @@
+using ImSharp;
+using Luna;
+using Penumbra.Mods;
+using Penumbra.Mods.Settings;
+
+namespace Penumbra.UI.ModsTab.Selector;
+
+public sealed class TemporaryButtons(ModFileSystemDrawer drawer) : BaseButton
+{
+ public override ReadOnlySpan Label(in IFileSystemData data)
+ => throw new NotImplementedException();
+
+ public override bool DrawMenuItem(in IFileSystemData data)
+ {
+ if (data.GetValue() is not { } mod)
+ return false;
+
+ var current = drawer.CollectionManager.Active.Current;
+ var tempSettings = current.GetTempSettings(mod.Index);
+ if (tempSettings is { Lock: > 0 })
+ return false;
+
+ var editor = drawer.CollectionManager.Editor;
+ if (tempSettings is { Lock: <= 0 } && Im.Menu.Item("Remove Temporary Settings"u8))
+ editor.SetTemporarySettings(current, mod, null);
+ var actual = current.GetActualSettings(mod.Index).Settings;
+ if (actual?.Enabled is true && Im.Menu.Item("Disable Temporarily"u8))
+ editor.SetTemporarySettings(current, mod, new TemporaryModSettings(mod, actual) { Enabled = false });
+
+ if (actual is not { Enabled: true } && Im.Menu.Item("Enable Temporarily"u8))
+ {
+ var newSettings = actual is null
+ ? TemporaryModSettings.DefaultSettings(mod, TemporaryModSettings.OwnSource, true)
+ : new TemporaryModSettings(mod, actual) { Enabled = true };
+ editor.SetTemporarySettings(current, mod, newSettings);
+ }
+
+ if (tempSettings is null && Im.Menu.Item("Turn Temporary"u8))
+ editor.SetTemporarySettings(current, mod, new TemporaryModSettings(mod, actual));
+ return false;
+ }
+}
diff --git a/Penumbra/UI/ModsTab/Selector/ModFileSystemCache.cs b/Penumbra/UI/ModsTab/Selector/ModFileSystemCache.cs
index e9191076..ac25d5ed 100644
--- a/Penumbra/UI/ModsTab/Selector/ModFileSystemCache.cs
+++ b/Penumbra/UI/ModsTab/Selector/ModFileSystemCache.cs
@@ -1,15 +1,79 @@
using ImSharp;
using Luna;
using Penumbra.Collections;
+using Penumbra.Collections.Manager;
+using Penumbra.Communication;
using Penumbra.Mods;
+using Penumbra.Mods.Manager;
using Penumbra.Mods.Settings;
using Penumbra.UI.Classes;
namespace Penumbra.UI.ModsTab.Selector;
-public sealed class ModFileSystemCache(ModFileSystemDrawer parent)
- : FileSystemCache(parent), IService
+public sealed class ModFileSystemCache : FileSystemCache, IService
{
+ private new ModFileSystemDrawer Parent
+ => (ModFileSystemDrawer)base.Parent;
+
+ public ModFileSystemCache(ModFileSystemDrawer parent)
+ : base(parent)
+ {
+ Parent.Communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.ModFileSystemCache);
+ Parent.Communicator.CollectionInheritanceChanged.Subscribe(OnInheritanceChange,
+ CollectionInheritanceChanged.Priority.ModFileSystemCache);
+ Parent.Communicator.ModSettingChanged.Subscribe(OnSettingChange, ModSettingChanged.Priority.ModFileSystemCache);
+ Parent.Communicator.ModDataChanged.Subscribe(OnModDataChange, ModDataChanged.Priority.ModFileSystemCache);
+ }
+
+ private void OnModDataChange(in ModDataChanged.Arguments arguments)
+ {
+ const ModDataChangeType relevantFlags =
+ ModDataChangeType.Name
+ | ModDataChangeType.Author
+ | ModDataChangeType.ModTags
+ | ModDataChangeType.LocalTags
+ | ModDataChangeType.Favorite
+ | ModDataChangeType.ImportDate;
+ if ((arguments.Type & relevantFlags) is not 0 && !Filter.IsEmpty)
+ VisibleDirty = true;
+
+ if (arguments.Mod.Node is { } node && AllNodes.TryGetValue(node, out var cache))
+ cache.Dirty = true;
+ }
+
+ private void OnSettingChange(in ModSettingChanged.Arguments arguments)
+ {
+ if (!Filter.IsEmpty)
+ VisibleDirty = true;
+
+ if (arguments.Mod?.Node is { } node && AllNodes.TryGetValue(node, out var cache))
+ cache.Dirty = true;
+ }
+
+ private void OnInheritanceChange(in CollectionInheritanceChanged.Arguments arguments)
+ {
+ if (arguments.Collection == Parent.CollectionManager.Active.Current)
+ {
+ if (!Filter.IsEmpty)
+ VisibleDirty = true;
+
+ foreach (var node in AllNodes.Values)
+ node.Dirty = true;
+ }
+ }
+
+ private void OnCollectionChange(in CollectionChange.Arguments arguments)
+ {
+ if (arguments.Type is CollectionType.Current && arguments.OldCollection != arguments.NewCollection)
+ {
+ if (!Filter.IsEmpty)
+ VisibleDirty = true;
+
+ foreach (var node in AllNodes.Values)
+ node.Dirty = true;
+ }
+ }
+
public sealed class ModData(IFileSystemData node) : BaseFileSystemNodeCache
{
public readonly IFileSystemData Node = node;
@@ -130,6 +194,15 @@ public sealed class ModFileSystemCache(ModFileSystemDrawer parent)
}
}
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ Parent.Communicator.CollectionChange.Unsubscribe(OnCollectionChange);
+ Parent.Communicator.CollectionInheritanceChanged.Unsubscribe(OnInheritanceChange);
+ Parent.Communicator.ModSettingChanged.Unsubscribe(OnSettingChange);
+ Parent.Communicator.ModDataChanged.Unsubscribe(OnModDataChange);
+ }
+
protected override ModData ConvertNode(in IFileSystemNode node)
=> new((IFileSystemData)node);
}
diff --git a/Penumbra/UI/ModsTab/Selector/ModFileSystemDrawer.cs b/Penumbra/UI/ModsTab/Selector/ModFileSystemDrawer.cs
index 1c658e42..f66ef125 100644
--- a/Penumbra/UI/ModsTab/Selector/ModFileSystemDrawer.cs
+++ b/Penumbra/UI/ModsTab/Selector/ModFileSystemDrawer.cs
@@ -2,33 +2,52 @@ using Luna;
using Penumbra.Collections.Manager;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
+using Penumbra.Services;
+using Penumbra.UI.Classes;
namespace Penumbra.UI.ModsTab.Selector;
public sealed class ModFileSystemDrawer : FileSystemDrawer
{
- public readonly ModManager ModManager;
- public readonly CollectionManager CollectionManager;
- public readonly Configuration Config;
+ public readonly ModManager ModManager;
+ public readonly CollectionManager CollectionManager;
+ public readonly Configuration Config;
+ public readonly ModImportManager ModImport;
+ public readonly FileDialogService FileService;
+ public readonly TutorialService Tutorial;
+ public readonly CommunicatorService Communicator;
- public ModFileSystemDrawer(ModFileSystem2 fileSystem, ModManager modManager, CollectionManager collectionManager, Configuration config)
+ public ModFileSystemDrawer(ModFileSystem fileSystem, ModManager modManager, CollectionManager collectionManager, Configuration config,
+ ModImportManager modImport, FileDialogService fileService, TutorialService tutorial, CommunicatorService communicator)
: base(fileSystem, new ModFilter(modManager, collectionManager.Active))
{
ModManager = modManager;
CollectionManager = collectionManager;
Config = config;
+ ModImport = modImport;
+ FileService = fileService;
+ Tutorial = tutorial;
+ Communicator = communicator;
MainContext.AddButton(new ClearTemporarySettingsButton(this), 105);
- MainContext.AddButton(new ClearDefaultImportFolderButton(this), 10);
+ MainContext.AddButton(new ClearDefaultImportFolderButton(this), -10);
+ MainContext.AddButton(new ClearQuickMoveFoldersButtons(this), -20);
FolderContext.AddButton(new SetDescendantsButton(this, true), 11);
FolderContext.AddButton(new SetDescendantsButton(this, false), 10);
FolderContext.AddButton(new SetDescendantsButton(this, true, true), 6);
FolderContext.AddButton(new SetDescendantsButton(this, false, true), 5);
- FolderContext.AddButton(new SetDefaultImportFolderButton(this), -100);
+ FolderContext.AddButton(new SetDefaultImportFolderButton(this), -50);
+ FolderContext.AddButton(new SetQuickMoveFoldersButtons(this), -70);
DataContext.AddButton(new ToggleFavoriteButton(this), 10);
- Footer.Buttons.AddButton(new AddNewModButton(this), 1000);
+ DataContext.AddButton(new TemporaryButtons(this), 20);
+ DataContext.AddButton(new MoveToQuickMoveFoldersButtons(this), -100);
+
+ Footer.Buttons.AddButton(new AddNewModButton(this), 1000);
+ Footer.Buttons.AddButton(new ImportModButton(this), 900);
+ Footer.Buttons.AddButton(new HelpButton(this), 500);
+ Footer.Buttons.AddButton(new DeleteSelectionButton(this), -100);
}
public override ReadOnlySpan Id
@@ -37,7 +56,6 @@ public sealed class ModFileSystemDrawer : FileSystemDrawer CreateCache()
=> new ModFileSystemCache(this);
-
public void SetDescendants(IFileSystemFolder folder, bool enabled, bool inherit = false)
{
var mods = folder.GetDescendants().OfType>().Select(l =>
diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs
index d931799c..c74b45b2 100644
--- a/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs
+++ b/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs
@@ -336,12 +336,9 @@ public sealed class ResourceWatcher : IDisposable, ITab
private void Enqueue(Record record)
{
- lock (_newRecords)
- {
- // Discard entries that exceed the number of records.
- while (_newRecords.Count >= _config.MaxResourceWatcherRecords)
- _newRecords.TryDequeue(out _);
- _newRecords.Enqueue(record);
- }
+ // Discard entries that exceed the number of records.
+ while (_newRecords.Count >= _config.MaxResourceWatcherRecords)
+ _newRecords.TryDequeue(out _);
+ _newRecords.Enqueue(record);
}
}
diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs
index 628c0e36..6385b52a 100644
--- a/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs
+++ b/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs
@@ -76,12 +76,14 @@ internal sealed class ResourceWatcherTable : TableBase _records;
- public bool WouldBeVisible(Record record)
+ internal interface ICheckRecord
{
- var cached = new CachedRecord(record);
- return Columns.All(c => c.WouldBeVisible(cached, -1));
+ public bool WouldBeVisible(in Record record);
}
+ public bool WouldBeVisible(Record record)
+ => Columns.OfType().All(column => column.WouldBeVisible(record));
+
public ResourceWatcherTable(ResourceWatcherConfig config, IReadOnlyList records)
: base(new StringU8("##records"u8),
new PathColumn(config) { Label = new StringU8("Path"u8) },
@@ -145,7 +147,7 @@ internal sealed class ResourceWatcherTable : TableBase
+ private sealed class PathColumn : TextColumn, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
@@ -173,9 +175,12 @@ internal sealed class ResourceWatcherTable : TableBase item.Record.Path;
+
+ public bool WouldBeVisible(in Record record)
+ => Filter.Text.Length is 0 || Filter.WouldBeVisible(record.Path.ToString());
}
- private sealed class RecordTypeColumn : FlagColumn
+ private sealed class RecordTypeColumn : FlagColumn, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
@@ -201,6 +206,9 @@ internal sealed class ResourceWatcherTable : TableBase item.Record.RecordType;
+
+ public bool WouldBeVisible(in Record record)
+ => Filter.FilterValue.HasFlag(record.RecordType);
}
private sealed class DateColumn : BasicColumn
@@ -215,7 +223,7 @@ internal sealed class ResourceWatcherTable : TableBase Im.Text(item.Time);
}
- private sealed class Crc64Column : TextColumn
+ private sealed class Crc64Column : TextColumn, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
@@ -250,10 +258,13 @@ internal sealed class ResourceWatcherTable : TableBase item.Crc64;
+
+ public bool WouldBeVisible(in Record record)
+ => Filter.Text.Length is 0 || Filter.WouldBeVisible(record.Crc64.ToString("X16"));
}
- private sealed class CollectionColumn : TextColumn
+ private sealed class CollectionColumn : TextColumn, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
@@ -276,9 +287,12 @@ internal sealed class ResourceWatcherTable : TableBase item.Collection;
+
+ public bool WouldBeVisible(in Record record)
+ => Filter.WouldBeVisible(record.Collection?.Identity.Name ?? string.Empty);
}
- private sealed class ObjectColumn : TextColumn
+ private sealed class ObjectColumn : TextColumn, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
@@ -301,9 +315,12 @@ internal sealed class ResourceWatcherTable : TableBase item.AssociatedGameObject;
+
+ public bool WouldBeVisible(in Record record)
+ => Filter.WouldBeVisible(record.AssociatedGameObject);
}
- private sealed class OriginalPathColumn : TextColumn
+ private sealed class OriginalPathColumn : TextColumn, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
@@ -331,9 +348,12 @@ internal sealed class ResourceWatcherTable : TableBase item.Record.OriginalPath;
+
+ public bool WouldBeVisible(in Record record)
+ => Filter.Text.Length is 0 || Filter.WouldBeVisible(record.OriginalPath.ToString());
}
- private sealed class ResourceCategoryColumn : FlagColumn
+ private sealed class ResourceCategoryColumn : FlagColumn, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
@@ -359,9 +379,12 @@ internal sealed class ResourceWatcherTable : TableBase item.Record.Category;
+
+ public bool WouldBeVisible(in Record record)
+ => Filter.FilterValue.HasFlag(record.Category);
}
- private sealed class ResourceTypeColumn : FlagColumn
+ private sealed class ResourceTypeColumn : FlagColumn, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
@@ -387,9 +410,12 @@ internal sealed class ResourceWatcherTable : TableBase item.Record.ResourceType;
+
+ public bool WouldBeVisible(in Record record)
+ => Filter.FilterValue.HasFlag(record.ResourceType);
}
- private sealed class LoadStateColumn : FlagColumn
+ private sealed class LoadStateColumn : FlagColumn, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
@@ -447,18 +473,25 @@ internal sealed class ResourceWatcherTable : TableBase item.Record.LoadState switch
- {
- LoadState.None => LoadStateFlag.None,
- LoadState.Success => LoadStateFlag.Success,
- LoadState.FailedSubResource => LoadStateFlag.FailedSub,
- <= LoadState.Constructed => LoadStateFlag.Unknown,
- < LoadState.Success => LoadStateFlag.Async,
- > LoadState.Success => LoadStateFlag.Failed,
- };
+ => GetValue(item.Record.LoadState);
+
+
+ public bool WouldBeVisible(in Record record)
+ => Filter.FilterValue.HasFlag(GetValue(record.LoadState));
+
+ private static LoadStateFlag GetValue(LoadState value)
+ => value switch
+ {
+ LoadState.None => LoadStateFlag.None,
+ LoadState.Success => LoadStateFlag.Success,
+ LoadState.FailedSubResource => LoadStateFlag.FailedSub,
+ <= LoadState.Constructed => LoadStateFlag.Unknown,
+ < LoadState.Success => LoadStateFlag.Async,
+ > LoadState.Success => LoadStateFlag.Failed,
+ };
}
- private sealed class HandleColumn : TextColumn
+ private sealed class HandleColumn : TextColumn, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
@@ -489,6 +522,9 @@ internal sealed class ResourceWatcherTable : TableBase StringU8.Empty;
+
+ public unsafe bool WouldBeVisible(in Record record)
+ => Filter.Text.Length is 0 || Filter.WouldBeVisible($"0x{(nint)record.Handle:X}");
}
@@ -531,7 +567,7 @@ internal sealed class ResourceWatcherTable : TableBase ToValue(item.Record.CustomLoad);
+
+ public bool WouldBeVisible(in Record record)
+ => Filter.FilterValue.HasFlag(ToValue(record.CustomLoad));
}
- private sealed class SynchronousLoadColumn : OptBoolColumn
+ private sealed class SynchronousLoadColumn : OptBoolColumn, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
@@ -573,12 +612,15 @@ internal sealed class ResourceWatcherTable : TableBase ToValue(item.Record.Synchronously);
+
+ public bool WouldBeVisible(in Record record)
+ => Filter.FilterValue.HasFlag(ToValue(record.Synchronously));
}
- private sealed class RefCountColumn : NumberColumn
+ private sealed class RefCountColumn : NumberColumn, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
-
+
public RefCountColumn(ResourceWatcherConfig config)
{
_config = config;
@@ -601,9 +643,12 @@ internal sealed class ResourceWatcherTable : TableBase item.RefCount;
+
+ public bool WouldBeVisible(in Record record)
+ => Filter.WouldBeVisible(record.RefCount);
}
- private sealed class OsThreadColumn : NumberColumn
+ private sealed class OsThreadColumn : NumberColumn, ICheckRecord
{
private readonly ResourceWatcherConfig _config;
@@ -629,6 +674,9 @@ internal sealed class ResourceWatcherTable : TableBase item.Thread;
+
+ public bool WouldBeVisible(in Record record)
+ => Filter.WouldBeVisible(record.OsThreadId);
}
public override IEnumerable GetItems()
diff --git a/Penumbra/UI/Tabs/CollectionButtonFooter.cs b/Penumbra/UI/Tabs/CollectionButtonFooter.cs
index e4e0bb55..bb184573 100644
--- a/Penumbra/UI/Tabs/CollectionButtonFooter.cs
+++ b/Penumbra/UI/Tabs/CollectionButtonFooter.cs
@@ -16,6 +16,9 @@ public sealed class CollectionButtonFooter : ButtonFooter
Buttons.AddButton(new DeleteButton(collectionManager.Storage, collectionManager.Active, configuration), 0);
}
+ public int Count
+ => Buttons.Count;
+
public sealed class AddButton(CollectionStorage collections) : BaseIconButton
{
public override AwesomeIcon Icon
diff --git a/Penumbra/UI/Tabs/CollectionsTab.cs b/Penumbra/UI/Tabs/CollectionsTab.cs
index cc1eef02..ffcc5d8a 100644
--- a/Penumbra/UI/Tabs/CollectionsTab.cs
+++ b/Penumbra/UI/Tabs/CollectionsTab.cs
@@ -9,12 +9,13 @@ namespace Penumbra.UI.Tabs;
public sealed class CollectionsTab : TwoPanelLayout, ITab
{
private readonly TutorialService _tutorial;
+ private readonly UiConfig _config;
public TabType Identifier
=> TabType.Collections;
public CollectionsTab(TutorialService tutorial, CollectionButtonFooter leftFooter, CollectionSelector leftPanel, CollectionFilter filter,
- CollectionModeHeader rightHeader, CollectionPanel rightPanel)
+ CollectionModeHeader rightHeader, CollectionPanel rightPanel, UiConfig config)
{
LeftHeader = new FilterHeader(filter, new StringU8("Filter..."u8));
LeftPanel = leftPanel;
@@ -23,19 +24,29 @@ public sealed class CollectionsTab : TwoPanelLayout, ITab
RightPanel = rightPanel;
RightFooter = NopHeaderFooter.Instance;
_tutorial = tutorial;
+ _config = config;
}
+ protected override float MinimumWidth
+ => LeftFooter.MinimumWidth;
+
+ protected override float MaximumWidth
+ => Im.Window.Width - 690 * Im.Style.GlobalScale;
+
public override ReadOnlySpan Label
=> "Collections"u8;
- protected override void DrawLeftGroup()
+ protected override void DrawLeftGroup(in TwoPanelWidth width)
{
- base.DrawLeftGroup();
+ base.DrawLeftGroup(width);
_tutorial.OpenTutorial(BasicTutorialSteps.EditingCollections);
}
public void DrawContent()
- => Draw();
+ => Draw(_config.CollectionTabScale);
+
+ protected override void SetWidth(float width, ScalingMode mode)
+ => _config.CollectionTabScale = new TwoPanelWidth(width, mode);
public void PostTabButton()
=> _tutorial.OpenTutorial(BasicTutorialSteps.Collections);
diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs
index cb4cb578..1f903123 100644
--- a/Penumbra/UI/Tabs/Debug/DebugTab.cs
+++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs
@@ -1,4 +1,3 @@
-using Dalamud.Interface;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Group;
@@ -1049,8 +1048,8 @@ public sealed class DebugTab : Window, ITab
if (!table)
return;
- table.SetupColumn("Hash"u8, TableColumnFlags.WidthFixed, 18 * UiBuilder.MonoFont.GetCharAdvance('0'));
- table.SetupColumn("Type"u8, TableColumnFlags.WidthFixed, 5 * UiBuilder.MonoFont.GetCharAdvance('0'));
+ table.SetupColumn("Hash"u8, TableColumnFlags.WidthFixed, 18 * Im.Font.Mono.GetCharacterAdvance('0'));
+ table.SetupColumn("Type"u8, TableColumnFlags.WidthFixed, 5 * Im.Font.Mono.GetCharacterAdvance('0'));
table.HeaderRow();
foreach (var (hash, type) in _rsfService.CustomCache)
@@ -1173,7 +1172,7 @@ public sealed class DebugTab : Window, ITab
_config.Ephemeral.Save();
}
- public static unsafe void DrawCopyableAddress(ReadOnlySpan label, nint address)
+ public static void DrawCopyableAddress(ReadOnlySpan label, nint address)
{
Penumbra.Dynamis.DrawPointer(address);
Im.Line.SameInner();
diff --git a/Penumbra/UI/Tabs/Debug/ShapeInspector.cs b/Penumbra/UI/Tabs/Debug/ShapeInspector.cs
index b373b681..cbcf5a2b 100644
--- a/Penumbra/UI/Tabs/Debug/ShapeInspector.cs
+++ b/Penumbra/UI/Tabs/Debug/ShapeInspector.cs
@@ -1,4 +1,3 @@
-using Dalamud.Interface;
using ImSharp;
using Penumbra.Collections.Cache;
using Penumbra.GameData.Enums;
@@ -176,9 +175,9 @@ public class ShapeInspector(ObjectManager objects, CollectionResolver resolver)
table.SetupColumn("#"u8, TableColumnFlags.WidthFixed, 25 * Im.Style.GlobalScale);
table.SetupColumn("Slot"u8, TableColumnFlags.WidthFixed, 150 * Im.Style.GlobalScale);
- table.SetupColumn("Address"u8, TableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 14);
- table.SetupColumn("Mask"u8, TableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 8);
- table.SetupColumn("ID"u8, TableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 4);
+ table.SetupColumn("Address"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 14);
+ table.SetupColumn("Mask"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 8);
+ table.SetupColumn("ID"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 4);
table.SetupColumn("Count"u8, TableColumnFlags.WidthFixed, 30 * Im.Style.GlobalScale);
table.SetupColumn("Shapes"u8, TableColumnFlags.WidthStretch);
table.HeaderRow();
@@ -232,9 +231,9 @@ public class ShapeInspector(ObjectManager objects, CollectionResolver resolver)
table.SetupColumn("#"u8, TableColumnFlags.WidthFixed, 25 * Im.Style.GlobalScale);
table.SetupColumn("Slot"u8, TableColumnFlags.WidthFixed, 150 * Im.Style.GlobalScale);
- table.SetupColumn("Address"u8, TableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 14);
- table.SetupColumn("Mask"u8, TableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 8);
- table.SetupColumn("ID"u8, TableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 4);
+ table.SetupColumn("Address"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 14);
+ table.SetupColumn("Mask"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 8);
+ table.SetupColumn("ID"u8, TableColumnFlags.WidthFixed, Im.Font.Mono.GetCharacterAdvance('0') * 4);
table.SetupColumn("Count"u8, TableColumnFlags.WidthFixed, 30 * Im.Style.GlobalScale);
table.SetupColumn("Attributes"u8, TableColumnFlags.WidthStretch);
table.HeaderRow();
diff --git a/Penumbra/UI/Tabs/ModsTab.cs b/Penumbra/UI/Tabs/ModsTab.cs
deleted file mode 100644
index 3abcadcd..00000000
--- a/Penumbra/UI/Tabs/ModsTab.cs
+++ /dev/null
@@ -1,181 +0,0 @@
-using Dalamud.Plugin.Services;
-using Penumbra.UI.Classes;
-using FFXIVClientStructs.FFXIV.Client.Game;
-using ImSharp;
-using Luna;
-using Penumbra.Api.Enums;
-using Penumbra.Interop.Services;
-using Penumbra.Mods;
-using Penumbra.Mods.Manager;
-using Penumbra.UI.ModsTab;
-using ModFileSystemSelector = Penumbra.UI.ModsTab.ModFileSystemSelector;
-using Penumbra.Collections.Manager;
-using Penumbra.GameData.Interop;
-
-namespace Penumbra.UI.Tabs;
-
-public sealed class ModsTab(
- ModManager modManager,
- CollectionManager collectionManager,
- ModFileSystemSelector selector,
- ModPanel panel,
- TutorialService tutorial,
- RedrawService redrawService,
- Configuration config,
- CollectionSelectHeader collectionHeader,
- ITargetManager targets,
- ObjectManager objects)
- : ITab
-{
- private readonly ActiveCollections _activeCollections = collectionManager.Active;
-
- public bool IsEnabled
- => modManager.Valid;
-
- public ReadOnlySpan Label
- => "Mods"u8;
-
- public TabType Identifier
- => TabType.Mods;
-
- public void PostTabButton()
- => tutorial.OpenTutorial(BasicTutorialSteps.Mods);
-
- public Mod SelectMod
- {
- set => selector.SelectByValue(value);
- }
-
- public void DrawContent()
- {
- try
- {
- selector.Draw();
- Im.Line.Same();
- Im.Cursor.X = MathF.Round(Im.Cursor.X);
- using var group = Im.Group();
- collectionHeader.Draw(false);
-
- using var style = ImStyleDouble.ItemSpacing.Push(Vector2.Zero);
- using (var child = Im.Child.Begin("##ModsTabMod"u8,
- Im.ContentRegion.Available with { Y = config.HideRedrawBar ? 0 : -Im.Style.FrameHeight },
- true, WindowFlags.HorizontalScrollbar))
- {
- style.Pop();
- if (child)
- panel.Draw();
-
- style.Push(ImStyleDouble.ItemSpacing, Vector2.Zero);
- }
-
- style.Push(ImStyleSingle.FrameRounding, 0);
- DrawRedrawLine();
- }
- catch (Exception e)
- {
- Penumbra.Log.Error($"Exception thrown during ModPanel Render:\n{e}");
- Penumbra.Log.Error($"{modManager.Count} Mods\n"
- + $"{_activeCollections.Current.Identity.AnonymizedName} Current Collection\n"
- + $"{_activeCollections.Current.Settings.Count} Settings\n"
- + $"{selector.SortMode.Name} Sort Mode\n"
- + $"{selector.SelectedLeaf?.Name ?? "NULL"} Selected Leaf\n"
- + $"{selector.Selected?.Name ?? "NULL"} Selected Mod\n"
- + $"{string.Join(", ", _activeCollections.Current.Inheritance.DirectlyInheritsFrom.Select(c => c.Identity.AnonymizedName))} Inheritances\n");
- }
- }
-
- private void DrawRedrawLine()
- {
- if (config.HideRedrawBar)
- {
- tutorial.SkipTutorial(BasicTutorialSteps.Redrawing);
- return;
- }
-
- var frameHeight = new Vector2(0, Im.Style.FrameHeight);
- var frameColor = Im.Style[ImGuiColor.FrameBackground];
- using (Im.Group())
- {
- using (AwesomeIcon.Font.Push())
- {
- ImEx.TextFramed(LunaStyle.HelpMarker.Span, frameHeight, frameColor);
- }
-
- Im.Line.Same();
- ImEx.TextFramed("Redraw: "u8, frameHeight, frameColor);
- }
-
- var hovered = Im.Item.Hovered();
- tutorial.OpenTutorial(BasicTutorialSteps.Redrawing);
- if (hovered)
- {
- using var _ = Im.Tooltip.Begin();
- Im.Text("The supported modifiers for '/penumbra redraw' are:"u8);
- Im.BulletText("nothing, to redraw all characters\n"u8);
- Im.BulletText("'self' or '': your own character\n"u8);
- Im.BulletText("'target' or '': your target\n"u8);
- Im.BulletText("'focus' or ': your focus target\n"u8);
- Im.BulletText("'mouseover' or '': the actor you are currently hovering over\n"u8);
- Im.BulletText("'furniture': most indoor furniture, does not currently work outdoors\n"u8);
- Im.BulletText("any specific actor name to redraw all actors of that exactly matching name."u8);
- }
-
- using var id = Im.Id.Push("Redraw"u8);
- using var disabled = Im.Disabled(!objects[0].Valid);
- Im.Line.Same();
- var buttonWidth = frameHeight with { X = Im.ContentRegion.Available.X / 5 };
- var tt = !objects[0].Valid
- ? "Can only be used when you are logged in and your character is available."u8
- : StringU8.Empty;
- DrawButton(buttonWidth, "All"u8, string.Empty, tt);
- Im.Line.Same();
- DrawButton(buttonWidth, "Self"u8, "self", tt);
- Im.Line.Same();
-
- tt = targets.Target is null && targets.GPoseTarget is null
- ? "Can only be used when you have a target."u8
- : StringU8.Empty;
- DrawButton(buttonWidth, "Target"u8, "target", tt);
- Im.Line.Same();
-
- tt = targets.FocusTarget is null
- ? "Can only be used when you have a focus target."u8
- : StringU8.Empty;
- DrawButton(buttonWidth, "Focus"u8, "focus", tt);
- Im.Line.Same();
-
- tt = !IsIndoors()
- ? "Can currently only be used for indoor furniture."u8
- : StringU8.Empty;
- DrawButton(frameHeight with { X = Im.ContentRegion.Available.X - 1 }, "Furniture"u8, "furniture", tt);
- return;
-
- void DrawButton(Vector2 size, ReadOnlySpan label, string lower, ReadOnlySpan additionalTooltip)
- {
- using (Im.Disabled(additionalTooltip.Length > 0))
- {
- if (Im.Button(label, size))
- {
- if (lower.Length > 0)
- redrawService.RedrawObject(lower, RedrawType.Redraw);
- else
- redrawService.RedrawAll(RedrawType.Redraw);
- }
- }
-
- if (!Im.Item.Hovered(HoveredFlags.AllowWhenDisabled))
- return;
-
- using var _ = Im.Tooltip.Begin();
- if (lower.Length > 0)
- Im.Text($"Execute '/penumbra redraw {lower}'.");
- else
- Im.Text("Execute '/penumbra redraw'."u8);
- if (additionalTooltip.Length > 0)
- Im.Text(additionalTooltip);
- }
- }
-
- private static unsafe bool IsIndoors()
- => HousingManager.Instance()->IsInside();
-}
diff --git a/Penumbra/UI/Tabs/SettingsTab.cs b/Penumbra/UI/Tabs/SettingsTab.cs
index c1669cbf..ee0dd249 100644
--- a/Penumbra/UI/Tabs/SettingsTab.cs
+++ b/Penumbra/UI/Tabs/SettingsTab.cs
@@ -36,7 +36,6 @@ public sealed class SettingsTab : ITab
private readonly ModManager _modManager;
private readonly FileWatcher _fileWatcher;
private readonly ModExportManager _modExportManager;
- private readonly ModFileSystemSelector _selector;
private readonly CharacterUtility _characterUtility;
private readonly ResidentResourceManager _residentResources;
private readonly HttpApi _httpApi;
@@ -53,18 +52,20 @@ public sealed class SettingsTab : ITab
private readonly AttributeHook _attributeHook;
private readonly PcpService _pcpService;
private readonly IntegrationSettingsRegistry _integrationSettings;
+ private readonly ModFileSystemDrawer _modFileSystemDrawer;
private string _lastCloudSyncTestedPath = string.Empty;
private bool _lastCloudSyncTestResult;
public SettingsTab(IDalamudPluginInterface pluginInterface, Configuration config, FontReloader fontReloader, TutorialService tutorial,
- Penumbra penumbra, FileDialogService fileDialog, ModManager modManager, ModFileSystemSelector selector,
- CharacterUtility characterUtility, ResidentResourceManager residentResources, ModExportManager modExportManager,
+ Penumbra penumbra, FileDialogService fileDialog, ModManager modManager, CharacterUtility characterUtility,
+ ResidentResourceManager residentResources, ModExportManager modExportManager,
FileWatcher fileWatcher, HttpApi httpApi,
DalamudSubstitutionProvider dalamudSubstitutionProvider, FileCompactor compactor, DalamudConfigService dalamudConfig,
IDataManager gameData, PredefinedTagManager predefinedTagConfig, CrashHandlerService crashService,
MigrationSectionDrawer migrationDrawer, CollectionAutoSelector autoSelector, CleanupService cleanupService,
- AttributeHook attributeHook, PcpService pcpService, IntegrationSettingsRegistry integrationSettings)
+ AttributeHook attributeHook, PcpService pcpService, IntegrationSettingsRegistry integrationSettings,
+ ModFileSystemDrawer modFileSystemDrawer)
{
_pluginInterface = pluginInterface;
_config = config;
@@ -73,7 +74,6 @@ public sealed class SettingsTab : ITab
_penumbra = penumbra;
_fileDialog = fileDialog;
_modManager = modManager;
- _selector = selector;
_characterUtility = characterUtility;
_residentResources = residentResources;
_modExportManager = modExportManager;
@@ -93,6 +93,7 @@ public sealed class SettingsTab : ITab
_attributeHook = attributeHook;
_pcpService = pcpService;
_integrationSettings = integrationSettings;
+ _modFileSystemDrawer = modFileSystemDrawer;
}
public void PostTabButton()
@@ -164,9 +165,8 @@ public sealed class SettingsTab : ITab
private bool DrawPressEnterWarning(string newName, string old, float width, bool saved, bool selected)
{
using var color = ImGuiColor.Button.Push(Colors.PressEnterWarningBg);
- var w = new Vector2(width, 0);
var (text, valid) = CheckRootDirectoryPath(newName, old, selected);
-
+ var w = new Vector2(Math.Max(width, Im.Font.CalculateButtonSize(text).X), 0);
return (Im.Button(text, w) || saved) && valid;
}
@@ -279,6 +279,7 @@ public sealed class SettingsTab : ITab
+ "It should also be placed near the root of a logical drive - the shorter the total path to this folder, the better.\n"u8
+ "Definitely do not place it in your Dalamud directory or any sub-directory thereof."u8;
+ Im.Line.SameInner();
LunaStyle.DrawAlignedHelpMarker(tt);
_tutorial.OpenTutorial(BasicTutorialSteps.GeneralTooltips);
Im.Line.SameInner();
@@ -389,7 +390,7 @@ public sealed class SettingsTab : ITab
private void DrawHidingSettings()
{
Checkbox("Open Config Window at Game Start"u8, "Whether the Penumbra main window should be open or closed after launching the game."u8,
- _config.OpenWindowAtStart, v => _config.OpenWindowAtStart = v);
+ _config.OpenWindowAtStart, v => _config.OpenWindowAtStart = v);
Checkbox("Hide Config Window when UI is Hidden"u8,
"Hide the Penumbra main window when you manually hide the in-game user interface."u8, _config.HideUiWhenUiHidden,
@@ -424,9 +425,9 @@ public sealed class SettingsTab : ITab
"Chat Commands usually print messages on failure but also on success to confirm your action. You can disable this here."u8,
_config.PrintSuccessfulCommandsToChat, v => _config.PrintSuccessfulCommandsToChat = v);
Checkbox("Hide Redraw Bar in Mod Panel"u8, "Hides the lower redraw buttons in the mod panel in your Mods tab."u8,
- _config.HideRedrawBar, v => _config.HideRedrawBar = v);
+ _config.HideRedrawBar, v => _config.HideRedrawBar = v);
Checkbox("Hide Changed Item Filters"u8, "Hides the category filter line in the Changed Items tab and the Changed Items mod panel."u8,
- _config.HideChangedItemFilters, v =>
+ _config.HideChangedItemFilters, v =>
{
_config.HideChangedItemFilters = v;
if (v)
@@ -477,7 +478,8 @@ public sealed class SettingsTab : ITab
Checkbox("Use Assigned Collections in Try-On Window"u8,
"Use the individual collection for your character's name in your try-on, dye preview or glamour plate window, if it is set."u8,
_config.UseCharacterCollectionInTryOn, v => _config.UseCharacterCollectionInTryOn = v);
- Checkbox("Use No Mods in Inspect Windows"u8, "Use the empty collection for characters you are inspecting, regardless of the character.\n"u8
+ Checkbox("Use No Mods in Inspect Windows"u8,
+ "Use the empty collection for characters you are inspecting, regardless of the character.\n"u8
+ "Takes precedence before the next option."u8, _config.UseNoModsInInspect, v => _config.UseNoModsInInspect = v);
Checkbox("Use Assigned Collections in Inspect Windows"u8,
"Use the appropriate individual collection for the character you are currently inspecting, based on their name."u8,
@@ -499,8 +501,8 @@ public sealed class SettingsTab : ITab
{
if (Im.Selectable(val.Name, val.Equals(sortMode)) && !val.Equals(sortMode))
{
- _config.SortMode = val;
- _selector.SetFilterDirty();
+ _config.SortMode = val;
+ _modFileSystemDrawer.SortMode = val;
_config.Save();
}
@@ -522,14 +524,17 @@ public sealed class SettingsTab : ITab
if (Im.Selectable(value.ToNameU8(), _config.ShowRename == value))
{
_config.ShowRename = value;
- _selector.SetRenameSearchPath(value);
+ // TODO
+ // _selector.SetRenameSearchPath(value);
_config.Save();
}
+
Im.Tooltip.OnHover(value.Tooltip());
}
}
- LunaStyle.DrawAlignedHelpMarkerLabel("Rename Fields in Mod Context Menu"u8, "Select which of the two renaming input fields are visible when opening the right-click context menu of a mod in the mod selector."u8);
+ LunaStyle.DrawAlignedHelpMarkerLabel("Rename Fields in Mod Context Menu"u8,
+ "Select which of the two renaming input fields are visible when opening the right-click context menu of a mod in the mod selector."u8);
}
/// Draw all settings pertaining to the mod selector.
@@ -538,10 +543,11 @@ public sealed class SettingsTab : ITab
DrawFolderSortType();
DrawRenameSettings();
Checkbox("Open Folders by Default"u8, "Whether to start with all folders collapsed or expanded in the mod selector."u8,
- _config.OpenFoldersByDefault, v =>
+ _config.OpenFoldersByDefault, v =>
{
_config.OpenFoldersByDefault = v;
- _selector.SetFilterDirty();
+ // TODO
+ // SetFilterDirty
});
KeySelector.DoubleModifier("Mod Deletion Modifier"u8,
@@ -587,7 +593,8 @@ public sealed class SettingsTab : ITab
Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteModModifier} while clicking.");
Im.Line.Same();
- if (ImEx.Button("Delete all PCP Collections"u8, default, "Deletes all collections whose name starts with 'PCP/' from the collection list."u8, !active))
+ if (ImEx.Button("Delete all PCP Collections"u8, default,
+ "Deletes all collections whose name starts with 'PCP/' from the collection list."u8, !active))
_pcpService.CleanPcpCollections();
if (!active)
Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteModModifier} while clicking.");
@@ -757,7 +764,6 @@ public sealed class SettingsTab : ITab
LunaStyle.DrawAlignedHelpMarkerLabel("Default PCP Organizational Folder"u8,
"The folder any penumbra character packs are moved to on import.\nLeave blank to import into Root."u8);
-
}
private void DrawPcpExtension()
@@ -1061,7 +1067,7 @@ public sealed class SettingsTab : ITab
Im.Tooltip.OnHover(HoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteModModifier} while clicking to delete files.");
if (ImEx.Button("Clear All Unused Settings"u8, default,
- "Remove all mod settings in all of your collections that do not correspond to currently installed mods."u8,
+ "Remove all mod settings in all of your collections that do not correspond to currently installed mods."u8,
!enabled || _cleanupService.IsRunning))
_cleanupService.CleanupAllUnusedSettings();
if (!enabled)
@@ -1089,7 +1095,7 @@ public sealed class SettingsTab : ITab
}
#endregion
-
+
/// Draw the support button group on the right-hand side of the window.
private void DrawSupportButtons()
{
@@ -1128,7 +1134,7 @@ public sealed class SettingsTab : ITab
if (!Im.Tree.Header("Tags"u8))
return;
- var tagIdx = Luna.TagButtons.Draw("Predefined Tags: "u8,
+ var tagIdx = TagButtons.Draw("Predefined Tags: "u8,
"Predefined tags that can be added or removed from mods with a single click."u8, _predefinedTagManager,
out var editedTag);