Implemented title animation for options menu (#235)

* options_menu: implemented title animation

* Adjust options title flash animation.

* Replace use of std::numbers::pi with M_PI.

* Implement shader modifier for options title rectangle.

* Replicate the same scaling applied to the rectangle in world map.

---------

Co-authored-by: Skyth <19259897+blueskythlikesclouds@users.noreply.github.com>
This commit is contained in:
Hyper 2025-01-29 11:39:10 +00:00 committed by GitHub
parent 863e1602ff
commit aa6118b448
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 187 additions and 44 deletions

View file

@ -9,6 +9,7 @@
#define IMGUI_SHADER_MODIFIER_GRAYSCALE 6
#define IMGUI_SHADER_MODIFIER_TITLE_BEVEL 7
#define IMGUI_SHADER_MODIFIER_CATEGORY_BEVEL 8
#define IMGUI_SHADER_MODIFIER_RECTANGLE_BEVEL 9
#ifdef __cplusplus

View file

@ -146,7 +146,27 @@ float4 main(in Interpolators interpolators) : SV_Target
else if (any(g_PushConstants.BoundsMin != g_PushConstants.BoundsMax))
{
float2 factor = saturate((interpolators.Position.xy - g_PushConstants.BoundsMin) / (g_PushConstants.BoundsMax - g_PushConstants.BoundsMin));
color *= lerp(DecodeColor(g_PushConstants.GradientTop), DecodeColor(g_PushConstants.GradientBottom), smoothstep(0.0, 1.0, factor.y));
if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_RECTANGLE_BEVEL)
{
float bevelSize = 0.9;
float shadow = saturate((factor.x - bevelSize) / (1.0 - bevelSize));
shadow = max(shadow, saturate((factor.y - bevelSize) / (1.0 - bevelSize)));
float rim = saturate((1.0 - factor.x - bevelSize) / (1.0 - bevelSize));
rim = max(rim, saturate((1.0 - factor.y - bevelSize) / (1.0 - bevelSize)));
float3 rimColor = float3(1, 0.8, 0.29);
float3 shadowColor = float3(0.84, 0.57, 0);
color.rgb = lerp(color.rgb, rimColor, smoothstep(0.0, 1.0, rim));
color.rgb = lerp(color.rgb, shadowColor, smoothstep(0.0, 1.0, shadow));
}
else
{
color *= lerp(DecodeColor(g_PushConstants.GradientTop), DecodeColor(g_PushConstants.GradientBottom), smoothstep(0.0, 1.0, factor.y));
}
}
if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_GRAYSCALE)

View file

@ -24,7 +24,12 @@ std::unordered_map<std::string, std::unordered_map<ELanguage, std::string>> g_lo
{
"Options_Header_Name",
{
{ ELanguage::English, "OPTIONS" }
{ ELanguage::English, "OPTIONS" },
{ ELanguage::Japanese, "OPTION" },
{ ELanguage::German, "OPTIONEN" },
{ ELanguage::French, "OPTIONS" },
{ ELanguage::Spanish, "OPCIONES" },
{ ELanguage::Italian, "OPZIONI" }
}
},
{

View file

@ -126,9 +126,14 @@ inline float Scale(float size)
return size * g_aspectRatioScale;
}
inline double ComputeLinearMotion(double duration, double offset, double total)
{
return std::clamp((ImGui::GetTime() - duration - offset / 60.0) / total * 60.0, 0.0, 1.0);
}
inline 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));
return sqrt(ComputeLinearMotion(duration, offset, total));
}
inline void DrawPauseContainer(GuestTexture* texture, ImVec2 min, ImVec2 max, float alpha = 1)

View file

@ -89,11 +89,139 @@ static bool g_isControlsVisible = false;
static bool g_isEnterKeyBuffered = false;
static bool g_canReset = false;
static bool g_isLanguageOptionChanged = false;
static bool g_titleAnimBegin = true;
static double g_appearTime = 0.0;
static std::unique_ptr<GuestTexture> g_upMilesElectric;
static void DrawTitle()
{
static constexpr double fadeOffset = 3.0;
static constexpr double fadeDuration = 28.0;
static constexpr double fadeAdditiveDuration = 20.0;
static constexpr double squareMoveDuration = 5.0;
static constexpr double squareMoveEndDuration = 40.0;
static constexpr double squareBlinkDuration = 10.0;
static constexpr double squareFadeDuration = 45.0;
static bool isRectVisible;
static bool isRectFinalAdjustment;
static float rectX;
static double rectMoveMotionOffset;
static double rectBlinkMotionOffset;
if (g_titleAnimBegin)
{
isRectVisible = true;
isRectFinalAdjustment = false;
rectX = 0;
rectMoveMotionOffset = 0;
rectBlinkMotionOffset = 0;
g_titleAnimBegin = false;
}
auto drawList = ImGui::GetForegroundDrawList();
auto x = Scale(122);
auto y = Scale(56);
if (g_aspectRatio >= WIDE_ASPECT_RATIO)
x += g_aspectRatioOffsetX;
else
x += (1.0f - g_aspectRatioNarrowScale) * g_aspectRatioScale * -20.0f;
ImVec2 optionsMin = { x, y };
auto drawText = [&](float alpha)
{
DrawTextWithOutline
(
g_dfsogeistdFont,
Scale(48),
optionsMin,
IM_COL32(255, 190, 33, 255 * alpha),
Localise("Options_Header_Name").c_str(),
4,
IM_COL32(0, 0, 0, 255 * alpha),
IMGUI_SHADER_MODIFIER_TITLE_BEVEL
);
};
if (g_isStage)
{
drawText(1.0f);
return;
}
drawText(Hermite(0.0f, 1.0f, ComputeMotion(g_appearTime, fadeOffset, fadeDuration)));
auto rectMoveMotion = ComputeMotion(g_appearTime, rectMoveMotionOffset, squareMoveDuration);
auto rectEndMotion = ComputeMotion(g_appearTime, 0.0, squareMoveEndDuration);
auto rectBlinkMotion = sin(ComputeMotion(g_appearTime, squareMoveEndDuration + rectBlinkMotionOffset, squareBlinkDuration) * M_PI);
auto rectAlphaMotion = 1.0f;
auto rectY = Scale(10);
auto rectSize = Scale(32);
if (rectBlinkMotion > 0.0)
{
if (!isRectFinalAdjustment)
{
/* Ensure the blinking animation starts
past the German localisation. */
rectX += rectSize + (rectSize * 0.25f);
isRectFinalAdjustment = true;
}
isRectVisible = !isRectVisible;
rectBlinkMotionOffset += squareBlinkDuration;
}
if (rectEndMotion >= 1.0)
{
// Fade out the square.
rectAlphaMotion = 1.0f - ComputeMotion(g_appearTime, squareMoveEndDuration + squareBlinkDuration, squareFadeDuration);
}
else if (rectMoveMotion >= 1.0)
{
// Move the square along in steps by its own width and offset the animation to repeat.
rectX += rectSize;
rectMoveMotionOffset += squareMoveDuration;
}
if (isRectVisible)
{
float rectScale = 1.0f;
if (rectBlinkMotion == 0.0)
{
constexpr float RECT_SCALES[] = { 1.2f, 1.1f, 1.1f, 1.0f, 1.15f, 0.4f, 1.2f, 1.1f, 1.05f, 1.0f, 1.5f, 1.2f, 1.0f };
rectScale = RECT_SCALES[uint32_t(round(rectX / rectSize)) % std::size(RECT_SCALES)];
}
ImVec2 rectMin = { optionsMin.x + rectX, optionsMin.y + rectY };
ImVec2 rectMax = { optionsMin.x + rectX + rectSize * rectScale, optionsMin.y + rectY + rectSize };
auto rectOutlineMargin = Scale(2.5f);
ImVec2 rectOutlineMin = { rectMin.x - rectOutlineMargin, rectMin.y - rectOutlineMargin };
ImVec2 rectOutlineMax = { rectMax.x + rectOutlineMargin, rectMax.y + rectOutlineMargin };
drawList->AddRectFilled(rectOutlineMin, rectOutlineMax, IM_COL32(0, 0, 0, 255 * rectAlphaMotion), Scale(5));
SetShaderModifier(IMGUI_SHADER_MODIFIER_RECTANGLE_BEVEL);
SetGradient(rectMin, rectMax, IM_COL32_WHITE, IM_COL32_WHITE);
drawList->AddRectFilled(rectMin, rectMax, IM_COL32(255, 188, 0, 255 * rectAlphaMotion));
ResetGradient();
SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);
}
// The flash gets rendered after the rectangle in the original game.
SetAdditive(true);
drawText(1.0 - 2.0 * abs(ComputeLinearMotion(g_appearTime, fadeDuration, fadeAdditiveDuration) - 0.5));
SetAdditive(false);
}
static void DrawScanlineBars()
{
constexpr uint32_t COLOR0 = IM_COL32(203, 255, 0, 0);
@ -162,53 +290,36 @@ static void DrawScanlineBars()
SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);
float optionsX;
if (g_aspectRatio >= WIDE_ASPECT_RATIO)
optionsX = g_aspectRatioOffsetX;
else
optionsX = (1.0f - g_aspectRatioNarrowScale) * g_aspectRatioScale * -20.0f;
// Options text
DrawTextWithOutline
(
g_dfsogeistdFont,
Scale(48.0f),
{ optionsX + Scale(122.0f), Scale(56.0f) },
IM_COL32(255, 190, 33, 255),
Localise("Options_Header_Name").c_str(),
4,
IM_COL32_BLACK,
IMGUI_SHADER_MODIFIER_TITLE_BEVEL
);
DrawTitle();
auto drawLine = [&](bool top)
{
float y = top ? height : (res.y - height);
{
float y = top ? height : (res.y - height);
constexpr uint32_t TOP_COLOR0 = IM_COL32(222, 255, 189, 7);
constexpr uint32_t TOP_COLOR1 = IM_COL32(222, 255, 189, 65);
constexpr uint32_t BOTTOM_COLOR0 = IM_COL32(173, 255, 156, 65);
constexpr uint32_t BOTTOM_COLOR1 = IM_COL32(173, 255, 156, 7);
constexpr uint32_t TOP_COLOR0 = IM_COL32(222, 255, 189, 7);
constexpr uint32_t TOP_COLOR1 = IM_COL32(222, 255, 189, 65);
constexpr uint32_t BOTTOM_COLOR0 = IM_COL32(173, 255, 156, 65);
constexpr uint32_t BOTTOM_COLOR1 = IM_COL32(173, 255, 156, 7);
drawList->AddRectFilledMultiColor(
{ 0.0f, y - Scale(2.0f) },
{ res.x, y },
top ? TOP_COLOR0 : BOTTOM_COLOR1,
top ? TOP_COLOR0 : BOTTOM_COLOR1,
top ? TOP_COLOR1 : BOTTOM_COLOR0,
top ? TOP_COLOR1 : BOTTOM_COLOR0);
drawList->AddRectFilledMultiColor(
{ 0.0f, y - Scale(2.0f) },
{ res.x, y },
top ? TOP_COLOR0 : BOTTOM_COLOR1,
top ? TOP_COLOR0 : BOTTOM_COLOR1,
top ? TOP_COLOR1 : BOTTOM_COLOR0,
top ? TOP_COLOR1 : BOTTOM_COLOR0);
drawList->AddRectFilledMultiColor(
{ 0.0f, y + Scale(1.0f) },
{ res.x, y + Scale(3.0f) },
top ? BOTTOM_COLOR0 : TOP_COLOR1,
top ? BOTTOM_COLOR0 : TOP_COLOR1,
top ? BOTTOM_COLOR1 : TOP_COLOR0,
top ? BOTTOM_COLOR1 : TOP_COLOR0);
drawList->AddRectFilledMultiColor(
{ 0.0f, y + Scale(1.0f) },
{ res.x, y + Scale(3.0f) },
top ? BOTTOM_COLOR0 : TOP_COLOR1,
top ? BOTTOM_COLOR0 : TOP_COLOR1,
top ? BOTTOM_COLOR1 : TOP_COLOR0,
top ? BOTTOM_COLOR1 : TOP_COLOR0);
constexpr uint32_t CENTER_COLOR = IM_COL32(115, 178, 104, 255);
drawList->AddRectFilled({ 0.0f, y }, { res.x, y + Scale(1.0f) }, CENTER_COLOR);
};
constexpr uint32_t CENTER_COLOR = IM_COL32(115, 178, 104, 255);
drawList->AddRectFilled({ 0.0f, y }, { res.x, y + Scale(1.0f) }, CENTER_COLOR);
};
// Top bar line
drawLine(true);
@ -1284,6 +1395,7 @@ void OptionsMenu::Open(bool isPause, SWA::EMenuType pauseMenuType)
g_categoryAnimMin = { 0.0f, 0.0f };
g_categoryAnimMax = { 0.0f, 0.0f };
g_selectedItem = nullptr;
g_titleAnimBegin = true;
/* Store button state so we can track it later
and prevent the first item being selected. */