Commit 61eafba1 authored by digitalLumberjack's avatar digitalLumberjack

added favorites

parent 3b28e2ab
......@@ -52,6 +52,7 @@ Some metadata is also marked as "statistic" - these are kept track of by ES and
* `players` - integer, the number of players the game supports.
* `playcount` - statistic, integer, the number of times this game has been played
* `lastplayed` - statistic, datetime, the last date and time this game was played.
* `favorite` - string, yes / no is this a favorite.
#### `<folder>`
......@@ -85,3 +86,4 @@ Things to be Aware Of
* If at least one game in a system has an image specified, ES will use the detailed view for that system (which displays metadata alongside the game list).
* If you want to write your own scraper, the built-in scraping system is actually pretty extendable if you can get past the ugly function declarations and your instinctual fear of C++. Check out `src/scrapers/GamesDBScraper.cpp` for an example (it's less than a hundred lines of actual code). An offline scraper is also possible (though you'll have to subclass `ScraperRequest`). I hope to write a more complete guide on how to do this in the future.
......@@ -259,6 +259,9 @@ Which is equivalent to:
<text name="md_lbl_playcount">
<color>48474D</color>
</text>
<text name="md_lbl_favorite">
<color>48474D</color>
</text>
</view>
</theme>
```
......@@ -307,6 +310,7 @@ Reference
* `text name="md_lbl_players"` - ALL
* `text name="md_lbl_lastplayed"` - ALL
* `text name="md_lbl_playcount"` - ALL
* `text name="md_lbl_favorite"` - ALL
* Values
* All values will follow to the right of their labels if a position isn't specified.
......@@ -329,6 +333,8 @@ Reference
- The "lastplayed" metadata. Displayed as a string representing the time relative to "now" (e.g. "3 hours ago").
* `text name="md_playcount"` - ALL
- The "playcount" metadata (number of times the game has been played).
* `text name="md_favorite"` - ALL
- The "favorite" metadata (is this game a favorite).
* `text name="md_description"` - POSITION | SIZE | FONT_PATH | FONT_SIZE | COLOR
- Text is the "desc" metadata. If no `pos`/`size` is specified, will move and resize to fit under the lowest label and reach to the bottom of the screen.
......
......@@ -97,6 +97,22 @@ std::vector<FileData*> FileData::getFilesRecursive(unsigned int typeMask) const
return out;
}
std::vector<FileData*> FileData::getFavoritesRecursive(unsigned int typeMask) const
{
std::vector<FileData*> out;
std::vector<FileData*> files = getFilesRecursive(typeMask);
for (auto it = files.begin(); it != files.end(); it++)
{
if ((*it)->metadata.get("favorite").compare("yes") == 0)
{
out.push_back(*it);
}
}
return out;
}
void FileData::addChild(FileData* file)
{
assert(mType == FOLDER);
......
......@@ -45,6 +45,7 @@ public:
virtual const std::string& getThumbnailPath() const;
std::vector<FileData*> getFilesRecursive(unsigned int typeMask) const;
std::vector<FileData*> getFavoritesRecursive(unsigned int typeMask) const;
void addChild(FileData* file); // Error if mType != FOLDER
void removeChild(FileData* file); //Error if mType != FOLDER
......
......@@ -18,7 +18,8 @@ MetaDataDecl gameDecls[] = {
{"genre", MD_STRING, "unknown", false, "genre", "enter game genre"},
{"players", MD_INT, "1", false, "players", "enter number of players"},
{"playcount", MD_INT, "0", true, "play count", "enter number of times played"},
{"lastplayed", MD_TIME, "0", true, "last played", "enter last played date"}
{"lastplayed", MD_TIME, "0", true, "last played", "enter last played date"},
{"favorite", MD_STRING, "no", false, "favorite", "enter favorite"}
};
const std::vector<MetaDataDecl> gameMDD(gameDecls, gameDecls + sizeof(gameDecls) / sizeof(gameDecls[0]));
......
......@@ -438,6 +438,11 @@ unsigned int SystemData::getGameCount() const
return mRootFolder->getFilesRecursive(GAME).size();
}
unsigned int SystemData::getFavoritesCount() const
{
return mRootFolder->getFavoritesRecursive(GAME).size();
}
void SystemData::loadTheme()
{
mTheme = std::make_shared<ThemeData>();
......@@ -450,6 +455,7 @@ void SystemData::loadTheme()
try
{
mTheme->loadFile(path);
mHasFavorites = mTheme->getHasFavoritesInTheme();
} catch(ThemeException& e)
{
LOG(LogError) << e.what();
......
......@@ -21,6 +21,7 @@ public:
inline const std::string& getStartPath() const { return mStartPath; }
inline const std::vector<std::string>& getExtensions() const { return mSearchExtensions; }
inline const std::string& getThemeFolder() const { return mThemeFolder; }
inline bool getHasFavorites() const { return mHasFavorites; }
inline const std::vector<PlatformIds::PlatformId>& getPlatformIds() const { return mPlatformIds; }
inline bool hasPlatformId(PlatformIds::PlatformId id) { return std::find(mPlatformIds.begin(), mPlatformIds.end(), id) != mPlatformIds.end(); }
......@@ -32,6 +33,7 @@ public:
std::string getThemePath() const;
unsigned int getGameCount() const;
unsigned int getFavoritesCount() const;
void launchGame(Window* window, FileData* game);
......@@ -74,6 +76,8 @@ private:
std::string mThemeFolder;
std::shared_ptr<ThemeData> mTheme;
bool mHasFavorites;
void populateFolder(FileData* folder);
FileData* mRootFolder;
......
......@@ -13,9 +13,8 @@ public:
bool input(InputConfig* config, Input input);
void update(int deltaTime);
virtual void setScrollDir(int dir);
private:
void setScrollDir(int dir);
void scroll();
void updateGameListCursor();
void updateGameListSort();
......
#include "GuiGamelistOptions.h"
#include "GuiMetaDataEd.h"
#include "Settings.h"
#include "views/gamelist/IGameListView.h"
#include "views/ViewController.h"
#include "components/SwitchComponent.h"
#include "guis/GuiSettings.h"
GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : GuiComponent(window),
mSystem(system),
......@@ -45,6 +48,11 @@ GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : Gui
mMenu.addWithLabel("SORT GAMES BY", mListSort);
auto favorite_only = std::make_shared<SwitchComponent>(mWindow);
favorite_only->setState(Settings::getInstance()->getBool("FavoritesOnly"));
mMenu.addWithLabel("FAVORITES ONLY", favorite_only);
addSaveFunc([favorite_only] { Settings::getInstance()->setBool("FavoritesOnly", favorite_only->getState()); });
// edit game metadata
row.elements.clear();
row.addElement(std::make_shared<TextComponent>(mWindow, "EDIT THIS GAME'S METADATA", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
......@@ -55,6 +63,8 @@ GuiGamelistOptions::GuiGamelistOptions(Window* window, SystemData* system) : Gui
// center the menu
setSize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
mMenu.setPosition((mSize.x() - mMenu.getSize().x()) / 2, (mSize.y() - mMenu.getSize().y()) / 2);
mFavoriteState = Settings::getInstance()->getBool("FavoritesOnly");
}
GuiGamelistOptions::~GuiGamelistOptions()
......@@ -65,6 +75,12 @@ GuiGamelistOptions::~GuiGamelistOptions()
// notify that the root folder was sorted
getGamelist()->onFileChanged(root, FILE_SORTED);
if (Settings::getInstance()->getBool("FavoritesOnly") != mFavoriteState)
{
ViewController::get()->setAllInvalidGamesList(getGamelist()->getCursor()->getSystem());
ViewController::get()->reloadGameListView(getGamelist()->getCursor()->getSystem());
}
}
void GuiGamelistOptions::openMetaDataEd()
......@@ -122,6 +138,7 @@ bool GuiGamelistOptions::input(InputConfig* config, Input input)
{
if((config->isMappedTo("b", input) || config->isMappedTo("select", input)) && input.value)
{
save();
delete this;
return true;
}
......@@ -140,3 +157,14 @@ IGameListView* GuiGamelistOptions::getGamelist()
{
return ViewController::get()->getGameListView(mSystem).get();
}
void GuiGamelistOptions::save()
{
if (!mSaveFuncs.size())
return;
for (auto it = mSaveFuncs.begin(); it != mSaveFuncs.end(); it++)
(*it)();
Settings::getInstance()->saveFile();
}
#include "GuiComponent.h"
#include "components/MenuComponent.h"
#include "components/OptionListComponent.h"
#include "components/SwitchComponent.h"
#include "FileSorts.h"
class IGameListView;
......@@ -11,6 +12,9 @@ public:
GuiGamelistOptions(Window* window, SystemData* system);
virtual ~GuiGamelistOptions();
void save();
inline void addSaveFunc(const std::function<void()>& func) { mSaveFuncs.push_back(func); };
virtual bool input(InputConfig* config, Input input) override;
virtual std::vector<HelpPrompt> getHelpPrompts() override;
......@@ -20,11 +24,17 @@ private:
MenuComponent mMenu;
std::vector< std::function<void()> > mSaveFuncs;
typedef OptionListComponent<char> LetterList;
std::shared_ptr<LetterList> mJumpToLetterList;
typedef OptionListComponent<const FileData::SortType*> SortList;
std::shared_ptr<SortList> mListSort;
std::shared_ptr<SwitchComponent> mFavoriteOption;
bool mFavoriteState;
SystemData* mSystem;
IGameListView* getGamelist();
......
......@@ -182,30 +182,34 @@ void SystemView::onCursorChanged(const CursorState& state)
}, (int)(infoStartOpacity * 150));
unsigned int gameCount = getSelected()->getGameCount();
unsigned int favoritesCount = getSelected()->getFavoritesCount();
// also change the text after we've fully faded out
setAnimation(infoFadeOut, 0, [this, gameCount] {
setAnimation(infoFadeOut, 0, [this, gameCount, favoritesCount] {
std::stringstream ss;
// only display a game count if there are at least 2 games
if(gameCount > 1)
if (gameCount > 1)
{
ss << gameCount << boost::locale::gettext(" GAMES AVAILABLE");
}
else if (favoritesCount > 1)
{
ss << ", " << favoritesCount << boost::locale::gettext(" FAVORITES");
}
mSystemInfo.setText(ss.str());
mSystemInfo.setText(ss.str());
}, false, 1);
// only display a game count if there are at least 2 games
if(gameCount > 1)
Animation* infoFadeIn = new LambdaAnimation(
[this](float t)
{
Animation* infoFadeIn = new LambdaAnimation(
[this](float t)
{
mSystemInfo.setOpacity((unsigned char)(lerp<float>(0.f, 1.f, t) * 255));
}, 300);
mSystemInfo.setOpacity((unsigned char)(lerp<float>(0.f, 1.f, t) * 255));
}, 300);
// wait 600ms to fade in
setAnimation(infoFadeIn, 2000, nullptr, false, 2);
}
// wait ms to fade in
setAnimation(infoFadeIn, 800, nullptr, false, 2);
// no need to animate transition, we're not going anywhere (probably mEntries.size() == 1)
if(endPos == mCamOffset && endPos == mExtrasCamOffset)
......
......@@ -33,6 +33,7 @@ ViewController::ViewController(Window* window)
: GuiComponent(window), mCurrentView(nullptr), mCamera(Eigen::Affine3f::Identity()), mFadeOpacity(0), mLockInput(false)
{
mState.viewing = NOTHING;
mFavoritesOnly = Settings::getInstance()->getBool("FavoritesOnly");
}
ViewController::~ViewController()
......@@ -102,6 +103,17 @@ void ViewController::goToGameList(SystemData* system)
mCamera.translation().x() -= offX;
}
if (mInvalidGameList[system] == true)
{
updateFavorite(system, getGameListView(system).get()->getCursor());
if (mFavoritesOnly != Settings::getInstance()->getBool("FavoritesOnly"))
{
reloadGameListView(system);
mFavoritesOnly = Settings::getInstance()->getBool("FavoritesOnly");
}
mInvalidGameList[system] = false;
}
mState.viewing = GAME_LIST;
mState.system = system;
......@@ -109,6 +121,52 @@ void ViewController::goToGameList(SystemData* system)
playViewTransition();
}
void ViewController::updateFavorite(SystemData* system, FileData* file)
{
IGameListView* view = getGameListView(system).get();
if (Settings::getInstance()->getBool("FavoritesOnly"))
{
const std::vector<FileData*>& files = system->getRootFolder()->getChildren();
view->populateList(files);
int pos = std::find(files.begin(), files.end(), file) - files.begin();
bool found = false;
for (auto it = files.begin() + pos; it != files.end(); it++)
{
if ((*it)->getType() == GAME)
{
if ((*it)->metadata.get("favorite").compare("yes") == 0)
{
view->setCursor(*it);
found = true;
break;
}
}
}
if (!found)
{
for (auto it = files.begin() + pos; it != files.begin(); it--)
{
if ((*it)->getType() == GAME)
{
if ((*it)->metadata.get("favorite").compare("yes") == 0)
{
view->setCursor(*it);
break;
}
}
}
}
if (!found)
{
view->setCursor(*(files.begin() + pos));
}
}
view->updateInfoPanel();
}
void ViewController::playViewTransition()
{
Eigen::Vector3f target(Eigen::Vector3f::Identity());
......@@ -227,7 +285,7 @@ std::shared_ptr<IGameListView> ViewController::getGameListView(SystemData* syste
}
if(detailed)
view = std::shared_ptr<IGameListView>(new DetailedGameListView(mWindow, system->getRootFolder()));
view = std::shared_ptr<IGameListView>(new DetailedGameListView(mWindow, system->getRootFolder(), system));
else
view = std::shared_ptr<IGameListView>(new BasicGameListView(mWindow, system->getRootFolder()));
......@@ -243,6 +301,7 @@ std::shared_ptr<IGameListView> ViewController::getGameListView(SystemData* syste
addChild(view.get());
mGameListViews[system] = view;
mInvalidGameList[system] = false;
return view;
}
......@@ -388,6 +447,29 @@ void ViewController::reloadAll()
updateHelpPrompts();
}
void ViewController::setInvalidGamesList(SystemData* system)
{
for (auto it = mGameListViews.begin(); it != mGameListViews.end(); it++)
{
if (system == (it->first))
{
mInvalidGameList[it->first] = true;
break;
}
}
}
void ViewController::setAllInvalidGamesList(SystemData* systemExclude)
{
for (auto it = mGameListViews.begin(); it != mGameListViews.end(); it++)
{
if (systemExclude != (it->first))
{
mInvalidGameList[it->first] = true;
}
}
}
std::vector<HelpPrompt> ViewController::getHelpPrompts()
{
std::vector<HelpPrompt> prompts;
......
......@@ -23,6 +23,8 @@ public:
void reloadGameListView(IGameListView* gamelist, bool reloadTheme = false);
inline void reloadGameListView(SystemData* system, bool reloadTheme = false) { reloadGameListView(getGameListView(system).get(), reloadTheme); }
void reloadAll(); // Reload everything with a theme. Used when the "ThemeSet" setting changes.
void setInvalidGamesList(SystemData* system);
void setAllInvalidGamesList(SystemData* systemExclude);
// Navigation.
void goToNextGameList();
......@@ -33,6 +35,8 @@ public:
void onFileChanged(FileData* file, FileChangeType change);
void updateFavorite(SystemData* system, FileData* file);
// Plays a nice launch effect and launches the game at the end of it.
// Once the game terminates, plays a return effect.
void launch(FileData* game, Eigen::Vector3f centerCameraOn = Eigen::Vector3f(Renderer::getScreenWidth() / 2.0f, Renderer::getScreenHeight() / 2.0f, 0));
......@@ -78,10 +82,13 @@ private:
std::shared_ptr<GuiComponent> mCurrentView;
std::map< SystemData*, std::shared_ptr<IGameListView> > mGameListViews;
std::shared_ptr<SystemView> mSystemListView;
std::map<SystemData*, bool> mInvalidGameList;
Eigen::Affine3f mCamera;
float mFadeOpacity;
bool mLockInput;
bool mFavoritesOnly;
State mState;
};
......@@ -41,9 +41,52 @@ void BasicGameListView::populateList(const std::vector<FileData*>& files)
mHeaderText.setText(files.at(0)->getSystem()->getFullName());
bool hasFavorites = false;
if (Settings::getInstance()->getBool("FavoritesOnly"))
{
for (auto it = files.begin(); it != files.end(); it++)
{
if ((*it)->getType() == GAME)
{
if ((*it)->metadata.get("favorite").compare("yes") == 0)
{
hasFavorites = true;
break;
}
}
}
}
// The TextListComponent would be able to insert at a specific position,
// but the cost of this operation could be seriously huge.
// This naive implemention of doing a first pass in the list is used instead.
if(! Settings::getInstance()->getBool("FavoritesOnly")){
for(auto it = files.begin(); it != files.end(); it++)
{
if ((*it)->metadata.get("favorite").compare("yes") == 0)
{
mList.add("☆ " + (*it)->getName(), *it, ((*it)->getType() == FOLDER)); // FIXME Folder as favorite ?
}
}
}
for(auto it = files.begin(); it != files.end(); it++)
{
mList.add((*it)->getName(), *it, ((*it)->getType() == FOLDER));
if (Settings::getInstance()->getBool("FavoritesOnly") && hasFavorites)
{
if ((*it)->getType() == GAME)
{
if ((*it)->metadata.get("favorite").compare("yes") == 0)
{
mList.add((*it)->getName(), *it, ((*it)->getType() == FOLDER));
}
}
}
else
{
mList.add((*it)->getName(), *it, ((*it)->getType() == FOLDER));
}
}
}
......
......@@ -20,8 +20,10 @@ public:
virtual std::vector<HelpPrompt> getHelpPrompts() override;
protected:
virtual void populateList(const std::vector<FileData*>& files) override;
virtual inline void updateInfoPanel() override {}
protected:
virtual void launch(FileData* game) override;
TextListComponent<FileData*> mList;
......
#include "views/gamelist/DetailedGameListView.h"
#include "views/ViewController.h"
#include "Window.h"
#include "Settings.h"
#include "animations/LambdaAnimation.h"
DetailedGameListView::DetailedGameListView(Window* window, FileData* root) :
DetailedGameListView::DetailedGameListView(Window* window, FileData* root, SystemData* system) :
BasicGameListView(window, root),
mDescContainer(window), mDescription(window),
mImage(window),
mImage(window), mSystem(system),
mLblRating(window), mLblReleaseDate(window), mLblDeveloper(window), mLblPublisher(window),
mLblGenre(window), mLblPlayers(window), mLblLastPlayed(window), mLblPlayCount(window),
mLblGenre(window), mLblPlayers(window), mLblLastPlayed(window), mLblPlayCount(window), mLblFavorite(window),
mRating(window), mReleaseDate(window), mDeveloper(window), mPublisher(window),
mGenre(window), mPlayers(window), mLastPlayed(window), mPlayCount(window)
mGenre(window), mPlayers(window), mLastPlayed(window), mPlayCount(window), mFavorite(window)
{
//mHeaderImage.setPosition(mSize.x() * 0.25f, 0);
......@@ -55,6 +56,12 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) :
mLblPlayCount.setText("Times played: ");
addChild(&mLblPlayCount);
addChild(&mPlayCount);
if (system->getHasFavorites())
{
mLblFavorite.setText("Favorite: ");
addChild(&mLblFavorite);
addChild(&mFavorite);
}
mDescContainer.setPosition(mSize.x() * padding, mSize.y() * 0.65f);
mDescContainer.setSize(mSize.x() * (0.50f - 2*padding), mSize.y() - mDescContainer.getPosition().y());
......@@ -80,29 +87,62 @@ void DetailedGameListView::onThemeChanged(const std::shared_ptr<ThemeData>& them
initMDLabels();
std::vector<TextComponent*> labels = getMDLabels();
assert(labels.size() == 8);
const char* lblElements[8] = {
"md_lbl_rating", "md_lbl_releasedate", "md_lbl_developer", "md_lbl_publisher",
"md_lbl_genre", "md_lbl_players", "md_lbl_lastplayed", "md_lbl_playcount"
};
for(unsigned int i = 0; i < labels.size(); i++)
if (mSystem->getHasFavorites())
{
labels[i]->applyTheme(theme, getName(), lblElements[i], ALL);
assert(labels.size() == 9);
const char* lblElements[9] = {
"md_lbl_rating", "md_lbl_releasedate", "md_lbl_developer", "md_lbl_publisher",
"md_lbl_genre", "md_lbl_players", "md_lbl_lastplayed", "md_lbl_playcount", "md_lbl_favorite"
};
for (unsigned int i = 0; i < labels.size(); i++)
{
labels[i]->applyTheme(theme, getName(), lblElements[i], ALL);
}
}
else
{
assert(labels.size() == 8);
const char* lblElements[8] = {
"md_lbl_rating", "md_lbl_releasedate", "md_lbl_developer", "md_lbl_publisher",
"md_lbl_genre", "md_lbl_players", "md_lbl_lastplayed", "md_lbl_playcount"
};
for(unsigned int i = 0; i < labels.size(); i++)
{
labels[i]->applyTheme(theme, getName(), lblElements[i], ALL);
}
}
initMDValues();
std::vector<GuiComponent*> values = getMDValues();
assert(values.size() == 8);
const char* valElements[8] = {
"md_rating", "md_releasedate", "md_developer", "md_publisher",
"md_genre", "md_players", "md_lastplayed", "md_playcount"
};
for(unsigned int i = 0; i < values.size(); i++)
if (mSystem->getHasFavorites())
{
values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT);
assert(values.size() == 9);
const char* valElements[9] = {
"md_rating", "md_releasedate", "md_developer", "md_publisher",
"md_genre", "md_players", "md_lastplayed", "md_playcount", "md_favorite"
};
for (unsigned int i = 0; i < values.size(); i++)
{
values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT);
}
}
else
{
assert(values.size() == 8);
const char* valElements[8] = {
"md_rating", "md_releasedate", "md_developer", "md_publisher",
"md_genre", "md_players", "md_lastplayed", "md_playcount"
};
for (unsigned int i = 0; i < values.size(); i++)
{
values[i]->applyTheme(theme, getName(), valElements[i], ALL ^ ThemeFlags::TEXT);
}
}
mDescContainer.applyTheme(theme, getName(), "md_description", POSITION | ThemeFlags::SIZE);
......@@ -158,6 +198,7 @@ void DetailedGameListView::initMDValues()
mPlayers.setFont(defaultFont);
mLastPlayed.setFont(defaultFont);
mPlayCount.setFont(defaultFont);
mFavorite.setFont(defaultFont);
float bottom = 0.0f;
......@@ -202,6 +243,7 @@ void DetailedGameListView::updateInfoPanel()
mPlayers.setValue(file->metadata.get("players"));
mLastPlayed.setValue(file->metadata.get("lastplayed"));
mPlayCount.setValue(file->metadata.get("playcount"));
mFavorite.setValue(file->metadata.get("favorite"));
}
fadingOut = false;
......@@ -252,6 +294,10 @@ std::vector<TextComponent*> DetailedGameListView::getMDLabels()
ret.push_back(&mLblPlayers);
ret.push_back(&mLblLastPlayed);
ret.push_back(&mLblPlayCount);
if (mSystem->getHasFavorites())
{
ret.push_back(&mLblFavorite);
}
return ret;
}
......@@ -266,5 +312,29 @@ std::vector<GuiComponent*> DetailedGameListView::getMDValues()
ret.push_back(&mPlayers);
ret.push_back(&mLastPlayed);
ret.push_back(&mPlayCount);
if (mSystem->getHasFavorites())
{
ret.push_back(&mFavorite);
}
return ret;
}
std::vector<HelpPrompt> DetailedGameListView::getHelpPrompts()
{
std::vector<HelpPrompt> prompts;
if (Settings::getInstance()->getBool("QuickSystemSelect"))
{
prompts.push_back(HelpPrompt("left/right", "system"));
}
prompts.push_back(HelpPrompt("up/down", "choose"));
prompts.push_back(HelpPrompt("a", "launch"));
prompts.push_back(HelpPrompt("b", "back"));
if (mSystem->getHasFavorites())
{
prompts.push_back(HelpPrompt("y", "toggle favorite"));
}
prompts.push_back(HelpPrompt("select", "options"));
return prompts;
}
......@@ -4,28 +4,31 @@
#include "components/ScrollableContainer.h"
#include "components/RatingComponent.h"
#include "components/DateTimeComponent.h"
#include "SystemData.h"
class DetailedGameListView : public BasicGameListView
{
public:
DetailedGameListView(Window* window, FileData* root);
DetailedGameListView(Window* window, FileData* root, SystemData* system);
virtual void onThemeChanged(const std::shared_ptr<ThemeData>& theme) override;
virtual const char* getName() const override { return "detailed"; }
virtual void updateInfoPanel() override;
protected:
virtual void launch(FileData* game) override;
private: