mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-23 14:45:38 +00:00
482 lines
12 KiB
C++
482 lines
12 KiB
C++
#include "Window.h"
|
|
#include <iostream>
|
|
#include "Renderer.h"
|
|
#include "AudioManager.h"
|
|
#include "Log.h"
|
|
#include "Settings.h"
|
|
#include <algorithm>
|
|
#include <iomanip>
|
|
#include "components/HelpComponent.h"
|
|
#include "components/ImageComponent.h"
|
|
|
|
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.begin(); i != mGuiStack.end(); i++)
|
|
{
|
|
if(*i == gui)
|
|
{
|
|
i = mGuiStack.erase(i);
|
|
|
|
if(i == mGuiStack.end() && 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.begin(); i != mGuiStack.end(); 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<TextCache>(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<HelpPrompt>& prompts, const HelpStyle& style)
|
|
{
|
|
mHelp->clearPrompts();
|
|
mHelp->setStyle(style);
|
|
|
|
std::vector<HelpPrompt> addPrompts;
|
|
|
|
std::map<std::string, bool> inputSeenMap;
|
|
std::map<std::string, int> mappedToSeenMap;
|
|
for(auto it = prompts.begin(); it != prompts.end(); 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.end())
|
|
{
|
|
// 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.begin(), mGuiStack.end(), [](GuiComponent* c) { return c->isProcessing(); }) > 0;
|
|
}
|
|
|
|
void Window::startScreenSaver()
|
|
{
|
|
if (mScreenSaver && !mRenderScreenSaver)
|
|
{
|
|
// Tell the GUI components the screensaver is starting
|
|
for(auto i = mGuiStack.begin(); i != mGuiStack.end(); 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.begin(); i != mGuiStack.end(); 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;
|
|
}
|