Files
ppsspp/Common/UI/PopupScreens.h
T
Henrik RydgårdandClaude Opus 5.5 b91bd6af86 Settings: Build the settings screen from pages described as data
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>
2026-10-25 15:26:58 +01:00

624 lines
19 KiB
C++

#pragma once
#include "Common/System/Request.h"
#include "Common/Data/Text/I18n.h"
#include "Common/UI/UIScreen.h"
#include "Common/UI/UI.h"
#include "Common/UI/View.h"
#include "Common/UI/ScrollView.h"
#include "Common/UI/Notice.h"
// from StringUtils
enum class StringRestriction;
enum class OSDType; // From OSD
namespace UI {
constexpr float NO_DEFAULT_FLOAT = -1000000.0f;
constexpr int NO_DEFAULT_INT = -1000000;
class PopupScreen : public UIDialogScreen {
public:
PopupScreen(std::string_view title, std::string_view button1 = "", std::string_view button2 = "");
virtual void CreatePopupContents(UI::ViewGroup *parent) = 0;
void CreateViews() override;
bool isTransparent() const override { return true; }
bool touch(const TouchInput &touch) override;
bool key(const KeyInput &key) override;
void TriggerFinish(DialogResult result) override;
void SetPopupOrigin(const UI::View *view);
void SetPopupOffset(float y) { offsetY_ = y; }
void SetAlignTop(bool alignTop) { alignTop_ = alignTop; }
void SetHasDropShadow(bool has) { hasDropShadow_ = has; }
// For the postproc param sliders on DisplayLayoutScreen
bool wantBrightBackground() const override { return !hasDropShadow_; }
void SetNotification(NoticeLevel noticeLevel, std::string_view str) {
notificationLevel_ = noticeLevel;
notificationString_ = str;
}
virtual UI::Margins RootMargins() const override { return UI::Margins(12, 24); }
protected:
virtual bool FillVertical() const { return false; }
virtual UI::Size PopupWidth() const { return 550; }
virtual bool ShowButtons() const { return true; }
virtual bool CanComplete(DialogResult result) { return true; }
virtual void OnCompleted(DialogResult result) {}
std::string_view Title() { return title_; }
void update() override;
private:
UI::LinearLayout *box_ = nullptr;
UI::Choice *defaultButton_ = nullptr;
ImageID button1Image_;
ImageID button2Image_;
std::string title_;
std::string button1_;
std::string button2_;
enum {
FRAMES_LEAD_IN = 6,
FRAMES_LEAD_OUT = 4,
};
int frames_ = 0;
int finishFrame_ = -1;
DialogResult finishResult_ = DR_CANCEL;
bool hasPopupOrigin_ = false;
Point2D popupOrigin_;
float offsetY_ = 0.0f;
bool alignTop_ = false;
bool hasDropShadow_ = true;
NoticeLevel notificationLevel_{};
std::string notificationString_;
};
class ListPopupScreen : public PopupScreen {
public:
ListPopupScreen(std::string_view title) : PopupScreen(title) {}
ListPopupScreen(std::string_view title, const std::vector<std::string> &items, int selected, std::function<void(int)> callback, bool showButtons = false)
: PopupScreen(title, "OK", "Cancel"), adaptor_(items, selected), callback_(callback), showButtons_(showButtons) {
}
ListPopupScreen(std::string_view title, const std::vector<std::string> &items, int selected, bool showButtons = false)
: PopupScreen(title, "OK", "Cancel"), adaptor_(items, selected), showButtons_(showButtons) {
}
~ListPopupScreen() override;
int GetChoice() const {
return listView_->GetSelected();
}
std::string GetChoiceString() const {
return adaptor_.GetTitle(listView_->GetSelected());
}
void SetHiddenChoices(const std::set<int> &hidden) {
hidden_ = hidden;
}
void SetChoiceIcons(const std::map<int, ImageID> &icons) {
icons_ = icons;
}
void SetDefault(int defaultChoice) {
adaptor_.SetDefault(defaultChoice);
}
const char *tag() const override { return "listpopup"; }
UI::Event OnChoice;
protected:
bool FillVertical() const override { return false; }
bool ShowButtons() const override { return showButtons_; }
void CreatePopupContents(UI::ViewGroup *parent) override;
UI::StringVectorListAdaptor adaptor_;
UI::ListView *listView_ = nullptr;
private:
void OnListChoice(UI::EventParams &e);
std::function<void(int)> callback_;
bool showButtons_ = false;
std::set<int> hidden_;
std::map<int, ImageID> icons_;
};
class MessagePopupScreen : public PopupScreen {
public:
MessagePopupScreen(std::string_view title, std::string_view message, std::string_view button1, std::string_view button2, std::function<void(bool)> callback = nullptr)
: PopupScreen(title, button1, button2), message_(message), callback_(callback) {}
const char *tag() const override { return "MessagePopupScreen"; }
UI::Event OnChoice;
protected:
bool FillVertical() const override { return false; }
bool ShowButtons() const override { return true; }
void CreatePopupContents(UI::ViewGroup *parent) override;
private:
void OnCompleted(DialogResult result) override;
std::string message_;
std::function<void(bool)> callback_;
};
class SliderPopupScreen : public PopupScreen {
public:
SliderPopupScreen(int *value, int minValue, int maxValue, int defaultValue, std::string_view title, int step, std::string_view units, bool liveUpdate)
: PopupScreen(title, T(I18NCat::DIALOG, "OK"), T(I18NCat::DIALOG, "Cancel")), units_(units), value_(value), minValue_(minValue), maxValue_(maxValue), defaultValue_(defaultValue), step_(step), liveUpdate_(liveUpdate) {}
void CreatePopupContents(ViewGroup *parent) override;
void SetNegativeDisable(const std::string &str) {
negativeLabel_ = str;
disabled_ = *value_ < 0;
}
void RestrictChoices(const int *fixedChoices, size_t numFixedChoices) {
fixedChoices_ = fixedChoices;
numFixedChoices_ = numFixedChoices;
}
const char *tag() const override { return "SliderPopup"; }
Event OnChange;
private:
void OnDecrease(EventParams &params);
void OnIncrease(EventParams &params);
void OnTextChange(EventParams &params);
void OnSliderChange(EventParams &params);
void OnCompleted(DialogResult result) override;
void UpdateTextBox();
Slider *slider_ = nullptr;
UI::TextEdit *edit_ = nullptr;
std::string units_;
std::string negativeLabel_;
int *value_;
int sliderValue_ = 0;
int minValue_;
int maxValue_;
int defaultValue_;
int step_;
bool liveUpdate_;
bool changing_ = false;
bool disabled_ = false;
const int *fixedChoices_ = nullptr;
size_t numFixedChoices_ = 0;
};
class SliderFloatPopupScreen : public PopupScreen {
public:
SliderFloatPopupScreen(float *value, float minValue, float maxValue, float defaultValue, std::string_view title, float step = 1.0f, std::string_view units = "", bool liveUpdate = false)
: PopupScreen(title, T(I18NCat::DIALOG, "OK"), T(I18NCat::DIALOG, "Cancel")), units_(units), value_(value), originalValue_(*value), minValue_(minValue), maxValue_(maxValue), defaultValue_(defaultValue), step_(step), liveUpdate_(liveUpdate) {}
void CreatePopupContents(UI::ViewGroup *parent) override;
const char *tag() const override { return "SliderFloatPopup"; }
Event OnChange;
private:
void OnIncrease(EventParams &params);
void OnDecrease(EventParams &params);
void OnTextChange(EventParams &params);
void OnSliderChange(EventParams &params);
void OnCompleted(DialogResult result) override;
void UpdateTextBox();
UI::SliderFloat *slider_ = nullptr;
UI::TextEdit *edit_ = nullptr;
std::string units_;
float sliderValue_ = 0.0f;
float originalValue_ = 0.0f;
float *value_;
float minValue_;
float maxValue_;
float defaultValue_;
float step_;
bool changing_ = false;
bool liveUpdate_;
};
enum class SoftKeyboardState {
Upper = 0,
Lower,
Symbols,
MAX,
};
ViewGroup *CreateSoftKeyboard(TextEdit *edit, SoftKeyboardState *state);
class TextEditPopupScreen : public PopupScreen {
public:
TextEditPopupScreen(std::string *value, std::string_view placeholder, std::string_view title, int maxLen);
void CreatePopupContents(ViewGroup *parent) override;
const char *tag() const override { return "TextEditPopup"; }
void SetPasswordMasking(bool masking) {
passwordMasking_ = masking;
}
Event OnChange;
private:
virtual UI::Size PopupWidth() const override { return 600; }
void OnCompleted(DialogResult result) override;
TextEdit *edit_ = nullptr;
ViewGroup *keyboard_ = nullptr;
Choice *showKeyboardChoice_ = nullptr;
std::string *value_;
std::string textEditValue_;
std::string placeholder_;
int maxLen_;
SoftKeyboardState kbState_{};
bool passwordMasking_ = false;
};
struct ContextMenuItem {
const char *text;
const char *imageID;
};
class AbstractContextMenuScreen : public PopupScreen {
public:
AbstractContextMenuScreen(UI::View *sourceView) : PopupScreen("", "", ""), sourceView_(sourceView) {}
protected:
UI::Size PopupWidth() const override {
return 350;
}
UI::View *sourceView_;
void AlignPopup(UI::View *parent);
};
// Once a selection has been made,
class PopupContextMenuScreen : public AbstractContextMenuScreen {
public:
PopupContextMenuScreen(const ContextMenuItem *items, size_t itemCount, I18NCat category, UI::View *sourceView);
const char *tag() const override { return "ContextMenuPopup"; }
void SetEnabled(size_t index, bool enabled) {
enabled_[index] = enabled;
}
UI::Event OnChoice;
private:
void CreatePopupContents(ViewGroup *parent) override;
const ContextMenuItem *items_;
size_t itemCount_;
I18NCat category_;
std::vector<bool> enabled_;
};
class PopupCallbackScreen : public AbstractContextMenuScreen {
public:
PopupCallbackScreen(std::function<void(UI::ViewGroup *)> createViews, UI::View *sourceView);
const char *tag() const override { return "ContextMenuCallbackPopup"; }
private:
void CreatePopupContents(ViewGroup *parent) override;
std::function<void(UI::ViewGroup *)> createViews_;
};
// Reads and writes value to determine the current selection.
class PopupMultiChoice : public AbstractChoiceWithValueDisplay {
public:
PopupMultiChoice(int *value, std::string_view text, const char **choices, int minVal, int numChoices,
I18NCat category, ScreenManager *screenManager, UI::LayoutParams *layoutParams = nullptr);
void Update() override;
void HideChoice(int c) {
hidden_.insert(c);
}
int NumChoices() const { return numChoices_; }
bool IsChoiceHidden(int c) const {
return hidden_.find(c) != hidden_.end();
}
// Marks a choice (index into the choices array) as not needing translation, like a plain number.
// Without this, every lookup of such a choice gets logged as a missing translation.
void SetChoiceUntranslated(int c) {
untranslated_.insert(c);
}
void SetChoicesUntranslated(int first, int last) {
for (int i = first; i <= last; i++) {
untranslated_.insert(i);
}
}
void SetPreOpenCallback(std::function<void(PopupMultiChoice *)> callback) {
preOpenCallback_ = callback;
}
void SetChoiceIcon(int c, ImageID id) {
icons_[c] = id;
}
void SetChoiceIcons(std::map<int, ImageID> icons) {
icons_ = icons;
}
void SetDefault(int defaultChoice) {
default_ = defaultChoice;
}
UI::Event OnChoice;
protected:
std::string ValueText(bool *shadow) const override;
ImageID ValueImage() const override {
auto iter = icons_.find(*value_);
if (iter != icons_.end()) {
return iter->second;
}
return ImageID::invalid();
}
int *value_;
const char **choices_;
int minVal_;
int numChoices_;
void UpdateText();
private:
void HandleClick(UI::EventParams &e);
std::string_view TranslateChoice(int index) const;
void ChoiceCallback(int num);
virtual bool PostChoiceCallback(int num) { return true; }
I18NCat category_;
ScreenManager *screenManager_;
std::string valueText_;
bool restoreFocus_ = false;
std::set<int> hidden_;
std::set<int> untranslated_;
std::map<int, ImageID> icons_;
std::function<void(PopupMultiChoice *)> preOpenCallback_;
bool callbackExecuted_ = false;
int default_ = -99;
};
// Allows passing in a dynamic vector of strings. Saves the string.
class PopupMultiChoiceDynamic : public PopupMultiChoice {
public:
// TODO: This all is absolutely terrible, just done this way to be conformant with the internals of PopupMultiChoice.
PopupMultiChoiceDynamic(std::string *value, std::string_view text, const std::vector<std::string> &choices,
I18NCat category, ScreenManager *screenManager, std::vector<std::string> *values = nullptr, UI::LayoutParams *layoutParams = nullptr)
: UI::PopupMultiChoice(&valueInt_, text, nullptr, 0, (int)choices.size(), category, screenManager, layoutParams), valueStr_(value) {
if (values) {
_dbg_assert_(choices.size() == values->size());
}
choices_ = new const char *[numChoices_];
valueInt_ = 0;
for (int i = 0; i < numChoices_; i++) {
choices_[i] = new char[choices[i].size() + 1];
memcpy((char *)choices_[i], choices[i].c_str(), choices[i].size() + 1);
if (values) {
if (*value == (*values)[i])
valueInt_ = i;
}
if (*value == choices_[i])
valueInt_ = i;
}
value_ = &valueInt_;
if (values) {
choiceValues_ = *values;
}
UpdateText();
}
~PopupMultiChoiceDynamic() {
for (int i = 0; i < numChoices_; i++) {
delete[] choices_[i];
}
delete[] choices_;
}
protected:
bool PostChoiceCallback(int num) override {
if (!valueStr_) {
return true;
}
const char *value = choices_[num];
if (choiceValues_.size() == numChoices_) {
value = choiceValues_[num].c_str();
}
if (*valueStr_ != value) {
*valueStr_ = value;
return true;
} else {
return false;
}
}
private:
int valueInt_;
std::string *valueStr_;
std::vector<std::string> choiceValues_;
};
class PopupSliderChoice : public AbstractChoiceWithValueDisplay {
public:
PopupSliderChoice(int *value, int minValue, int maxValue, int defaultValue, std::string_view text, ScreenManager *screenManager, std::string_view units = "", LayoutParams *layoutParams = 0);
PopupSliderChoice(int *value, int minValue, int maxValue, int defaultValue, std::string_view text, int step, ScreenManager *screenManager, std::string_view units = "", LayoutParams *layoutParams = 0);
void SetFormat(std::string_view fmt);
void SetZeroLabel(std::string_view str) {
zeroLabel_ = str;
}
void SetLiveUpdate(bool update) {
liveUpdate_ = update;
}
void SetNegativeDisable(std::string_view str) {
negativeLabel_ = str;
}
void RestrictChoices(const int *fixedChoices, size_t numFixedChoices) {
fixedChoices_ = fixedChoices;
numFixedChoices_ = numFixedChoices;
}
Event OnChange;
protected:
std::string ValueText(bool *shadow) const override;
private:
void HandleClick(EventParams &e);
void HandleChange(EventParams &e);
int *value_;
int minValue_;
int maxValue_;
int defaultValue_;
int step_;
std::string fmt_;
std::string zeroLabel_;
std::string negativeLabel_;
std::string units_;
ScreenManager *screenManager_;
bool restoreFocus_ = false;
bool liveUpdate_ = false;
const int *fixedChoices_ = nullptr;
size_t numFixedChoices_ = 0;
};
class PopupSliderChoiceFloat : public AbstractChoiceWithValueDisplay {
public:
PopupSliderChoiceFloat(float *value, float minValue, float maxValue, float defaultValue, std::string_view text, ScreenManager *screenManager, std::string_view units = "", LayoutParams *layoutParams = 0);
PopupSliderChoiceFloat(float *value, float minValue, float maxValue, float defaultValue, std::string_view text, float step, ScreenManager *screenManager, std::string_view units = "", LayoutParams *layoutParams = 0);
void SetFormat(std::string_view fmt);
void SetZeroLabel(const std::string &str) {
zeroLabel_ = str;
}
void SetLiveUpdate(bool update) {
liveUpdate_ = update;
}
void SetHasDropShadow(bool has) {
hasDropShadow_ = has;
}
Event OnChange;
protected:
std::string ValueText(bool *shadow) const override;
private:
void HandleClick(EventParams &e);
void HandleChange(EventParams &e);
float *value_;
float minValue_;
float maxValue_;
float defaultValue_;
float step_;
std::string fmt_;
std::string zeroLabel_;
std::string units_;
ScreenManager *screenManager_;
bool restoreFocus_ = false;
bool liveUpdate_ = false;
bool hasDropShadow_ = true;
};
// NOTE: This one will defer to a system-native dialog if possible.
class PopupTextInputChoice : public AbstractChoiceWithValueDisplay {
public:
PopupTextInputChoice(RequesterToken token, std::string *value, std::string_view title, std::string_view placeholder, int maxLen, ScreenManager *screenManager, LayoutParams *layoutParams = 0);
Event OnChange;
void SetRestriction(StringRestriction restriction, int minLength) {
restriction_ = restriction;
minLen_ = minLength;
}
void SetShadowText(std::string_view text) {
shadowText_ = text;
}
protected:
std::string ValueText(bool *shadow) const override;
private:
void HandleClick(EventParams &e);
RequesterToken token_;
ScreenManager *screenManager_;
std::string *value_;
std::string placeHolder_;
std::string defaultText_;
std::string shadowText_;
int maxLen_;
int minLen_ = 0;
bool restoreFocus_ = false;
StringRestriction restriction_;
};
void AskForInput(ScreenManager *screenManager, RequesterToken token, UI::View *sourceView, std::string_view title, std::function<void(const std::string &, bool)> callback);
class ChoiceWithValueDisplay : public AbstractChoiceWithValueDisplay {
public:
ChoiceWithValueDisplay(int *value, std::string_view text, LayoutParams *layoutParams = 0)
: AbstractChoiceWithValueDisplay(text, layoutParams), iValue_(value) {}
ChoiceWithValueDisplay(int *value, ImageID imageId, LayoutParams *layoutParams = 0)
: AbstractChoiceWithValueDisplay("", imageId, layoutParams), iValue_(value) {}
ChoiceWithValueDisplay(std::string *value, std::string_view text, I18NCat category, LayoutParams *layoutParams = 0)
: AbstractChoiceWithValueDisplay(text, layoutParams), sValue_(value), category_(category) {}
ChoiceWithValueDisplay(std::string *value, std::string_view text, std::string(*translateCallback)(std::string_view value), LayoutParams *layoutParams = 0)
: AbstractChoiceWithValueDisplay(text, layoutParams), sValue_(value), translateCallback_(translateCallback) {}
private:
std::string ValueText(bool *shadow) const override;
std::string *sValue_ = nullptr;
int *iValue_ = nullptr;
I18NCat category_ = I18NCat::CATEGORY_COUNT;
std::string(*translateCallback_)(std::string_view value) = nullptr;
};
class ChoiceWithFixedValueDisplay : public AbstractChoiceWithValueDisplay {
public:
ChoiceWithFixedValueDisplay(std::string_view value, std::string_view text, LayoutParams *layoutParams = 0)
: AbstractChoiceWithValueDisplay(text, layoutParams), sValue_(value) {}
private:
std::string ValueText(bool *shadow) const override {
*shadow = false;
return sValue_;
}
std::string sValue_;
};
enum class FileChooserFileType {
WAVE_FILE,
};
class FileChooserChoice : public AbstractChoiceWithValueDisplay {
public:
FileChooserChoice(RequesterToken token, std::string *value, std::string_view title, BrowseFileType fileType, LayoutParams *layoutParams = nullptr);
std::string ValueText(bool *shadow) const override;
Event OnChange;
private:
std::string *value_;
};
class FolderChooserChoice : public AbstractChoiceWithValueDisplay {
public:
FolderChooserChoice(RequesterToken token, std::string *value, std::string_view title, LayoutParams *layoutParams = nullptr);
std::string ValueText(bool *shadow) const override;
Event OnChange;
private:
std::string *value_;
RequesterToken token_;
};
} // namespace UI