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