Files
UnleashedRecomp-hedge-dev/UnleashedRecomp/ui/message_window.cpp
Hyper df03a64305 Redirected Install button to custom implementation
This currently fades out and closes the game upon accepting the message, it does not yet reboot into the installer menu using --install-dlc.
2024-12-07 01:02:59 +00:00

473 lines
15 KiB
C++

#include "message_window.h"
#include "imgui_utils.h"
#include <api/SWA.h>
#include <gpu/video.h>
#include <hid/hid_detail.h>
#include <locale/locale.h>
#include <ui/button_guide.h>
#include <ui/sdl_listener.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 ImVec2 g_joypadAxis = {};
static bool g_isAccepted;
static bool g_isDeclined;
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;
int g_cancelButtonIndex;
class SDLEventListenerForMessageWindow : public SDLEventListener
{
public:
void OnSDLEvent(SDL_Event* event) override
{
if (!MessageWindow::s_isVisible)
return;
constexpr float axisValueRange = 32767.0f;
constexpr float axisTapRange = 0.5f;
ImVec2 tapDirection = {};
switch (event->type)
{
case SDL_KEYDOWN:
{
if (g_isAppInit)
break;
switch (event->key.keysym.scancode)
{
case SDL_SCANCODE_UP:
g_joypadAxis.y = -1.0f;
break;
case SDL_SCANCODE_DOWN:
g_joypadAxis.y = 1.0f;
break;
case SDL_SCANCODE_RETURN:
case SDL_SCANCODE_KP_ENTER:
g_isAccepted = true;
break;
case SDL_SCANCODE_ESCAPE:
g_isDeclined = true;
break;
}
break;
}
case SDL_MOUSEBUTTONDOWN:
{
if (g_isAppInit)
break;
g_isAccepted = true;
break;
}
case SDL_CONTROLLERBUTTONDOWN:
{
switch (event->cbutton.button)
{
case SDL_CONTROLLER_BUTTON_DPAD_UP:
g_joypadAxis = { 0.0f, -1.0f };
break;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
g_joypadAxis = { 0.0f, 1.0f };
break;
case SDL_CONTROLLER_BUTTON_A:
g_isAccepted = true;
break;
case SDL_CONTROLLER_BUTTON_B:
g_isDeclined = true;
break;
}
break;
}
case SDL_CONTROLLERAXISMOTION:
{
if (event->caxis.axis < 2)
{
float newAxisValue = event->caxis.value / axisValueRange;
bool sameDirection = (newAxisValue * g_joypadAxis[event->caxis.axis]) > 0.0f;
bool wasInRange = abs(g_joypadAxis[event->caxis.axis]) > axisTapRange;
bool isInRange = abs(newAxisValue) > axisTapRange;
if (sameDirection && !wasInRange && isInRange)
tapDirection[event->caxis.axis] = newAxisValue;
g_joypadAxis[event->caxis.axis] = newAxisValue;
}
break;
}
}
}
};
static SDLEventListenerForMessageWindow g_eventListener;
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, 190 * (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;
g_joypadAxis = {};
g_isAccepted = false;
g_isDeclined = 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 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);
bool isController = g_isAppInit ? true : hid::detail::g_inputDevice == hid::detail::EInputDevice::Controller;
bool isKeyboard = g_isAppInit ? false : hid::detail::g_inputDevice == hid::detail::EInputDevice::Keyboard;
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();
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 (isController || isKeyboard)
{
bool upIsHeld = g_joypadAxis.y > 0.5f;
bool downIsHeld = g_joypadAxis.y < -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 (isController)
{
std::array<Button, 2> buttons =
{
Button(Localise("Common_Select"), EButtonIcon::A),
Button(Localise("Common_Back"), EButtonIcon::B),
};
ButtonGuide::Open(buttons);
}
else
{
ButtonGuide::Open(Button(Localise("Common_Select"), EButtonIcon::Enter));
}
if (g_isDeclined)
{
g_result = g_cancelButtonIndex;
Game_PlaySound("sys_actstg_pausecansel");
MessageWindow::Close();
}
}
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 && g_isAccepted)
{
g_result = g_selectedRowIndex;
Game_PlaySound("sys_actstg_pausedecide");
MessageWindow::Close();
}
drawList->PopClipRect();
}
else
{
auto icon = isController
? EButtonIcon::A
: isKeyboard
? EButtonIcon::Enter
: EButtonIcon::LMB;
ButtonGuide::Open(Button(Localise("Common_Next"), icon));
if (!g_isControlsVisible && g_isAccepted)
{
g_controlsAppearTime = ImGui::GetTime();
g_isControlsVisible = true;
ResetSelection();
Game_PlaySound("sys_actstg_pausewinopen");
}
}
}
else
{
if (g_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, int cancelButtonIndex)
{
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 = defaultButtonIndex;
g_cancelButtonIndex = cancelButtonIndex;
ResetSelection();
Game_PlaySound("sys_actstg_pausewinopen");
g_isAwaitingResult = true;
}
*result = g_result;
// Returns true when the message window is closed.
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");
}