diff --git a/Luna b/Luna index 384b437a..14544263 160000 --- a/Luna +++ b/Luna @@ -1 +1 @@ -Subproject commit 384b437a8cf16b4b1a51da43a4837d8fcc2116bd +Subproject commit 14544263ad78d95d96235eab597a7cb0a76015eb diff --git a/Penumbra/Import/TexToolsImport.cs b/Penumbra/Import/TexToolsImport.cs index 29c3d7c5..dea06dc6 100644 --- a/Penumbra/Import/TexToolsImport.cs +++ b/Penumbra/Import/TexToolsImport.cs @@ -19,6 +19,7 @@ public partial class TexToolsImporter : IDisposable private readonly string _tmpFile; private readonly IEnumerable _modPackFiles; + private readonly int _previousModPackCount; private readonly int _modPackCount; private FileStream? _tmpFileStream; private StreamDisposer? _streamDisposer; @@ -37,8 +38,16 @@ public partial class TexToolsImporter : IDisposable public TexToolsImporter(int count, IEnumerable modPackFiles, Action handler, Configuration config, DuplicateManager duplicates, ModNormalizer modNormalizer, ModManager modManager, FileCompactor compactor, - MigrationManager migrationManager) + MigrationManager migrationManager, TexToolsImporter? previous) { + if (previous is not null) + { + if (previous.State is not ImporterState.Done) + throw new ArgumentException($"The previous {nameof(TexToolsImporter)} must have completed its job."); + + _previousModPackCount = previous.ExtractedMods.Count; + } + _baseDirectory = modManager.BasePath; _tmpFile = Path.Combine(_baseDirectory.FullName, TempFileName); _modPackFiles = modPackFiles; @@ -48,14 +57,16 @@ public partial class TexToolsImporter : IDisposable _modManager = modManager; _compactor = compactor; _migrationManager = migrationManager; - _modPackCount = count; - ExtractedMods = new List<(FileInfo, DirectoryInfo?, Exception?)>(count); + _modPackCount = count + _previousModPackCount; + ExtractedMods = new List<(FileInfo, DirectoryInfo?, Exception?)>(count + _previousModPackCount); _token = _cancellation.Token; + if (previous is not null) + ExtractedMods.AddRange(previous.ExtractedMods); Task.Run(ImportFiles, _token) .ContinueWith(_ => CloseStreams(), TaskScheduler.Default) .ContinueWith(_ => { - foreach (var (file, dir, error) in ExtractedMods) + foreach (var (file, dir, error) in ExtractedMods.Skip(_previousModPackCount)) handler(file, dir, error); }, TaskScheduler.Default); } @@ -83,7 +94,7 @@ public partial class TexToolsImporter : IDisposable private void ImportFiles() { State = ImporterState.None; - _currentModPackIdx = 0; + _currentModPackIdx = _previousModPackCount; foreach (var file in _modPackFiles) { _currentModDirectory = null; diff --git a/Penumbra/Import/TexToolsImporter.Gui.cs b/Penumbra/Import/TexToolsImporter.Gui.cs index 88068db6..55389cf3 100644 --- a/Penumbra/Import/TexToolsImporter.Gui.cs +++ b/Penumbra/Import/TexToolsImporter.Gui.cs @@ -18,6 +18,24 @@ public partial class TexToolsImporter private string _currentOptionName = string.Empty; private string _currentFileName = string.Empty; + public (string Text, float Progress, bool Ended, bool Successful) ComputeNotificationData() + { + if (_modPackCount is 0) + return ("Nothing to extract.", 1.0f, true, true); + + if (_modPackCount == _currentModPackIdx) + { + var success = ExtractedMods.Count(t => t.Error == null); + + return ($"Successfully extracted {success} / {ExtractedMods.Count} files.", 1.0f, true, success == ExtractedMods.Count); + } + + if (State is ImporterState.DeduplicatingFiles) + return ($"Deduplicating {_currentModName}...", 1.0f, false, true); + + return ($"Extracting {_currentModName}...", _currentNumFiles > 0 ? _currentFileIdx / (float)_currentNumFiles : 0.0f, false, true); + } + public bool DrawProgressInfo(Vector2 size) { if (_modPackCount is 0) diff --git a/Penumbra/Mods/Manager/ModImportManager.cs b/Penumbra/Mods/Manager/ModImportManager.cs index 613a89f5..e8beb13e 100644 --- a/Penumbra/Mods/Manager/ModImportManager.cs +++ b/Penumbra/Mods/Manager/ModImportManager.cs @@ -1,6 +1,7 @@ using Dalamud.Interface.ImGuiNotification; using Luna; using Penumbra.Import; +using Penumbra.Import.Structs; using Penumbra.Mods.Editor; using Penumbra.Services; @@ -31,7 +32,7 @@ public class ModImportManager( public void TryUnpacking() { - if (Importing || !_modsToUnpack.TryDequeue(out var newMods)) + if (Importing && _import!.State is not ImporterState.Done || !_modsToUnpack.TryDequeue(out var newMods)) return; var files = newMods.Where(s => @@ -49,7 +50,7 @@ public class ModImportManager( return; _import = new TexToolsImporter(files.Length, files, AddNewMod, config, duplicates, modNormalizer, modManager, compactor, - migrationManager); + migrationManager, _import); } public bool Importing diff --git a/Penumbra/UI/ImportPopup.cs b/Penumbra/UI/ImportPopup.cs index 4e8184fd..f5481d5f 100644 --- a/Penumbra/UI/ImportPopup.cs +++ b/Penumbra/UI/ImportPopup.cs @@ -1,22 +1,37 @@ +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Interface.ImGuiNotification; +using Dalamud.Interface.ImGuiNotification.EventArgs; using ImSharp; using Luna; using Penumbra.Import.Structs; using Penumbra.Mods.Manager; +using MessageService = Penumbra.Services.MessageService; namespace Penumbra.UI; /// Draw the progress information for import. -public sealed class ImportPopup : Window +public sealed class ImportPopup : Window, INotificationAwareMessage { public const string WindowLabel = "Penumbra Import Status"; private readonly ModImportManager _modImportManager; + private readonly MessageService _messageService; private static readonly Vector2 OneHalf = Vector2.One / 2; + private IActiveNotification? _notification; + private string _notificationMessage = string.Empty; + private float _notificationProgress = 1.0f; + private bool _notificationEnded = true; + private bool _notificationSuccessful = true; + private bool _openPopup = false; + + public bool HasNotification + => _notification is not null; + public bool WasDrawn { get; private set; } public bool PopupWasDrawn { get; private set; } - public ImportPopup(ModImportManager modImportManager) + public ImportPopup(ModImportManager modImportManager, MessageService messageService) : base(WindowLabel, WindowFlags.NoCollapse | WindowFlags.NoDecoration @@ -30,6 +45,7 @@ public sealed class ImportPopup : Window | WindowFlags.NoTitleBar, true) { _modImportManager = modImportManager; + _messageService = messageService; DisableWindowSounds = true; IsOpen = true; RespectCloseHotkey = false; @@ -58,8 +74,26 @@ public sealed class ImportPopup : Window if (!_modImportManager.IsImporting(out var import)) return; - if (!Im.Popup.IsOpen("##PenumbraImportPopup"u8)) + (_notificationMessage, _notificationProgress, _notificationEnded, _notificationSuccessful) = import.ComputeNotificationData(); + + _notification?.Title = NotificationTitle; + _notification?.Type = NotificationType; + _notification?.Content = _notificationMessage; + _notification?.Progress = _notificationProgress; + _notification?.UserDismissable = _notificationEnded; + + if (_openPopup) + { Im.Popup.Open("##PenumbraImportPopup"u8); + _openPopup = false; + } + + if (!Im.Popup.IsOpen("##PenumbraImportPopup"u8)) + { + if (_notification is null) + _messageService.AddMessage(this, false, true, false); + return; + } var display = Im.Io.DisplaySize; var height = Math.Max(display.Y / 4, 15 * Im.Style.FrameHeightWithSpacing); @@ -78,10 +112,109 @@ public sealed class ImportPopup : Window terminate = true; } + if (import.State != ImporterState.Done) + { + if (Im.Button("Continue in the Background"u8, + new Vector2((Im.ContentRegion.Available.X - Im.GetStyle().ItemSpacing.X) * 0.5f, 0.0f))) + Im.Popup.CloseCurrent(); + Im.Line.Same(); + } + terminate |= import.State == ImporterState.Done ? Im.Button("Close"u8, -Vector2.UnitX) : import.DrawCancelButton(-Vector2.UnitX); if (terminate) _modImportManager.ClearImport(); } + + #region Luna Message implementation + + private NotificationType NotificationType + => (_notificationEnded, _notificationSuccessful) switch + { + (false, _) => NotificationType.Info, + (true, true) => NotificationType.Success, + (true, false) => NotificationType.Error, + }; + + private string NotificationTitle + => (_notificationEnded, _notificationSuccessful) switch + { + (false, _) => "Importing mods", + (true, true) => "Successfully imported mods", + (true, false) => "Failed to import some mods", + }; + + NotificationType IMessage.NotificationType + => NotificationType; + + string IMessage.NotificationMessage + => _notificationMessage; + + TimeSpan IMessage.NotificationDuration + => TimeSpan.MaxValue; + + string IMessage.NotificationTitle + => NotificationTitle; + + string IMessage.LogMessage + => string.Empty; + + SeString IMessage.ChatMessage + => SeString.Empty; + + StringU8 IMessage.StoredMessage + => StringU8.Empty; + + StringU8 IMessage.StoredTooltip + => StringU8.Empty; + + void IMessage.OnNotificationActions(INotificationDrawArgs args) + { + if (_notificationEnded) + { + if (Im.Button("Open Report"u8, -Vector2.UnitX)) + { + _openPopup = true; + _notification?.DismissNow(); + } + } + else + { + if (Im.Button("Show Details"u8, new Vector2((Im.ContentRegion.Available.X - Im.GetStyle().ItemSpacing.X) * 0.5f, 0.0f))) + { + _openPopup = true; + _notification?.DismissNow(); + } + + Im.Line.Same(); + if (_modImportManager.IsImporting(out var import) && import.DrawCancelButton(-Vector2.UnitX)) + { + _modImportManager.ClearImport(); + _notification?.DismissNow(); + } + } + } + + void INotificationAwareMessage.OnNotificationCreated(IActiveNotification notification) + { + var previousNotification = _notification; + _notification = notification; + previousNotification?.DismissNow(); + notification.Progress = _notificationProgress; + notification.UserDismissable = _notificationEnded; + notification.Dismiss += OnNotificationDismissed; + } + + private void OnNotificationDismissed(INotificationDismissArgs args) + { + if (args.Notification != _notification) + return; + + _notification = null; + if (!_openPopup && !PopupWasDrawn) + _modImportManager.ClearImport(); + } + + #endregion } diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs index 1f903123..830f1d25 100644 --- a/Penumbra/UI/Tabs/Debug/DebugTab.cs +++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs @@ -350,10 +350,11 @@ public sealed class DebugTab : Window, ITab if (table) { var importing = _modImporter.IsImporting(out var importer); - table.DrawDataPair("Is Importing"u8, importing.ToString()); - table.DrawDataPair("Importer State"u8, (importer?.State ?? ImporterState.None).ToString()); - table.DrawDataPair("Import Window Was Drawn"u8, _importPopup.WasDrawn.ToString()); - table.DrawDataPair("Import Popup Was Drawn"u8, _importPopup.PopupWasDrawn.ToString()); + table.DrawDataPair("Is Importing"u8, importing.ToString()); + table.DrawDataPair("Importer State"u8, (importer?.State ?? ImporterState.None).ToString()); + table.DrawDataPair("Import Notification Exists"u8, _importPopup.HasNotification.ToString()); + table.DrawDataPair("Import Window Was Drawn"u8, _importPopup.WasDrawn.ToString()); + table.DrawDataPair("Import Popup Was Drawn"u8, _importPopup.PopupWasDrawn.ToString()); table.DrawColumn("Import Batches"u8); table.NextColumn(); foreach (var (index, batch) in _modImporter.ModBatches.Index())