mirror of
https://github.com/hedge-dev/UnleashedRecomp.git
synced 2026-04-27 04:41:39 +00:00
Implemented message window
This commit is contained in:
parent
03381065c7
commit
55d40032b0
7 changed files with 433 additions and 2 deletions
|
|
@ -86,6 +86,7 @@ set(SWA_UI_CXX_SOURCES
|
|||
"ui/achievement_menu.cpp"
|
||||
"ui/achievement_overlay.cpp"
|
||||
"ui/installer_wizard.cpp"
|
||||
"ui/message_window.cpp"
|
||||
"ui/options_menu.cpp"
|
||||
"ui/sdl_listener.cpp"
|
||||
"ui/window.cpp"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <shader/shader_cache.h>
|
||||
#include <ui/achievement_menu.h>
|
||||
#include <ui/achievement_overlay.h>
|
||||
#include <ui/message_window.h>
|
||||
#include <ui/options_menu.h>
|
||||
#include <ui/installer_wizard.h>
|
||||
|
||||
|
|
@ -1066,6 +1067,7 @@ static void CreateImGuiBackend()
|
|||
|
||||
AchievementMenu::Init();
|
||||
AchievementOverlay::Init();
|
||||
MessageWindow::Init();
|
||||
OptionsMenu::Init();
|
||||
InstallerWizard::Init();
|
||||
|
||||
|
|
@ -1741,6 +1743,7 @@ static void DrawImGui()
|
|||
OptionsMenu::Draw();
|
||||
AchievementOverlay::Draw();
|
||||
InstallerWizard::Draw();
|
||||
MessageWindow::Draw();
|
||||
|
||||
ImGui::Render();
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <string>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <span>
|
||||
#include <xbox.h>
|
||||
#include <xxhash.h>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@
|
|||
#include <gpu/imgui_common.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)
|
||||
|
||||
static std::vector<std::unique_ptr<ImGuiCallbackData>> g_callbackData;
|
||||
static uint32_t g_callbackDataIndex = 0;
|
||||
|
||||
|
|
@ -150,7 +156,80 @@ static void DrawTextWithShadow(const ImFont* font, float fontSize, const ImVec2&
|
|||
|
||||
DrawTextWithOutline<float>(font, fontSize, { pos.x + offset, pos.y + offset }, shadowColour, text, radius, shadowColour);
|
||||
|
||||
drawList->AddText(font, fontSize, pos, colour, text);
|
||||
drawList->AddText(font, fontSize, pos, colour, text, 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::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;
|
||||
|
||||
for (auto& str : lines)
|
||||
{
|
||||
auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str());
|
||||
|
||||
drawMethod(str.c_str(), ImVec2(/* X */ 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))
|
||||
|
|
|
|||
335
UnleashedRecomp/ui/message_window.cpp
Normal file
335
UnleashedRecomp/ui/message_window.cpp
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
#include "message_window.h"
|
||||
#include "imgui_utils.h"
|
||||
#include <api/SWA.h>
|
||||
#include <gpu/video.h>
|
||||
#include <exports.h>
|
||||
#include "../UnleashedRecompResources/images/pause.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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
auto vertices = GetPauseContainerVertices(_min, _max);
|
||||
|
||||
// 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)));
|
||||
|
||||
auto colShadow = IM_COL32(0, 0, 0, 156 * alpha);
|
||||
auto colGradientTop = IM_COL32(197, 194, 197, 200 * alpha);
|
||||
auto colGradientBottom = IM_COL32(115, 113, 115, 236 * alpha);
|
||||
|
||||
// Draw vertices with gradient.
|
||||
SetGradient(_min, _max, colGradientTop, colGradientBottom);
|
||||
drawList->AddConvexPolyFilled(vertices.data(), vertices.size(), IM_COL32(255, 255, 255, 255 * alpha));
|
||||
ResetGradient();
|
||||
|
||||
// Draw outline.
|
||||
drawList->AddPolyline
|
||||
(
|
||||
vertices.data(),
|
||||
vertices.size(),
|
||||
IM_COL32(247, 247, 247, 255 * alpha),
|
||||
true,
|
||||
Scale(2.5f)
|
||||
);
|
||||
|
||||
// Offset vertices to draw 3D effect lines.
|
||||
for (int i = 0; i < vertices.size(); i++)
|
||||
{
|
||||
vertices[i].x -= Scale(0.4f);
|
||||
vertices[i].y -= Scale(0.2f);
|
||||
}
|
||||
|
||||
auto colLineTop = IM_COL32(165, 170, 165, 230 * alpha);
|
||||
auto colLineBottom = IM_COL32(190, 190, 190, 230 * alpha);
|
||||
auto lineThickness = Scale(1.0f);
|
||||
|
||||
// Top left corner bottom to top left corner top.
|
||||
drawList->AddLine(vertices[0], vertices[1], colLineTop, lineThickness * 0.5f);
|
||||
|
||||
// Top left corner bottom to bottom left.
|
||||
drawList->AddRectFilledMultiColor({ vertices[0].x - 0.2f, vertices[0].y }, { vertices[6].x + lineThickness - 0.2f, vertices[6].y }, colLineTop, colLineTop, colLineBottom, colLineBottom);
|
||||
|
||||
// Top left corner top to top right.
|
||||
drawList->AddLine(vertices[1], vertices[2], colLineTop, lineThickness);
|
||||
|
||||
drawList->PushClipRect(_min, _max);
|
||||
|
||||
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(128, 128, 0, 0, 11, 50);
|
||||
auto centre = PIXELS_TO_UV_COORDS(128, 128, 11, 0, 8, 50);
|
||||
auto right = PIXELS_TO_UV_COORDS(128, 128, 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 = io.Fonts->AddFontFromFileTTF("FOT-SeuratPro-M.otf", 28.0f * FONT_SCALE);
|
||||
|
||||
g_upSelectionCursor = LoadTexture((uint8_t*)g_res_pause, g_res_pause_size);
|
||||
}
|
||||
|
||||
void MessageWindow::Draw()
|
||||
{
|
||||
if (!s_isVisible)
|
||||
return;
|
||||
|
||||
auto pInputState = SWA::CInputState::GetInstance();
|
||||
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(32);
|
||||
auto textMarginY = Scale(40);
|
||||
|
||||
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(18);
|
||||
auto windowMarginY = Scale(25);
|
||||
|
||||
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);
|
||||
|
||||
drawList->PopClipRect();
|
||||
|
||||
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_A))
|
||||
{
|
||||
g_result = g_selectedRowIndex;
|
||||
|
||||
Game_PlaySound("sys_actstg_pausedecide");
|
||||
MessageWindow::Close();
|
||||
}
|
||||
else if (pInputState->GetPadState().IsTapped(SWA::eKeyState_B))
|
||||
{
|
||||
g_result = -1;
|
||||
|
||||
Game_PlaySound("sys_actstg_pausecansel");
|
||||
MessageWindow::Close();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!g_isControlsVisible && pInputState->GetPadState().IsTapped(SWA::eKeyState_A))
|
||||
{
|
||||
g_controlsAppearTime = ImGui::GetTime();
|
||||
g_isControlsVisible = true;
|
||||
|
||||
Game_PlaySound("sys_actstg_pausewinopen");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pInputState->GetPadState().IsTapped(SWA::eKeyState_A))
|
||||
MessageWindow::Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = defaultButtonIndex;
|
||||
|
||||
ResetSelection();
|
||||
|
||||
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_isAwaitingResult = false;
|
||||
}
|
||||
|
||||
Game_PlaySound("sys_actstg_pausewinclose");
|
||||
}
|
||||
12
UnleashedRecomp/ui/message_window.h
Normal file
12
UnleashedRecomp/ui/message_window.h
Normal 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();
|
||||
};
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include <api/SWA.h>
|
||||
|
||||
struct OptionsMenu
|
||||
class OptionsMenu
|
||||
{
|
||||
public:
|
||||
inline static bool s_isVisible = false;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue