Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Add convenient UI on the pause screen for changing/locking the screen…
… orientation
  • Loading branch information
hrydgard committed Nov 26, 2025
commit ed25b5e99159cbaa69e771ecb8ca1966a48ce87b
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ imgui.ini

# debug file
ui_atlas_gen.png
buttons_rasterized.png

# For vim
*.swp
Expand Down
24 changes: 22 additions & 2 deletions Common/UI/PopupScreens.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,20 @@ std::string ChopTitle(const std::string &title) {
return title;
}

PopupMultiChoice::PopupMultiChoice(int *value, std::string_view text, const char **choices, int minVal, int numChoices,
I18NCat category, ScreenManager *screenManager, UI::LayoutParams *layoutParams)
: AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), choices_(choices), minVal_(minVal), numChoices_(numChoices), category_(category), screenManager_(screenManager) {
if (choices) {
// If choices is nullptr, we're being called from PopupMultiChoiceDynamic where value doesn't yet point to anything valid.
if (*value >= numChoices + minVal)
*value = numChoices + minVal - 1;
if (*value < minVal)
*value = minVal;
UpdateText();
}
OnClick.Handle(this, &PopupMultiChoice::HandleClick);
}

void PopupMultiChoice::HandleClick(UI::EventParams &e) {
if (!callbackExecuted_ && preOpenCallback_) {
preOpenCallback_(this);
Expand All @@ -324,8 +338,7 @@ void PopupMultiChoice::HandleClick(UI::EventParams &e) {
choices.push_back(category ? std::string(category->T(choices_[i])) : std::string(choices_[i]));
}

ListPopupScreen *popupScreen = new ListPopupScreen(ChopTitle(text_), choices, *value_ - minVal_,
std::bind(&PopupMultiChoice::ChoiceCallback, this, std::placeholders::_1));
ListPopupScreen *popupScreen = new ListPopupScreen(ChopTitle(text_), choices, *value_ - minVal_, [this](int num) {ChoiceCallback(num);});
popupScreen->SetHiddenChoices(hidden_);
popupScreen->SetChoiceIcons(icons_);
if (e.v)
Expand Down Expand Up @@ -870,6 +883,13 @@ void AbstractChoiceWithValueDisplay::Draw(UIContext &dc) {
dc.SetFontScale(1.0f, 1.0f);
} else {
Choice::Draw(dc);

if (text_.empty() && !image_.isValid()) {
// In this case we only display the image of the choice. Useful for small buttons spawning a popup.
dc.Draw()->DrawImageRotated(ValueImage(), bounds_.centerX(), bounds_.centerY(), imgScale_, imgRot_, style.fgColor, imgFlipH_);
return;
}

float scale = CalculateValueScale(dc, valueText, bounds_.w);
dc.SetFontScale(scale, scale);
dc.DrawTextRect(valueText, bounds_.Expand(-paddingX, 0.0f), style.fgColor, ALIGN_LEFT | ALIGN_VCENTER | FLAG_WRAP_TEXT);
Expand Down
33 changes: 15 additions & 18 deletions Common/UI/PopupScreens.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,40 +282,38 @@ class PopupCallbackScreen : public AbstractContextMenuScreen {
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)
: AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), choices_(choices), minVal_(minVal), numChoices_(numChoices),
category_(category), screenManager_(screenManager) {
if (choices) {
// If choices is nullptr, we're being called from PopupMultiChoiceDynamic where value doesn't yet point to anything valid.
if (*value >= numChoices + minVal)
*value = numChoices + minVal - 1;
if (*value < minVal)
*value = minVal;
UpdateText();
}
OnClick.Handle(this, &PopupMultiChoice::HandleClick);
}
I18NCat category, ScreenManager *screenManager, UI::LayoutParams *layoutParams = nullptr);

void Update() override;

void HideChoice(int c) {
hidden_.insert(c);
}
void SetChoiceIcon(int c, ImageID id) {
icons_[c] = id;
}
bool IsChoiceHidden(int c) const {
return hidden_.find(c) != hidden_.end();
}

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;
}

UI::Event OnChoice;

protected:
std::string ValueText() 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_;
Expand Down Expand Up @@ -346,8 +344,7 @@ class PopupMultiChoiceDynamic : public PopupMultiChoice {
// 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) {
: UI::PopupMultiChoice(&valueInt_, text, nullptr, 0, (int)choices.size(), category, screenManager, layoutParams), valueStr_(value) {
if (values) {
_dbg_assert_(choices.size() == values->size());
}
Expand Down
19 changes: 14 additions & 5 deletions Common/UI/ScrollView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,9 @@ void ListView::CreateAllItems() {
imageID = &iter->second;
}
View *v = linLayout_->Add(adaptor_->CreateItemView(i, imageID));
adaptor_->AddEventCallback(v, std::bind(&ListView::OnItemCallback, this, i, std::placeholders::_1));
adaptor_->AddEventCallback(v, [this, i](UI::EventParams &e) {
OnItemCallback(i, e);
});
}
}
}
Expand All @@ -601,9 +603,12 @@ void ListView::OnItemCallback(int num, EventParams &e) {
}

View *ChoiceListAdaptor::CreateItemView(int index, ImageID *optionalImageID) {
Choice *choice = new Choice(items_[index]);
Choice *choice;
if (optionalImageID) {
choice->SetIcon(*optionalImageID);
choice = new Choice(items_[index], *optionalImageID);
} else {
choice = new Choice(items_[index]);
//choice->SetIconRight(*optionalImageID);
}
return choice;
}
Expand All @@ -614,10 +619,14 @@ void ChoiceListAdaptor::AddEventCallback(View *view, std::function<void(EventPar
}

View *StringVectorListAdaptor::CreateItemView(int index, ImageID *optionalImageID) {
Choice *choice = new Choice(items_[index], "", index == selected_);
ImageID temp;
if (optionalImageID) {
choice->SetIcon(*optionalImageID);
temp = *optionalImageID;
}
Choice *choice = new Choice(items_[index], temp);
// if (optionalImageID) {
// choice->SetIconRight(*optionalImageID);
// }
return choice;
}

Expand Down
13 changes: 9 additions & 4 deletions Common/UI/View.h
Original file line number Diff line number Diff line change
Expand Up @@ -706,10 +706,10 @@ class ClickableItem : public Clickable {
class Choice : public ClickableItem {
public:
Choice(std::string_view text, LayoutParams *layoutParams = nullptr)
: Choice(text, "", false, layoutParams) { }
: ClickableItem(layoutParams), text_(text) { }
Choice(std::string_view text, ImageID image, LayoutParams *layoutParams = nullptr)
: ClickableItem(layoutParams), text_(text), image_(image) {}
Choice(std::string_view text, std::string_view smallText, bool selected = false, LayoutParams *layoutParams = nullptr)
Choice(std::string_view text, std::string_view smallText, LayoutParams *layoutParams = nullptr)
: ClickableItem(layoutParams), text_(text), smallText_(smallText), image_(ImageID::invalid()) {}
Choice(ImageID image, LayoutParams *layoutParams = nullptr)
: ClickableItem(layoutParams), image_(image), rightIconImage_(ImageID::invalid()) {}
Expand All @@ -725,7 +725,10 @@ class Choice : public ClickableItem {
void SetDrawTextFlags(u32 flags) {
drawTextFlags_ = flags;
}
void SetIcon(ImageID iconImage, float scale = 1.0f, float rot = 0.0f, bool flipH = false, bool keepColor = true) {
void SetIconLeft(ImageID iconImage) {
image_ = iconImage;
}
void SetIconRight(ImageID iconImage, float scale = 1.0f, float rot = 0.0f, bool flipH = false, bool keepColor = true) {
rightIconKeepColor_ = keepColor;
rightIconScale_ = scale;
rightIconRot_ = rot;
Expand Down Expand Up @@ -776,7 +779,7 @@ class Choice : public ClickableItem {
class StickyChoice : public Choice {
public:
StickyChoice(std::string_view text, std::string_view smallText = "", LayoutParams *layoutParams = nullptr)
: Choice(text, smallText, false, layoutParams) {}
: Choice(text, smallText, layoutParams) {}
StickyChoice(ImageID buttonImage, LayoutParams *layoutParams = nullptr)
: Choice(buttonImage, layoutParams) {}
StickyChoice(std::string_view text, ImageID image, LayoutParams *layoutParams = nullptr)
Expand Down Expand Up @@ -835,8 +838,10 @@ class AbstractChoiceWithValueDisplay : public Choice {
void SetPasswordDisplay() {
passwordMasking_ = true;
}

protected:
virtual std::string ValueText() const = 0;
virtual ImageID ValueImage() const { return ImageID::invalid(); }

float CalculateValueScale(const UIContext &dc, std::string_view valueText, float availWidth) const;

Expand Down
7 changes: 7 additions & 0 deletions Core/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,13 @@ void Config::PostLoadCleanup() {
if (g_Config.sCustomDriver == "Default") {
g_Config.sCustomDriver = "";
}

// Squash unsupported screen rotations.
if (g_Config.iScreenRotation == ROTATION_AUTO_HORIZONTAL) {
g_Config.iScreenRotation = ROTATION_LOCKED_HORIZONTAL;
} else if (g_Config.iScreenRotation == ROTATION_LOCKED_VERTICAL180) {
g_Config.iScreenRotation = ROTATION_LOCKED_VERTICAL;
}
}

void Config::PreSaveCleanup() {
Expand Down
4 changes: 2 additions & 2 deletions Core/ConfigValues.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ enum {
ROTATION_LOCKED_HORIZONTAL = 1,
ROTATION_LOCKED_VERTICAL = 2,
ROTATION_LOCKED_HORIZONTAL180 = 3,
ROTATION_LOCKED_VERTICAL180 = 4,
ROTATION_AUTO_HORIZONTAL = 5,
ROTATION_LOCKED_VERTICAL180 = 4, // Deprecated
ROTATION_AUTO_HORIZONTAL = 5, // Deprecated
};

enum TextureFiltering {
Expand Down
4 changes: 2 additions & 2 deletions UI/CustomButtonMappingScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ void CustomButtonMappingScreen::CreateDialogViews(UI::ViewGroup *parent) {
vertLayout->Add(new CheckBox(show, co->T("Visible")));

Choice *icon = vertLayout->Add(new Choice(co->T("Icon")));
icon->SetIcon(ImageID(customKeyImages[cfg->image].i), 1.0f, customKeyImages[cfg->image].r*PI/180, false, false); // Set right icon on the choice
icon->SetIconRight(ImageID(customKeyImages[cfg->image].i), 1.0f, customKeyImages[cfg->image].r*PI/180, false, false); // Set right icon on the choice
icon->OnClick.Add([=](UI::EventParams &e) {
auto iconScreen = new ButtonIconScreen(co->T("Icon"), &(cfg->image));
if (e.v)
Expand All @@ -168,7 +168,7 @@ void CustomButtonMappingScreen::CreateDialogViews(UI::ViewGroup *parent) {
});

Choice *shape = vertLayout->Add(new Choice(co->T("Shape")));
shape->SetIcon(ImageID(customKeyShapes[cfg->shape].l), 0.6f, customKeyShapes[cfg->shape].r*PI/180, customKeyShapes[cfg->shape].f, false); // Set right icon on the choice
shape->SetIconRight(ImageID(customKeyShapes[cfg->shape].l), 0.6f, customKeyShapes[cfg->shape].r*PI/180, customKeyShapes[cfg->shape].f, false); // Set right icon on the choice
shape->OnClick.Add([=](UI::EventParams &e) {
auto shape = new ButtonShapeScreen(co->T("Shape"), &(cfg->shape));
if (e.v)
Expand Down
21 changes: 3 additions & 18 deletions UI/GameSettingsScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ void GameSettingsScreen::CreateGraphicsSettings(UI::ViewGroup *graphicsSettings)
return !g_Config.bSoftwareRendering && !g_Config.bSkipBufferEffects;
});
if (g_Config.iMultiSampleLevel > 1 && draw->GetDeviceCaps().isTilingGPU) {
msaaChoice->SetIcon(ImageID("I_WARNING"), 0.7f);
msaaChoice->SetIconRight(ImageID("I_WARNING"), 0.7f);
}
msaaChoice->SetEnabledFunc([] {
return !g_Config.bSoftwareRendering && !g_Config.bSkipBufferEffects;
Expand Down Expand Up @@ -1115,7 +1115,7 @@ void GameSettingsScreen::CreateToolsSettings(UI::ViewGroup *tools) {
retro->OnClick.Add([=](UI::EventParams &) -> void {
screenManager()->push(new RetroAchievementsSettingsScreen(gamePath_));
});
retro->SetIcon(ImageID("I_RETROACHIEVEMENTS_LOGO"));
retro->SetIconRight(ImageID("I_RETROACHIEVEMENTS_LOGO"));
}

// These were moved here so use the wrong translation objects, to avoid having to change all inis... This isn't a sustainable situation :P
Expand Down Expand Up @@ -1358,15 +1358,7 @@ void GameSettingsScreen::CreateSystemSettings(UI::ViewGroup *systemSettings) {
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_MOBILE) {
auto co = GetI18NCategory(I18NCat::CONTROLS);

static const char *screenRotation[] = { "Auto", "Landscape", "Portrait", "Landscape Reversed", "Portrait Reversed", "Landscape Auto" };
PopupMultiChoice *rot = systemSettings->Add(new PopupMultiChoice(&g_Config.iScreenRotation, co->T("Screen Rotation"), screenRotation, 0, ARRAY_SIZE(screenRotation), I18NCat::CONTROLS, screenManager()));
#if PPSSPP_PLATFORM(IOS)
// Portrait Reversed is not recommended on iPhone (and we also ban it in the plist).
// However it's recommended to support it on iPad, so maybe we will in the future.
rot->HideChoice(4);
#endif

rot->OnChoice.Handle(this, &GameSettingsScreen::OnScreenRotation);
AddRotationPicker(screenManager(), systemSettings, true);

if (System_GetPropertyBool(SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE)) {
systemSettings->Add(new CheckBox(&g_Config.bSustainedPerformanceMode, sy->T("Sustained performance mode")))->OnClick.Handle(this, &GameSettingsScreen::OnSustainedPerformanceModeChange);
Expand Down Expand Up @@ -1467,13 +1459,6 @@ void GameSettingsScreen::OnAutoFrameskip(UI::EventParams &e) {
g_Config.UpdateAfterSettingAutoFrameSkip();
}

void GameSettingsScreen::OnScreenRotation(UI::EventParams &e) {
INFO_LOG(Log::System, "New display rotation: %d", g_Config.iScreenRotation);
INFO_LOG(Log::System, "Sending rotate");
System_Notify(SystemNotification::ROTATE_UPDATED);
INFO_LOG(Log::System, "Got back from rotate");
}

void GameSettingsScreen::OnAdhocGuides(UI::EventParams &e) {
auto n = GetI18NCategory(I18NCat::NETWORKING);
std::string url(n->T("MultiplayerHowToURL", "https://github.com/hrydgard/ppsspp/wiki/How-to-play-multiplayer-games-with-PPSSPP"));
Expand Down
1 change: 0 additions & 1 deletion UI/GameSettingsScreen.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ class GameSettingsScreen : public UITabbedBaseDialogScreen {
void OnMemoryStickMyDoc(UI::EventParams &e);
void OnMemoryStickOther(UI::EventParams &e);
#endif
void OnScreenRotation(UI::EventParams &e);
void OnImmersiveModeChange(UI::EventParams &e);
void OnSustainedPerformanceModeChange(UI::EventParams &e);

Expand Down
2 changes: 1 addition & 1 deletion UI/IAPScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ void IAPScreen::CreateViews() {
image = ImageID("I_LOGO_APP_STORE");
#endif
Choice *buyButton = rightColumnItems->Add(new Choice(mm->T("Buy PPSSPP Gold"), image));
buyButton->SetIcon(ImageID("I_ICON_GOLD"), 0.5f);
buyButton->SetIconRight(ImageID("I_ICON_GOLD"), 0.5f);
buyButton->SetShine(true);
const int requesterToken = GetRequesterToken();
buyButton->OnClick.Add([this, requesterToken](UI::EventParams &) {
Expand Down
2 changes: 1 addition & 1 deletion UI/MainScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1206,7 +1206,7 @@ void MainScreen::CreateMainButtons(UI::ViewGroup *parent, bool portrait) {
gold->OnClick.Add([this](UI::EventParams &) {
LaunchBuyGold(this->screenManager());
});
gold->SetIcon(ImageID("I_ICON_GOLD"), 0.5f);
gold->SetIconRight(ImageID("I_ICON_GOLD"), 0.5f);
gold->SetImageScale(0.6f); // for the left-icon in case of vertical.
gold->SetShine(true);
}
Expand Down
2 changes: 1 addition & 1 deletion UI/MiscScreens.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ void CreditsScreen::CreateDialogViews(UI::ViewGroup *parent) {
if (!System_GetPropertyBool(SYSPROP_APP_GOLD)) {
ScreenManager *sm = screenManager();
Choice *gold = new Choice(mm->T("Buy PPSSPP Gold"));
gold->SetIcon(ImageID("I_ICON_GOLD"), 0.5f);
gold->SetIconRight(ImageID("I_ICON_GOLD"), 0.5f);
gold->SetImageScale(0.6f); // for the left-icon in case of vertical.
gold->SetShine(true);

Expand Down
24 changes: 24 additions & 0 deletions UI/MiscViews.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "Common/StringUtils.h"
#include "UI/MiscViews.h"
#include "UI/GameInfoCache.h"
#include "Common/UI/PopupScreens.h"
#include "Core/Config.h"

TextWithImage::TextWithImage(ImageID imageID, std::string_view text, UI::LinearLayoutParams *layoutParams) : UI::LinearLayout(ORIENT_HORIZONTAL, layoutParams) {
Expand Down Expand Up @@ -224,3 +225,26 @@ void GameImageView::Draw(UIContext &dc) {
dc.Flush();
dc.RebindTexture();
}

void AddRotationPicker(ScreenManager *screenManager, UI::ViewGroup *parent, bool text) {
using namespace UI;
static const char *screenRotation[] = { "Auto", "Landscape", "Portrait", "Landscape Reversed" };
static const std::map<int, ImageID> screenRotationIcons{
{ROTATION_AUTO, ImageID("I_DEVICE_ROTATION_AUTO")},
{ROTATION_LOCKED_HORIZONTAL, ImageID("I_DEVICE_ROTATION_LANDSCAPE")},
{ROTATION_LOCKED_VERTICAL, ImageID("I_DEVICE_ROTATION_PORTRAIT")},
{ROTATION_LOCKED_HORIZONTAL180, ImageID("I_DEVICE_ROTATION_LANDSCAPE_REV")},
};

auto co = GetI18NCategory(I18NCat::CONTROLS);

PopupMultiChoice *rot = parent->Add(new PopupMultiChoice(&g_Config.iScreenRotation, text ? co->T("Screen Rotation") : "", screenRotation, 0, ARRAY_SIZE(screenRotation), I18NCat::CONTROLS, screenManager, text ? nullptr : new LinearLayoutParams(ITEM_HEIGHT, ITEM_HEIGHT)));
rot->SetChoiceIcons(screenRotationIcons);
// Portrait Reversed is not recommended on iPhone (and we also ban it in the plist).
// However it's recommended to support it on iPad, so maybe we will in the future.
rot->HideChoice(4);
rot->OnChoice.Add([](UI::EventParams &) {
INFO_LOG(Log::System, "New display rotation: %d", g_Config.iScreenRotation);
System_Notify(SystemNotification::ROTATE_UPDATED);
});
}
2 changes: 2 additions & 0 deletions UI/MiscViews.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,5 @@ class GameImageView : public UI::InertView {
GameInfoFlags image_;
float scale_ = 1.0f;
};

void AddRotationPicker(ScreenManager *screenManager, UI::ViewGroup *parent, bool text);
Loading
Loading