Merge pull request #21295 from hrydgard/assorted-fixes

Assorted UI fixes
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-02-24 14:20:46 +01:00
commit 11dbbfdd2f
13 files changed
+161 -89

No files matched your search

+10 -7
View File
@@ -55,6 +55,9 @@ void ScrollView::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec ver
if (layoutParams_->height == WRAP_CONTENT)
MeasureBySpec(layoutParams_->height, views_[0]->GetMeasuredHeight(), vert, &measuredHeight_);
}
// The below seems misguided and leads to wrong sized scrollviews. Need to investigate if it's needed somewhere...
/*
if (orientation_ == ORIENT_VERTICAL && vert.type != EXACTLY) {
float bestHeight = std::max(views_[0]->GetMeasuredHeight(), views_[0]->GetBounds().h);
if (vert.type == AT_MOST)
@@ -63,7 +66,7 @@ void ScrollView::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec ver
if (measuredHeight_ < bestHeight && layoutParams_->height < 0.0f) {
measuredHeight_ = bestHeight;
}
}
}*/
}
}
@@ -276,8 +279,8 @@ void ScrollView::Draw(UIContext &dc) {
// If not anchored at the top of the screen exactly, and not scrolled to the top,
// draw a subtle drop shadow to indicate scrollability.
const float darkness = 0.4f;
if (bounds_.y > 0.0f && orientation_ == ORIENT_VERTICAL) {
constexpr float darkness = 0.4f;
if (shadows_ && bounds_.y > 0.0f && orientation_ == ORIENT_VERTICAL) {
float radius = 20.0f;
Bounds shadowBounds = bounds_;
@@ -292,8 +295,8 @@ void ScrollView::Draw(UIContext &dc) {
}
// Same at the bottom.
float y2 = dc.GetLayoutBounds().y2();
if (bounds_.y2() < y2 && orientation_ == ORIENT_VERTICAL) {
const float y2 = dc.GetLayoutBounds().y2();
if (shadows_ && bounds_.y2() < y2 && orientation_ == ORIENT_VERTICAL) {
float radius = 20.0f;
Bounds shadowBounds = bounds_;
@@ -483,10 +486,10 @@ float ScrollView::ClampedScrollPos(float pos) {
float maxPull = bounds_.h * 0.1f;
if (pos < 0.0f) {
float dist = std::min(-pos * (1.0f / bounds_.h), 1.0f);
pull_ = -(sqrt(dist) * maxPull);
pull_ = -(sqrtf(dist) * maxPull);
} else if (pos > scrollMax) {
float dist = std::min((pos - scrollMax) * (1.0f / bounds_.h), 1.0f);
pull_ = sqrt(dist) * maxPull;
pull_ = sqrtf(dist) * maxPull;
} else {
pull_ = 0.0f;
}
+5
View File
@@ -44,6 +44,10 @@ public:
alignOpposite_ = alignOpposite;
}
void SetShadows(bool shadows) {
shadows_ = shadows;
}
NeighborResult FindScrollNeighbor(View *view, const Point2D &target, FocusDirection direction, NeighborResult best) override;
private:
@@ -81,6 +85,7 @@ private:
bool alignOpposite_ = false;
bool draggingBob_ = false;
bool mouseHover_ = false;
bool shadows_ = true;
float barDragStart_ = 0.0f;
float barDragOffset_ = 0.0f;
+7
View File
@@ -636,6 +636,9 @@ bool PSP_InitStart(const CoreParameter &coreParam) {
}
}
// Use this to test exit-during-boot and other exceptional cases.
// sleep_ms(6000, "test");
g_CoreParameter.fileType = fileType;
// TODO: The reason we pass in g_CoreParameter.errorString here is that it's persistent -
@@ -720,6 +723,10 @@ BootState PSP_Init(const CoreParameter &coreParam, std::string *error_string) {
}
}
BootState PollBootState() {
return g_bootState;
}
void PSP_Shutdown(bool success) {
// Reduce the risk for weird races with the Windows GE debugger.
gpuDebug = nullptr;
+3
View File
@@ -75,6 +75,9 @@ BootState PSP_InitUpdate(std::string *error_string);
// Returns either BootState::Complete or BootState::Failed.
BootState PSP_Init(const CoreParameter &coreParam, std::string *error_string);
// The main thread needs to block on this in some situations, like losing the surface.
BootState PollBootState();
void PSP_Shutdown(bool success);
FileLoader *PSP_LoadedFile();
+1 -1
View File
@@ -2874,7 +2874,7 @@ void FramebufferManagerCommon::NotifyRenderResized(const DisplayLayoutConfig &co
PSP_CoreParameter().renderHeight = h;
PSP_CoreParameter().renderScaleFactor = scaleFactor;
if (UpdateRenderSize(msaaLevel)) {
if (draw_ && UpdateRenderSize(msaaLevel)) {
draw_->StopThreads();
DestroyAllFBOs();
draw_->StartThreads();
+25 -7
View File
@@ -460,13 +460,20 @@ EmuScreen::~EmuScreen() {
std::string gameID = g_paramSFO.GetValueString("DISC_ID");
g_Config.TimeTracker().Stop(gameID);
// Should not be able to quit during boot, as boot can't be cancelled.
_dbg_assert_(!bootPending_);
if (!bootPending_) {
Achievements::UnloadGame();
PSP_Shutdown(true);
if (bootPending_) {
// We probably quit during boot, got blocked in lostdevice, and then didn't end up in update again to call PSP_InitUpdate.
// So we need to finish and join the boot thread before we can exit.
_dbg_assert_(PollBootState() != BootState::Booting);
// Make sure we join the boot thread, by calling PSP_InitUpdate.
std::string error_string = "(unknown error)";
PSP_InitUpdate(&error_string);
ERROR_LOG(Log::G3D, "Quit during boot, not good. %s", error_string.c_str());
bootPending_ = false;
}
Achievements::UnloadGame();
PSP_Shutdown(true);
// If achievements are disabled in the global config, let's shut it down here.
if (!g_Config.bAchievementsEnable) {
Achievements::Shutdown();
@@ -1368,6 +1375,13 @@ void EmuScreen::CreateViews() {
}
void EmuScreen::deviceLost() {
// If we are currently in the middle of boot, we have to block here!
// Otherwise the boot thread will encounter draw_ == nullptr and weird stuff like that.
// We're doing this in a very ugly way for now.
while (PollBootState() == BootState::Booting) {
sleep_ms(100, "device-lost-during-boot");
}
UIScreen::deviceLost();
if (imguiInited_) {
@@ -1634,11 +1648,15 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) {
const DeviceOrientation orientation = GetDeviceOrientation();
const DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(orientation);
// We might have a bad viewport after RunEmulation, reset.
Viewport viewport{0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f};
if (!skipBufferEffects_ && !ShouldRunEmulation(mode)) {
if (gpu) {
gpu->CopyDisplayToOutput(displayLayoutConfig);
}
draw->SetViewport(viewport);
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
darken();
return screenRenderFlags;
}
@@ -1649,6 +1667,8 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) {
if (mode & ScreenRenderMode::TOP) {
checkPowerDown();
}
draw->SetViewport(viewport);
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
renderUI();
return screenRenderFlags;
}
@@ -1658,8 +1678,6 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) {
screenRenderFlags = RunEmulation(true);
}
// We might have a bad viewport after RunEmulation, reset.
Viewport viewport{0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f};
draw->SetViewport(viewport);
ProcessQueuedVKeys();
+11 -1
View File
@@ -1744,9 +1744,19 @@ void TriggerRestart(const char *why, bool editThenRestore, const Path &gamePath)
System_RestartApp(param);
}
// From PauseScreen
std::string GetConfirmExitMessage();
void GameSettingsScreen::TriggerRestartOrDo(std::function<void()> callback) {
auto di = GetI18NCategory(I18NCat::DIALOG);
screenManager()->push(new UI::MessagePopupScreen(di->T("Restart"), di->T("Changing this setting requires PPSSPP to restart."), di->T("Restart"), di->T("Cancel"), [=](bool yes) {
std::string confirmExitMessage = GetConfirmExitMessage();
if (!confirmExitMessage.empty()) {
confirmExitMessage = "\n\n" + confirmExitMessage;
}
confirmExitMessage = std::string(di->T("Changing this setting requires PPSSPP to restart.")) + confirmExitMessage;
screenManager()->push(new UI::MessagePopupScreen(di->T("Restart"), confirmExitMessage, di->T("Restart"), di->T("Cancel"), [=](bool yes) {
if (yes) {
TriggerRestart("GameSettingsScreen::RenderingBackendYes", editGameSpecificThenRestore_, gamePath_);
} else {
+39 -30
View File
@@ -380,7 +380,7 @@ void GamePauseScreen::OnVKey(VirtKey virtualKeyCode, bool down) {
}
}
void GamePauseScreen::CreateSavestateControls(UI::LinearLayout *leftColumnItems) {
void GamePauseScreen::CreateSavestateControls(UI::LinearLayout *leftColumnItems, UI::LinearLayout **extraRow) {
auto pa = GetI18NCategory(I18NCat::PAUSE);
using namespace UI;
@@ -407,23 +407,39 @@ void GamePauseScreen::CreateSavestateControls(UI::LinearLayout *leftColumnItems)
RecreateViews();
});
}
leftColumnItems->Add(new Spacer(0.0));
LinearLayout *buttonRow = leftColumnItems->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(Margins(10, 0, 0, 0))));
*extraRow = nullptr;
LinearLayout *buttonRow = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(Margins(10, 0, 0, 0)));
if (g_Config.bEnableStateUndo && !Achievements::HardcoreModeActive() && NetworkAllowSaveState()) {
UI::Choice *loadUndoButton = buttonRow->Add(new Choice(pa->T("Undo last load"), ImageID("I_NAVIGATE_BACK")));
UI::Choice *loadUndoButton = buttonRow->Add(new Choice(pa->T("Undo last load"), ImageID("I_NAVIGATE_BACK"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)));
loadUndoButton->SetEnabled(SaveState::HasUndoLoad(saveStatePrefix_));
loadUndoButton->OnClick.Handle(this, &GamePauseScreen::OnLoadUndo);
loadUndoButton->OnClick.Add([this](UI::EventParams &e) {
SaveState::UndoLoad(saveStatePrefix_, &AfterSaveStateAction);
TriggerFinish(DR_CANCEL);
});
UI::Choice *saveUndoButton = buttonRow->Add(new Choice(pa->T("Undo last save"), ImageID("I_NAVIGATE_BACK")));
UI::Choice *saveUndoButton = buttonRow->Add(new Choice(pa->T("Undo last save"), ImageID("I_NAVIGATE_BACK"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)));
saveUndoButton->SetEnabled(SaveState::HasUndoLastSave(saveStatePrefix_));
saveUndoButton->OnClick.Handle(this, &GamePauseScreen::OnLastSaveUndo);
saveUndoButton->OnClick.Add([this](UI::EventParams &e) {
SaveState::UndoLastSave(saveStatePrefix_);
RecreateViews();
});
}
if (g_Config.iRewindSnapshotInterval > 0 && !Achievements::HardcoreModeActive() && NetworkAllowSaveState()) {
UI::Choice *rewindButton = buttonRow->Add(new Choice(pa->T("Rewind"), ImageID("I_REWIND")));
UI::Choice *rewindButton = buttonRow->Add(new Choice(pa->T("Rewind"), ImageID("I_REWIND"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)));
rewindButton->SetEnabled(SaveState::CanRewind());
rewindButton->OnClick.Handle(this, &GamePauseScreen::OnRewind);
rewindButton->OnClick.Add([this](UI::EventParams &e) {
SaveState::Rewind(&AfterSaveStateAction);
TriggerFinish(DR_CANCEL);
});
}
if (buttonRow->GetNumSubviews() == 0) {
delete buttonRow;
} else {
*extraRow = buttonRow;
}
}
@@ -476,8 +492,11 @@ void GamePauseScreen::CreateViews() {
});
}
ViewGroup *saveStateScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0f, scrollMargins));
root_->Add(saveStateScroll);
ViewGroup *saveScrollContainer = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0f, scrollMargins));
root_->Add(saveScrollContainer);
ScrollView *saveStateScroll = saveScrollContainer->Add(new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f)));
saveStateScroll->SetShadows(false);
LinearLayout *saveDataScrollItems = new LinearLayoutList(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, WRAP_CONTENT));
saveStateScroll->Add(saveDataScrollItems);
@@ -547,7 +566,11 @@ void GamePauseScreen::CreateViews() {
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org/docs/troubleshooting/save-state-time-warps");
});
}
CreateSavestateControls(saveDataScrollItems);
LinearLayout *extraRow = nullptr;
CreateSavestateControls(saveDataScrollItems, &extraRow);
if (extraRow) {
saveScrollContainer->Add(extraRow);
}
} else {
// Let's show the active challenges.
std::set<uint32_t> ids = Achievements::GetActiveChallengeIDs();
@@ -782,6 +805,10 @@ int GetUnsavedProgressSeconds() {
std::string GetConfirmExitMessage() {
std::string confirmMessage;
if (!PSP_IsInited()) {
return std::string();
}
int unsavedSeconds = GetUnsavedProgressSeconds();
// If RAIntegration has dirty info, ask for confirmation.
@@ -842,24 +869,6 @@ void GamePauseScreen::OnReportFeedback(UI::EventParams &e) {
screenManager()->push(new ReportScreen(gamePath_));
}
void GamePauseScreen::OnRewind(UI::EventParams &e) {
SaveState::Rewind(&AfterSaveStateAction);
TriggerFinish(DR_CANCEL);
}
void GamePauseScreen::OnLoadUndo(UI::EventParams &e) {
SaveState::UndoLoad(saveStatePrefix_, &AfterSaveStateAction);
TriggerFinish(DR_CANCEL);
}
void GamePauseScreen::OnLastSaveUndo(UI::EventParams &e) {
SaveState::UndoLastSave(saveStatePrefix_);
RecreateViews();
}
void GamePauseScreen::OnCreateConfig(UI::EventParams &e) {
std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO);
if (info->Ready(GameInfoFlags::PARAM_SFO)) {
+1 -5
View File
@@ -49,16 +49,12 @@ protected:
void OnVKey(VirtKey virtualKeyCode, bool down) override;
private:
void CreateSavestateControls(UI::LinearLayout *viewGroup);
void CreateSavestateControls(UI::LinearLayout *viewGroup, UI::LinearLayout **extraRow);
void OnGameSettings(UI::EventParams &e);
void OnExit(UI::EventParams &e);
void OnReportFeedback(UI::EventParams &e);
void OnRewind(UI::EventParams &e);
void OnLoadUndo(UI::EventParams &e);
void OnLastSaveUndo(UI::EventParams &e);
void OnCreateConfig(UI::EventParams &e);
void OnDeleteConfig(UI::EventParams &e);
+14 -11
View File
@@ -88,29 +88,32 @@ static void LoadThemeInfo(const std::vector<Path> &directories) {
themeInfos.push_back(info);
};
for (size_t d = 0; d < directories.size(); d++) {
for (const auto &d : directories) {
std::vector<File::FileInfo> fileInfo;
g_VFS.GetFileListing(directories[d].c_str(), &fileInfo, "ini:");
g_VFS.GetFileListing(d.c_str(), &fileInfo, "ini:");
if (fileInfo.empty()) {
File::GetFilesInDir(directories[d], &fileInfo, "ini:");
File::GetFilesInDir(d, &fileInfo, "ini:");
}
for (size_t f = 0; f < fileInfo.size(); f++) {
for (const auto &f : fileInfo) {
IniFile ini;
bool success = false;
if (fileInfo[f].isDirectory)
if (f.isDirectory) {
continue;
}
Path name = fileInfo[f].fullName;
Path path = directories[d];
Path name(f.fullName);
Path path = d;
// Hack around Android VFS path bug. really need to redesign this.
if (name.ToString().substr(0, 7) == "assets/")
if (name.ToString().substr(0, 7) == "assets/") {
name = Path(name.ToString().substr(7));
if (path.ToString().substr(0, 7) == "assets/")
}
if (path.ToString().substr(0, 7) == "assets/") {
path = Path(path.ToString().substr(7));
}
if (ini.LoadFromVFS(g_VFS, name.ToString()) || ini.Load(fileInfo[f].fullName)) {
if (ini.LoadFromVFS(g_VFS, name.ToString()) || ini.Load(f.fullName)) {
success = true;
}
@@ -245,7 +248,7 @@ void UpdateTheme() {
ui_theme.popupSliderFocusedColor = themeInfo.uPopupSliderFocusedColor;
}
UI::Theme *GetTheme() {
const UI::Theme *GetTheme() {
return &ui_theme;
}
+1 -1
View File
@@ -27,4 +27,4 @@ void ReloadAllThemeInfo();
std::vector<std::string> GetThemeInfoNames();
void UpdateTheme();
UI::Theme *GetTheme();
const UI::Theme *GetTheme();
@@ -48,6 +48,7 @@ import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.FrameLayout;
@@ -1501,26 +1502,24 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
}
private AlertDialog.Builder createDialogBuilderWithDeviceThemeAndUiVisibility() {
AlertDialog.Builder bld = new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE_DEFAULT_DARK);
bld.setOnDismissListener(dialog -> updateSystemUiVisibility());
return bld;
return new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE_DEFAULT_DARK);
}
@RequiresApi(Build.VERSION_CODES.M)
private AlertDialog.Builder createDialogBuilderNew() {
AlertDialog.Builder bld = new AlertDialog.Builder(this, android.R.style.Theme_DeviceDefault_Dialog_Alert);
bld.setOnDismissListener(dialog -> updateSystemUiVisibility());
return bld;
return new AlertDialog.Builder(this, android.R.style.Theme_DeviceDefault_Dialog_Alert);
}
private AlertDialog.Builder createDialogBuilder() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return createDialogBuilderNew();
} else {
return createDialogBuilderWithDeviceThemeAndUiVisibility();
}
}
// The return value is sent to C++ via requestID.
public void inputBox(final int requestId, final String title, String defaultText, String defaultAction) {
// Workaround for issue #13363 to fix Split/Second game start
if (isVRDevice()) {
NativeApp.sendRequestResult(requestId, false, defaultText, 0);
return;
}
final FrameLayout fl = new FrameLayout(this);
final EditText input = new EditText(this);
input.setGravity(Gravity.CENTER);
@@ -1530,39 +1529,52 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
fl.addView(input, editBoxLayout);
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setImeOptions(EditorInfo.IME_ACTION_DONE);
input.setText(defaultText);
input.setFocusableInTouchMode(true);
input.requestFocus();
input.selectAll();
//input.setSelection(input.getText().length());
AlertDialog.Builder bld;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
bld = createDialogBuilderNew();
} else {
bld = createDialogBuilderWithDeviceThemeAndUiVisibility();
}
AlertDialog.Builder builder = bld
AlertDialog.Builder builder = createDialogBuilder()
.setView(fl)
.setTitle(title)
.setPositiveButton(defaultAction, (d, which) -> {
Log.i(TAG, "input box successful");
NativeApp.sendRequestResult(requestId, true, input.getText().toString(), 0);
d.dismiss(); // It's OK that this will cause an extra dismiss message. It'll be ignored since the request number has already been processed.
// Dismiss happens automatically.
})
.setNegativeButton("Cancel", (d, which) -> {
Log.i(TAG, "input box cancelled");
NativeApp.sendRequestResult(requestId, false, "", 0);
d.cancel();
});
builder.setOnDismissListener(d -> {
builder.setOnDismissListener( d -> {
Log.i(TAG, "input box dismissed");
// This will be ignored if we already sent a success.
NativeApp.sendRequestResult(requestId, false, "", 0);
updateSystemUiVisibility();
});
AlertDialog dlg = builder.create();
AlertDialog dlg = builder.create();
input.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_DONE) {
Log.i(TAG, "input box successful via Keyboard Done");
NativeApp.sendRequestResult(requestId, true, input.getText().toString(), 0);
// We must dismiss the dialog manually here
dlg.dismiss();
return true; // Consume the event
}
return false;
});
dlg.setCancelable(true);
Window wnd = dlg.getWindow();
if (wnd != null) {
wnd.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
}
try {
dlg.show();
input.requestFocus();
} catch (Exception e) {
NativeApp.reportException(e, "AlertDialog");
}
@@ -1711,6 +1723,12 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
title = param[1];
if (param.length > 2)
defString = param[2];
// Workaround for issue #13363 to fix Split/Second game start - it requires text input
// but we don't support it on VR devices.
if (isVRDevice()) {
NativeApp.sendRequestResult(requestID, false, defString, 0);
return true;
}
Log.i(TAG, "Launching inputbox: #" + requestID + " " + title + " " + defString);
inputBox(requestID, title, defString, "OK");
return true;