mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-01 03:57:17 +01:00
Each tab is now a SettingsPage: a list of items (headers, settings, buttons, hints, links to other pages) that a function builds and views are created from. Settings are added with page->Setting(&g_Config.x), which takes the label, the kind of view, the range or choices, units, labels for special values and the default from the config. The pages keep what depends on the UI (which settings to show and when, enable rules, and behavior that needs the screen), and reach the screen through the page's SettingsPageHost. A page can link to other pages, which open on top of it in the same tab, under a bar showing where you are, like Tools > Developer tools > Graphics, where the earlier pages can be clicked to go back to them. The bar has back, search and help buttons, and the back key also goes back a page (or out of a search) before leaving the screen. Each page remembers its scroll position. In portrait there's no tab strip: a page links to the tabs, and the bar shows just the current page's title. Rotating keeps showing the same page. Moving between pages only focuses an item when navigating with a keyboard or controller. The developer tools are now pages linked from the Tools tab, instead of a screen of their own. The in-game developer menu and --start-screen=developertools open the settings on that page. The toggles of live state (the remote debugger and the MIPS tracer) show and change it through functions, and pretending to be in-game for the notification tests is a flag on the OSD. The memory map and memstick tests, only reachable from commented-out code, are removed. Search lists the matching settings from all tabs and the pages they link to, grouped by where they are. Each result goes to the setting on its page, scrolled to and highlighted (outlined, when picked with a mouse or touch), or for a page, opens it. This replaces the search tab and the old filter that hid the views that didn't match, and with them ShowSearchControls. Changing tabs leaves the search. The pages, the bar and search are in SettingsPagesDialogScreen, between the tabbed dialog screen and the settings screen, so the other tabbed screens keep plain tabs. Other visible changes: - Reset buttons that went to the wrong value now go to the real default: the touch control auto-hide delay (was 20 seconds instead of 8), the mouse wheel delay (1 ms, below its own range, instead of 80) and the UI size (0 instead of -1 on desktop). The alternative speeds get a reset button. - Units are shown on more sliders (frames, MHz, seconds, Hz), and popups no longer spell out what 0 means in the units, since the zero label does. Units of up to three characters (%, x, ms, MHz) aren't translated. - The Android HW scaler hides the scales the display is too small for, instead of cutting the list short. - Choices are translated in the setting's own section. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
364 lines
9.5 KiB
C++
364 lines
9.5 KiB
C++
#include <cstring>
|
|
#include <algorithm>
|
|
// for std::min
|
|
|
|
#include "Common/System/OSD.h"
|
|
#include "Common/TimeUtil.h"
|
|
#include "Common/Log.h"
|
|
#include "Common/Math/math_util.h"
|
|
|
|
OnScreenDisplay g_OSD;
|
|
|
|
// Effectively forever.
|
|
constexpr double forever_s = 10000000000.0;
|
|
|
|
void OnScreenDisplay::Update() {
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
|
|
double now = time_now_d();
|
|
for (auto iter = entries_.begin(); iter != entries_.end(); ) {
|
|
if (now >= iter->endTime) {
|
|
iter = entries_.erase(iter);
|
|
} else {
|
|
iter++;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<OnScreenDisplay::Entry> OnScreenDisplay::Entries() {
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
return entries_; // makes a copy.
|
|
}
|
|
|
|
void OnScreenDisplay::NudgeIngameNotifications() {
|
|
sideBarShowTime_ = time_now_d();
|
|
}
|
|
|
|
float OnScreenDisplay::IngameAlpha() const {
|
|
if (pretendIngame_) {
|
|
return 1.0f;
|
|
}
|
|
double timeSinceNudge = time_now_d() - sideBarShowTime_;
|
|
|
|
// Fade out in 1/4 second, 0.1s after the last nudge.
|
|
return saturatef(1.0f - ((float)timeSinceNudge - 0.1f) * 4.0f);
|
|
}
|
|
|
|
void OnScreenDisplay::ClickEntry(size_t index, double now) {
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
if (index < entries_.size() && entries_[index].type != OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR) {
|
|
if (entries_[index].clickCallback) {
|
|
entries_[index].clickCallback();
|
|
} else {
|
|
entries_[index].endTime = std::min(now + FadeoutTime(), entries_[index].endTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnScreenDisplay::Show(OSDType type, std::string_view text, std::string_view text2, std::string_view icon, float duration_s, const char *id) {
|
|
if (text.empty() && type != OSDType::STATUS_ICON) {
|
|
// The user hacked the translation files to get rid of the message. Let's reward the dedication
|
|
// by skipping it entirely.
|
|
return;
|
|
}
|
|
|
|
INFO_LOG(Log::UI, "OSD: %.*s (%.*s)", STR_VIEW(text), STR_VIEW(text2));
|
|
|
|
// Automatic duration based on type.
|
|
if (duration_s <= 0.0f) {
|
|
switch (type) {
|
|
case OSDType::MESSAGE_ERROR:
|
|
case OSDType::MESSAGE_WARNING:
|
|
duration_s = 4.0f;
|
|
break;
|
|
case OSDType::MESSAGE_FILE_LINK:
|
|
duration_s = 5.0f;
|
|
break;
|
|
case OSDType::MESSAGE_INFO:
|
|
duration_s = 3.0f;
|
|
break;
|
|
case OSDType::MESSAGE_SUCCESS:
|
|
duration_s = 2.0f;
|
|
break;
|
|
default:
|
|
duration_s = 1.5f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
double now = time_now_d();
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
if (id) {
|
|
for (auto iter = entries_.begin(); iter != entries_.end(); ++iter) {
|
|
if (iter->id == id) {
|
|
Entry msg = *iter;
|
|
msg.endTime = now + duration_s;
|
|
msg.text = text;
|
|
msg.text2 = text2;
|
|
msg.type = type;
|
|
msg.iconName = icon;
|
|
// Move to top (should we? maybe not?)
|
|
entries_.erase(iter);
|
|
entries_.insert(entries_.begin(), msg);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
Entry msg{};
|
|
msg.text = text;
|
|
msg.text2 = text2;
|
|
msg.iconName = icon;
|
|
msg.startTime = now;
|
|
msg.endTime = now + duration_s;
|
|
msg.type = type;
|
|
if (id) {
|
|
msg.id = id;
|
|
}
|
|
entries_.insert(entries_.begin(), msg);
|
|
}
|
|
|
|
void OnScreenDisplay::CancelById(std::string_view id) {
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
for (auto iter = entries_.begin(); iter != entries_.end();) {
|
|
if (iter->id == id) {
|
|
iter = entries_.erase(iter);
|
|
} else {
|
|
iter++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnScreenDisplay::ShowOnOff(std::string_view message, bool on, float duration_s) {
|
|
std::string msg(message);
|
|
msg += ": ";
|
|
msg += on ? "on" : "off";
|
|
// TODO: translate "on" and "off"? Or just get rid of this whole thing?
|
|
Show(OSDType::MESSAGE_INFO, msg, duration_s);
|
|
}
|
|
|
|
void OnScreenDisplay::ShowAchievementUnlocked(int achievementID) {
|
|
double now = time_now_d();
|
|
|
|
double duration_s = 5.0;
|
|
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
Entry msg{};
|
|
msg.numericID = achievementID;
|
|
msg.type = OSDType::ACHIEVEMENT_UNLOCKED;
|
|
msg.startTime = now;
|
|
msg.endTime = now + duration_s;
|
|
entries_.insert(entries_.begin(), msg);
|
|
}
|
|
|
|
void OnScreenDisplay::ShowAchievementProgress(int achievementID, bool show) {
|
|
double now = time_now_d();
|
|
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
// There can only be one of these at a time.
|
|
for (auto &entry : entries_) {
|
|
if (entry.type == OSDType::ACHIEVEMENT_PROGRESS) {
|
|
if (!show) {
|
|
// Hide and eventually delete it.
|
|
entry.endTime = now + (double)FadeoutTime();
|
|
// Found it, we're done.
|
|
return;
|
|
}
|
|
// Else update it.
|
|
entry.numericID = achievementID;
|
|
entry.endTime = now + forever_s;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!show) {
|
|
// Sanity check
|
|
return;
|
|
}
|
|
|
|
// OK, let's make a new side-entry.
|
|
Entry entry{};
|
|
entry.numericID = achievementID;
|
|
entry.type = OSDType::ACHIEVEMENT_PROGRESS;
|
|
entry.startTime = now;
|
|
entry.endTime = now + forever_s;
|
|
entries_.insert(entries_.begin(), entry);
|
|
}
|
|
|
|
void OnScreenDisplay::ShowChallengeIndicator(int achievementID, bool show) {
|
|
double now = time_now_d();
|
|
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
for (auto &entry : entries_) {
|
|
if (entry.numericID == achievementID && entry.type == OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR && !show) {
|
|
// Hide and eventually delete it.
|
|
entry.endTime = now + (double)FadeoutTime();
|
|
// Found it, we're done.
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!show) {
|
|
// Sanity check
|
|
return;
|
|
}
|
|
|
|
// OK, let's make a new side-entry.
|
|
Entry entry{};
|
|
entry.numericID = achievementID;
|
|
entry.type = OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR;
|
|
entry.startTime = now;
|
|
entry.endTime = now + forever_s;
|
|
entries_.insert(entries_.begin(), entry);
|
|
}
|
|
|
|
void OnScreenDisplay::ShowLeaderboardTracker(int leaderboardTrackerID, std::string_view trackerText, bool show) { // show=true is used both for create and update.
|
|
double now = time_now_d();
|
|
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
for (auto &entry : entries_) {
|
|
if (entry.numericID == leaderboardTrackerID && entry.type == OSDType::LEADERBOARD_TRACKER) {
|
|
if (show) {
|
|
// Just an update.
|
|
entry.text = trackerText;
|
|
// Bump the end-time, in case it was fading out.
|
|
entry.endTime = now + forever_s;
|
|
} else {
|
|
// Keep the current text, hide and eventually delete it.
|
|
entry.endTime = now + (double)FadeoutTime();
|
|
}
|
|
// Found it, we're done.
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!show) {
|
|
// Sanity check
|
|
return;
|
|
}
|
|
|
|
// OK, let's make a new side-entry.
|
|
Entry entry{};
|
|
entry.numericID = leaderboardTrackerID;
|
|
entry.type = OSDType::LEADERBOARD_TRACKER;
|
|
entry.startTime = now;
|
|
entry.endTime = now + forever_s;
|
|
entry.text = trackerText;
|
|
entries_.insert(entries_.begin(), entry);
|
|
}
|
|
|
|
void OnScreenDisplay::ShowLeaderboardStartEnd(std::string_view title, std::string_view description, bool started) {
|
|
g_OSD.Show(OSDType::LEADERBOARD_STARTED_FAILED, title, description, 3.0f);
|
|
}
|
|
|
|
void OnScreenDisplay::ShowLeaderboardSubmitted(std::string_view title, std::string_view value) {
|
|
g_OSD.Show(OSDType::LEADERBOARD_SUBMITTED, title, value, 3.0f);
|
|
}
|
|
|
|
void OnScreenDisplay::SetProgressBar(std::string_view id, std::string_view message, float minValue, float maxValue, float progress, float delay) {
|
|
_dbg_assert_(!my_isnanorinf(progress));
|
|
_dbg_assert_(!my_isnanorinf(minValue));
|
|
_dbg_assert_(!my_isnanorinf(maxValue));
|
|
|
|
double now = time_now_d();
|
|
bool found = false;
|
|
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
for (auto &bar : entries_) {
|
|
if (bar.type == OSDType::PROGRESS_BAR && bar.id == id) {
|
|
bar.minValue = minValue;
|
|
bar.maxValue = maxValue;
|
|
bar.progress = progress;
|
|
bar.text = message;
|
|
bar.endTime = now + 60.0; // Nudge the progress bar to keep it shown.
|
|
return;
|
|
}
|
|
}
|
|
|
|
Entry bar{};
|
|
bar.id = id;
|
|
bar.type = OSDType::PROGRESS_BAR;
|
|
bar.text = message;
|
|
bar.minValue = minValue;
|
|
bar.maxValue = maxValue;
|
|
bar.progress = progress;
|
|
bar.startTime = now + delay;
|
|
bar.endTime = now + 60.0; // Show the progress bar for 60 seconds, then fade it out.
|
|
entries_.push_back(bar);
|
|
}
|
|
|
|
void OnScreenDisplay::RemoveProgressBar(std::string_view id, bool success, float delay_s) {
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
for (auto &ent : entries_) {
|
|
if (ent.type == OSDType::PROGRESS_BAR && ent.id == id) {
|
|
if (success) {
|
|
// Quickly shoot up to max, if we weren't there.
|
|
if (ent.maxValue != 0.0f) {
|
|
ent.progress = ent.maxValue;
|
|
} else {
|
|
// Fake a full progress
|
|
ent.minValue = 0;
|
|
ent.maxValue = 1;
|
|
ent.progress = 1;
|
|
}
|
|
}
|
|
ent.endTime = time_now_d() + delay_s + FadeoutTime();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fades out everything related to achievements. Should be used on game shutdown.
|
|
void OnScreenDisplay::ClearAchievementStuff() {
|
|
double now = time_now_d();
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
for (auto &iter : entries_) {
|
|
switch (iter.type) {
|
|
case OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR:
|
|
case OSDType::ACHIEVEMENT_UNLOCKED:
|
|
case OSDType::ACHIEVEMENT_PROGRESS:
|
|
case OSDType::LEADERBOARD_TRACKER:
|
|
case OSDType::LEADERBOARD_STARTED_FAILED:
|
|
case OSDType::LEADERBOARD_SUBMITTED:
|
|
iter.endTime = now;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnScreenDisplay::SetClickCallback(std::string_view id, std::function<void()> callback) {
|
|
_dbg_assert_(callback != nullptr);
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
for (auto &ent : entries_) {
|
|
// protect against dupes.
|
|
if (ent.id == id) {
|
|
ent.clickCallback = callback;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnScreenDisplay::SetFlags(std::string_view id, OSDMessageFlags flags) {
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
for (auto &ent : entries_) {
|
|
// protect against dupes.
|
|
if (ent.id == id) {
|
|
ent.flags = flags;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnScreenDisplay::ClearNonAchievementEntries() {
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
for (auto iter = entries_.begin(); iter != entries_.end();) {
|
|
if (iter->type != OSDType::ACHIEVEMENT_UNLOCKED &&
|
|
iter->type != OSDType::ACHIEVEMENT_PROGRESS &&
|
|
iter->type != OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR) {
|
|
iter = entries_.erase(iter);
|
|
} else {
|
|
iter++;
|
|
}
|
|
}
|
|
}
|