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