mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2025-04-10 19:15:13 +00:00

You don't need to dynamic_cast everywhere to check things anymore. Folders can have metadata now (currently not set up). Metadata is now a public member variable instead of a function that returns a pointer to make actually using const possible.
506 lines
14 KiB
C++
506 lines
14 KiB
C++
#include "GuiGameList.h"
|
|
#include "../InputManager.h"
|
|
#include <iostream>
|
|
#include "GuiMenu.h"
|
|
#include "GuiFastSelect.h"
|
|
#include <boost/filesystem.hpp>
|
|
#include "../Log.h"
|
|
#include "../Settings.h"
|
|
|
|
#include "GuiMetaDataEd.h"
|
|
#include "GuiScraperStart.h"
|
|
|
|
Eigen::Vector3f GuiGameList::getImagePos()
|
|
{
|
|
return Eigen::Vector3f(Renderer::getScreenWidth() * mTheme->getFloat("gameImageOffsetX"), Renderer::getScreenHeight() * mTheme->getFloat("gameImageOffsetY"), 0.0f);
|
|
}
|
|
|
|
bool GuiGameList::isDetailed() const
|
|
{
|
|
if(!mFolder)
|
|
return false;
|
|
|
|
//return true if any game has an image specified
|
|
for(auto it = mFolder->getChildren().begin(); it != mFolder->getChildren().end(); it++)
|
|
{
|
|
if(!(*it)->getThumbnailPath().empty())
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
GuiGameList::GuiGameList(Window* window) : GuiComponent(window),
|
|
mTheme(new ThemeComponent(mWindow)),
|
|
mList(window, 0.0f, 0.0f, Font::get(FONT_SIZE_MEDIUM)),
|
|
mScreenshot(window),
|
|
mDescription(window),
|
|
mRating(window),
|
|
mReleaseDateLabel(window),
|
|
mReleaseDate(window),
|
|
mDescContainer(window),
|
|
mTransitionImage(window, 0.0f, 0.0f, "", (float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight(), true),
|
|
mHeaderText(mWindow),
|
|
mLockInput(false),
|
|
mEffectFunc(NULL), mEffectTime(0), mGameLaunchEffectLength(700)
|
|
{
|
|
mImageAnimation.addChild(&mScreenshot);
|
|
mDescContainer.addChild(&mReleaseDateLabel);
|
|
mDescContainer.addChild(&mReleaseDate);
|
|
mDescContainer.addChild(&mRating);
|
|
mDescContainer.addChild(&mDescription);
|
|
|
|
//scale delay with screen width (higher width = more text per line)
|
|
//the scroll speed is automatically scaled by component size
|
|
mDescContainer.setAutoScroll((int)(1500 + (Renderer::getScreenWidth() * 0.5)), 0.025f);
|
|
|
|
mTransitionImage.setPosition((float)Renderer::getScreenWidth(), 0);
|
|
mTransitionImage.setOrigin(0, 0);
|
|
|
|
mHeaderText.setColor(0xFF0000FF);
|
|
mHeaderText.setFont(Font::get(FONT_SIZE_LARGE));
|
|
mHeaderText.setPosition(0, 1);
|
|
mHeaderText.setSize((float)Renderer::getScreenWidth(), 0);
|
|
mHeaderText.setCentered(true);
|
|
|
|
addChild(mTheme);
|
|
addChild(&mHeaderText);
|
|
addChild(&mScreenshot);
|
|
addChild(&mDescContainer);
|
|
addChild(&mList);
|
|
addChild(&mTransitionImage);
|
|
|
|
mTransitionAnimation.addChild(this);
|
|
|
|
setSystemId(0);
|
|
}
|
|
|
|
GuiGameList::~GuiGameList()
|
|
{
|
|
delete mTheme;
|
|
}
|
|
|
|
void GuiGameList::setSystemId(int id)
|
|
{
|
|
if(SystemData::sSystemVector.size() == 0)
|
|
{
|
|
LOG(LogError) << "Error - no systems found!";
|
|
return;
|
|
}
|
|
|
|
//make sure the id is within range
|
|
if(id >= (int)SystemData::sSystemVector.size())
|
|
id -= SystemData::sSystemVector.size();
|
|
if(id < 0)
|
|
id += SystemData::sSystemVector.size();
|
|
|
|
mSystemId = id;
|
|
mSystem = SystemData::sSystemVector.at(mSystemId);
|
|
|
|
//clear the folder stack
|
|
while(mFolderStack.size()){ mFolderStack.pop(); }
|
|
|
|
mFolder = mSystem->getRootFolder();
|
|
|
|
updateTheme();
|
|
updateList();
|
|
updateDetailData();
|
|
mWindow->normalizeNextUpdate(); //image loading can be slow
|
|
}
|
|
|
|
void GuiGameList::render(const Eigen::Affine3f& parentTrans)
|
|
{
|
|
Eigen::Affine3f trans = parentTrans * getTransform();
|
|
renderChildren(trans);
|
|
}
|
|
|
|
bool GuiGameList::input(InputConfig* config, Input input)
|
|
{
|
|
if(mLockInput)
|
|
return false;
|
|
|
|
mList.input(config, input);
|
|
|
|
if(input.id == SDLK_F3)
|
|
{
|
|
FileData* game = mList.getSelectedObject();
|
|
if(game->getType() == GAME)
|
|
{
|
|
FileData* root = mSystem->getRootFolder();
|
|
ScraperSearchParams searchParams;
|
|
searchParams.game = game;
|
|
searchParams.system = mSystem;
|
|
mWindow->pushGui(new GuiMetaDataEd(mWindow, &game->metadata, game->metadata.getMDD(), searchParams, game->getPath().stem().string(),
|
|
[&] { updateDetailData(); },
|
|
[game, root, this] {
|
|
boost::filesystem::remove(game->getPath());
|
|
root->removeChild(game);
|
|
updateList();
|
|
}));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if(input.id == SDLK_F5)
|
|
{
|
|
mWindow->pushGui(new GuiScraperStart(mWindow));
|
|
return true;
|
|
}
|
|
|
|
if(config->isMappedTo("a", input) && input.value != 0)
|
|
{
|
|
//play select sound
|
|
mTheme->getSound("menuSelect")->play();
|
|
|
|
FileData* file = mList.getSelectedObject();
|
|
if(file->getType() == FOLDER) //if you selected a folder, add this directory to the stack, and use the selected one
|
|
{
|
|
mFolderStack.push(mFolder);
|
|
mFolder = file;
|
|
updateList();
|
|
updateDetailData();
|
|
return true;
|
|
}else{
|
|
mList.stopScrolling();
|
|
|
|
mEffectFunc = &GuiGameList::updateGameLaunchEffect;
|
|
mEffectTime = 0;
|
|
mGameLaunchEffectLength = (int)mTheme->getSound("menuSelect")->getLengthMS();
|
|
if(mGameLaunchEffectLength < 800)
|
|
mGameLaunchEffectLength = 800;
|
|
|
|
mLockInput = true;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//if there's something on the directory stack, return to it
|
|
if(config->isMappedTo("b", input) && input.value != 0 && mFolderStack.size())
|
|
{
|
|
mFolder = mFolderStack.top();
|
|
mFolderStack.pop();
|
|
updateList();
|
|
updateDetailData();
|
|
|
|
//play the back sound
|
|
mTheme->getSound("menuBack")->play();
|
|
|
|
return true;
|
|
}
|
|
|
|
//only allow switching systems if more than one exists (otherwise it'll reset your position when you switch and it's annoying)
|
|
if(SystemData::sSystemVector.size() > 1 && input.value != 0)
|
|
{
|
|
if(config->isMappedTo("right", input))
|
|
{
|
|
setSystemId(mSystemId + 1);
|
|
doTransition(-1);
|
|
return true;
|
|
}
|
|
if(config->isMappedTo("left", input))
|
|
{
|
|
setSystemId(mSystemId - 1);
|
|
doTransition(1);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//open the "start menu"
|
|
if(config->isMappedTo("menu", input) && input.value != 0)
|
|
{
|
|
mWindow->pushGui(new GuiMenu(mWindow, this));
|
|
return true;
|
|
}
|
|
|
|
//open the fast select menu
|
|
if(config->isMappedTo("select", input) && input.value != 0)
|
|
{
|
|
mWindow->pushGui(new GuiFastSelect(mWindow, this, &mList, mList.getSelectedObject()->getName()[0], mTheme));
|
|
return true;
|
|
}
|
|
|
|
if(isDetailed())
|
|
{
|
|
if(config->isMappedTo("up", input) || config->isMappedTo("down", input) || config->isMappedTo("pageup", input) || config->isMappedTo("pagedown", input))
|
|
{
|
|
if(input.value == 0)
|
|
updateDetailData();
|
|
else
|
|
hideDetailData();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void GuiGameList::sort(const FileData::SortType& type)
|
|
{
|
|
//resort list and update it
|
|
mFolder->sort(type);
|
|
updateList();
|
|
updateDetailData();
|
|
}
|
|
|
|
void GuiGameList::updateList()
|
|
{
|
|
mList.clear();
|
|
|
|
for(auto it = mFolder->getChildren().begin(); it != mFolder->getChildren().end(); it++)
|
|
{
|
|
if((*it)->getType() == FOLDER)
|
|
mList.addObject((*it)->getName(), *it, mTheme->getColor("secondary"));
|
|
else
|
|
mList.addObject((*it)->getName(), *it, mTheme->getColor("primary"));
|
|
}
|
|
}
|
|
|
|
std::string GuiGameList::getThemeFile()
|
|
{
|
|
std::string themePath;
|
|
|
|
themePath = getHomePath();
|
|
themePath += "/.emulationstation/" + mSystem->getName() + "/theme.xml";
|
|
if(boost::filesystem::exists(themePath))
|
|
return themePath;
|
|
|
|
themePath = mSystem->getStartPath() + "/theme.xml";
|
|
if(boost::filesystem::exists(themePath))
|
|
return themePath;
|
|
|
|
themePath = getHomePath();
|
|
themePath += "/.emulationstation/es_theme.xml";
|
|
if(boost::filesystem::exists(themePath))
|
|
return themePath;
|
|
|
|
return "";
|
|
}
|
|
|
|
void GuiGameList::updateTheme()
|
|
{
|
|
mTheme->readXML(getThemeFile(), isDetailed());
|
|
|
|
mList.setSelectorColor(mTheme->getColor("selector"));
|
|
mList.setSelectedTextColor(mTheme->getColor("selected"));
|
|
mList.setScrollSound(mTheme->getSound("menuScroll"));
|
|
|
|
mList.setFont(mTheme->getListFont());
|
|
mList.setPosition(0.0f, Font::get(FONT_SIZE_LARGE)->getHeight() + 2.0f);
|
|
|
|
if(!mTheme->getBool("hideHeader"))
|
|
{
|
|
mHeaderText.setText(mSystem->getFullName());
|
|
}else{
|
|
mHeaderText.setText("");
|
|
}
|
|
|
|
if(isDetailed())
|
|
{
|
|
mList.setCentered(mTheme->getBool("listCentered"));
|
|
|
|
mList.setPosition(mTheme->getFloat("listOffsetX") * Renderer::getScreenWidth(), mList.getPosition().y());
|
|
mList.setTextOffsetX((int)(mTheme->getFloat("listTextOffsetX") * Renderer::getScreenWidth()));
|
|
|
|
mScreenshot.setPosition(mTheme->getFloat("gameImageOffsetX") * Renderer::getScreenWidth(), mTheme->getFloat("gameImageOffsetY") * Renderer::getScreenHeight());
|
|
mScreenshot.setOrigin(mTheme->getFloat("gameImageOriginX"), mTheme->getFloat("gameImageOriginY"));
|
|
mScreenshot.setResize(mTheme->getFloat("gameImageWidth") * Renderer::getScreenWidth(), mTheme->getFloat("gameImageHeight") * Renderer::getScreenHeight(), false);
|
|
|
|
mReleaseDateLabel.setColor(mTheme->getColor("description"));
|
|
mReleaseDateLabel.setFont(mTheme->getDescriptionFont());
|
|
mReleaseDate.setColor(mTheme->getColor("description"));
|
|
mReleaseDate.setFont(mTheme->getDescriptionFont());
|
|
|
|
mDescription.setColor(mTheme->getColor("description"));
|
|
mDescription.setFont(mTheme->getDescriptionFont());
|
|
}else{
|
|
mList.setCentered(true);
|
|
mList.setPosition(0, mList.getPosition().y());
|
|
mList.setTextOffsetX(0);
|
|
}
|
|
}
|
|
|
|
void GuiGameList::updateDetailData()
|
|
{
|
|
if(!isDetailed() || !mList.getSelectedObject() || mList.getSelectedObject()->getType() == FOLDER)
|
|
{
|
|
hideDetailData();
|
|
}else{
|
|
if(mDescContainer.getParent() != this)
|
|
addChild(&mDescContainer);
|
|
|
|
FileData* game = mList.getSelectedObject();
|
|
|
|
//set image to either "not found" image or metadata image
|
|
if(!boost::filesystem::exists(game->metadata.get("image")))
|
|
{
|
|
//image doesn't exist
|
|
if(mTheme->getString("imageNotFoundPath").empty())
|
|
{
|
|
//"not found" image doesn't exist
|
|
mScreenshot.setImage("");
|
|
mScreenshot.setSize(0, 0); //clear old size
|
|
}else{
|
|
mScreenshot.setImage(mTheme->getString("imageNotFoundPath"));
|
|
}
|
|
}else{
|
|
mScreenshot.setImage(game->metadata.get("image"));
|
|
}
|
|
|
|
Eigen::Vector3f imgOffset = Eigen::Vector3f(Renderer::getScreenWidth() * 0.10f, 0, 0);
|
|
mScreenshot.setPosition(getImagePos() - imgOffset);
|
|
|
|
mImageAnimation.fadeIn(35);
|
|
mImageAnimation.move((int)imgOffset.x(), (int)imgOffset.y(), 20);
|
|
|
|
mDescContainer.setPosition(Eigen::Vector3f(Renderer::getScreenWidth() * 0.03f, getImagePos().y() + mScreenshot.getSize().y() + 12, 0));
|
|
mDescContainer.setSize(Eigen::Vector2f(Renderer::getScreenWidth() * (mTheme->getFloat("listOffsetX") - 0.03f), Renderer::getScreenHeight() - mDescContainer.getPosition().y()));
|
|
mDescContainer.setScrollPos(Eigen::Vector2d(0, 0));
|
|
mDescContainer.resetAutoScrollTimer();
|
|
|
|
const float colwidth = mDescContainer.getSize().x();
|
|
float ratingHeight = colwidth * 0.3f / 5.0f;
|
|
mRating.setSize(ratingHeight * 5.0f, ratingHeight);
|
|
|
|
mReleaseDateLabel.setPosition(0, 0);
|
|
mReleaseDateLabel.setText("Released: ");
|
|
mReleaseDate.setPosition(mReleaseDateLabel.getPosition().x() + mReleaseDateLabel.getSize().x(), mReleaseDateLabel.getPosition().y());
|
|
mReleaseDate.setValue(game->metadata.get("releasedate"));
|
|
|
|
mRating.setPosition(colwidth - mRating.getSize().x() - 12, 0);
|
|
mRating.setValue(game->metadata.get("rating"));
|
|
|
|
mDescription.setPosition(0, mRating.getSize().y());
|
|
mDescription.setSize(Eigen::Vector2f(Renderer::getScreenWidth() * (mTheme->getFloat("listOffsetX") - 0.03f), 0));
|
|
mDescription.setText(game->metadata.get("desc"));
|
|
}
|
|
}
|
|
|
|
void GuiGameList::hideDetailData()
|
|
{
|
|
if(mDescContainer.getParent() == this)
|
|
removeChild(&mDescContainer);
|
|
|
|
mImageAnimation.fadeOut(35);
|
|
}
|
|
|
|
GuiGameList* GuiGameList::create(Window* window)
|
|
{
|
|
GuiGameList* list = new GuiGameList(window);
|
|
window->pushGui(list);
|
|
return list;
|
|
}
|
|
|
|
void GuiGameList::update(int deltaTime)
|
|
{
|
|
mTransitionAnimation.update(deltaTime);
|
|
mImageAnimation.update(deltaTime);
|
|
|
|
if(mEffectFunc != NULL)
|
|
{
|
|
mEffectTime += deltaTime;
|
|
(this->*mEffectFunc)(mEffectTime);
|
|
}
|
|
|
|
GuiComponent::update(deltaTime);
|
|
}
|
|
|
|
void GuiGameList::doTransition(int dir)
|
|
{
|
|
mTransitionImage.copyScreen();
|
|
mTransitionImage.setOpacity(255);
|
|
|
|
//put the image of what's currently onscreen at what will be (in screen coords) 0, 0
|
|
mTransitionImage.setPosition((float)Renderer::getScreenWidth() * dir, 0);
|
|
|
|
//move the entire thing offscreen so we'll move into place
|
|
setPosition((float)Renderer::getScreenWidth() * -dir, mPosition[1]);
|
|
|
|
mTransitionAnimation.move(Renderer::getScreenWidth() * dir, 0, 50);
|
|
}
|
|
|
|
float lerpFloat(const float& start, const float& end, float t)
|
|
{
|
|
if(t <= 0)
|
|
return start;
|
|
if(t >= 1)
|
|
return end;
|
|
|
|
return (start * (1 - t) + end * t);
|
|
}
|
|
|
|
Eigen::Vector2f lerpVector2f(const Eigen::Vector2f& start, const Eigen::Vector2f& end, float t)
|
|
{
|
|
if(t <= 0)
|
|
return start;
|
|
if(t >= 1)
|
|
return end;
|
|
|
|
return (start * (1 - t) + end * t);
|
|
}
|
|
|
|
float clamp(float min, float max, float val)
|
|
{
|
|
if(val < min)
|
|
val = min;
|
|
else if(val > max)
|
|
val = max;
|
|
|
|
return val;
|
|
}
|
|
|
|
//http://en.wikipedia.org/wiki/Smoothstep
|
|
float smoothStep(float edge0, float edge1, float x)
|
|
{
|
|
// Scale, and clamp x to 0..1 range
|
|
x = clamp(0, 1, (x - edge0)/(edge1 - edge0));
|
|
|
|
// Evaluate polynomial
|
|
return x*x*x*(x*(x*6 - 15) + 10);
|
|
}
|
|
|
|
void GuiGameList::updateGameLaunchEffect(int t)
|
|
{
|
|
const int endTime = mGameLaunchEffectLength;
|
|
|
|
const int zoomTime = endTime;
|
|
const int centerTime = endTime - 50;
|
|
|
|
const int fadeDelay = endTime - 600;
|
|
const int fadeTime = endTime - fadeDelay - 100;
|
|
|
|
Eigen::Vector2f imageCenter(mScreenshot.getCenter());
|
|
if(!isDetailed())
|
|
{
|
|
imageCenter << mList.getPosition().x() + mList.getSize().x() / 2, mList.getPosition().y() + mList.getSize().y() / 2;
|
|
}
|
|
|
|
const Eigen::Vector2f centerStart(Renderer::getScreenWidth() / 2, Renderer::getScreenHeight() / 2);
|
|
|
|
//remember to clamp or zoom factor will be incorrect with a negative t because squared
|
|
const float tNormalized = clamp(0, 1, (float)t / endTime);
|
|
|
|
mWindow->setCenterPoint(lerpVector2f(centerStart, imageCenter, smoothStep(0.0, 1.0, tNormalized)));
|
|
mWindow->setZoomFactor(lerpFloat(1.0f, 3.0f, tNormalized*tNormalized));
|
|
mWindow->setFadePercent(lerpFloat(0.0f, 1.0f, (float)(t - fadeDelay) / fadeTime));
|
|
|
|
if(t > endTime)
|
|
{
|
|
//effect done
|
|
mTransitionImage.setImage(""); //fixes "tried to bind uninitialized texture!" since copyScreen()'d textures don't reinit
|
|
mSystem->launchGame(mWindow, mList.getSelectedObject());
|
|
mEffectFunc = &GuiGameList::updateGameReturnEffect;
|
|
mEffectTime = 0;
|
|
mGameLaunchEffectLength = 700;
|
|
mLockInput = false;
|
|
}
|
|
}
|
|
|
|
void GuiGameList::updateGameReturnEffect(int t)
|
|
{
|
|
updateGameLaunchEffect(mGameLaunchEffectLength - t);
|
|
|
|
if(t >= mGameLaunchEffectLength)
|
|
mEffectFunc = NULL;
|
|
}
|