From 4285dafeb6147358e135cbd05a39a337ee46b56b Mon Sep 17 00:00:00 2001 From: pas2704 Date: Mon, 11 Nov 2024 14:51:09 -0500 Subject: [PATCH] Add NotificationsComponent --- CringePlugins/CringePlugins.csproj | 1 + CringePlugins/Loader/PluginWrapper.cs | 17 +++- CringePlugins/Loader/PluginsLifetime.cs | 3 + CringePlugins/Ui/NotificationsComponent.cs | 102 +++++++++++++++++++++ 4 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 CringePlugins/Ui/NotificationsComponent.cs diff --git a/CringePlugins/CringePlugins.csproj b/CringePlugins/CringePlugins.csproj index 4be62e5..82f9347 100644 --- a/CringePlugins/CringePlugins.csproj +++ b/CringePlugins/CringePlugins.csproj @@ -18,6 +18,7 @@ + diff --git a/CringePlugins/Loader/PluginWrapper.cs b/CringePlugins/Loader/PluginWrapper.cs index fccca66..e8f13ba 100644 --- a/CringePlugins/Loader/PluginWrapper.cs +++ b/CringePlugins/Loader/PluginWrapper.cs @@ -1,4 +1,6 @@ -using NLog; +using CringePlugins.Ui; +using ImGuiNET; +using NLog; using VRage.Plugins; namespace CringePlugins.Loader; @@ -13,6 +15,8 @@ internal sealed class PluginWrapper(PluginMetadata metadata, IPlugin plugin) : I private readonly IHandleInputPlugin? _handleInputPlugin = plugin as IHandleInputPlugin; + private const float ErrorShowTime = 10f; + public void Dispose() { try @@ -23,6 +27,7 @@ internal sealed class PluginWrapper(PluginMetadata metadata, IPlugin plugin) : I { Log.Error(e, "Exception while Disposing {Metadata}", metadata); LastException = e; + NotificationsComponent.SpawnNotification(ErrorShowTime, RenderError); } } @@ -39,6 +44,7 @@ internal sealed class PluginWrapper(PluginMetadata metadata, IPlugin plugin) : I { Log.Error(e, "Exception while Updating {Metadata}", metadata); LastException = e; + NotificationsComponent.SpawnNotification(ErrorShowTime, RenderError); } } @@ -52,6 +58,7 @@ internal sealed class PluginWrapper(PluginMetadata metadata, IPlugin plugin) : I { Log.Error(e, "Exception while Initializing {Metadata}", metadata); LastException = e; + NotificationsComponent.SpawnNotification(ErrorShowTime, RenderError); } } @@ -68,8 +75,16 @@ internal sealed class PluginWrapper(PluginMetadata metadata, IPlugin plugin) : I { Log.Error(e, "Exception while Updating {Metadata}", metadata); LastException = e; + NotificationsComponent.SpawnNotification(ErrorShowTime, RenderError); } } public override string ToString() => metadata.ToString(); + + private void RenderError() + { + ImGui.TextColored(new System.Numerics.Vector4(1f, 0f, 0f, 0f), "Error: "); + ImGui.SameLine(); + ImGui.TextWrapped($"Fatal error in {metadata.Name}: {LastException?.Message}"); + } } diff --git a/CringePlugins/Loader/PluginsLifetime.cs b/CringePlugins/Loader/PluginsLifetime.cs index cba0316..75bd9b4 100644 --- a/CringePlugins/Loader/PluginsLifetime.cs +++ b/CringePlugins/Loader/PluginsLifetime.cs @@ -63,6 +63,9 @@ public class PluginsLifetime(string gameFolder) : ILoadingStage progress.Report("Loading plugins"); + //we can move this, but it should be before plugin init + RenderHandler.Current.RegisterComponent(new NotificationsComponent()); + await LoadPlugins(cachedPackages, sourceMapping, packagesConfig); RenderHandler.Current.RegisterComponent(new PluginListComponent(packagesConfig, sourceMapping, configPath, gameFolder, _plugins)); diff --git a/CringePlugins/Ui/NotificationsComponent.cs b/CringePlugins/Ui/NotificationsComponent.cs new file mode 100644 index 0000000..86d08db --- /dev/null +++ b/CringePlugins/Ui/NotificationsComponent.cs @@ -0,0 +1,102 @@ +using CringePlugins.Abstractions; +using HarmonyLib; +using ImGuiNET; +using System.Numerics; +using VRage; +using VRageRender; + +namespace CringePlugins.Ui; +internal class NotificationsComponent : IRenderComponent +{ + private static int _globalNotificationId; + private const float TransitionTime = .5f; + private const float YPadding = 5f; + + private static Vector2 _notificationSize = new(300, 75); + private static readonly List Notifications = []; + + private static Size WindowSize => ((Form)MyVRage.Platform.Windows.Window).Size; + + private static float _time; + + public void OnFrame() + { + var y = 0f; + + var lastY = _notificationSize.Y; + var viewportPos = ImGui.GetMainViewport().Pos; + + + //todo: consider adding a limit to the number of messages that can be displayed at once + for (var i = Notifications.Count; i-- > 0;) + { + var lerpMult = 0f; + var notification = Notifications[i]; + var startDelta = _time - notification.StartTime; + var endDelta = _time - notification.HideTime; + + var inTransition = true; + if (startDelta is > 0f and < TransitionTime) + { + lerpMult = startDelta / TransitionTime; + } + else if (endDelta is > 0f and < TransitionTime) + { + lerpMult = 1f - (endDelta / TransitionTime); + } + else if (startDelta > 0f && endDelta < 0f) + { + lerpMult = 1f; + inTransition = false; + } + + y += (lastY + YPadding) * EaseInOutCubic(lerpMult); + if (!inTransition && y < _notificationSize.Y + YPadding) + y = _notificationSize.Y + YPadding; + + ImGui.SetNextWindowPos(new Vector2(WindowSize.Width, y * EaseInOutCubic(lerpMult)) - _notificationSize + viewportPos); + ImGui.SetNextWindowSize(new Vector2(_notificationSize.X, 0f)); + + Vector2 lastWinSize = Vector2.Zero; + + if (ImGui.Begin($"notification-{notification.GlobalId}", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.Tooltip)) + { + notification.RenderCallback?.Invoke(); + + //todo: add indicator for when message will expire. probaby just want a rectangle at the bottom of the message + //var fraction = Math.Min(1f, (_time - notification.StartTime) / (notification.HideTime - notification.StartTime)); + //ImGui.ProgressBar(fraction, new Vector2(_notificationSize.X, 10)); + + lastWinSize = ImGui.GetWindowSize(); + + ImGui.End(); + } + + lastY = lastWinSize.Y; + } + + + Notifications.RemoveAll(x => x.IsGarbage); + + _time += MyCommon.GetLastFrameDelta(); + } + public static void SpawnNotification(float showTime, Action renderCallback) + => Notifications.Add(new NotificationInstruction(_globalNotificationId++, renderCallback, _time, _time + showTime)); + public static void SpawnNotification(float showTime, string text) + => Notifications.Add(new NotificationInstruction(_globalNotificationId++, () => ImGui.TextWrapped(text), _time, _time + showTime)); + + private static float EaseInOutCubic(float x) => x < 0.5f + ? 4f * x * x * x + : 1f - MathF.Pow((-2f * x + 2f), 3f) / 2f; + + private readonly struct NotificationInstruction(int id, Action renderCallback, float startTime, float hideTime) + { + public readonly int GlobalId = id; + public readonly Action RenderCallback = renderCallback; + public readonly float StartTime = startTime; + public readonly float HideTime = hideTime; + + public readonly bool IsGarbage + => _time > HideTime + TransitionTime; + } +}