ES-DE/src/components/GuiGameList.cpp
Aloshi 3a3471cfe8 Combined FolderData and GameData into one class, FileData.
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.
2013-11-05 19:41:49 -06:00

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;
}