#include "Window.h" #include "components/HelpComponent.h" #include "components/ImageComponent.h" #include "resources/Font.h" #include "resources/TextureResource.h" #include "InputManager.h" #include "Log.h" #include "Renderer.h" #include Window::Window() : mNormalizeNextUpdate(false), mFrameTimeElapsed(0), mFrameCountElapsed(0), mAverageDeltaTime(10), mAllowSleep(true), mSleeping(false), mTimeSinceLastInput(0), mScreenSaver(NULL), mRenderScreenSaver(false), mInfoPopup(NULL) { mHelp = new HelpComponent(this); mBackgroundOverlay = new ImageComponent(this); mPassKeyListener = new PassKeyListener; } Window::~Window() { delete mBackgroundOverlay; // delete all our GUIs while(peekGui()) delete peekGui(); delete mHelp; } void Window::pushGui(GuiComponent* gui) { if (mGuiStack.size() > 0) { auto& top = mGuiStack.back(); top->topWindow(false); } mGuiStack.push_back(gui); gui->updateHelpPrompts(); } void Window::removeGui(GuiComponent* gui) { for(auto i = mGuiStack.cbegin(); i != mGuiStack.cend(); i++) { if(*i == gui) { i = mGuiStack.erase(i); if(i == mGuiStack.cend() && mGuiStack.size()) // we just popped the stack and the stack is not empty { mGuiStack.back()->updateHelpPrompts(); mGuiStack.back()->topWindow(true); } return; } } } GuiComponent* Window::peekGui() { if(mGuiStack.size() == 0) return NULL; return mGuiStack.back(); } bool Window::init(unsigned int width, unsigned int height) { if(!Renderer::init(width, height)) { LOG(LogError) << "Renderer failed to initialize!"; return false; } InputManager::getInstance()->init(); ResourceManager::getInstance()->reloadAll(); //keep a reference to the default fonts, so they don't keep getting destroyed/recreated if(mDefaultFonts.empty()) { mDefaultFonts.push_back(Font::get(FONT_SIZE_SMALL)); mDefaultFonts.push_back(Font::get(FONT_SIZE_MEDIUM)); mDefaultFonts.push_back(Font::get(FONT_SIZE_LARGE)); } mBackgroundOverlay->setImage(":/scroll_gradient.png"); mBackgroundOverlay->setResize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight()); // update our help because font sizes probably changed if(peekGui()) peekGui()->updateHelpPrompts(); return true; } void Window::deinit() { // Hide all GUI elements on uninitialisation - this disable for(auto i = mGuiStack.cbegin(); i != mGuiStack.cend(); i++) { (*i)->onHide(); } InputManager::getInstance()->deinit(); ResourceManager::getInstance()->unloadAll(); Renderer::deinit(); } void Window::textInput(const char* text) { if(peekGui()) peekGui()->textInput(text); } void Window::input(InputConfig* config, Input input) { if (mScreenSaver) { if(mScreenSaver->isScreenSaverActive() && Settings::getInstance()->getBool("ScreenSaverControls") && (Settings::getInstance()->getString("ScreenSaverBehavior") == "random video")) { if(mScreenSaver->getCurrentGame() != NULL && (config->isMappedTo("right", input) || config->isMappedTo("start", input) || config->isMappedTo("select", input))) { if(config->isMappedTo("right", input) || config->isMappedTo("select", input)) { if (input.value != 0) { // handle screensaver control mScreenSaver->nextVideo(); } return; } else if(config->isMappedTo("start", input) && input.value != 0) { // launch game! cancelScreenSaver(); mScreenSaver->launchGame(); // to force handling the wake up process mSleeping = true; } } /*else if(input.value != 0) { return; }*/ } } if(mSleeping) { // wake up mTimeSinceLastInput = 0; cancelScreenSaver(); mSleeping = false; onWake(); return; } mTimeSinceLastInput = 0; cancelScreenSaver(); if(config->getDeviceId() == DEVICE_KEYBOARD && input.value && input.id == SDLK_g && SDL_GetModState() & KMOD_LCTRL && Settings::getInstance()->getBool("Debug")) { // toggle debug grid with Ctrl-G Settings::getInstance()->setBool("DebugGrid", !Settings::getInstance()->getBool("DebugGrid")); } else if(config->getDeviceId() == DEVICE_KEYBOARD && input.value && input.id == SDLK_t && SDL_GetModState() & KMOD_LCTRL && Settings::getInstance()->getBool("Debug")) { // toggle TextComponent debug view with Ctrl-T Settings::getInstance()->setBool("DebugText", !Settings::getInstance()->getBool("DebugText")); } else { if (!mPassKeyListener->isUIModeChanged(config, input, this) && // check if UI mode has changed due to passphrase completion peekGui()) { this->peekGui()->input(config, input); // this is where the majority of inputs will be consumed: the GuiComponent Stack } } } void Window::update(int deltaTime) { if(mNormalizeNextUpdate) { mNormalizeNextUpdate = false; if(deltaTime > mAverageDeltaTime) deltaTime = mAverageDeltaTime; } mFrameTimeElapsed += deltaTime; mFrameCountElapsed++; if(mFrameTimeElapsed > 500) { mAverageDeltaTime = mFrameTimeElapsed / mFrameCountElapsed; if(Settings::getInstance()->getBool("DrawFramerate")) { std::stringstream ss; // fps ss << std::fixed << std::setprecision(1) << (1000.0f * (float)mFrameCountElapsed / (float)mFrameTimeElapsed) << "fps, "; ss << std::fixed << std::setprecision(2) << ((float)mFrameTimeElapsed / (float)mFrameCountElapsed) << "ms"; // vram float textureVramUsageMb = TextureResource::getTotalMemUsage() / 1000.0f / 1000.0f; float textureTotalUsageMb = TextureResource::getTotalTextureSize() / 1000.0f / 1000.0f; float fontVramUsageMb = Font::getTotalMemUsage() / 1000.0f / 1000.0f;; ss << "\nFont VRAM: " << fontVramUsageMb << " Tex VRAM: " << textureVramUsageMb << " Tex Max: " << textureTotalUsageMb; mFrameDataText = std::unique_ptr(mDefaultFonts.at(1)->buildTextCache(ss.str(), 50.f, 50.f, 0xFF00FFFF)); } mFrameTimeElapsed = 0; mFrameCountElapsed = 0; } mTimeSinceLastInput += deltaTime; if(peekGui()) peekGui()->update(deltaTime); // Update the screensaver if (mScreenSaver) mScreenSaver->update(deltaTime); } void Window::render() { Transform4x4f transform = Transform4x4f::Identity(); mRenderedHelpPrompts = false; // draw only bottom and top of GuiStack (if they are different) if(mGuiStack.size()) { auto& bottom = mGuiStack.front(); auto& top = mGuiStack.back(); bottom->render(transform); if(bottom != top) { mBackgroundOverlay->render(transform); top->render(transform); } } if(!mRenderedHelpPrompts) mHelp->render(transform); if(Settings::getInstance()->getBool("DrawFramerate") && mFrameDataText) { Renderer::setMatrix(Transform4x4f::Identity()); mDefaultFonts.at(1)->renderTextCache(mFrameDataText.get()); } unsigned int screensaverTime = (unsigned int)Settings::getInstance()->getInt("ScreenSaverTime"); if(mTimeSinceLastInput >= screensaverTime && screensaverTime != 0) startScreenSaver(); // Always call the screensaver render function regardless of whether the screensaver is active // or not because it may perform a fade on transition renderScreenSaver(); if(!mRenderScreenSaver && mInfoPopup) { mInfoPopup->render(transform); } if(mTimeSinceLastInput >= screensaverTime && screensaverTime != 0) { if (!isProcessing() && mAllowSleep && (!mScreenSaver || mScreenSaver->allowSleep())) { // go to sleep mSleeping = true; onSleep(); } } } void Window::normalizeNextUpdate() { mNormalizeNextUpdate = true; } bool Window::getAllowSleep() { return mAllowSleep; } void Window::setAllowSleep(bool sleep) { mAllowSleep = sleep; } void Window::renderLoadingScreen() { Transform4x4f trans = Transform4x4f::Identity(); Renderer::setMatrix(trans); Renderer::drawRect(0, 0, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0x000000FF); ImageComponent splash(this, true); splash.setResize(Renderer::getScreenWidth() * 0.6f, 0.0f); splash.setImage(":/splash.svg"); splash.setPosition((Renderer::getScreenWidth() - splash.getSize().x()) / 2, (Renderer::getScreenHeight() - splash.getSize().y()) / 2 * 0.6f); splash.render(trans); auto& font = mDefaultFonts.at(1); TextCache* cache = font->buildTextCache("LOADING...", 0, 0, 0x656565FF); trans = trans.translate(Vector3f(round((Renderer::getScreenWidth() - cache->metrics.size.x()) / 2.0f), round(Renderer::getScreenHeight() * 0.835f), 0.0f)); Renderer::setMatrix(trans); font->renderTextCache(cache); delete cache; Renderer::swapBuffers(); } void Window::renderHelpPromptsEarly() { mHelp->render(Transform4x4f::Identity()); mRenderedHelpPrompts = true; } void Window::setHelpPrompts(const std::vector& prompts, const HelpStyle& style) { mHelp->clearPrompts(); mHelp->setStyle(style); std::vector addPrompts; std::map inputSeenMap; std::map mappedToSeenMap; for(auto it = prompts.cbegin(); it != prompts.cend(); it++) { // only add it if the same icon hasn't already been added if(inputSeenMap.emplace(it->first, true).second) { // this symbol hasn't been seen yet, what about the action name? auto mappedTo = mappedToSeenMap.find(it->second); if(mappedTo != mappedToSeenMap.cend()) { // yes, it has! // can we combine? (dpad only) if((it->first == "up/down" && addPrompts.at(mappedTo->second).first != "left/right") || (it->first == "left/right" && addPrompts.at(mappedTo->second).first != "up/down")) { // yes! addPrompts.at(mappedTo->second).first = "up/down/left/right"; // don't need to add this to addPrompts since we just merged }else{ // no, we can't combine! addPrompts.push_back(*it); } }else{ // no, it hasn't! mappedToSeenMap.emplace(it->second, addPrompts.size()); addPrompts.push_back(*it); } } } // sort prompts so it goes [dpad_all] [dpad_u/d] [dpad_l/r] [a/b/x/y/l/r] [start/select] std::sort(addPrompts.begin(), addPrompts.end(), [](const HelpPrompt& a, const HelpPrompt& b) -> bool { static const char* map[] = { "up/down/left/right", "up/down", "left/right", "a", "b", "x", "y", "l", "r", "start", "select", NULL }; int i = 0; int aVal = 0; int bVal = 0; while(map[i] != NULL) { if(a.first == map[i]) aVal = i; if(b.first == map[i]) bVal = i; i++; } return aVal > bVal; }); mHelp->setPrompts(addPrompts); } void Window::onSleep() { } void Window::onWake() { } bool Window::isProcessing() { return count_if(mGuiStack.cbegin(), mGuiStack.cend(), [](GuiComponent* c) { return c->isProcessing(); }) > 0; } void Window::startScreenSaver() { if (mScreenSaver && !mRenderScreenSaver) { // Tell the GUI components the screensaver is starting for(auto i = mGuiStack.cbegin(); i != mGuiStack.cend(); i++) (*i)->onScreenSaverActivate(); mScreenSaver->startScreenSaver(); mRenderScreenSaver = true; } } void Window::cancelScreenSaver() { if (mScreenSaver && mRenderScreenSaver) { mScreenSaver->stopScreenSaver(); mRenderScreenSaver = false; // Tell the GUI components the screensaver has stopped for(auto i = mGuiStack.cbegin(); i != mGuiStack.cend(); i++) (*i)->onScreenSaverDeactivate(); } } void Window::renderScreenSaver() { if (mScreenSaver) mScreenSaver->renderScreenSaver(); } bool Window::PassKeyListener::isUIModeChanged(InputConfig * config, Input input, Window* window) { // This function reads the current input to listen for the passkey // sequence to unlock the UI mode. The progress is saved in mPassKeyCounter // supported sequence-inputs: u (up), d (down), l (left), r (right), a, b, x, y // default passkeyseq = "uuddlrlrba", as defined in the setting 'UIMode_passkey'. if ((Settings::getInstance()->getString("UIMode") == "Full") || (!input.value)) { return false; // Already unlocked, or no keydown, nothing to do here. } bool foundMatch = false; for (auto valstring : mInputVals) { if (config->isMappedTo(valstring, input) && (this->mPassKeySequence[this->mPassKeyCounter] == valstring[0])) { this->mPassKeyCounter ++; foundMatch = true; } } if (!foundMatch) { this->mPassKeyCounter = 0; // current input is incorrect, reset counter } if (this->mPassKeyCounter == (this->mPassKeySequence.length())) { // When we have reached the end of the list, trigger UI_mode unlock LOG(LogDebug) << " Window::PassKeyListener::isUIModeChanged(): Passkey sequence completed, switching UIMode to full"; Settings::getInstance()->setString("UIMode", "Full"); Settings::getInstance()->saveFile(); this->mPassKeyCounter = 0; return true; } return false; }