Files
ppsspp/UI/DisplayLayoutScreen.cpp
T
Henrik RydgårdandClaude Opus 5.5 080470d041 Display layout: No editing while the TV shows a layout for another orientation
With the game on a TV (landscape) and the device in portrait, the display
layout screen edited the portrait layout, which the TV doesn't use, so
nothing seemed to change. Its controls are now disabled in that case, and
work as before with the device held the same way as the TV.

The screen rotation hotkeys change the layout the game is shown with, the
TV's, instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:06:07 +02:00

641 lines
25 KiB
C++

// Copyright (c) 2013- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <algorithm>
#include "Common/System/Display.h"
#include "Common/System/System.h"
#include "Common/Render/TextureAtlas.h"
#include "Common/Render/DrawBuffer.h"
#include "Common/UI/Context.h"
#include "Common/UI/View.h"
#include "Common/UI/UIScreen.h"
#include "Common/UI/TabHolder.h"
#include "Common/UI/ScreenManager.h"
#include "Common/Math/math_util.h"
#include "Common/System/NativeApp.h"
#include "Common/VR/PPSSPPVR.h"
#include "Common/StringUtils.h"
#include "Common/File/FileUtil.h"
#include "Common/System/OSD.h"
#include "Common/System/Request.h"
#include "Core/Util/PathUtil.h"
#include "Common/Data/Color/RGBAUtil.h"
#include "Common/Data/Text/I18n.h"
#include "UI/DisplayLayoutScreen.h"
#include "UI/EmuScreen.h"
#include "UI/Background.h"
#include "UI/MiscViews.h"
#include "Core/Config.h"
#include "Core/ConfigValues.h"
#include "Core/System.h"
#include "GPU/GPUCommon.h"
#include "GPU/Common/FramebufferManagerCommon.h"
#include "GPU/Common/Overlay.h"
#include "GPU/Common/PresentationCommon.h"
static const int leftColumnWidth = 200;
static const float orgRatio = 1.764706f; // 480.0 / 272.0
enum Mode {
MODE_INACTIVE = 0,
MODE_MOVE = 1,
MODE_RESIZE = 2,
};
static Bounds FRectToBounds(FRect rc) {
Bounds b;
b.x = rc.x * g_display.dpi_scale_x;
b.y = rc.y * g_display.dpi_scale_y;
b.w = rc.w * g_display.dpi_scale_x;
b.h = rc.h * g_display.dpi_scale_y;
return b;
}
class DisplayLayoutBackground : public UI::View {
public:
DisplayLayoutBackground(UI::ChoiceStrip *mode, DeviceOrientation orientation, UI::LayoutParams *layoutParams) : UI::View(layoutParams), mode_(mode), orientation_(orientation) {}
bool Touch(const TouchInput &touch) override {
int mode = mode_ ? mode_->GetSelection() : 0;
DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(orientation_);
if ((touch.flags & TouchInputFlags::MOVE) != 0 && dragging_) {
float relativeTouchX = touch.x - startX_;
float relativeTouchY = touch.y - startY_;
switch (mode) {
case MODE_MOVE:
config.fDisplayOffsetX = clamp_value(startDisplayOffsetX_ + relativeTouchX / bounds_.w, 0.0f, 1.0f);
config.fDisplayOffsetY = clamp_value(startDisplayOffsetY_ + relativeTouchY / bounds_.h, 0.0f, 1.0f);
break;
case MODE_RESIZE:
{
// Resize. Vertical = scaling; Up should be bigger so let's negate in that direction
float diffYProp = -relativeTouchY * 0.007f;
config.fDisplayScale = clamp_value(startScale_ * powf(2.0f, diffYProp), 0.2f, 2.0f);
break;
}
}
}
if ((touch.flags & TouchInputFlags::DOWN) != 0 && !dragging_) {
// Check that we're in the central 80% of the screen.
// If outside, it may be a drag from displaying the back button on phones
// where you have to drag from the side, etc.
if (touch.x >= bounds_.w * 0.1f && touch.x <= bounds_.w * 0.9f &&
touch.y >= bounds_.h * 0.1f && touch.y <= bounds_.h * 0.9f) {
dragging_ = true;
startX_ = touch.x;
startY_ = touch.y;
startDisplayOffsetX_ = config.fDisplayOffsetX;
startDisplayOffsetY_ = config.fDisplayOffsetY;
startScale_ = config.fDisplayScale;
}
}
if ((touch.flags & TouchInputFlags::UP) != 0 && dragging_) {
dragging_ = false;
}
return true;
}
private:
UI::ChoiceStrip *mode_;
bool dragging_ = false;
const DeviceOrientation orientation_;
// Touch down state for drag to resize etc
float startX_ = 0.0f;
float startY_ = 0.0f;
float startScale_ = -1.0f;
float startDisplayOffsetX_ = -1.0f;
float startDisplayOffsetY_ = -1.0f;
};
DisplayLayoutScreen::DisplayLayoutScreen(const Path &filename) : UIBaseDialogScreen(filename) {}
void DisplayLayoutScreen::DrawBackground(UIContext &dc) {
if (PSP_GetBootState() == BootState::Complete && !g_Config.bSkipBufferEffects) {
// We normally rely on the PSP screen showing through.
} else {
// But if it's not present (we're not in game, or skip buffer effects is used),
// we have to draw a substitute ourselves.
UIContext &dc = *screenManager()->getUIContext();
DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation());
// TODO: Clean this up a bit, this GetScreenFrame/CenterDisplay combo is too common.
FRect screenFrame = GetScreenFrame(config.bIgnoreScreenInsets, g_display.pixel_xres, g_display.pixel_yres);
FRect rc;
CalculateDisplayOutputRect(config, &rc, 480.0f, 272.0f, screenFrame, config.iInternalScreenRotation);
dc.Flush();
ImageID bg = ImageID("I_PSP_DISPLAY");
dc.Draw()->DrawImageStretch(bg, dc.GetBounds(), 0x7F000000);
dc.Draw()->DrawImageStretch(bg, FRectToBounds(rc), 0x7FFFFFFF);
}
}
void DisplayLayoutScreen::onFinish(DialogResult reason) {
g_Config.Save("DisplayLayoutScreen::onFinish");
}
void DisplayLayoutScreen::dialogFinished(const Screen *dialog, DialogResult result) {
RecreateViews();
}
static void NotifyPostChanges() {
System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
System_PostUIMessage(UIMessage::GPU_RENDER_RESIZED); // To deal with shaders that can change render resolution like upscaling.
System_PostUIMessage(UIMessage::POSTSHADER_UPDATED);
if (gpu) {
gpu->NotifyConfigChanged();
}
}
void DisplayLayoutScreen::OnPostProcShaderChange(UI::EventParams &e) {
// Remove the virtual "Off" entry. TODO: Get rid of it generally.
g_Config.vPostShaderNames.erase(std::remove(g_Config.vPostShaderNames.begin(), g_Config.vPostShaderNames.end(), "Off"), g_Config.vPostShaderNames.end());
FixPostShaderOrder(&g_Config.vPostShaderNames);
NotifyPostChanges();
}
static std::string PostShaderTranslateName(std::string_view value) {
if (value == "Off") {
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
// Off is a legacy fake item (gonna migrate off it later).
return std::string(gr->T("Add postprocessing shader"));
}
const ShaderInfo *info = GetPostShaderInfo(value);
if (info) {
auto ps = GetI18NCategory(I18NCat::POSTSHADERS);
return std::string(ps->T(value, info->name));
} else {
return std::string(value);
}
}
void DisplayLayoutScreen::sendMessage(UIMessage message, const char *value) {
UIBaseDialogScreen::sendMessage(message, value);
if (message == UIMessage::POSTSHADER_UPDATED) {
g_Config.bShaderChainRequires60FPS = PostShaderChainRequires60FPS(GetFullPostShadersChain(g_Config.vPostShaderNames));
RecreateViews();
}
}
// Disables every control in the group, except keep.
static void DisableControls(UI::ViewGroup *group, const UI::View *keep) {
for (int i = 0; i < group->GetNumSubviews(); i++) {
UI::View *view = group->GetViewByIndex(i);
if (view == keep) {
continue;
}
if (view->IsViewGroup()) {
DisableControls((UI::ViewGroup *)view, keep);
} else {
view->SetEnabled(false);
}
}
}
void DisplayLayoutScreen::CreateViews() {
const Bounds &bounds = screenManager()->getUIContext()->GetBounds();
using namespace UI;
auto di = GetI18NCategory(I18NCat::DIALOG);
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
auto co = GetI18NCategory(I18NCat::CONTROLS);
auto ps = GetI18NCategory(I18NCat::POSTSHADERS);
auto sy = GetI18NCategory(I18NCat::SYSTEM);
root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT));
const bool portrait = GetDeviceOrientation() == DeviceOrientation::Portrait;
// Make it so that a touch can only affect one view. Makes manipulating the background through the buttons
// impossible.
root_->SetExclusiveTouch(true);
// Add indicator of the current mode we're editing. Not sure why these strings are in Controls.
root_->Add(new TextView(portrait ? co->T("Portrait") : co->T("Landscape"), new AnchorLayoutParams(portrait ? 10.0f : NONE, 10.0f, NONE, NONE)))->SetSmall(true);
DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation());
bool internalPortrait = config.iInternalScreenRotation == ROTATION_LOCKED_VERTICAL || config.iInternalScreenRotation == ROTATION_LOCKED_VERTICAL180;
LinearLayout *leftColumn;
if (!portrait) {
ScrollView *leftScrollView = new ScrollView(ORIENT_VERTICAL, new AnchorLayoutParams(420.0f, FILL_PARENT, 0.f, 0.f, NONE, 0.f));
leftColumn = new LinearLayout(ORIENT_VERTICAL);
leftColumn->padding.SetAll(8.0f);
leftScrollView->Add(leftColumn);
leftScrollView->SetClickableBackground(true);
root_->Add(leftScrollView);
}
ScrollView *rightScrollView = new ScrollView(ORIENT_VERTICAL, new AnchorLayoutParams(300.0f, FILL_PARENT, NONE, 0.f, 0.f, 0.f));
LinearLayout *rightColumn = new LinearLayout(ORIENT_VERTICAL);
rightColumn->padding.SetAll(8.0f);
rightColumn->SetSpacing(0.0f);
rightScrollView->Add(rightColumn);
rightScrollView->SetClickableBackground(true);
root_->Add(rightScrollView);
Choice *back = rightColumn->Add(new Choice(di->T("Back"), ImageID("I_NAVIGATE_BACK")));
back->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
rightColumn->Add(new Spacer(12.0f));
LinearLayout *bottomControls;
if (portrait) {
bottomControls = new LinearLayout(ORIENT_HORIZONTAL);
rightColumn->Add(bottomControls);
leftColumn = rightColumn;
} else {
bottomControls = new LinearLayout(ORIENT_HORIZONTAL, new AnchorLayoutParams(NONE, NONE, NONE, 10.0f));
root_->Add(bottomControls);
}
// Set backgrounds for readability
Drawable backgroundWithAlpha(GetBackgroundColorWithAlpha(*screenManager()->getUIContext()));
leftColumn->SetBG(backgroundWithAlpha);
rightColumn->SetBG(backgroundWithAlpha);
if (!IsVREnabled()) {
if (portrait == internalPortrait) {
// Stretch doesn't make sense in portrait mode (looks crazy), so we only show it in landscape mode.
// Vice versa if internal is portrait.
auto stretch = new CheckBox(&config.bDisplayStretch, gr->T("Stretch"));
stretch->SetDisabledPtr(&config.bDisplayIntegerScale);
rightColumn->Add(stretch);
}
PopupSliderChoiceFloat *aspectRatio = new PopupSliderChoiceFloat(&config.fDisplayAspectRatio, 0.1f, 2.0f, 1.0f, gr->T("Aspect Ratio"), screenManager());
rightColumn->Add(aspectRatio);
aspectRatio->SetEnabledFunc([&config]() {
return !config.bDisplayStretch && !config.bDisplayIntegerScale;
});
aspectRatio->SetHasDropShadow(false);
aspectRatio->SetLiveUpdate(true);
rightColumn->Add(new CheckBox(&config.bDisplayIntegerScale, gr->T("Integer scale factor")));
bool supportsInsets = false;
#if PPSSPP_PLATFORM(ANDROID)
supportsInsets = System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 28;
#elif PPSSPP_PLATFORM(IOS)
supportsInsets = true;
#endif
// Hide insets option if no insets, or OS too old.
float insetLeft = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_LEFT);
float insetRight = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_RIGHT);
float insetTop = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_TOP);
float insetBottom = System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_BOTTOM);
if (supportsInsets && (
insetLeft != 0.0f ||
insetTop != 0.0f ||
insetRight != 0.0f ||
insetBottom != 0.0f) && (insetLeft != insetTop || insetRight != insetBottom)) {
rightColumn->Add(new CheckBox(&config.bIgnoreScreenInsets, gr->T("Ignore camera notch when centering")));
}
if (System_GetPropertyBool(SYSPROP_CAN_RESTRICT_ORIENTATION)) {
rightColumn->Add(new Spacer(12.0f));
AddRotationPicker(screenManager(), rightColumn, true);
}
rightColumn->Add(new ItemHeader(gr->T("PSP display rotation")));
static const char *displayRotation[] = { "Landscape", "Portrait", "Landscape Reversed", "Portrait Reversed" };
auto rotation = new PopupMultiChoice(&config.iInternalScreenRotation, gr->T("Rotation"), displayRotation, 1, ARRAY_SIZE(displayRotation), I18NCat::CONTROLS, screenManager());
rotation->OnChoice.Add([this](UI::EventParams &) {
// Affects the presence of the Stretch checkbox.
RecreateViews();
});
rotation->SetEnabledFunc([] {
return !g_Config.bSkipBufferEffects || g_Config.bSoftwareRendering;
});
rotation->SetHideTitle(true);
rightColumn->Add(rotation);
rightColumn->Add(new CheckBox(&config.bRotateControlsWithScreen, gr->T("Rotate controls")))->SetEnabledFunc([&config]() -> bool {
return (!g_Config.bSkipBufferEffects || g_Config.bSoftwareRendering) && config.iInternalScreenRotation != 1;
});
rightColumn->Add(new Spacer(12.0f));
Choice *center = new Choice(di->T("Reset"));
center->OnClick.Add([this, &config, portrait](UI::EventParams &) {
// Hm, not really ideal to have to use strings here.
// The overlay is a choice of its own, not part of the size/position, so keep it.
const std::string overlay = config.sOverlay;
config.ResetToDefault(portrait ? "DisplayLayout.Portrait" : "DisplayLayout.Landscape");
config.sOverlay = overlay;
// Which checkboxes are shown depends on the rotation.
RecreateViews();
});
rightColumn->Add(center);
mode_ = new ChoiceStrip(ORIENT_HORIZONTAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT));
mode_->AddChoice(sy->T("Off"));
mode_->AddChoice(ImageID("I_MOVE"));
mode_->AddChoice(ImageID("I_RESIZE"));
mode_->SetSelection(0, false);
// The game goes to another output, like a TV, which doesn't use this layout.
mode_->SetEnabled(screenManager()->FindOutput(OutputRole::Game) == nullptr);
bottomControls->Add(mode_);
}
if (portrait) {
leftColumn->Add(new Spacer(24.0f));
}
if (!IsVREnabled()) {
static const char *bufFilters[] = { "Linear", "Nearest", };
leftColumn->Add(new PopupMultiChoice(&config.iDisplayFilter, gr->T("Screen Scaling Filter"), bufFilters, 1, ARRAY_SIZE(bufFilters), I18NCat::GRAPHICS, screenManager()));
// Overlays (bezels), from PSP/OVERLAYS. Rescanned each time, so new files show up without a restart.
ReloadAllOverlayInfo();
const std::vector<OverlayInfo> overlays = GetAllOverlayInfo();
std::vector<std::string> overlayNames{ std::string(sy->T("Off")) };
std::vector<std::string> overlayIds{ "" };
bool selectedFound = config.sOverlay.empty();
for (const auto &overlay : overlays) {
overlayNames.push_back(overlay.name);
overlayIds.push_back(overlay.id);
if (overlay.id == config.sOverlay)
selectedFound = true;
}
if (!selectedFound) {
// Keep showing a selected overlay whose files went missing, so it can be turned off.
overlayNames.push_back(config.sOverlay);
overlayIds.push_back(config.sOverlay);
}
if (overlayIds.size() > 1) {
auto overlayChoice = leftColumn->Add(new PopupMultiChoiceDynamic(&config.sOverlay, gr->T("Overlay"), overlayNames, I18NCat::NONE, screenManager(), &overlayIds));
overlayChoice->OnChoice.Add([](UI::EventParams &e) {
// e.b is set if the value actually changed. Re-picking the current one shouldn't
// cause a reload, since that glitches the audio of a game running in the background.
if (!e.b)
return;
// The game rect may have moved, which matters for post shaders that run at output resolution.
NotifyPostChanges();
});
}
// Just pick any PNG: it's copied into the overlays folder and everything is auto-detected.
Choice *browseOverlay = leftColumn->Add(new Choice(gr->T("Load overlay image...")));
const DeviceOrientation orientation = GetDeviceOrientation();
browseOverlay->OnClick.Add([this, orientation](UI::EventParams &) {
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
const Path savePath = GetSysDirectory(DIRECTORY_CUSTOM_OVERLAYS) / "imported.png";
System_BrowseForImage(GetRequesterToken(), gr->T("Load overlay image..."), savePath, [this, orientation, savePath](std::string_view value, int converted) {
// If the platform converted the image, it's been saved to savePath already.
const Path source = converted == 1 ? savePath : Path(value);
std::string id;
if (!source.empty() && ImportOverlayImage(source, &id)) {
if (converted == 1)
File::Delete(savePath);
g_Config.GetDisplayLayoutConfig(orientation).sOverlay = id;
NotifyPostChanges();
RecreateViews();
} else if (!value.empty() || converted == 1) {
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
g_OSD.Show(OSDType::MESSAGE_ERROR, gr->T("Overlay images must be PNG files"));
}
});
});
}
Draw::DrawContext *draw = screenManager()->getDrawContext();
bool multiViewSupported = draw->GetDeviceCaps().multiViewSupported;
auto enableStereo = [=]() -> bool {
return g_Config.bStereoRendering && multiViewSupported;
};
leftColumn->Add(new ItemHeader(gr->T("Postprocessing shaders")));
std::set<std::string> alreadyAddedShader;
// If there's a single post shader and we're just entering the dialog,
// auto-open the settings.
if (settingsVisible_.empty() && g_Config.vPostShaderNames.size() == 1) {
settingsVisible_.push_back(true);
} else if (settingsVisible_.size() < g_Config.vPostShaderNames.size()) {
settingsVisible_.resize(g_Config.vPostShaderNames.size());
}
static const ContextMenuItem postShaderContextMenu[] = {
{ "Move Up", "I_ARROW_UP" },
{ "Move Down", "I_ARROW_DOWN" },
{ "Remove", "I_TRASHCAN" },
};
for (int i = 0; i < (int)g_Config.vPostShaderNames.size() + 1 && i < ARRAY_SIZE(shaderNames_); ++i) {
// Vector element pointer get invalidated on resize, cache name to have always a valid reference in the rendering thread
shaderNames_[i] = i == g_Config.vPostShaderNames.size() ? "Off" : g_Config.vPostShaderNames[i];
LinearLayout *shaderRow = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(UI::FILL_PARENT, UI::WRAP_CONTENT));
shaderRow->SetSpacing(4.0f);
leftColumn->Add(shaderRow);
if (shaderNames_[i] != "Off") {
postProcChoice_ = shaderRow->Add(new ChoiceWithValueDisplay(&shaderNames_[i], "", &PostShaderTranslateName, new LinearLayoutParams(1.0f)));
} else {
postProcChoice_ = shaderRow->Add(new Choice(ImageID("I_PLUS")));
}
postProcChoice_->OnClick.Add([=](EventParams &e) {
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
auto procScreen = new PostProcScreen(gr->T("Postprocessing shaders"), i, false);
procScreen->SetHasDropShadow(false);
procScreen->OnChoice.Handle(this, &DisplayLayoutScreen::OnPostProcShaderChange);
if (e.v)
procScreen->SetPopupOrigin(e.v);
screenManager()->push(procScreen);
});
postProcChoice_->SetEnabledFunc([=] {
return !g_Config.bSkipBufferEffects && !enableStereo();
});
if (i < g_Config.vPostShaderNames.size()) {
bool hasSettings = false;
std::vector<const ShaderInfo *> shaderChain = GetPostShaderChain(g_Config.vPostShaderNames[i]);
for (auto shaderInfo : shaderChain) {
for (size_t i = 0; i < ARRAY_SIZE(shaderInfo->settings); ++i) {
auto &setting = shaderInfo->settings[i];
if (!setting.name.empty()) {
hasSettings = true;
break;
}
}
}
if (hasSettings) {
CheckBox *checkBox = new CheckBox(&settingsVisible_[i], ImageID("I_SLIDERS"), new LinearLayoutParams(0.0f));
auto settingsButton = shaderRow->Add(checkBox);
settingsButton->OnClick.Add([=](EventParams &e) {
RecreateViews();
});
}
auto removeButton = shaderRow->Add(new Choice(ImageID("I_TRASHCAN"), new LinearLayoutParams(0.0f)));
removeButton->OnClick.Add([=](EventParams &e) -> void {
// Protect against possible race conditions.
if (i < g_Config.vPostShaderNames.size()) {
g_Config.vPostShaderNames.erase(g_Config.vPostShaderNames.begin() + i);
FixPostShaderOrder(&g_Config.vPostShaderNames);
NotifyPostChanges();
RecreateViews();
}
});
auto moreButton = shaderRow->Add(new Choice(ImageID("I_THREE_DOTS"), new LinearLayoutParams(0.0f)));
moreButton->OnClick.Add([=](EventParams &e) -> void {
if (i >= g_Config.vPostShaderNames.size()) {
// Protect against possible race conditions.
return;
}
PopupContextMenuScreen *contextMenu = new UI::PopupContextMenuScreen(postShaderContextMenu, ARRAY_SIZE(postShaderContextMenu), I18NCat::DIALOG, moreButton);
screenManager()->push(contextMenu);
const ShaderInfo *info = GetPostShaderInfo(g_Config.vPostShaderNames[i]);
bool usesLastFrame = info ? info->usePreviousFrame : false;
contextMenu->SetEnabled(0, i > 0 && !usesLastFrame);
contextMenu->SetEnabled(1, i < g_Config.vPostShaderNames.size() - 1);
contextMenu->OnChoice.Add([=](EventParams &e) -> void {
switch (e.a) {
case 0: // Move up
std::swap(g_Config.vPostShaderNames[i - 1], g_Config.vPostShaderNames[i]);
break;
case 1: // Move down
std::swap(g_Config.vPostShaderNames[i], g_Config.vPostShaderNames[i + 1]);
break;
case 2: // Remove
g_Config.vPostShaderNames.erase(g_Config.vPostShaderNames.begin() + i);
break;
default:
return;
}
FixPostShaderOrder(&g_Config.vPostShaderNames);
NotifyPostChanges();
RecreateViews();
});
});
}
// No need for settings on the last one.
if (i == g_Config.vPostShaderNames.size())
continue;
if (!settingsVisible_[i])
continue;
std::vector<const ShaderInfo *> shaderChain = GetPostShaderChain(g_Config.vPostShaderNames[i]);
for (auto shaderInfo : shaderChain) {
// Disable duplicated shader slider
bool duplicated = alreadyAddedShader.find(shaderInfo->section) != alreadyAddedShader.end();
alreadyAddedShader.insert(shaderInfo->section);
LinearLayout *settingContainer = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(UI::FILL_PARENT, UI::WRAP_CONTENT, UI::Margins(24.0f, 0.0f, 0.0f, 0.0f)));
leftColumn->Add(settingContainer);
for (size_t i = 0; i < ARRAY_SIZE(shaderInfo->settings); ++i) {
auto &setting = shaderInfo->settings[i];
if (!setting.name.empty()) {
// This map lookup will create the setting in the mPostShaderSetting map if it doesn't exist, with a default value of 0.0.
std::string key = StringFromFormat("%sSettingCurrentValue%d", shaderInfo->section.c_str(), i + 1);
bool keyExisted = g_Config.mPostShaderSetting.find(key) != g_Config.mPostShaderSetting.end();
auto &value = g_Config.mPostShaderSetting[key];
if (!keyExisted)
value = setting.value;
if (duplicated) {
auto sliderName = StringFromFormat("%s %s", ps->T_cstr(setting.name.c_str()), ps->T_cstr("(duplicated setting, previous slider will be used)"));
PopupSliderChoiceFloat *settingValue = settingContainer->Add(new PopupSliderChoiceFloat(&value, setting.minValue, setting.maxValue, setting.value, sliderName, setting.step, screenManager()));
settingValue->SetEnabled(false);
} else {
PopupSliderChoiceFloat *settingValue = settingContainer->Add(new PopupSliderChoiceFloat(&value, setting.minValue, setting.maxValue, setting.value, ps->T(setting.name), setting.step, screenManager()));
settingValue->SetLiveUpdate(true);
settingValue->SetHasDropShadow(false);
settingValue->SetEnabledFunc([=] {
return !g_Config.bSkipBufferEffects && !enableStereo();
});
}
}
}
}
}
// With the game on a TV and the device held the other way, these would edit a layout that isn't shown.
DeviceOrientation gameOrientation;
if (GetGameOutputOrientation(screenManager(), &gameOrientation) && gameOrientation != GetDeviceOrientation()) {
DisableControls(root_, back);
}
root_->Add(new DisplayLayoutBackground(mode_, GetDeviceOrientation(), new AnchorLayoutParams(FILL_PARENT, FILL_PARENT, 0.0f, 0.0f, 0.0f, 0.0f)));
}
void PostProcScreen::CreateViews() {
auto ps = GetI18NCategory(I18NCat::POSTSHADERS);
ReloadAllPostShaderInfo(screenManager()->getDrawContext());
shaders_ = GetAllPostShaderInfo();
std::vector<std::string> items;
int selected = -1;
const std::string selectedName = id_ >= (int)g_Config.vPostShaderNames.size() ? "Off" : g_Config.vPostShaderNames[id_];
indexTranslation_.clear();
for (int i = 0; i < (int)shaders_.size(); i++) {
if (!shaders_[i].visible)
continue;
if (shaders_[i].isStereo != showStereoShaders_)
continue;
if (shaders_[i].section == selectedName)
selected = (int)indexTranslation_.size();
items.push_back(std::string(ps->T(shaders_[i].section.c_str(), shaders_[i].name.c_str())));
indexTranslation_.push_back(i);
}
adaptor_ = UI::StringVectorListAdaptor(items, selected);
ListPopupScreen::CreateViews();
}
void PostProcScreen::OnCompleted(DialogResult result) {
if (result != DR_OK)
return;
const std::string &value = shaders_[indexTranslation_[listView_->GetSelected()]].section;
// I feel this logic belongs more in the caller, but eh...
if (showStereoShaders_) {
if (g_Config.sStereoToMonoShader != value) {
g_Config.sStereoToMonoShader = value;
System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
}
} else {
if (id_ < (int)g_Config.vPostShaderNames.size()) {
if (g_Config.vPostShaderNames[id_] != value) {
g_Config.vPostShaderNames[id_] = value;
System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
}
} else {
g_Config.vPostShaderNames.push_back(value);
System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
}
}
}