CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/UI/MiscScreens.cpp
Views: 1401
1
// Copyright (c) 2013- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include "ppsspp_config.h"
19
20
#include <algorithm>
21
#include <functional>
22
23
#include "Common/Render/DrawBuffer.h"
24
#include "Common/UI/Context.h"
25
#include "Common/UI/View.h"
26
#include "Common/UI/ViewGroup.h"
27
#include "Common/UI/UI.h"
28
29
#include "Common/System/Display.h"
30
#include "Common/System/NativeApp.h"
31
#include "Common/System/System.h"
32
#include "Common/System/Request.h"
33
#include "Common/Math/curves.h"
34
#include "Common/File/VFS/VFS.h"
35
36
#include "Common/Data/Color/RGBAUtil.h"
37
#include "Common/Data/Encoding/Utf8.h"
38
#include "Common/Data/Text/I18n.h"
39
#include "Common/Data/Random/Rng.h"
40
#include "Common/TimeUtil.h"
41
#include "Common/File/FileUtil.h"
42
#include "Common/Render/ManagedTexture.h"
43
44
#include "Core/Config.h"
45
#include "Core/System.h"
46
#include "Core/MIPS/JitCommon/JitCommon.h"
47
#include "Core/HLE/sceUtility.h"
48
#include "GPU/GPUState.h"
49
#include "GPU/GPUInterface.h"
50
#include "GPU/Common/PostShader.h"
51
52
#include "UI/ControlMappingScreen.h"
53
#include "UI/DisplayLayoutScreen.h"
54
#include "UI/EmuScreen.h"
55
#include "UI/GameInfoCache.h"
56
#include "UI/GameSettingsScreen.h"
57
#include "UI/MainScreen.h"
58
#include "UI/MiscScreens.h"
59
#include "UI/MemStickScreen.h"
60
61
#ifdef _MSC_VER
62
#pragma execution_character_set("utf-8")
63
#endif
64
65
static const ImageID symbols[4] = {
66
ImageID("I_CROSS"),
67
ImageID("I_CIRCLE"),
68
ImageID("I_SQUARE"),
69
ImageID("I_TRIANGLE"),
70
};
71
72
static const uint32_t colors[4] = {
73
0xC0FFFFFF,
74
0xC0FFFFFF,
75
0xC0FFFFFF,
76
0xC0FFFFFF,
77
};
78
79
static Draw::Texture *bgTexture;
80
81
class Animation {
82
public:
83
virtual ~Animation() {}
84
virtual void Draw(UIContext &dc, double t, float alpha, float x, float y, float z) = 0;
85
};
86
87
static constexpr float XFAC = 0.3f;
88
static constexpr float YFAC = 0.3f;
89
static constexpr float ZFAC = 0.12f;
90
static constexpr float XSPEED = 0.05f;
91
static constexpr float YSPEED = 0.05f;
92
static constexpr float ZSPEED = 0.1f;
93
94
class MovingBackground : public Animation {
95
public:
96
void Draw(UIContext &dc, double t, float alpha, float x, float y, float z) override {
97
if (!bgTexture)
98
return;
99
100
dc.Flush();
101
dc.GetDrawContext()->BindTexture(0, bgTexture);
102
Bounds bounds = dc.GetBounds();
103
104
x = std::min(std::max(x/bounds.w, 0.0f), 1.0f) * XFAC;
105
y = std::min(std::max(y/bounds.h, 0.0f), 1.0f) * YFAC;
106
z = 1.0f + std::max(XFAC, YFAC) + (z-1.0f) * ZFAC;
107
108
lastX_ = abs(x-lastX_) > 0.001f ? x*XSPEED+lastX_*(1.0f-XSPEED) : x;
109
lastY_ = abs(y-lastY_) > 0.001f ? y*YSPEED+lastY_*(1.0f-YSPEED) : y;
110
lastZ_ = abs(z-lastZ_) > 0.001f ? z*ZSPEED+lastZ_*(1.0f-ZSPEED) : z;
111
112
float u1 = lastX_/lastZ_;
113
float v1 = lastY_/lastZ_;
114
float u2 = (1.0f+lastX_)/lastZ_;
115
float v2 = (1.0f+lastY_)/lastZ_;
116
117
dc.Draw()->DrawTexRect(bounds, u1, v1, u2, v2, whiteAlpha(alpha));
118
119
dc.Flush();
120
dc.RebindTexture();
121
}
122
123
private:
124
float lastX_ = 0.0f;
125
float lastY_ = 0.0f;
126
float lastZ_ = 1.0f + std::max(XFAC, YFAC);
127
};
128
129
class WaveAnimation : public Animation {
130
public:
131
void Draw(UIContext &dc, double t, float alpha, float x, float y, float z) override {
132
const uint32_t color = colorAlpha(0xFFFFFFFF, alpha * 0.2f);
133
const float speed = 1.0;
134
135
Bounds bounds = dc.GetBounds();
136
dc.Flush();
137
dc.BeginNoTex();
138
139
// 500 is enough for any resolution really. 24 * 500 = 12000 which fits handily in our UI vertex buffer (max 65536 per flush).
140
const int steps = std::max(20, std::min((int)g_display.dp_xres, 500));
141
float step = (float)g_display.dp_xres / (float)steps;
142
t *= speed;
143
144
for (int n = 0; n < steps; n++) {
145
float x = (float)n * step;
146
float nextX = (float)(n + 1) * step;
147
float i = x * 1280 / bounds.w;
148
149
float wave0 = sin(i*0.005+t*0.8)*0.05 + sin(i*0.002+t*0.25)*0.02 + sin(i*0.001+t*0.3)*0.03 + 0.625;
150
float wave1 = sin(i*0.0044+t*0.4)*0.07 + sin(i*0.003+t*0.1)*0.02 + sin(i*0.001+t*0.3)*0.01 + 0.625;
151
dc.Draw()->RectVGradient(x, wave0*bounds.h, nextX, bounds.h, color, 0x00000000);
152
dc.Draw()->RectVGradient(x, wave1*bounds.h, nextX, bounds.h, color, 0x00000000);
153
154
// Add some "antialiasing"
155
dc.Draw()->RectVGradient(x, wave0*bounds.h-3.0f * g_display.pixel_in_dps_y, nextX, wave0 * bounds.h, 0x00000000, color);
156
dc.Draw()->RectVGradient(x, wave1*bounds.h-3.0f * g_display.pixel_in_dps_y, nextX, wave1 * bounds.h, 0x00000000, color);
157
}
158
159
dc.Flush();
160
dc.Begin();
161
}
162
};
163
164
class FloatingSymbolsAnimation : public Animation {
165
public:
166
void Draw(UIContext &dc, double t, float alpha, float x, float y, float z) override {
167
dc.Flush();
168
dc.Begin();
169
float xres = dc.GetBounds().w;
170
float yres = dc.GetBounds().h;
171
if (last_xres != xres || last_yres != yres) {
172
Regenerate(xres, yres);
173
}
174
175
for (int i = 0; i < COUNT; i++) {
176
float x = xbase[i] + dc.GetBounds().x;
177
float y = ybase[i] + dc.GetBounds().y + 40 * cosf(i * 7.2f + t * 1.3f);
178
float angle = (float)sin(i + t);
179
int n = i & 3;
180
ui_draw2d.DrawImageRotated(symbols[n], x, y, 1.0f, angle, colorAlpha(colors[n], alpha * 0.1f));
181
}
182
dc.Flush();
183
}
184
185
private:
186
static constexpr int COUNT = 100;
187
188
float xbase[COUNT]{};
189
float ybase[COUNT]{};
190
float last_xres = 0;
191
float last_yres = 0;
192
193
void Regenerate(int xres, int yres) {
194
GMRng rng;
195
for (int i = 0; i < COUNT; i++) {
196
xbase[i] = rng.F() * xres;
197
ybase[i] = rng.F() * yres;
198
}
199
200
last_xres = xres;
201
last_yres = yres;
202
}
203
};
204
205
class RecentGamesAnimation : public Animation {
206
public:
207
void Draw(UIContext &dc, double t, float alpha, float x, float y, float z) override {
208
if (lastIndex_ == nextIndex_) {
209
CheckNext(dc, t);
210
} else if (t > nextT_) {
211
lastIndex_ = nextIndex_;
212
}
213
214
if (g_Config.HasRecentIsos()) {
215
std::shared_ptr<GameInfo> lastInfo = GetInfo(dc, lastIndex_);
216
std::shared_ptr<GameInfo> nextInfo = GetInfo(dc, nextIndex_);
217
dc.Flush();
218
219
float lastAmount = Clamp((float)(nextT_ - t) * 1.0f / TRANSITION, 0.0f, 1.0f);
220
DrawTex(dc, lastInfo, lastAmount * alpha * 0.2f);
221
222
float nextAmount = lastAmount <= 0.0f ? 1.0f : 1.0f - lastAmount;
223
DrawTex(dc, nextInfo, nextAmount * alpha * 0.2f);
224
225
dc.RebindTexture();
226
}
227
}
228
229
private:
230
void CheckNext(UIContext &dc, double t) {
231
if (!g_Config.HasRecentIsos()) {
232
return;
233
}
234
235
for (int index = lastIndex_ + 1; index != lastIndex_; ++index) {
236
if (index < 0 || index >= (int)g_Config.RecentIsos().size()) {
237
if (lastIndex_ == -1)
238
break;
239
index = 0;
240
}
241
242
std::shared_ptr<GameInfo> ginfo = GetInfo(dc, index);
243
if (ginfo && !ginfo->Ready(GameInfoFlags::BG)) {
244
// Wait for it to load. It might be the next one.
245
break;
246
}
247
if (ginfo && (ginfo->pic1.texture || ginfo->pic0.texture)) {
248
nextIndex_ = index;
249
nextT_ = t + INTERVAL;
250
break;
251
}
252
253
// Otherwise, keep going. This skips games with no BG.
254
}
255
}
256
257
std::shared_ptr<GameInfo> GetInfo(UIContext &dc, int index) {
258
if (index < 0) {
259
return nullptr;
260
}
261
const auto recentIsos = g_Config.RecentIsos();
262
if (index >= (int)recentIsos.size())
263
return nullptr;
264
return g_gameInfoCache->GetInfo(dc.GetDrawContext(), Path(recentIsos[index]), GameInfoFlags::BG);
265
}
266
267
void DrawTex(UIContext &dc, std::shared_ptr<GameInfo> &ginfo, float amount) {
268
if (!ginfo || amount <= 0.0f)
269
return;
270
GameInfoTex *pic = ginfo->GetBGPic();
271
if (!pic)
272
return;
273
274
dc.GetDrawContext()->BindTexture(0, pic->texture);
275
uint32_t color = whiteAlpha(amount) & 0xFFc0c0c0;
276
dc.Draw()->DrawTexRect(dc.GetBounds(), 0, 0, 1, 1, color);
277
dc.Flush();
278
}
279
280
static constexpr double INTERVAL = 8.0;
281
static constexpr float TRANSITION = 3.0f;
282
283
int lastIndex_ = -1;
284
int nextIndex_ = -1;
285
double nextT_ = -INTERVAL;
286
};
287
288
// TODO: Add more styles. Remember to add to the enum in ConfigValues.h and the selector in GameSettings too.
289
290
static BackgroundAnimation g_CurBackgroundAnimation = BackgroundAnimation::OFF;
291
static std::unique_ptr<Animation> g_Animation;
292
static bool bgTextureInited = false; // Separate variable because init could fail.
293
294
void UIBackgroundInit(UIContext &dc) {
295
const Path bgPng = GetSysDirectory(DIRECTORY_SYSTEM) / "background.png";
296
const Path bgJpg = GetSysDirectory(DIRECTORY_SYSTEM) / "background.jpg";
297
if (File::Exists(bgPng) || File::Exists(bgJpg)) {
298
const Path &bgFile = File::Exists(bgPng) ? bgPng : bgJpg;
299
bgTexture = CreateTextureFromFile(dc.GetDrawContext(), bgFile.c_str(), ImageFileType::DETECT, true);
300
}
301
}
302
303
void UIBackgroundShutdown() {
304
if (bgTexture) {
305
bgTexture->Release();
306
bgTexture = nullptr;
307
}
308
bgTextureInited = false;
309
g_Animation.reset(nullptr);
310
g_CurBackgroundAnimation = BackgroundAnimation::OFF;
311
}
312
313
void DrawBackground(UIContext &dc, float alpha, float x, float y, float z) {
314
if (!bgTextureInited) {
315
UIBackgroundInit(dc);
316
bgTextureInited = true;
317
}
318
if (g_CurBackgroundAnimation != (BackgroundAnimation)g_Config.iBackgroundAnimation) {
319
g_CurBackgroundAnimation = (BackgroundAnimation)g_Config.iBackgroundAnimation;
320
321
switch (g_CurBackgroundAnimation) {
322
case BackgroundAnimation::FLOATING_SYMBOLS:
323
g_Animation.reset(new FloatingSymbolsAnimation());
324
break;
325
case BackgroundAnimation::RECENT_GAMES:
326
g_Animation.reset(new RecentGamesAnimation());
327
break;
328
case BackgroundAnimation::WAVE:
329
g_Animation.reset(new WaveAnimation());
330
break;
331
case BackgroundAnimation::MOVING_BACKGROUND:
332
g_Animation.reset(new MovingBackground());
333
break;
334
default:
335
g_Animation.reset(nullptr);
336
}
337
}
338
339
uint32_t bgColor = whiteAlpha(alpha);
340
341
if (bgTexture != nullptr) {
342
dc.Flush();
343
dc.Begin();
344
dc.GetDrawContext()->BindTexture(0, bgTexture);
345
dc.Draw()->DrawTexRect(dc.GetBounds(), 0, 0, 1, 1, bgColor);
346
347
dc.Flush();
348
dc.RebindTexture();
349
} else {
350
// I_BG original color: 0xFF754D24
351
ImageID img = ImageID("I_BG");
352
dc.Begin();
353
dc.Draw()->DrawImageStretch(img, dc.GetBounds(), bgColor & dc.theme->backgroundColor);
354
dc.Flush();
355
}
356
357
#if PPSSPP_PLATFORM(IOS)
358
// iOS uses an old screenshot when restoring the task, so to avoid an ugly
359
// jitter we accumulate time instead.
360
static int frameCount = 0.0;
361
frameCount++;
362
double t = (double)frameCount / System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE);
363
#else
364
double t = time_now_d();
365
#endif
366
367
if (g_Animation) {
368
g_Animation->Draw(dc, t, alpha, x, y, z);
369
}
370
}
371
372
uint32_t GetBackgroundColorWithAlpha(const UIContext &dc) {
373
return colorAlpha(colorBlend(dc.GetTheme().backgroundColor, 0, 0.5f), 0.65f); // 0.65 = 166 = A6
374
}
375
376
void DrawGameBackground(UIContext &dc, const Path &gamePath, float x, float y, float z) {
377
using namespace Draw;
378
using namespace UI;
379
dc.Flush();
380
381
std::shared_ptr<GameInfo> ginfo;
382
if (!gamePath.empty()) {
383
ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), gamePath, GameInfoFlags::BG);
384
}
385
386
GameInfoTex *pic = (ginfo && ginfo->Ready(GameInfoFlags::BG)) ? ginfo->GetBGPic() : nullptr;
387
if (pic) {
388
dc.GetDrawContext()->BindTexture(0, pic->texture);
389
uint32_t color = whiteAlpha(ease((time_now_d() - pic->timeLoaded) * 3)) & 0xFFc0c0c0;
390
dc.Draw()->DrawTexRect(dc.GetBounds(), 0,0,1,1, color);
391
dc.Flush();
392
dc.RebindTexture();
393
} else {
394
::DrawBackground(dc, 1.0f, x, y, z);
395
dc.RebindTexture();
396
dc.Flush();
397
}
398
}
399
400
void HandleCommonMessages(UIMessage message, const char *value, ScreenManager *manager, const Screen *activeScreen) {
401
bool isActiveScreen = manager->topScreen() == activeScreen;
402
403
if (message == UIMessage::REQUEST_CLEAR_JIT && PSP_IsInited()) {
404
// TODO: This seems to clearly be the wrong place to handle this.
405
if (MIPSComp::jit) {
406
std::lock_guard<std::recursive_mutex> guard(MIPSComp::jitLock);
407
if (MIPSComp::jit)
408
MIPSComp::jit->ClearCache();
409
}
410
currentMIPS->UpdateCore((CPUCore)g_Config.iCpuCore);
411
} else if (message == UIMessage::SHOW_CONTROL_MAPPING && isActiveScreen && std::string(activeScreen->tag()) != "ControlMapping") {
412
UpdateUIState(UISTATE_MENU);
413
manager->push(new ControlMappingScreen(Path()));
414
} else if (message == UIMessage::SHOW_DISPLAY_LAYOUT_EDITOR && isActiveScreen && std::string(activeScreen->tag()) != "DisplayLayout") {
415
UpdateUIState(UISTATE_MENU);
416
manager->push(new DisplayLayoutScreen(Path()));
417
} else if (message == UIMessage::SHOW_SETTINGS && isActiveScreen && std::string(activeScreen->tag()) != "GameSettings") {
418
UpdateUIState(UISTATE_MENU);
419
manager->push(new GameSettingsScreen(Path()));
420
} else if (message == UIMessage::SHOW_LANGUAGE_SCREEN && isActiveScreen) {
421
auto sy = GetI18NCategory(I18NCat::SYSTEM);
422
auto langScreen = new NewLanguageScreen(sy->T("Language"));
423
langScreen->OnChoice.Add([](UI::EventParams &) {
424
System_PostUIMessage(UIMessage::RECREATE_VIEWS);
425
System_Notify(SystemNotification::UI);
426
return UI::EVENT_DONE;
427
});
428
manager->push(langScreen);
429
} else if (message == UIMessage::WINDOW_MINIMIZED) {
430
if (!strcmp(value, "true")) {
431
gstate_c.skipDrawReason |= SKIPDRAW_WINDOW_MINIMIZED;
432
} else {
433
gstate_c.skipDrawReason &= ~SKIPDRAW_WINDOW_MINIMIZED;
434
}
435
}
436
}
437
438
ScreenRenderFlags BackgroundScreen::render(ScreenRenderMode mode) {
439
if (mode & ScreenRenderMode::FIRST) {
440
SetupViewport();
441
} else {
442
_dbg_assert_(false);
443
}
444
445
UIContext *uiContext = screenManager()->getUIContext();
446
447
uiContext->PushTransform({ translation_, scale_, alpha_ });
448
449
uiContext->Begin();
450
float x, y, z;
451
screenManager()->getFocusPosition(x, y, z);
452
453
if (!gamePath_.empty()) {
454
::DrawGameBackground(*uiContext, gamePath_, x, y, z);
455
} else {
456
::DrawBackground(*uiContext, 1.0f, x, y, z);
457
}
458
459
uiContext->Flush();
460
461
uiContext->PopTransform();
462
463
return ScreenRenderFlags::NONE;
464
}
465
466
void BackgroundScreen::sendMessage(UIMessage message, const char *value) {
467
switch (message) {
468
case UIMessage::GAME_SELECTED:
469
if (value && strlen(value)) {
470
gamePath_ = Path(value);
471
} else {
472
gamePath_.clear();
473
}
474
break;
475
default:
476
break;
477
}
478
}
479
480
void UIScreenWithGameBackground::sendMessage(UIMessage message, const char *value) {
481
if (message == UIMessage::SHOW_SETTINGS && screenManager()->topScreen() == this) {
482
screenManager()->push(new GameSettingsScreen(gamePath_));
483
} else {
484
UIScreenWithBackground::sendMessage(message, value);
485
}
486
}
487
488
void UIDialogScreenWithGameBackground::sendMessage(UIMessage message, const char *value) {
489
if (message == UIMessage::SHOW_SETTINGS && screenManager()->topScreen() == this) {
490
screenManager()->push(new GameSettingsScreen(gamePath_));
491
} else {
492
UIDialogScreenWithBackground::sendMessage(message, value);
493
}
494
}
495
496
void UIScreenWithBackground::sendMessage(UIMessage message, const char *value) {
497
HandleCommonMessages(message, value, screenManager(), this);
498
}
499
500
void UIDialogScreenWithBackground::AddStandardBack(UI::ViewGroup *parent) {
501
using namespace UI;
502
auto di = GetI18NCategory(I18NCat::DIALOG);
503
parent->Add(new Choice(di->T("Back"), "", false, new AnchorLayoutParams(150, 64, 10, NONE, NONE, 10)))->OnClick.Handle<UIScreen>(this, &UIScreen::OnBack);
504
}
505
506
void UIDialogScreenWithBackground::sendMessage(UIMessage message, const char *value) {
507
HandleCommonMessages(message, value, screenManager(), this);
508
}
509
510
PromptScreen::PromptScreen(const Path &gamePath, std::string_view message, std::string_view yesButtonText, std::string_view noButtonText, std::function<void(bool)> callback)
511
: UIDialogScreenWithGameBackground(gamePath), message_(message), callback_(callback) {
512
auto di = GetI18NCategory(I18NCat::DIALOG);
513
yesButtonText_ = di->T(yesButtonText);
514
noButtonText_ = di->T(noButtonText);
515
}
516
517
void PromptScreen::CreateViews() {
518
// Information in the top left.
519
// Back button to the bottom left.
520
// Scrolling action menu to the right.
521
using namespace UI;
522
523
Margins actionMenuMargins(0, 100, 15, 0);
524
525
root_ = new AnchorLayout();
526
527
root_->Add(new TextView(message_, ALIGN_LEFT | FLAG_WRAP_TEXT, false, new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 15, 15, 330, 10)))->SetClip(false);
528
529
ViewGroup *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new AnchorLayoutParams(300, WRAP_CONTENT, NONE, 15, 15, NONE));
530
root_->Add(rightColumnItems);
531
532
Choice *yesButton = rightColumnItems->Add(new Choice(yesButtonText_));
533
yesButton->OnClick.Handle(this, &PromptScreen::OnYes);
534
root_->SetDefaultFocusView(yesButton);
535
if (!noButtonText_.empty()) {
536
rightColumnItems->Add(new Choice(noButtonText_))->OnClick.Handle(this, &PromptScreen::OnNo);
537
} else {
538
// This is an information screen, not a question.
539
// Sneak in the version of PPSSPP in the corner, for debug-reporting user screenshots.
540
std::string version = System_GetProperty(SYSPROP_BUILD_VERSION);
541
root_->Add(new TextView(version, 0, true, new AnchorLayoutParams(10.0f, NONE, NONE, 10.0f)));
542
}
543
}
544
545
UI::EventReturn PromptScreen::OnYes(UI::EventParams &e) {
546
TriggerFinish(DR_OK);
547
return UI::EVENT_DONE;
548
}
549
550
UI::EventReturn PromptScreen::OnNo(UI::EventParams &e) {
551
TriggerFinish(DR_CANCEL);
552
return UI::EVENT_DONE;
553
}
554
555
void PromptScreen::TriggerFinish(DialogResult result) {
556
if (callback_) {
557
callback_(result == DR_OK || result == DR_YES);
558
}
559
UIDialogScreenWithBackground::TriggerFinish(result);
560
}
561
562
TextureShaderScreen::TextureShaderScreen(std::string_view title) : ListPopupScreen(title) {}
563
564
void TextureShaderScreen::CreateViews() {
565
auto ps = GetI18NCategory(I18NCat::TEXTURESHADERS);
566
ReloadAllPostShaderInfo(screenManager()->getDrawContext());
567
shaders_ = GetAllTextureShaderInfo();
568
std::vector<std::string> items;
569
int selected = -1;
570
for (int i = 0; i < (int)shaders_.size(); i++) {
571
if (shaders_[i].section == g_Config.sTextureShaderName)
572
selected = i;
573
items.push_back(std::string(ps->T(shaders_[i].section.c_str(), shaders_[i].name.c_str())));
574
}
575
adaptor_ = UI::StringVectorListAdaptor(items, selected);
576
577
ListPopupScreen::CreateViews();
578
}
579
580
void TextureShaderScreen::OnCompleted(DialogResult result) {
581
if (result != DR_OK)
582
return;
583
g_Config.sTextureShaderName = shaders_[listView_->GetSelected()].section;
584
}
585
586
NewLanguageScreen::NewLanguageScreen(std::string_view title) : ListPopupScreen(title) {
587
// Disable annoying encoding warning
588
#ifdef _MSC_VER
589
#pragma warning(disable:4566)
590
#endif
591
auto &langValuesMapping = g_Config.GetLangValuesMapping();
592
593
std::vector<File::FileInfo> tempLangs;
594
g_VFS.GetFileListing("lang", &tempLangs, "ini");
595
std::vector<std::string> listing;
596
int selected = -1;
597
int counter = 0;
598
for (size_t i = 0; i < tempLangs.size(); i++) {
599
// Skip README
600
if (tempLangs[i].name.find("README") != std::string::npos) {
601
continue;
602
}
603
604
// We only support Arabic on platforms where we have support for the native text rendering
605
// APIs, as proper Arabic support is way too difficult to implement ourselves.
606
#if !(defined(USING_QT_UI) || PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(ANDROID))
607
if (tempLangs[i].name.find("ar_AE") != std::string::npos) {
608
continue;
609
}
610
611
if (tempLangs[i].name.find("fa_IR") != std::string::npos) {
612
continue;
613
}
614
#endif
615
616
File::FileInfo lang = tempLangs[i];
617
langs_.push_back(lang);
618
619
std::string code;
620
size_t dot = lang.name.find('.');
621
if (dot != std::string::npos)
622
code = lang.name.substr(0, dot);
623
624
std::string buttonTitle = lang.name;
625
626
if (!code.empty()) {
627
auto iter = langValuesMapping.find(code);
628
if (iter == langValuesMapping.end()) {
629
// No title found, show locale code
630
buttonTitle = code;
631
} else {
632
buttonTitle = iter->second.first;
633
}
634
}
635
if (g_Config.sLanguageIni == code)
636
selected = counter;
637
listing.push_back(buttonTitle);
638
counter++;
639
}
640
641
adaptor_ = UI::StringVectorListAdaptor(listing, selected);
642
}
643
644
void NewLanguageScreen::OnCompleted(DialogResult result) {
645
if (result != DR_OK)
646
return;
647
std::string oldLang = g_Config.sLanguageIni;
648
std::string iniFile = langs_[listView_->GetSelected()].name;
649
650
size_t dot = iniFile.find('.');
651
std::string code;
652
if (dot != std::string::npos)
653
code = iniFile.substr(0, dot);
654
655
if (code.empty())
656
return;
657
658
g_Config.sLanguageIni = code;
659
660
bool iniLoadedSuccessfully = false;
661
// Allow the lang directory to be overridden for testing purposes (e.g. Android, where it's hard to
662
// test new languages without recompiling the entire app, which is a hassle).
663
const Path langOverridePath = GetSysDirectory(DIRECTORY_SYSTEM) / "lang";
664
665
// If we run into the unlikely case that "lang" is actually a file, just use the built-in translations.
666
if (!File::Exists(langOverridePath) || !File::IsDirectory(langOverridePath))
667
iniLoadedSuccessfully = g_i18nrepo.LoadIni(g_Config.sLanguageIni);
668
else
669
iniLoadedSuccessfully = g_i18nrepo.LoadIni(g_Config.sLanguageIni, langOverridePath);
670
671
if (iniLoadedSuccessfully) {
672
RecreateViews();
673
} else {
674
// Failed to load the language ini. Shouldn't really happen, but let's just switch back to the old language.
675
g_Config.sLanguageIni = oldLang;
676
}
677
}
678
679
void LogoScreen::Next() {
680
if (!switched_) {
681
switched_ = true;
682
Path gamePath = boot_filename;
683
684
switch (afterLogoScreen_) {
685
case AfterLogoScreen::TO_GAME_SETTINGS:
686
if (!gamePath.empty()) {
687
screenManager()->switchScreen(new EmuScreen(gamePath));
688
} else {
689
screenManager()->switchScreen(new MainScreen());
690
}
691
screenManager()->push(new GameSettingsScreen(gamePath));
692
break;
693
case AfterLogoScreen::MEMSTICK_SCREEN_INITIAL_SETUP:
694
screenManager()->switchScreen(new MemStickScreen(true));
695
break;
696
case AfterLogoScreen::DEFAULT:
697
default:
698
if (boot_filename.size()) {
699
screenManager()->switchScreen(new EmuScreen(gamePath));
700
} else {
701
screenManager()->switchScreen(new MainScreen());
702
}
703
break;
704
}
705
}
706
}
707
708
const float logoScreenSeconds = 2.5f;
709
710
LogoScreen::LogoScreen(AfterLogoScreen afterLogoScreen)
711
: afterLogoScreen_(afterLogoScreen) {
712
}
713
714
void LogoScreen::update() {
715
UIScreen::update();
716
double rate = std::max(30.0, (double)System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE));
717
718
if ((double)frames_ / rate > logoScreenSeconds) {
719
Next();
720
}
721
frames_++;
722
sinceStart_ = (double)frames_ / rate;
723
}
724
725
void LogoScreen::sendMessage(UIMessage message, const char *value) {
726
if (message == UIMessage::REQUEST_GAME_BOOT && screenManager()->topScreen() == this) {
727
screenManager()->switchScreen(new EmuScreen(Path(value)));
728
}
729
}
730
731
bool LogoScreen::key(const KeyInput &key) {
732
if (key.deviceId != DEVICE_ID_MOUSE && (key.flags & KEY_DOWN)) {
733
Next();
734
return true;
735
}
736
return false;
737
}
738
739
void LogoScreen::touch(const TouchInput &touch) {
740
if (touch.flags & TOUCH_DOWN) {
741
Next();
742
}
743
}
744
745
void LogoScreen::DrawForeground(UIContext &dc) {
746
using namespace Draw;
747
748
const Bounds &bounds = dc.GetBounds();
749
750
dc.Begin();
751
752
float t = (float)sinceStart_ / (logoScreenSeconds / 3.0f);
753
754
float alpha = t;
755
if (t > 1.0f)
756
alpha = 1.0f;
757
float alphaText = alpha;
758
if (t > 2.0f)
759
alphaText = 3.0f - t;
760
uint32_t textColor = colorAlpha(dc.theme->infoStyle.fgColor, alphaText);
761
762
auto cr = GetI18NCategory(I18NCat::PSPCREDITS);
763
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
764
char temp[256];
765
// Manually formatting UTF-8 is fun. \xXX doesn't work everywhere.
766
snprintf(temp, sizeof(temp), "%s Henrik Rydg%c%crd", cr->T_cstr("created", "Created by"), 0xC3, 0xA5);
767
if (System_GetPropertyBool(SYSPROP_APP_GOLD)) {
768
dc.Draw()->DrawImage(ImageID("I_ICONGOLD"), bounds.centerX() - 120, bounds.centerY() - 30, 1.2f, 0xFFFFFFFF, ALIGN_CENTER);
769
} else {
770
dc.Draw()->DrawImage(ImageID("I_ICON"), bounds.centerX() - 120, bounds.centerY() - 30, 1.2f, 0xFFFFFFFF, ALIGN_CENTER);
771
}
772
dc.Draw()->DrawImage(ImageID("I_LOGO"), bounds.centerX() + 40, bounds.centerY() - 30, 1.5f, 0xFFFFFFFF, ALIGN_CENTER);
773
//dc.Draw()->DrawTextShadow(UBUNTU48, "PPSSPP", bounds.w / 2, bounds.h / 2 - 30, textColor, ALIGN_CENTER);
774
dc.SetFontScale(1.0f, 1.0f);
775
dc.SetFontStyle(dc.theme->uiFont);
776
dc.DrawText(temp, bounds.centerX(), bounds.centerY() + 40, textColor, ALIGN_CENTER);
777
dc.DrawText(cr->T_cstr("license", "Free Software under GPL 2.0+"), bounds.centerX(), bounds.centerY() + 70, textColor, ALIGN_CENTER);
778
779
int ppsspp_org_y = bounds.h / 2 + 130;
780
dc.DrawText("www.ppsspp.org", bounds.centerX(), ppsspp_org_y, textColor, ALIGN_CENTER);
781
782
#if !PPSSPP_PLATFORM(UWP) || defined(_DEBUG)
783
// Draw the graphics API, except on UWP where it's always D3D11
784
std::string apiName = screenManager()->getDrawContext()->GetInfoString(InfoField::APINAME);
785
#ifdef _DEBUG
786
apiName += ", debug build ";
787
// Add some emoji for testing.
788
apiName += CodepointToUTF8(0x1F41B) + CodepointToUTF8(0x1F41C) + CodepointToUTF8(0x1F914);
789
#endif
790
dc.DrawText(gr->T_cstr(apiName.c_str()), bounds.centerX(), ppsspp_org_y + 50, textColor, ALIGN_CENTER);
791
#endif
792
793
dc.Flush();
794
}
795
796
void CreditsScreen::CreateViews() {
797
using namespace UI;
798
auto di = GetI18NCategory(I18NCat::DIALOG);
799
auto cr = GetI18NCategory(I18NCat::PSPCREDITS);
800
801
root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT));
802
Button *back = root_->Add(new Button(di->T("Back"), new AnchorLayoutParams(260, 64, NONE, NONE, 10, 10, false)));
803
back->OnClick.Handle<UIScreen>(this, &UIScreen::OnOK);
804
root_->SetDefaultFocusView(back);
805
806
// Really need to redo this whole layout with some linear layouts...
807
808
int rightYOffset = 0;
809
if (!System_GetPropertyBool(SYSPROP_APP_GOLD)) {
810
root_->Add(new Button(cr->T("Buy Gold"), new AnchorLayoutParams(260, 64, NONE, NONE, 10, 84, false)))->OnClick.Handle(this, &CreditsScreen::OnSupport);
811
rightYOffset = 74;
812
}
813
root_->Add(new Button(cr->T("PPSSPP Forums"), new AnchorLayoutParams(260, 64, 10, NONE, NONE, 158, false)))->OnClick.Handle(this, &CreditsScreen::OnForums);
814
root_->Add(new Button(cr->T("Discord"), new AnchorLayoutParams(260, 64, 10, NONE, NONE, 232, false)))->OnClick.Handle(this, &CreditsScreen::OnDiscord);
815
root_->Add(new Button("www.ppsspp.org", new AnchorLayoutParams(260, 64, 10, NONE, NONE, 10, false)))->OnClick.Handle(this, &CreditsScreen::OnPPSSPPOrg);
816
root_->Add(new Button(cr->T("Privacy Policy"), new AnchorLayoutParams(260, 64, 10, NONE, NONE, 84, false)))->OnClick.Handle(this, &CreditsScreen::OnPrivacy);
817
root_->Add(new Button(cr->T("Twitter @PPSSPP_emu"), new AnchorLayoutParams(260, 64, NONE, NONE, 10, rightYOffset + 84, false)))->OnClick.Handle(this, &CreditsScreen::OnTwitter);
818
#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS)
819
root_->Add(new Button(cr->T("Share PPSSPP"), new AnchorLayoutParams(260, 64, NONE, NONE, 10, rightYOffset + 158, false)))->OnClick.Handle(this, &CreditsScreen::OnShare);
820
#endif
821
if (System_GetPropertyBool(SYSPROP_APP_GOLD)) {
822
root_->Add(new ImageView(ImageID("I_ICONGOLD"), "", IS_DEFAULT, new AnchorLayoutParams(100, 64, 10, 10, NONE, NONE, false)));
823
} else {
824
root_->Add(new ImageView(ImageID("I_ICON"), "", IS_DEFAULT, new AnchorLayoutParams(100, 64, 10, 10, NONE, NONE, false)));
825
}
826
}
827
828
UI::EventReturn CreditsScreen::OnSupport(UI::EventParams &e) {
829
#ifdef __ANDROID__
830
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "market://details?id=org.ppsspp.ppssppgold");
831
#else
832
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org/buygold");
833
#endif
834
return UI::EVENT_DONE;
835
}
836
837
UI::EventReturn CreditsScreen::OnTwitter(UI::EventParams &e) {
838
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://twitter.com/PPSSPP_emu");
839
return UI::EVENT_DONE;
840
}
841
842
UI::EventReturn CreditsScreen::OnPPSSPPOrg(UI::EventParams &e) {
843
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org");
844
return UI::EVENT_DONE;
845
}
846
847
UI::EventReturn CreditsScreen::OnPrivacy(UI::EventParams &e) {
848
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org/privacy");
849
return UI::EVENT_DONE;
850
}
851
852
UI::EventReturn CreditsScreen::OnForums(UI::EventParams &e) {
853
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://forums.ppsspp.org");
854
return UI::EVENT_DONE;
855
}
856
857
UI::EventReturn CreditsScreen::OnDiscord(UI::EventParams &e) {
858
System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://discord.gg/5NJB6dD");
859
return UI::EVENT_DONE;
860
}
861
862
UI::EventReturn CreditsScreen::OnShare(UI::EventParams &e) {
863
auto cr = GetI18NCategory(I18NCat::PSPCREDITS);
864
System_ShareText(cr->T("CheckOutPPSSPP", "Check out PPSSPP, the awesome PSP emulator: https://www.ppsspp.org/"));
865
return UI::EVENT_DONE;
866
}
867
868
CreditsScreen::CreditsScreen() {
869
startTime_ = time_now_d();
870
}
871
872
void CreditsScreen::update() {
873
UIScreen::update();
874
UpdateUIState(UISTATE_MENU);
875
}
876
877
void CreditsScreen::DrawForeground(UIContext &dc) {
878
auto cr = GetI18NCategory(I18NCat::PSPCREDITS);
879
880
std::string specialthanksMaxim = "Maxim ";
881
specialthanksMaxim += cr->T("specialthanksMaxim", "for his amazing Atrac3+ decoder work");
882
883
std::string specialthanksKeithGalocy = "Keith Galocy ";
884
specialthanksKeithGalocy += cr->T("specialthanksKeithGalocy", "at NVIDIA (hardware, advice)");
885
886
std::string specialthanksOrphis = "Orphis (";
887
specialthanksOrphis += cr->T("build server");
888
specialthanksOrphis += ')';
889
890
std::string specialthanksangelxwind = "angelxwind (";
891
specialthanksangelxwind += cr->T("iOS builds");
892
specialthanksangelxwind += ')';
893
894
std::string specialthanksW_MS = "W.MS (";
895
specialthanksW_MS += cr->T("iOS builds");
896
specialthanksW_MS += ')';
897
898
std::string specialthankssolarmystic = "solarmystic (";
899
specialthankssolarmystic += cr->T("testing");
900
specialthankssolarmystic += ')';
901
902
std::string_view credits[] = {
903
System_GetPropertyBool(SYSPROP_APP_GOLD) ? "PPSSPP Gold" : "PPSSPP",
904
"",
905
cr->T("title", "A fast and portable PSP emulator"),
906
"",
907
"",
908
cr->T("created", "Created by"),
909
"Henrik Rydg\xc3\xa5rd",
910
"",
911
"",
912
cr->T("contributors", "Contributors:"),
913
"unknownbrackets",
914
"oioitff",
915
"xsacha",
916
"raven02",
917
"tpunix",
918
"orphis",
919
"sum2012",
920
"mikusp",
921
"aquanull",
922
"The Dax",
923
"bollu",
924
"tmaul",
925
"artart78",
926
"ced2911",
927
"soywiz",
928
"kovensky",
929
"xele",
930
"chaserhjk",
931
"evilcorn",
932
"daniel dressler",
933
"makotech222",
934
"CPkmn",
935
"mgaver",
936
"jeid3",
937
"cinaera/BeaR",
938
"jtraynham",
939
"Kingcom",
940
"arnastia",
941
"lioncash",
942
"JulianoAmaralChaves",
943
"vnctdj",
944
"kaienfr",
945
"shenweip",
946
"Danyal Zia",
947
"Igor Calabria",
948
"Coldbird",
949
"Kyhel",
950
"xebra",
951
"LunaMoo",
952
"zminhquanz",
953
"ANR2ME",
954
"adenovan",
955
"iota97",
956
"Lubos",
957
"stenzek", // For retroachievements integration
958
"fp64",
959
"",
960
cr->T("specialthanks", "Special thanks to:"),
961
specialthanksMaxim.c_str(),
962
specialthanksKeithGalocy.c_str(),
963
specialthanksOrphis.c_str(),
964
specialthanksangelxwind.c_str(),
965
specialthanksW_MS.c_str(),
966
specialthankssolarmystic.c_str(),
967
cr->T("all the forum mods"),
968
"",
969
cr->T("this translation by", ""), // Empty string as this is the original :)
970
cr->T("translators1", ""),
971
cr->T("translators2", ""),
972
cr->T("translators3", ""),
973
cr->T("translators4", ""),
974
cr->T("translators5", ""),
975
cr->T("translators6", ""),
976
"",
977
cr->T("written", "Written in C++ for speed and portability"),
978
"",
979
"",
980
cr->T("tools", "Free tools used:"),
981
#if PPSSPP_PLATFORM(ANDROID)
982
"Android SDK + NDK",
983
#endif
984
#if defined(USING_QT_UI)
985
"Qt",
986
#endif
987
#if defined(SDL)
988
"SDL",
989
#endif
990
"CMake",
991
"freetype2",
992
"zlib",
993
"rcheevos",
994
"SPIRV-Cross",
995
"armips",
996
"Basis Universal",
997
"cityhash",
998
"zstd",
999
"glew",
1000
"libchdr",
1001
"minimp3",
1002
"xxhash",
1003
"naett-http",
1004
"PSP SDK",
1005
"",
1006
"",
1007
cr->T("website", "Check out the website:"),
1008
"www.ppsspp.org",
1009
cr->T("list", "compatibility lists, forums, and development info"),
1010
"",
1011
"",
1012
cr->T("check", "Also check out Dolphin, the best Wii/GC emu around:"),
1013
"https://www.dolphin-emu.org",
1014
"",
1015
"",
1016
cr->T("info1", "PPSSPP is only intended to play games you own."),
1017
cr->T("info2", "Please make sure that you own the rights to any games"),
1018
cr->T("info3", "you play by owning the UMD or by buying the digital"),
1019
cr->T("info4", "download from the PSN store on your real PSP."),
1020
"",
1021
"",
1022
cr->T("info5", "PSP is a trademark by Sony, Inc."),
1023
};
1024
1025
// TODO: This is kinda ugly, done on every frame...
1026
char temp[256];
1027
if (System_GetPropertyBool(SYSPROP_APP_GOLD)) {
1028
snprintf(temp, sizeof(temp), "PPSSPP Gold %s", PPSSPP_GIT_VERSION);
1029
} else {
1030
snprintf(temp, sizeof(temp), "PPSSPP %s", PPSSPP_GIT_VERSION);
1031
}
1032
credits[0] = (const char *)temp;
1033
1034
dc.Begin();
1035
const Bounds &bounds = dc.GetLayoutBounds();
1036
1037
const int numItems = ARRAY_SIZE(credits);
1038
int itemHeight = 36;
1039
int totalHeight = numItems * itemHeight + bounds.h + 200;
1040
1041
float t = (float)(time_now_d() - startTime_) * 60.0;
1042
1043
float y = bounds.y2() - fmodf(t, (float)totalHeight);
1044
for (int i = 0; i < numItems; i++) {
1045
float alpha = linearInOut(y+32, 64, bounds.y2() - 192, 64);
1046
uint32_t textColor = colorAlpha(dc.theme->infoStyle.fgColor, alpha);
1047
1048
if (alpha > 0.0f) {
1049
dc.SetFontScale(ease(alpha), ease(alpha));
1050
dc.DrawText(credits[i], bounds.centerX(), y, textColor, ALIGN_HCENTER);
1051
dc.SetFontScale(1.0f, 1.0f);
1052
}
1053
y += itemHeight;
1054
}
1055
1056
dc.Flush();
1057
}
1058
1059
SettingInfoMessage::SettingInfoMessage(int align, float cutOffY, UI::AnchorLayoutParams *lp)
1060
: UI::LinearLayout(UI::ORIENT_HORIZONTAL, lp), cutOffY_(cutOffY) {
1061
using namespace UI;
1062
SetSpacing(0.0f);
1063
Add(new UI::Spacer(10.0f));
1064
text_ = Add(new UI::TextView("", align, false, new LinearLayoutParams(1.0, Margins(0, 10))));
1065
Add(new UI::Spacer(10.0f));
1066
}
1067
1068
void SettingInfoMessage::Show(std::string_view text, const UI::View *refView) {
1069
if (refView) {
1070
Bounds b = refView->GetBounds();
1071
const UI::AnchorLayoutParams *lp = GetLayoutParams()->As<UI::AnchorLayoutParams>();
1072
if (lp) {
1073
if (cutOffY_ != -1.0f && b.y >= cutOffY_) {
1074
ReplaceLayoutParams(new UI::AnchorLayoutParams(lp->width, lp->height, lp->left, 80.0f, lp->right, lp->bottom, lp->center));
1075
} else {
1076
ReplaceLayoutParams(new UI::AnchorLayoutParams(lp->width, lp->height, lp->left, g_display.dp_yres - 80.0f - 40.0f, lp->right, lp->bottom, lp->center));
1077
}
1078
}
1079
}
1080
text_->SetText(text);
1081
timeShown_ = time_now_d();
1082
}
1083
1084
void SettingInfoMessage::Draw(UIContext &dc) {
1085
static const double FADE_TIME = 1.0;
1086
static const float MAX_ALPHA = 0.9f;
1087
1088
// Let's show longer messages for more time (guesstimate at reading speed.)
1089
// Note: this will give multibyte characters more time, but they often have shorter words anyway.
1090
double timeToShow = std::max(1.5, text_->GetText().size() * 0.05);
1091
1092
double sinceShow = time_now_d() - timeShown_;
1093
float alpha = MAX_ALPHA;
1094
if (timeShown_ == 0.0 || sinceShow > timeToShow + FADE_TIME) {
1095
alpha = 0.0f;
1096
} else if (sinceShow > timeToShow) {
1097
alpha = MAX_ALPHA - MAX_ALPHA * (float)((sinceShow - timeToShow) / FADE_TIME);
1098
}
1099
1100
if (alpha >= 0.1f) {
1101
UI::Style style = dc.theme->popupStyle;
1102
style.background.color = colorAlpha(style.background.color, alpha - 0.1f);
1103
dc.FillRect(style.background, bounds_);
1104
}
1105
1106
uint32_t textColor = colorAlpha(dc.GetTheme().itemStyle.fgColor, alpha);
1107
text_->SetTextColor(textColor);
1108
ViewGroup::Draw(dc);
1109
showing_ = sinceShow <= timeToShow; // Don't consider fade time
1110
}
1111
1112
std::string SettingInfoMessage::GetText() const {
1113
return (showing_ && text_) ? text_->GetText() : "";
1114
}
1115
1116