2023-06-15 11:40:37 +00:00
|
|
|
#include "UI/RetroAchievementScreens.h"
|
2023-06-16 14:29:44 +00:00
|
|
|
#include "UI/RetroAchievements.h"
|
2023-06-19 21:23:50 +00:00
|
|
|
#include "Common/System/Request.h"
|
2023-06-16 14:29:44 +00:00
|
|
|
#include "Common/UI/View.h"
|
|
|
|
#include "Common/UI/ViewGroup.h"
|
2023-06-17 20:22:59 +00:00
|
|
|
#include "Common/UI/Context.h"
|
2023-06-16 14:29:44 +00:00
|
|
|
#include "Common/Data/Text/I18n.h"
|
2023-06-18 13:13:16 +00:00
|
|
|
#include "Common/UI/IconCache.h"
|
2023-06-15 11:40:37 +00:00
|
|
|
|
2023-06-19 21:23:50 +00:00
|
|
|
#include "Core/Config.h"
|
|
|
|
|
2023-06-21 12:32:10 +00:00
|
|
|
void RetroAchievementsListScreen::CreateTabs() {
|
2023-06-15 11:40:37 +00:00
|
|
|
auto di = GetI18NCategory(I18NCat::DIALOG);
|
2023-06-19 21:47:23 +00:00
|
|
|
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
|
2023-06-15 11:40:37 +00:00
|
|
|
|
2023-06-17 09:41:05 +00:00
|
|
|
using namespace UI;
|
|
|
|
|
2023-06-21 12:32:10 +00:00
|
|
|
LinearLayout *achievements = AddTab("Achievements", ac->T("Achievements"));
|
2023-06-17 09:41:05 +00:00
|
|
|
|
2023-06-21 12:32:10 +00:00
|
|
|
achievements->SetSpacing(5.0f);
|
2023-06-19 21:47:23 +00:00
|
|
|
|
|
|
|
std::vector<Achievements::Achievement> unlockedAchievements;
|
|
|
|
std::vector<Achievements::Achievement> lockedAchievements;
|
2023-06-17 09:41:05 +00:00
|
|
|
|
2023-06-21 12:32:10 +00:00
|
|
|
achievements->Add(new ItemHeader(ac->T("Achievements")));
|
|
|
|
|
|
|
|
achievements->Add(new GameAchievementSummaryView(Achievements::GetGameID()));
|
2023-06-17 09:41:05 +00:00
|
|
|
|
|
|
|
Achievements::EnumerateAchievements([&](const Achievements::Achievement &achievement) {
|
2023-06-19 21:47:23 +00:00
|
|
|
if (achievement.locked) {
|
|
|
|
lockedAchievements.push_back(achievement);
|
|
|
|
} else {
|
|
|
|
unlockedAchievements.push_back(achievement);
|
|
|
|
}
|
2023-06-17 09:41:05 +00:00
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
2023-06-21 12:32:10 +00:00
|
|
|
achievements->Add(new ItemHeader(ac->T("Unlocked achievements")));
|
2023-06-19 21:47:23 +00:00
|
|
|
for (auto achievement : unlockedAchievements) {
|
2023-06-21 12:32:10 +00:00
|
|
|
achievements->Add(new AchievementView(achievement));
|
2023-06-19 21:47:23 +00:00
|
|
|
}
|
2023-06-21 12:32:10 +00:00
|
|
|
achievements->Add(new ItemHeader(ac->T("Locked achievements")));
|
2023-06-19 21:47:23 +00:00
|
|
|
for (auto achievement : lockedAchievements) {
|
2023-06-21 12:32:10 +00:00
|
|
|
achievements->Add(new AchievementView(achievement));
|
2023-06-17 09:41:05 +00:00
|
|
|
}
|
2023-06-15 11:40:37 +00:00
|
|
|
}
|
2023-06-16 14:29:44 +00:00
|
|
|
|
|
|
|
void RetroAchievementsSettingsScreen::CreateTabs() {
|
|
|
|
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
|
2023-06-23 20:08:52 +00:00
|
|
|
auto di = GetI18NCategory(I18NCat::DIALOG);
|
2023-06-16 14:29:44 +00:00
|
|
|
|
|
|
|
using namespace UI;
|
|
|
|
|
|
|
|
LinearLayout *account = AddTab("AchievementsAccount", ac->T("Account"));
|
|
|
|
CreateAccountTab(account);
|
2023-06-19 21:23:50 +00:00
|
|
|
|
2023-06-23 20:08:52 +00:00
|
|
|
LinearLayout *settings = AddTab("AchievementsSettings", di->T("Settings"));
|
2023-06-19 21:23:50 +00:00
|
|
|
CreateSettingsTab(settings);
|
2023-06-16 14:29:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RetroAchievementsSettingsScreen::sendMessage(const char *message, const char *value) {
|
|
|
|
TabbedUIDialogScreenWithGameBackground::sendMessage(message, value);
|
|
|
|
|
|
|
|
if (!strcmp(message, "achievements_loginstatechange")) {
|
|
|
|
RecreateViews();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RetroAchievementsSettingsScreen::CreateAccountTab(UI::ViewGroup *viewGroup) {
|
|
|
|
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
|
2023-06-21 13:45:07 +00:00
|
|
|
auto di = GetI18NCategory(I18NCat::DIALOG);
|
2023-06-16 14:29:44 +00:00
|
|
|
|
|
|
|
using namespace UI;
|
|
|
|
|
|
|
|
if (Achievements::IsLoggedIn()) {
|
2023-06-23 20:08:52 +00:00
|
|
|
viewGroup->Add(new InfoItem(ac->T("Username"), Achievements::GetUsername()));
|
|
|
|
viewGroup->Add(new Choice(di->T("Log out")))->OnClick.Add([=](UI::EventParams &) -> UI::EventReturn {
|
2023-06-16 14:29:44 +00:00
|
|
|
Achievements::Logout();
|
|
|
|
return UI::EVENT_DONE;
|
|
|
|
});
|
|
|
|
} else {
|
2023-06-19 21:23:50 +00:00
|
|
|
// TODO: Add UI for platforms that don't support System_AskUsernamePassword.
|
2023-06-21 13:45:07 +00:00
|
|
|
if (System_GetPropertyBool(SYSPROP_HAS_LOGIN_DIALOG)) {
|
2023-06-23 20:08:52 +00:00
|
|
|
viewGroup->Add(new Choice(ac->T("Log in")))->OnClick.Add([=](UI::EventParams &) -> UI::EventReturn {
|
2023-06-21 13:45:07 +00:00
|
|
|
System_AskUsernamePassword(ac->T("Log in"), [](const std::string &value, int) {
|
|
|
|
std::vector<std::string> parts;
|
|
|
|
SplitString(value, '\n', parts);
|
|
|
|
if (parts.size() == 2 && !parts[0].empty() && !parts[1].empty()) {
|
|
|
|
Achievements::LoginAsync(parts[0].c_str(), parts[1].c_str());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return UI::EVENT_DONE;
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// Hack up a temporary quick login-form-ish-thing
|
2023-06-23 20:08:52 +00:00
|
|
|
viewGroup->Add(new PopupTextInputChoice(&username_, di->T("Username"), "", 128, screenManager()));
|
|
|
|
viewGroup->Add(new PopupTextInputChoice(&password_, di->T("Password"), "", 128, screenManager()));
|
|
|
|
viewGroup->Add(new Choice(di->T("Log in")))->OnClick.Add([=](UI::EventParams &) -> UI::EventReturn {
|
2023-06-21 13:45:07 +00:00
|
|
|
if (!username_.empty() && !password_.empty()) {
|
|
|
|
Achievements::LoginAsync(username_.c_str(), password_.c_str());
|
2023-06-19 21:23:50 +00:00
|
|
|
}
|
2023-06-21 13:45:07 +00:00
|
|
|
return UI::EVENT_DONE;
|
2023-06-19 21:23:50 +00:00
|
|
|
});
|
2023-06-21 13:45:07 +00:00
|
|
|
}
|
2023-06-19 21:23:50 +00:00
|
|
|
viewGroup->Add(new Choice(ac->T("Register on www.retroachievements.org")))->OnClick.Add([&](UI::EventParams &) -> UI::EventReturn {
|
|
|
|
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://retroachievements.org/createaccount.php");
|
2023-06-16 14:29:44 +00:00
|
|
|
return UI::EVENT_DONE;
|
|
|
|
});
|
|
|
|
}
|
2023-06-21 13:45:07 +00:00
|
|
|
|
|
|
|
viewGroup->Add(new ItemHeader(di->T("Links")));
|
|
|
|
viewGroup->Add(new Choice(ac->T("RetroAchievements website")))->OnClick.Add([&](UI::EventParams &) -> UI::EventReturn {
|
2023-06-19 21:23:50 +00:00
|
|
|
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.retroachievements.org/");
|
|
|
|
return UI::EVENT_DONE;
|
|
|
|
});
|
2023-06-21 13:45:07 +00:00
|
|
|
viewGroup->Add(new Choice(ac->T("How to use RetroAchievements")))->OnClick.Add([&](UI::EventParams &) -> UI::EventReturn {
|
|
|
|
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org/docs/reference/retro-achievements");
|
|
|
|
return UI::EVENT_DONE;
|
|
|
|
});
|
2023-06-19 21:23:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RetroAchievementsSettingsScreen::CreateSettingsTab(UI::ViewGroup *viewGroup) {
|
|
|
|
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
|
|
|
|
|
|
|
|
using namespace UI;
|
|
|
|
viewGroup->Add(new ItemHeader(ac->T("Settings")));
|
|
|
|
viewGroup->Add(new CheckBox(&g_Config.bAchievementsRichPresence, ac->T("Rich Presence")));
|
2023-06-20 21:59:58 +00:00
|
|
|
viewGroup->Add(new CheckBox(&g_Config.bAchievementsSoundEffects, ac->T("Sound Effects"))); // not yet implemented
|
|
|
|
viewGroup->Add(new CheckBox(&g_Config.bAchievementsLogBadMemReads, ac->T("Log bad memory accesses")));
|
2023-06-19 21:23:50 +00:00
|
|
|
|
|
|
|
// Not yet fully implemented
|
|
|
|
// viewGroup->Add(new CheckBox(&g_Config.bAchievementsChallengeMode, ac->T("Challenge Mode")));
|
|
|
|
|
|
|
|
// TODO: What are these for?
|
|
|
|
// viewGroup->Add(new CheckBox(&g_Config.bAchievementsTestMode, ac->T("Test Mode")));
|
|
|
|
// viewGroup->Add(new CheckBox(&g_Config.bAchievementsUnofficialTestMode, ac->T("Unofficial Test Mode")));
|
2023-06-16 14:29:44 +00:00
|
|
|
}
|
2023-06-17 20:22:59 +00:00
|
|
|
|
2023-06-21 10:17:44 +00:00
|
|
|
void MeasureAchievement(const UIContext &dc, const Achievements::Achievement &achievement, float *w, float *h) {
|
2023-06-17 20:22:59 +00:00
|
|
|
*w = 0.0f;
|
2023-06-21 12:32:10 +00:00
|
|
|
*h = 72.0f;
|
2023-06-17 20:22:59 +00:00
|
|
|
}
|
|
|
|
|
2023-06-21 10:17:44 +00:00
|
|
|
void MeasureGameAchievementSummary(const UIContext &dc, int gameID, float *w, float *h) {
|
2023-06-19 21:47:23 +00:00
|
|
|
*w = 0.0f;
|
2023-06-21 12:32:10 +00:00
|
|
|
*h = 72.0f;
|
2023-06-19 21:47:23 +00:00
|
|
|
}
|
|
|
|
|
2023-06-17 20:22:59 +00:00
|
|
|
// Graphical
|
2023-06-21 12:32:10 +00:00
|
|
|
void RenderAchievement(UIContext &dc, const Achievements::Achievement &achievement, AchievementRenderStyle style, const Bounds &bounds, float alpha, float startTime, float time_s) {
|
2023-06-17 20:22:59 +00:00
|
|
|
using namespace UI;
|
2023-06-26 08:13:18 +00:00
|
|
|
UI::Drawable background = UI::Drawable(dc.theme->backgroundColor);
|
2023-06-21 12:32:10 +00:00
|
|
|
if (achievement.locked) {
|
|
|
|
background.color = 0x706060;
|
|
|
|
}
|
2023-06-21 10:17:44 +00:00
|
|
|
background.color = colorAlpha(background.color, alpha);
|
2023-06-22 07:27:58 +00:00
|
|
|
uint32_t fgColor = colorAlpha(dc.theme->itemStyle.fgColor, alpha);
|
2023-06-17 20:22:59 +00:00
|
|
|
|
2023-06-21 12:32:10 +00:00
|
|
|
if (style == AchievementRenderStyle::UNLOCKED) {
|
|
|
|
float mixWhite = pow(Clamp((float)(1.0f - (time_s - startTime)), 0.0f, 1.0f), 3.0f);
|
|
|
|
background.color = colorBlend(0xFFE0FFFF, background.color, mixWhite);
|
|
|
|
}
|
|
|
|
|
2023-06-18 13:13:16 +00:00
|
|
|
float iconSpace = 64.0f;
|
2023-06-20 07:18:47 +00:00
|
|
|
dc.Flush();
|
2023-06-18 13:13:16 +00:00
|
|
|
|
2023-06-17 20:22:59 +00:00
|
|
|
dc.Begin();
|
|
|
|
dc.FillRect(background, bounds);
|
|
|
|
|
2023-06-21 12:32:10 +00:00
|
|
|
dc.SetFontStyle(dc.theme->uiFont);
|
|
|
|
|
2023-06-19 21:47:23 +00:00
|
|
|
dc.SetFontScale(1.0f, 1.0f);
|
2023-06-22 07:27:58 +00:00
|
|
|
dc.DrawTextRect(achievement.title.c_str(), bounds.Inset(iconSpace + 12.0f, 2.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
|
2023-06-17 20:22:59 +00:00
|
|
|
|
2023-06-19 21:47:23 +00:00
|
|
|
dc.SetFontScale(0.66f, 0.66f);
|
2023-06-22 07:27:58 +00:00
|
|
|
dc.DrawTextRect(achievement.description.c_str(), bounds.Inset(iconSpace + 12.0f, 39.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
|
2023-06-17 20:22:59 +00:00
|
|
|
|
|
|
|
char temp[64];
|
|
|
|
snprintf(temp, sizeof(temp), "%d", achievement.points);
|
|
|
|
|
|
|
|
dc.SetFontScale(1.5f, 1.5f);
|
2023-06-22 07:27:58 +00:00
|
|
|
dc.DrawTextRect(temp, bounds.Expand(-5.0f, -5.0f), fgColor, ALIGN_RIGHT | ALIGN_VCENTER);
|
2023-06-17 20:22:59 +00:00
|
|
|
|
|
|
|
dc.SetFontScale(1.0f, 1.0f);
|
2023-06-18 13:13:16 +00:00
|
|
|
dc.Flush();
|
|
|
|
|
|
|
|
std::string name = Achievements::GetAchievementBadgePath(achievement);
|
|
|
|
if (g_iconCache.BindIconTexture(&dc, name)) {
|
2023-06-22 07:27:58 +00:00
|
|
|
dc.Draw()->DrawTexRect(Bounds(bounds.x + 4.0f, bounds.y + 4.0f, iconSpace, iconSpace), 0.0f, 0.0f, 1.0f, 1.0f, whiteAlpha(alpha));
|
2023-06-18 13:13:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dc.Flush();
|
|
|
|
dc.RebindTexture();
|
2023-06-17 20:22:59 +00:00
|
|
|
}
|
|
|
|
|
2023-06-21 10:17:44 +00:00
|
|
|
void RenderGameAchievementSummary(UIContext &dc, int gameID, const Bounds &bounds, float alpha) {
|
2023-06-19 21:47:23 +00:00
|
|
|
using namespace UI;
|
|
|
|
UI::Drawable background = dc.theme->itemStyle.background;
|
|
|
|
|
2023-06-21 10:17:44 +00:00
|
|
|
background.color = colorAlpha(background.color, alpha);
|
2023-06-22 07:27:58 +00:00
|
|
|
uint32_t fgColor = colorAlpha(dc.theme->itemStyle.fgColor, alpha);
|
2023-06-19 21:47:23 +00:00
|
|
|
|
|
|
|
float iconSpace = 64.0f;
|
2023-06-20 07:18:47 +00:00
|
|
|
dc.Flush();
|
2023-06-19 21:47:23 +00:00
|
|
|
|
|
|
|
dc.Begin();
|
|
|
|
dc.FillRect(background, bounds);
|
|
|
|
|
2023-06-21 12:32:10 +00:00
|
|
|
dc.SetFontStyle(dc.theme->uiFont);
|
|
|
|
|
2023-06-19 21:47:23 +00:00
|
|
|
dc.SetFontScale(1.0f, 1.0f);
|
2023-06-22 07:27:58 +00:00
|
|
|
dc.DrawTextRect(Achievements::GetGameTitle().c_str(), bounds.Inset(iconSpace + 5.0f, 2.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
|
2023-06-19 21:47:23 +00:00
|
|
|
|
|
|
|
std::string description = Achievements::GetGameAchievementSummary();
|
|
|
|
std::string icon = Achievements::GetGameIcon();
|
|
|
|
|
|
|
|
dc.SetFontScale(0.66f, 0.66f);
|
2023-06-22 07:27:58 +00:00
|
|
|
dc.DrawTextRect(description.c_str(), bounds.Inset(iconSpace + 5.0f, 38.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
|
2023-06-19 21:47:23 +00:00
|
|
|
|
|
|
|
dc.SetFontScale(1.0f, 1.0f);
|
|
|
|
dc.Flush();
|
|
|
|
|
|
|
|
std::string name = icon;
|
|
|
|
if (g_iconCache.BindIconTexture(&dc, name)) {
|
2023-06-22 07:27:58 +00:00
|
|
|
dc.Draw()->DrawTexRect(Bounds(bounds.x, bounds.y, iconSpace, iconSpace), 0.0f, 0.0f, 1.0f, 1.0f, whiteAlpha(alpha));
|
2023-06-19 21:47:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dc.Flush();
|
|
|
|
dc.RebindTexture();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-06-17 20:22:59 +00:00
|
|
|
void AchievementView::Draw(UIContext &dc) {
|
2023-06-26 08:13:18 +00:00
|
|
|
RenderAchievement(dc, achievement_, AchievementRenderStyle::LISTED, bounds_, 1.0f, 0.0f, 0.0f);
|
2023-06-17 20:22:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void AchievementView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
|
2023-06-21 10:17:44 +00:00
|
|
|
MeasureAchievement(dc, achievement_, &w, &h);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AchievementView::Click() {
|
|
|
|
// In debug builds, clicking achievements will show them being unlocked (which may be a lie).
|
|
|
|
#ifdef _DEBUG
|
|
|
|
g_OSD.ShowAchievementUnlocked(achievement_.id);
|
|
|
|
#endif
|
2023-06-17 20:22:59 +00:00
|
|
|
}
|
2023-06-19 21:47:23 +00:00
|
|
|
|
|
|
|
void GameAchievementSummaryView::Draw(UIContext &dc) {
|
2023-06-26 08:13:18 +00:00
|
|
|
RenderGameAchievementSummary(dc, gameID_, bounds_, 1.0f);
|
2023-06-19 21:47:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void GameAchievementSummaryView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
|
2023-06-21 10:17:44 +00:00
|
|
|
MeasureGameAchievementSummary(dc, gameID_, &w, &h);
|
2023-06-19 21:47:23 +00:00
|
|
|
}
|