1
// Copyright (c) 2013- PPSSPP Project.
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.
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.
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
15 // Official git repository and contact information can be found at
16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
20 #include "base/functional.h"
21 #include "base/colorutil.h"
22 #include "base/timeutil.h"
23 #include "gfx_es2/draw_buffer.h"
24 #include "math/curves.h"
25 #include "i18n/i18n.h"
26 #include "ui/ui_context.h"
28 #include "ui/viewgroup.h"
31 #include "UI/MiscScreens.h"
32 #include "UI/EmuScreen.h"
33 #include "UI/MainScreen.h"
34 #include "UI/GameInfoCache.h"
35 #include "Core/Config.h"
36 #include "Core/Host.h"
37 #include "Core/System.h"
38 #include "Core/MIPS/JitCommon/JitCommon.h"
39 #include "Core/MIPS/JitCommon/NativeJit.h"
40 #include "Core/HLE/sceUtility.h"
41 #include "Common/CPUDetect.h"
42 #include "Common/FileUtil.h"
43 #include "GPU/GPUState.h"
48 #pragma execution_character_set("utf-8")
51 #include "base/timeutil.h"
52 #include "base/colorutil.h"
53 #include "gfx_es2/draw_buffer.h"
54 #include "gfx_es2/gl_state.h"
55 #include "util/random/rng.h"
57 #include "UI/ui_atlas.h"
59 static const int symbols
[4] = {
66 static const uint32_t colors
[4] = {
73 void DrawBackground(UIContext
&dc
, float alpha
= 1.0f
) {
74 static float xbase
[100] = {0};
75 static float ybase
[100] = {0};
76 float xres
= dc
.GetBounds().w
;
77 float yres
= dc
.GetBounds().h
;
78 static int last_xres
= 0;
79 static int last_yres
= 0;
81 if (xbase
[0] == 0.0f
|| last_xres
!= xres
|| last_yres
!= yres
) {
83 for (int i
= 0; i
< 100; i
++) {
84 xbase
[i
] = rng
.F() * xres
;
85 ybase
[i
] = rng
.F() * yres
;
95 ui_draw2d
.DrawImageStretch(img
, dc
.GetBounds());
97 for (int i
= 0; i
< 100; i
++) {
98 float x
= xbase
[i
] + dc
.GetBounds().x
;
99 float y
= ybase
[i
] + dc
.GetBounds().y
+ 40 * cosf(i
* 7.2f
+ t
* 1.3f
);
100 float angle
= sinf(i
+ t
);
102 ui_draw2d
.DrawImageRotated(symbols
[n
], x
, y
, 1.0f
, angle
, colorAlpha(colors
[n
], alpha
* 0.1f
));
106 void DrawGameBackground(UIContext
&dc
, const std::string
&gamePath
) {
107 GameInfo
*ginfo
= g_gameInfoCache
.GetInfo(dc
.GetThin3DContext(), gamePath
, GAMEINFO_WANTBG
);
113 if (ginfo
->pic1Texture
) {
114 dc
.GetThin3DContext()->SetTexture(0, ginfo
->pic1Texture
);
115 loadTime
= ginfo
->timePic1WasLoaded
;
117 } else if (ginfo
->pic0Texture
) {
118 dc
.GetThin3DContext()->SetTexture(0, ginfo
->pic0Texture
);
119 loadTime
= ginfo
->timePic0WasLoaded
;
123 uint32_t color
= whiteAlpha(ease((time_now_d() - loadTime
) * 3)) & 0xFFc0c0c0;
124 dc
.Draw()->DrawTexRect(dc
.GetBounds(), 0,0,1,1, color
);
128 ::DrawBackground(dc
, 1.0f
);
135 void HandleCommonMessages(const char *message
, const char *value
, ScreenManager
*manager
) {
136 if (!strcmp(message
, "clear jit")) {
137 if (MIPSComp::jit
&& PSP_IsInited()) {
138 MIPSComp::jit
->ClearCache();
140 if (PSP_IsInited()) {
141 currentMIPS
->UpdateCore(g_Config
.bJit
? CPU_JIT
: CPU_INTERPRETER
);
146 void UIScreenWithBackground::DrawBackground(UIContext
&dc
) {
147 ::DrawBackground(dc
, 1.0f
);
151 void UIScreenWithGameBackground::DrawBackground(UIContext
&dc
) {
152 if (!gamePath_
.empty()) {
153 DrawGameBackground(dc
, gamePath_
);
155 ::DrawBackground(dc
, 1.0f
);
160 void UIDialogScreenWithGameBackground::DrawBackground(UIContext
&dc
) {
161 DrawGameBackground(dc
, gamePath_
);
164 void UIScreenWithBackground::sendMessage(const char *message
, const char *value
) {
165 HandleCommonMessages(message
, value
, screenManager());
166 I18NCategory
*dev
= GetI18NCategory("Developer");
167 if (!strcmp(message
, "language screen")) {
168 auto langScreen
= new NewLanguageScreen(dev
->T("Language"));
169 langScreen
->OnChoice
.Handle(this, &UIScreenWithBackground::OnLanguageChange
);
170 screenManager()->push(langScreen
);
174 UI::EventReturn
UIScreenWithBackground::OnLanguageChange(UI::EventParams
&e
) {
175 screenManager()->RecreateAllViews();
180 return UI::EVENT_DONE
;
183 UI::EventReturn
UIDialogScreenWithBackground::OnLanguageChange(UI::EventParams
&e
) {
184 screenManager()->RecreateAllViews();
189 return UI::EVENT_DONE
;
192 void UIDialogScreenWithBackground::DrawBackground(UIContext
&dc
) {
193 ::DrawBackground(dc
);
197 void UIDialogScreenWithBackground::AddStandardBack(UI::ViewGroup
*parent
) {
199 I18NCategory
*di
= GetI18NCategory("Dialog");
200 parent
->Add(new Choice(di
->T("Back"), "", false, new AnchorLayoutParams(150, 64, 10, NONE
, NONE
, 10)))->OnClick
.Handle
<UIScreen
>(this, &UIScreen::OnBack
);
203 void UIDialogScreenWithBackground::sendMessage(const char *message
, const char *value
) {
204 HandleCommonMessages(message
, value
, screenManager());
205 I18NCategory
*dev
= GetI18NCategory("Developer");
206 if (!strcmp(message
, "language screen")) {
207 auto langScreen
= new NewLanguageScreen(dev
->T("Language"));
208 langScreen
->OnChoice
.Handle(this, &UIDialogScreenWithBackground::OnLanguageChange
);
209 screenManager()->push(langScreen
);
210 } else if (!strcmp(message
, "window minimized")) {
211 if (!strcmp(value
, "true")) {
212 gstate_c
.skipDrawReason
|= SKIPDRAW_WINDOW_MINIMIZED
;
214 gstate_c
.skipDrawReason
&= ~SKIPDRAW_WINDOW_MINIMIZED
;
219 PromptScreen::PromptScreen(std::string message
, std::string yesButtonText
, std::string noButtonText
, std::function
<void(bool)> callback
)
220 : message_(message
), callback_(callback
) {
221 I18NCategory
*di
= GetI18NCategory("Dialog");
222 yesButtonText_
= di
->T(yesButtonText
.c_str());
223 noButtonText_
= di
->T(noButtonText
.c_str());
226 void PromptScreen::CreateViews() {
227 // Information in the top left.
228 // Back button to the bottom left.
229 // Scrolling action menu to the right.
232 Margins
actionMenuMargins(0, 100, 15, 0);
234 root_
= new LinearLayout(ORIENT_HORIZONTAL
);
236 ViewGroup
*leftColumn
= new AnchorLayout(new LinearLayoutParams(1.0f
));
237 root_
->Add(leftColumn
);
239 leftColumn
->Add(new TextView(message_
, ALIGN_LEFT
, false, new AnchorLayoutParams(10, 10, NONE
, NONE
)));
241 ViewGroup
*rightColumnItems
= new LinearLayout(ORIENT_VERTICAL
, new LinearLayoutParams(300, FILL_PARENT
, actionMenuMargins
));
242 root_
->Add(rightColumnItems
);
243 Choice
*yesButton
= rightColumnItems
->Add(new Choice(yesButtonText_
));
244 yesButton
->OnClick
.Handle(this, &PromptScreen::OnYes
);
245 root_
->SetDefaultFocusView(yesButton
);
246 if (noButtonText_
!= "")
247 rightColumnItems
->Add(new Choice(noButtonText_
))->OnClick
.Handle(this, &PromptScreen::OnNo
);
250 UI::EventReturn
PromptScreen::OnYes(UI::EventParams
&e
) {
252 screenManager()->finishDialog(this, DR_OK
);
253 return UI::EVENT_DONE
;
256 UI::EventReturn
PromptScreen::OnNo(UI::EventParams
&e
) {
258 screenManager()->finishDialog(this, DR_CANCEL
);
259 return UI::EVENT_DONE
;
262 PostProcScreen::PostProcScreen(const std::string
&title
) : ListPopupScreen(title
) {
263 I18NCategory
*ps
= GetI18NCategory("PostShaders");
264 shaders_
= GetAllPostShaderInfo();
265 std::vector
<std::string
> items
;
267 for (int i
= 0; i
< (int)shaders_
.size(); i
++) {
268 if (shaders_
[i
].section
== g_Config
.sPostShaderName
)
270 items
.push_back(ps
->T(shaders_
[i
].section
.c_str()));
272 adaptor_
= UI::StringVectorListAdaptor(items
, selected
);
275 void PostProcScreen::OnCompleted(DialogResult result
) {
278 g_Config
.sPostShaderName
= shaders_
[listView_
->GetSelected()].section
;
281 NewLanguageScreen::NewLanguageScreen(const std::string
&title
) : ListPopupScreen(title
) {
282 // Disable annoying encoding warning
284 #pragma warning(disable:4566)
286 langValuesMapping
= GetLangValuesMapping();
288 std::vector
<FileInfo
> tempLangs
;
289 VFSGetFileListing("lang", &tempLangs
, "ini");
290 std::vector
<std::string
> listing
;
293 for (size_t i
= 0; i
< tempLangs
.size(); i
++) {
295 if (tempLangs
[i
].name
.find("README") != std::string::npos
) {
300 // ar_AE only works on Windows.
301 if (tempLangs
[i
].name
.find("ar_AE") != std::string::npos
) {
304 // Farsi also only works on Windows.
305 if (tempLangs
[i
].name
.find("fa_IR") != std::string::npos
) {
309 FileInfo lang
= tempLangs
[i
];
310 langs_
.push_back(lang
);
313 size_t dot
= lang
.name
.find('.');
314 if (dot
!= std::string::npos
)
315 code
= lang
.name
.substr(0, dot
);
317 std::string buttonTitle
= lang
.name
;
320 if (langValuesMapping
.find(code
) == langValuesMapping
.end()) {
321 // No title found, show locale code
324 buttonTitle
= langValuesMapping
[code
].first
;
327 if (g_Config
.sLanguageIni
== code
)
329 listing
.push_back(buttonTitle
);
333 adaptor_
= UI::StringVectorListAdaptor(listing
, selected
);
336 void NewLanguageScreen::OnCompleted(DialogResult result
) {
339 std::string oldLang
= g_Config
.sLanguageIni
;
340 std::string iniFile
= langs_
[listView_
->GetSelected()].name
;
342 size_t dot
= iniFile
.find('.');
344 if (dot
!= std::string::npos
)
345 code
= iniFile
.substr(0, dot
);
350 g_Config
.sLanguageIni
= code
;
352 bool iniLoadedSuccessfully
= false;
353 // Allow the lang directory to be overridden for testing purposes (e.g. Android, where it's hard to
354 // test new languages without recompiling the entire app, which is a hassle).
355 const std::string langOverridePath
= g_Config
.memStickDirectory
+ "PSP/SYSTEM/lang/";
357 // If we run into the unlikely case that "lang" is actually a file, just use the built-in translations.
358 if (!File::Exists(langOverridePath
) || !File::IsDirectory(langOverridePath
))
359 iniLoadedSuccessfully
= i18nrepo
.LoadIni(g_Config
.sLanguageIni
);
361 iniLoadedSuccessfully
= i18nrepo
.LoadIni(g_Config
.sLanguageIni
, langOverridePath
);
363 if (iniLoadedSuccessfully
) {
364 // Dunno what else to do here.
365 if (langValuesMapping
.find(code
) == langValuesMapping
.end()) {
366 // Fallback to English
367 g_Config
.iLanguage
= PSP_SYSTEMPARAM_LANGUAGE_ENGLISH
;
369 g_Config
.iLanguage
= langValuesMapping
[code
].second
;
373 g_Config
.sLanguageIni
= oldLang
;
377 void LogoScreen::Next() {
380 if (boot_filename
.size()) {
381 screenManager()->switchScreen(new EmuScreen(boot_filename
));
383 screenManager()->switchScreen(new MainScreen());
388 void LogoScreen::update(InputState
&input_state
) {
389 UIScreen::update(input_state
);
391 if (frames_
> 180 || input_state
.pointer_down
[0]) {
396 void LogoScreen::sendMessage(const char *message
, const char *value
) {
397 if (!strcmp(message
, "boot")) {
398 screenManager()->switchScreen(new EmuScreen(value
));
402 bool LogoScreen::key(const KeyInput
&key
) {
403 if (key
.deviceId
!= DEVICE_ID_MOUSE
) {
410 void LogoScreen::render() {
412 UIContext
&dc
= *screenManager()->getUIContext();
414 const Bounds
&bounds
= dc
.GetBounds();
416 float xres
= dc
.GetBounds().w
;
417 float yres
= dc
.GetBounds().h
;
420 float t
= (float)frames_
/ 60.0f
;
425 float alphaText
= alpha
;
427 alphaText
= 3.0f
- t
;
429 ::DrawBackground(dc
, alpha
);
431 I18NCategory
*cr
= GetI18NCategory("PSPCredits");
433 // Manually formatting utf-8 is fun. \xXX doesn't work everywhere.
434 snprintf(temp
, sizeof(temp
), "%s Henrik Rydg%c%crd", cr
->T("created", "Created by"), 0xC3, 0xA5);
436 dc
.Draw()->DrawImage(I_ICONGOLD
, bounds
.centerX() - 120, bounds
.centerY() - 30, 1.2f
, colorAlpha(0xFFFFFFFF, alphaText
), ALIGN_CENTER
);
438 dc
.Draw()->DrawImage(I_ICON
, bounds
.centerX() - 120, bounds
.centerY() - 30, 1.2f
, colorAlpha(0xFFFFFFFF, alphaText
), ALIGN_CENTER
);
440 dc
.Draw()->DrawImage(I_LOGO
, bounds
.centerX() + 40, bounds
.centerY() - 30, 1.5f
, colorAlpha(0xFFFFFFFF, alphaText
), ALIGN_CENTER
);
441 //dc.Draw()->DrawTextShadow(UBUNTU48, "PPSSPP", xres / 2, yres / 2 - 30, colorAlpha(0xFFFFFFFF, alphaText), ALIGN_CENTER);
442 dc
.SetFontScale(1.0f
, 1.0f
);
443 dc
.SetFontStyle(dc
.theme
->uiFont
);
444 dc
.DrawText(temp
, bounds
.centerX(), bounds
.centerY() + 40, colorAlpha(0xFFFFFFFF, alphaText
), ALIGN_CENTER
);
445 dc
.DrawText(cr
->T("license", "Free Software under GPL 2.0"), bounds
.centerX(), bounds
.centerY() + 70, colorAlpha(0xFFFFFFFF, alphaText
), ALIGN_CENTER
);
446 dc
.DrawText("www.ppsspp.org", bounds
.centerX(), yres
/ 2 + 130, colorAlpha(0xFFFFFFFF, alphaText
), ALIGN_CENTER
);
447 if (boot_filename
.size()) {
448 dc
.DrawTextShadow(boot_filename
.c_str(), bounds
.centerX(), bounds
.centerY() + 180, colorAlpha(0xFFFFFFFF, alphaText
), ALIGN_CENTER
);
452 dc
.DrawText(screenManager()->getThin3DContext()->GetInfoString(T3DInfo::APINAME
), bounds
.centerX(), bounds
.y2() - 100, colorAlpha(0xFFFFFFFF, alphaText
), ALIGN_CENTER
);
459 void CreditsScreen::CreateViews() {
461 I18NCategory
*di
= GetI18NCategory("Dialog");
462 I18NCategory
*cr
= GetI18NCategory("PSPCredits");
464 root_
= new AnchorLayout(new LayoutParams(FILL_PARENT
, FILL_PARENT
));
465 Button
*back
= root_
->Add(new Button(di
->T("Back"), new AnchorLayoutParams(260, 64, NONE
, NONE
, 10, 10, false)));
466 back
->OnClick
.Handle(this, &CreditsScreen::OnOK
);
467 root_
->SetDefaultFocusView(back
);
469 root_
->Add(new Button(cr
->T("Buy Gold"), new AnchorLayoutParams(260, 64, 10, NONE
, NONE
, 10, false)))->OnClick
.Handle(this, &CreditsScreen::OnSupport
);
471 root_
->Add(new Button(cr
->T("PPSSPP Forums"), new AnchorLayoutParams(260, 64, 10, NONE
, NONE
, 84, false)))->OnClick
.Handle(this, &CreditsScreen::OnForums
);
472 root_
->Add(new Button("www.ppsspp.org", new AnchorLayoutParams(260, 64, 10, NONE
, NONE
, 158, false)))->OnClick
.Handle(this, &CreditsScreen::OnPPSSPPOrg
);
474 root_
->Add(new Button(cr
->T("Share PPSSPP"), new AnchorLayoutParams(260, 64, NONE
, NONE
, 10, 84, false)))->OnClick
.Handle(this, &CreditsScreen::OnShare
);
475 root_
->Add(new Button(cr
->T("Twitter @PPSSPP_emu"), new AnchorLayoutParams(260, 64, NONE
, NONE
, 10, 154, false)))->OnClick
.Handle(this, &CreditsScreen::OnTwitter
);
478 root_
->Add(new ImageView(I_ICONGOLD
, IS_DEFAULT
, new AnchorLayoutParams(100, 64, 10, 10, NONE
, NONE
, false)));
480 root_
->Add(new ImageView(I_ICON
, IS_DEFAULT
, new AnchorLayoutParams(100, 64, 10, 10, NONE
, NONE
, false)));
484 UI::EventReturn
CreditsScreen::OnSupport(UI::EventParams
&e
) {
486 LaunchBrowser("market://details?id=org.ppsspp.ppssppgold");
488 LaunchBrowser("http://central.ppsspp.org/buygold");
490 return UI::EVENT_DONE
;
493 UI::EventReturn
CreditsScreen::OnTwitter(UI::EventParams
&e
) {
495 System_SendMessage("showTwitter", "PPSSPP_emu");
497 LaunchBrowser("https://twitter.com/#!/PPSSPP_emu");
499 return UI::EVENT_DONE
;
502 UI::EventReturn
CreditsScreen::OnPPSSPPOrg(UI::EventParams
&e
) {
503 LaunchBrowser("http://www.ppsspp.org");
504 return UI::EVENT_DONE
;
507 UI::EventReturn
CreditsScreen::OnForums(UI::EventParams
&e
) {
508 LaunchBrowser("http://forums.ppsspp.org");
509 return UI::EVENT_DONE
;
512 UI::EventReturn
CreditsScreen::OnShare(UI::EventParams
&e
) {
513 I18NCategory
*cr
= GetI18NCategory("PSPCredits");
514 System_SendMessage("sharetext", cr
->T("CheckOutPPSSPP", "Check out PPSSPP, the awesome PSP emulator: http://www.ppsspp.org/"));
515 return UI::EVENT_DONE
;
518 UI::EventReturn
CreditsScreen::OnOK(UI::EventParams
&e
) {
519 screenManager()->finishDialog(this, DR_OK
);
520 return UI::EVENT_DONE
;
523 void CreditsScreen::update(InputState
&input_state
) {
524 UIScreen::update(input_state
);
525 UpdateUIState(UISTATE_MENU
);
526 if (input_state
.pad_buttons_down
& PAD_BUTTON_BACK
) {
527 screenManager()->finishDialog(this, DR_OK
);
532 void CreditsScreen::render() {
535 I18NCategory
*cr
= GetI18NCategory("PSPCredits");
537 const char * credits
[] = {
540 cr
->T("title", "A fast and portable PSP emulator"),
543 cr
->T("created", "Created by"),
544 "Henrik Rydg\xc3\xa5rd",
547 cr
->T("contributors", "Contributors:"),
577 "JulianoAmaralChaves",
587 cr
->T("specialthanks", "Special thanks to:"),
588 "Maxim for his amazing Atrac3+ decoder work",
589 "Keith Galocy at nVidia (hw, advice)",
590 "Orphis (build server)",
591 "angelxwind (iOS builds)",
593 "solarmystic (testing)",
594 "all the forum mods",
596 cr
->T("this translation by", ""), // Empty string as this is the original :)
597 cr
->T("translators1", ""),
598 cr
->T("translators2", ""),
599 cr
->T("translators3", ""),
600 cr
->T("translators4", ""),
601 cr
->T("translators5", ""),
602 cr
->T("translators6", ""),
604 cr
->T("written", "Written in C++ for speed and portability"),
607 cr
->T("tools", "Free tools used:"),
610 #elif defined(BLACKBERRY)
613 #if defined(USING_QT_UI)
615 #elif !defined(USING_WIN_UI)
624 cr
->T("website", "Check out the website:"),
626 cr
->T("list", "compatibility lists, forums, and development info"),
629 cr
->T("check", "Also check out Dolphin, the best Wii/GC emu around:"),
630 "http://www.dolphin-emu.org",
633 cr
->T("info1", "PPSSPP is only intended to play games you own."),
634 cr
->T("info2", "Please make sure that you own the rights to any games"),
635 cr
->T("info3", "you play by owning the UMD or by buying the digital"),
636 cr
->T("info4", "download from the PSN store on your real PSP."),
639 cr
->T("info5", "PSP is a trademark by Sony, Inc."),
643 // TODO: This is kinda ugly, done on every frame...
645 snprintf(temp
, sizeof(temp
), "PPSSPP %s", PPSSPP_GIT_VERSION
);
646 credits
[0] = (const char *)temp
;
648 UIContext
&dc
= *screenManager()->getUIContext();
650 const Bounds
&bounds
= dc
.GetBounds();
652 const int numItems
= ARRAY_SIZE(credits
);
654 int totalHeight
= numItems
* itemHeight
+ bounds
.h
+ 200;
655 int y
= bounds
.y2() - (frames_
% totalHeight
);
656 for (int i
= 0; i
< numItems
; i
++) {
657 float alpha
= linearInOut(y
+32, 64, bounds
.y2() - 192, 64);
659 dc
.SetFontScale(ease(alpha
), ease(alpha
));
660 dc
.DrawText(credits
[i
], dc
.GetBounds().centerX(), y
, whiteAlpha(alpha
), ALIGN_HCENTER
);
661 dc
.SetFontScale(1.0f
, 1.0f
);