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:
Skyth (Asilkan)
2024-12-06 18:52:06 +03:00
committed by GitHub
parent b0562b4360
commit c0897dd507
78 changed files with 8069 additions and 827 deletions
+678
View File
@@ -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, &timestamp);
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");
}
+12
View File
@@ -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();
};
+200
View File
@@ -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();
}
}
+16
View File
@@ -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();
};
+305
View File
@@ -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;
}
+61
View File
@@ -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();
};
+362
View File
@@ -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
+13
View File
@@ -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);
};
+351
View File
@@ -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");
}
+12
View File
@@ -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
+19
View File
@@ -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();
};
+7
View File
@@ -0,0 +1,7 @@
#include "sdl_listener.h"
std::vector<ISDLEventListener*>& GetEventListeners()
{
static std::vector<ISDLEventListener*> g_eventListeners;
return g_eventListeners;
}
+3 -3
View File
@@ -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 {}
+26 -3
View File
@@ -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
View File
@@ -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()
{