diff --git a/mm/2s2h/BenGui/BenGui.cpp b/mm/2s2h/BenGui/BenGui.cpp index dfc57bf34..4a9fa2967 100644 --- a/mm/2s2h/BenGui/BenGui.cpp +++ b/mm/2s2h/BenGui/BenGui.cpp @@ -8,6 +8,7 @@ #include #include "UIWidgets.hpp" #include "HudEditor.h" +#include "Notification.h" #ifdef __APPLE__ #include "graphic/Fast3D/gfx_metal.h" @@ -36,6 +37,7 @@ std::shared_ptr mCollisionViewerWindow; std::shared_ptr mEventLogWindow; std::shared_ptr mBenMenu; std::shared_ptr mBenInputEditorWindow; +std::shared_ptr mNotificationWindow; void SetupGuiElements() { auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui(); @@ -93,6 +95,10 @@ void SetupGuiElements() { mEventLogWindow = std::make_shared("gWindows.EventLog", "Event Log", ImVec2(520, 600)); gui->AddGuiWindow(mEventLogWindow); gui->SetPadBtnTogglesMenu(); + + mNotificationWindow = std::make_shared("gWindows.Notifications", "Notifications Window"); + gui->AddGuiWindow(mNotificationWindow); + mNotificationWindow->Show(); } void Destroy() { @@ -104,6 +110,7 @@ void Destroy() { mGfxDebuggerWindow = nullptr; mCollisionViewerWindow = nullptr; mEventLogWindow = nullptr; + mNotificationWindow = nullptr; mSaveEditorWindow = nullptr; mHudEditorWindow = nullptr; diff --git a/mm/2s2h/BenGui/Notification.cpp b/mm/2s2h/BenGui/Notification.cpp new file mode 100644 index 000000000..0c363d8d4 --- /dev/null +++ b/mm/2s2h/BenGui/Notification.cpp @@ -0,0 +1,140 @@ + +#include "Notification.h" +#include + +extern "C" { +#include "functions.h" +#include "macros.h" +#include "variables.h" +} + +namespace Notification { + +static uint32_t nextId = 0; +static std::vector notifications = {}; + +void Window::Draw() { + auto vp = ImGui::GetMainViewport(); + + const float margin = 30.0f; + const float padding = 10.0f; + + int position = CVarGetInteger("gNotifications.Position", 3); + + // 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); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f * CVarGetFloat("gNotifications.Size", 1.8f), 6.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0f * CVarGetFloat("gNotifications.Size", 1.8f), 8.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)); + + 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); + ImGui::AlignTextToFramePadding(); + + if (notification.itemIcon != nullptr) { + ImGui::Image( + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(notification.itemIcon), + ImVec2(22 * CVarGetFloat("gNotifications.Size", 1.8f), 22 * CVarGetFloat("gNotifications.Size", 1.8f))); + 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(3); + 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); + AudioSfx_PlaySfx(NA_SE_SY_METRONOME, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultReverb); +} + +} // namespace Notification diff --git a/mm/2s2h/BenGui/Notification.h b/mm/2s2h/BenGui/Notification.h new file mode 100644 index 000000000..bc3a93b9b --- /dev/null +++ b/mm/2s2h/BenGui/Notification.h @@ -0,0 +1,37 @@ +#ifndef NOTIFICATION_H +#define NOTIFICATION_H +#ifdef __cplusplus + +#include +#include + +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 +#endif // NOTIFICATION_H diff --git a/mm/2s2h/BenGui/SearchableMenuItems.h b/mm/2s2h/BenGui/SearchableMenuItems.h index 376aa379a..fa93b8ca7 100644 --- a/mm/2s2h/BenGui/SearchableMenuItems.h +++ b/mm/2s2h/BenGui/SearchableMenuItems.h @@ -2,6 +2,7 @@ #include "2s2h/DeveloperTools/DeveloperTools.h" #include "UIWidgets.hpp" #include "BenMenuBar.h" +#include "Notification.h" #include "macros.h" #include "variables.h" #include @@ -420,6 +421,10 @@ static const std::unordered_map timeStopOptions = { { TIME_STOP_TEMPLES_DUNGEONS, "Temples + Mini Dungeons" }, }; +static const std::unordered_map notificationPosition = { + { 0, "Top Left" }, { 1, "Top Right" }, { 2, "Bottom Left" }, { 3, "Bottom Right" }, { 4, "Hidden" }, +}; + void FreeLookPitchMinMax() { f32 maxY = CVarGetFloat("gEnhancements.Camera.FreeLook.MaxPitch", 72.0f); f32 minY = CVarGetFloat("gEnhancements.Camera.FreeLook.MinPitch", -49.0f); @@ -716,6 +721,44 @@ void AddSettings() { WIDGET_WINDOW_BUTTON, { .size = UIWidgets::Sizes::Inline, .windowName = "2S2H Input Editor" } } } } }); + settingsSidebar.push_back({ "Notifications", + 1, + { { + { "Position", + "gNotifications.Position", + "Which corner of the screen notifications appear in.", + WIDGET_CVAR_COMBOBOX, + { .defaultVariant = 3, .comboBoxOptions = notificationPosition } }, + { "Duration: %.0f seconds", + "gNotifications.Duration", + "How long notifications are displayed for.", + WIDGET_CVAR_SLIDER_FLOAT, + { .min = 300.0f, .max = 3000.0f, .defaultVariant = 1000.0f } }, + { "Background Opacity: %.0f%%", + "gNotifications.BgOpacity", + "How opaque the background of notifications is.", + WIDGET_CVAR_SLIDER_FLOAT, + { .min = 0.0f, .max = 100.0f, .defaultVariant = 50.0f, .isPercentage = true } }, + { "Size %.1f", + "gNotifications.Size", + "How large notifications are.", + WIDGET_CVAR_SLIDER_FLOAT, + { .min = 100.0f, .max = 500.0f, .defaultVariant = 180.0f } }, + { "Test Notification", + "", + "Displays a test notification.", + WIDGET_BUTTON, + {}, + [](widgetInfo& info) { + Notification::Emit({ + .itemIcon = "__OTR__icon_item_24_static_yar/gQuestIconGoldSkulltulaTex", + .prefix = "This", + .message = "is a", + .suffix = "test.", + }); + } }, + } } }); + if (CVarGetInteger("gSettings.SidebarSearch", 0)) { settingsSidebar.insert(settingsSidebar.begin() + searchSidebarIndex, searchSidebarEntry); } diff --git a/mm/2s2h/BenPort.cpp b/mm/2s2h/BenPort.cpp index e8a08471a..dbb56d2af 100644 --- a/mm/2s2h/BenPort.cpp +++ b/mm/2s2h/BenPort.cpp @@ -55,6 +55,7 @@ CrowdControl* CrowdControl::Instance; #include "2s2h/DeveloperTools/DebugConsole.h" #include "2s2h/DeveloperTools/DeveloperTools.h" #include "2s2h/SaveManager/SaveManager.h" +#include "2s2h/ShipUtils.h" // Resource Types/Factories #include "resource/type/Blob.h" @@ -512,6 +513,7 @@ extern "C" void InitOTR() { OTRGlobals::Instance = new OTRGlobals(); GameInteractor::Instance = new GameInteractor(); + LoadGuiTextures(); BenGui::SetupGuiElements(); InitEnhancements(); InitDeveloperTools(); diff --git a/mm/2s2h/DeveloperTools/SaveEditor.cpp b/mm/2s2h/DeveloperTools/SaveEditor.cpp index 4904fba28..1024b2028 100644 --- a/mm/2s2h/DeveloperTools/SaveEditor.cpp +++ b/mm/2s2h/DeveloperTools/SaveEditor.cpp @@ -1,6 +1,7 @@ #include "SaveEditor.h" #include "2s2h/BenGui/UIWidgets.hpp" #include "2s2h/GameInteractor/GameInteractor.h" +#include "2s2h/BenGui/Notification.h" #include "interface/icon_item_dungeon_static/icon_item_dungeon_static.h" #include "archives/icon_item_24_static/icon_item_24_static_yar.h" @@ -2011,24 +2012,6 @@ void SaveEditorWindow::DrawElement() { } } -const char* textureLoad[8] = { gDungeonStrayFairyWoodfallIconTex, - gDungeonStrayFairySnowheadIconTex, - gDungeonStrayFairyGreatBayIconTex, - gDungeonStrayFairyStoneTowerIconTex, - gQuestIconDungeonMapTex, - gQuestIconCompassTex, - gQuestIconSmallKeyTex, - gQuestIconBossKeyTex }; - void SaveEditorWindow::InitElement() { initSafeItemsForInventorySlot(); - - for (TexturePtr entry : gItemIcons) { - const char* path = static_cast(entry); - Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(path, path, ImVec4(1, 1, 1, 1)); - } - for (int i = 0; i <= 7; i++) { - Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(textureLoad[i], textureLoad[i], - ImVec4(1, 1, 1, 1)); - } } diff --git a/mm/2s2h/ShipUtils.cpp b/mm/2s2h/ShipUtils.cpp index 5d50eb62e..2b4c26ce9 100644 --- a/mm/2s2h/ShipUtils.cpp +++ b/mm/2s2h/ShipUtils.cpp @@ -1,6 +1,5 @@ #include "ShipUtils.h" -#include -#include "PR/ultratypes.h" +#include #include "assets/2s2h_assets.h" extern "C" { @@ -8,6 +7,9 @@ extern "C" { extern f32 sNESFontWidths[160]; extern const char* fontTbl[156]; +extern TexturePtr gItemIcons[131]; +extern TexturePtr gQuestIcons[14]; +extern TexturePtr gBombersNotebookPhotos[24]; } // Build vertex coordinates for a quad command @@ -54,3 +56,18 @@ extern "C" TexturePtr Ship_GetCharFontTextureNES(u8 character) { return (TexturePtr)fontTbl[adjustedChar]; } + +void LoadGuiTextures() { + for (TexturePtr entry : gItemIcons) { + const char* path = static_cast(entry); + Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(path, path, ImVec4(1, 1, 1, 1)); + } + for (TexturePtr entry : gQuestIcons) { + const char* path = static_cast(entry); + Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(path, path, ImVec4(1, 1, 1, 1)); + } + for (TexturePtr entry : gBombersNotebookPhotos) { + const char* path = static_cast(entry); + Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(path, path, ImVec4(1, 1, 1, 1)); + } +} diff --git a/mm/2s2h/ShipUtils.h b/mm/2s2h/ShipUtils.h index f30c39aac..8afcb5117 100644 --- a/mm/2s2h/ShipUtils.h +++ b/mm/2s2h/ShipUtils.h @@ -1,10 +1,13 @@ #ifndef SHIP_UTILS_H #define SHIP_UTILS_H -#include +#include #include "PR/ultratypes.h" #ifdef __cplusplus + +void LoadGuiTextures(); + extern "C" { #endif