mirror of
https://github.com/hedge-dev/UnleashedRecomp
synced 2026-09-04 02:49:35 -04:00
Options menu, achievements, and installer wizard. (#19)
* Implemented guest-to-host function pointers (WIP) Co-Authored-By: Skyth (Asilkan) <19259897+blueskythlikesclouds@users.noreply.github.com> * function: support more types for function pointers * Initial options menu implementation. * Improve options menu visuals. * Draw fade on borders, center tabs better. * Adjust line sizes, fix tab text centering. * Adjust padding & text sizes. * Fix bar dark gradient effect. * api: ported BlueBlur headers and misc. research * Fix config name padding not getting scaled at different resolutions. * config: use string_view, added method to get value pointer * config: use std::map for reverse enum template * Draw config options manually instead of looping through them. * config: implemented name and enum localisation * config_detail: move implementation to cpp, relocate sources * Implemented accessing options menu via pause and title screen * config: replace MSAA with AntiAliasing enum * options_menu: implemented info panel and text marquee (see TODOs) * Draw selection triangles. * Supersample fonts to 2K. * Implement options menu navigation. * Fix duplicate triangles when selecting options. * Draw scroll bar. * Adjust scroll bar padding. * Further scroll bar padding adjustments. * Draw outer container as an outline. * Improve marquee text scrolling. * CTitleMenu: fix options menu re-entering on A press whilst visible * Make procedural grid pattern more accurate. * Add enum & bool editing. * Update English localisation * Fix input state mapping. * options_menu: hide menu on Y hold * CHudPause: fix crash when opening options menu from village/lab * Implement float slider. * options_menu: round res scale description resolution * options_menu: use config callbacks after setting items * api: fix GameObject layout * camera_patches: implemented camera X/Y invert * options_menu: fix buffered A press selecting first option upon entry * config_locale: update description for Battle Music * config: added Allow Background Input option * options_menu: move ATOC option below Anti-Aliasing * options_menu: only draw header/footer fade in stages * Handle real-time modifications of some video config values. * Converge increments only when holding the left/right button. * Add sound effects to options menu. * Change some sounds used in options menu. * Give the final decide sound to bool toggling. * Add option select animation. * options_menu: only play slider sound between min/max range * Apply category select animation. * config: rename Controls category to Input * Implement intro transition animation for options menu. * audio_patches: implemented music volume * Implement FPS slider. * Prevent ImGui from displaying OS cursor. * Fade container brackets during intro transition. * player_patches: added penalty to Unleash Cancel * config_locale: update English localisation * player_patches: ensure Unleash gauge penalty doesn't dip into negatives * options_menu: fix being unable to press A at least once after opening the menu * CTitleMenu: added open/close sounds to the options menu * audio_patches: implemented Music and SE volume * api: update research * Implemented music volume attenuation for Windows 10+ * api: fix score offset * Add an interval between consecutive playbacks of the slider sound effect in fastIncrement mode * config: implemented enum descriptions * options_menu: fit thumbnail rect to grid, remove menu hide input * options_menu: fix description wrap width * camera_patches: fix FOV at narrow aspect ratios mobile gaming is back on the menu * options_menu: implemented greyed out options and localisation * options_menu: allow providing reasons for greyed out options * audio_patches: check if Windows version is supported * Update PowerRecomp submodule * api: more research * options_menu: forget selected item upon opening * options_menu: restrict XButtonHoming to title and world map * window: always hide mouse cursor The options menu doesn't accept mouse input, so there's not really any point to showing the cursor at all. * Animate category tab background from the center. * Fix clip rect in info panel not getting popped at all times. * Expose texture loader in "video.h". * config: use final names and descriptions, label options to be moved to exports * options_menu: implemented Voice Language (and some misc. clean-up) * Move Voice Language patch to resident_patches * config: added Aspect Ratio option (to be implemented) * options_menu: implemented Subtitles * Remove triple buffering from options menu, turn it to an enum. * window: hide mouse cursor on controller input for windowed mode * window: show window dimensions on title bar when resizing window * api: update research * Accept functions directly in GuestToHostFunction & add memory range asserts. * Add guest_stack_var, improve shared_ptr implementation. * Handle float/double arguments properly in GuestToHostFunction. * CHudPause_patches: allocate options strings on stack * api: update research * guest_stack_var: allow creation without constructing underlying type * memory: make assertions lenient towards nullptr * api: include guest_stack_var in SWA.inl * audio_patches: don't worry about it * Implemented achievement overlay (WIP) * Implemented achievements menu (WIP) * Clean-up, improved animation and layouts * options_menu: fix naming convention * achievements_overlay: implemented queue and hermite interpolation * achievements_menu: implemented animations and improved navigation * achievements_menu: improve animation accuracy * achievements_menu: added timestamps * achievement_data: added checksum and format verification * achievement_menu: improved outro animation * achievement_menu: added total unlocked achievements * achievement_menu: update sprite animation * Update resources submodule * Add installer wizard. * Skip drawing scanlines when height is 0. * Tweak install screen to better match the original * Added arrow circle to installer's header * Move icon header generation to resources submodule * Added missing animations and tweaked other ones for installer * Improve detection for DLC only mode. Add template for message prompts. * Add language picker. * window: update icon resources * Added file_to_c * Fixes to conversion. * Implemented message window * achievement_menu: use selection cursor texture * Update embedded resources * Implemented message window * Merge branch 'bin2c' into options-menu * Update embedded resources * Framework for max width for buttons. * Update embedded resources * Use textures for pause menu containers * audio_patches: check if Windows major version is >=10 Just in case. * installer_wizard: use integer outline for button text * Added arrow circle spinning animation during installation screen * achievement_menu: fix timestamp and scroll bar padding * achievement_overlay: fix achievement name padding * installer_wizard: fix arrow circle spinning animation misaligning * Add Scale and Origin to ImGui shaders. Change text to be squashed. * message_window: implemented mouse input * installer_wizard: implemented message windows * achievement_menu: start marquee before timestamp margin * Fix message box flow. * message_window: use pause container texture * Add extra condition for starting the installer. * message_window: only accept mouse click if option is selected * Implemented safer way to check if the game is loaded * Add queued update when using files pickers. * installer_wizard: implement localisation * installer_wizard: use enum for localisation * message_window: fix visibility persisting after window closes * Fix arrow circle animation and added pulse animation * Come back check space. * Implement ZSTD compression in file_to_c. * Add fade-in/out to installation icons and sleep after hitting 100% * Implement ImGui font atlas caching. * Controller navigation. * Implemented button guide * CTitleStateMenu: fix start button opening old options menu * Update resources submodule * imgui_snapshot: check if game is loaded before accessing XDBF * message_window: added button guide * options_menu: increase button guide side margins * video: disable imgui.ini creation * Use IM_DELETE for deleting the existing font atlas. * Remove redundant FlushViewport call. * Fix ImGui callbacks leaking memory. * Replace unique_ptr reference arguments with raw pointers. * Specialize description for resolution scale by reference. --------- Co-authored-by: Hyper <34012267+hyperbx@users.noreply.github.com> Co-authored-by: PTKay <jp_moura99@outlook.com> Co-authored-by: Dario <dariosamo@gmail.com>
This commit is contained in:
@@ -0,0 +1,678 @@
|
||||
#include "achievement_menu.h"
|
||||
#include "imgui_utils.h"
|
||||
#include <api/SWA.h>
|
||||
#include <gpu/video.h>
|
||||
#include <kernel/xdbf.h>
|
||||
#include <locale/locale.h>
|
||||
#include <ui/button_guide.h>
|
||||
#include <user/achievement_data.h>
|
||||
#include <user/config.h>
|
||||
#include <app.h>
|
||||
#include <exports.h>
|
||||
#include <decompressor.h>
|
||||
#include <res/images/achievements_menu/trophy.dds.h>
|
||||
#include <res/images/common/general_window.dds.h>
|
||||
#include <res/images/common/select_fill.dds.h>
|
||||
#include <gpu/imgui_snapshot.h>
|
||||
|
||||
constexpr double HEADER_CONTAINER_INTRO_MOTION_START = 0;
|
||||
constexpr double HEADER_CONTAINER_INTRO_MOTION_END = 15;
|
||||
constexpr double HEADER_CONTAINER_OUTRO_MOTION_START = 0;
|
||||
constexpr double HEADER_CONTAINER_OUTRO_MOTION_END = 40;
|
||||
constexpr double HEADER_CONTAINER_INTRO_FADE_START = 5;
|
||||
constexpr double HEADER_CONTAINER_INTRO_FADE_END = 14;
|
||||
constexpr double HEADER_CONTAINER_OUTRO_FADE_START = 0;
|
||||
constexpr double HEADER_CONTAINER_OUTRO_FADE_END = 7;
|
||||
|
||||
constexpr double CONTENT_CONTAINER_COMMON_MOTION_START = 11;
|
||||
constexpr double CONTENT_CONTAINER_COMMON_MOTION_END = 12;
|
||||
|
||||
constexpr double COUNTER_INTRO_FADE_START = 15;
|
||||
constexpr double COUNTER_INTRO_FADE_END = 16;
|
||||
|
||||
constexpr double SELECTION_CONTAINER_BREATHE = 30;
|
||||
|
||||
static bool g_isClosing = false;
|
||||
|
||||
static double g_appearTime;
|
||||
|
||||
static std::vector<std::tuple<Achievement, time_t>> g_achievements;
|
||||
|
||||
static ImFont* g_fntSeurat;
|
||||
static ImFont* g_fntNewRodinDB;
|
||||
static ImFont* g_fntNewRodinUB;
|
||||
|
||||
static std::unique_ptr<GuestTexture> g_upTrophyIcon;
|
||||
static std::unique_ptr<GuestTexture> g_upSelectionCursor;
|
||||
static std::unique_ptr<GuestTexture> g_upWindow;
|
||||
|
||||
static int g_firstVisibleRowIndex;
|
||||
static int g_selectedRowIndex;
|
||||
static double g_rowSelectionTime;
|
||||
|
||||
static bool g_upWasHeld;
|
||||
static bool g_downWasHeld;
|
||||
static bool g_leftWasHeld;
|
||||
static bool g_rightWasHeld;
|
||||
static bool g_upRSWasHeld;
|
||||
static bool g_downRSWasHeld;
|
||||
|
||||
static void ResetSelection()
|
||||
{
|
||||
g_firstVisibleRowIndex = 0;
|
||||
g_selectedRowIndex = 0;
|
||||
g_rowSelectionTime = ImGui::GetTime();
|
||||
g_upWasHeld = false;
|
||||
g_downWasHeld = false;
|
||||
}
|
||||
|
||||
static void DrawContainer(ImVec2 min, ImVec2 max, ImU32 gradientTop, ImU32 gradientBottom, float alpha = 1, float cornerRadius = 25)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
DrawPauseContainer(g_upWindow.get(), min, max, alpha);
|
||||
|
||||
drawList->PushClipRect({ min.x, min.y + Scale(20) }, { max.x, max.y - Scale(5) });
|
||||
}
|
||||
|
||||
static void DrawSelectionContainer(ImVec2 min, ImVec2 max)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
static auto breatheStart = ImGui::GetTime();
|
||||
auto alpha = Lerp(1.0f, 0.75f, (sin((ImGui::GetTime() - breatheStart) * (2.0f * M_PI / (55.0f / 60.0f))) + 1.0f) / 2.0f);
|
||||
auto colour = IM_COL32(255, 255, 255, 255 * alpha);
|
||||
|
||||
auto commonWidth = Scale(11);
|
||||
auto commonHeight = Scale(24);
|
||||
|
||||
auto tl = PIXELS_TO_UV_COORDS(64, 64, 0, 0, 11, 24);
|
||||
auto tc = PIXELS_TO_UV_COORDS(64, 64, 11, 0, 8, 24);
|
||||
auto tr = PIXELS_TO_UV_COORDS(64, 64, 19, 0, 11, 24);
|
||||
auto cl = PIXELS_TO_UV_COORDS(64, 64, 0, 24, 11, 2);
|
||||
auto cc = PIXELS_TO_UV_COORDS(64, 64, 11, 24, 8, 2);
|
||||
auto cr = PIXELS_TO_UV_COORDS(64, 64, 19, 24, 11, 2);
|
||||
auto bl = PIXELS_TO_UV_COORDS(64, 64, 0, 26, 11, 24);
|
||||
auto bc = PIXELS_TO_UV_COORDS(64, 64, 11, 26, 8, 24);
|
||||
auto br = PIXELS_TO_UV_COORDS(64, 64, 19, 26, 11, 24);
|
||||
|
||||
drawList->AddImage(g_upSelectionCursor.get(), min, { min.x + commonWidth, min.y + commonHeight }, GET_UV_COORDS(tl), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { min.x + commonWidth, min.y }, { max.x - commonWidth, min.y + commonHeight }, GET_UV_COORDS(tc), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { max.x - commonWidth, min.y }, { max.x, min.y + commonHeight }, GET_UV_COORDS(tr), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { min.x, min.y + commonHeight }, { min.x + commonWidth, max.y - commonHeight }, GET_UV_COORDS(cl), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { min.x + commonWidth, min.y + commonHeight }, { max.x - commonWidth, max.y - commonHeight }, GET_UV_COORDS(cc), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { max.x - commonWidth, min.y + commonHeight }, { max.x, max.y - commonHeight }, GET_UV_COORDS(cr), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { min.x, max.y - commonHeight }, { min.x + commonWidth, max.y }, GET_UV_COORDS(bl), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { min.x + commonWidth, max.y - commonHeight }, { max.x - commonWidth, max.y }, GET_UV_COORDS(bc), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { max.x - commonWidth, max.y - commonHeight }, { max.x, max.y }, GET_UV_COORDS(br), colour);
|
||||
}
|
||||
|
||||
static void DrawHeaderContainer(const char* text)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
auto fontSize = Scale(24);
|
||||
auto textSize = g_fntNewRodinUB->CalcTextSizeA(fontSize, FLT_MAX, 0, text);
|
||||
auto cornerRadius = 23;
|
||||
auto textMarginX = Scale(16) + (Scale(cornerRadius) / 2);
|
||||
|
||||
auto containerMotion = g_isClosing
|
||||
? ComputeMotion(g_appearTime, HEADER_CONTAINER_OUTRO_MOTION_START, HEADER_CONTAINER_OUTRO_MOTION_END)
|
||||
: ComputeMotion(g_appearTime, HEADER_CONTAINER_INTRO_MOTION_START, HEADER_CONTAINER_INTRO_MOTION_END);
|
||||
|
||||
auto colourMotion = g_isClosing
|
||||
? ComputeMotion(g_appearTime, HEADER_CONTAINER_OUTRO_FADE_START, HEADER_CONTAINER_OUTRO_FADE_END)
|
||||
: ComputeMotion(g_appearTime, HEADER_CONTAINER_INTRO_FADE_START, HEADER_CONTAINER_INTRO_FADE_END);
|
||||
|
||||
// Slide animation.
|
||||
auto containerMarginX = g_isClosing
|
||||
? Hermite(251, 151, containerMotion)
|
||||
: Hermite(151, 251, containerMotion);
|
||||
|
||||
// Transparency fade animation.
|
||||
auto alpha = g_isClosing
|
||||
? Lerp(1, 0, colourMotion)
|
||||
: Lerp(0, 1, colourMotion);
|
||||
|
||||
ImVec2 min = { Scale(containerMarginX), Scale(136) };
|
||||
ImVec2 max = { min.x + textMarginX * 2 + textSize.x + Scale(5), Scale(196) };
|
||||
|
||||
DrawPauseHeaderContainer(g_upWindow.get(), min, max, alpha);
|
||||
|
||||
// TODO: skew this text and apply bevel.
|
||||
DrawTextWithOutline<int>
|
||||
(
|
||||
g_fntNewRodinUB,
|
||||
fontSize,
|
||||
{ /* X */ min.x + textMarginX, /* Y */ CENTRE_TEXT_VERT(min, max, textSize) - Scale(5) },
|
||||
IM_COL32(255, 255, 255, 255 * alpha),
|
||||
text,
|
||||
3,
|
||||
IM_COL32(0, 0, 0, 255 * alpha)
|
||||
);
|
||||
}
|
||||
|
||||
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(700);
|
||||
auto itemHeight = Scale(94);
|
||||
auto itemMarginX = Scale(18);
|
||||
auto imageMarginX = Scale(25);
|
||||
auto imageMarginY = Scale(18);
|
||||
auto imageSize = Scale(60);
|
||||
|
||||
ImVec2 min = { itemMarginX + clipRectMin.x, clipRectMin.y + itemHeight * rowIndex + yOffset };
|
||||
ImVec2 max = { itemMarginX + min.x + itemWidth, min.y + itemHeight };
|
||||
|
||||
auto icon = g_xdbfTextureCache[achievement.ID];
|
||||
auto isSelected = rowIndex == g_selectedRowIndex;
|
||||
|
||||
if (isSelected)
|
||||
DrawSelectionContainer(min, max);
|
||||
|
||||
auto desc = isUnlocked ? achievement.UnlockedDesc.c_str() : achievement.LockedDesc.c_str();
|
||||
auto fontSize = Scale(24);
|
||||
auto textSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0, desc);
|
||||
auto textX = min.x + imageMarginX + imageSize + itemMarginX * 2;
|
||||
auto textMarqueeX = min.x + imageMarginX + imageSize;
|
||||
auto titleTextY = Scale(20);
|
||||
auto descTextY = Scale(52);
|
||||
|
||||
// Draw achievement icon.
|
||||
// TODO: make icon greyscale if locked?
|
||||
drawList->AddImage
|
||||
(
|
||||
icon,
|
||||
{ /* X */ min.x + imageMarginX, /* Y */ min.y + imageMarginY },
|
||||
{ /* X */ min.x + imageMarginX + imageSize, /* Y */ min.y + imageMarginY + imageSize },
|
||||
{ /* U */ 0, /* V */ 0 },
|
||||
{ /* U */ 1, /* V */ 1 },
|
||||
IM_COL32(255, 255, 255, 255 * (isUnlocked ? 1 : 0.5f))
|
||||
);
|
||||
|
||||
drawList->PushClipRect(min, max, true);
|
||||
|
||||
auto colLockedText = IM_COL32(60, 60, 60, 29);
|
||||
|
||||
auto colTextShadow = isUnlocked
|
||||
? IM_COL32(0, 0, 0, 255)
|
||||
: IM_COL32(60, 60, 60, 28);
|
||||
|
||||
auto shadowOffset = isUnlocked ? 2 : 1;
|
||||
|
||||
// Draw achievement name.
|
||||
DrawTextWithShadow
|
||||
(
|
||||
g_fntSeurat,
|
||||
fontSize,
|
||||
{ textX, min.y + titleTextY },
|
||||
isUnlocked ? IM_COL32(252, 243, 5, 255) : colLockedText,
|
||||
achievement.Name.c_str(),
|
||||
shadowOffset,
|
||||
0.4f,
|
||||
colTextShadow
|
||||
);
|
||||
|
||||
if (isSelected && textX + textSize.x >= max.x - Scale(10))
|
||||
{
|
||||
// Draw achievement description with marquee.
|
||||
DrawTextWithMarqueeShadow
|
||||
(
|
||||
g_fntSeurat,
|
||||
fontSize,
|
||||
{ textX, min.y + descTextY },
|
||||
{ textMarqueeX, min.y },
|
||||
max,
|
||||
isUnlocked ? IM_COL32(255, 255, 255, 255) : colLockedText,
|
||||
desc,
|
||||
g_rowSelectionTime,
|
||||
0.9,
|
||||
250.0,
|
||||
shadowOffset,
|
||||
0.4f,
|
||||
colTextShadow
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Draw achievement description.
|
||||
DrawTextWithShadow
|
||||
(
|
||||
g_fntSeurat,
|
||||
fontSize,
|
||||
{ textX, min.y + descTextY },
|
||||
isUnlocked ? IM_COL32(255, 255, 255, 255) : colLockedText,
|
||||
desc,
|
||||
shadowOffset,
|
||||
0.4f,
|
||||
colTextShadow
|
||||
);
|
||||
}
|
||||
|
||||
drawList->PopClipRect();
|
||||
|
||||
if (!isUnlocked)
|
||||
return;
|
||||
|
||||
auto timestamp = AchievementData::GetTimestamp(achievement.ID);
|
||||
|
||||
if (!timestamp)
|
||||
return;
|
||||
|
||||
char buffer[32];
|
||||
struct tm time;
|
||||
localtime_s(&time, ×tamp);
|
||||
strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M", &time);
|
||||
|
||||
fontSize = Scale(12);
|
||||
textSize = g_fntNewRodinDB->CalcTextSizeA(fontSize, FLT_MAX, 0, buffer);
|
||||
|
||||
auto containerMarginX = Scale(10);
|
||||
auto textMarginX = Scale(8);
|
||||
|
||||
ImVec2 timestampMin = { max.x - containerMarginX - textSize.x - (textMarginX * 2), min.y + titleTextY };
|
||||
ImVec2 timestampMax = { max.x - containerMarginX, min.y + Scale(46) };
|
||||
|
||||
drawList->PushClipRect(min, max, true);
|
||||
|
||||
auto bevelOffset = Scale(6);
|
||||
|
||||
// Left
|
||||
drawList->AddRectFilledMultiColor
|
||||
(
|
||||
timestampMin,
|
||||
{ timestampMin.x + bevelOffset, timestampMax.y },
|
||||
IM_COL32(255, 255, 255, 255),
|
||||
IM_COL32(149, 149, 149, 40),
|
||||
IM_COL32(149, 149, 149, 40),
|
||||
IM_COL32(255, 255, 255, 255)
|
||||
);
|
||||
|
||||
// Right
|
||||
drawList->AddRectFilledMultiColor
|
||||
(
|
||||
{ timestampMax.x - bevelOffset, timestampMin.y },
|
||||
{ timestampMax.x, timestampMax.y },
|
||||
IM_COL32(149, 149, 149, 40),
|
||||
IM_COL32(255, 255, 255, 255),
|
||||
IM_COL32(255, 255, 255, 255),
|
||||
IM_COL32(149, 149, 149, 40)
|
||||
);
|
||||
|
||||
// Centre
|
||||
drawList->AddRectFilled
|
||||
(
|
||||
{ timestampMin.x, timestampMin.y + bevelOffset },
|
||||
{ timestampMax.x, timestampMax.y - bevelOffset },
|
||||
IM_COL32(38, 38, 38, 172)
|
||||
);
|
||||
|
||||
// Top
|
||||
drawList->AddRectFilledMultiColor
|
||||
(
|
||||
timestampMin,
|
||||
{ timestampMax.x, timestampMin.y + bevelOffset },
|
||||
IM_COL32(16, 16, 16, 192),
|
||||
IM_COL32(16, 16, 16, 192),
|
||||
IM_COL32(38, 38, 38, 172),
|
||||
IM_COL32(38, 38, 38, 172)
|
||||
);
|
||||
|
||||
// Bottom
|
||||
drawList->AddRectFilledMultiColor
|
||||
(
|
||||
{ timestampMin.x, timestampMax.y - bevelOffset },
|
||||
{ timestampMax.x, timestampMax.y },
|
||||
IM_COL32(38, 40, 38, 169),
|
||||
IM_COL32(38, 40, 38, 169),
|
||||
IM_COL32(16, 16, 16, 192),
|
||||
IM_COL32(16, 16, 16, 192)
|
||||
);
|
||||
|
||||
// Draw timestamp text.
|
||||
DrawTextWithOutline<int>
|
||||
(
|
||||
g_fntNewRodinDB,
|
||||
fontSize,
|
||||
{ /* X */ CENTRE_TEXT_HORZ(timestampMin, timestampMax, textSize), /* Y */ CENTRE_TEXT_VERT(timestampMin, timestampMax, textSize) },
|
||||
IM_COL32(255, 255, 255, 255),
|
||||
buffer,
|
||||
2,
|
||||
IM_COL32(8, 8, 8, 255)
|
||||
);
|
||||
|
||||
drawList->PopClipRect();
|
||||
}
|
||||
|
||||
static void DrawAchievementTotal(ImVec2 min, ImVec2 max)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
// Transparency fade animation.
|
||||
auto alpha = Cubic(0, 1, ComputeMotion(g_appearTime, COUNTER_INTRO_FADE_START, COUNTER_INTRO_FADE_END));
|
||||
|
||||
auto imageMarginX = Scale(5);
|
||||
auto imageMarginY = Scale(5);
|
||||
auto imageSize = Scale(45);
|
||||
|
||||
ImVec2 imageMin = { max.x - imageSize - imageMarginX, min.y - imageSize - imageMarginY };
|
||||
ImVec2 imageMax = { imageMin.x + imageSize, imageMin.y + imageSize };
|
||||
|
||||
constexpr auto columns = 8;
|
||||
constexpr auto rows = 4;
|
||||
constexpr auto spriteSize = 256.0f;
|
||||
constexpr auto textureWidth = 2048.0f;
|
||||
constexpr auto textureHeight = 1024.0f;
|
||||
auto frameIndex = int32_t(floor(ImGui::GetTime() * 30.0f)) % 30;
|
||||
auto columnIndex = frameIndex % columns;
|
||||
auto rowIndex = frameIndex / columns;
|
||||
auto uv0 = ImVec2(columnIndex * spriteSize / textureWidth, rowIndex * spriteSize / textureHeight);
|
||||
auto uv1 = ImVec2((columnIndex + 1) * spriteSize / textureWidth, (rowIndex + 1) * spriteSize / textureHeight);
|
||||
|
||||
drawList->AddImage(g_upTrophyIcon.get(), imageMin, imageMax, uv0, uv1, IM_COL32(255, 255, 255, 255 * alpha));
|
||||
|
||||
auto str = std::format("{} / {}", AchievementData::GetTotalRecords(), ACH_RECORDS);
|
||||
auto fontSize = Scale(20);
|
||||
auto textSize = g_fntNewRodinDB->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str());
|
||||
|
||||
DrawTextWithOutline<int>
|
||||
(
|
||||
g_fntNewRodinDB,
|
||||
fontSize,
|
||||
{ /* X */ imageMin.x - textSize.x - Scale(6), /* Y */ CENTRE_TEXT_VERT(imageMin, imageMax, textSize) },
|
||||
IM_COL32(255, 255, 255, 255 * alpha),
|
||||
str.c_str(),
|
||||
2,
|
||||
IM_COL32(0, 0, 0, 255 * alpha)
|
||||
);
|
||||
}
|
||||
|
||||
static void DrawContentContainer()
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
// Expand/retract animation.
|
||||
auto motion = g_isClosing
|
||||
? ComputeMotion(g_appearTime, 0, CONTENT_CONTAINER_COMMON_MOTION_START)
|
||||
: ComputeMotion(g_appearTime, CONTENT_CONTAINER_COMMON_MOTION_START, CONTENT_CONTAINER_COMMON_MOTION_END);
|
||||
|
||||
auto minX = g_isClosing
|
||||
? Hermite(251, 301, motion)
|
||||
: Hermite(301, 251, motion);
|
||||
|
||||
auto minY = g_isClosing
|
||||
? Hermite(189, 206, motion)
|
||||
: Hermite(206, 189, motion);
|
||||
|
||||
auto maxX = g_isClosing
|
||||
? Hermite(1031, 978, motion)
|
||||
: Hermite(978, 1031, motion);
|
||||
|
||||
auto maxY = g_isClosing
|
||||
? Hermite(604, 573, motion)
|
||||
: Hermite(573, 604, motion);
|
||||
|
||||
ImVec2 min = { Scale(minX), Scale(minY) };
|
||||
ImVec2 max = { Scale(maxX), Scale(maxY) };
|
||||
|
||||
// Transparency fade animation.
|
||||
auto alpha = g_isClosing
|
||||
? Hermite(1, 0, motion)
|
||||
: Hermite(0, 1, motion);
|
||||
|
||||
DrawContainer(min, max, IM_COL32(197, 194, 197, 200), IM_COL32(115, 113, 115, 236), alpha);
|
||||
|
||||
if (motion < 1.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (g_isClosing)
|
||||
{
|
||||
AchievementMenu::s_isVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
auto clipRectMin = drawList->GetClipRectMin();
|
||||
auto clipRectMax = drawList->GetClipRectMax();
|
||||
|
||||
auto itemHeight = Scale(94);
|
||||
auto yOffset = -g_firstVisibleRowIndex * itemHeight + Scale(2);
|
||||
auto rowCount = 0;
|
||||
|
||||
// Draw separators.
|
||||
for (int i = 1; i <= 3; i++)
|
||||
{
|
||||
auto lineMarginLeft = Scale(35);
|
||||
auto lineMarginRight = Scale(55);
|
||||
auto lineMarginY = Scale(2);
|
||||
|
||||
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) }, { lineMax.x, lineMax.y + Scale(1) }, IM_COL32(143, 148, 143, 255));
|
||||
}
|
||||
|
||||
for (auto& tpl : g_achievements)
|
||||
{
|
||||
auto achievement = std::get<0>(tpl);
|
||||
|
||||
if (AchievementData::IsUnlocked(achievement.ID))
|
||||
DrawAchievement(rowCount++, yOffset, achievement, true);
|
||||
}
|
||||
|
||||
for (auto& tpl : g_achievements)
|
||||
{
|
||||
auto achievement = std::get<0>(tpl);
|
||||
|
||||
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 leftIsHeld = inputState->GetPadState().IsDown(SWA::eKeyState_DpadLeft) ||
|
||||
inputState->GetPadState().LeftStickHorizontal < -0.5f;
|
||||
|
||||
bool rightIsHeld = inputState->GetPadState().IsDown(SWA::eKeyState_DpadRight) ||
|
||||
inputState->GetPadState().LeftStickHorizontal > 0.5f;
|
||||
|
||||
bool upRSIsHeld = inputState->GetPadState().RightStickVertical > 0.5f;
|
||||
bool downRSIsHeld = inputState->GetPadState().RightStickVertical < -0.5f;
|
||||
|
||||
bool isReachedTop = g_selectedRowIndex == 0;
|
||||
bool isReachedBottom = g_selectedRowIndex == rowCount - 1;
|
||||
|
||||
bool scrollUp = !g_upWasHeld && upIsHeld;
|
||||
bool scrollDown = !g_downWasHeld && downIsHeld;
|
||||
bool scrollPageUp = !g_leftWasHeld && leftIsHeld && !isReachedTop;
|
||||
bool scrollPageDown = !g_rightWasHeld && rightIsHeld && !isReachedBottom;
|
||||
bool jumpToTop = !g_upRSWasHeld && upRSIsHeld && !isReachedTop;
|
||||
bool jumpToBottom = !g_downRSWasHeld && downRSIsHeld && !isReachedBottom;
|
||||
|
||||
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;
|
||||
}
|
||||
else if (scrollPageUp)
|
||||
{
|
||||
g_selectedRowIndex -= 3;
|
||||
if (g_selectedRowIndex < 0)
|
||||
g_selectedRowIndex = 0;
|
||||
}
|
||||
else if (scrollPageDown)
|
||||
{
|
||||
g_selectedRowIndex += 3;
|
||||
if (g_selectedRowIndex >= rowCount)
|
||||
g_selectedRowIndex = rowCount - 1;
|
||||
}
|
||||
else if (jumpToTop)
|
||||
{
|
||||
g_selectedRowIndex = 0;
|
||||
}
|
||||
else if (jumpToBottom)
|
||||
{
|
||||
g_selectedRowIndex = rowCount - 1;
|
||||
}
|
||||
|
||||
// lol
|
||||
if (scrollUp || scrollDown || scrollPageUp || scrollPageDown || jumpToTop || jumpToBottom)
|
||||
{
|
||||
g_rowSelectionTime = ImGui::GetTime();
|
||||
Game_PlaySound("sys_actstg_pausecursor");
|
||||
}
|
||||
|
||||
g_upWasHeld = upIsHeld;
|
||||
g_downWasHeld = downIsHeld;
|
||||
g_leftWasHeld = leftIsHeld;
|
||||
g_rightWasHeld = rightIsHeld;
|
||||
g_upRSWasHeld = upRSIsHeld;
|
||||
g_downRSWasHeld = downRSIsHeld;
|
||||
|
||||
int visibleRowCount = int(floor((clipRectMax.y - clipRectMin.y) / itemHeight));
|
||||
|
||||
if (g_firstVisibleRowIndex > g_selectedRowIndex)
|
||||
g_firstVisibleRowIndex = g_selectedRowIndex;
|
||||
|
||||
if (g_firstVisibleRowIndex + visibleRowCount - 1 < g_selectedRowIndex)
|
||||
g_firstVisibleRowIndex = std::max(0, g_selectedRowIndex - visibleRowCount + 1);
|
||||
|
||||
// Pop clip rect from DrawContentContainer
|
||||
drawList->PopClipRect();
|
||||
|
||||
DrawAchievementTotal(min, max);
|
||||
|
||||
// Draw scroll bar
|
||||
if (rowCount > visibleRowCount)
|
||||
{
|
||||
float cornerRadius = Scale(25);
|
||||
float totalHeight = (clipRectMax.y - clipRectMin.y - cornerRadius) - Scale(5);
|
||||
float heightRatio = float(visibleRowCount) / float(rowCount);
|
||||
float offsetRatio = float(g_firstVisibleRowIndex) / float(rowCount);
|
||||
float offsetX = clipRectMax.x - Scale(39);
|
||||
float offsetY = offsetRatio * totalHeight + clipRectMin.y + Scale(4);
|
||||
float maxY = max.y - cornerRadius - Scale(3);
|
||||
float lineThickness = Scale(1);
|
||||
float innerMarginX = Scale(2);
|
||||
float outerMarginX = Scale(24);
|
||||
|
||||
// Outline
|
||||
drawList->AddRect
|
||||
(
|
||||
{ /* X */ offsetX - lineThickness, /* Y */ clipRectMin.y - lineThickness },
|
||||
{ /* X */ clipRectMax.x - outerMarginX + lineThickness, /* Y */ maxY + lineThickness },
|
||||
IM_COL32(255, 255, 255, 155),
|
||||
Scale(1)
|
||||
);
|
||||
|
||||
// Background
|
||||
drawList->AddRectFilledMultiColor
|
||||
(
|
||||
{ /* X */ offsetX, /* Y */ clipRectMin.y },
|
||||
{ /* X */ clipRectMax.x - outerMarginX, /* Y */ maxY },
|
||||
IM_COL32(123, 125, 123, 255),
|
||||
IM_COL32(123, 125, 123, 255),
|
||||
IM_COL32(97, 99, 97, 255),
|
||||
IM_COL32(97, 99, 97, 255)
|
||||
);
|
||||
|
||||
// Scroll Bar Outline
|
||||
drawList->AddRectFilledMultiColor
|
||||
(
|
||||
{ /* X */ offsetX + innerMarginX, /* Y */ offsetY - lineThickness },
|
||||
{ /* X */ clipRectMax.x - outerMarginX - innerMarginX, /* Y */ 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
|
||||
(
|
||||
{ /* X */ offsetX + innerMarginX + lineThickness, /* Y */ offsetY },
|
||||
{ /* X */ clipRectMax.x - outerMarginX - innerMarginX - lineThickness, /* Y */ offsetY + totalHeight * heightRatio },
|
||||
IM_COL32(255, 255, 255, 255)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void AchievementMenu::Init()
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
constexpr float FONT_SCALE = 2.0f;
|
||||
|
||||
g_fntSeurat = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE);
|
||||
g_fntNewRodinDB = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-DB.otf", 20.0f * FONT_SCALE);
|
||||
g_fntNewRodinUB = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-UB.otf", 20.0f * FONT_SCALE);
|
||||
|
||||
g_upTrophyIcon = LoadTexture(decompressZstd(g_trophy, g_trophy_uncompressed_size).get(), g_trophy_uncompressed_size);
|
||||
g_upSelectionCursor = LoadTexture(decompressZstd(g_select_fill, g_select_fill_uncompressed_size).get(), g_select_fill_uncompressed_size);
|
||||
g_upWindow = LoadTexture(decompressZstd(g_general_window, g_general_window_uncompressed_size).get(), g_general_window_uncompressed_size);
|
||||
}
|
||||
|
||||
void AchievementMenu::Draw()
|
||||
{
|
||||
if (!s_isVisible)
|
||||
return;
|
||||
|
||||
DrawHeaderContainer(Localise("Achievements_Name_Uppercase").c_str());
|
||||
DrawContentContainer();
|
||||
}
|
||||
|
||||
void AchievementMenu::Open()
|
||||
{
|
||||
s_isVisible = true;
|
||||
g_isClosing = false;
|
||||
g_appearTime = ImGui::GetTime();
|
||||
|
||||
g_achievements.clear();
|
||||
|
||||
for (auto& achievement : g_xdbfWrapper.GetAchievements((EXDBFLanguage)Config::Language.Value))
|
||||
g_achievements.push_back(std::make_tuple(achievement, AchievementData::GetTimestamp(achievement.ID)));
|
||||
|
||||
std::sort(g_achievements.begin(), g_achievements.end(), [](const auto& a, const auto& b)
|
||||
{
|
||||
return std::get<1>(a) > std::get<1>(b);
|
||||
});
|
||||
|
||||
ButtonGuide::Open({ Button(Localise("Common_Back"), EButtonIcon::B) });
|
||||
|
||||
ResetSelection();
|
||||
Game_PlaySound("sys_actstg_pausewinopen");
|
||||
}
|
||||
|
||||
void AchievementMenu::Close()
|
||||
{
|
||||
if (!g_isClosing)
|
||||
{
|
||||
g_appearTime = ImGui::GetTime();
|
||||
g_isClosing = true;
|
||||
}
|
||||
|
||||
ButtonGuide::Close();
|
||||
|
||||
Game_PlaySound("sys_actstg_pausewinclose");
|
||||
Game_PlaySound("sys_actstg_pausecansel");
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
class AchievementMenu
|
||||
{
|
||||
public:
|
||||
inline static bool s_isVisible = false;
|
||||
|
||||
static void Init();
|
||||
static void Draw();
|
||||
static void Open();
|
||||
static void Close();
|
||||
};
|
||||
@@ -0,0 +1,200 @@
|
||||
#include "achievement_overlay.h"
|
||||
#include "imgui_utils.h"
|
||||
#include <gpu/video.h>
|
||||
#include <kernel/memory.h>
|
||||
#include <kernel/xdbf.h>
|
||||
#include <locale/locale.h>
|
||||
#include <user/config.h>
|
||||
#include <user/achievement_data.h>
|
||||
#include <app.h>
|
||||
#include <exports.h>
|
||||
#include <decompressor.h>
|
||||
#include <res/images/common/general_window.dds.h>
|
||||
#include <gpu/imgui_snapshot.h>
|
||||
|
||||
constexpr double OVERLAY_CONTAINER_COMMON_MOTION_START = 0;
|
||||
constexpr double OVERLAY_CONTAINER_COMMON_MOTION_END = 11;
|
||||
constexpr double OVERLAY_CONTAINER_INTRO_FADE_START = 5;
|
||||
constexpr double OVERLAY_CONTAINER_INTRO_FADE_END = 9;
|
||||
constexpr double OVERLAY_CONTAINER_OUTRO_FADE_START = 0;
|
||||
constexpr double OVERLAY_CONTAINER_OUTRO_FADE_END = 4;
|
||||
|
||||
constexpr double OVERLAY_DURATION = 3;
|
||||
|
||||
static bool g_isClosing = false;
|
||||
|
||||
static double g_appearTime = 0;
|
||||
|
||||
static Achievement g_achievement;
|
||||
|
||||
static ImFont* g_fntSeurat;
|
||||
|
||||
static std::unique_ptr<GuestTexture> g_upWindow;
|
||||
|
||||
static bool DrawContainer(ImVec2 min, ImVec2 max, float cornerRadius = 25)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
// Expand/retract animation.
|
||||
auto containerMotion = ComputeMotion(g_appearTime, OVERLAY_CONTAINER_COMMON_MOTION_START, OVERLAY_CONTAINER_COMMON_MOTION_END);
|
||||
|
||||
auto centreX = (min.x + max.x) / 2;
|
||||
auto centreY = (min.y + max.y) / 2;
|
||||
|
||||
if (g_isClosing)
|
||||
{
|
||||
min.x = Hermite(min.x, centreX, containerMotion);
|
||||
max.x = Hermite(max.x, centreX, containerMotion);
|
||||
min.y = Hermite(min.y, centreY, containerMotion);
|
||||
max.y = Hermite(max.y, centreY, containerMotion);
|
||||
}
|
||||
else
|
||||
{
|
||||
min.x = Hermite(centreX, min.x, containerMotion);
|
||||
max.x = Hermite(centreX, max.x, containerMotion);
|
||||
min.y = Hermite(centreY, min.y, containerMotion);
|
||||
max.y = Hermite(centreY, max.y, containerMotion);
|
||||
}
|
||||
|
||||
// Transparency fade animation.
|
||||
auto colourMotion = g_isClosing
|
||||
? ComputeMotion(g_appearTime, OVERLAY_CONTAINER_OUTRO_FADE_START, OVERLAY_CONTAINER_OUTRO_FADE_END)
|
||||
: ComputeMotion(g_appearTime, OVERLAY_CONTAINER_INTRO_FADE_START, OVERLAY_CONTAINER_INTRO_FADE_END);
|
||||
|
||||
auto alpha = g_isClosing
|
||||
? Hermite(1, 0, colourMotion)
|
||||
: Hermite(0, 1, colourMotion);
|
||||
|
||||
DrawPauseContainer(g_upWindow.get(), min, max, alpha);
|
||||
|
||||
drawList->PushClipRect(min, max);
|
||||
|
||||
return containerMotion >= 1.0f;
|
||||
}
|
||||
|
||||
void AchievementOverlay::Init()
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
constexpr float FONT_SCALE = 2.0f;
|
||||
|
||||
g_fntSeurat = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE);
|
||||
|
||||
g_upWindow = LoadTexture(decompressZstd(g_general_window, g_general_window_uncompressed_size).get(), g_general_window_uncompressed_size);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// Calculate text sizes.
|
||||
auto fontSize = Scale(24);
|
||||
auto headerSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementUnlocked);
|
||||
auto bodySize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementName);
|
||||
auto maxSize = std::max(headerSize.x, bodySize.x) + Scale(5);
|
||||
|
||||
// Calculate image margins.
|
||||
auto imageMarginX = Scale(25);
|
||||
auto imageMarginY = Scale(22.5f);
|
||||
auto imageSize = Scale(60);
|
||||
|
||||
// Calculate text margins.
|
||||
auto textMarginX = imageMarginX * 2 + imageSize - Scale(5);
|
||||
auto textMarginY = imageMarginY + Scale(2);
|
||||
|
||||
auto containerWidth = imageMarginX + textMarginX + maxSize;
|
||||
|
||||
ImVec2 min = { (res.x / 2) - (containerWidth / 2), Scale(55) };
|
||||
ImVec2 max = { min.x + containerWidth, min.y + Scale(105) };
|
||||
|
||||
if (DrawContainer(min, max))
|
||||
{
|
||||
if (g_isClosing)
|
||||
{
|
||||
s_isVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw achievement icon.
|
||||
drawList->AddImage
|
||||
(
|
||||
g_xdbfTextureCache[g_achievement.ID], // user_texture_id
|
||||
{ /* X */ min.x + imageMarginX, /* Y */ min.y + imageMarginY }, // p_min
|
||||
{ /* X */ min.x + imageMarginX + imageSize, /* Y */ min.y + imageMarginY + imageSize }, // p_max
|
||||
{ 0, 0 }, // uv_min
|
||||
{ 1, 1 }, // uv_max
|
||||
IM_COL32(255, 255, 255, 255) // col
|
||||
);
|
||||
|
||||
// Draw header text.
|
||||
DrawTextWithShadow
|
||||
(
|
||||
g_fntSeurat, // font
|
||||
fontSize, // fontSize
|
||||
{ /* X */ min.x + textMarginX + (maxSize - headerSize.x) / 2, /* Y */ min.y + textMarginY }, // pos
|
||||
IM_COL32(252, 243, 5, 255), // colour
|
||||
strAchievementUnlocked, // text
|
||||
2, // offset
|
||||
0.4f, // radius
|
||||
IM_COL32(0, 0, 0, 255) // shadowColour
|
||||
);
|
||||
|
||||
// Draw achievement name.
|
||||
DrawTextWithShadow
|
||||
(
|
||||
g_fntSeurat, // font
|
||||
fontSize, // fontSize
|
||||
{ /* X */ min.x + textMarginX + (maxSize - bodySize.x) / 2, /* Y */ min.y + textMarginY + bodySize.y + Scale(6) }, // pos
|
||||
IM_COL32(255, 255, 255, 255), // colour
|
||||
strAchievementName, // text
|
||||
2, // offset
|
||||
0.4f, // radius
|
||||
IM_COL32(0, 0, 0, 255) // shadowColour
|
||||
);
|
||||
|
||||
// Pop clip rect from DrawContainer.
|
||||
drawList->PopClipRect();
|
||||
}
|
||||
}
|
||||
|
||||
void AchievementOverlay::Open(int id)
|
||||
{
|
||||
if (s_isVisible)
|
||||
{
|
||||
s_queue.emplace(id);
|
||||
return;
|
||||
}
|
||||
|
||||
s_isVisible = true;
|
||||
g_isClosing = false;
|
||||
g_appearTime = ImGui::GetTime();
|
||||
g_achievement = g_xdbfWrapper.GetAchievement((EXDBFLanguage)Config::Language.Value, id);
|
||||
|
||||
Game_PlaySound("obj_navi_appear");
|
||||
}
|
||||
|
||||
void AchievementOverlay::Close()
|
||||
{
|
||||
if (!g_isClosing)
|
||||
{
|
||||
g_appearTime = ImGui::GetTime();
|
||||
g_isClosing = true;
|
||||
}
|
||||
|
||||
if (s_queue.size())
|
||||
{
|
||||
s_isVisible = false;
|
||||
AchievementOverlay::Open(s_queue.front());
|
||||
s_queue.pop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <queue>
|
||||
|
||||
class AchievementOverlay
|
||||
{
|
||||
public:
|
||||
inline static bool s_isVisible = false;
|
||||
|
||||
inline static std::queue<uint16_t> s_queue{};
|
||||
|
||||
static void Init();
|
||||
static void Draw();
|
||||
static void Open(int id);
|
||||
static void Close();
|
||||
};
|
||||
@@ -0,0 +1,305 @@
|
||||
#include "button_guide.h"
|
||||
#include "imgui_utils.h"
|
||||
#include <gpu/imgui_snapshot.h>
|
||||
#include <gpu/video.h>
|
||||
#include <decompressor.h>
|
||||
#include <res/images/common/left_mouse_button.dds.h>
|
||||
#include <res/images/common/mat_comon_x360_001.dds.h>
|
||||
#include <res/images/common/start_back.dds.h>
|
||||
|
||||
constexpr float DEFAULT_SIDE_MARGINS = 379;
|
||||
|
||||
ImFont* g_fntNewRodin;
|
||||
ImFont* g_fntNewRodinLQ;
|
||||
|
||||
std::unique_ptr<GuestTexture> g_upIcons;
|
||||
std::unique_ptr<GuestTexture> g_upLMBIcon;
|
||||
std::unique_ptr<GuestTexture> g_upStartBackIcons;
|
||||
|
||||
float g_sideMargins = DEFAULT_SIDE_MARGINS;
|
||||
|
||||
std::vector<Button> g_buttons;
|
||||
|
||||
std::unordered_map<EButtonIcon, float> g_iconWidths =
|
||||
{
|
||||
{ EButtonIcon::A, 40 },
|
||||
{ EButtonIcon::B, 40 },
|
||||
{ EButtonIcon::X, 40 },
|
||||
{ EButtonIcon::Y, 40 },
|
||||
{ EButtonIcon::LB, 70 },
|
||||
{ EButtonIcon::RB, 70 },
|
||||
{ EButtonIcon::LBRB, 70 },
|
||||
{ EButtonIcon::LT, 42 },
|
||||
{ EButtonIcon::RT, 42 },
|
||||
{ EButtonIcon::LTRT, 42 },
|
||||
{ EButtonIcon::Start, 40 },
|
||||
{ EButtonIcon::Back, 40 },
|
||||
{ EButtonIcon::LMB, 40 }
|
||||
};
|
||||
|
||||
std::unordered_map<EButtonIcon, float> g_iconHeights =
|
||||
{
|
||||
{ EButtonIcon::A, 40 },
|
||||
{ EButtonIcon::B, 40 },
|
||||
{ EButtonIcon::X, 40 },
|
||||
{ EButtonIcon::Y, 40 },
|
||||
{ EButtonIcon::LB, 40 },
|
||||
{ EButtonIcon::RB, 40 },
|
||||
{ EButtonIcon::LBRB, 40 },
|
||||
{ EButtonIcon::LT, 42 },
|
||||
{ EButtonIcon::RT, 42 },
|
||||
{ EButtonIcon::LTRT, 42 },
|
||||
{ EButtonIcon::Start, 40 },
|
||||
{ EButtonIcon::Back, 40 },
|
||||
{ EButtonIcon::LMB, 40 }
|
||||
};
|
||||
|
||||
std::tuple<std::tuple<ImVec2, ImVec2>, GuestTexture*> GetButtonIcon(EButtonIcon icon)
|
||||
{
|
||||
std::tuple<ImVec2, ImVec2> btn;
|
||||
GuestTexture* texture;
|
||||
|
||||
switch (icon)
|
||||
{
|
||||
case EButtonIcon::A:
|
||||
btn = PIXELS_TO_UV_COORDS(512, 512, 0, 0, 40, 40);
|
||||
texture = g_upIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::B:
|
||||
btn = PIXELS_TO_UV_COORDS(512, 512, 40, 0, 40, 40);
|
||||
texture = g_upIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::X:
|
||||
btn = PIXELS_TO_UV_COORDS(512, 512, 80, 0, 40, 40);
|
||||
texture = g_upIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::Y:
|
||||
btn = PIXELS_TO_UV_COORDS(512, 512, 120, 0, 40, 40);
|
||||
texture = g_upIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::LB:
|
||||
btn = PIXELS_TO_UV_COORDS(512, 512, 166, 0, 70, 40);
|
||||
texture = g_upIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::RB:
|
||||
btn = PIXELS_TO_UV_COORDS(512, 512, 246, 0, 70, 40);
|
||||
texture = g_upIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::LT:
|
||||
btn = PIXELS_TO_UV_COORDS(512, 512, 319, 0, 42, 42);
|
||||
texture = g_upIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::RT:
|
||||
btn = PIXELS_TO_UV_COORDS(512, 512, 359, 0, 42, 42);
|
||||
texture = g_upIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::Start:
|
||||
btn = PIXELS_TO_UV_COORDS(256, 256, 0, 0, 128, 128);
|
||||
texture = g_upStartBackIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::Back:
|
||||
btn = PIXELS_TO_UV_COORDS(256, 256, 0, 128, 128, 128);
|
||||
texture = g_upStartBackIcons.get();
|
||||
break;
|
||||
|
||||
case EButtonIcon::LMB:
|
||||
btn = PIXELS_TO_UV_COORDS(128, 128, 0, 0, 128, 128);
|
||||
texture = g_upLMBIcon.get();
|
||||
break;
|
||||
}
|
||||
|
||||
return std::make_tuple(btn, texture);
|
||||
}
|
||||
|
||||
ImFont* GetFont(EFontQuality fontQuality)
|
||||
{
|
||||
return fontQuality == EFontQuality::Low
|
||||
? g_fntNewRodinLQ
|
||||
: g_fntNewRodin;
|
||||
}
|
||||
|
||||
static void DrawGuide(float* offset, ImVec2 regionMin, ImVec2 regionMax, EButtonIcon icon,
|
||||
EButtonAlignment alignment, ImVec2 iconMin, ImVec2 iconMax, EFontQuality fontQuality,
|
||||
ImVec2 textSize, float fontSize, const char* text)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
auto _icon = icon;
|
||||
auto iconWidth = Scale(g_iconWidths[icon]);
|
||||
auto dualIconMarginX = Scale(25);
|
||||
|
||||
if (icon == EButtonIcon::LBRB)
|
||||
{
|
||||
_icon = EButtonIcon::LB;
|
||||
}
|
||||
else if (icon == EButtonIcon::LTRT)
|
||||
{
|
||||
_icon = EButtonIcon::LT;
|
||||
}
|
||||
else
|
||||
{
|
||||
dualIconMarginX = 0;
|
||||
}
|
||||
|
||||
if (icon == EButtonIcon::LBRB || icon == EButtonIcon::LTRT)
|
||||
{
|
||||
iconMin = alignment == EButtonAlignment::Left
|
||||
? ImVec2(/* X */ regionMin.x + *offset - dualIconMarginX * 3, /* Y */ iconMin.y)
|
||||
: ImVec2(/* X */ regionMax.x - *offset - textSize.x - iconWidth, /* Y */ iconMin.y);
|
||||
|
||||
iconMax = alignment == EButtonAlignment::Left
|
||||
? ImVec2(iconMin.x + iconWidth, iconMax.y)
|
||||
: ImVec2(iconMin.x, iconMax.y);
|
||||
}
|
||||
|
||||
auto btnIcon = GetButtonIcon(_icon);
|
||||
drawList->AddImage(std::get<1>(btnIcon), iconMin, iconMax, GET_UV_COORDS(std::get<0>(btnIcon)));
|
||||
|
||||
auto textMarginX = alignment == EButtonAlignment::Left
|
||||
? regionMin.x + *offset + dualIconMarginX
|
||||
: regionMax.x - *offset - dualIconMarginX * 2;
|
||||
|
||||
DrawTextWithOutline<int>
|
||||
(
|
||||
GetFont(fontQuality),
|
||||
fontSize,
|
||||
{ /* X */ textMarginX, /* Y */ regionMin.y + Scale(8) },
|
||||
IM_COL32(255, 255, 255, 255),
|
||||
text,
|
||||
2,
|
||||
IM_COL32(0, 0, 0, 255)
|
||||
);
|
||||
|
||||
if (icon == EButtonIcon::LBRB || icon == EButtonIcon::LTRT)
|
||||
{
|
||||
auto btnIcon = GetButtonIcon(icon == EButtonIcon::LBRB ? EButtonIcon::RB : EButtonIcon::RT);
|
||||
|
||||
auto dualIconMin = alignment == EButtonAlignment::Left
|
||||
? ImVec2(/* X */ regionMin.x + *offset + textSize.x + dualIconMarginX * 2, /* Y */ iconMin.y)
|
||||
: ImVec2(/* X */ regionMax.x - *offset + textSize.x - dualIconMarginX, /* Y */ iconMin.y);
|
||||
|
||||
auto dualIconMax = ImVec2(dualIconMin.x + iconWidth, iconMax.y);
|
||||
|
||||
drawList->AddImage(std::get<1>(btnIcon), dualIconMin, dualIconMax, GET_UV_COORDS(std::get<0>(btnIcon)));
|
||||
|
||||
*offset += dualIconMarginX + iconWidth;
|
||||
}
|
||||
}
|
||||
|
||||
void ButtonGuide::Init()
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
constexpr float FONT_SCALE = 2.0f;
|
||||
|
||||
g_fntNewRodin = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-M.otf", 24.0f * FONT_SCALE);
|
||||
g_fntNewRodinLQ = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-M.otf", 19.0f);
|
||||
|
||||
g_upIcons = LoadTexture(
|
||||
decompressZstd(g_mat_comon_x360_001, g_mat_comon_x360_001_uncompressed_size).get(),
|
||||
g_mat_comon_x360_001_uncompressed_size);
|
||||
|
||||
g_upLMBIcon = LoadTexture(
|
||||
decompressZstd(g_left_mouse_button, g_left_mouse_button_uncompressed_size).get(),
|
||||
g_left_mouse_button_uncompressed_size);
|
||||
|
||||
g_upStartBackIcons = LoadTexture(
|
||||
decompressZstd(g_start_back, g_start_back_uncompressed_size).get(),
|
||||
g_start_back_uncompressed_size);
|
||||
}
|
||||
|
||||
void ButtonGuide::Draw()
|
||||
{
|
||||
if (!s_isVisible)
|
||||
return;
|
||||
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
auto& res = ImGui::GetIO().DisplaySize;
|
||||
|
||||
auto regionMarginX = Scale(g_sideMargins);
|
||||
auto regionHeight = Scale(102);
|
||||
|
||||
ImVec2 regionMin = { regionMarginX, res.y - regionHeight };
|
||||
ImVec2 regionMax = { res.x - regionMarginX, res.y };
|
||||
|
||||
auto textMarginX = Scale(57);
|
||||
auto textMarginY = Scale(8);
|
||||
auto iconMarginX = Scale(4);
|
||||
auto fontSize = Scale(22.5f);
|
||||
|
||||
auto offsetLeft = 0.0f;
|
||||
auto offsetRight = 0.0f;
|
||||
|
||||
// Draw left aligned icons.
|
||||
for (int i = 0; i < g_buttons.size(); i++)
|
||||
{
|
||||
auto& btn = g_buttons[i];
|
||||
|
||||
if (btn.Alignment != EButtonAlignment::Left)
|
||||
continue;
|
||||
|
||||
if (btn.Visibility && !*btn.Visibility)
|
||||
continue;
|
||||
|
||||
auto iconWidth = Scale(g_iconWidths[btn.Icon]);
|
||||
auto iconHeight = Scale(g_iconHeights[btn.Icon]);
|
||||
auto textSize = g_fntNewRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, btn.Name.c_str());
|
||||
|
||||
if (i > 0)
|
||||
offsetLeft += textSize.x + iconWidth + textMarginX;
|
||||
|
||||
ImVec2 iconMin = { regionMin.x + offsetLeft - iconWidth - iconMarginX, regionMin.y };
|
||||
ImVec2 iconMax = { regionMin.x + offsetLeft - iconMarginX, regionMin.y + iconHeight };
|
||||
|
||||
DrawGuide(&offsetLeft, regionMin, regionMax, btn.Icon, btn.Alignment, iconMin, iconMax, btn.FontQuality, textSize, fontSize, btn.Name.c_str());
|
||||
}
|
||||
|
||||
// Draw right aligned icons.
|
||||
for (int i = g_buttons.size() - 1; i >= 0; i--)
|
||||
{
|
||||
auto& btn = g_buttons[i];
|
||||
|
||||
if (btn.Alignment != EButtonAlignment::Right)
|
||||
continue;
|
||||
|
||||
if (btn.Visibility && !*btn.Visibility)
|
||||
continue;
|
||||
|
||||
auto iconWidth = Scale(g_iconWidths[btn.Icon]);
|
||||
auto iconHeight = Scale(g_iconHeights[btn.Icon]);
|
||||
auto textSize = g_fntNewRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, btn.Name.c_str());
|
||||
|
||||
if (i < g_buttons.size() - 1)
|
||||
offsetRight += textSize.x + iconWidth + textMarginX;
|
||||
|
||||
ImVec2 iconMin = { regionMax.x - offsetRight - iconWidth - iconMarginX, regionMin.y };
|
||||
ImVec2 iconMax = { regionMax.x - offsetRight - iconMarginX, regionMin.y + iconHeight };
|
||||
|
||||
DrawGuide(&offsetRight, regionMin, regionMax, btn.Icon, btn.Alignment, iconMin, iconMax, btn.FontQuality, textSize, fontSize, btn.Name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void ButtonGuide::Open(const std::vector<Button> buttons)
|
||||
{
|
||||
s_isVisible = true;
|
||||
g_sideMargins = DEFAULT_SIDE_MARGINS;
|
||||
g_buttons = buttons;
|
||||
}
|
||||
|
||||
void ButtonGuide::SetSideMargins(float width = DEFAULT_SIDE_MARGINS)
|
||||
{
|
||||
g_sideMargins = width;
|
||||
}
|
||||
|
||||
void ButtonGuide::Close()
|
||||
{
|
||||
s_isVisible = false;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <gpu/video.h>
|
||||
|
||||
enum class EButtonIcon
|
||||
{
|
||||
A,
|
||||
B,
|
||||
X,
|
||||
Y,
|
||||
LB,
|
||||
RB,
|
||||
LBRB,
|
||||
LT,
|
||||
RT,
|
||||
LTRT,
|
||||
Start,
|
||||
Back,
|
||||
LMB
|
||||
};
|
||||
|
||||
enum class EButtonAlignment
|
||||
{
|
||||
Left,
|
||||
Right
|
||||
};
|
||||
|
||||
enum class EFontQuality
|
||||
{
|
||||
Low,
|
||||
High
|
||||
};
|
||||
|
||||
class Button
|
||||
{
|
||||
public:
|
||||
std::string Name{};
|
||||
EButtonIcon Icon{};
|
||||
EButtonAlignment Alignment{ EButtonAlignment::Right };
|
||||
EFontQuality FontQuality{ EFontQuality::High };
|
||||
bool* Visibility{ nullptr };
|
||||
|
||||
Button(std::string name, EButtonIcon icon, EButtonAlignment alignment, EFontQuality fontQuality = EFontQuality::High, bool* visibility = nullptr)
|
||||
: Name(name), Icon(icon), Alignment(alignment), FontQuality(fontQuality), Visibility(visibility) {}
|
||||
|
||||
Button(std::string name, EButtonIcon icon, bool* visibility) : Name(name), Icon(icon), Visibility(visibility) {}
|
||||
|
||||
Button(std::string name, EButtonIcon icon) : Name(name), Icon(icon) {}
|
||||
};
|
||||
|
||||
class ButtonGuide
|
||||
{
|
||||
public:
|
||||
inline static bool s_isVisible = false;
|
||||
|
||||
static void Init();
|
||||
static void Draw();
|
||||
static void Open(const std::vector<Button> buttons);
|
||||
static void SetSideMargins(float width);
|
||||
static void Close();
|
||||
};
|
||||
@@ -0,0 +1,362 @@
|
||||
#pragma once
|
||||
|
||||
#include <gpu/imgui_common.h>
|
||||
#include <gpu/video.h>
|
||||
#include <app.h>
|
||||
|
||||
#define PIXELS_TO_UV_COORDS(textureWidth, textureHeight, x, y, width, height) \
|
||||
std::make_tuple(ImVec2((float)x / (float)textureWidth, (float)y / (float)textureHeight), \
|
||||
ImVec2(((float)x + (float)width) / (float)textureWidth, ((float)y + (float)height) / (float)textureHeight))
|
||||
|
||||
#define GET_UV_COORDS(tuple) std::get<0>(tuple), std::get<1>(tuple)
|
||||
|
||||
#define CENTRE_TEXT_HORZ(min, max, textSize) min.x + ((max.x - min.x) - textSize.x) / 2
|
||||
#define CENTRE_TEXT_VERT(min, max, textSize) min.y + ((max.y - min.y) - textSize.y) / 2
|
||||
|
||||
static void SetGradient(const ImVec2& min, const ImVec2& max, ImU32 top, ImU32 bottom)
|
||||
{
|
||||
auto callbackData = AddImGuiCallback(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 = AddImGuiCallback(ImGuiCallback::SetGradient);
|
||||
memset(&callbackData->setGradient, 0, sizeof(callbackData->setGradient));
|
||||
}
|
||||
|
||||
static void SetShaderModifier(uint32_t shaderModifier)
|
||||
{
|
||||
auto callbackData = AddImGuiCallback(ImGuiCallback::SetShaderModifier);
|
||||
callbackData->setShaderModifier.shaderModifier = shaderModifier;
|
||||
}
|
||||
|
||||
static void SetOrigin(ImVec2 origin)
|
||||
{
|
||||
auto callbackData = AddImGuiCallback(ImGuiCallback::SetOrigin);
|
||||
callbackData->setOrigin.origin[0] = origin.x;
|
||||
callbackData->setOrigin.origin[1] = origin.y;
|
||||
}
|
||||
|
||||
static void SetScale(ImVec2 scale)
|
||||
{
|
||||
auto callbackData = AddImGuiCallback(ImGuiCallback::SetScale);
|
||||
callbackData->setScale.scale[0] = scale.x;
|
||||
callbackData->setScale.scale[1] = scale.y;
|
||||
}
|
||||
|
||||
// Aspect ratio aware.
|
||||
static float Scale(float size)
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
if (io.DisplaySize.x > io.DisplaySize.y)
|
||||
return size * std::max(1.0f, io.DisplaySize.y / 720.0f);
|
||||
else
|
||||
return size * std::max(1.0f, io.DisplaySize.x / 1280.0f);
|
||||
}
|
||||
|
||||
// Not aspect ratio aware. Will stretch.
|
||||
static float ScaleX(float x)
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
return x * io.DisplaySize.x / 1280.0f;
|
||||
}
|
||||
|
||||
// Not aspect ratio aware. Will stretch.
|
||||
static float ScaleY(float y)
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
return y * io.DisplaySize.y / 720.0f;
|
||||
}
|
||||
|
||||
static double ComputeMotion(double duration, double offset, double total)
|
||||
{
|
||||
return sqrt(std::clamp((ImGui::GetTime() - duration - offset / 60.0) / total * 60.0, 0.0, 1.0));
|
||||
}
|
||||
|
||||
static void DrawPauseContainer(GuestTexture* texture, ImVec2 min, ImVec2 max, float alpha = 1)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
auto commonWidth = Scale(35);
|
||||
auto commonHeight = Scale(35);
|
||||
auto bottomHeight = Scale(5);
|
||||
|
||||
auto tl = PIXELS_TO_UV_COORDS(512, 512, 0, 0, 35, 35);
|
||||
auto tc = PIXELS_TO_UV_COORDS(512, 512, 51, 0, 5, 35);
|
||||
auto tr = PIXELS_TO_UV_COORDS(512, 512, 70, 0, 35, 35);
|
||||
auto cl = PIXELS_TO_UV_COORDS(512, 512, 0, 35, 35, 235);
|
||||
auto cc = PIXELS_TO_UV_COORDS(512, 512, 51, 35, 5, 235);
|
||||
auto cr = PIXELS_TO_UV_COORDS(512, 512, 70, 35, 35, 235);
|
||||
auto bl = PIXELS_TO_UV_COORDS(512, 512, 0, 270, 35, 40);
|
||||
auto bc = PIXELS_TO_UV_COORDS(512, 512, 51, 270, 5, 40);
|
||||
auto br = PIXELS_TO_UV_COORDS(512, 512, 70, 270, 35, 40);
|
||||
|
||||
auto colour = IM_COL32(255, 255, 255, 255 * alpha);
|
||||
|
||||
drawList->AddImage(texture, min, { min.x + commonWidth, min.y + commonHeight }, GET_UV_COORDS(tl), colour);
|
||||
drawList->AddImage(texture, { min.x + commonWidth, min.y }, { max.x - commonWidth, min.y + commonHeight }, GET_UV_COORDS(tc), colour);
|
||||
drawList->AddImage(texture, { max.x - commonWidth, min.y }, { max.x, min.y + commonHeight }, GET_UV_COORDS(tr), colour);
|
||||
drawList->AddImage(texture, { min.x, min.y + commonHeight }, { min.x + commonWidth, max.y - commonHeight }, GET_UV_COORDS(cl), colour);
|
||||
drawList->AddImage(texture, { min.x + commonWidth, min.y + commonHeight }, { max.x - commonWidth, max.y - commonHeight }, GET_UV_COORDS(cc), colour);
|
||||
drawList->AddImage(texture, { max.x - commonWidth, min.y + commonHeight }, { max.x, max.y - commonHeight }, GET_UV_COORDS(cr), colour);
|
||||
drawList->AddImage(texture, { min.x, max.y - commonHeight }, { min.x + commonWidth, max.y + bottomHeight }, GET_UV_COORDS(bl), colour);
|
||||
drawList->AddImage(texture, { min.x + commonWidth, max.y - commonHeight }, { max.x - commonWidth, max.y + bottomHeight }, GET_UV_COORDS(bc), colour);
|
||||
drawList->AddImage(texture, { max.x - commonWidth, max.y - commonHeight }, { max.x, max.y + bottomHeight }, GET_UV_COORDS(br), colour);
|
||||
}
|
||||
|
||||
static void DrawPauseHeaderContainer(GuestTexture* texture, ImVec2 min, ImVec2 max, float alpha = 1)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
auto commonWidth = Scale(35);
|
||||
|
||||
auto left = PIXELS_TO_UV_COORDS(512, 512, 0, 314, 35, 60);
|
||||
auto centre = PIXELS_TO_UV_COORDS(512, 512, 51, 314, 5, 60);
|
||||
auto right = PIXELS_TO_UV_COORDS(512, 512, 70, 314, 35, 60);
|
||||
|
||||
auto colour = IM_COL32(255, 255, 255, 255 * alpha);
|
||||
|
||||
drawList->AddImage(texture, min, { min.x + commonWidth, max.y }, GET_UV_COORDS(left), colour);
|
||||
drawList->AddImage(texture, { min.x + commonWidth, min.y }, { max.x - commonWidth, max.y }, GET_UV_COORDS(centre), colour);
|
||||
drawList->AddImage(texture, { max.x - commonWidth, min.y }, max, GET_UV_COORDS(right), colour);
|
||||
}
|
||||
|
||||
static void DrawTextWithMarquee(const ImFont* font, float fontSize, const ImVec2& pos, const ImVec2& min, const ImVec2& max, ImU32 color, const char* text, double time, double delay, double speed)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
auto rectWidth = max.x - min.x;
|
||||
auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, 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)
|
||||
drawList->AddText(font, fontSize, { textX, pos.y }, color, text);
|
||||
|
||||
if (textX + textSize.x < pos.x)
|
||||
drawList->AddText(font, fontSize, { textX + textSize.x + rectWidth, pos.y }, color, text);
|
||||
|
||||
drawList->PopClipRect();
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
outlineSize = Scale(outlineSize);
|
||||
|
||||
if constexpr (std::is_same_v<float, T> || std::is_same_v<double, T>)
|
||||
{
|
||||
// TODO: This is still very inefficient!
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
offset = Scale(offset);
|
||||
radius = Scale(radius);
|
||||
|
||||
DrawTextWithOutline<float>(font, fontSize, { pos.x + offset, pos.y + offset }, shadowColour, text, radius, shadowColour);
|
||||
|
||||
drawList->AddText(font, fontSize, pos, colour, text, nullptr);
|
||||
}
|
||||
|
||||
static float CalcWidestTextSize(const ImFont* font, float fontSize, std::span<std::string> strs)
|
||||
{
|
||||
auto result = 0.0f;
|
||||
|
||||
for (auto& str : strs)
|
||||
result = std::max(result, font->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str()).x);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string Truncate(const std::string& input, size_t maxLength, bool useEllipsis = true, bool usePrefixEllipsis = false)
|
||||
{
|
||||
const std::string ellipsis = "...";
|
||||
|
||||
if (input.length() > maxLength)
|
||||
{
|
||||
if (useEllipsis && maxLength > ellipsis.length())
|
||||
{
|
||||
if (usePrefixEllipsis)
|
||||
{
|
||||
return ellipsis + input.substr(0, maxLength - ellipsis.length());
|
||||
}
|
||||
else
|
||||
{
|
||||
return input.substr(0, maxLength - ellipsis.length()) + ellipsis;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return input.substr(0, maxLength);
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
static std::vector<std::string> Split(const char* str, char delimiter)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
|
||||
if (!str)
|
||||
return result;
|
||||
|
||||
const char* start = str;
|
||||
const char* current = str;
|
||||
|
||||
while (*current)
|
||||
{
|
||||
if (*current == delimiter)
|
||||
{
|
||||
result.emplace_back(start, current - start);
|
||||
start = current + 1;
|
||||
}
|
||||
|
||||
current++;
|
||||
}
|
||||
|
||||
result.emplace_back(start);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static ImVec2 MeasureCentredParagraph(const ImFont* font, float fontSize, float lineMargin, std::vector<std::string> lines)
|
||||
{
|
||||
auto x = 0.0f;
|
||||
auto y = 0.0f;
|
||||
|
||||
for (auto& str : lines)
|
||||
{
|
||||
auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str());
|
||||
|
||||
x = std::max(x, textSize.x);
|
||||
y += textSize.y + Scale(lineMargin);
|
||||
}
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
static ImVec2 MeasureCentredParagraph(const ImFont* font, float fontSize, float lineMargin, const char* text)
|
||||
{
|
||||
return MeasureCentredParagraph(font, fontSize, lineMargin, Split(text, '\n'));
|
||||
}
|
||||
|
||||
static void DrawCentredParagraph(const ImFont* font, float fontSize, const ImVec2& centre, float lineMargin, const char* text, std::function<void(const char*, ImVec2)> drawMethod)
|
||||
{
|
||||
auto lines = Split(text, '\n');
|
||||
auto paragraphSize = MeasureCentredParagraph(font, fontSize, lineMargin, lines);
|
||||
auto offsetY = 0.0f;
|
||||
|
||||
auto hasList = std::strstr(text, "- ");
|
||||
auto isList = false;
|
||||
auto listOffsetX = 0.0f;
|
||||
|
||||
for (int i = 0; i < lines.size(); i++)
|
||||
{
|
||||
auto& str = lines[i];
|
||||
auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str());
|
||||
|
||||
if (hasList)
|
||||
{
|
||||
if (!isList && str.starts_with("- ") && lines.size() > i + 1 && lines[i + 1].starts_with("- "))
|
||||
{
|
||||
isList = true;
|
||||
listOffsetX = centre.x - textSize.x / 2;
|
||||
}
|
||||
else if (isList && !str.starts_with("- "))
|
||||
{
|
||||
isList = false;
|
||||
}
|
||||
}
|
||||
|
||||
drawMethod(str.c_str(), ImVec2(/* X */ isList ? listOffsetX : centre.x - textSize.x / 2, /* Y */ centre.y - paragraphSize.y / 2 + offsetY));
|
||||
|
||||
offsetY += textSize.y + Scale(lineMargin);
|
||||
}
|
||||
}
|
||||
|
||||
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, 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)
|
||||
{
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
static float Cubic(float a, float b, float t)
|
||||
{
|
||||
return a + (b - a) * (t * t * t);
|
||||
}
|
||||
|
||||
static float Hermite(float a, float b, float t)
|
||||
{
|
||||
return a + (b - a) * (t * t * (3 - 2 * t));
|
||||
}
|
||||
|
||||
static ImVec2 Lerp(const ImVec2& a, const ImVec2& b, float t)
|
||||
{
|
||||
return { a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t };
|
||||
}
|
||||
|
||||
static ImU32 ColourLerp(ImU32 c0, ImU32 c1, float t)
|
||||
{
|
||||
auto a = ImGui::ColorConvertU32ToFloat4(c0);
|
||||
auto b = ImGui::ColorConvertU32ToFloat4(c1);
|
||||
|
||||
ImVec4 result;
|
||||
result.x = a.x + (b.x - a.x) * t;
|
||||
result.y = a.y + (b.y - a.y) * t;
|
||||
result.z = a.z + (b.z - a.z) * t;
|
||||
result.w = a.w + (b.w - a.w) * t;
|
||||
|
||||
return ImGui::ColorConvertFloat4ToU32(result);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <api/SWA.h>
|
||||
|
||||
struct InstallerWizard
|
||||
{
|
||||
inline static bool s_isVisible = false;
|
||||
|
||||
static void Init();
|
||||
static void Draw();
|
||||
static void Shutdown();
|
||||
static bool Run(bool skipGame);
|
||||
};
|
||||
@@ -0,0 +1,351 @@
|
||||
#include "message_window.h"
|
||||
#include "imgui_utils.h"
|
||||
#include <api/SWA.h>
|
||||
#include <gpu/video.h>
|
||||
#include <locale/locale.h>
|
||||
#include <ui/button_guide.h>
|
||||
#include <app.h>
|
||||
#include <exports.h>
|
||||
#include <res/images/common/general_window.dds.h>
|
||||
#include <decompressor.h>
|
||||
#include <res/images/common/select_fade.dds.h>
|
||||
#include <gpu/imgui_snapshot.h>
|
||||
|
||||
constexpr double OVERLAY_CONTAINER_COMMON_MOTION_START = 0;
|
||||
constexpr double OVERLAY_CONTAINER_COMMON_MOTION_END = 11;
|
||||
constexpr double OVERLAY_CONTAINER_INTRO_FADE_START = 5;
|
||||
constexpr double OVERLAY_CONTAINER_INTRO_FADE_END = 9;
|
||||
constexpr double OVERLAY_CONTAINER_OUTRO_FADE_START = 0;
|
||||
constexpr double OVERLAY_CONTAINER_OUTRO_FADE_END = 4;
|
||||
|
||||
static bool g_isAwaitingResult = false;
|
||||
static bool g_isClosing = false;
|
||||
static bool g_isControlsVisible = false;
|
||||
|
||||
static int g_selectedRowIndex;
|
||||
static int g_foregroundCount;
|
||||
|
||||
static bool g_upWasHeld;
|
||||
static bool g_downWasHeld;
|
||||
|
||||
static double g_appearTime;
|
||||
static double g_controlsAppearTime;
|
||||
|
||||
static ImFont* g_fntSeurat;
|
||||
|
||||
static std::unique_ptr<GuestTexture> g_upSelectionCursor;
|
||||
static std::unique_ptr<GuestTexture> g_upWindow;
|
||||
|
||||
std::string g_text;
|
||||
int g_result;
|
||||
std::vector<std::string> g_buttons;
|
||||
int g_defaultButtonIndex;
|
||||
|
||||
bool DrawContainer(float appearTime, ImVec2 centre, ImVec2 max, bool isForeground = true)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
ImVec2 _min = { centre.x - max.x, centre.y - max.y };
|
||||
ImVec2 _max = { centre.x + max.x, centre.y + max.y };
|
||||
|
||||
// Expand/retract animation.
|
||||
auto containerMotion = ComputeMotion(appearTime, OVERLAY_CONTAINER_COMMON_MOTION_START, OVERLAY_CONTAINER_COMMON_MOTION_END);
|
||||
|
||||
if (g_isClosing)
|
||||
{
|
||||
_min.x = Hermite(_min.x, centre.x, containerMotion);
|
||||
_max.x = Hermite(_max.x, centre.x, containerMotion);
|
||||
_min.y = Hermite(_min.y, centre.y, containerMotion);
|
||||
_max.y = Hermite(_max.y, centre.y, containerMotion);
|
||||
}
|
||||
else
|
||||
{
|
||||
_min.x = Hermite(centre.x, _min.x, containerMotion);
|
||||
_max.x = Hermite(centre.x, _max.x, containerMotion);
|
||||
_min.y = Hermite(centre.y, _min.y, containerMotion);
|
||||
_max.y = Hermite(centre.y, _max.y, containerMotion);
|
||||
}
|
||||
|
||||
// Transparency fade animation.
|
||||
auto colourMotion = g_isClosing
|
||||
? ComputeMotion(appearTime, OVERLAY_CONTAINER_OUTRO_FADE_START, OVERLAY_CONTAINER_OUTRO_FADE_END)
|
||||
: ComputeMotion(appearTime, OVERLAY_CONTAINER_INTRO_FADE_START, OVERLAY_CONTAINER_INTRO_FADE_END);
|
||||
|
||||
auto alpha = g_isClosing
|
||||
? Lerp(1, 0, colourMotion)
|
||||
: Lerp(0, 1, colourMotion);
|
||||
|
||||
if (!isForeground)
|
||||
g_foregroundCount++;
|
||||
|
||||
if (isForeground)
|
||||
drawList->AddRectFilled({ 0.0f, 0.0f }, ImGui::GetIO().DisplaySize, IM_COL32(0, 0, 0, 223 * (g_foregroundCount ? 1 : alpha)));
|
||||
|
||||
DrawPauseContainer(g_upWindow.get(), _min, _max, alpha);
|
||||
|
||||
drawList->PushClipRect(_min, _max);
|
||||
|
||||
return containerMotion >= 1.0f && !g_isClosing;
|
||||
}
|
||||
|
||||
void DrawButton(int rowIndex, float yOffset, float width, float height, std::string& text)
|
||||
{
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
|
||||
auto clipRectMin = drawList->GetClipRectMin();
|
||||
auto clipRectMax = drawList->GetClipRectMax();
|
||||
|
||||
ImVec2 min = { clipRectMin.x + ((clipRectMax.x - clipRectMin.x) - width) / 2, clipRectMin.y + height * rowIndex + yOffset };
|
||||
ImVec2 max = { min.x + width, min.y + height };
|
||||
|
||||
bool isSelected = rowIndex == g_selectedRowIndex;
|
||||
|
||||
if (isSelected)
|
||||
{
|
||||
static auto breatheStart = ImGui::GetTime();
|
||||
auto alpha = Lerp(1.0f, 0.75f, (sin((ImGui::GetTime() - breatheStart) * (2.0f * M_PI / (55.0f / 60.0f))) + 1.0f) / 2.0f);
|
||||
auto colour = IM_COL32(255, 255, 255, 255 * alpha);
|
||||
|
||||
auto width = Scale(11);
|
||||
auto left = PIXELS_TO_UV_COORDS(64, 64, 0, 0, 11, 50);
|
||||
auto centre = PIXELS_TO_UV_COORDS(64, 64, 11, 0, 8, 50);
|
||||
auto right = PIXELS_TO_UV_COORDS(64, 64, 19, 0, 11, 50);
|
||||
|
||||
drawList->AddImage(g_upSelectionCursor.get(), min, { min.x + width, max.y }, GET_UV_COORDS(left), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { min.x + width, min.y }, { max.x - width, max.y }, GET_UV_COORDS(centre), colour);
|
||||
drawList->AddImage(g_upSelectionCursor.get(), { max.x - width, min.y }, max, GET_UV_COORDS(right), colour);
|
||||
}
|
||||
|
||||
auto fontSize = Scale(28);
|
||||
auto textSize = g_fntSeurat->CalcTextSizeA(fontSize, FLT_MAX, 0, text.c_str());
|
||||
|
||||
DrawTextWithShadow
|
||||
(
|
||||
g_fntSeurat,
|
||||
fontSize,
|
||||
{ /* X */ min.x + ((max.x - min.x) - textSize.x) / 2, /* Y */ min.y + ((max.y - min.y) - textSize.y) / 2 },
|
||||
isSelected ? IM_COL32(255, 128, 0, 255) : IM_COL32(255, 255, 255, 255),
|
||||
text.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
static void ResetSelection()
|
||||
{
|
||||
g_selectedRowIndex = g_defaultButtonIndex;
|
||||
g_upWasHeld = false;
|
||||
g_downWasHeld = false;
|
||||
}
|
||||
|
||||
void MessageWindow::Init()
|
||||
{
|
||||
auto& io = ImGui::GetIO();
|
||||
|
||||
constexpr float FONT_SCALE = 2.0f;
|
||||
|
||||
g_fntSeurat = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf", 24.0f * FONT_SCALE);
|
||||
|
||||
g_upSelectionCursor = LoadTexture(decompressZstd(g_select_fade, g_select_fade_uncompressed_size).get(), g_select_fade_uncompressed_size);
|
||||
g_upWindow = LoadTexture(decompressZstd(g_general_window, g_general_window_uncompressed_size).get(), g_general_window_uncompressed_size);
|
||||
}
|
||||
|
||||
void MessageWindow::Draw()
|
||||
{
|
||||
if (!s_isVisible)
|
||||
return;
|
||||
|
||||
auto pInputState = g_isGameLoaded ? SWA::CInputState::GetInstance() : nullptr;
|
||||
auto drawList = ImGui::GetForegroundDrawList();
|
||||
auto& res = ImGui::GetIO().DisplaySize;
|
||||
|
||||
ImVec2 centre = { res.x / 2, res.y / 2 };
|
||||
|
||||
auto fontSize = Scale(28);
|
||||
auto textSize = MeasureCentredParagraph(g_fntSeurat, fontSize, 5, g_text.c_str());
|
||||
auto textMarginX = Scale(37);
|
||||
auto textMarginY = Scale(45);
|
||||
|
||||
if (DrawContainer(g_appearTime, centre, { textSize.x / 2 + textMarginX, textSize.y / 2 + textMarginY }, !g_isControlsVisible))
|
||||
{
|
||||
DrawCentredParagraph
|
||||
(
|
||||
g_fntSeurat,
|
||||
fontSize,
|
||||
{ centre.x, centre.y + Scale(3) },
|
||||
5,
|
||||
g_text.c_str(),
|
||||
|
||||
[=](const char* str, ImVec2 pos)
|
||||
{
|
||||
DrawTextWithShadow(g_fntSeurat, fontSize, pos, IM_COL32(255, 255, 255, 255), str);
|
||||
}
|
||||
);
|
||||
|
||||
drawList->PopClipRect();
|
||||
|
||||
bool isAccepted = pInputState
|
||||
? pInputState->GetPadState().IsTapped(SWA::eKeyState_A)
|
||||
: ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
|
||||
if (g_buttons.size())
|
||||
{
|
||||
auto itemWidth = std::max(Scale(162), Scale(CalcWidestTextSize(g_fntSeurat, fontSize, g_buttons)));
|
||||
auto itemHeight = Scale(57);
|
||||
auto windowMarginX = Scale(23);
|
||||
auto windowMarginY = Scale(30);
|
||||
|
||||
ImVec2 controlsMax = { /* X */ itemWidth / 2 + windowMarginX, /* Y */ itemHeight / 2 * g_buttons.size() + windowMarginY };
|
||||
|
||||
if (g_isControlsVisible && DrawContainer(g_controlsAppearTime, centre, controlsMax))
|
||||
{
|
||||
auto rowCount = 0;
|
||||
|
||||
for (auto& button : g_buttons)
|
||||
DrawButton(rowCount++, windowMarginY, itemWidth, itemHeight, button);
|
||||
|
||||
if (pInputState)
|
||||
{
|
||||
bool upIsHeld = pInputState->GetPadState().IsDown(SWA::eKeyState_DpadUp) ||
|
||||
pInputState->GetPadState().LeftStickVertical > 0.5f;
|
||||
|
||||
bool downIsHeld = pInputState->GetPadState().IsDown(SWA::eKeyState_DpadDown) ||
|
||||
pInputState->GetPadState().LeftStickVertical < -0.5f;
|
||||
|
||||
bool scrollUp = !g_upWasHeld && upIsHeld;
|
||||
bool scrollDown = !g_downWasHeld && downIsHeld;
|
||||
|
||||
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)
|
||||
Game_PlaySound("sys_actstg_pausecursor");
|
||||
|
||||
g_upWasHeld = upIsHeld;
|
||||
g_downWasHeld = downIsHeld;
|
||||
|
||||
if (pInputState->GetPadState().IsTapped(SWA::eKeyState_B))
|
||||
{
|
||||
g_result = -1;
|
||||
|
||||
Game_PlaySound("sys_actstg_pausecansel");
|
||||
MessageWindow::Close();
|
||||
}
|
||||
|
||||
ButtonGuide::Open
|
||||
(
|
||||
{
|
||||
Button(Localise("Common_Select"), EButtonIcon::A),
|
||||
Button(Localise("Common_Back"),EButtonIcon::B),
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto clipRectMin = drawList->GetClipRectMin();
|
||||
auto clipRectMax = drawList->GetClipRectMax();
|
||||
|
||||
g_selectedRowIndex = -1;
|
||||
|
||||
for (int i = 0; i < rowCount; i++)
|
||||
{
|
||||
ImVec2 itemMin = { clipRectMin.x + windowMarginX, clipRectMin.y + windowMarginY + itemHeight * i };
|
||||
ImVec2 itemMax = { clipRectMax.x - windowMarginX, clipRectMin.y + windowMarginY + itemHeight * i + itemHeight };
|
||||
|
||||
if (ImGui::IsMouseHoveringRect(itemMin, itemMax, false))
|
||||
g_selectedRowIndex = i;
|
||||
}
|
||||
|
||||
ButtonGuide::Open({ Button(Localise("Common_Select"), EButtonIcon::LMB) });
|
||||
}
|
||||
|
||||
if (g_selectedRowIndex != -1 && isAccepted)
|
||||
{
|
||||
g_result = g_selectedRowIndex;
|
||||
|
||||
Game_PlaySound("sys_actstg_pausedecide");
|
||||
MessageWindow::Close();
|
||||
}
|
||||
|
||||
drawList->PopClipRect();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!g_isControlsVisible && isAccepted)
|
||||
{
|
||||
g_controlsAppearTime = ImGui::GetTime();
|
||||
g_isControlsVisible = true;
|
||||
|
||||
Game_PlaySound("sys_actstg_pausewinopen");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isAccepted)
|
||||
{
|
||||
g_result = 0;
|
||||
|
||||
MessageWindow::Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (g_isClosing)
|
||||
{
|
||||
s_isVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool MessageWindow::Open(std::string text, int* result, std::span<std::string> buttons, int defaultButtonIndex)
|
||||
{
|
||||
if (!g_isAwaitingResult && *result == -1)
|
||||
{
|
||||
s_isVisible = true;
|
||||
g_isClosing = false;
|
||||
g_isControlsVisible = false;
|
||||
g_foregroundCount = 0;
|
||||
g_appearTime = ImGui::GetTime();
|
||||
g_controlsAppearTime = ImGui::GetTime();
|
||||
|
||||
g_text = text;
|
||||
g_buttons = std::vector(buttons.begin(), buttons.end());
|
||||
g_defaultButtonIndex = g_isGameLoaded ? defaultButtonIndex : -1;
|
||||
|
||||
ResetSelection();
|
||||
|
||||
ButtonGuide::Open({ Button(Localise("Common_Next"), g_isGameLoaded ? EButtonIcon::A : EButtonIcon::LMB) });
|
||||
|
||||
Game_PlaySound("sys_actstg_pausewinopen");
|
||||
|
||||
g_isAwaitingResult = true;
|
||||
}
|
||||
|
||||
*result = g_result;
|
||||
|
||||
return !g_isAwaitingResult;
|
||||
}
|
||||
|
||||
void MessageWindow::Close()
|
||||
{
|
||||
if (!g_isClosing)
|
||||
{
|
||||
g_appearTime = ImGui::GetTime();
|
||||
g_controlsAppearTime = ImGui::GetTime();
|
||||
g_isClosing = true;
|
||||
g_isControlsVisible = false;
|
||||
g_foregroundCount = 0;
|
||||
g_isAwaitingResult = false;
|
||||
|
||||
ButtonGuide::Close();
|
||||
}
|
||||
|
||||
Game_PlaySound("sys_actstg_pausewinclose");
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
class MessageWindow
|
||||
{
|
||||
public:
|
||||
inline static bool s_isVisible = false;
|
||||
|
||||
static void Init();
|
||||
static void Draw();
|
||||
static bool Open(std::string text, int* result, std::span<std::string> buttons = {}, int defaultButtonIndex = 0);
|
||||
static void Close();
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <api/SWA.h>
|
||||
|
||||
class OptionsMenu
|
||||
{
|
||||
public:
|
||||
inline static bool s_isVisible = false;
|
||||
inline static bool s_isPause = false;
|
||||
|
||||
inline static SWA::EMenuType s_pauseMenuType;
|
||||
|
||||
static void Init();
|
||||
static void Draw();
|
||||
static void Open(bool isPause = false, SWA::EMenuType pauseMenuType = SWA::eMenuType_WorldMap);
|
||||
static void Close();
|
||||
|
||||
static bool CanClose();
|
||||
};
|
||||
@@ -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
|
||||
|
||||
#include "ui/window.h"
|
||||
|
||||
class ISDLEventListener
|
||||
{
|
||||
public:
|
||||
@@ -9,12 +7,14 @@ public:
|
||||
virtual void OnSDLEvent(SDL_Event* event) = 0;
|
||||
};
|
||||
|
||||
std::vector<ISDLEventListener*>& GetEventListeners();
|
||||
|
||||
class SDLEventListener : public ISDLEventListener
|
||||
{
|
||||
public:
|
||||
SDLEventListener()
|
||||
{
|
||||
Window::s_eventListeners.emplace_back(this);
|
||||
GetEventListeners().emplace_back(this);
|
||||
}
|
||||
|
||||
void OnSDLEvent(SDL_Event* event) override {}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
#include "window.h"
|
||||
#include "sdl_listener.h"
|
||||
#include <cfg/config.h>
|
||||
#include <user/config.h>
|
||||
#include <SDL_syswm.h>
|
||||
|
||||
bool m_isFullscreenKeyReleased = true;
|
||||
bool m_isResizing = false;
|
||||
|
||||
int Window_OnSDLEvent(void*, SDL_Event* event)
|
||||
{
|
||||
if (ImGui::GetIO().BackendPlatformUserData != nullptr)
|
||||
ImGui_ImplSDL2_ProcessEvent(event);
|
||||
|
||||
for (auto listener : Window::s_eventListeners)
|
||||
for (auto listener : GetEventListeners())
|
||||
listener->OnSDLEvent(event);
|
||||
|
||||
switch (event->type)
|
||||
@@ -84,7 +85,7 @@ int Window_OnSDLEvent(void*, SDL_Event* event)
|
||||
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
Window::s_isFocused = true;
|
||||
SDL_ShowCursor(Window::IsFullscreen() ? SDL_DISABLE : SDL_ENABLE);
|
||||
SDL_ShowCursor(Window::IsFullscreen() && !Window::s_cursorAllowed ? SDL_DISABLE : SDL_ENABLE);
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_RESTORED:
|
||||
@@ -96,8 +97,10 @@ int Window_OnSDLEvent(void*, SDL_Event* event)
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
m_isResizing = true;
|
||||
Window::s_width = event->window.data1;
|
||||
Window::s_height = event->window.data2;
|
||||
Window::SetTitle(std::format("{} - [{}x{}]", Window::GetTitle(), Window::s_width, Window::s_height).c_str());
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT_MOVED:
|
||||
@@ -117,6 +120,20 @@ int Window_OnSDLEvent(void*, SDL_Event* event)
|
||||
}
|
||||
}
|
||||
|
||||
if (!Window::IsFullscreen())
|
||||
{
|
||||
if (event->type == SDL_CONTROLLERBUTTONDOWN || event->type == SDL_CONTROLLERBUTTONUP || event->type == SDL_CONTROLLERAXISMOTION)
|
||||
{
|
||||
// Hide mouse cursor when controller input is detected.
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
}
|
||||
else if (event->type == SDL_MOUSEMOTION)
|
||||
{
|
||||
// Restore mouse cursor when mouse input is detected.
|
||||
SDL_ShowCursor(SDL_ENABLE);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -177,4 +194,10 @@ void Window::Update()
|
||||
Config::WindowWidth = Window::s_width;
|
||||
Config::WindowHeight = Window::s_height;
|
||||
}
|
||||
|
||||
if (m_isResizing)
|
||||
{
|
||||
SetTitle();
|
||||
m_isResizing = false;
|
||||
}
|
||||
}
|
||||
|
||||
+23
-18
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <res/icon.h>
|
||||
#include <res/icon_night.h>
|
||||
#include <res/images/game_icon.bmp.h>
|
||||
#include <res/images/game_icon_night.bmp.h>
|
||||
#include <ui/window_events.h>
|
||||
#include <cfg/config.h>
|
||||
#include <user/config.h>
|
||||
|
||||
#if _WIN32
|
||||
#include <dwmapi.h>
|
||||
@@ -14,8 +14,6 @@
|
||||
#define DEFAULT_WIDTH 1280
|
||||
#define DEFAULT_HEIGHT 720
|
||||
|
||||
class SDLEventListener;
|
||||
|
||||
class Window
|
||||
{
|
||||
public:
|
||||
@@ -29,8 +27,7 @@ public:
|
||||
|
||||
inline static bool s_isFocused;
|
||||
inline static bool s_isIconNight;
|
||||
|
||||
inline static std::vector<SDLEventListener*> s_eventListeners;
|
||||
inline static bool s_cursorAllowed = false;
|
||||
|
||||
static SDL_Surface* GetIconSurface(void* pIconBmp, size_t iconSize)
|
||||
{
|
||||
@@ -56,24 +53,24 @@ public:
|
||||
{
|
||||
if (isNight)
|
||||
{
|
||||
SetIcon((void*)g_iconNight, g_iconNight_size);
|
||||
SetIcon(g_game_icon_night, sizeof(g_game_icon_night));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetIcon((void*)g_icon, g_icon_size);
|
||||
SetIcon(g_game_icon, sizeof(g_game_icon));
|
||||
}
|
||||
}
|
||||
|
||||
static const char* GetTitle()
|
||||
{
|
||||
return Config::Language == ELanguage::Japanese
|
||||
? "SONIC WORLD ADVENTURE"
|
||||
: "SONIC UNLEASHED";
|
||||
}
|
||||
|
||||
static void SetTitle(const char* title = nullptr)
|
||||
{
|
||||
if (!title)
|
||||
{
|
||||
title = Config::Language == ELanguage::Japanese
|
||||
? "SONIC WORLD ADVENTURE"
|
||||
: "SONIC UNLEASHED";
|
||||
}
|
||||
|
||||
SDL_SetWindowTitle(s_pWindow, title);
|
||||
SDL_SetWindowTitle(s_pWindow, title ? title : GetTitle());
|
||||
}
|
||||
|
||||
static void SetDarkTitleBar(bool isEnabled)
|
||||
@@ -103,7 +100,7 @@ public:
|
||||
if (isEnabled)
|
||||
{
|
||||
SDL_SetWindowFullscreen(s_pWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
SDL_ShowCursor(s_cursorAllowed ? SDL_ENABLE : SDL_DISABLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -114,6 +111,14 @@ public:
|
||||
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
static void SetCursorAllowed(bool isCursorAllowed)
|
||||
{
|
||||
s_cursorAllowed = isCursorAllowed;
|
||||
|
||||
// Refresh fullscreen state to enable the right cursor behavior.
|
||||
SetFullscreen(IsFullscreen());
|
||||
}
|
||||
|
||||
static bool IsMaximised()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user