1 #include "Window.h"
2
3 #include "components/HelpComponent.h"
4 #include "components/ImageComponent.h"
5 #include "resources/Font.h"
6 #include "resources/TextureResource.h"
7 #include "InputManager.h"
8 #include "Log.h"
9 #include "Scripting.h"
10 #include <algorithm>
11 #include <iomanip>
12
Window()13 Window::Window() : mNormalizeNextUpdate(false), mFrameTimeElapsed(0), mFrameCountElapsed(0), mAverageDeltaTime(10),
14 mAllowSleep(true), mSleeping(false), mTimeSinceLastInput(0), mScreenSaver(NULL), mRenderScreenSaver(false), mInfoPopup(NULL)
15 {
16 mHelp = new HelpComponent(this);
17 mBackgroundOverlay = new ImageComponent(this);
18 }
19
~Window()20 Window::~Window()
21 {
22 delete mBackgroundOverlay;
23
24 // delete all our GUIs
25 while(peekGui())
26 delete peekGui();
27
28 delete mHelp;
29 }
30
pushGui(GuiComponent * gui)31 void Window::pushGui(GuiComponent* gui)
32 {
33 if (mGuiStack.size() > 0)
34 {
35 auto& top = mGuiStack.back();
36 top->topWindow(false);
37 }
38 mGuiStack.push_back(gui);
39 gui->updateHelpPrompts();
40 }
41
removeGui(GuiComponent * gui)42 void Window::removeGui(GuiComponent* gui)
43 {
44 for(auto i = mGuiStack.cbegin(); i != mGuiStack.cend(); i++)
45 {
46 if(*i == gui)
47 {
48 i = mGuiStack.erase(i);
49
50 if(i == mGuiStack.cend() && mGuiStack.size()) // we just popped the stack and the stack is not empty
51 {
52 mGuiStack.back()->updateHelpPrompts();
53 mGuiStack.back()->topWindow(true);
54 }
55
56 return;
57 }
58 }
59 }
60
peekGui()61 GuiComponent* Window::peekGui()
62 {
63 if(mGuiStack.size() == 0)
64 return NULL;
65
66 return mGuiStack.back();
67 }
68
init()69 bool Window::init()
70 {
71 if(!Renderer::init())
72 {
73 LOG(LogError) << "Renderer failed to initialize!";
74 return false;
75 }
76
77 InputManager::getInstance()->init();
78
79 ResourceManager::getInstance()->reloadAll();
80
81 //keep a reference to the default fonts, so they don't keep getting destroyed/recreated
82 if(mDefaultFonts.empty())
83 {
84 mDefaultFonts.push_back(Font::get(FONT_SIZE_SMALL));
85 mDefaultFonts.push_back(Font::get(FONT_SIZE_MEDIUM));
86 mDefaultFonts.push_back(Font::get(FONT_SIZE_LARGE));
87 }
88
89 mBackgroundOverlay->setImage(":/scroll_gradient.png");
90 mBackgroundOverlay->setResize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
91
92 // update our help because font sizes probably changed
93 if(peekGui())
94 peekGui()->updateHelpPrompts();
95
96 return true;
97 }
98
deinit()99 void Window::deinit()
100 {
101 // Hide all GUI elements on uninitialisation - this disable
102 for(auto i = mGuiStack.cbegin(); i != mGuiStack.cend(); i++)
103 {
104 (*i)->onHide();
105 }
106 InputManager::getInstance()->deinit();
107 ResourceManager::getInstance()->unloadAll();
108 Renderer::deinit();
109 }
110
textInput(const char * text)111 void Window::textInput(const char* text)
112 {
113 if(peekGui())
114 peekGui()->textInput(text);
115 }
116
input(InputConfig * config,Input input)117 void Window::input(InputConfig* config, Input input)
118 {
119 if (mScreenSaver && mScreenSaver->isScreenSaverActive() && Settings::getInstance()->getBool("ScreenSaverControls")
120 && inputDuringScreensaver(config, input))
121 {
122 return;
123 }
124
125 if (mSleeping)
126 {
127 mSleeping = false;
128 mTimeSinceLastInput = 0;
129 onWake();
130 return;
131 }
132
133 mTimeSinceLastInput = 0;
134 if (cancelScreenSaver())
135 return;
136
137 bool dbg_keyboard_key_press = Settings::getInstance()->getBool("Debug") && config->getDeviceId() == DEVICE_KEYBOARD && input.value;
138 if (dbg_keyboard_key_press && input.id == SDLK_g && SDL_GetModState() & KMOD_LCTRL)
139 {
140 // toggle debug grid with Ctrl-G
141 Settings::getInstance()->setBool("DebugGrid", !Settings::getInstance()->getBool("DebugGrid"));
142 }
143 else if (dbg_keyboard_key_press && input.id == SDLK_t && SDL_GetModState() & KMOD_LCTRL)
144 {
145 // toggle TextComponent debug view with Ctrl-T
146 Settings::getInstance()->setBool("DebugText", !Settings::getInstance()->getBool("DebugText"));
147 }
148 else if (dbg_keyboard_key_press && input.id == SDLK_i && SDL_GetModState() & KMOD_LCTRL)
149 {
150 // toggle TextComponent debug view with Ctrl-I
151 Settings::getInstance()->setBool("DebugImage", !Settings::getInstance()->getBool("DebugImage"));
152 }
153 else if (peekGui())
154 {
155 this->peekGui()->input(config, input); // this is where the majority of inputs will be consumed: the GuiComponent Stack
156 }
157 }
158
inputDuringScreensaver(InputConfig * config,Input input)159 bool Window::inputDuringScreensaver(InputConfig* config, Input input)
160 {
161 bool input_consumed = false;
162 std::string screensaver_type = Settings::getInstance()->getString("ScreenSaverBehavior");
163
164 if (screensaver_type == "random video" || screensaver_type == "slideshow")
165 {
166 bool is_select_input = config->isMappedLike("right", input) || config->isMappedTo("select", input);
167 bool is_start_input = config->isMappedTo("start", input);
168
169 if (is_select_input)
170 {
171 if (input.value) {
172 mScreenSaver->nextMediaItem();
173 // user input resets sleep time counter
174 mTimeSinceLastInput = 0;
175 }
176 input_consumed = true;
177 }
178 else if (is_start_input)
179 {
180 bool slideshow_custom_images = Settings::getInstance()->getBool("SlideshowScreenSaverCustomImageSource");
181 if (!slideshow_custom_images)
182 {
183 mScreenSaver->launchGame();
184 }
185 }
186 }
187 return input_consumed;
188 }
189
update(int deltaTime)190 void Window::update(int deltaTime)
191 {
192 if(mNormalizeNextUpdate)
193 {
194 mNormalizeNextUpdate = false;
195 if(deltaTime > mAverageDeltaTime)
196 deltaTime = mAverageDeltaTime;
197 }
198
199 mFrameTimeElapsed += deltaTime;
200 mFrameCountElapsed++;
201 if(mFrameTimeElapsed > 500)
202 {
203 mAverageDeltaTime = mFrameTimeElapsed / mFrameCountElapsed;
204
205 if(Settings::getInstance()->getBool("DrawFramerate"))
206 {
207 std::stringstream ss;
208
209 // fps
210 ss << std::fixed << std::setprecision(1) << (1000.0f * (float)mFrameCountElapsed / (float)mFrameTimeElapsed) << "fps, ";
211 ss << std::fixed << std::setprecision(2) << ((float)mFrameTimeElapsed / (float)mFrameCountElapsed) << "ms";
212
213 // vram
214 float textureVramUsageMb = TextureResource::getTotalMemUsage() / 1000.0f / 1000.0f;
215 float textureTotalUsageMb = TextureResource::getTotalTextureSize() / 1000.0f / 1000.0f;
216 float fontVramUsageMb = Font::getTotalMemUsage() / 1000.0f / 1000.0f;
217
218 ss << "\nFont VRAM: " << fontVramUsageMb << " Tex VRAM: " << textureVramUsageMb <<
219 " Tex Max: " << textureTotalUsageMb;
220 mFrameDataText = std::unique_ptr<TextCache>(mDefaultFonts.at(1)->buildTextCache(ss.str(), 50.f, 50.f, 0xFF00FFFF));
221 }
222
223 mFrameTimeElapsed = 0;
224 mFrameCountElapsed = 0;
225 }
226
227 mTimeSinceLastInput += deltaTime;
228
229 if(peekGui())
230 peekGui()->update(deltaTime);
231
232 // Update the screensaver
233 if (mScreenSaver)
234 mScreenSaver->update(deltaTime);
235 }
236
render()237 void Window::render()
238 {
239 Transform4x4f transform = Transform4x4f::Identity();
240
241 mRenderedHelpPrompts = false;
242
243 // draw only bottom and top of GuiStack (if they are different)
244 if(mGuiStack.size())
245 {
246 auto& bottom = mGuiStack.front();
247 auto& top = mGuiStack.back();
248
249 bottom->render(transform);
250 if(bottom != top)
251 {
252 mBackgroundOverlay->render(transform);
253 top->render(transform);
254 }
255 }
256
257 if(!mRenderedHelpPrompts)
258 mHelp->render(transform);
259
260 if(Settings::getInstance()->getBool("DrawFramerate") && mFrameDataText)
261 {
262 Renderer::setMatrix(Transform4x4f::Identity());
263 mDefaultFonts.at(1)->renderTextCache(mFrameDataText.get());
264 }
265
266 unsigned int screensaverTime = (unsigned int)Settings::getInstance()->getInt("ScreenSaverTime");
267 if(mTimeSinceLastInput >= screensaverTime && screensaverTime != 0)
268 startScreenSaver();
269
270 // Always call the screensaver render function regardless of whether the screensaver is active
271 // or not because it may perform a fade on transition
272 renderScreenSaver();
273
274 if(!mRenderScreenSaver && mInfoPopup)
275 {
276 mInfoPopup->render(transform);
277 }
278
279 if(mTimeSinceLastInput >= screensaverTime && screensaverTime != 0)
280 {
281 unsigned int systemSleepTime = (unsigned int)Settings::getInstance()->getInt("SystemSleepTime");
282 if(!isProcessing() && mAllowSleep && systemSleepTime != 0 && mTimeSinceLastInput >= systemSleepTime) {
283 mSleeping = true;
284 onSleep();
285 }
286 }
287 }
288
normalizeNextUpdate()289 void Window::normalizeNextUpdate()
290 {
291 mNormalizeNextUpdate = true;
292 }
293
getAllowSleep()294 bool Window::getAllowSleep()
295 {
296 return mAllowSleep;
297 }
298
setAllowSleep(bool sleep)299 void Window::setAllowSleep(bool sleep)
300 {
301 mAllowSleep = sleep;
302 }
303
renderLoadingScreen(std::string text)304 void Window::renderLoadingScreen(std::string text)
305 {
306 Transform4x4f trans = Transform4x4f::Identity();
307 Renderer::setMatrix(trans);
308 Renderer::drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0x000000FF, 0x000000FF);
309
310 ImageComponent splash(this, true);
311 splash.setResize(Renderer::getScreenWidth() * 0.6f, 0.0f);
312 splash.setImage(":/splash.svg");
313 splash.setPosition((Renderer::getScreenWidth() - splash.getSize().x()) / 2, (Renderer::getScreenHeight() - splash.getSize().y()) / 2 * 0.6f);
314 splash.render(trans);
315
316 auto& font = mDefaultFonts.at(1);
317 TextCache* cache = font->buildTextCache(text, 0, 0, 0x656565FF);
318
319 float x = Math::round((Renderer::getScreenWidth() - cache->metrics.size.x()) / 2.0f);
320 float y = Math::round(Renderer::getScreenHeight() * 0.835f);
321 trans = trans.translate(Vector3f(x, y, 0.0f));
322 Renderer::setMatrix(trans);
323 font->renderTextCache(cache);
324 delete cache;
325
326 Renderer::swapBuffers();
327 }
328
renderHelpPromptsEarly()329 void Window::renderHelpPromptsEarly()
330 {
331 mHelp->render(Transform4x4f::Identity());
332 mRenderedHelpPrompts = true;
333 }
334
setHelpPrompts(const std::vector<HelpPrompt> & prompts,const HelpStyle & style)335 void Window::setHelpPrompts(const std::vector<HelpPrompt>& prompts, const HelpStyle& style)
336 {
337 mHelp->clearPrompts();
338 mHelp->setStyle(style);
339
340 std::vector<HelpPrompt> addPrompts;
341
342 std::map<std::string, bool> inputSeenMap;
343 std::map<std::string, int> mappedToSeenMap;
344 for(auto it = prompts.cbegin(); it != prompts.cend(); it++)
345 {
346 // only add it if the same icon hasn't already been added
347 if(inputSeenMap.emplace(it->first, true).second)
348 {
349 // this symbol hasn't been seen yet, what about the action name?
350 auto mappedTo = mappedToSeenMap.find(it->second);
351 if(mappedTo != mappedToSeenMap.cend())
352 {
353 // yes, it has!
354
355 // can we combine? (dpad only)
356 if((it->first == "up/down" && addPrompts.at(mappedTo->second).first != "left/right") ||
357 (it->first == "left/right" && addPrompts.at(mappedTo->second).first != "up/down"))
358 {
359 // yes!
360 addPrompts.at(mappedTo->second).first = "up/down/left/right";
361 // don't need to add this to addPrompts since we just merged
362 }else{
363 // no, we can't combine!
364 addPrompts.push_back(*it);
365 }
366 }else{
367 // no, it hasn't!
368 mappedToSeenMap.emplace(it->second, (int)addPrompts.size());
369 addPrompts.push_back(*it);
370 }
371 }
372 }
373
374 // sort prompts so it goes [dpad_all] [dpad_u/d] [dpad_l/r] [a/b/x/y/l/r] [start/select]
375 std::sort(addPrompts.begin(), addPrompts.end(), [](const HelpPrompt& a, const HelpPrompt& b) -> bool {
376
377 static const char* map[] = {
378 "up/down/left/right",
379 "up/down",
380 "left/right",
381 "a", "b", "x", "y", "l", "r",
382 "start", "select",
383 NULL
384 };
385
386 int i = 0;
387 int aVal = 0;
388 int bVal = 0;
389 while(map[i] != NULL)
390 {
391 if(a.first == map[i])
392 aVal = i;
393 if(b.first == map[i])
394 bVal = i;
395 i++;
396 }
397
398 return aVal > bVal;
399 });
400
401 mHelp->setPrompts(addPrompts);
402 }
403
404
onSleep()405 void Window::onSleep()
406 {
407 if (Settings::getInstance()->getBool("Windowed")) {
408 LOG(LogInfo) << "running windowed. No further onSleep() processing.";
409 return;
410 }
411
412 int gotErrors = Scripting::fireEvent("sleep");
413
414 if (gotErrors == 0 && mScreenSaver && mRenderScreenSaver)
415 {
416 mScreenSaver->stopScreenSaver();
417 mRenderScreenSaver = false;
418 mScreenSaver->resetCounts();
419 }
420 }
421
onWake()422 void Window::onWake()
423 {
424 Scripting::fireEvent("wake");
425 }
426
isProcessing()427 bool Window::isProcessing()
428 {
429 return count_if(mGuiStack.cbegin(), mGuiStack.cend(), [](GuiComponent* c) { return c->isProcessing(); }) > 0;
430 }
431
startScreenSaver()432 void Window::startScreenSaver()
433 {
434 if (mScreenSaver && !mRenderScreenSaver)
435 {
436 // Tell the GUI components the screensaver is starting
437 for(auto i = mGuiStack.cbegin(); i != mGuiStack.cend(); i++)
438 (*i)->onScreenSaverActivate();
439
440 mScreenSaver->startScreenSaver();
441 mRenderScreenSaver = true;
442 Scripting::fireEvent("screensaver-start");
443 }
444 }
445
cancelScreenSaver()446 bool Window::cancelScreenSaver()
447 {
448 if (mScreenSaver && mRenderScreenSaver)
449 {
450 mScreenSaver->stopScreenSaver();
451 mRenderScreenSaver = false;
452 mScreenSaver->resetCounts();
453 Scripting::fireEvent("screensaver-stop");
454
455 // Tell the GUI components the screensaver has stopped
456 for(auto i = mGuiStack.cbegin(); i != mGuiStack.cend(); i++)
457 (*i)->onScreenSaverDeactivate();
458
459 return true;
460 }
461
462 return false;
463 }
464
renderScreenSaver()465 void Window::renderScreenSaver()
466 {
467 if (mScreenSaver)
468 mScreenSaver->renderScreenSaver();
469 }
470