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