mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-02-18 13:57:46 +01:00
Rework mod import UX
This commit is contained in:
parent
753876cb9b
commit
582a4d61a9
6 changed files with 179 additions and 15 deletions
2
Luna
2
Luna
|
|
@ -1 +1 @@
|
||||||
Subproject commit 384b437a8cf16b4b1a51da43a4837d8fcc2116bd
|
Subproject commit 14544263ad78d95d96235eab597a7cb0a76015eb
|
||||||
|
|
@ -19,6 +19,7 @@ public partial class TexToolsImporter : IDisposable
|
||||||
private readonly string _tmpFile;
|
private readonly string _tmpFile;
|
||||||
|
|
||||||
private readonly IEnumerable<FileInfo> _modPackFiles;
|
private readonly IEnumerable<FileInfo> _modPackFiles;
|
||||||
|
private readonly int _previousModPackCount;
|
||||||
private readonly int _modPackCount;
|
private readonly int _modPackCount;
|
||||||
private FileStream? _tmpFileStream;
|
private FileStream? _tmpFileStream;
|
||||||
private StreamDisposer? _streamDisposer;
|
private StreamDisposer? _streamDisposer;
|
||||||
|
|
@ -37,8 +38,16 @@ public partial class TexToolsImporter : IDisposable
|
||||||
|
|
||||||
public TexToolsImporter(int count, IEnumerable<FileInfo> modPackFiles, Action<FileInfo, DirectoryInfo?, Exception?> handler,
|
public TexToolsImporter(int count, IEnumerable<FileInfo> modPackFiles, Action<FileInfo, DirectoryInfo?, Exception?> handler,
|
||||||
Configuration config, DuplicateManager duplicates, ModNormalizer modNormalizer, ModManager modManager, FileCompactor compactor,
|
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;
|
_baseDirectory = modManager.BasePath;
|
||||||
_tmpFile = Path.Combine(_baseDirectory.FullName, TempFileName);
|
_tmpFile = Path.Combine(_baseDirectory.FullName, TempFileName);
|
||||||
_modPackFiles = modPackFiles;
|
_modPackFiles = modPackFiles;
|
||||||
|
|
@ -48,14 +57,16 @@ public partial class TexToolsImporter : IDisposable
|
||||||
_modManager = modManager;
|
_modManager = modManager;
|
||||||
_compactor = compactor;
|
_compactor = compactor;
|
||||||
_migrationManager = migrationManager;
|
_migrationManager = migrationManager;
|
||||||
_modPackCount = count;
|
_modPackCount = count + _previousModPackCount;
|
||||||
ExtractedMods = new List<(FileInfo, DirectoryInfo?, Exception?)>(count);
|
ExtractedMods = new List<(FileInfo, DirectoryInfo?, Exception?)>(count + _previousModPackCount);
|
||||||
_token = _cancellation.Token;
|
_token = _cancellation.Token;
|
||||||
|
if (previous is not null)
|
||||||
|
ExtractedMods.AddRange(previous.ExtractedMods);
|
||||||
Task.Run(ImportFiles, _token)
|
Task.Run(ImportFiles, _token)
|
||||||
.ContinueWith(_ => CloseStreams(), TaskScheduler.Default)
|
.ContinueWith(_ => CloseStreams(), TaskScheduler.Default)
|
||||||
.ContinueWith(_ =>
|
.ContinueWith(_ =>
|
||||||
{
|
{
|
||||||
foreach (var (file, dir, error) in ExtractedMods)
|
foreach (var (file, dir, error) in ExtractedMods.Skip(_previousModPackCount))
|
||||||
handler(file, dir, error);
|
handler(file, dir, error);
|
||||||
}, TaskScheduler.Default);
|
}, TaskScheduler.Default);
|
||||||
}
|
}
|
||||||
|
|
@ -83,7 +94,7 @@ public partial class TexToolsImporter : IDisposable
|
||||||
private void ImportFiles()
|
private void ImportFiles()
|
||||||
{
|
{
|
||||||
State = ImporterState.None;
|
State = ImporterState.None;
|
||||||
_currentModPackIdx = 0;
|
_currentModPackIdx = _previousModPackCount;
|
||||||
foreach (var file in _modPackFiles)
|
foreach (var file in _modPackFiles)
|
||||||
{
|
{
|
||||||
_currentModDirectory = null;
|
_currentModDirectory = null;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,24 @@ public partial class TexToolsImporter
|
||||||
private string _currentOptionName = string.Empty;
|
private string _currentOptionName = string.Empty;
|
||||||
private string _currentFileName = 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)
|
public bool DrawProgressInfo(Vector2 size)
|
||||||
{
|
{
|
||||||
if (_modPackCount is 0)
|
if (_modPackCount is 0)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using Dalamud.Interface.ImGuiNotification;
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
using Luna;
|
using Luna;
|
||||||
using Penumbra.Import;
|
using Penumbra.Import;
|
||||||
|
using Penumbra.Import.Structs;
|
||||||
using Penumbra.Mods.Editor;
|
using Penumbra.Mods.Editor;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
|
||||||
|
|
@ -31,7 +32,7 @@ public class ModImportManager(
|
||||||
|
|
||||||
public void TryUnpacking()
|
public void TryUnpacking()
|
||||||
{
|
{
|
||||||
if (Importing || !_modsToUnpack.TryDequeue(out var newMods))
|
if (Importing && _import!.State is not ImporterState.Done || !_modsToUnpack.TryDequeue(out var newMods))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var files = newMods.Where(s =>
|
var files = newMods.Where(s =>
|
||||||
|
|
@ -49,7 +50,7 @@ public class ModImportManager(
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_import = new TexToolsImporter(files.Length, files, AddNewMod, config, duplicates, modNormalizer, modManager, compactor,
|
_import = new TexToolsImporter(files.Length, files, AddNewMod, config, duplicates, modNormalizer, modManager, compactor,
|
||||||
migrationManager);
|
migrationManager, _import);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Importing
|
public bool Importing
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,37 @@
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
|
using Dalamud.Interface.ImGuiNotification.EventArgs;
|
||||||
using ImSharp;
|
using ImSharp;
|
||||||
using Luna;
|
using Luna;
|
||||||
using Penumbra.Import.Structs;
|
using Penumbra.Import.Structs;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
using MessageService = Penumbra.Services.MessageService;
|
||||||
|
|
||||||
namespace Penumbra.UI;
|
namespace Penumbra.UI;
|
||||||
|
|
||||||
/// <summary> Draw the progress information for import. </summary>
|
/// <summary> Draw the progress information for import. </summary>
|
||||||
public sealed class ImportPopup : Window
|
public sealed class ImportPopup : Window, INotificationAwareMessage
|
||||||
{
|
{
|
||||||
public const string WindowLabel = "Penumbra Import Status";
|
public const string WindowLabel = "Penumbra Import Status";
|
||||||
|
|
||||||
private readonly ModImportManager _modImportManager;
|
private readonly ModImportManager _modImportManager;
|
||||||
|
private readonly MessageService _messageService;
|
||||||
private static readonly Vector2 OneHalf = Vector2.One / 2;
|
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 WasDrawn { get; private set; }
|
||||||
public bool PopupWasDrawn { get; private set; }
|
public bool PopupWasDrawn { get; private set; }
|
||||||
|
|
||||||
public ImportPopup(ModImportManager modImportManager)
|
public ImportPopup(ModImportManager modImportManager, MessageService messageService)
|
||||||
: base(WindowLabel,
|
: base(WindowLabel,
|
||||||
WindowFlags.NoCollapse
|
WindowFlags.NoCollapse
|
||||||
| WindowFlags.NoDecoration
|
| WindowFlags.NoDecoration
|
||||||
|
|
@ -30,6 +45,7 @@ public sealed class ImportPopup : Window
|
||||||
| WindowFlags.NoTitleBar, true)
|
| WindowFlags.NoTitleBar, true)
|
||||||
{
|
{
|
||||||
_modImportManager = modImportManager;
|
_modImportManager = modImportManager;
|
||||||
|
_messageService = messageService;
|
||||||
DisableWindowSounds = true;
|
DisableWindowSounds = true;
|
||||||
IsOpen = true;
|
IsOpen = true;
|
||||||
RespectCloseHotkey = false;
|
RespectCloseHotkey = false;
|
||||||
|
|
@ -58,8 +74,26 @@ public sealed class ImportPopup : Window
|
||||||
if (!_modImportManager.IsImporting(out var import))
|
if (!_modImportManager.IsImporting(out var import))
|
||||||
return;
|
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);
|
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 display = Im.Io.DisplaySize;
|
||||||
var height = Math.Max(display.Y / 4, 15 * Im.Style.FrameHeightWithSpacing);
|
var height = Math.Max(display.Y / 4, 15 * Im.Style.FrameHeightWithSpacing);
|
||||||
|
|
@ -78,10 +112,109 @@ public sealed class ImportPopup : Window
|
||||||
terminate = true;
|
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
|
terminate |= import.State == ImporterState.Done
|
||||||
? Im.Button("Close"u8, -Vector2.UnitX)
|
? Im.Button("Close"u8, -Vector2.UnitX)
|
||||||
: import.DrawCancelButton(-Vector2.UnitX);
|
: import.DrawCancelButton(-Vector2.UnitX);
|
||||||
if (terminate)
|
if (terminate)
|
||||||
_modImportManager.ClearImport();
|
_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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -350,10 +350,11 @@ public sealed class DebugTab : Window, ITab<TabType>
|
||||||
if (table)
|
if (table)
|
||||||
{
|
{
|
||||||
var importing = _modImporter.IsImporting(out var importer);
|
var importing = _modImporter.IsImporting(out var importer);
|
||||||
table.DrawDataPair("Is Importing"u8, importing.ToString());
|
table.DrawDataPair("Is Importing"u8, importing.ToString());
|
||||||
table.DrawDataPair("Importer State"u8, (importer?.State ?? ImporterState.None).ToString());
|
table.DrawDataPair("Importer State"u8, (importer?.State ?? ImporterState.None).ToString());
|
||||||
table.DrawDataPair("Import Window Was Drawn"u8, _importPopup.WasDrawn.ToString());
|
table.DrawDataPair("Import Notification Exists"u8, _importPopup.HasNotification.ToString());
|
||||||
table.DrawDataPair("Import Popup Was Drawn"u8, _importPopup.PopupWasDrawn.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.DrawColumn("Import Batches"u8);
|
||||||
table.NextColumn();
|
table.NextColumn();
|
||||||
foreach (var (index, batch) in _modImporter.ModBatches.Index())
|
foreach (var (index, batch) in _modImporter.ModBatches.Index())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue