diff --git a/Penumbra/Import/Models/IoNotifier.cs b/Penumbra/Import/Models/IoNotifier.cs
new file mode 100644
index 00000000..e1d649f6
--- /dev/null
+++ b/Penumbra/Import/Models/IoNotifier.cs
@@ -0,0 +1,52 @@
+using Dalamud.Interface.Internal.Notifications;
+using OtterGui.Classes;
+
+namespace Penumbra.Import.Models;
+
+public record class IoNotifier
+{
+ /// Notification subclass so that we have a distinct type to filter by.
+ private class LegallyDistinctNotification : Notification
+ {
+ public LegallyDistinctNotification(string content, NotificationType type): base(content, type)
+ {}
+ }
+
+ private readonly DateTime _startTime = DateTime.UtcNow;
+ private string _context = "";
+
+ /// Create a new notifier with the specified context appended to any other context already present.
+ public IoNotifier WithContext(string context)
+ => this with { _context = $"{_context}{context}: "};
+
+ /// Send a warning with any current context to notification channels.
+ public void Warning(string content)
+ => SendNotification(content, NotificationType.Warning);
+
+ /// Get the current warnings for this notifier.
+ /// This does not currently filter to notifications with the current notifier's context - it will return all IO notifications from all notifiers.
+ public IEnumerable GetWarnings()
+ => GetFilteredNotifications(NotificationType.Warning);
+
+ /// Create an exception with any current context.
+ [StackTraceHidden]
+ public Exception Exception(string message)
+ => Exception(message);
+
+ /// Create an exception of the provided type with any current context.
+ [StackTraceHidden]
+ public TException Exception(string message)
+ where TException : Exception, new()
+ => (TException)Activator.CreateInstance(typeof(TException), $"{_context}{message}")!;
+
+ private void SendNotification(string message, NotificationType type)
+ => Penumbra.Messager.AddMessage(
+ new LegallyDistinctNotification($"{_context}{message}", type),
+ true, false, true, false
+ );
+
+ private IEnumerable GetFilteredNotifications(NotificationType type)
+ => Penumbra.Messager
+ .Where(p => p.Key >= _startTime && p.Value is LegallyDistinctNotification && p.Value.NotificationType == type)
+ .Select(p => p.Value.PrintMessage);
+}
diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs
index 26fcd1ee..15c6cb21 100644
--- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs
+++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs
@@ -25,6 +25,7 @@ public partial class ModEditWindow
private bool _dirty;
public bool PendingIo { get; private set; }
public List IoExceptions { get; private set; } = [];
+ public List IoWarnings { get; private set; } = [];
public MdlTab(ModEditWindow edit, byte[] bytes, string path)
{
diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs
index ad609285..1a200fdf 100644
--- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs
+++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs
@@ -63,6 +63,7 @@ public partial class ModEditWindow
DrawExport(tab, childSize, disabled);
DrawIoExceptions(tab);
+ DrawIoWarnings(tab);
}
private void DrawImport(MdlTab tab, Vector2 size, bool _1)
@@ -148,7 +149,43 @@ public partial class ModEditWindow
using var exceptionNode = ImRaii.TreeNode(message);
if (exceptionNode)
+ {
+ ImGui.Dummy(new Vector2(ImGui.GetStyle().IndentSpacing, 0));
+ ImGui.SameLine();
ImGuiUtil.TextWrapped(exception.ToString());
+ }
+ }
+ }
+
+ private static void DrawIoWarnings(MdlTab tab)
+ {
+ if (tab.IoWarnings.Count == 0)
+ return;
+
+ var size = new Vector2(ImGui.GetContentRegionAvail().X, 0);
+ using var frame = ImRaii.FramedGroup("Warnings", size, headerPreIcon: FontAwesomeIcon.ExclamationCircle, borderColor: 0xFF40FFFF);
+
+ var spaceAvail = ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X - 100;
+ foreach (var (warning, index) in tab.IoWarnings.WithIndex())
+ {
+ using var id = ImRaii.PushId(index);
+ var textSize = ImGui.CalcTextSize(warning).X;
+
+ if (textSize <= spaceAvail)
+ {
+ ImRaii.TreeNode(warning, ImGuiTreeNodeFlags.Leaf).Dispose();
+ continue;
+ }
+
+ var firstLine = warning[..(int)Math.Floor(warning.Length * (spaceAvail / textSize))] + "...";
+
+ using var warningNode = ImRaii.TreeNode(firstLine);
+ if (warningNode)
+ {
+ ImGui.Dummy(new Vector2(ImGui.GetStyle().IndentSpacing, 0));
+ ImGui.SameLine();
+ ImGuiUtil.TextWrapped(warning.ToString());
+ }
}
}