mirror of
https://github.com/hedge-dev/UnleashedRecomp.git
synced 2026-04-27 04:41:39 +00:00
Implemented achievements menu (WIP)
This commit is contained in:
parent
15e1472684
commit
8c3b50738d
18 changed files with 616 additions and 104 deletions
|
|
@ -75,8 +75,11 @@ set(SWA_PATCHES_CXX_SOURCES
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SWA_UI_CXX_SOURCES
|
set(SWA_UI_CXX_SOURCES
|
||||||
|
"ui/achievement_menu.cpp"
|
||||||
"ui/achievement_overlay.cpp"
|
"ui/achievement_overlay.cpp"
|
||||||
|
"ui/imgui_view.cpp"
|
||||||
"ui/options_menu.cpp"
|
"ui/options_menu.cpp"
|
||||||
|
"ui/sdl_listener.cpp"
|
||||||
"ui/window.cpp"
|
"ui/window.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,7 @@
|
||||||
#include <kernel/memory.h>
|
#include <kernel/memory.h>
|
||||||
#include <xxHashMap.h>
|
#include <xxHashMap.h>
|
||||||
#include <shader/shader_cache.h>
|
#include <shader/shader_cache.h>
|
||||||
#include <ui/achievement_overlay.h>
|
#include <ui/imgui_view.h>
|
||||||
#include <ui/options_menu.h>
|
|
||||||
|
|
||||||
#include "imgui_snapshot.h"
|
#include "imgui_snapshot.h"
|
||||||
#include "imgui_common.h"
|
#include "imgui_common.h"
|
||||||
|
|
@ -1011,8 +1010,10 @@ static void CreateImGuiBackend()
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
|
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
|
io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
|
||||||
OptionsMenu::Init();
|
|
||||||
AchievementOverlay::Init();
|
for (auto& view : GetImGuiViews())
|
||||||
|
view->Init();
|
||||||
|
|
||||||
ImGui_ImplSDL2_InitForOther(Window::s_pWindow);
|
ImGui_ImplSDL2_InitForOther(Window::s_pWindow);
|
||||||
|
|
||||||
g_imFontTexture = std::make_unique<GuestTexture>(ResourceType::Texture);
|
g_imFontTexture = std::make_unique<GuestTexture>(ResourceType::Texture);
|
||||||
|
|
@ -1651,8 +1652,10 @@ static void DrawImGui()
|
||||||
{
|
{
|
||||||
ImGui_ImplSDL2_NewFrame();
|
ImGui_ImplSDL2_NewFrame();
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
AchievementOverlay::Draw();
|
|
||||||
OptionsMenu::Draw();
|
for (auto& view : GetImGuiViews())
|
||||||
|
view->Draw();
|
||||||
|
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
|
|
||||||
auto drawData = ImGui::GetDrawData();
|
auto drawData = ImGui::GetDrawData();
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,12 @@ inline static std::unordered_map<std::string, std::unordered_map<ELanguage, std:
|
||||||
{ ELanguage::English, "Achievements" }
|
{ ELanguage::English, "Achievements" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Achievements_Name_Uppercase",
|
||||||
|
{
|
||||||
|
{ ELanguage::English, "ACHIEVEMENTS" }
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Achievements_Unlock",
|
"Achievements_Unlock",
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
#include <cpu/guest_code.h>
|
#include <cpu/guest_code.h>
|
||||||
#include <kernel/function.h>
|
#include <kernel/function.h>
|
||||||
#include <api/SWA.h>
|
#include <api/SWA.h>
|
||||||
|
#include <ui/achievement_menu.h>
|
||||||
#include <ui/options_menu.h>
|
#include <ui/options_menu.h>
|
||||||
|
#include <app.h>
|
||||||
|
|
||||||
|
float m_ungracefulExitWaitTime = 0.0f;
|
||||||
|
constexpr float m_ungracefulExitWaitThreshold = 3.0f;
|
||||||
|
|
||||||
void CHudPauseAddOptionsItemMidAsmHook(PPCRegister& pThis)
|
void CHudPauseAddOptionsItemMidAsmHook(PPCRegister& pThis)
|
||||||
{
|
{
|
||||||
|
|
@ -11,7 +16,7 @@ void CHudPauseAddOptionsItemMidAsmHook(PPCRegister& pThis)
|
||||||
GuestToHostFunction<int>(0x824AE690, pThis.u32, menu.get(), name.get());
|
GuestToHostFunction<int>(0x824AE690, pThis.u32, menu.get(), name.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InjectOptionsBehaviour(uint32_t pThis, uint32_t count)
|
bool InjectMenuBehaviour(uint32_t pThis, uint32_t count)
|
||||||
{
|
{
|
||||||
auto pHudPause = (SWA::CHudPause*)g_memory.Translate(pThis);
|
auto pHudPause = (SWA::CHudPause*)g_memory.Translate(pThis);
|
||||||
auto cursorIndex = *(be<uint32_t>*)g_memory.Translate(4 * (*(be<uint32_t>*)g_memory.Translate(pThis + 0x19C) + 0x68) + pThis);
|
auto cursorIndex = *(be<uint32_t>*)g_memory.Translate(4 * (*(be<uint32_t>*)g_memory.Translate(pThis + 0x19C) + 0x68) + pThis);
|
||||||
|
|
@ -35,6 +40,19 @@ bool InjectOptionsBehaviour(uint32_t pThis, uint32_t count)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto pInputState = SWA::CInputState::GetInstance())
|
||||||
|
{
|
||||||
|
if (pInputState->GetPadState().IsTapped(SWA::eKeyState_Select))
|
||||||
|
{
|
||||||
|
AchievementMenu::Open();
|
||||||
|
|
||||||
|
pHudPause->m_Action = SWA::eActionType_Undefined;
|
||||||
|
pHudPause->m_Transition = SWA::eTransitionType_SubMenu;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (pHudPause->m_Status == SWA::eStatusType_Accept)
|
if (pHudPause->m_Status == SWA::eStatusType_Accept)
|
||||||
{
|
{
|
||||||
if (cursorIndex == count - 2)
|
if (cursorIndex == count - 2)
|
||||||
|
|
@ -62,14 +80,14 @@ bool CHudPauseItemCountMidAsmHook(PPCRegister& pThis, PPCRegister& count)
|
||||||
{
|
{
|
||||||
count.u32 += 1;
|
count.u32 += 1;
|
||||||
|
|
||||||
return InjectOptionsBehaviour(pThis.u32, count.u32);
|
return InjectMenuBehaviour(pThis.u32, count.u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CHudPauseVillageItemCountMidAsmHook(PPCRegister& pThis, PPCRegister& count)
|
void CHudPauseVillageItemCountMidAsmHook(PPCRegister& pThis, PPCRegister& count)
|
||||||
{
|
{
|
||||||
count.u32 += 1;
|
count.u32 += 1;
|
||||||
|
|
||||||
InjectOptionsBehaviour(pThis.u32, count.u32);
|
InjectMenuBehaviour(pThis.u32, count.u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CHudPauseMiscItemCountMidAsmHook(PPCRegister& count)
|
bool CHudPauseMiscItemCountMidAsmHook(PPCRegister& count)
|
||||||
|
|
@ -82,28 +100,45 @@ bool CHudPauseMiscItemCountMidAsmHook(PPCRegister& count)
|
||||||
|
|
||||||
bool CHudPauseMiscInjectOptionsMidAsmHook(PPCRegister& pThis)
|
bool CHudPauseMiscInjectOptionsMidAsmHook(PPCRegister& pThis)
|
||||||
{
|
{
|
||||||
return InjectOptionsBehaviour(pThis.u32, 3);
|
return InjectMenuBehaviour(pThis.u32, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SWA::CHudPause::Update
|
// SWA::CHudPause::Update
|
||||||
PPC_FUNC_IMPL(__imp__sub_824B0930);
|
PPC_FUNC_IMPL(__imp__sub_824B0930);
|
||||||
PPC_FUNC(sub_824B0930)
|
PPC_FUNC(sub_824B0930)
|
||||||
{
|
{
|
||||||
if (!OptionsMenu::s_isVisible || !OptionsMenu::s_isPause)
|
auto pHudPause = (SWA::CHudPause*)g_memory.Translate(ctx.r3.u32);
|
||||||
{
|
auto pInputState = SWA::CInputState::GetInstance();
|
||||||
__imp__sub_824B0930(ctx, base);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto pInputState = SWA::CInputState::GetInstance())
|
m_ungracefulExitWaitTime += g_deltaTime;
|
||||||
|
|
||||||
|
// TODO: disable Start button closing menu.
|
||||||
|
if (AchievementMenu::s_isVisible)
|
||||||
|
{
|
||||||
|
// HACK: wait for transition to finish before restoring control.
|
||||||
|
if (m_ungracefulExitWaitThreshold >= m_ungracefulExitWaitTime)
|
||||||
|
__imp__sub_824B0930(ctx, base);
|
||||||
|
|
||||||
|
if (pInputState->GetPadState().IsTapped(SWA::eKeyState_B))
|
||||||
|
{
|
||||||
|
AchievementMenu::Close();
|
||||||
|
|
||||||
|
GuestToHostFunction<int>(0x824AFD28, pHudPause, 0, 1, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (OptionsMenu::s_isVisible && OptionsMenu::s_isPause)
|
||||||
{
|
{
|
||||||
// TODO: disable Start button closing menu.
|
|
||||||
if (OptionsMenu::CanClose() && pInputState->GetPadState().IsTapped(SWA::eKeyState_B))
|
if (OptionsMenu::CanClose() && pInputState->GetPadState().IsTapped(SWA::eKeyState_B))
|
||||||
{
|
{
|
||||||
OptionsMenu::Close();
|
OptionsMenu::Close();
|
||||||
|
|
||||||
// Re-open pause menu.
|
GuestToHostFunction<int>(0x824AFD28, pHudPause, 0, 0, 0, 1);
|
||||||
GuestToHostFunction<int>(0x824AFD28, ctx.r3.u32, 0, 0, 0, 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_ungracefulExitWaitTime = 0.0f;
|
||||||
|
|
||||||
|
__imp__sub_824B0930(ctx, base);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
370
UnleashedRecomp/ui/achievement_menu.cpp
Normal file
370
UnleashedRecomp/ui/achievement_menu.cpp
Normal file
|
|
@ -0,0 +1,370 @@
|
||||||
|
#include "achievement_menu.h"
|
||||||
|
#include "imgui_utils.h"
|
||||||
|
#include <xdbf_wrapper.h>
|
||||||
|
#include <api/SWA.h>
|
||||||
|
#include <gpu/video.h>
|
||||||
|
#include <locale/locale.h>
|
||||||
|
#include <user/achievement_data.h>
|
||||||
|
#include <user/config.h>
|
||||||
|
#include <app.h>
|
||||||
|
#include <exports.h>
|
||||||
|
|
||||||
|
AchievementMenu m_achievementMenu;
|
||||||
|
|
||||||
|
static std::vector<Achievement> g_achievements;
|
||||||
|
static std::unordered_map<uint16_t, GuestTexture*> g_achievementTextures;
|
||||||
|
|
||||||
|
static ImFont* g_fntSeurat;
|
||||||
|
static ImFont* g_fntNewRodin;
|
||||||
|
|
||||||
|
static int g_firstVisibleRowIndex;
|
||||||
|
static int g_prevSelectedRowIndex;
|
||||||
|
static int g_selectedRowIndex;
|
||||||
|
static double g_rowSelectionTime;
|
||||||
|
|
||||||
|
static bool g_upWasHeld;
|
||||||
|
static bool g_downWasHeld;
|
||||||
|
|
||||||
|
static void ResetSelection()
|
||||||
|
{
|
||||||
|
g_firstVisibleRowIndex = 0;
|
||||||
|
g_selectedRowIndex = 0;
|
||||||
|
g_prevSelectedRowIndex = 0;
|
||||||
|
g_rowSelectionTime = ImGui::GetTime();
|
||||||
|
g_upWasHeld = false;
|
||||||
|
g_downWasHeld = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AchievementMenu::Init()
|
||||||
|
{
|
||||||
|
auto& io = ImGui::GetIO();
|
||||||
|
|
||||||
|
constexpr float FONT_SCALE = 2.0f;
|
||||||
|
|
||||||
|
g_fntSeurat = io.Fonts->AddFontFromFileTTF("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE);
|
||||||
|
g_fntNewRodin = io.Fonts->AddFontFromFileTTF("FOT-NewRodinPro-UB.otf", 20.0f * FONT_SCALE);
|
||||||
|
|
||||||
|
g_achievements = g_xdbf.GetAchievements((EXDBFLanguage)Config::Language.Value);
|
||||||
|
|
||||||
|
for (auto& achievement : g_achievements)
|
||||||
|
{
|
||||||
|
auto texture = LoadTexture((uint8_t*)achievement.pImageBuffer, achievement.ImageBufferSize);
|
||||||
|
g_achievementTextures[achievement.ID] = texture.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawContainer(ImVec2 min, ImVec2 max, ImU32 gradientTop, ImU32 gradientBottom, float cornerRadius = 25.0f)
|
||||||
|
{
|
||||||
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
|
||||||
|
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 };
|
||||||
|
|
||||||
|
// TODO: add a drop shadow.
|
||||||
|
|
||||||
|
SetGradient(min, max, gradientTop, gradientBottom);
|
||||||
|
drawList->AddConvexPolyFilled(vertices, IM_ARRAYSIZE(vertices), IM_COL32(255, 255, 255, 255));
|
||||||
|
ResetGradient();
|
||||||
|
|
||||||
|
drawList->AddPolyline(vertices, IM_ARRAYSIZE(vertices), IM_COL32(247, 247, 247, 255), true, Scale(2.5f));
|
||||||
|
|
||||||
|
for (int i = 0; i < IM_ARRAYSIZE(vertices); i++)
|
||||||
|
{
|
||||||
|
vertices[i].x -= 0.4f;
|
||||||
|
vertices[i].y -= 0.2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto colLineTop = IM_COL32(165, 170, 165, 230);
|
||||||
|
auto colLineBottom = IM_COL32(190, 190, 190, 230);
|
||||||
|
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.x, min.y + 20.0f }, { max.x, max.y - 5.0f });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawSelectionContainer(ImVec2 min, ImVec2 max)
|
||||||
|
{
|
||||||
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
|
||||||
|
auto cornerRadius = Scale(10.0f);
|
||||||
|
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 };
|
||||||
|
|
||||||
|
SetGradient(min, max, IM_COL32(255, 246, 0, 129), IM_COL32(255, 194, 0, 118));
|
||||||
|
drawList->AddConvexPolyFilled(vertices, IM_ARRAYSIZE(vertices), IM_COL32(255, 255, 255, 255));
|
||||||
|
ResetGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawHeaderContainer(const char* text)
|
||||||
|
{
|
||||||
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
|
||||||
|
ImVec2 min = { Scale(256.0f), Scale(138.0f) };
|
||||||
|
ImVec2 max = { Scale(556.0f), Scale(185.0f) };
|
||||||
|
|
||||||
|
DrawContainer(min, max, IM_COL32(140, 142, 140, 201), IM_COL32(66, 65, 66, 234), Scale(23.0f));
|
||||||
|
drawList->PopClipRect();
|
||||||
|
|
||||||
|
auto textSize = g_fntNewRodin->CalcTextSizeA(Scale(26.0f), FLT_MAX, 0.0f, text);
|
||||||
|
|
||||||
|
// TODO: skew this text and apply bevel.
|
||||||
|
DrawTextWithOutline<int>(g_fntNewRodin, Scale(26.0f), { min.x + Scale(20.0f), min.y + textSize.y / 2.0f - 3.0f }, IM_COL32(255, 255, 255, 255), text, Scale(3), IM_COL32(0, 0, 0, 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawAchievement(int rowIndex, float yOffset, Achievement& achievement, bool isUnlocked)
|
||||||
|
{
|
||||||
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
|
||||||
|
auto clipRectMin = drawList->GetClipRectMin();
|
||||||
|
auto clipRectMax = drawList->GetClipRectMax();
|
||||||
|
|
||||||
|
auto itemWidth = Scale(708.0f);
|
||||||
|
auto itemHeight = Scale(94.0f);
|
||||||
|
auto itemMarginX = Scale(13.0f);
|
||||||
|
auto imageMarginX = Scale(25.0f);
|
||||||
|
auto imageMarginY = Scale(18.0f);
|
||||||
|
auto imageSize = Scale(60.0f);
|
||||||
|
|
||||||
|
ImVec2 min = { itemMarginX + clipRectMin.x, clipRectMin.y + itemHeight * rowIndex + yOffset };
|
||||||
|
ImVec2 max = { itemMarginX + min.x + itemWidth, min.y + itemHeight };
|
||||||
|
|
||||||
|
auto icon = g_achievementTextures[achievement.ID];
|
||||||
|
auto isSelected = rowIndex == g_selectedRowIndex;
|
||||||
|
|
||||||
|
if (isSelected)
|
||||||
|
DrawSelectionContainer(min, max);
|
||||||
|
|
||||||
|
auto alpha = isUnlocked ? 255 : 127;
|
||||||
|
auto desc = isUnlocked ? achievement.UnlockedDesc.c_str() : achievement.LockedDesc.c_str();
|
||||||
|
auto fontSize = Scale(24.0f);
|
||||||
|
auto textSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, desc);
|
||||||
|
auto textX = min.x + imageMarginX + imageSize + itemMarginX * 2.0f;
|
||||||
|
auto textMarqueeX = min.x + imageMarginX + imageSize;
|
||||||
|
auto titleTextY = Scale(20.0f);
|
||||||
|
auto descTextY = Scale(52.0f);
|
||||||
|
auto cmnShadowOffset = Scale(2.0f);
|
||||||
|
auto cmnShadowScale = Scale(0.4f);
|
||||||
|
|
||||||
|
// Draw achievement icon.
|
||||||
|
// TODO: make image greyscale if locked.
|
||||||
|
drawList->AddImage(icon, { min.x + imageMarginX, min.y + imageMarginY }, { min.x + imageMarginX + imageSize, min.y + imageMarginY + imageSize }, { 0, 0 }, { 1, 1 }, IM_COL32(255, 255, 255, alpha));
|
||||||
|
|
||||||
|
drawList->PushClipRect(min, max, true);
|
||||||
|
|
||||||
|
// Draw achievement name.
|
||||||
|
DrawTextWithShadow(g_fntSeurat, fontSize, { textX, min.y + titleTextY }, IM_COL32(252, 243, 5, alpha), achievement.Name.c_str(), cmnShadowOffset, cmnShadowScale, IM_COL32(0, 0, 0, alpha));
|
||||||
|
|
||||||
|
if (isSelected && textX + textSize.x >= max.x)
|
||||||
|
{
|
||||||
|
// Draw achievement description with marquee.
|
||||||
|
DrawTextWithMarqueeShadow(g_fntSeurat, fontSize, { textX, min.y + descTextY }, { textMarqueeX, min.y }, max, IM_COL32(255, 255, 255, alpha), desc, g_rowSelectionTime, 0.9, 250.0, cmnShadowOffset, cmnShadowScale);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Draw achievement description.
|
||||||
|
DrawTextWithShadow(g_fntSeurat, fontSize, { textX, min.y + descTextY }, IM_COL32(255, 255, 255, alpha), desc, cmnShadowOffset, cmnShadowScale, IM_COL32(0, 0, 0, alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
drawList->PopClipRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawContentContainer()
|
||||||
|
{
|
||||||
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
|
||||||
|
ImVec2 min = { Scale(256.0f), Scale(192.0f) };
|
||||||
|
ImVec2 max = { Scale(1026.0f), Scale(601.0f) };
|
||||||
|
|
||||||
|
DrawContainer(min, max, IM_COL32(197, 194, 197, 200), IM_COL32(115, 113, 115, 236), Scale(25.0f));
|
||||||
|
|
||||||
|
auto clipRectMin = drawList->GetClipRectMin();
|
||||||
|
auto clipRectMax = drawList->GetClipRectMax();
|
||||||
|
|
||||||
|
auto itemHeight = Scale(94.0f);
|
||||||
|
auto yOffset = -g_firstVisibleRowIndex * itemHeight + Scale(2.0f);
|
||||||
|
auto rowCount = 0;
|
||||||
|
|
||||||
|
// Draw separators.
|
||||||
|
for (int i = 1; i <= 3; i++)
|
||||||
|
{
|
||||||
|
auto lineMarginLeft = Scale(31.0f);
|
||||||
|
auto lineMarginRight = Scale(46.0f);
|
||||||
|
auto lineMarginY = Scale(2.0f);
|
||||||
|
|
||||||
|
ImVec2 lineMin = { clipRectMin.x + lineMarginLeft, clipRectMin.y + itemHeight * i + lineMarginY };
|
||||||
|
ImVec2 lineMax = { clipRectMax.x - lineMarginRight, clipRectMin.y + itemHeight * i + lineMarginY };
|
||||||
|
|
||||||
|
drawList->AddLine(lineMin, lineMax, IM_COL32(163, 163, 163, 255));
|
||||||
|
drawList->AddLine({ lineMin.x, lineMin.y + Scale(1.0f) }, { lineMax.x, lineMax.y + Scale(1.0f) }, IM_COL32(143, 148, 143, 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto achievement : g_achievements)
|
||||||
|
{
|
||||||
|
if (AchievementData::IsUnlocked(achievement.ID))
|
||||||
|
DrawAchievement(rowCount++, yOffset, achievement, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto achievement : g_achievements)
|
||||||
|
{
|
||||||
|
if (!AchievementData::IsUnlocked(achievement.ID))
|
||||||
|
DrawAchievement(rowCount++, yOffset, achievement, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto inputState = SWA::CInputState::GetInstance();
|
||||||
|
|
||||||
|
bool upIsHeld = inputState->GetPadState().IsDown(SWA::eKeyState_DpadUp) ||
|
||||||
|
inputState->GetPadState().LeftStickVertical > 0.5f;
|
||||||
|
|
||||||
|
bool downIsHeld = inputState->GetPadState().IsDown(SWA::eKeyState_DpadDown) ||
|
||||||
|
inputState->GetPadState().LeftStickVertical < -0.5f;
|
||||||
|
|
||||||
|
bool scrollUp = !g_upWasHeld && upIsHeld;
|
||||||
|
bool scrollDown = !g_downWasHeld && downIsHeld;
|
||||||
|
|
||||||
|
int prevSelectedRowIndex = g_selectedRowIndex;
|
||||||
|
|
||||||
|
if (scrollUp)
|
||||||
|
{
|
||||||
|
--g_selectedRowIndex;
|
||||||
|
if (g_selectedRowIndex < 0)
|
||||||
|
g_selectedRowIndex = rowCount - 1;
|
||||||
|
}
|
||||||
|
else if (scrollDown)
|
||||||
|
{
|
||||||
|
++g_selectedRowIndex;
|
||||||
|
if (g_selectedRowIndex >= rowCount)
|
||||||
|
g_selectedRowIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scrollUp || scrollDown)
|
||||||
|
{
|
||||||
|
g_rowSelectionTime = ImGui::GetTime();
|
||||||
|
g_prevSelectedRowIndex = prevSelectedRowIndex;
|
||||||
|
Game_PlaySound("sys_actstg_pausecursor");
|
||||||
|
}
|
||||||
|
|
||||||
|
g_upWasHeld = upIsHeld;
|
||||||
|
g_downWasHeld = downIsHeld;
|
||||||
|
|
||||||
|
int visibleRowCount = int(floor((clipRectMax.y - clipRectMin.y) / itemHeight));
|
||||||
|
|
||||||
|
bool disableMoveAnimation = false;
|
||||||
|
|
||||||
|
if (g_firstVisibleRowIndex > g_selectedRowIndex)
|
||||||
|
{
|
||||||
|
g_firstVisibleRowIndex = g_selectedRowIndex;
|
||||||
|
disableMoveAnimation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_firstVisibleRowIndex + visibleRowCount - 1 < g_selectedRowIndex)
|
||||||
|
{
|
||||||
|
g_firstVisibleRowIndex = std::max(0, g_selectedRowIndex - visibleRowCount + 1);
|
||||||
|
disableMoveAnimation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disableMoveAnimation)
|
||||||
|
g_prevSelectedRowIndex = g_selectedRowIndex;
|
||||||
|
|
||||||
|
// Pop clip rect from DrawContentContainer
|
||||||
|
drawList->PopClipRect();
|
||||||
|
|
||||||
|
// Draw scroll bar
|
||||||
|
if (rowCount > visibleRowCount)
|
||||||
|
{
|
||||||
|
float cornerRadius = Scale(25.0f);
|
||||||
|
float totalHeight = (clipRectMax.y - clipRectMin.y - cornerRadius) - Scale(3.0f);
|
||||||
|
float heightRatio = float(visibleRowCount) / float(rowCount);
|
||||||
|
float offsetRatio = float(g_firstVisibleRowIndex) / float(rowCount);
|
||||||
|
float offsetX = clipRectMax.x - Scale(31.0f);
|
||||||
|
float offsetY = offsetRatio * totalHeight + clipRectMin.y + Scale(4.0f);
|
||||||
|
float lineThickness = Scale(1.0f);
|
||||||
|
float innerMarginX = Scale(2.0f);
|
||||||
|
float outerMarginX = Scale(16.0f);
|
||||||
|
|
||||||
|
// Outline
|
||||||
|
drawList->AddRect
|
||||||
|
(
|
||||||
|
{ offsetX - lineThickness, clipRectMin.y - lineThickness },
|
||||||
|
{ clipRectMax.x - outerMarginX + lineThickness, max.y - cornerRadius + lineThickness },
|
||||||
|
IM_COL32(255, 255, 255, 155),
|
||||||
|
Scale(0.5f)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Background
|
||||||
|
drawList->AddRectFilledMultiColor
|
||||||
|
(
|
||||||
|
{ offsetX, clipRectMin.y },
|
||||||
|
{ clipRectMax.x - outerMarginX, max.y - cornerRadius },
|
||||||
|
IM_COL32(82, 85, 82, 186),
|
||||||
|
IM_COL32(82, 85, 82, 186),
|
||||||
|
IM_COL32(74, 73, 74, 185),
|
||||||
|
IM_COL32(74, 73, 74, 185)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scroll Bar Outline
|
||||||
|
drawList->AddRectFilledMultiColor
|
||||||
|
(
|
||||||
|
{ offsetX + innerMarginX, offsetY - lineThickness },
|
||||||
|
{ clipRectMax.x - outerMarginX - innerMarginX, offsetY + lineThickness + totalHeight * heightRatio},
|
||||||
|
IM_COL32(185, 185, 185, 255),
|
||||||
|
IM_COL32(185, 185, 185, 255),
|
||||||
|
IM_COL32(172, 172, 172, 255),
|
||||||
|
IM_COL32(172, 172, 172, 255)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scroll Bar
|
||||||
|
drawList->AddRectFilled
|
||||||
|
(
|
||||||
|
{ offsetX + innerMarginX + lineThickness, offsetY },
|
||||||
|
{ clipRectMax.x - outerMarginX - innerMarginX - lineThickness, offsetY + totalHeight * heightRatio },
|
||||||
|
IM_COL32(255, 255, 255, 255)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AchievementMenu::Draw()
|
||||||
|
{
|
||||||
|
if (!s_isVisible)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DrawHeaderContainer(Localise("Achievements_Name_Uppercase").c_str());
|
||||||
|
DrawContentContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AchievementMenu::Open()
|
||||||
|
{
|
||||||
|
s_isVisible = true;
|
||||||
|
|
||||||
|
ResetSelection();
|
||||||
|
Game_PlaySound("sys_actstg_pausewinopen");
|
||||||
|
}
|
||||||
|
|
||||||
|
void AchievementMenu::Close()
|
||||||
|
{
|
||||||
|
s_isVisible = false;
|
||||||
|
|
||||||
|
Game_PlaySound("sys_actstg_pausewinclose");
|
||||||
|
Game_PlaySound("sys_actstg_pausecansel");
|
||||||
|
}
|
||||||
14
UnleashedRecomp/ui/achievement_menu.h
Normal file
14
UnleashedRecomp/ui/achievement_menu.h
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "imgui_view.h"
|
||||||
|
|
||||||
|
class AchievementMenu : ImGuiView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
inline static bool s_isVisible = false;
|
||||||
|
|
||||||
|
void Init() override;
|
||||||
|
void Draw() override;
|
||||||
|
static void Open();
|
||||||
|
static void Close();
|
||||||
|
};
|
||||||
|
|
@ -9,6 +9,8 @@
|
||||||
#include <app.h>
|
#include <app.h>
|
||||||
#include <exports.h>
|
#include <exports.h>
|
||||||
|
|
||||||
|
AchievementOverlay m_achievementOverlay;
|
||||||
|
|
||||||
constexpr double OVERLAY_CONTAINER_MOTION_START = 0.0;
|
constexpr double OVERLAY_CONTAINER_MOTION_START = 0.0;
|
||||||
constexpr double OVERLAY_CONTAINER_MOTION_END = 8.0;
|
constexpr double OVERLAY_CONTAINER_MOTION_END = 8.0;
|
||||||
constexpr double OVERLAY_CONTAINER_FADE_IN_START = 5.0;
|
constexpr double OVERLAY_CONTAINER_FADE_IN_START = 5.0;
|
||||||
|
|
@ -30,7 +32,9 @@ void AchievementOverlay::Init()
|
||||||
{
|
{
|
||||||
auto& io = ImGui::GetIO();
|
auto& io = ImGui::GetIO();
|
||||||
|
|
||||||
g_fntSeurat = io.Fonts->AddFontFromFileTTF("FOT-SeuratPro-M.otf", 30.0f);
|
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)
|
static double ComputeMotion(double frameOffset, double frames)
|
||||||
|
|
@ -39,8 +43,7 @@ static double ComputeMotion(double frameOffset, double frames)
|
||||||
return sqrt(t);
|
return sqrt(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move this somewhere where it can be re-used.
|
static void DrawContainer(ImVec2 min, ImVec2 max, float cornerRadius = 25.0f)
|
||||||
void DrawContainer(ImVec2 min, ImVec2 max, float cornerRadius = 25.0f)
|
|
||||||
{
|
{
|
||||||
auto drawList = ImGui::GetForegroundDrawList();
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
|
||||||
|
|
@ -71,29 +74,27 @@ void DrawContainer(ImVec2 min, ImVec2 max, float cornerRadius = 25.0f)
|
||||||
ImVec2 v6 = { max.x - cornerRadius, max.y };
|
ImVec2 v6 = { max.x - cornerRadius, max.y };
|
||||||
ImVec2 v7 = { min.x, max.y };
|
ImVec2 v7 = { min.x, max.y };
|
||||||
ImVec2 v8 = { min.x, max.y - cornerRadius };
|
ImVec2 v8 = { min.x, max.y - cornerRadius };
|
||||||
|
ImVec2 vertices[] = { v1, v2, v3, v4, v5, v6, v7, v8 };
|
||||||
ImVec2 top[] = { v1, v2, v3, v4 };
|
|
||||||
ImVec2 bottom[] = { v5, v6, v7, v8 };
|
|
||||||
ImVec2 border[] = { v1, v2, v3, v4, v5, v6, v7, v8 };
|
|
||||||
|
|
||||||
auto colourMotion = ComputeMotion(g_isClosing ? OVERLAY_CONTAINER_MOTION_START : OVERLAY_CONTAINER_FADE_IN_START,
|
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);
|
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 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 colGradientTop = IM_COL32(197, 194, 197, (int)CubicEase(g_isClosing ? 200 : 0, g_isClosing ? 0 : 200, colourMotion));
|
||||||
auto colGradientBottom = IM_COL32(121, 120, 121, (int)CubicEase(g_isClosing ? 236 : 0, g_isClosing ? 0 : 236, colourMotion)); // TODO: match gradient used by the game (115, 113, 115, 236).
|
auto colGradientBottom = IM_COL32(115, 113, 115, (int)CubicEase(g_isClosing ? 236 : 0, g_isClosing ? 0 : 236, colourMotion));
|
||||||
|
|
||||||
// TODO: add a drop shadow.
|
// TODO: add a drop shadow.
|
||||||
|
|
||||||
drawList->AddConvexPolyFilled(top, IM_ARRAYSIZE(top), colGradientTop);
|
SetGradient(min, max, colGradientTop, colGradientBottom);
|
||||||
drawList->AddRectFilledMultiColor({ min.x, min.y + cornerRadius }, { max.x, max.y - cornerRadius }, colGradientTop, colGradientTop, colGradientBottom, colGradientBottom);
|
drawList->AddConvexPolyFilled(vertices, IM_ARRAYSIZE(vertices), IM_COL32(255, 255, 255, 255));
|
||||||
drawList->AddConvexPolyFilled(bottom, IM_ARRAYSIZE(bottom), colGradientBottom);
|
ResetGradient();
|
||||||
drawList->AddPolyline(border, IM_ARRAYSIZE(border), 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(border); i++)
|
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++)
|
||||||
{
|
{
|
||||||
border[i].x -= 0.4f;
|
vertices[i].x -= 0.4f;
|
||||||
border[i].y -= 0.2f;
|
vertices[i].y -= 0.2f;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto lineAlpha = (int)CubicEase(g_isClosing ? 230 : 0, g_isClosing ? 0 : 230, colourMotion);
|
auto lineAlpha = (int)CubicEase(g_isClosing ? 230 : 0, g_isClosing ? 0 : 230, colourMotion);
|
||||||
|
|
@ -102,13 +103,13 @@ void DrawContainer(ImVec2 min, ImVec2 max, float cornerRadius = 25.0f)
|
||||||
auto lineThickness = Scale(1.0f);
|
auto lineThickness = Scale(1.0f);
|
||||||
|
|
||||||
// Top left corner bottom to top left corner top.
|
// Top left corner bottom to top left corner top.
|
||||||
drawList->AddLine(border[0], border[1], colLineTop, lineThickness * 0.5f);
|
drawList->AddLine(vertices[0], vertices[1], colLineTop, lineThickness * 0.5f);
|
||||||
|
|
||||||
// Top left corner bottom to bottom left.
|
// Top left corner bottom to bottom left.
|
||||||
drawList->AddRectFilledMultiColor({ border[0].x - 0.2f, border[0].y }, { border[6].x + lineThickness - 0.2f, border[6].y }, colLineTop, colLineTop, colLineBottom, colLineBottom);
|
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.
|
// Top left corner top to top right.
|
||||||
drawList->AddLine(border[1], border[2], colLineTop, lineThickness);
|
drawList->AddLine(vertices[1], vertices[2], colLineTop, lineThickness);
|
||||||
|
|
||||||
drawList->PushClipRect(min, max);
|
drawList->PushClipRect(min, max);
|
||||||
}
|
}
|
||||||
|
|
@ -151,8 +152,11 @@ void AchievementOverlay::Draw()
|
||||||
|
|
||||||
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));
|
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));
|
||||||
|
|
||||||
DrawTextWithShadow(g_fntSeurat, fontSize, { min.x + textX + (longestTextSize - headerTextSize.x) / 2.0f, min.y + 30.0f}, IM_COL32(252, 243, 5, alpha), strAchievementUnlocked, 2.0f, IM_COL32(0, 0, 0, alpha));
|
auto cmnShadowOffset = Scale(2.0f);
|
||||||
DrawTextWithShadow(g_fntSeurat, fontSize, { min.x + textX + (longestTextSize - bodyTextSize.x) / 2.0f, min.y + 68.0f}, IM_COL32(255, 255, 255, alpha), strAchievementName, 2.0f, IM_COL32(0, 0, 0, alpha));
|
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
|
// Pop clip rect from DrawContainer
|
||||||
drawList->PopClipRect();
|
drawList->PopClipRect();
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
struct AchievementOverlay
|
#include "imgui_view.h"
|
||||||
|
|
||||||
|
class AchievementOverlay : ImGuiView
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
inline static bool s_isVisible = false;
|
inline static bool s_isVisible = false;
|
||||||
|
|
||||||
static void Init();
|
void Init() override;
|
||||||
static void Draw();
|
void Draw() override;
|
||||||
static void Open(int id);
|
static void Open(int id);
|
||||||
static void Close();
|
static void Close();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,46 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <gpu/imgui_common.h>
|
||||||
|
|
||||||
|
static std::vector<std::unique_ptr<ImGuiCallbackData>> g_callbackData;
|
||||||
|
static uint32_t g_callbackDataIndex = 0;
|
||||||
|
|
||||||
|
static ImGuiCallbackData* AddCallback(ImGuiCallback callback)
|
||||||
|
{
|
||||||
|
if (g_callbackDataIndex >= g_callbackData.size())
|
||||||
|
g_callbackData.emplace_back(std::make_unique<ImGuiCallbackData>());
|
||||||
|
|
||||||
|
auto& callbackData = g_callbackData[g_callbackDataIndex];
|
||||||
|
++g_callbackDataIndex;
|
||||||
|
|
||||||
|
ImGui::GetForegroundDrawList()->AddCallback(reinterpret_cast<ImDrawCallback>(callback), callbackData.get());
|
||||||
|
|
||||||
|
return callbackData.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetGradient(const ImVec2& min, const ImVec2& max, ImU32 top, ImU32 bottom)
|
||||||
|
{
|
||||||
|
auto callbackData = AddCallback(ImGuiCallback::SetGradient);
|
||||||
|
callbackData->setGradient.gradientMin[0] = min.x;
|
||||||
|
callbackData->setGradient.gradientMin[1] = min.y;
|
||||||
|
callbackData->setGradient.gradientMax[0] = max.x;
|
||||||
|
callbackData->setGradient.gradientMax[1] = max.y;
|
||||||
|
callbackData->setGradient.gradientTop = top;
|
||||||
|
callbackData->setGradient.gradientBottom = bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ResetGradient()
|
||||||
|
{
|
||||||
|
auto callbackData = AddCallback(ImGuiCallback::SetGradient);
|
||||||
|
memset(&callbackData->setGradient, 0, sizeof(callbackData->setGradient));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetShaderModifier(uint32_t shaderModifier)
|
||||||
|
{
|
||||||
|
auto callbackData = AddCallback(ImGuiCallback::SetShaderModifier);
|
||||||
|
callbackData->setShaderModifier.shaderModifier = shaderModifier;
|
||||||
|
}
|
||||||
|
|
||||||
// Aspect ratio aware.
|
// Aspect ratio aware.
|
||||||
static float Scale(float size)
|
static float Scale(float size)
|
||||||
{
|
{
|
||||||
|
|
@ -43,28 +84,64 @@ static void DrawTextWithMarquee(const ImFont* font, float fontSize, const ImVec2
|
||||||
drawList->PopClipRect();
|
drawList->PopClipRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DrawTextWithOutline(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 color, const char* text, int32_t outlineSize, ImU32 outlineColor)
|
template<typename T>
|
||||||
|
static void DrawTextWithOutline(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 color, const char* text, T outlineSize, ImU32 outlineColor)
|
||||||
{
|
{
|
||||||
auto drawList = ImGui::GetForegroundDrawList();
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
|
||||||
// TODO: This is very inefficient!
|
if constexpr (std::is_same_v<float, T> || std::is_same_v<double, T>)
|
||||||
for (int32_t i = -outlineSize + 1; i < outlineSize; i++)
|
|
||||||
{
|
{
|
||||||
for (int32_t j = -outlineSize + 1; j < outlineSize; j++)
|
// TODO: This is still very inefficient!
|
||||||
drawList->AddText(font, fontSize, { pos.x + i, pos.y + j }, outlineColor, text);
|
for (float i = -outlineSize; i <= outlineSize; i += 0.5f)
|
||||||
|
{
|
||||||
|
for (float j = -outlineSize; j <= outlineSize; j += 0.5f)
|
||||||
|
{
|
||||||
|
if (i == 0.0f && j == 0.0f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
drawList->AddText(font, fontSize, { pos.x + i, pos.y + j }, outlineColor, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_integral_v<T>)
|
||||||
|
{
|
||||||
|
// TODO: This is very inefficient!
|
||||||
|
for (int32_t i = -outlineSize + 1; i < outlineSize; i++)
|
||||||
|
{
|
||||||
|
for (int32_t j = -outlineSize + 1; j < outlineSize; j++)
|
||||||
|
drawList->AddText(font, fontSize, { pos.x + i, pos.y + j }, outlineColor, text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawList->AddText(font, fontSize, pos, color, text);
|
drawList->AddText(font, fontSize, pos, color, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DrawTextWithShadow(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 colour, const char* text, float offset = 2.5f, ImU32 shadowColour = IM_COL32(0, 0, 0, 255))
|
static void DrawTextWithShadow(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 colour, const char* text, float offset = 2.0f, float radius = 0.4f, ImU32 shadowColour = IM_COL32(0, 0, 0, 255))
|
||||||
{
|
{
|
||||||
auto drawList = ImGui::GetForegroundDrawList();
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
|
||||||
drawList->AddText(font, fontSize, { pos.x + offset, pos.y + offset }, shadowColour, text);
|
DrawTextWithOutline<float>(font, fontSize, { pos.x + offset, pos.y + offset }, shadowColour, text, radius, shadowColour);
|
||||||
drawList->AddText(font, fontSize, pos, colour, text);
|
drawList->AddText(font, fontSize, pos, colour, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void DrawTextWithMarqueeShadow(const ImFont* font, float fontSize, const ImVec2& pos, const ImVec2& min, const ImVec2& max, ImU32 colour, const char* text, double time, double delay, double speed, float offset = 2.0f, float radius = 0.4f, ImU32 shadowColour = IM_COL32(0, 0, 0, 255))
|
||||||
|
{
|
||||||
|
auto drawList = ImGui::GetForegroundDrawList();
|
||||||
|
auto rectWidth = max.x - min.x;
|
||||||
|
auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, text);
|
||||||
|
auto textX = pos.x - fmodf(std::max(0.0, ImGui::GetTime() - (time + delay)) * speed, textSize.x + rectWidth);
|
||||||
|
|
||||||
|
drawList->PushClipRect(min, max, true);
|
||||||
|
|
||||||
|
if (textX <= pos.x)
|
||||||
|
DrawTextWithShadow(font, fontSize, { textX, pos.y }, colour, text, offset, radius, shadowColour);
|
||||||
|
|
||||||
|
if (textX + textSize.x < pos.x)
|
||||||
|
DrawTextWithShadow(font, fontSize, { textX + textSize.x + rectWidth, pos.y }, colour, text, offset, radius, shadowColour);
|
||||||
|
|
||||||
|
drawList->PopClipRect();
|
||||||
|
}
|
||||||
|
|
||||||
static float Lerp(float a, float b, float t)
|
static float Lerp(float a, float b, float t)
|
||||||
{
|
{
|
||||||
return a + (b - a) * t;
|
return a + (b - a) * t;
|
||||||
|
|
|
||||||
7
UnleashedRecomp/ui/imgui_view.cpp
Normal file
7
UnleashedRecomp/ui/imgui_view.cpp
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#include "imgui_view.h"
|
||||||
|
|
||||||
|
std::vector<IImGuiView*>& GetImGuiViews()
|
||||||
|
{
|
||||||
|
static std::vector<IImGuiView*> g_imGuiViews;
|
||||||
|
return g_imGuiViews;
|
||||||
|
}
|
||||||
23
UnleashedRecomp/ui/imgui_view.h
Normal file
23
UnleashedRecomp/ui/imgui_view.h
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
class IImGuiView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~IImGuiView() = default;
|
||||||
|
virtual void Init() = 0;
|
||||||
|
virtual void Draw() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<IImGuiView*>& GetImGuiViews();
|
||||||
|
|
||||||
|
class ImGuiView : public IImGuiView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ImGuiView()
|
||||||
|
{
|
||||||
|
GetImGuiViews().emplace_back(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Init() override {}
|
||||||
|
void Draw() override {}
|
||||||
|
};
|
||||||
|
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
#include <patches/audio_patches.h>
|
#include <patches/audio_patches.h>
|
||||||
|
|
||||||
|
OptionsMenu m_optionsMenu;
|
||||||
|
|
||||||
constexpr float COMMON_PADDING_POS_Y = 118.0f;
|
constexpr float COMMON_PADDING_POS_Y = 118.0f;
|
||||||
constexpr float COMMON_PADDING_POS_X = 30.0f;
|
constexpr float COMMON_PADDING_POS_X = 30.0f;
|
||||||
constexpr float INFO_CONTAINER_POS_X = 870.0f;
|
constexpr float INFO_CONTAINER_POS_X = 870.0f;
|
||||||
|
|
@ -39,45 +41,6 @@ void OptionsMenu::Init()
|
||||||
g_newRodinFont = io.Fonts->AddFontFromFileTTF("FOT-NewRodinPro-DB.otf", 20.0f * FONT_SCALE);
|
g_newRodinFont = io.Fonts->AddFontFromFileTTF("FOT-NewRodinPro-DB.otf", 20.0f * FONT_SCALE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<std::unique_ptr<ImGuiCallbackData>> g_callbackData;
|
|
||||||
static uint32_t g_callbackDataIndex = 0;
|
|
||||||
|
|
||||||
static ImGuiCallbackData* AddCallback(ImGuiCallback callback)
|
|
||||||
{
|
|
||||||
if (g_callbackDataIndex >= g_callbackData.size())
|
|
||||||
g_callbackData.emplace_back(std::make_unique<ImGuiCallbackData>());
|
|
||||||
|
|
||||||
auto& callbackData = g_callbackData[g_callbackDataIndex];
|
|
||||||
++g_callbackDataIndex;
|
|
||||||
|
|
||||||
ImGui::GetForegroundDrawList()->AddCallback(reinterpret_cast<ImDrawCallback>(callback), callbackData.get());
|
|
||||||
|
|
||||||
return callbackData.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void SetGradient(const ImVec2& min, const ImVec2& max, ImU32 top, ImU32 bottom)
|
|
||||||
{
|
|
||||||
auto callbackData = AddCallback(ImGuiCallback::SetGradient);
|
|
||||||
callbackData->setGradient.gradientMin[0] = min.x;
|
|
||||||
callbackData->setGradient.gradientMin[1] = min.y;
|
|
||||||
callbackData->setGradient.gradientMax[0] = max.x;
|
|
||||||
callbackData->setGradient.gradientMax[1] = max.y;
|
|
||||||
callbackData->setGradient.gradientTop = top;
|
|
||||||
callbackData->setGradient.gradientBottom = bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ResetGradient()
|
|
||||||
{
|
|
||||||
auto callbackData = AddCallback(ImGuiCallback::SetGradient);
|
|
||||||
memset(&callbackData->setGradient, 0, sizeof(callbackData->setGradient));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void SetShaderModifier(uint32_t shaderModifier)
|
|
||||||
{
|
|
||||||
auto callbackData = AddCallback(ImGuiCallback::SetShaderModifier);
|
|
||||||
callbackData->setShaderModifier.shaderModifier = shaderModifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void DrawScanlineBars()
|
static void DrawScanlineBars()
|
||||||
{
|
{
|
||||||
constexpr uint32_t COLOR0 = IM_COL32(203, 255, 0, 0);
|
constexpr uint32_t COLOR0 = IM_COL32(203, 255, 0, 0);
|
||||||
|
|
@ -135,7 +98,8 @@ static void DrawScanlineBars()
|
||||||
SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);
|
SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);
|
||||||
|
|
||||||
// Options text
|
// Options text
|
||||||
DrawTextWithOutline(g_dfsogeistdFont, Scale(48.0f), { Scale(122.0f), Scale(56.0f) }, IM_COL32(255, 195, 0, 255), "OPTIONS", Scale(4), IM_COL32_BLACK);
|
// TODO: localise this.
|
||||||
|
DrawTextWithOutline<int>(g_dfsogeistdFont, Scale(48.0f), { Scale(122.0f), Scale(56.0f) }, IM_COL32(255, 195, 0, 255), "OPTIONS", Scale(4), IM_COL32_BLACK);
|
||||||
|
|
||||||
// Top bar line
|
// Top bar line
|
||||||
drawList->AddLine(
|
drawList->AddLine(
|
||||||
|
|
@ -417,7 +381,7 @@ static bool DrawCategories()
|
||||||
IM_COL32(128, 255, 0, alpha),
|
IM_COL32(128, 255, 0, alpha),
|
||||||
IM_COL32(255, 192, 0, alpha));
|
IM_COL32(255, 192, 0, alpha));
|
||||||
|
|
||||||
DrawTextWithOutline(
|
DrawTextWithOutline<int>(
|
||||||
g_dfsogeistdFont,
|
g_dfsogeistdFont,
|
||||||
size,
|
size,
|
||||||
min,
|
min,
|
||||||
|
|
@ -752,7 +716,7 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef<T>* conf
|
||||||
IM_COL32(128, 170, 0, 255)
|
IM_COL32(128, 170, 0, 255)
|
||||||
);
|
);
|
||||||
|
|
||||||
DrawTextWithOutline(
|
DrawTextWithOutline<int>(
|
||||||
g_newRodinFont,
|
g_newRodinFont,
|
||||||
size,
|
size,
|
||||||
min,
|
min,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "imgui_view.h"
|
||||||
#include <api/SWA.h>
|
#include <api/SWA.h>
|
||||||
|
|
||||||
struct OptionsMenu
|
struct OptionsMenu : ImGuiView
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
inline static bool s_isVisible = false;
|
inline static bool s_isVisible = false;
|
||||||
|
|
@ -10,8 +11,8 @@ public:
|
||||||
|
|
||||||
inline static SWA::EMenuType s_pauseMenuType;
|
inline static SWA::EMenuType s_pauseMenuType;
|
||||||
|
|
||||||
static void Init();
|
void Init() override;
|
||||||
static void Draw();
|
void Draw() override;
|
||||||
static void Open(bool isPause = false, SWA::EMenuType pauseMenuType = SWA::eMenuType_WorldMap);
|
static void Open(bool isPause = false, SWA::EMenuType pauseMenuType = SWA::eMenuType_WorldMap);
|
||||||
static void Close();
|
static void Close();
|
||||||
|
|
||||||
|
|
|
||||||
7
UnleashedRecomp/ui/sdl_listener.cpp
Normal file
7
UnleashedRecomp/ui/sdl_listener.cpp
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#include "sdl_listener.h"
|
||||||
|
|
||||||
|
std::vector<ISDLEventListener*>& GetEventListeners()
|
||||||
|
{
|
||||||
|
static std::vector<ISDLEventListener*> g_eventListeners;
|
||||||
|
return g_eventListeners;
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/window.h"
|
|
||||||
|
|
||||||
class ISDLEventListener
|
class ISDLEventListener
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -9,12 +7,14 @@ public:
|
||||||
virtual void OnSDLEvent(SDL_Event* event) = 0;
|
virtual void OnSDLEvent(SDL_Event* event) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::vector<ISDLEventListener*>& GetEventListeners();
|
||||||
|
|
||||||
class SDLEventListener : public ISDLEventListener
|
class SDLEventListener : public ISDLEventListener
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SDLEventListener()
|
SDLEventListener()
|
||||||
{
|
{
|
||||||
Window::s_eventListeners.emplace_back(this);
|
GetEventListeners().emplace_back(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnSDLEvent(SDL_Event* event) override {}
|
void OnSDLEvent(SDL_Event* event) override {}
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ int Window_OnSDLEvent(void*, SDL_Event* event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto listener : Window::s_eventListeners)
|
for (auto listener : GetEventListeners())
|
||||||
listener->OnSDLEvent(event);
|
listener->OnSDLEvent(event);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@
|
||||||
#define DEFAULT_WIDTH 1280
|
#define DEFAULT_WIDTH 1280
|
||||||
#define DEFAULT_HEIGHT 720
|
#define DEFAULT_HEIGHT 720
|
||||||
|
|
||||||
class SDLEventListener;
|
|
||||||
|
|
||||||
class Window
|
class Window
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -24,8 +22,6 @@ public:
|
||||||
inline static bool s_isFocused;
|
inline static bool s_isFocused;
|
||||||
inline static bool s_isIconNight;
|
inline static bool s_isIconNight;
|
||||||
|
|
||||||
inline static std::vector<SDLEventListener*> s_eventListeners;
|
|
||||||
|
|
||||||
static SDL_Surface* GetIconSurface(void* pIconBmp, size_t iconSize)
|
static SDL_Surface* GetIconSurface(void* pIconBmp, size_t iconSize)
|
||||||
{
|
{
|
||||||
auto rw = SDL_RWFromMem(pIconBmp, iconSize);
|
auto rw = SDL_RWFromMem(pIconBmp, iconSize);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
bool AchievementData::IsUnlocked(uint16_t id)
|
bool AchievementData::IsUnlocked(uint16_t id)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < sizeof(Data.Records); i++)
|
for (int i = 0; i < sizeof(Data.Records) / sizeof(Record); i++)
|
||||||
{
|
{
|
||||||
if (Data.Records[i].ID == id)
|
if (Data.Records[i].ID == id)
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -19,7 +19,7 @@ void AchievementData::Unlock(uint16_t id)
|
||||||
if (IsUnlocked(id))
|
if (IsUnlocked(id))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (int i = 0; i < sizeof(Data.Records); i++)
|
for (int i = 0; i < sizeof(Data.Records) / sizeof(Record); i++)
|
||||||
{
|
{
|
||||||
if (Data.Records[i].ID == 0)
|
if (Data.Records[i].ID == 0)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue