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:
~Animation()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:
Draw(UIContext & dc,double t,float alpha,float x,float y,float z)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:
Draw(UIContext & dc,double t,float alpha,float x,float y,float z)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:
~FloatingSymbolsAnimation()159 	~FloatingSymbolsAnimation() override {}
Draw(UIContext & dc,double t,float alpha,float x,float y,float z)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 
Regenerate(int xres,int yres)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:
~RecentGamesAnimation()198 	~RecentGamesAnimation() override {}
Draw(UIContext & dc,double t,float alpha,float x,float y,float z)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:
CheckNext(UIContext & dc,double t)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 
GetInfo(UIContext & dc,int index)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 
DrawTex(UIContext & dc,std::shared_ptr<GameInfo> & ginfo,float amount)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 
UIBackgroundInit(UIContext & dc)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 
UIBackgroundShutdown()292 void UIBackgroundShutdown() {
293 	bgTexture.reset(nullptr);
294 	g_Animation.reset(nullptr);
295 	g_CurBackgroundAnimation = BackgroundAnimation::OFF;
296 	bgTextureInited = false;
297 }
298 
DrawBackground(UIContext & dc,float alpha,float x,float y,float z)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 
DrawGameBackground(UIContext & dc,const Path & gamePath,float x,float y,float z)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 
HandleCommonMessages(const char * message,const char * value,ScreenManager * manager,Screen * activeScreen)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 
DrawBackground(UIContext & dc)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 
DrawBackground(UIContext & dc)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 
sendMessage(const char * message,const char * value)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 
DrawBackground(UIContext & dc)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 
sendMessage(const char * message,const char * value)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 
sendMessage(const char * message,const char * value)455 void UIScreenWithBackground::sendMessage(const char *message, const char *value) {
456 	HandleCommonMessages(message, value, screenManager(), this);
457 }
458 
DrawBackground(UIContext & dc)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 
AddStandardBack(UI::ViewGroup * parent)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 
sendMessage(const char * message,const char * value)472 void UIDialogScreenWithBackground::sendMessage(const char *message, const char *value) {
473 	HandleCommonMessages(message, value, screenManager(), this);
474 }
475 
PromptScreen(std::string message,std::string yesButtonText,std::string noButtonText,std::function<void (bool)> callback)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 
CreateViews()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 
OnYes(UI::EventParams & e)508 UI::EventReturn PromptScreen::OnYes(UI::EventParams &e) {
509 	TriggerFinish(DR_OK);
510 	return UI::EVENT_DONE;
511 }
512 
OnNo(UI::EventParams & e)513 UI::EventReturn PromptScreen::OnNo(UI::EventParams &e) {
514 	TriggerFinish(DR_CANCEL);
515 	return UI::EVENT_DONE;
516 }
517 
TriggerFinish(DialogResult result)518 void PromptScreen::TriggerFinish(DialogResult result) {
519 	callback_(result == DR_OK || result == DR_YES);
520 	UIDialogScreenWithBackground::TriggerFinish(result);
521 }
522 
PostProcScreen(const std::string & title,int id)523 PostProcScreen::PostProcScreen(const std::string &title, int id) : ListPopupScreen(title), id_(id) { }
524 
CreateViews()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 
OnCompleted(DialogResult result)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 
TextureShaderScreen(const std::string & title)553 TextureShaderScreen::TextureShaderScreen(const std::string &title) : ListPopupScreen(title) {}
554 
CreateViews()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 
OnCompleted(DialogResult result)571 void TextureShaderScreen::OnCompleted(DialogResult result) {
572 	if (result != DR_OK)
573 		return;
574 	g_Config.sTextureShaderName = shaders_[listView_->GetSelected()].section;
575 }
576 
NewLanguageScreen(const std::string & title)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 
OnCompleted(DialogResult result)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 
Next()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 
LogoScreen(AfterLogoScreen afterLogoScreen)706 LogoScreen::LogoScreen(AfterLogoScreen afterLogoScreen)
707 	: afterLogoScreen_(afterLogoScreen) {
708 }
709 
update()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 
sendMessage(const char * message,const char * value)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 
key(const KeyInput & key)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 
touch(const TouchInput & touch)735 bool LogoScreen::touch(const TouchInput &touch) {
736 	if (touch.flags & TOUCH_DOWN) {
737 		Next();
738 		return true;
739 	}
740 	return false;
741 }
742 
render()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 
CreateViews()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 
OnSupport(UI::EventParams & e)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 
OnTwitter(UI::EventParams & e)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 
OnPPSSPPOrg(UI::EventParams & e)849 UI::EventReturn CreditsScreen::OnPPSSPPOrg(UI::EventParams &e) {
850 	LaunchBrowser("https://www.ppsspp.org");
851 	return UI::EVENT_DONE;
852 }
853 
OnPrivacy(UI::EventParams & e)854 UI::EventReturn CreditsScreen::OnPrivacy(UI::EventParams &e) {
855 	LaunchBrowser("https://www.ppsspp.org/privacy.html");
856 	return UI::EVENT_DONE;
857 }
858 
OnForums(UI::EventParams & e)859 UI::EventReturn CreditsScreen::OnForums(UI::EventParams &e) {
860 	LaunchBrowser("https://forums.ppsspp.org");
861 	return UI::EVENT_DONE;
862 }
863 
OnDiscord(UI::EventParams & e)864 UI::EventReturn CreditsScreen::OnDiscord(UI::EventParams &e) {
865 	LaunchBrowser("https://discord.gg/5NJB6dD");
866 	return UI::EVENT_DONE;
867 }
868 
OnShare(UI::EventParams & e)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 
OnOK(UI::EventParams & e)875 UI::EventReturn CreditsScreen::OnOK(UI::EventParams &e) {
876 	TriggerFinish(DR_OK);
877 	return UI::EVENT_DONE;
878 }
879 
CreditsScreen()880 CreditsScreen::CreditsScreen() {
881 	startTime_ = time_now_d();
882 }
883 
update()884 void CreditsScreen::update() {
885 	UIScreen::update();
886 	UpdateUIState(UISTATE_MENU);
887 }
888 
render()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 
SettingInfoMessage(int align,UI::AnchorLayoutParams * lp)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 
Show(const std::string & text,UI::View * refView)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 
Draw(UIContext & dc)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 
GetText() const1110 std::string SettingInfoMessage::GetText() const {
1111 	return showing_ && text_ ? text_->GetText() : "";
1112 }
1113