UnleashedRecomp/UnleashedRecomp/ui/achievement_overlay.cpp
2024-11-28 00:34:57 +00:00

187 lines
7 KiB
C++

#include "achievement_overlay.h"
#include "imgui_utils.h"
#include <user/config.h>
#include <user/achievement_data.h>
#include <gpu/video.h>
#include <kernel/memory.h>
#include <locale/locale.h>
#include <app.h>
#include <exports.h>
AchievementOverlay m_achievementOverlay;
constexpr double OVERLAY_CONTAINER_MOTION_START = 0.0;
constexpr double OVERLAY_CONTAINER_MOTION_END = 8.0;
constexpr double OVERLAY_CONTAINER_FADE_IN_START = 5.0;
constexpr double OVERLAY_CONTAINER_FADE_OUT_START = 4.0;
constexpr double OVERLAY_ELEMENTS_FADE_START = 10.0;
constexpr double OVERLAY_ELEMENTS_FADE_END = 12.0;
constexpr double OVERLAY_DURATION = 5.0;
static bool g_isClosing = false;
static double g_appearTime = 0.0;
static Achievement g_achievement;
static std::unique_ptr<GuestTexture> g_upAchievementIcon;
static ImFont* g_fntSeurat;
void AchievementOverlay::Init()
{
auto& io = ImGui::GetIO();
constexpr float FONT_SCALE = 2.0f;
g_fntSeurat = io.Fonts->AddFontFromFileTTF("FOT-SeuratPro-M.otf", 26.0f * FONT_SCALE);
}
static double ComputeMotion(double frameOffset, double frames)
{
double t = std::clamp((ImGui::GetTime() - g_appearTime - frameOffset / 60.0) / frames * 60.0, 0.0, 1.0);
return sqrt(t);
}
static void DrawContainer(ImVec2 min, ImVec2 max, float cornerRadius = 25.0f)
{
auto drawList = ImGui::GetForegroundDrawList();
auto containerMotion = ComputeMotion(OVERLAY_CONTAINER_MOTION_START, OVERLAY_CONTAINER_MOTION_END);
auto centreX = (min.x + max.x) / 2.0f;
auto centreY = (min.y + max.y) / 2.0f;
if (g_isClosing)
{
min.x = CubicEase(min.x, centreX, containerMotion);
max.x = CubicEase(max.x, centreX, containerMotion);
min.y = CubicEase(min.y, centreY, containerMotion);
max.y = CubicEase(max.y, centreY, containerMotion);
}
else
{
min.x = CubicEase(centreX, min.x, containerMotion);
max.x = CubicEase(centreX, max.x, containerMotion);
min.y = CubicEase(centreY, min.y, containerMotion);
max.y = CubicEase(centreY, max.y, containerMotion);
}
ImVec2 v1 = { min.x, min.y + cornerRadius };
ImVec2 v2 = { min.x + cornerRadius, min.y };
ImVec2 v3 = { max.x, min.y };
ImVec2 v4 = { max.x, min.y + cornerRadius };
ImVec2 v5 = { max.x, max.y - cornerRadius };
ImVec2 v6 = { max.x - cornerRadius, max.y };
ImVec2 v7 = { min.x, max.y };
ImVec2 v8 = { min.x, max.y - cornerRadius };
ImVec2 vertices[] = { v1, v2, v3, v4, v5, v6, v7, v8 };
auto colourMotion = ComputeMotion(g_isClosing ? OVERLAY_CONTAINER_MOTION_START : OVERLAY_CONTAINER_FADE_IN_START,
g_isClosing ? OVERLAY_CONTAINER_FADE_OUT_START : OVERLAY_CONTAINER_MOTION_END);
auto colShadow = IM_COL32(0, 0, 0, (int)CubicEase(g_isClosing ? 156 : 0, g_isClosing ? 0 : 156, colourMotion));
auto colGradientTop = IM_COL32(197, 194, 197, (int)CubicEase(g_isClosing ? 200 : 0, g_isClosing ? 0 : 200, colourMotion));
auto colGradientBottom = IM_COL32(115, 113, 115, (int)CubicEase(g_isClosing ? 236 : 0, g_isClosing ? 0 : 236, colourMotion));
// TODO: add a drop shadow.
SetGradient(min, max, colGradientTop, colGradientBottom);
drawList->AddConvexPolyFilled(vertices, IM_ARRAYSIZE(vertices), IM_COL32(255, 255, 255, 255));
ResetGradient();
drawList->AddPolyline(vertices, IM_ARRAYSIZE(vertices), IM_COL32(247, 247, 247, (int)CubicEase(g_isClosing ? 255 : 0, g_isClosing ? 0 : 255, colourMotion)), true, Scale(2.5f));
for (int i = 0; i < IM_ARRAYSIZE(vertices); i++)
{
vertices[i].x -= 0.4f;
vertices[i].y -= 0.2f;
}
auto lineAlpha = (int)CubicEase(g_isClosing ? 230 : 0, g_isClosing ? 0 : 230, colourMotion);
auto colLineTop = IM_COL32(165, 170, 165, lineAlpha);
auto colLineBottom = IM_COL32(190, 190, 190, lineAlpha);
auto lineThickness = Scale(1.0f);
// Top left corner bottom to top left corner top.
drawList->AddLine(vertices[0], vertices[1], colLineTop, lineThickness * 0.5f);
// Top left corner bottom to bottom left.
drawList->AddRectFilledMultiColor({ vertices[0].x - 0.2f, vertices[0].y }, { vertices[6].x + lineThickness - 0.2f, vertices[6].y }, colLineTop, colLineTop, colLineBottom, colLineBottom);
// Top left corner top to top right.
drawList->AddLine(vertices[1], vertices[2], colLineTop, lineThickness);
drawList->PushClipRect(min, max);
}
void AchievementOverlay::Draw()
{
if (!s_isVisible)
return;
if (ImGui::GetTime() - g_appearTime >= OVERLAY_DURATION)
AchievementOverlay::Close();
auto drawList = ImGui::GetForegroundDrawList();
auto& res = ImGui::GetIO().DisplaySize;
auto strAchievementUnlocked = Localise("Achievements_Unlock").c_str();
auto strAchievementName = g_achievement.Name.c_str();
auto fontSize = 30.0f;
auto headerTextSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, strAchievementUnlocked);
auto bodyTextSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, strAchievementName);
auto longestTextSize = std::max(headerTextSize.x, bodyTextSize.x);
constexpr auto imageX = 25.0f;
constexpr auto imageY = 20.0f;
constexpr auto imageSize = 90.0f;
constexpr auto textX = 140.0f;
auto width = textX + longestTextSize + 30.0f;
ImVec2 min = { (res.x / 2.0f) - (width / 2.0f), 50.0f };
ImVec2 max = { min.x + width, min.y + 125.0f };
DrawContainer(min, max);
auto colourMotion = ComputeMotion(g_isClosing ? OVERLAY_CONTAINER_MOTION_START : OVERLAY_ELEMENTS_FADE_START,
g_isClosing ? OVERLAY_CONTAINER_FADE_OUT_START : OVERLAY_ELEMENTS_FADE_END);
auto alpha = (int)CubicEase(g_isClosing ? 255 : 0, g_isClosing ? 0 : 255, colourMotion);
drawList->AddImage(g_upAchievementIcon.get(), { min.x + imageX, min.y + imageY }, { min.x + imageX + imageSize, min.y + imageY + imageSize }, { 0, 0 }, { 1, 1 }, IM_COL32(255, 255, 255, alpha));
auto cmnShadowOffset = Scale(2.0f);
auto cmnShadowScale = Scale(0.4f);
DrawTextWithShadow(g_fntSeurat, fontSize, { min.x + textX + (longestTextSize - headerTextSize.x) / 2.0f, min.y + 30.0f}, IM_COL32(252, 243, 5, alpha), strAchievementUnlocked, cmnShadowOffset, cmnShadowScale, IM_COL32(0, 0, 0, alpha));
DrawTextWithShadow(g_fntSeurat, fontSize, { min.x + textX + (longestTextSize - bodyTextSize.x) / 2.0f, min.y + 68.0f}, IM_COL32(255, 255, 255, alpha), strAchievementName, cmnShadowOffset, cmnShadowScale, IM_COL32(0, 0, 0, alpha));
// Pop clip rect from DrawContainer
drawList->PopClipRect();
}
void AchievementOverlay::Open(int id)
{
s_isVisible = true;
g_isClosing = false;
g_appearTime = ImGui::GetTime();
g_achievement = g_xdbf.GetAchievement((EXDBFLanguage)Config::Language.Value, id);
g_upAchievementIcon = LoadTexture((uint8_t*)g_achievement.pImageBuffer, g_achievement.ImageBufferSize);
Game_PlaySound("obj_navi_appear");
}
void AchievementOverlay::Close()
{
if (!g_isClosing)
{
g_appearTime = ImGui::GetTime();
g_isClosing = true;
}
if (ImGui::GetTime() - g_appearTime >= OVERLAY_ELEMENTS_FADE_END)
s_isVisible = false;
}