mirror of
https://github.com/stenzek/duckstation.git
synced 2026-02-04 05:04:33 +00:00
Achievements: Show leaderboard times as relative
With a tooltip showing the absolute time.
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/time_helpers.h"
|
||||
#include "common/timer.h"
|
||||
|
||||
#include "IconsEmoji.h"
|
||||
@@ -93,7 +94,8 @@ static void CloseLeaderboard();
|
||||
static void DrawLeaderboardListEntry(const rc_client_leaderboard_t* lboard);
|
||||
static void DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& entry, u32 index, bool is_self,
|
||||
float rank_column_width, float name_column_width, float time_column_width,
|
||||
float column_spacing);
|
||||
float column_spacing, std::time_t current_time, const std::tm& current_tm);
|
||||
static SmallString FormatRelativeTimestamp(std::time_t timestamp, std::time_t current_time, const std::tm& current_tm);
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -1692,6 +1694,14 @@ void FullscreenUI::DrawLeaderboardsWindow()
|
||||
BeginMenuButtons(0, 0.0f, LAYOUT_MENU_BUTTON_X_PADDING, 8.0f, 0.0f, 4.0f);
|
||||
ResetFocusHere();
|
||||
|
||||
// for drawing time popups
|
||||
const std::time_t current_time = std::time(nullptr);
|
||||
std::optional<std::tm> current_tm = Common::LocalTime(current_time);
|
||||
if (!current_tm.has_value())
|
||||
current_tm = std::tm{};
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 0.0f);
|
||||
|
||||
if (!s_achievements_locals.is_showing_all_leaderboard_entries)
|
||||
{
|
||||
if (s_achievements_locals.leaderboard_nearby_entries)
|
||||
@@ -1700,7 +1710,8 @@ void FullscreenUI::DrawLeaderboardsWindow()
|
||||
{
|
||||
DrawLeaderboardEntry(s_achievements_locals.leaderboard_nearby_entries->entries[i], i,
|
||||
static_cast<s32>(i) == s_achievements_locals.leaderboard_nearby_entries->user_index,
|
||||
rank_column_width, name_column_width, time_column_width, column_spacing);
|
||||
rank_column_width, name_column_width, time_column_width, column_spacing, current_time,
|
||||
current_tm.value());
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1720,7 +1731,8 @@ void FullscreenUI::DrawLeaderboardsWindow()
|
||||
for (u32 i = 0; i < list->num_entries; i++)
|
||||
{
|
||||
DrawLeaderboardEntry(list->entries[i], i, static_cast<s32>(i) == list->user_index, rank_column_width,
|
||||
name_column_width, time_column_width, column_spacing);
|
||||
name_column_width, time_column_width, column_spacing, current_time,
|
||||
current_tm.value());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1734,6 +1746,8 @@ void FullscreenUI::DrawLeaderboardsWindow()
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
EndMenuButtons();
|
||||
}
|
||||
EndFullscreenWindow();
|
||||
@@ -1767,7 +1781,7 @@ void FullscreenUI::DrawLeaderboardsWindow()
|
||||
|
||||
void FullscreenUI::DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& entry, u32 index, bool is_self,
|
||||
float rank_column_width, float name_column_width, float time_column_width,
|
||||
float column_spacing)
|
||||
float column_spacing, std::time_t current_time, const std::tm& current_tm)
|
||||
{
|
||||
ImRect bb;
|
||||
bool visible, hovered;
|
||||
@@ -1831,10 +1845,20 @@ void FullscreenUI::DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& ent
|
||||
|
||||
const ImRect time_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
|
||||
|
||||
const std::string submit_time =
|
||||
Host::FormatNumber(Host::NumberFormatType::LongDateTime, static_cast<s64>(entry.submitted));
|
||||
const SmallString relative_time =
|
||||
FormatRelativeTimestamp(static_cast<std::time_t>(entry.submitted), current_time, current_tm);
|
||||
RenderShadowedTextClipped(UIStyle.Font, UIStyle.MediumLargeFontSize, font_weight, time_bb.Min, time_bb.Max,
|
||||
text_color, submit_time, nullptr, ImVec2(0.0f, 0.0f), 0.0f, &time_bb);
|
||||
text_color, relative_time, nullptr, ImVec2(0.0f, 0.0f), 0.0f, &time_bb);
|
||||
|
||||
if (time_bb.Contains(ImGui::GetIO().MousePos) && ImGui::BeginItemTooltip())
|
||||
{
|
||||
const std::string submit_time =
|
||||
Host::FormatNumber(Host::NumberFormatType::LongDateTime, static_cast<s64>(entry.submitted));
|
||||
ImGui::PushFont(UIStyle.Font, UIStyle.MediumLargeFontSize, UIStyle.NormalFontWeight);
|
||||
ImGui::Text(ICON_EMOJI_CLOCK_FIVE_OCLOCK " %s", submit_time.c_str());
|
||||
ImGui::PopFont();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
if (pressed)
|
||||
{
|
||||
@@ -1843,6 +1867,83 @@ void FullscreenUI::DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& ent
|
||||
Host::OpenURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
SmallString FullscreenUI::FormatRelativeTimestamp(time_t timestamp, time_t current_time, const std::tm& current_tm)
|
||||
{
|
||||
const s64 diff = static_cast<s64>(current_time) - static_cast<s64>(timestamp);
|
||||
|
||||
constexpr s64 MINUTE = 60;
|
||||
constexpr s64 HOUR = 60 * MINUTE;
|
||||
constexpr s64 DAY = 24 * HOUR;
|
||||
constexpr s64 WEEK = 7 * DAY;
|
||||
|
||||
if (diff < MINUTE)
|
||||
return SmallString(TRANSLATE_SV("Achievements", "Just now"));
|
||||
|
||||
if (diff < HOUR)
|
||||
{
|
||||
const s64 minutes = diff / MINUTE;
|
||||
return TRANSLATE_PLURAL_SSTR("Achievements", "%n minutes ago", "Relative time", static_cast<int>(minutes));
|
||||
}
|
||||
|
||||
if (diff < DAY)
|
||||
{
|
||||
const s64 hours = diff / HOUR;
|
||||
return TRANSLATE_PLURAL_SSTR("Achievements", "%n hours ago", "Relative time", static_cast<int>(hours));
|
||||
}
|
||||
|
||||
if (diff < DAY * 2)
|
||||
{
|
||||
// Check if it's actually today vs yesterday
|
||||
const std::optional<std::tm> timestamp_tm = Common::LocalTime(timestamp);
|
||||
if (timestamp_tm.has_value() && timestamp_tm->tm_yday == current_tm.tm_yday &&
|
||||
timestamp_tm->tm_year == current_tm.tm_year)
|
||||
{
|
||||
return SmallString(TRANSLATE_SV("Achievements", "Today"));
|
||||
}
|
||||
|
||||
return SmallString(TRANSLATE_SV("Achievements", "Yesterday"));
|
||||
}
|
||||
|
||||
if (diff < WEEK)
|
||||
{
|
||||
const s64 days = diff / DAY;
|
||||
return TRANSLATE_PLURAL_SSTR("Achievements", "%n days ago", "Relative time", static_cast<int>(days));
|
||||
}
|
||||
|
||||
const std::optional<std::tm> timestamp_tm = Common::LocalTime(timestamp);
|
||||
if (!timestamp_tm.has_value())
|
||||
return SmallString();
|
||||
|
||||
const int year_diff = current_tm.tm_year - timestamp_tm->tm_year;
|
||||
const int month_diff = current_tm.tm_mon - timestamp_tm->tm_mon;
|
||||
const int total_months = year_diff * 12 + month_diff;
|
||||
|
||||
if (total_months == 0)
|
||||
{
|
||||
// Less than a month - use weeks
|
||||
const s64 weeks = diff / WEEK;
|
||||
return TRANSLATE_PLURAL_SSTR("Achievements", "%n weeks ago", "Relative time", static_cast<int>(weeks));
|
||||
}
|
||||
|
||||
if (total_months < 12)
|
||||
return TRANSLATE_PLURAL_SSTR("Achievements", "%n months ago", "Relative time", total_months);
|
||||
|
||||
// For years, adjust if we haven't reached the anniversary yet
|
||||
int years = year_diff;
|
||||
if (current_tm.tm_mon < timestamp_tm->tm_mon ||
|
||||
(current_tm.tm_mon == timestamp_tm->tm_mon && current_tm.tm_mday < timestamp_tm->tm_mday))
|
||||
{
|
||||
years--;
|
||||
}
|
||||
|
||||
// Edge case: less than a full year but more than 11 months
|
||||
if (years < 1)
|
||||
return TRANSLATE_PLURAL_SSTR("Achievements", "%n months ago", "Relative time", total_months);
|
||||
|
||||
return TRANSLATE_PLURAL_SSTR("Achievements", "%n years ago", "Relative time", years);
|
||||
}
|
||||
|
||||
void FullscreenUI::DrawLeaderboardListEntry(const rc_client_leaderboard_t* lboard)
|
||||
{
|
||||
SmallString title;
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<context>
|
||||
<name>AchievementSettingsWidget</name>
|
||||
<message numerus="yes">
|
||||
<location filename="../achievementsettingswidget.cpp" line="194"/>
|
||||
<location filename="../achievementsettingswidget.cpp" line="201"/>
|
||||
<location filename="../achievementsettingswidget.cpp" line="197"/>
|
||||
<location filename="../achievementsettingswidget.cpp" line="204"/>
|
||||
<source>%n seconds</source>
|
||||
<translation>
|
||||
<numerusform>%n second</numerusform>
|
||||
@@ -16,7 +16,7 @@
|
||||
<context>
|
||||
<name>Achievements</name>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/achievements.cpp" line="1272"/>
|
||||
<location filename="../../core/achievements.cpp" line="1296"/>
|
||||
<source>You have unlocked {} of %n achievements</source>
|
||||
<comment>Achievement popup</comment>
|
||||
<translation>
|
||||
@@ -25,7 +25,7 @@
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/achievements.cpp" line="1275"/>
|
||||
<location filename="../../core/achievements.cpp" line="1299"/>
|
||||
<source>and earned {} of %n points</source>
|
||||
<comment>Achievement popup</comment>
|
||||
<translation>
|
||||
@@ -34,7 +34,7 @@
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/achievements.cpp" line="1305"/>
|
||||
<location filename="../../core/achievements.cpp" line="1329"/>
|
||||
<source>%n achievements are not supported by DuckStation.</source>
|
||||
<comment>Achievement popup</comment>
|
||||
<translation>
|
||||
@@ -43,8 +43,8 @@
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/achievements.cpp" line="1379"/>
|
||||
<location filename="../../core/achievements.cpp" line="1401"/>
|
||||
<location filename="../../core/achievements.cpp" line="1403"/>
|
||||
<location filename="../../core/achievements.cpp" line="1425"/>
|
||||
<source>%n achievements</source>
|
||||
<comment>Mastery popup</comment>
|
||||
<translation>
|
||||
@@ -53,9 +53,9 @@
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/achievements.cpp" line="1381"/>
|
||||
<location filename="../../core/achievements.cpp" line="1403"/>
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="1161"/>
|
||||
<location filename="../../core/achievements.cpp" line="1405"/>
|
||||
<location filename="../../core/achievements.cpp" line="1427"/>
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="1315"/>
|
||||
<source>%n points</source>
|
||||
<comment>Achievement points</comment>
|
||||
<translation>
|
||||
@@ -64,7 +64,7 @@
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="354"/>
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="358"/>
|
||||
<source>%n unlocks have not been confirmed by the server.</source>
|
||||
<comment>Pause Menu</comment>
|
||||
<translation>
|
||||
@@ -73,7 +73,7 @@
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="888"/>
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="919"/>
|
||||
<source>You have unlocked all achievements and earned %n points!</source>
|
||||
<comment>Point count</comment>
|
||||
<translation>
|
||||
@@ -82,7 +82,7 @@
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="915"/>
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="946"/>
|
||||
<source>%n achievements are not supported by DuckStation and cannot be unlocked.</source>
|
||||
<comment>Unsupported achievement count</comment>
|
||||
<translation>
|
||||
@@ -91,7 +91,7 @@
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="1405"/>
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="1467"/>
|
||||
<source>This game has %n leaderboards.</source>
|
||||
<comment>Leaderboard count</comment>
|
||||
<translation>
|
||||
@@ -100,7 +100,7 @@
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="1407"/>
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="1469"/>
|
||||
<source>This subset has %n leaderboards.</source>
|
||||
<comment>Leaderboard count</comment>
|
||||
<translation>
|
||||
@@ -108,11 +108,66 @@
|
||||
<numerusform>This subset has %n leaderboards.</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="1886"/>
|
||||
<source>%n minutes ago</source>
|
||||
<comment>Relative time</comment>
|
||||
<translation>
|
||||
<numerusform>%n minute ago</numerusform>
|
||||
<numerusform>%n minutes ago</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="1892"/>
|
||||
<source>%n hours ago</source>
|
||||
<comment>Relative time</comment>
|
||||
<translation>
|
||||
<numerusform>%n hour ago</numerusform>
|
||||
<numerusform>%n hours ago</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="1911"/>
|
||||
<source>%n days ago</source>
|
||||
<comment>Relative time</comment>
|
||||
<translation>
|
||||
<numerusform>%n day ago</numerusform>
|
||||
<numerusform>%n days ago</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="1928"/>
|
||||
<source>%n weeks ago</source>
|
||||
<comment>Relative time</comment>
|
||||
<translation>
|
||||
<numerusform>%n week ago</numerusform>
|
||||
<numerusform>%n weeks ago</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="1932"/>
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="1944"/>
|
||||
<source>%n months ago</source>
|
||||
<comment>Relative time</comment>
|
||||
<translation>
|
||||
<numerusform>%n month ago</numerusform>
|
||||
<numerusform>%n months ago</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/fullscreenui_achievements.cpp" line="1946"/>
|
||||
<source>%n years ago</source>
|
||||
<comment>Relative time</comment>
|
||||
<translation>
|
||||
<numerusform>%n year ago</numerusform>
|
||||
<numerusform>%n years ago</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Cheats</name>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/cheats.cpp" line="1098"/>
|
||||
<location filename="../../core/cheats.cpp" line="1118"/>
|
||||
<source>%n game patches are active.</source>
|
||||
<comment>OSD Message</comment>
|
||||
<translation>
|
||||
@@ -121,7 +176,7 @@
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/cheats.cpp" line="1105"/>
|
||||
<location filename="../../core/cheats.cpp" line="1125"/>
|
||||
<source>%n cheats are enabled.</source>
|
||||
<comment>OSD Message</comment>
|
||||
<translation>
|
||||
@@ -130,7 +185,7 @@
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/cheats.cpp" line="1131"/>
|
||||
<location filename="../../core/cheats.cpp" line="1151"/>
|
||||
<source>%n cheats</source>
|
||||
<comment>Cheats blocked by hardcore mode</comment>
|
||||
<translation>
|
||||
@@ -139,7 +194,7 @@
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/cheats.cpp" line="1133"/>
|
||||
<location filename="../../core/cheats.cpp" line="1153"/>
|
||||
<source>%n patches</source>
|
||||
<comment>Patches blocked by hardcore mode</comment>
|
||||
<translation>
|
||||
@@ -170,7 +225,7 @@
|
||||
<context>
|
||||
<name>FullscreenUI</name>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/fullscreenui_widgets.cpp" line="4597"/>
|
||||
<location filename="../../core/fullscreenui_widgets.cpp" line="4740"/>
|
||||
<source>%n seconds remaining</source>
|
||||
<comment>Loading time</comment>
|
||||
<translation>
|
||||
@@ -179,7 +234,7 @@
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/fullscreenui_widgets.cpp" line="4602"/>
|
||||
<location filename="../../core/fullscreenui_widgets.cpp" line="4745"/>
|
||||
<source>%n minutes remaining</source>
|
||||
<comment>Loading time</comment>
|
||||
<translation>
|
||||
@@ -191,7 +246,7 @@
|
||||
<context>
|
||||
<name>GPU_HW</name>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/gpu_hw_texture_cache.cpp" line="3710"/>
|
||||
<location filename="../../core/gpu_hw_texture_cache.cpp" line="3708"/>
|
||||
<source>%n replacement textures found.</source>
|
||||
<comment>Replacement texture count</comment>
|
||||
<translation>
|
||||
@@ -203,7 +258,7 @@
|
||||
<context>
|
||||
<name>GameList</name>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/game_list.cpp" line="1646"/>
|
||||
<location filename="../../core/game_list.cpp" line="1648"/>
|
||||
<source>%n seconds</source>
|
||||
<translation>
|
||||
<numerusform>%n second</numerusform>
|
||||
@@ -211,7 +266,7 @@
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/game_list.cpp" line="1642"/>
|
||||
<location filename="../../core/game_list.cpp" line="1644"/>
|
||||
<source>%n hours</source>
|
||||
<translation>
|
||||
<numerusform>%n hour</numerusform>
|
||||
@@ -219,7 +274,7 @@
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../../core/game_list.cpp" line="1644"/>
|
||||
<location filename="../../core/game_list.cpp" line="1646"/>
|
||||
<source>%n minutes</source>
|
||||
<translation>
|
||||
<numerusform>%n minute</numerusform>
|
||||
@@ -230,7 +285,7 @@
|
||||
<context>
|
||||
<name>InputBindingWidget</name>
|
||||
<message numerus="yes">
|
||||
<location filename="../inputbindingwidgets.cpp" line="101"/>
|
||||
<location filename="../inputbindingwidgets.cpp" line="102"/>
|
||||
<source>%n bindings</source>
|
||||
<translation>
|
||||
<numerusform>%n binding</numerusform>
|
||||
|
||||
Reference in New Issue
Block a user