From 08dc266cfbe055b93806401f0574a02aab5140d5 Mon Sep 17 00:00:00 2001
From: KiritoDv <kiritodev01@gmail.com>
Date: Sat, 21 Dec 2024 22:13:40 -0600
Subject: [PATCH] Added notification system from soh

---
 src/port/notification/notification.cpp | 132 +++++++++++++++++++++++++
 src/port/notification/notification.h   |  35 +++++++
 src/port/ui/ImguiUI.cpp                |   9 ++
 3 files changed, 176 insertions(+)
 create mode 100644 src/port/notification/notification.cpp
 create mode 100644 src/port/notification/notification.h

diff --git a/src/port/notification/notification.cpp b/src/port/notification/notification.cpp
new file mode 100644
index 00000000..94f8ea59
--- /dev/null
+++ b/src/port/notification/notification.cpp
@@ -0,0 +1,132 @@
+
+#include "notification.h"
+#include <libultraship/libultraship.h>
+
+namespace Notification {
+
+static uint32_t nextId = 0;
+static std::vector<Options> notifications = {};
+
+void Window::Draw() {
+    auto vp = ImGui::GetMainViewport();
+
+    const float margin = 30.0f;
+    const float padding = 10.0f;
+
+    int position = CVarGetInteger("gNotifications.Position", 0);
+
+    // Top Left
+    ImVec2 basePosition;
+    switch (position) {
+        case 0: // Top Left
+            basePosition = ImVec2(vp->Pos.x + margin, vp->Pos.y + margin);
+            break;  
+        case 1: // Top Right
+            basePosition = ImVec2(vp->Pos.x + vp->Size.x - margin, vp->Pos.y + margin);
+            break;
+        case 2: // Bottom Left
+            basePosition = ImVec2(vp->Pos.x + margin, vp->Pos.y + vp->Size.y - margin);
+            break;
+        case 3: // Bottom Right
+            basePosition = ImVec2(vp->Pos.x + vp->Size.x - margin, vp->Pos.y + vp->Size.y - margin);
+            break;
+        case 4: // Hidden
+            return;
+    }
+
+    ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, CVarGetFloat("gNotifications.BgOpacity", 0.5f)));
+    ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
+    ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f);
+
+    for (int index = 0; index < notifications.size(); ++index) {
+        auto& notification = notifications[index];
+        int inverseIndex = -ABS(index - (notifications.size() - 1));
+
+        ImGui::SetNextWindowViewport(vp->ID);
+        if (notification.remainingTime < 4.0f) {
+            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, (notification.remainingTime - 1) / 3.0f);
+        } else {
+            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.0f);
+        }
+
+        ImGui::Begin(("notification#" + std::to_string(notification.id)).c_str(), nullptr,
+            ImGuiWindowFlags_AlwaysAutoResize |
+            ImGuiWindowFlags_NoNav |
+            ImGuiWindowFlags_NoFocusOnAppearing |
+            ImGuiWindowFlags_NoResize |
+            ImGuiWindowFlags_NoDocking |
+            ImGuiWindowFlags_NoTitleBar |
+            ImGuiWindowFlags_NoScrollWithMouse |
+            ImGuiWindowFlags_NoInputs |
+            ImGuiWindowFlags_NoMove |
+            ImGuiWindowFlags_NoScrollbar
+        );
+
+        ImGui::SetWindowFontScale(CVarGetFloat("gNotifications.Size", 1.8f)); // Make this adjustable
+
+        ImVec2 notificationPos;
+        switch (position) {
+            case 0: // Top Left
+                notificationPos = ImVec2(basePosition.x, basePosition.y + ((ImGui::GetWindowSize().y + padding) * inverseIndex));
+                break;
+            case 1: // Top Right
+                notificationPos = ImVec2(basePosition.x - ImGui::GetWindowSize().x, basePosition.y + ((ImGui::GetWindowSize().y + padding) * inverseIndex));
+                break;
+            case 2: // Bottom Left
+                notificationPos = ImVec2(basePosition.x, basePosition.y - ((ImGui::GetWindowSize().y + padding) * (inverseIndex + 1)));
+                break;
+            case 3: // Bottom Right
+                notificationPos = ImVec2(basePosition.x - ImGui::GetWindowSize().x, basePosition.y - ((ImGui::GetWindowSize().y + padding) * (inverseIndex + 1)));
+                break;
+        }
+
+        ImGui::SetWindowPos(notificationPos);
+
+        if (notification.itemIcon != nullptr) {
+            ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(notification.itemIcon), ImVec2(24, 24));
+            ImGui::SameLine();
+        }
+        if (!notification.prefix.empty()) {
+            ImGui::TextColored(notification.prefixColor, "%s", notification.prefix.c_str());
+            ImGui::SameLine();
+        }
+        ImGui::TextColored(notification.messageColor, "%s", notification.message.c_str());
+        if (!notification.suffix.empty()) {
+            ImGui::SameLine();
+            ImGui::TextColored(notification.suffixColor, "%s", notification.suffix.c_str());
+        }
+
+        ImGui::End();
+        ImGui::PopStyleVar();
+    }
+
+    ImGui::PopStyleVar();
+    ImGui::PopStyleColor(2);
+}
+
+
+void Window::UpdateElement() {
+    for (int index = 0; index < notifications.size(); ++index) {
+        auto& notification = notifications[index];
+
+        // decrement remainingTime
+        notification.remainingTime -= ImGui::GetIO().DeltaTime;
+
+        // remove notification if it has expired
+        if (notification.remainingTime <= 0) {
+            notifications.erase(notifications.begin() + index);
+            --index;
+        }
+    }
+}
+
+void Emit(Options notification) {
+    notification.id = nextId++;
+    if (notification.remainingTime == 0.0f) {
+        notification.remainingTime = CVarGetFloat("gNotifications.Duration", 10.0f);
+    }
+    notifications.push_back(notification);
+    // Audio_PlaySoundGeneral(NA_SE_SY_METRONOME, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
+}
+
+} // namespace Notification
diff --git a/src/port/notification/notification.h b/src/port/notification/notification.h
new file mode 100644
index 00000000..dbcece01
--- /dev/null
+++ b/src/port/notification/notification.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#ifdef __cplusplus
+
+#include <string>
+#include <libultraship/libultraship.h>
+
+namespace Notification {
+
+struct Options {
+    uint32_t id = 0;
+    const char* itemIcon = nullptr;
+    std::string prefix = "";
+    ImVec4 prefixColor = ImVec4(0.5f, 0.5f, 1.0f, 1.0f);
+    std::string message = "";
+    ImVec4 messageColor = ImVec4(0.7f, 0.7f, 0.7f, 1.0f);
+    std::string suffix = "";
+    ImVec4 suffixColor = ImVec4(1.0f, 0.5f, 0.5f, 1.0f);
+    float remainingTime = 0.0f; // Seconds
+};
+
+class Window : public Ship::GuiWindow {
+  public:
+    using GuiWindow::GuiWindow;
+
+    void InitElement() override {};
+    void DrawElement() override {};
+    void Draw() override;
+    void UpdateElement() override;
+};
+
+void Emit(Options notification);
+
+} // namespace Notification
+#endif // __cplusplus
diff --git a/src/port/ui/ImguiUI.cpp b/src/port/ui/ImguiUI.cpp
index eb9a62c8..5c77bf14 100644
--- a/src/port/ui/ImguiUI.cpp
+++ b/src/port/ui/ImguiUI.cpp
@@ -11,6 +11,7 @@
 #include <libultraship/libultraship.h>
 #include <Fast3D/gfx_pc.h>
 #include "port/Engine.h"
+#include "port/notification/notification.h"
 
 extern "C" {
 #include "sys.h"
@@ -24,6 +25,7 @@ std::shared_ptr<Ship::GuiWindow> mConsoleWindow;
 std::shared_ptr<Ship::GuiWindow> mStatsWindow;
 std::shared_ptr<Ship::GuiWindow> mInputEditorWindow;
 std::shared_ptr<Ship::GuiWindow> mGfxDebuggerWindow;
+std::shared_ptr<Notification::Window> mNotificationWindow;
 std::shared_ptr<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow> mAdvancedResolutionSettingsWindow;
 
 void SetupGuiElements() {
@@ -59,13 +61,20 @@ void SetupGuiElements() {
 
     mAdvancedResolutionSettingsWindow = std::make_shared<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow>("gAdvancedResolutionEditorEnabled", "Advanced Resolution Settings");
     gui->AddGuiWindow(mAdvancedResolutionSettingsWindow);
+    mNotificationWindow = std::make_shared<Notification::Window>("gNotifications", "Notifications Window");
+    gui->AddGuiWindow(mNotificationWindow);
+    mNotificationWindow->Show();
 }
 
 void Destroy() {
+    auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui();
+    gui->RemoveAllGuiWindows();
+
     mAdvancedResolutionSettingsWindow = nullptr;
     mConsoleWindow = nullptr;
     mStatsWindow = nullptr;
     mInputEditorWindow = nullptr;
+    mNotificationWindow = nullptr;
 }
 
 std::string GetWindowButtonText(const char* text, bool menuOpen) {