From 556b9fa3fee8ddaa91d70e7fd292be4c1fdfa498 Mon Sep 17 00:00:00 2001 From: Bim Overbohm Date: Fri, 28 Jun 2013 14:54:14 +0200 Subject: [PATCH 01/16] Add functions for writing data to gamelist.xml Also add a "rating" and "timePlayed" variable to GameData. Some cleanup in GameData and FolderData. Added sorting functions for rating and timesPlayed to FolderData. Testing and UI support still tbd. --- src/FileData.h | 6 +- src/FolderData.cpp | 100 ++++++++++++++++++++++++----- src/FolderData.h | 14 +++-- src/GameData.cpp | 101 +++++++++++++++++++++++------- src/GameData.h | 40 +++++++++--- src/SystemData.cpp | 7 +++ src/XMLReader.cpp | 152 ++++++++++++++++++++++++++++++++++++++++----- src/XMLReader.h | 3 + 8 files changed, 352 insertions(+), 71 deletions(-) diff --git a/src/FileData.h b/src/FileData.h index a0c7abde8..ebb44b9b9 100644 --- a/src/FileData.h +++ b/src/FileData.h @@ -9,9 +9,9 @@ class FileData { public: virtual ~FileData() { }; - virtual bool isFolder() = 0; - virtual std::string getName() = 0; - virtual std::string getPath() = 0; + virtual bool isFolder() const = 0; + virtual const std::string & getName() const = 0; + virtual const std::string & getPath() const = 0; }; #endif diff --git a/src/FolderData.cpp b/src/FolderData.cpp index 2bc937cca..5ea2cd8e5 100644 --- a/src/FolderData.cpp +++ b/src/FolderData.cpp @@ -1,13 +1,14 @@ #include "FolderData.h" #include "SystemData.h" +#include "GameData.h" #include #include -bool FolderData::isFolder() { return true; } -std::string FolderData::getName() { return mName; } -std::string FolderData::getPath() { return mPath; } +bool FolderData::isFolder() const { return true; } +const std::string & FolderData::getName() const { return mName; } +const std::string & FolderData::getPath() const { return mPath; } unsigned int FolderData::getFileCount() { return mFileVector.size(); } -FileData* FolderData::getFile(unsigned int i) { return mFileVector.at(i); } + FolderData::FolderData(SystemData* system, std::string path, std::string name) { @@ -32,7 +33,7 @@ void FolderData::pushFileData(FileData* file) } //returns if file1 should come before file2 -bool filesort(FileData* file1, FileData* file2) +bool FolderData::compareFileName(const FileData* file1, const FileData* file2) { std::string name1 = file1->getName(); std::string name2 = file2->getName(); @@ -43,25 +44,39 @@ bool filesort(FileData* file1, FileData* file2) { if(toupper(name1[i]) != toupper(name2[i])) { - if(toupper(name1[i]) < toupper(name2[i])) - { - return true; - }else{ - return false; - } + return toupper(name1[i]) < toupper(name2[i]); } } - if(name1.length() < name2.length()) - return true; - else - return false; + return name1.length() < name2.length(); +} + +bool FolderData::compareRating(const FileData* file1, const FileData* file2) +{ + //we need game data. try to cast + const GameData * game1 = dynamic_cast(file1); + const GameData * game2 = dynamic_cast(file2); + if (game1 != nullptr && game2 != nullptr) { + return game1->getRating() < game2->getRating(); + } + return false; +} + +bool FolderData::compareTimesPlayed(const FileData* file1, const FileData* file2) +{ + //we need game data. try to cast + const GameData * game1 = dynamic_cast(file1); + const GameData * game2 = dynamic_cast(file2); + if (game1 != nullptr && game2 != nullptr) { + return game1->getTimesPlayed() < game2->getTimesPlayed(); + } + return false; } //sort this folder and any subfolders void FolderData::sort() { - std::sort(mFileVector.begin(), mFileVector.end(), filesort); + std::sort(mFileVector.begin(), mFileVector.end(), compareFileName); for(unsigned int i = 0; i < mFileVector.size(); i++) { @@ -69,3 +84,56 @@ void FolderData::sort() ((FolderData*)mFileVector.at(i))->sort(); } } + +FileData* FolderData::getFile(unsigned int i) const +{ + return mFileVector.at(i); +} + +std::vector FolderData::getFiles(bool onlyFiles) const +{ + std::vector temp; + //now check if a child is a folder and get those children in turn + std::vector::const_iterator fdit = mFileVector.cbegin(); + while(fdit != mFileVector.cend()) { + //dynamically try to cast to FolderData type + FolderData * folder = dynamic_cast(*fdit); + if (folder != nullptr) { + //add this only when user wanted it + if (!onlyFiles) { + temp.push_back(*fdit); + } + } + else { + temp.push_back(*fdit); + } + ++fdit; + } + return temp; +} + +std::vector FolderData::getFilesRecursive(bool onlyFiles) const +{ + std::vector temp; + //now check if a child is a folder and get those children in turn + std::vector::const_iterator fdit = mFileVector.cbegin(); + while(fdit != mFileVector.cend()) { + //dynamically try to cast to FolderData type + FolderData * folder = dynamic_cast(*fdit); + if (folder != nullptr) { + //add this onyl when user wanted it + if (!onlyFiles) { + temp.push_back(*fdit); + } + //worked. Is actual folder data. recurse + std::vector children = folder->getFilesRecursive(onlyFiles); + //insert children into return vector + temp.insert(temp.end(), children.cbegin(), children.cend()); + } + else { + temp.push_back(*fdit); + } + ++fdit; + } + return temp; +} \ No newline at end of file diff --git a/src/FolderData.h b/src/FolderData.h index e453ac5e7..d91d69c64 100644 --- a/src/FolderData.h +++ b/src/FolderData.h @@ -13,16 +13,22 @@ public: FolderData(SystemData* system, std::string path, std::string name); ~FolderData(); - bool isFolder(); - std::string getName(); - std::string getPath(); + bool isFolder() const; + const std::string & getName() const; + const std::string & getPath() const; unsigned int getFileCount(); - FileData* getFile(unsigned int i); + FileData* getFile(unsigned int i) const; + std::vector getFiles(bool onlyFiles = false) const; + std::vector getFilesRecursive(bool onlyFiles = false) const; void pushFileData(FileData* file); void sort(); + + static bool compareFileName(const FileData* file1, const FileData* file2); + static bool compareRating(const FileData* file1, const FileData* file2); + static bool compareTimesPlayed(const FileData* file1, const FileData* file2); private: SystemData* mSystem; std::string mPath; diff --git a/src/GameData.cpp b/src/GameData.cpp index bb656be01..f72d9f9a4 100644 --- a/src/GameData.cpp +++ b/src/GameData.cpp @@ -2,23 +2,88 @@ #include #include -bool GameData::isFolder() { return false; } -std::string GameData::getName() { return mName; } -std::string GameData::getPath() { return mPath; } -std::string GameData::getDescription() { return mDescription; } -std::string GameData::getImagePath() { return mImagePath; } + +const std::string GameData::xmlTagGameList = "gameList"; +const std::string GameData::xmlTagGame = "game"; +const std::string GameData::xmlTagName = "name"; +const std::string GameData::xmlTagPath = "path"; +const std::string GameData::xmlTagDescription = "desc"; +const std::string GameData::xmlTagImagePath = "image"; +const std::string GameData::xmlTagRating = "rating"; +const std::string GameData::xmlTagTimesPlayed = "timesplayed"; + GameData::GameData(SystemData* system, std::string path, std::string name) + : mSystem(system), mPath(path), mName(name), mRating(0), mTimesPlayed(0) { - mSystem = system; - mPath = path; - mName = name; - - mDescription = ""; - mImagePath = ""; } -std::string GameData::getBashPath() +bool GameData::isFolder() const +{ + return false; +} + +const std::string & GameData::getName() const +{ + return mName; +} + +void GameData::setName(const std::string & name) +{ + mName = name; +} + +const std::string & GameData::getPath() const +{ + return mPath; +} + +void GameData::setPath(const std::string & path) +{ + mPath = path; +} + +const std::string & GameData::getDescription() const +{ + return mDescription; +} + +void GameData::setDescription(const std::string & description) +{ + mDescription = description; +} + +const std::string & GameData::getImagePath() const +{ + return mImagePath; +} + +void GameData::setImagePath(const std::string & imagePath) +{ + mImagePath = imagePath; +} + +size_t GameData::getRating() const +{ + return mRating; +} + +void GameData::setRating(size_t rating) +{ + mRating = rating; +} + +size_t GameData::getTimesPlayed() const +{ + return mTimesPlayed; +} + +void GameData::setTimesPlayed(size_t timesPlayed) +{ + mTimesPlayed = timesPlayed; +} + +std::string GameData::getBashPath() const { //a quick and dirty way to insert a backslash before most characters that would mess up a bash path std::string path = mPath; @@ -44,18 +109,8 @@ std::string GameData::getBashPath() } //returns the boost::filesystem stem of our path - e.g. for "/foo/bar.rom" returns "bar" -std::string GameData::getBaseName() +std::string GameData::getBaseName() const { boost::filesystem::path path(mPath); return path.stem().string(); } - -void GameData::set(std::string name, std::string description, std::string imagePath) -{ - if(!name.empty()) - mName = name; - if(!description.empty()) - mDescription = description; - if(!imagePath.empty() && boost::filesystem::exists(imagePath)) - mImagePath = imagePath; -} diff --git a/src/GameData.h b/src/GameData.h index ad0db2038..bd34be4ea 100644 --- a/src/GameData.h +++ b/src/GameData.h @@ -9,19 +9,41 @@ class GameData : public FileData { public: + //static tag names for reading/writing XML documents. This might fail in PUGIXML_WCHAR_MODE + //TODO: The class should have member to read fromXML() and write toXML() probably... + static const std::string xmlTagGameList; + static const std::string xmlTagGame; + static const std::string xmlTagName; + static const std::string xmlTagPath; + static const std::string xmlTagDescription; + static const std::string xmlTagImagePath; + static const std::string xmlTagRating; + static const std::string xmlTagTimesPlayed; + GameData(SystemData* system, std::string path, std::string name); - void set(std::string name = "", std::string description = "", std::string imagePath = ""); + const std::string & getName() const; + void setName(const std::string & name); - std::string getName(); - std::string getPath(); - std::string getBashPath(); - std::string getBaseName(); + const std::string & getPath() const; + void setPath(const std::string & path); - std::string getDescription(); - std::string getImagePath(); + const std::string & getDescription() const; + void setDescription(const std::string & description); - bool isFolder(); + const std::string & getImagePath() const; + void setImagePath(const std::string & imagePath); + + size_t getRating() const; + void setRating(size_t rating); + + size_t getTimesPlayed() const; + void setTimesPlayed(size_t timesPlayed); + + std::string getBashPath() const; + std::string getBaseName() const; + + bool isFolder() const; private: SystemData* mSystem; std::string mPath; @@ -30,6 +52,8 @@ private: //extra data std::string mDescription; std::string mImagePath; + size_t mRating; + size_t mTimesPlayed; }; #endif diff --git a/src/SystemData.cpp b/src/SystemData.cpp index 2e983e5e4..d77bfb024 100644 --- a/src/SystemData.cpp +++ b/src/SystemData.cpp @@ -50,6 +50,10 @@ SystemData::SystemData(std::string name, std::string descName, std::string start SystemData::~SystemData() { + //save changed game data back to xml + if(!Settings::getInstance()->getBool("IGNOREGAMELIST")) { + updateGamelist(this); + } delete mRootFolder; } @@ -90,6 +94,9 @@ void SystemData::launchGame(Window* window, GameData* game) window->init(); VolumeControl::getInstance()->init(); AudioManager::getInstance()->init(); + + //update number of times the game has been launched + game->setTimesPlayed(game->getTimesPlayed() + 1); } void SystemData::populateFolder(FolderData* folder) diff --git a/src/XMLReader.cpp b/src/XMLReader.cpp index d1e19b8ae..928fbd8ff 100644 --- a/src/XMLReader.cpp +++ b/src/XMLReader.cpp @@ -117,19 +117,19 @@ void parseGamelist(SystemData* system) return; } - pugi::xml_node root = doc.child("gameList"); + pugi::xml_node root = doc.child(GameData::xmlTagGameList.c_str()); if(!root) { - LOG(LogError) << "Could not find node in gamelist \"" << xmlpath << "\"!"; + LOG(LogError) << "Could not find <" << GameData::xmlTagGameList << "> node in gamelist \"" << xmlpath << "\"!"; return; } - for(pugi::xml_node gameNode = root.child("game"); gameNode; gameNode = gameNode.next_sibling("game")) + for(pugi::xml_node gameNode = root.child(GameData::xmlTagGame.c_str()); gameNode; gameNode = gameNode.next_sibling(GameData::xmlTagGame.c_str())) { - pugi::xml_node pathNode = gameNode.child("path"); + pugi::xml_node pathNode = gameNode.child(GameData::xmlTagPath.c_str()); if(!pathNode) { - LOG(LogError) << " node contains no child!"; + LOG(LogError) << "<" << GameData::xmlTagGame << "> node contains no <" << GameData::xmlTagPath << "> child!"; continue; } @@ -152,13 +152,17 @@ void parseGamelist(SystemData* system) //actually gather the information in the XML doc, then pass it to the game's set method std::string newName, newDesc, newImage; - if(gameNode.child("name")) - newName = gameNode.child("name").text().get(); - if(gameNode.child("desc")) - newDesc = gameNode.child("desc").text().get(); - if(gameNode.child("image")) + if(gameNode.child(GameData::xmlTagName.c_str())) { - newImage = gameNode.child("image").text().get(); + game->setName(gameNode.child(GameData::xmlTagName.c_str()).text().get()); + } + if(gameNode.child(GameData::xmlTagDescription.c_str())) + { + game->setDescription(gameNode.child(GameData::xmlTagDescription.c_str()).text().get()); + } + if(gameNode.child(GameData::xmlTagImagePath.c_str())) + { + newImage = gameNode.child(GameData::xmlTagImagePath.c_str()).text().get(); //expand "." if(newImage[0] == '.') @@ -168,15 +172,129 @@ void parseGamelist(SystemData* system) newImage.insert(0, pathname.parent_path().string() ); } - //if the image doesn't exist, forget it - if(!boost::filesystem::exists(newImage)) - newImage = ""; + //if the image exist, set it + if(boost::filesystem::exists(newImage)) + { + game->setImagePath(newImage); + } } - game->set(newName, newDesc, newImage); - - }else{ + //get rating and the times played from the XML doc + if(gameNode.child(GameData::xmlTagRating.c_str())) + { + size_t rating; + std::istringstream(gameNode.child(GameData::xmlTagRating.c_str()).text().get()) >> rating; + game->setRating(rating); + } + if(gameNode.child(GameData::xmlTagTimesPlayed.c_str())) + { + size_t timesPlayed; + std::istringstream(gameNode.child(GameData::xmlTagTimesPlayed.c_str()).text().get()) >> timesPlayed; + game->setRating(timesPlayed); + } + } + else{ LOG(LogWarning) << "Game at \"" << path << "\" does not exist!"; } } } + +void addGameDataNode(pugi::xml_node & parent, const GameData * game) +{ + //create game and add to parent node + pugi::xml_node newGame = parent.append_child(GameData::xmlTagGame.c_str()); + //add values + if (!game->getPath().empty()) { + pugi::xml_node pathNode = newGame.append_child(GameData::xmlTagPath.c_str()); + pathNode.text().set(game->getPath().c_str()); + } + if (!game->getName().empty()) { + pugi::xml_node nameNode = newGame.append_child(GameData::xmlTagName.c_str()); + nameNode.text().set(game->getName().c_str()); + } + if (!game->getDescription().empty()) { + pugi::xml_node descriptionNode = newGame.append_child(GameData::xmlTagDescription.c_str()); + descriptionNode.text().set(game->getDescription().c_str()); + } + if (!game->getImagePath().empty()) { + pugi::xml_node imagePathNode = newGame.append_child(GameData::xmlTagImagePath.c_str()); + imagePathNode.text().set(game->getImagePath().c_str()); + } + //all other values are added regardless of their value + pugi::xml_node ratingNode = newGame.append_child(GameData::xmlTagRating.c_str()); + ratingNode.text().set(std::to_string((unsigned long long)game->getRating()).c_str()); + pugi::xml_node timesPlayedNode = newGame.append_child(GameData::xmlTagTimesPlayed.c_str()); + timesPlayedNode.text().set(std::to_string((unsigned long long)game->getTimesPlayed()).c_str()); +} + +void updateGamelist(SystemData* system) +{ + //We do this by reading the XML again, adding changes and then writing it back, + //because there might be information missing in our systemdata which would then miss in the new XML. + //We have the complete information for every game though, so we can simply remove a game + //we already have in the system from the XML, and then add it back from its GameData information... + + std::string xmlpath = system->getGamelistPath(); + if(xmlpath.empty()) { + return; + } + + LOG(LogInfo) << "Parsing XML file \"" << xmlpath << "\" before writing..."; + + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(xmlpath.c_str()); + + if(!result) { + LOG(LogError) << "Error parsing XML file \"" << xmlpath << "\"!\n " << result.description(); + return; + } + + pugi::xml_node root = doc.child(GameData::xmlTagGameList.c_str()); + if(!root) { + LOG(LogError) << "Could not find <" << GameData::xmlTagGameList << "> node in gamelist \"" << xmlpath << "\"!"; + return; + } + + //now we have all the information from the XML. now iterate through all our games and add information from there + FolderData * rootFolder = system->getRootFolder(); + if (rootFolder != nullptr) { + //get only files, no folders + std::vector files = rootFolder->getFilesRecursive(true); + //iterate through all files, checking if they're already in the XML + std::vector::const_iterator fit = files.cbegin(); + while(fit != files.cend()) { + //try to cast to gamedata + const GameData * game = dynamic_cast(*fit); + if (game != nullptr) { + //worked. check if this games' path can be found somewhere in the XML + for(pugi::xml_node gameNode = root.child(GameData::xmlTagGame.c_str()); gameNode; gameNode = gameNode.next_sibling(GameData::xmlTagGame.c_str())) { + //get path from game node + pugi::xml_node pathNode = gameNode.child(GameData::xmlTagPath.c_str()); + if(!pathNode) + { + LOG(LogError) << "<" << GameData::xmlTagGame << "> node contains no <" << GameData::xmlTagPath << "> child!"; + continue; + } + //check path + if (pathNode.text().get() == game->getPath()) { + //found the game. remove it. it will be added again later with updated values + root.remove_child(gameNode); + //break node search loop + break; + } + } + //either the game content was removed, because it needs to be updated, + //or didn't exist in the first place, so just add it + addGameDataNode(root, game); + } + ++fit; + } + //now write the file + if (!doc.save_file(xmlpath.c_str())) { + LOG(LogError) << "Error saving XML file \"" << xmlpath << "\"!"; + } + } + else { + LOG(LogError) << "Found no root folder for system \"" << system->getName() << "\"!"; + } +} diff --git a/src/XMLReader.h b/src/XMLReader.h index 403c96fcf..ea3b4399b 100644 --- a/src/XMLReader.h +++ b/src/XMLReader.h @@ -7,4 +7,7 @@ class SystemData; //Loads gamelist.xml data into a SystemData. void parseGamelist(SystemData* system); +//Writes changes to SystemData back to a previously loaded gamelist.xml. +void updateGamelist(SystemData* system); + #endif From d99134763fedec2d04b9ae41a053405423424007 Mon Sep 17 00:00:00 2001 From: Bim Overbohm Date: Fri, 28 Jun 2013 16:13:57 +0200 Subject: [PATCH 02/16] Convert rating to float. Fix reading of timesPlayed. http://thegamesdb.net API seems to use a float. Fix a but where the times played was read into the rating member. --- src/GameData.cpp | 6 +++--- src/GameData.h | 6 +++--- src/XMLReader.cpp | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/GameData.cpp b/src/GameData.cpp index f72d9f9a4..908e5603a 100644 --- a/src/GameData.cpp +++ b/src/GameData.cpp @@ -14,7 +14,7 @@ const std::string GameData::xmlTagTimesPlayed = "timesplayed"; GameData::GameData(SystemData* system, std::string path, std::string name) - : mSystem(system), mPath(path), mName(name), mRating(0), mTimesPlayed(0) + : mSystem(system), mPath(path), mName(name), mRating(0.0f), mTimesPlayed(0) { } @@ -63,12 +63,12 @@ void GameData::setImagePath(const std::string & imagePath) mImagePath = imagePath; } -size_t GameData::getRating() const +float GameData::getRating() const { return mRating; } -void GameData::setRating(size_t rating) +void GameData::setRating(float rating) { mRating = rating; } diff --git a/src/GameData.h b/src/GameData.h index bd34be4ea..edcede2c2 100644 --- a/src/GameData.h +++ b/src/GameData.h @@ -34,8 +34,8 @@ public: const std::string & getImagePath() const; void setImagePath(const std::string & imagePath); - size_t getRating() const; - void setRating(size_t rating); + float getRating() const; + void setRating(float rating); size_t getTimesPlayed() const; void setTimesPlayed(size_t timesPlayed); @@ -52,7 +52,7 @@ private: //extra data std::string mDescription; std::string mImagePath; - size_t mRating; + float mRating; size_t mTimesPlayed; }; diff --git a/src/XMLReader.cpp b/src/XMLReader.cpp index 928fbd8ff..47e6527aa 100644 --- a/src/XMLReader.cpp +++ b/src/XMLReader.cpp @@ -182,7 +182,7 @@ void parseGamelist(SystemData* system) //get rating and the times played from the XML doc if(gameNode.child(GameData::xmlTagRating.c_str())) { - size_t rating; + float rating; std::istringstream(gameNode.child(GameData::xmlTagRating.c_str()).text().get()) >> rating; game->setRating(rating); } @@ -190,7 +190,7 @@ void parseGamelist(SystemData* system) { size_t timesPlayed; std::istringstream(gameNode.child(GameData::xmlTagTimesPlayed.c_str()).text().get()) >> timesPlayed; - game->setRating(timesPlayed); + game->setTimesPlayed(timesPlayed); } } else{ @@ -222,7 +222,7 @@ void addGameDataNode(pugi::xml_node & parent, const GameData * game) } //all other values are added regardless of their value pugi::xml_node ratingNode = newGame.append_child(GameData::xmlTagRating.c_str()); - ratingNode.text().set(std::to_string((unsigned long long)game->getRating()).c_str()); + ratingNode.text().set(std::to_string((long double)game->getRating()).c_str()); pugi::xml_node timesPlayedNode = newGame.append_child(GameData::xmlTagTimesPlayed.c_str()); timesPlayedNode.text().set(std::to_string((unsigned long long)game->getTimesPlayed()).c_str()); } From c61a470694b7491c0c2d748e6209eb5c3f343696 Mon Sep 17 00:00:00 2001 From: Bim Overbohm Date: Fri, 28 Jun 2013 17:25:18 +0200 Subject: [PATCH 03/16] Add user rating and last time played to game data That should be about it... --- src/FolderData.cpp | 22 ++++++++++++++++++++++ src/FolderData.h | 2 ++ src/GameData.cpp | 24 +++++++++++++++++++++++- src/GameData.h | 12 ++++++++++++ src/SystemData.cpp | 3 ++- src/XMLReader.cpp | 19 +++++++++++++++++++ 6 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/FolderData.cpp b/src/FolderData.cpp index 5ea2cd8e5..2d229b675 100644 --- a/src/FolderData.cpp +++ b/src/FolderData.cpp @@ -62,6 +62,17 @@ bool FolderData::compareRating(const FileData* file1, const FileData* file2) return false; } +bool FolderData::compareUserRating(const FileData* file1, const FileData* file2) +{ + //we need game data. try to cast + const GameData * game1 = dynamic_cast(file1); + const GameData * game2 = dynamic_cast(file2); + if (game1 != nullptr && game2 != nullptr) { + return game1->getUserRating() < game2->getUserRating(); + } + return false; +} + bool FolderData::compareTimesPlayed(const FileData* file1, const FileData* file2) { //we need game data. try to cast @@ -73,6 +84,17 @@ bool FolderData::compareTimesPlayed(const FileData* file1, const FileData* file2 return false; } +bool FolderData::compareLastPlayed(const FileData* file1, const FileData* file2) +{ + //we need game data. try to cast + const GameData * game1 = dynamic_cast(file1); + const GameData * game2 = dynamic_cast(file2); + if (game1 != nullptr && game2 != nullptr) { + return game1->getLastPlayed() < game2->getLastPlayed(); + } + return false; +} + //sort this folder and any subfolders void FolderData::sort() { diff --git a/src/FolderData.h b/src/FolderData.h index d91d69c64..7391f824e 100644 --- a/src/FolderData.h +++ b/src/FolderData.h @@ -28,7 +28,9 @@ public: static bool compareFileName(const FileData* file1, const FileData* file2); static bool compareRating(const FileData* file1, const FileData* file2); + static bool compareUserRating(const FileData* file1, const FileData* file2); static bool compareTimesPlayed(const FileData* file1, const FileData* file2); + static bool compareLastPlayed(const FileData* file1, const FileData* file2); private: SystemData* mSystem; std::string mPath; diff --git a/src/GameData.cpp b/src/GameData.cpp index 908e5603a..ef8384dfd 100644 --- a/src/GameData.cpp +++ b/src/GameData.cpp @@ -10,11 +10,13 @@ const std::string GameData::xmlTagPath = "path"; const std::string GameData::xmlTagDescription = "desc"; const std::string GameData::xmlTagImagePath = "image"; const std::string GameData::xmlTagRating = "rating"; +const std::string GameData::xmlTagUserRating = "userrating"; const std::string GameData::xmlTagTimesPlayed = "timesplayed"; +const std::string GameData::xmlTagLastPlayed = "lastplayed"; GameData::GameData(SystemData* system, std::string path, std::string name) - : mSystem(system), mPath(path), mName(name), mRating(0.0f), mTimesPlayed(0) + : mSystem(system), mPath(path), mName(name), mRating(0.0f), mUserRating(0.0f), mTimesPlayed(0), mLastPlayed(0) { } @@ -73,6 +75,16 @@ void GameData::setRating(float rating) mRating = rating; } +float GameData::getUserRating() const +{ + return mUserRating; +} + +void GameData::setUserRating(float rating) +{ + mUserRating = rating; +} + size_t GameData::getTimesPlayed() const { return mTimesPlayed; @@ -83,6 +95,16 @@ void GameData::setTimesPlayed(size_t timesPlayed) mTimesPlayed = timesPlayed; } +std::time_t GameData::getLastPlayed() const +{ + return mLastPlayed; +} + +void GameData::setLastPlayed(std::time_t lastPlayed) +{ + mLastPlayed = lastPlayed; +} + std::string GameData::getBashPath() const { //a quick and dirty way to insert a backslash before most characters that would mess up a bash path diff --git a/src/GameData.h b/src/GameData.h index edcede2c2..ebfca5e69 100644 --- a/src/GameData.h +++ b/src/GameData.h @@ -2,6 +2,8 @@ #define _GAMEDATA_H_ #include +#include + #include "FileData.h" #include "SystemData.h" @@ -18,7 +20,9 @@ public: static const std::string xmlTagDescription; static const std::string xmlTagImagePath; static const std::string xmlTagRating; + static const std::string xmlTagUserRating; static const std::string xmlTagTimesPlayed; + static const std::string xmlTagLastPlayed; GameData(SystemData* system, std::string path, std::string name); @@ -37,9 +41,15 @@ public: float getRating() const; void setRating(float rating); + float getUserRating() const; + void setUserRating(float rating); + size_t getTimesPlayed() const; void setTimesPlayed(size_t timesPlayed); + std::time_t getLastPlayed() const; + void setLastPlayed(std::time_t lastPlayed); + std::string getBashPath() const; std::string getBaseName() const; @@ -53,7 +63,9 @@ private: std::string mDescription; std::string mImagePath; float mRating; + float mUserRating; size_t mTimesPlayed; + std::time_t mLastPlayed; }; #endif diff --git a/src/SystemData.cpp b/src/SystemData.cpp index d77bfb024..041b47716 100644 --- a/src/SystemData.cpp +++ b/src/SystemData.cpp @@ -95,8 +95,9 @@ void SystemData::launchGame(Window* window, GameData* game) VolumeControl::getInstance()->init(); AudioManager::getInstance()->init(); - //update number of times the game has been launched + //update number of times the game has been launched and the time game->setTimesPlayed(game->getTimesPlayed() + 1); + game->setLastPlayed(std::time(nullptr)); } void SystemData::populateFolder(FolderData* folder) diff --git a/src/XMLReader.cpp b/src/XMLReader.cpp index 47e6527aa..d8a8f0eea 100644 --- a/src/XMLReader.cpp +++ b/src/XMLReader.cpp @@ -186,12 +186,24 @@ void parseGamelist(SystemData* system) std::istringstream(gameNode.child(GameData::xmlTagRating.c_str()).text().get()) >> rating; game->setRating(rating); } + if(gameNode.child(GameData::xmlTagUserRating.c_str())) + { + float userRating; + std::istringstream(gameNode.child(GameData::xmlTagUserRating.c_str()).text().get()) >> userRating; + game->setUserRating(userRating); + } if(gameNode.child(GameData::xmlTagTimesPlayed.c_str())) { size_t timesPlayed; std::istringstream(gameNode.child(GameData::xmlTagTimesPlayed.c_str()).text().get()) >> timesPlayed; game->setTimesPlayed(timesPlayed); } + if(gameNode.child(GameData::xmlTagLastPlayed.c_str())) + { + std::time_t lastPlayed; + std::istringstream(gameNode.child(GameData::xmlTagLastPlayed.c_str()).text().get()) >> lastPlayed; + game->setLastPlayed(lastPlayed); + } } else{ LOG(LogWarning) << "Game at \"" << path << "\" does not exist!"; @@ -223,8 +235,15 @@ void addGameDataNode(pugi::xml_node & parent, const GameData * game) //all other values are added regardless of their value pugi::xml_node ratingNode = newGame.append_child(GameData::xmlTagRating.c_str()); ratingNode.text().set(std::to_string((long double)game->getRating()).c_str()); + + pugi::xml_node userRatingNode = newGame.append_child(GameData::xmlTagUserRating.c_str()); + userRatingNode.text().set(std::to_string((long double)game->getUserRating()).c_str()); + pugi::xml_node timesPlayedNode = newGame.append_child(GameData::xmlTagTimesPlayed.c_str()); timesPlayedNode.text().set(std::to_string((unsigned long long)game->getTimesPlayed()).c_str()); + + pugi::xml_node lastPlayedNode = newGame.append_child(GameData::xmlTagLastPlayed.c_str()); + lastPlayedNode.text().set(std::to_string((unsigned long long)game->getLastPlayed()).c_str()); } void updateGamelist(SystemData* system) From a60fe463d4b6f02fbcb7ef8d4614541788ad32ac Mon Sep 17 00:00:00 2001 From: Bim Overbohm Date: Fri, 28 Jun 2013 19:44:28 +0200 Subject: [PATCH 04/16] Support sorting of game list via input You can now map the functions "sortordernext" and "sortorderprevious" to inputs (in es_input.cfg) and toggle the game list sort order with them. The order is: "file name, ascending" (default), "file name, descending", "rating ascending", "rating descending", "user rating ascending", "user rating descending", "time played ascending", "times played descending", "last played time ascending", "last played time descending". --- src/FolderData.cpp | 47 +++++++++++++++++------ src/FolderData.h | 23 ++++++++++-- src/InputManager.cpp | 3 ++ src/components/GuiGameList.cpp | 69 +++++++++++++++++++++++++++++++++- src/components/GuiGameList.h | 8 ++++ 5 files changed, 134 insertions(+), 16 deletions(-) diff --git a/src/FolderData.cpp b/src/FolderData.cpp index 2d229b675..ac9e08477 100644 --- a/src/FolderData.cpp +++ b/src/FolderData.cpp @@ -4,6 +4,9 @@ #include #include + +std::map FolderData::sortStateNameMap; + bool FolderData::isFolder() const { return true; } const std::string & FolderData::getName() const { return mName; } const std::string & FolderData::getPath() const { return mPath; } @@ -11,10 +14,16 @@ unsigned int FolderData::getFileCount() { return mFileVector.size(); } FolderData::FolderData(SystemData* system, std::string path, std::string name) + : mSystem(system), mPath(path), mName(name) { - mSystem = system; - mPath = path; - mName = name; + //first created folder data initializes the list + if (sortStateNameMap.empty()) { + sortStateNameMap[compareFileName] = "file name"; + sortStateNameMap[compareRating] = "rating"; + sortStateNameMap[compareUserRating] = "user rating"; + sortStateNameMap[compareTimesPlayed] = "times played"; + sortStateNameMap[compareLastPlayed] = "last time played"; + } } FolderData::~FolderData() @@ -32,6 +41,22 @@ void FolderData::pushFileData(FileData* file) mFileVector.push_back(file); } +//sort this folder and any subfolders +void FolderData::sort(ComparisonFunction & comparisonFunction, bool ascending) +{ + std::sort(mFileVector.begin(), mFileVector.end(), comparisonFunction); + + for(unsigned int i = 0; i < mFileVector.size(); i++) + { + if(mFileVector.at(i)->isFolder()) + ((FolderData*)mFileVector.at(i))->sort(comparisonFunction, ascending); + } + + if (!ascending) { + std::reverse(mFileVector.begin(), mFileVector.end()); + } +} + //returns if file1 should come before file2 bool FolderData::compareFileName(const FileData* file1, const FileData* file2) { @@ -95,16 +120,16 @@ bool FolderData::compareLastPlayed(const FileData* file1, const FileData* file2) return false; } -//sort this folder and any subfolders -void FolderData::sort() +std::string FolderData::getSortStateName(ComparisonFunction & comparisonFunction, bool ascending) { - std::sort(mFileVector.begin(), mFileVector.end(), compareFileName); - - for(unsigned int i = 0; i < mFileVector.size(); i++) - { - if(mFileVector.at(i)->isFolder()) - ((FolderData*)mFileVector.at(i))->sort(); + std::string temp = sortStateNameMap[comparisonFunction]; + if (ascending) { + temp.append(" (ascending)"); } + else { + temp.append(" (descending)"); + } + return temp; } FileData* FolderData::getFile(unsigned int i) const diff --git a/src/FolderData.h b/src/FolderData.h index 7391f824e..6f47329c7 100644 --- a/src/FolderData.h +++ b/src/FolderData.h @@ -1,14 +1,30 @@ #ifndef _FOLDER_H_ #define _FOLDER_H_ -#include "FileData.h" +#include #include +#include "FileData.h" + + class SystemData; //This class lets us hold a vector of FileDatas within under a common name. class FolderData : public FileData { +public: + typedef bool ComparisonFunction(const FileData* a, const FileData* b); + struct SortState + { + ComparisonFunction & comparisonFunction; + bool ascending; + + SortState(ComparisonFunction & sortFunction, bool sortAscending) : comparisonFunction(sortFunction), ascending(sortAscending) {} + }; + +private: + static std::map sortStateNameMap; + public: FolderData(SystemData* system, std::string path, std::string name); ~FolderData(); @@ -24,13 +40,14 @@ public: void pushFileData(FileData* file); - void sort(); - + void sort(ComparisonFunction & comparisonFunction = compareFileName, bool ascending = true); static bool compareFileName(const FileData* file1, const FileData* file2); static bool compareRating(const FileData* file1, const FileData* file2); static bool compareUserRating(const FileData* file1, const FileData* file2); static bool compareTimesPlayed(const FileData* file1, const FileData* file2); static bool compareLastPlayed(const FileData* file1, const FileData* file2); + static std::string getSortStateName(ComparisonFunction & comparisonFunction = compareFileName, bool ascending = true); + private: SystemData* mSystem; std::string mPath; diff --git a/src/InputManager.cpp b/src/InputManager.cpp index e3490e207..8a4b916be 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -404,6 +404,9 @@ void InputManager::loadDefaultConfig() cfg->mapInput("mastervolup", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_PLUS, 1, true)); cfg->mapInput("mastervoldown", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_MINUS, 1, true)); + + cfg->mapInput("sortordernext", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F7, 1, true)); + cfg->mapInput("sortorderprevious", Input(DEVICE_KEYBOARD, TYPE_KEY, SDLK_F8, 1, true)); } void InputManager::writeConfig() diff --git a/src/components/GuiGameList.cpp b/src/components/GuiGameList.cpp index a65c94668..044aa0325 100644 --- a/src/components/GuiGameList.cpp +++ b/src/components/GuiGameList.cpp @@ -7,15 +7,31 @@ #include "../Log.h" #include "../Settings.h" + +std::vector GuiGameList::sortStates; + + Vector2i GuiGameList::getImagePos() { return Vector2i((int)(Renderer::getScreenWidth() * mTheme->getFloat("gameImageOffsetX")), (int)(Renderer::getScreenHeight() * mTheme->getFloat("gameImageOffsetY"))); } GuiGameList::GuiGameList(Window* window, bool useDetail) : GuiComponent(window), - mDescription(window), mTransitionImage(window, 0, 0, "", Renderer::getScreenWidth(), Renderer::getScreenHeight(), true) + mDescription(window), mTransitionImage(window, 0, 0, "", Renderer::getScreenWidth(), Renderer::getScreenHeight(), true), mDetailed(useDetail), sortStateIndex(0) { - mDetailed = useDetail; + //first object initializes the vector + if (sortStates.empty()) { + sortStates.push_back(FolderData::SortState(FolderData::compareFileName, true)); + sortStates.push_back(FolderData::SortState(FolderData::compareFileName, false)); + sortStates.push_back(FolderData::SortState(FolderData::compareRating, true)); + sortStates.push_back(FolderData::SortState(FolderData::compareRating, false)); + sortStates.push_back(FolderData::SortState(FolderData::compareUserRating, true)); + sortStates.push_back(FolderData::SortState(FolderData::compareUserRating, false)); + sortStates.push_back(FolderData::SortState(FolderData::compareTimesPlayed, true)); + sortStates.push_back(FolderData::SortState(FolderData::compareTimesPlayed, false)); + sortStates.push_back(FolderData::SortState(FolderData::compareLastPlayed, true)); + sortStates.push_back(FolderData::SortState(FolderData::compareLastPlayed, false)); + } mTheme = new ThemeComponent(mWindow, mDetailed); @@ -190,6 +206,16 @@ bool GuiGameList::input(InputConfig* config, Input input) } } + //change sort order + if(config->isMappedTo("sortordernext", input) && input.value != 0) { + setNextSortIndex(); + //std::cout << "Sort order is " << FolderData::getSortStateName(sortStates.at(sortStateIndex).comparisonFunction, sortStates.at(sortStateIndex).ascending) << std::endl; + } + else if(config->isMappedTo("sortorderprevious", input) && input.value != 0) { + setPreviousSortIndex(); + //std::cout << "Sort order is " << FolderData::getSortStateName(sortStates.at(sortStateIndex).comparisonFunction, sortStates.at(sortStateIndex).ascending) << std::endl; + } + //open the "start menu" if(config->isMappedTo("menu", input) && input.value != 0) { @@ -219,6 +245,45 @@ bool GuiGameList::input(InputConfig* config, Input input) return false; } +void GuiGameList::setSortIndex(size_t index) +{ + //make the index valid + if (index >= sortStates.size()) { + index = 0; + } + if (index != sortStateIndex) { + //get sort state from vector and sort list + sortStateIndex = index; + sort(sortStates.at(sortStateIndex).comparisonFunction, sortStates.at(sortStateIndex).ascending); + } +} + +void GuiGameList::setNextSortIndex() +{ + //make the index wrap around + if ((sortStateIndex - 1) >= sortStates.size()) { + setSortIndex(0); + } + setSortIndex(sortStateIndex + 1); +} + +void GuiGameList::setPreviousSortIndex() +{ + //make the index wrap around + if (((int)sortStateIndex - 1) < 0) { + setSortIndex(sortStates.size() - 1); + } + setSortIndex(sortStateIndex - 1); +} + +void GuiGameList::sort(FolderData::ComparisonFunction & comparisonFunction, bool ascending) +{ + //resort list and update it + mFolder->sort(comparisonFunction, ascending); + updateList(); + updateDetailData(); +} + void GuiGameList::updateList() { if(mDetailed) diff --git a/src/components/GuiGameList.h b/src/components/GuiGameList.h index 7f51a0f15..2dae2f2ba 100644 --- a/src/components/GuiGameList.h +++ b/src/components/GuiGameList.h @@ -17,6 +17,9 @@ //It has a TextListComponent child that handles the game list, a ThemeComponent that handles the theming system, and an ImageComponent for game images. class GuiGameList : public GuiComponent { + static std::vector sortStates; + size_t sortStateIndex; + public: GuiGameList(Window* window, bool useDetail = false); virtual ~GuiGameList(); @@ -32,6 +35,11 @@ public: void updateDetailData(); + void setSortIndex(size_t index); + void setNextSortIndex(); + void setPreviousSortIndex(); + void sort(FolderData::ComparisonFunction & comparisonFunction = FolderData::compareFileName, bool ascending = true); + static GuiGameList* create(Window* window); static const float sInfoWidth; From 3971fdc674fc8addc4a1a17501e3b3133358e037 Mon Sep 17 00:00:00 2001 From: Aloshi Date: Sat, 29 Jun 2013 20:57:14 -0500 Subject: [PATCH 05/16] Fix last entry not showing (issue #90) --- src/components/TextListComponent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TextListComponent.h b/src/components/TextListComponent.h index 0aea6cea4..e5c16e2fd 100644 --- a/src/components/TextListComponent.h +++ b/src/components/TextListComponent.h @@ -117,7 +117,7 @@ void TextListComponent::onRender() //number of entries that can fit on the screen simultaniously int screenCount = (Renderer::getScreenHeight() - cutoff) / entrySize; - //screenCount -= 1; + screenCount -= 1; if((int)mRowVector.size() >= screenCount) { From 94e32f198bfb902ea9ef4c2b24a500902884d410 Mon Sep 17 00:00:00 2001 From: Bim Date: Sun, 30 Jun 2013 19:24:09 +0200 Subject: [PATCH 06/16] Make sort order changeable via fast-select menu Use the left/right keys to switch it. --- src/FolderData.h | 3 ++- src/components/GuiFastSelect.cpp | 34 +++++++++++++++++++++----------- src/components/GuiFastSelect.h | 6 +++--- src/components/GuiGameList.cpp | 27 ++++++++++++++----------- src/components/GuiGameList.h | 1 + 5 files changed, 45 insertions(+), 26 deletions(-) diff --git a/src/FolderData.h b/src/FolderData.h index 6f47329c7..ecbd844fb 100644 --- a/src/FolderData.h +++ b/src/FolderData.h @@ -18,8 +18,9 @@ public: { ComparisonFunction & comparisonFunction; bool ascending; + std::string description; - SortState(ComparisonFunction & sortFunction, bool sortAscending) : comparisonFunction(sortFunction), ascending(sortAscending) {} + SortState(ComparisonFunction & sortFunction, bool sortAscending, const std::string & sortDescription) : comparisonFunction(sortFunction), ascending(sortAscending), description(sortDescription) {} }; private: diff --git a/src/components/GuiFastSelect.cpp b/src/components/GuiFastSelect.cpp index 008f4b972..fee1ba337 100644 --- a/src/components/GuiFastSelect.cpp +++ b/src/components/GuiFastSelect.cpp @@ -7,17 +7,15 @@ const std::string GuiFastSelect::LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const int GuiFastSelect::SCROLLSPEED = 100; const int GuiFastSelect::SCROLLDELAY = 507; -GuiFastSelect::GuiFastSelect(Window* window, GuiGameList* parent, TextListComponent* list, char startLetter, GuiBoxData data, - int textcolor, std::shared_ptr & scrollsound, Font* font) : GuiComponent(window) +GuiFastSelect::GuiFastSelect(Window* window, GuiGameList* parent, TextListComponent* list, char startLetter, ThemeComponent * theme) + : GuiComponent(window), mParent(parent), mList(list), mTheme(theme) { mLetterID = LETTERS.find(toupper(startLetter)); if(mLetterID == std::string::npos) mLetterID = 0; - mParent = parent; - mList = list; - mScrollSound = scrollsound; - mFont = font; + mScrollSound = mTheme->getSound("menuScroll"); + mTextColor = mTheme->getColor("fastSelect"); mScrolling = false; mScrollTimer = 0; @@ -25,9 +23,7 @@ GuiFastSelect::GuiFastSelect(Window* window, GuiGameList* parent, TextListCompon unsigned int sw = Renderer::getScreenWidth(), sh = Renderer::getScreenHeight(); mBox = new GuiBox(window, (int)(sw * 0.2f), (int)(sh * 0.2f), (int)(sw * 0.6f), (int)(sh * 0.6f)); - mBox->setData(data); - - mTextColor = textcolor; + mBox->setData(mTheme->getBoxData()); } GuiFastSelect::~GuiFastSelect() @@ -41,11 +37,14 @@ void GuiFastSelect::render() unsigned int sw = Renderer::getScreenWidth(), sh = Renderer::getScreenHeight(); if(!mBox->hasBackground()) - Renderer::drawRect((int)(sw * 0.2f), (int)(sh * 0.2f), (int)(sw * 0.6f), (int)(sh * 0.6f), 0x000FF0FF); + Renderer::drawRect((int)(sw * 0.3f), (int)(sh * 0.3f), (int)(sw * 0.4f), (int)(sh * 0.4f), 0x000FF0AA); mBox->render(); - Renderer::drawCenteredText(LETTERS.substr(mLetterID, 1), 0, (int)(sh * 0.5f - (mFont->getHeight() * 0.5f)), mTextColor, mFont); + Renderer::drawCenteredText(LETTERS.substr(mLetterID, 1), 0, (int)(sh * 0.5f - (mTheme->getFastSelectFont()->getHeight() * 0.5f)), mTextColor, mTheme->getFastSelectFont()); + Renderer::drawCenteredText("Sort order:", 0, (int)(sh * 0.6f - (mTheme->getDescriptionFont()->getHeight() * 0.5f)), mTextColor, mTheme->getDescriptionFont()); + std::string sortString = "<- " + mParent->getSortState().description + " ->"; + Renderer::drawCenteredText(sortString, 0, (int)(sh * 0.6f + (mTheme->getDescriptionFont()->getHeight() * 0.5f)), mTextColor, mTheme->getDescriptionFont()); } bool GuiFastSelect::input(InputConfig* config, Input input) @@ -64,6 +63,19 @@ bool GuiFastSelect::input(InputConfig* config, Input input) return true; } + if(config->isMappedTo("left", input) && input.value != 0) + { + mParent->setPreviousSortIndex(); + mScrollSound->play(); + return true; + } + else if(config->isMappedTo("right", input) && input.value != 0) + { + mParent->setNextSortIndex(); + mScrollSound->play(); + return true; + } + if((config->isMappedTo("up", input) || config->isMappedTo("down", input)) && input.value == 0) { mScrolling = false; diff --git a/src/components/GuiFastSelect.h b/src/components/GuiFastSelect.h index 67032e1f1..81ae2ec4d 100644 --- a/src/components/GuiFastSelect.h +++ b/src/components/GuiFastSelect.h @@ -5,6 +5,7 @@ #include "../SystemData.h" #include "../FolderData.h" #include "../Sound.h" +#include "ThemeComponent.h" #include "TextListComponent.h" #include "GuiBox.h" @@ -13,8 +14,7 @@ class GuiGameList; class GuiFastSelect : public GuiComponent { public: - GuiFastSelect(Window* window, GuiGameList* parent, TextListComponent* list, char startLetter, GuiBoxData data, - int textcolor, std::shared_ptr & scrollsound, Font* font); + GuiFastSelect(Window* window, GuiGameList* parent, TextListComponent* list, char startLetter, ThemeComponent * theme); ~GuiFastSelect(); bool input(InputConfig* config, Input input); @@ -41,7 +41,7 @@ private: bool mScrolling; std::shared_ptr mScrollSound; - Font* mFont; + ThemeComponent * mTheme; }; #endif diff --git a/src/components/GuiGameList.cpp b/src/components/GuiGameList.cpp index c77775f46..e222de28e 100644 --- a/src/components/GuiGameList.cpp +++ b/src/components/GuiGameList.cpp @@ -21,16 +21,16 @@ GuiGameList::GuiGameList(Window* window, bool useDetail) : GuiComponent(window), { //first object initializes the vector if (sortStates.empty()) { - sortStates.push_back(FolderData::SortState(FolderData::compareFileName, true)); - sortStates.push_back(FolderData::SortState(FolderData::compareFileName, false)); - sortStates.push_back(FolderData::SortState(FolderData::compareRating, true)); - sortStates.push_back(FolderData::SortState(FolderData::compareRating, false)); - sortStates.push_back(FolderData::SortState(FolderData::compareUserRating, true)); - sortStates.push_back(FolderData::SortState(FolderData::compareUserRating, false)); - sortStates.push_back(FolderData::SortState(FolderData::compareTimesPlayed, true)); - sortStates.push_back(FolderData::SortState(FolderData::compareTimesPlayed, false)); - sortStates.push_back(FolderData::SortState(FolderData::compareLastPlayed, true)); - sortStates.push_back(FolderData::SortState(FolderData::compareLastPlayed, false)); + sortStates.push_back(FolderData::SortState(FolderData::compareFileName, true, "file name, ascending")); + sortStates.push_back(FolderData::SortState(FolderData::compareFileName, false, "file name, descending")); + sortStates.push_back(FolderData::SortState(FolderData::compareRating, true, "database rating, ascending")); + sortStates.push_back(FolderData::SortState(FolderData::compareRating, false, "database rating, descending")); + sortStates.push_back(FolderData::SortState(FolderData::compareUserRating, true, "your rating, ascending")); + sortStates.push_back(FolderData::SortState(FolderData::compareUserRating, false, "your rating, descending")); + sortStates.push_back(FolderData::SortState(FolderData::compareTimesPlayed, true, "played least often")); + sortStates.push_back(FolderData::SortState(FolderData::compareTimesPlayed, false, "played most often")); + sortStates.push_back(FolderData::SortState(FolderData::compareLastPlayed, true, "played least recently")); + sortStates.push_back(FolderData::SortState(FolderData::compareLastPlayed, false, "played most recently")); } mTheme = new ThemeComponent(mWindow, mDetailed); @@ -227,7 +227,7 @@ bool GuiGameList::input(InputConfig* config, Input input) //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->getBoxData(), mTheme->getColor("fastSelect"), mTheme->getSound("menuScroll"), mTheme->getFastSelectFont())); + mWindow->pushGui(new GuiFastSelect(mWindow, this, mList, mList->getSelectedObject()->getName()[0], mTheme)); return true; } @@ -246,6 +246,11 @@ bool GuiGameList::input(InputConfig* config, Input input) return false; } +const FolderData::SortState & GuiGameList::getSortState() const +{ + return sortStates.at(sortStateIndex); +} + void GuiGameList::setSortIndex(size_t index) { //make the index valid diff --git a/src/components/GuiGameList.h b/src/components/GuiGameList.h index 2dae2f2ba..8d270510b 100644 --- a/src/components/GuiGameList.h +++ b/src/components/GuiGameList.h @@ -35,6 +35,7 @@ public: void updateDetailData(); + const FolderData::SortState & getSortState() const; void setSortIndex(size_t index); void setNextSortIndex(); void setPreviousSortIndex(); From ec7ad28fdc5188191d561342d388b68dd19f93f8 Mon Sep 17 00:00:00 2001 From: Aloshi Date: Tue, 2 Jul 2013 00:57:31 -0500 Subject: [PATCH 07/16] Finally added scrolling description text. :) --- src/Renderer.h | 1 + src/Renderer_draw_gl.cpp | 57 ++++++++++++++++++++ src/Vector2.h | 1 + src/components/GuiGameList.cpp | 13 +++-- src/components/TextComponent.cpp | 90 ++++++++++++++++++++++++++++++-- src/components/TextComponent.h | 16 +++++- 6 files changed, 168 insertions(+), 10 deletions(-) diff --git a/src/Renderer.h b/src/Renderer.h index f70b8c83b..80e2ab437 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -43,6 +43,7 @@ namespace Renderer void drawText(std::string text, int x, int y, unsigned int color, Font* font); void drawCenteredText(std::string text, int xOffset, int y, unsigned int color, Font* font); void drawWrappedText(std::string text, int xStart, int yStart, int xLen, unsigned int color, Font* font); + void sizeWrappedText(std::string text, int xLen, Font* font, int* xOut, int* yOut); } #endif diff --git a/src/Renderer_draw_gl.cpp b/src/Renderer_draw_gl.cpp index afa0b1d4b..10fa92f33 100644 --- a/src/Renderer_draw_gl.cpp +++ b/src/Renderer_draw_gl.cpp @@ -252,4 +252,61 @@ namespace Renderer { } } + void sizeWrappedText(std::string text, int xLen, Font* font, int* xOut, int* yOut) + { + *xOut = xLen; + + int y = 0; + + std::string line, word, temp; + int w, h; + size_t space, newline; + + while(text.length() > 0 || !line.empty()) //while there's text or we still have text to render + { + space = text.find(' ', 0); + if(space == std::string::npos) + space = text.length() - 1; + + word = text.substr(0, space + 1); + + //check if the next word contains a newline + newline = word.find('\n', 0); + if(newline != std::string::npos) + { + word = word.substr(0, newline); + text.erase(0, newline + 1); + }else{ + text.erase(0, space + 1); + } + + temp = line + word; + + font->sizeText(temp, &w, &h); + + //if we're on the last word and it'll fit on the line, just add it to the line + if((w <= xLen && text.length() == 0) || newline != std::string::npos) + { + line = temp; + word = ""; + } + + //if the next line will be too long or we're on the last of the text, render it + if(w > xLen || text.length() == 0 || newline != std::string::npos) + { + //increment y by height and some extra padding for the next line + y += h + 4; + + //move the word we skipped to the next line + line = word; + }else{ + //there's still space, continue building the line + line = temp; + } + + } + + *yOut = y; + } + }; diff --git a/src/Vector2.h b/src/Vector2.h index 672c8ab3e..ddb6e1fd7 100644 --- a/src/Vector2.h +++ b/src/Vector2.h @@ -93,6 +93,7 @@ bool operator !=(const Vector2& left, const Vector2& right) typedef Vector2 Vector2i; typedef Vector2 Vector2u; typedef Vector2 Vector2f; +typedef Vector2 Vector2d; class Rect { diff --git a/src/components/GuiGameList.cpp b/src/components/GuiGameList.cpp index c91d48ade..d6b2cd60d 100644 --- a/src/components/GuiGameList.cpp +++ b/src/components/GuiGameList.cpp @@ -36,9 +36,11 @@ GuiGameList::GuiGameList(Window* window, bool useDetail) : GuiComponent(window), mScreenshot->setOrigin(mTheme->getFloat("gameImageOriginX"), mTheme->getFloat("gameImageOriginY")); - mDescription.setOffset(Vector2i((int)(Renderer::getScreenWidth() * 0.03), mScreenshot->getOffset().y + mScreenshot->getSize().y + 12)); - mDescription.setExtent(Vector2u((int)(Renderer::getScreenWidth() * (mTheme->getFloat("listOffsetX") - 0.03)), 0)); - + mDescription.setOffset(Vector2i((int)(Renderer::getScreenWidth() * 0.03), getImagePos().y + mScreenshot->getSize().y + 12)); + //scale delay with screen width (higher width = more text per line) + //the scroll speed is automatically scrolled by component size + mDescription.setAutoScroll((int)(1500 + (Renderer::getScreenWidth() * 0.5)), 0.025f); + mTransitionImage.setOffset(Renderer::getScreenWidth(), 0); mTransitionImage.setOrigin(0, 0); mTransitionAnimation.addChild(&mTransitionImage); @@ -307,7 +309,8 @@ void GuiGameList::updateDetailData() mImageAnimation->fadeIn(35); mImageAnimation->move(imgOffset.x, imgOffset.y, 20); - mDescription.setOffset(Vector2i((int)(Renderer::getScreenWidth() * 0.03), mScreenshot->getOffset().y + mScreenshot->getSize().y + 12)); + mDescription.setOffset(Vector2i((int)(Renderer::getScreenWidth() * 0.03), getImagePos().y + mScreenshot->getSize().y + 12)); + mDescription.setExtent(Vector2u((int)(Renderer::getScreenWidth() * (mTheme->getFloat("listOffsetX") - 0.03)), Renderer::getScreenHeight() - mDescription.getOffset().y)); mDescription.setText(((GameData*)mList->getSelectedObject())->getDescription()); }else{ mScreenshot->setImage(""); @@ -373,6 +376,8 @@ void GuiGameList::update(int deltaTime) mTransitionAnimation.update(deltaTime); mList->update(deltaTime); + + mDescription.update(deltaTime); } void GuiGameList::doTransition(int dir) diff --git a/src/components/TextComponent.cpp b/src/components/TextComponent.cpp index 76312e80c..477a19417 100644 --- a/src/components/TextComponent.cpp +++ b/src/components/TextComponent.cpp @@ -2,12 +2,13 @@ #include "../Renderer.h" #include "../Log.h" -TextComponent::TextComponent(Window* window) : GuiComponent(window), mFont(NULL), mColor(0x000000FF), mAutoCalcExtent(true) +TextComponent::TextComponent(Window* window) : GuiComponent(window), + mFont(NULL), mColor(0x000000FF), mAutoCalcExtent(true), mAutoScrollDelay(0), mAutoScrollSpeed(0), mAutoScrollTimer(0) { } TextComponent::TextComponent(Window* window, const std::string& text, Font* font, Vector2i pos, Vector2u size) : GuiComponent(window), - mFont(NULL), mColor(0x000000FF), mAutoCalcExtent(true) + mFont(NULL), mColor(0x000000FF), mAutoCalcExtent(true), mAutoScrollDelay(0), mAutoScrollSpeed(0), mAutoScrollTimer(0) { setText(text); setFont(font); @@ -51,11 +52,20 @@ void TextComponent::setText(const std::string& text) if(mAutoCalcExtent) calculateExtent(); + + mScrollPos = Vector2d(0, 0); + mScrollDir = Vector2d(0, 0); + resetAutoScrollTimer(); +} + +Font* TextComponent::getFont() const +{ + return (mFont ? mFont : Renderer::getDefaultFont(Renderer::MEDIUM));; } void TextComponent::onRender() { - Font* font = (mFont ? mFont : Renderer::getDefaultFont(Renderer::MEDIUM)); + Font* font = getFont(); if(font == NULL) { LOG(LogError) << "TextComponent can't get a valid font!"; @@ -64,7 +74,7 @@ void TextComponent::onRender() Renderer::pushClipRect(getGlobalOffset(), getSize()); - Renderer::drawWrappedText(mText, 0, 0, mSize.x, mColor, font); + Renderer::drawWrappedText(mText, (int)-mScrollPos.x, (int)-mScrollPos.y, mSize.x, mColor, font); Renderer::popClipRect(); @@ -73,7 +83,7 @@ void TextComponent::onRender() void TextComponent::calculateExtent() { - Font* font = (mFont ? mFont : Renderer::getDefaultFont(Renderer::MEDIUM)); + Font* font = getFont(); if(font == NULL) { LOG(LogError) << "TextComponent can't get a valid font!"; @@ -82,3 +92,73 @@ void TextComponent::calculateExtent() font->sizeText(mText, (int*)&mSize.x, (int*)&mSize.y); } + +void TextComponent::setAutoScroll(int delay, double speed) +{ + mAutoScrollDelay = delay; + mAutoScrollSpeed = speed; + resetAutoScrollTimer(); +} + +void TextComponent::resetAutoScrollTimer() +{ + mAutoScrollTimer = 0; +} + +Vector2d TextComponent::getScrollPos() const +{ + return mScrollPos; +} + +void TextComponent::setScrollPos(const Vector2d& pos) +{ + mScrollPos = pos; +} + +void TextComponent::update(int deltaTime) +{ + double scrollAmt = (double)deltaTime; + + if(mAutoScrollSpeed != 0) + { + mAutoScrollTimer += deltaTime; + + scrollAmt = (float)(mAutoScrollTimer - mAutoScrollDelay); + + if(scrollAmt > 0) + { + //scroll the amount of time left over from the delay + mAutoScrollTimer = mAutoScrollDelay; + + //scale speed by our width! more text per line = slower scrolling + const double widthMod = (680.0 / getSize().x); + mScrollDir = Vector2d(0, mAutoScrollSpeed * widthMod); + }else{ + //not enough to pass the delay, do nothing + scrollAmt = 0; + } + } + + Vector2d scroll = mScrollDir * scrollAmt; + mScrollPos += scroll; + + //clip scrolling within bounds + if(mScrollPos.x < 0) + mScrollPos.x = 0; + if(mScrollPos.y < 0) + mScrollPos.y = 0; + + Font* font = getFont(); + if(font != NULL) + { + Vector2i textSize; + Renderer::sizeWrappedText(mText, getSize().x, getFont(), &textSize.x, &textSize.y); + + if(mScrollPos.x + getSize().x > textSize.x) + mScrollPos.x = (double)textSize.x - getSize().x; + if(mScrollPos.y + getSize().y > textSize.y) + mScrollPos.y = (double)textSize.y - getSize().y; + } + + GuiComponent::update(deltaTime); +} diff --git a/src/components/TextComponent.h b/src/components/TextComponent.h index 67a6f9b5d..a77a7ee5a 100644 --- a/src/components/TextComponent.h +++ b/src/components/TextComponent.h @@ -16,15 +16,29 @@ public: void setText(const std::string& text); void setColor(unsigned int color); - void onRender(); + Vector2d getScrollPos() const; + void setScrollPos(const Vector2d& pos); + void setAutoScroll(int delay, double speed); //Use 0 for speed to disable. + void resetAutoScrollTimer(); + + void update(int deltaTime) override; + void onRender() override; private: + Font* getFont() const; void calculateExtent(); unsigned int mColor; Font* mFont; bool mAutoCalcExtent; std::string mText; + + //scrolling + Vector2d mScrollPos; + Vector2d mScrollDir; + int mAutoScrollDelay; + double mAutoScrollSpeed; + int mAutoScrollTimer; }; #endif From e785a2dfe001c580be48653d745a314a8bace6c0 Mon Sep 17 00:00:00 2001 From: Aloshi Date: Tue, 2 Jul 2013 02:04:52 -0500 Subject: [PATCH 08/16] You can now mix and match game list detail levels. Basically, only games that have a gamelist.xml will use the detailed view. --- README.md | 2 +- src/components/GuiGameList.cpp | 193 ++++++++++++------------------ src/components/GuiGameList.h | 15 +-- src/components/ThemeComponent.cpp | 11 +- src/components/ThemeComponent.h | 5 +- 5 files changed, 92 insertions(+), 134 deletions(-) diff --git a/README.md b/README.md index 5a1700639..d96a20257 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ The gamelist.xml for a system defines metadata for a system's games. This metada **Making a gamelist.xml by hand sucks, so a cool guy named Pendor made a python script which automatically generates a gamelist.xml for you, with boxart automatically downloaded. It can be found here:** https://github.com/elpendor/ES-scraper -If a file named gamelist.xml is found in the root of a system's search directory OR within `~/.emulationstation/%NAME%/`, it will be parsed and the detailed GuiGameList will be used. This means you can define images, descriptions, and different names for files. Note that only standard ASCII characters are supported (if you see a weird [X] symbol, you're probably using unicode!). +If a file named gamelist.xml is found in the root of a system's search directory OR within `~/.emulationstation/%NAME%/`, game metadata will be loaded from it. This allows you to define images, descriptions, and different names for files. Note that only standard ASCII characters are supported for text (if you see a weird [X] symbol, you're probably using unicode!). Images will be automatically resized to fit within the left column of the screen. Smaller images will load faster, so try to keep your resolution low. An example gamelist.xml: ``` diff --git a/src/components/GuiGameList.cpp b/src/components/GuiGameList.cpp index d6b2cd60d..0b66c944e 100644 --- a/src/components/GuiGameList.cpp +++ b/src/components/GuiGameList.cpp @@ -12,31 +12,23 @@ Vector2i GuiGameList::getImagePos() return Vector2i((int)(Renderer::getScreenWidth() * mTheme->getFloat("gameImageOffsetX")), (int)(Renderer::getScreenHeight() * mTheme->getFloat("gameImageOffsetY"))); } -GuiGameList::GuiGameList(Window* window, bool useDetail) : GuiComponent(window), - mDescription(window), mTransitionImage(window, 0, 0, "", Renderer::getScreenWidth(), Renderer::getScreenHeight(), true) +bool GuiGameList::isDetailed() const { - mDetailed = useDetail; + if(mSystem == NULL) + return false; - mTheme = new ThemeComponent(mWindow, mDetailed); + return mSystem->hasGamelist(); +} - mScreenshot = new ImageComponent(mWindow, getImagePos().x, getImagePos().y, "", (unsigned int)mTheme->getFloat("gameImageWidth"), (unsigned int)mTheme->getFloat("gameImageHeight"), false); +GuiGameList::GuiGameList(Window* window) : GuiComponent(window), + mTheme(new ThemeComponent(mWindow)), + mList(window, 0, 0, Renderer::getDefaultFont(Renderer::MEDIUM)), + mScreenshot(window), + mDescription(window), + mTransitionImage(window, 0, 0, "", Renderer::getScreenWidth(), Renderer::getScreenHeight(), true) +{ + mImageAnimation.addChild(&mScreenshot); - //The GuiGameList can use the older, simple game list if so desired. - //The old view only shows a list in the center of the screen; the new view can display an image and description. - //Those with smaller displays may prefer the older view. - if(mDetailed) - { - mList = new TextListComponent(mWindow, (int)(Renderer::getScreenWidth() * mTheme->getFloat("listOffsetX")), Renderer::getDefaultFont(Renderer::LARGE)->getHeight() + 2, Renderer::getDefaultFont(Renderer::MEDIUM)); - - mImageAnimation = new AnimationComponent(); - mImageAnimation->addChild(mScreenshot); - }else{ - mList = new TextListComponent(mWindow, 0, Renderer::getDefaultFont(Renderer::LARGE)->getHeight() + 2, Renderer::getDefaultFont(Renderer::MEDIUM)); - } - - mScreenshot->setOrigin(mTheme->getFloat("gameImageOriginX"), mTheme->getFloat("gameImageOriginY")); - - mDescription.setOffset(Vector2i((int)(Renderer::getScreenWidth() * 0.03), getImagePos().y + mScreenshot->getSize().y + 12)); //scale delay with screen width (higher width = more text per line) //the scroll speed is automatically scrolled by component size mDescription.setAutoScroll((int)(1500 + (Renderer::getScreenWidth() * 0.5)), 0.025f); @@ -49,7 +41,7 @@ GuiGameList::GuiGameList(Window* window, bool useDetail) : GuiComponent(window), //the list depends on knowing it's final window coordinates (getGlobalOffset), which requires knowing the where the GuiGameList is. //the GuiGameList now moves during screen transitions, so we have to let it know somehow. //this should be removed in favor of using real children soon. - mList->setParent(this); + mList.setParent(this); setSystemId(0); } @@ -57,15 +49,8 @@ GuiGameList::GuiGameList(Window* window, bool useDetail) : GuiComponent(window), GuiGameList::~GuiGameList() { //undo the parenting hack because otherwise it's not really a child and will try to remove itself on delete - mList->setParent(NULL); - delete mList; - delete mScreenshot; - - if(mDetailed) - { - delete mImageAnimation; - } - + mList.setParent(NULL); + delete mTheme; } @@ -112,22 +97,17 @@ void GuiGameList::render() if(!mTheme->getBool("hideHeader")) Renderer::drawCenteredText(mSystem->getDescName(), 0, 1, 0xFF0000FF, Renderer::getDefaultFont(Renderer::LARGE)); - if(mDetailed) + if(isDetailed()) { //divider if(!mTheme->getBool("hideDividers")) Renderer::drawRect((int)(Renderer::getScreenWidth() * mTheme->getFloat("listOffsetX")) - 4, Renderer::getDefaultFont(Renderer::LARGE)->getHeight() + 2, 8, Renderer::getScreenHeight(), 0x0000FFFF); - mScreenshot->render(); - - //if we're not scrolling and we have selected a non-folder - if(!mList->isScrolling() && !mList->getSelectedObject()->isFolder()) - { - mDescription.render(); - } + mScreenshot.render(); + mDescription.render(); } - mList->render(); + mList.render(); Renderer::translate(-mOffset); @@ -136,14 +116,14 @@ void GuiGameList::render() bool GuiGameList::input(InputConfig* config, Input input) { - mList->input(config, input); + mList.input(config, input); if(config->isMappedTo("a", input) && mFolder->getFileCount() > 0 && input.value != 0) { //play select sound mTheme->getSound("menuSelect")->play(); - FileData* file = mList->getSelectedObject(); + FileData* file = mList.getSelectedObject(); if(file->isFolder()) //if you selected a folder, add this directory to the stack, and use the selected one { mFolderStack.push(mFolder); @@ -152,7 +132,7 @@ bool GuiGameList::input(InputConfig* config, Input input) updateDetailData(); return true; }else{ - mList->stopScrolling(); + mList.stopScrolling(); //wait for the sound to finish or we'll never hear it... while(mTheme->getSound("menuSelect")->isPlaying()); @@ -203,11 +183,11 @@ bool GuiGameList::input(InputConfig* config, Input input) //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->getBoxData(), mTheme->getColor("fastSelect"), mTheme->getSound("menuScroll"), mTheme->getFastSelectFont())); + mWindow->pushGui(new GuiFastSelect(mWindow, this, &mList, mList.getSelectedObject()->getName()[0], mTheme->getBoxData(), mTheme->getColor("fastSelect"), mTheme->getSound("menuScroll"), mTheme->getFastSelectFont())); return true; } - if(mDetailed) + if(isDetailed()) { if(config->isMappedTo("up", input) || config->isMappedTo("down", input) || config->isMappedTo("pageup", input) || config->isMappedTo("pagedown", input)) { @@ -224,19 +204,16 @@ bool GuiGameList::input(InputConfig* config, Input input) void GuiGameList::updateList() { - if(mDetailed) - mScreenshot->setImage(""); - - mList->clear(); + mList.clear(); for(unsigned int i = 0; i < mFolder->getFileCount(); i++) { FileData* file = mFolder->getFile(i); if(file->isFolder()) - mList->addObject(file->getName(), file, mTheme->getColor("secondary")); + mList.addObject(file->getName(), file, mTheme->getColor("secondary")); else - mList->addObject(file->getName(), file, mTheme->getColor("primary")); + mList.addObject(file->getName(), file, mTheme->getColor("primary")); } } @@ -263,65 +240,72 @@ std::string GuiGameList::getThemeFile() void GuiGameList::updateTheme() { - if(!mTheme) - return; + mTheme->readXML(getThemeFile(), isDetailed()); - mTheme->readXML( getThemeFile() ); + mList.setSelectorColor(mTheme->getColor("selector")); + mList.setSelectedTextColor(mTheme->getColor("selected")); + mList.setScrollSound(mTheme->getSound("menuScroll")); - mList->setSelectorColor(mTheme->getColor("selector")); - mList->setSelectedTextColor(mTheme->getColor("selected")); - mList->setScrollSound(mTheme->getSound("menuScroll")); + mList.setFont(mTheme->getListFont()); + mList.setOffset(0, Renderer::getDefaultFont(Renderer::LARGE)->getHeight() + 2); - //fonts - mList->setFont(mTheme->getListFont()); - - if(mDetailed) + if(isDetailed()) { - mList->setCentered(mTheme->getBool("listCentered")); + mList.setCentered(mTheme->getBool("listCentered")); - mList->setOffset((int)(mTheme->getFloat("listOffsetX") * Renderer::getScreenWidth()), mList->getOffset().y); - mList->setTextOffsetX((int)(mTheme->getFloat("listTextOffsetX") * Renderer::getScreenWidth())); + mList.setOffset((int)(mTheme->getFloat("listOffsetX") * Renderer::getScreenWidth()), mList.getOffset().y); + mList.setTextOffsetX((int)(mTheme->getFloat("listTextOffsetX") * Renderer::getScreenWidth())); - mScreenshot->setOffset((int)(mTheme->getFloat("gameImageOffsetX") * Renderer::getScreenWidth()), (int)(mTheme->getFloat("gameImageOffsetY") * Renderer::getScreenHeight())); - mScreenshot->setOrigin(mTheme->getFloat("gameImageOriginX"), mTheme->getFloat("gameImageOriginY")); - mScreenshot->setResize((int)mTheme->getFloat("gameImageWidth"), (int)mTheme->getFloat("gameImageHeight"), false); + mScreenshot.setOffset((int)(mTheme->getFloat("gameImageOffsetX") * Renderer::getScreenWidth()), (int)(mTheme->getFloat("gameImageOffsetY") * Renderer::getScreenHeight())); + mScreenshot.setOrigin(mTheme->getFloat("gameImageOriginX"), mTheme->getFloat("gameImageOriginY")); + mScreenshot.setResize((int)mTheme->getFloat("gameImageWidth"), (int)mTheme->getFloat("gameImageHeight"), false); mDescription.setColor(mTheme->getColor("description")); mDescription.setFont(mTheme->getDescriptionFont()); + }else{ + mList.setCentered(true); + mList.setOffset(0, mList.getOffset().y); } } void GuiGameList::updateDetailData() { - if(!mDetailed) - return; - - if(mList->getSelectedObject() && !mList->getSelectedObject()->isFolder()) + if(!isDetailed()) { - if(((GameData*)mList->getSelectedObject())->getImagePath().empty()) - mScreenshot->setImage(mTheme->getString("imageNotFoundPath")); - else - mScreenshot->setImage(((GameData*)mList->getSelectedObject())->getImagePath()); - - Vector2i imgOffset = Vector2i((int)(Renderer::getScreenWidth() * 0.10f), 0); - mScreenshot->setOffset(getImagePos() - imgOffset); - - mImageAnimation->fadeIn(35); - mImageAnimation->move(imgOffset.x, imgOffset.y, 20); - - mDescription.setOffset(Vector2i((int)(Renderer::getScreenWidth() * 0.03), getImagePos().y + mScreenshot->getSize().y + 12)); - mDescription.setExtent(Vector2u((int)(Renderer::getScreenWidth() * (mTheme->getFloat("listOffsetX") - 0.03)), Renderer::getScreenHeight() - mDescription.getOffset().y)); - mDescription.setText(((GameData*)mList->getSelectedObject())->getDescription()); + mScreenshot.setImage(""); + mDescription.setText(""); }else{ - mScreenshot->setImage(""); + //if we've selected a game + if(mList.getSelectedObject() && !mList.getSelectedObject()->isFolder()) + { + //set image to either "not found" image or metadata image + if(((GameData*)mList.getSelectedObject())->getImagePath().empty()) + mScreenshot.setImage(mTheme->getString("imageNotFoundPath")); + else + mScreenshot.setImage(((GameData*)mList.getSelectedObject())->getImagePath()); + + Vector2i imgOffset = Vector2i((int)(Renderer::getScreenWidth() * 0.10f), 0); + mScreenshot.setOffset(getImagePos() - imgOffset); + + mImageAnimation.fadeIn(35); + mImageAnimation.move(imgOffset.x, imgOffset.y, 20); + + mDescription.setOffset(Vector2i((int)(Renderer::getScreenWidth() * 0.03), getImagePos().y + mScreenshot.getSize().y + 12)); + mDescription.setExtent(Vector2u((int)(Renderer::getScreenWidth() * (mTheme->getFloat("listOffsetX") - 0.03)), Renderer::getScreenHeight() - mDescription.getOffset().y)); + mDescription.setText(((GameData*)mList.getSelectedObject())->getDescription()); + }else{ + mScreenshot.setImage(""); + mDescription.setText(""); + } } } void GuiGameList::clearDetailData() { - if(mDetailed) + if(isDetailed()) { - mImageAnimation->fadeOut(35); + mImageAnimation.fadeOut(35); + mDescription.setText(""); } } @@ -329,11 +313,8 @@ void GuiGameList::clearDetailData() //we have to manually call init/deinit on mTheme because it is not our child void GuiGameList::deinit() { - if(mDetailed) - { - mScreenshot->deinit(); - } - + mScreenshot.deinit(); + mTheme->deinit(); } @@ -341,41 +322,23 @@ void GuiGameList::init() { mTheme->init(); - if(mDetailed) - { - mScreenshot->init(); - } + mScreenshot.init(); } GuiGameList* GuiGameList::create(Window* window) { - bool detailed = false; - - if(!Settings::getInstance()->getBool("IGNOREGAMELIST")) - { - for(unsigned int i = 0; i < SystemData::sSystemVector.size(); i++) - { - if(SystemData::sSystemVector.at(i)->hasGamelist()) - { - detailed = true; - break; - } - } - } - - GuiGameList* list = new GuiGameList(window, detailed); + GuiGameList* list = new GuiGameList(window); window->pushGui(list); return list; } void GuiGameList::update(int deltaTime) { - if(mDetailed) - mImageAnimation->update(deltaTime); + mImageAnimation.update(deltaTime); mTransitionAnimation.update(deltaTime); - mList->update(deltaTime); + mList.update(deltaTime); mDescription.update(deltaTime); } diff --git a/src/components/GuiGameList.h b/src/components/GuiGameList.h index 7f51a0f15..51e581bbe 100644 --- a/src/components/GuiGameList.h +++ b/src/components/GuiGameList.h @@ -18,13 +18,13 @@ class GuiGameList : public GuiComponent { public: - GuiGameList(Window* window, bool useDetail = false); + GuiGameList(Window* window); virtual ~GuiGameList(); void setSystemId(int id); - bool input(InputConfig* config, Input input); - void update(int deltaTime); + bool input(InputConfig* config, Input input) override; + void update(int deltaTime) override; void render(); void init(); @@ -34,6 +34,8 @@ public: static GuiGameList* create(Window* window); + bool isDetailed() const; + static const float sInfoWidth; private: void updateList(); @@ -47,12 +49,11 @@ private: FolderData* mFolder; std::stack mFolderStack; int mSystemId; - bool mDetailed; - TextListComponent* mList; - ImageComponent* mScreenshot; + TextListComponent mList; + ImageComponent mScreenshot; TextComponent mDescription; - AnimationComponent* mImageAnimation; + AnimationComponent mImageAnimation; ThemeComponent* mTheme; ImageComponent mTransitionImage; diff --git a/src/components/ThemeComponent.cpp b/src/components/ThemeComponent.cpp index 56a5d42e1..0e54caa81 100644 --- a/src/components/ThemeComponent.cpp +++ b/src/components/ThemeComponent.cpp @@ -59,10 +59,8 @@ Font* ThemeComponent::getFastSelectFont() return mFastSelectFont; } -ThemeComponent::ThemeComponent(Window* window, bool detailed, std::string path) : GuiComponent(window) +ThemeComponent::ThemeComponent(Window* window) : GuiComponent(window) { - mDetailed = detailed; - mSoundMap["menuScroll"] = std::shared_ptr(new Sound); mSoundMap["menuSelect"] = std::shared_ptr(new Sound); mSoundMap["menuBack"] = std::shared_ptr(new Sound); @@ -79,9 +77,6 @@ ThemeComponent::ThemeComponent(Window* window, bool detailed, std::string path) mFastSelectFont = NULL; setDefaults(); - - if(!path.empty()) - readXML(path); } ThemeComponent::~ThemeComponent() @@ -159,7 +154,7 @@ void ThemeComponent::deleteComponents() -void ThemeComponent::readXML(std::string path) +void ThemeComponent::readXML(std::string path, bool detailed) { if(mPath == path) return; @@ -185,7 +180,7 @@ void ThemeComponent::readXML(std::string path) pugi::xml_node root; - if(!mDetailed) + if(!detailed) { //if we're using the basic view, check if there's a basic version of the theme root = doc.child("basicTheme"); diff --git a/src/components/ThemeComponent.h b/src/components/ThemeComponent.h index 8b711c8ec..c6ec05f4e 100644 --- a/src/components/ThemeComponent.h +++ b/src/components/ThemeComponent.h @@ -13,10 +13,10 @@ class ThemeComponent : public GuiComponent { public: - ThemeComponent(Window* window, bool detailed, std::string path = ""); + ThemeComponent(Window* window); virtual ~ThemeComponent(); - void readXML(std::string path); + void readXML(std::string path, bool detailed); GuiBoxData getBoxData(); @@ -48,7 +48,6 @@ private: Font* resolveFont(pugi::xml_node node, std::string defaultPath, unsigned int defaultSize); std::string mPath; - bool mDetailed; std::map mColorMap; std::map mBoolMap; From 0a6196dd9f0af642c457a6bfbc1f117fd211732d Mon Sep 17 00:00:00 2001 From: Aloshi Date: Tue, 2 Jul 2013 02:53:23 -0500 Subject: [PATCH 09/16] Refactored Font class to use a TextCache for vertex data. The TextComponent class should start using it soon. --- src/Font.cpp | 145 +++++++++++++++++++++------------ src/Font.h | 31 ++++++- src/components/GuiGameList.cpp | 2 +- 3 files changed, 124 insertions(+), 54 deletions(-) diff --git a/src/Font.cpp b/src/Font.cpp index bb62f06a8..187ada7a7 100644 --- a/src/Font.cpp +++ b/src/Font.cpp @@ -5,7 +5,6 @@ #include "Renderer.h" #include #include "Log.h" -#include "Vector2.h" FT_Library Font::sLibrary; bool Font::libraryInitialized = false; @@ -220,13 +219,14 @@ Font::~Font() } -struct Vertex -{ - Vector2 pos; - Vector2 tex; -}; - void Font::drawText(std::string text, int startx, int starty, int color) +{ + TextCache* cache = buildTextCache(text, startx, starty, color); + renderTextCache(cache); + delete cache; +} + +void Font::renderTextCache(TextCache* cache) { if(!textureID) { @@ -234,24 +234,92 @@ void Font::drawText(std::string text, int startx, int starty, int color) return; } - const int triCount = text.length() * 2; - Vertex* vert = new Vertex[triCount * 3]; - GLubyte* colors = new GLubyte[triCount * 3 * 4]; + if(cache == NULL) + { + LOG(LogError) << "Attempted to draw NULL TextCache!"; + return; + } + + if(cache->sourceFont != this) + { + LOG(LogError) << "Attempted to draw TextCache with font other than its source!"; + return; + } glBindTexture(GL_TEXTURE_2D, textureID); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + glVertexPointer(2, GL_FLOAT, sizeof(TextCache::Vertex), &cache->verts[0].pos); + glTexCoordPointer(2, GL_FLOAT, sizeof(TextCache::Vertex), &cache->verts[0].tex); + glColorPointer(4, GL_UNSIGNED_BYTE, 0, cache->colors); + + glDrawArrays(GL_TRIANGLES, 0, cache->vertCount); + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); +} + +void Font::sizeText(std::string text, int* w, int* h) +{ + float cwidth = 0.0f; + for(unsigned int i = 0; i < text.length(); i++) + { + unsigned char letter = text[i]; + if(letter < 32 || letter >= 128) + letter = 127; + + cwidth += charData[letter].advX * fontScale; + } + + if(w != NULL) + *w = (int)cwidth; + + if(h != NULL) + *h = getHeight(); +} + +int Font::getHeight() +{ + return (int)(mMaxGlyphHeight * 1.5f * fontScale); +} + + +//============================================================================================================= +//TextCache +//============================================================================================================= + +TextCache* Font::buildTextCache(const std::string& text, int offsetX, int offsetY, unsigned int color) +{ + if(!textureID) + { + LOG(LogError) << "Error - tried to build TextCache with Font that has no texture loaded!"; + return NULL; + } + + const int triCount = text.length() * 2; + const int vertCount = triCount * 3; + TextCache::Vertex* vert = new TextCache::Vertex[vertCount]; + GLubyte* colors = new GLubyte[vertCount * 4]; + //texture atlas width/height float tw = (float)textureWidth; float th = (float)textureHeight; - float x = (float)startx; - float y = starty + mMaxGlyphHeight * 1.1f * fontScale; //padding (another 0.5% is added to the bottom through the sizeText function) + float x = (float)offsetX; + float y = offsetY + mMaxGlyphHeight * 1.1f * fontScale; //padding (another 0.5% is added to the bottom through the sizeText function) int charNum = 0; - for(int i = 0; i < triCount * 3; i += 6, charNum++) + for(int i = 0; i < vertCount; i += 6, charNum++) { unsigned char letter = text[charNum]; @@ -286,49 +354,24 @@ void Font::drawText(std::string text, int startx, int starty, int color) x += charData[letter].advX * fontScale; } - Renderer::buildGLColorArray(colors, color, triCount * 3); + TextCache* cache = new TextCache(vertCount, vert, colors, this); + if(color != 0x00000000) + cache->setColor(color); - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); + return cache; +} - glVertexPointer(2, GL_FLOAT, sizeof(Vertex), &vert[0].pos); - glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), &vert[0].tex); - glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors); +TextCache::TextCache(int verts, Vertex* v, GLubyte* c, Font* f) : vertCount(verts), verts(v), colors(c), sourceFont(f) +{ +} - glDrawArrays(GL_TRIANGLES, 0, triCount * 3); - - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - glDisableClientState(GL_COLOR_ARRAY); - - glDisable(GL_TEXTURE_2D); - glDisable(GL_BLEND); - - delete[] vert; +TextCache::~TextCache() +{ + delete[] verts; delete[] colors; } -void Font::sizeText(std::string text, int* w, int* h) +void TextCache::setColor(unsigned int color) { - float cwidth = 0.0f; - for(unsigned int i = 0; i < text.length(); i++) - { - unsigned char letter = text[i]; - if(letter < 32 || letter >= 128) - letter = 127; - - cwidth += charData[letter].advX * fontScale; - } - - if(w != NULL) - *w = (int)cwidth; - - if(h != NULL) - *h = getHeight(); -} - -int Font::getHeight() -{ - return (int)(mMaxGlyphHeight * 1.5f * fontScale); + Renderer::buildGLColorArray(const_cast(colors), color, vertCount); } diff --git a/src/Font.h b/src/Font.h index 69c19fa3d..ed156ed37 100644 --- a/src/Font.h +++ b/src/Font.h @@ -6,6 +6,9 @@ #include GLHEADER #include #include FT_FREETYPE_H +#include "Vector2.h" + +class TextCache; //A TrueType Font renderer that uses FreeType and OpenGL. //The library is automatically initialized when it's needed. @@ -37,8 +40,12 @@ public: GLuint textureID; - void drawText(std::string text, int startx, int starty, int color); //Render some text using this font. - void sizeText(std::string text, int* w, int* h); //Sets the width and height of a given string to given pointers. Skipped if pointer is NULL. + TextCache* buildTextCache(const std::string& text, int offsetX, int offsetY, unsigned int color); + void renderTextCache(TextCache* cache); + + //Create a TextCache, render with it, then delete it. Best used for short text or text that changes frequently. + void drawText(std::string text, int startx, int starty, int color); + void sizeText(std::string text, int* w, int* h); //Sets the width and height of a given string to supplied pointers. A dimension is skipped if its pointer is NULL. int getHeight(); void init(); @@ -65,4 +72,24 @@ private: int mSize; }; +class TextCache +{ +public: + struct Vertex + { + Vector2 pos; + Vector2 tex; + }; + + void setColor(unsigned int color); + + TextCache(int verts, Vertex* v, GLubyte* c, Font* f); + ~TextCache(); + + const int vertCount; + const Vertex* verts; + const GLubyte* colors; + const Font* sourceFont; +}; + #endif diff --git a/src/components/GuiGameList.cpp b/src/components/GuiGameList.cpp index 0b66c944e..265d8da59 100644 --- a/src/components/GuiGameList.cpp +++ b/src/components/GuiGameList.cpp @@ -30,7 +30,7 @@ GuiGameList::GuiGameList(Window* window) : GuiComponent(window), mImageAnimation.addChild(&mScreenshot); //scale delay with screen width (higher width = more text per line) - //the scroll speed is automatically scrolled by component size + //the scroll speed is automatically scaled by component size mDescription.setAutoScroll((int)(1500 + (Renderer::getScreenWidth() * 0.5)), 0.025f); mTransitionImage.setOffset(Renderer::getScreenWidth(), 0); From b4e554153a104511d1ceacf3ee1e1a13b0ad74aa Mon Sep 17 00:00:00 2001 From: Sir_Leon Date: Tue, 2 Jul 2013 16:51:33 +0200 Subject: [PATCH 10/16] Moved Opacity logic to GuiComponent Moved Opacity logic from ImageComponent to GuiComponent so any extender of GuiComponent che implement its opacity logic. Implemented Opacity logic for TextComponent (now text can have fade animation) --- src/GuiComponent.cpp | 11 +++++++++++ src/GuiComponent.h | 3 +++ src/components/ImageComponent.cpp | 2 -- src/components/ImageComponent.h | 5 ----- src/components/TextComponent.cpp | 2 +- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/GuiComponent.cpp b/src/GuiComponent.cpp index 4a5ee0195..88830b9d0 100644 --- a/src/GuiComponent.cpp +++ b/src/GuiComponent.cpp @@ -156,3 +156,14 @@ GuiComponent* GuiComponent::getParent() { return mParent; } + + +unsigned char GuiComponent::getOpacity() +{ + return mOpacity; +} + +void GuiComponent::setOpacity(unsigned char opacity) +{ + mOpacity = opacity; +} \ No newline at end of file diff --git a/src/GuiComponent.h b/src/GuiComponent.h index b7f36af64..6e09b5383 100644 --- a/src/GuiComponent.h +++ b/src/GuiComponent.h @@ -45,11 +45,14 @@ public: void clearChildren(); unsigned int getChildCount(); GuiComponent* getChild(unsigned int i); + unsigned char getOpacity(); + void setOpacity(unsigned char opacity); protected: //Default implementation just renders children - you should probably always call GuiComponent::onRender at some point in your custom onRender. virtual void onRender(); + unsigned char mOpacity; Window* mWindow; GuiComponent* mParent; Vector2i mOffset; diff --git a/src/components/ImageComponent.cpp b/src/components/ImageComponent.cpp index 238c9019e..dc8021ef8 100644 --- a/src/components/ImageComponent.cpp +++ b/src/components/ImageComponent.cpp @@ -352,8 +352,6 @@ bool ImageComponent::hasImage() return !mPath.empty(); } -unsigned char ImageComponent::getOpacity() { return mOpacity; } -void ImageComponent::setOpacity(unsigned char opacity) { mOpacity = opacity; } void ImageComponent::copyScreen() { diff --git a/src/components/ImageComponent.h b/src/components/ImageComponent.h index 3cf0204b1..ca6939509 100644 --- a/src/components/ImageComponent.h +++ b/src/components/ImageComponent.h @@ -38,9 +38,6 @@ public: void init(); void deinit(); - unsigned char getOpacity(); - void setOpacity(unsigned char opacity); - protected: void onRender(); @@ -51,8 +48,6 @@ private: bool mAllowUpscale, mTiled, mFlipX, mFlipY; - unsigned char mOpacity; - void loadImage(std::string path); void resize(); void buildImageArray(int x, int y, GLfloat* points, GLfloat* texs, float percentageX = 1, float percentageY = 1); //writes 12 GLfloat points and 12 GLfloat texture coordinates to a given array at a given position diff --git a/src/components/TextComponent.cpp b/src/components/TextComponent.cpp index 477a19417..6b455881c 100644 --- a/src/components/TextComponent.cpp +++ b/src/components/TextComponent.cpp @@ -74,7 +74,7 @@ void TextComponent::onRender() Renderer::pushClipRect(getGlobalOffset(), getSize()); - Renderer::drawWrappedText(mText, (int)-mScrollPos.x, (int)-mScrollPos.y, mSize.x, mColor, font); + Renderer::drawWrappedText(mText, (int)-mScrollPos.x, (int)-mScrollPos.y, mSize.x, mColor >> 8 << 8 | getOpacity(), font); Renderer::popClipRect(); From ac516565279b3b862ac8af325983b97c0ade01c9 Mon Sep 17 00:00:00 2001 From: Bim Date: Tue, 2 Jul 2013 23:14:33 +0200 Subject: [PATCH 11/16] Read/Write sort order to settings Not written to disk atm. --- src/Settings.cpp | 2 ++ src/components/GuiGameList.cpp | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Settings.cpp b/src/Settings.cpp index 52111668d..8319a3b8c 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -33,6 +33,8 @@ void Settings::setDefaults() mBoolMap["WINDOWED"] = false; mIntMap["DIMTIME"] = 30*1000; + + mIntMap["GameListSortIndex"] = 0; } template diff --git a/src/components/GuiGameList.cpp b/src/components/GuiGameList.cpp index 5aeda1106..a19fb2688 100644 --- a/src/components/GuiGameList.cpp +++ b/src/components/GuiGameList.cpp @@ -30,7 +30,7 @@ GuiGameList::GuiGameList(Window* window) : GuiComponent(window), mScreenshot(window), mDescription(window), mTransitionImage(window, 0, 0, "", Renderer::getScreenWidth(), Renderer::getScreenHeight(), true), - sortStateIndex(0) + sortStateIndex(Settings::getInstance()->getInt("GameListSortIndex")) { //first object initializes the vector if (sortStates.empty()) { @@ -247,6 +247,8 @@ void GuiGameList::setSortIndex(size_t index) sortStateIndex = index; sort(sortStates.at(sortStateIndex).comparisonFunction, sortStates.at(sortStateIndex).ascending); } + //save new index to settings + Settings::getInstance()->setInt("GameListSortIndex", sortStateIndex); } void GuiGameList::setNextSortIndex() From df78b5352ddadcc4911c8acc87099d939b96a0b3 Mon Sep 17 00:00:00 2001 From: Aloshi Date: Tue, 2 Jul 2013 18:13:55 -0500 Subject: [PATCH 12/16] Changed AnimationComponent to use GuiComponent instead of ImageComponent. Possible now that the opacity logic has been moved to GuiComponent. --- src/components/AnimationComponent.cpp | 4 ++-- src/components/AnimationComponent.h | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/AnimationComponent.cpp b/src/components/AnimationComponent.cpp index 31c4c327e..1c400b4d3 100644 --- a/src/components/AnimationComponent.cpp +++ b/src/components/AnimationComponent.cpp @@ -76,7 +76,7 @@ void AnimationComponent::update(int deltaTime) } } -void AnimationComponent::addChild(ImageComponent* gui) +void AnimationComponent::addChild(GuiComponent* gui) { mChildren.push_back(gui); } @@ -86,7 +86,7 @@ void AnimationComponent::moveChildren(int offsetx, int offsety) Vector2i move(offsetx, offsety); for(unsigned int i = 0; i < mChildren.size(); i++) { - ImageComponent* comp = mChildren.at(i); + GuiComponent* comp = mChildren.at(i); comp->setOffset(comp->getOffset() + move); } } diff --git a/src/components/AnimationComponent.h b/src/components/AnimationComponent.h index 99cbd35b7..4591c10c2 100644 --- a/src/components/AnimationComponent.h +++ b/src/components/AnimationComponent.h @@ -2,7 +2,6 @@ #define _ANIMATIONCOMPONENT_H_ #include "../GuiComponent.h" -#include "ImageComponent.h" #include #define ANIMATION_TICK_SPEED 16 @@ -18,12 +17,12 @@ public: void update(int deltaTime); - void addChild(ImageComponent* gui); + void addChild(GuiComponent* gui); private: unsigned char mOpacity; - std::vector mChildren; + std::vector mChildren; void moveChildren(int offsetx, int offsety); void setChildrenOpacity(unsigned char opacity); From 45ed6ae4da40c4d64265bbde286298221aa1c9fc Mon Sep 17 00:00:00 2001 From: Bim Date: Wed, 3 Jul 2013 01:48:39 +0200 Subject: [PATCH 13/16] Add size set function to GuiComponent Similar to #92... --- src/GuiComponent.cpp | 13 +++++++++++++ src/GuiComponent.h | 3 +++ 2 files changed, 16 insertions(+) diff --git a/src/GuiComponent.cpp b/src/GuiComponent.cpp index 4a5ee0195..94afcdec0 100644 --- a/src/GuiComponent.cpp +++ b/src/GuiComponent.cpp @@ -102,6 +102,19 @@ Vector2u GuiComponent::getSize() return mSize; } +void GuiComponent::setSize(Vector2u size) +{ + mSize = size; + onSizeChanged(); +} + +void GuiComponent::setSize(unsigned int w, unsigned int h) +{ + mSize.x = w; + mSize.y = h; + onSizeChanged(); +} + //Children stuff. void GuiComponent::addChild(GuiComponent* cmp) { diff --git a/src/GuiComponent.h b/src/GuiComponent.h index b7f36af64..2335b3a33 100644 --- a/src/GuiComponent.h +++ b/src/GuiComponent.h @@ -36,6 +36,9 @@ public: virtual void onOffsetChanged() {}; Vector2u getSize(); + void setSize(Vector2u size); + void setSize(unsigned int w, unsigned int h); + virtual void onSizeChanged() {}; void setParent(GuiComponent* parent); GuiComponent* getParent(); From ed384e057b40aaad444435c54b943132c752dcf7 Mon Sep 17 00:00:00 2001 From: Aloshi Date: Tue, 2 Jul 2013 20:01:58 -0500 Subject: [PATCH 14/16] Move scrolling out of TextComponent and into a generic ScrollableContainer. --- CMakeLists.txt | 2 + src/Renderer_draw_gl.cpp | 6 +- src/components/GuiGameList.cpp | 17 ++-- src/components/GuiGameList.h | 2 + src/components/ScrollableContainer.cpp | 111 +++++++++++++++++++++++++ src/components/ScrollableContainer.h | 29 +++++++ src/components/TextComponent.cpp | 107 ++++-------------------- src/components/TextComponent.h | 18 +--- 8 files changed, 178 insertions(+), 114 deletions(-) create mode 100644 src/components/ScrollableContainer.cpp create mode 100644 src/components/ScrollableContainer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ab88f5c13..132ee4752 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,6 +135,7 @@ set(ES_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimationComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentListComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ScrollableContainer.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/SliderComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.h @@ -175,6 +176,7 @@ set(ES_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/components/AnimationComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentListComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ScrollableContainer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/SliderComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/SwitchComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/TextComponent.cpp diff --git a/src/Renderer_draw_gl.cpp b/src/Renderer_draw_gl.cpp index 10fa92f33..05b8f4ae5 100644 --- a/src/Renderer_draw_gl.cpp +++ b/src/Renderer_draw_gl.cpp @@ -254,7 +254,8 @@ namespace Renderer { void sizeWrappedText(std::string text, int xLen, Font* font, int* xOut, int* yOut) { - *xOut = xLen; + if(xOut != NULL) + *xOut = xLen; int y = 0; @@ -306,7 +307,8 @@ namespace Renderer { } - *yOut = y; + if(yOut != NULL) + *yOut = y; } }; diff --git a/src/components/GuiGameList.cpp b/src/components/GuiGameList.cpp index 265d8da59..f2f8875dd 100644 --- a/src/components/GuiGameList.cpp +++ b/src/components/GuiGameList.cpp @@ -25,13 +25,15 @@ GuiGameList::GuiGameList(Window* window) : GuiComponent(window), mList(window, 0, 0, Renderer::getDefaultFont(Renderer::MEDIUM)), mScreenshot(window), mDescription(window), + mDescContainer(window), mTransitionImage(window, 0, 0, "", Renderer::getScreenWidth(), Renderer::getScreenHeight(), true) { mImageAnimation.addChild(&mScreenshot); + mDescContainer.addChild(&mDescription); //scale delay with screen width (higher width = more text per line) //the scroll speed is automatically scaled by component size - mDescription.setAutoScroll((int)(1500 + (Renderer::getScreenWidth() * 0.5)), 0.025f); + mDescContainer.setAutoScroll((int)(1500 + (Renderer::getScreenWidth() * 0.5)), 0.025f); mTransitionImage.setOffset(Renderer::getScreenWidth(), 0); mTransitionImage.setOrigin(0, 0); @@ -104,7 +106,7 @@ void GuiGameList::render() Renderer::drawRect((int)(Renderer::getScreenWidth() * mTheme->getFloat("listOffsetX")) - 4, Renderer::getDefaultFont(Renderer::LARGE)->getHeight() + 2, 8, Renderer::getScreenHeight(), 0x0000FFFF); mScreenshot.render(); - mDescription.render(); + mDescContainer.render(); } mList.render(); @@ -290,8 +292,13 @@ void GuiGameList::updateDetailData() mImageAnimation.fadeIn(35); mImageAnimation.move(imgOffset.x, imgOffset.y, 20); - mDescription.setOffset(Vector2i((int)(Renderer::getScreenWidth() * 0.03), getImagePos().y + mScreenshot.getSize().y + 12)); - mDescription.setExtent(Vector2u((int)(Renderer::getScreenWidth() * (mTheme->getFloat("listOffsetX") - 0.03)), Renderer::getScreenHeight() - mDescription.getOffset().y)); + mDescContainer.setOffset(Vector2i((int)(Renderer::getScreenWidth() * 0.03), getImagePos().y + mScreenshot.getSize().y + 12)); + mDescContainer.setSize(Vector2u((int)(Renderer::getScreenWidth() * (mTheme->getFloat("listOffsetX") - 0.03)), Renderer::getScreenHeight() - mDescContainer.getOffset().y)); + mDescContainer.setScrollPos(Vector2d(0, 0)); + mDescContainer.resetAutoScrollTimer(); + + mDescription.setOffset(0, 0); + mDescription.setExtent(Vector2u((int)(Renderer::getScreenWidth() * (mTheme->getFloat("listOffsetX") - 0.03)), 0)); mDescription.setText(((GameData*)mList.getSelectedObject())->getDescription()); }else{ mScreenshot.setImage(""); @@ -340,7 +347,7 @@ void GuiGameList::update(int deltaTime) mList.update(deltaTime); - mDescription.update(deltaTime); + mDescContainer.update(deltaTime); } void GuiGameList::doTransition(int dir) diff --git a/src/components/GuiGameList.h b/src/components/GuiGameList.h index 51e581bbe..610900ff8 100644 --- a/src/components/GuiGameList.h +++ b/src/components/GuiGameList.h @@ -12,6 +12,7 @@ #include "../SystemData.h" #include "../GameData.h" #include "../FolderData.h" +#include "ScrollableContainer.h" //This is where the magic happens - GuiGameList is the parent of almost every graphical element in ES at the moment. //It has a TextListComponent child that handles the game list, a ThemeComponent that handles the theming system, and an ImageComponent for game images. @@ -53,6 +54,7 @@ private: TextListComponent mList; ImageComponent mScreenshot; TextComponent mDescription; + ScrollableContainer mDescContainer; AnimationComponent mImageAnimation; ThemeComponent* mTheme; diff --git a/src/components/ScrollableContainer.cpp b/src/components/ScrollableContainer.cpp new file mode 100644 index 000000000..38f05f56f --- /dev/null +++ b/src/components/ScrollableContainer.cpp @@ -0,0 +1,111 @@ +#include "ScrollableContainer.h" +#include "../Renderer.h" +#include "../Log.h" + +ScrollableContainer::ScrollableContainer(Window* window) : GuiComponent(window), + mAutoScrollDelay(0), mAutoScrollSpeed(0), mAutoScrollTimer(0) +{ +} + +void ScrollableContainer::render() +{ + Renderer::pushClipRect(getGlobalOffset(), getSize()); + + Vector2f translate = (Vector2f)mOffset - (Vector2f)mScrollPos; + + Renderer::translatef(translate.x, translate.y); + + Renderer::drawRect(0, 0, 800, 800, 0xFF0000FF); + + GuiComponent::onRender(); + + Renderer::translatef(-translate.x, -translate.y); + + Renderer::popClipRect(); +} + +void ScrollableContainer::setAutoScroll(int delay, double speed) +{ + mAutoScrollDelay = delay; + mAutoScrollSpeed = speed; + mAutoScrollTimer = 0; +} + +Vector2d ScrollableContainer::getScrollPos() const +{ + return mScrollPos; +} + +void ScrollableContainer::setScrollPos(const Vector2d& pos) +{ + mScrollPos = pos; +} + +void ScrollableContainer::update(int deltaTime) +{ + double scrollAmt = (double)deltaTime; + + if(mAutoScrollSpeed != 0) + { + mAutoScrollTimer += deltaTime; + + scrollAmt = (float)(mAutoScrollTimer - mAutoScrollDelay); + + if(scrollAmt > 0) + { + //scroll the amount of time left over from the delay + mAutoScrollTimer = mAutoScrollDelay; + + //scale speed by our width! more text per line = slower scrolling + const double widthMod = (680.0 / getSize().x); + mScrollDir = Vector2d(0, mAutoScrollSpeed * widthMod); + }else{ + //not enough to pass the delay, do nothing + scrollAmt = 0; + } + } + + Vector2d scroll = mScrollDir * scrollAmt; + mScrollPos += scroll; + + //clip scrolling within bounds + if(mScrollPos.x < 0) + mScrollPos.x = 0; + if(mScrollPos.y < 0) + mScrollPos.y = 0; + + + Vector2i contentSize = getContentSize(); + if(mScrollPos.x + getSize().x > contentSize.x) + mScrollPos.x = (double)contentSize.x - getSize().x; + if(mScrollPos.y + getSize().y > contentSize.y) + mScrollPos.y = (double)contentSize.y - getSize().y; + + GuiComponent::update(deltaTime); +} + +//this should probably return a box to allow for when controls don't start at 0,0 +Vector2i ScrollableContainer::getContentSize() +{ + Vector2i max; + for(unsigned int i = 0; i < mChildren.size(); i++) + { + Vector2i bottomRight = (Vector2i)mChildren.at(i)->getSize() + mChildren.at(i)->getOffset(); + if(bottomRight.x > max.x) + max.x = bottomRight.x; + if(bottomRight.y > max.y) + max.y = bottomRight.y; + } + + return max; +} + +void ScrollableContainer::setSize(Vector2u size) +{ + mSize = size; +} + +void ScrollableContainer::resetAutoScrollTimer() +{ + mAutoScrollTimer = 0; +} diff --git a/src/components/ScrollableContainer.h b/src/components/ScrollableContainer.h new file mode 100644 index 000000000..2e139bd7a --- /dev/null +++ b/src/components/ScrollableContainer.h @@ -0,0 +1,29 @@ +#pragma once + +#include "../GuiComponent.h" + +class ScrollableContainer : public GuiComponent +{ +public: + ScrollableContainer(Window* window); + + void setSize(Vector2u size); + + Vector2d getScrollPos() const; + void setScrollPos(const Vector2d& pos); + void setAutoScroll(int delay, double speed); //Use 0 for speed to disable. + void resetAutoScrollTimer(); + + void update(int deltaTime) override; + void render() override; + + //Vector2i getGlobalOffset() override; +private: + Vector2i getContentSize(); + + Vector2d mScrollPos; + Vector2d mScrollDir; + int mAutoScrollDelay; + double mAutoScrollSpeed; + int mAutoScrollTimer; +}; diff --git a/src/components/TextComponent.cpp b/src/components/TextComponent.cpp index 6b455881c..fc8dec8f3 100644 --- a/src/components/TextComponent.cpp +++ b/src/components/TextComponent.cpp @@ -3,12 +3,12 @@ #include "../Log.h" TextComponent::TextComponent(Window* window) : GuiComponent(window), - mFont(NULL), mColor(0x000000FF), mAutoCalcExtent(true), mAutoScrollDelay(0), mAutoScrollSpeed(0), mAutoScrollTimer(0) + mFont(NULL), mColor(0x000000FF), mAutoCalcExtent(true, true) { } TextComponent::TextComponent(Window* window, const std::string& text, Font* font, Vector2i pos, Vector2u size) : GuiComponent(window), - mFont(NULL), mColor(0x000000FF), mAutoCalcExtent(true), mAutoScrollDelay(0), mAutoScrollSpeed(0), mAutoScrollTimer(0) + mFont(NULL), mColor(0x000000FF), mAutoCalcExtent(true, true) { setText(text); setFont(font); @@ -23,22 +23,16 @@ void TextComponent::setBox(Vector2i pos, Vector2u size) void TextComponent::setExtent(Vector2u size) { - if(size == Vector2u(0, 0)) - { - mAutoCalcExtent = true; - calculateExtent(); - }else{ - mAutoCalcExtent = false; - mSize = size; - } + mSize = size; + mAutoCalcExtent = Vector2(size.x == 0, size.y == 0); + calculateExtent(); } void TextComponent::setFont(Font* font) { mFont = font; - if(mAutoCalcExtent) - calculateExtent(); + calculateExtent(); } void TextComponent::setColor(unsigned int color) @@ -50,12 +44,7 @@ void TextComponent::setText(const std::string& text) { mText = text; - if(mAutoCalcExtent) - calculateExtent(); - - mScrollPos = Vector2d(0, 0); - mScrollDir = Vector2d(0, 0); - resetAutoScrollTimer(); + calculateExtent(); } Font* TextComponent::getFont() const @@ -72,11 +61,11 @@ void TextComponent::onRender() return; } - Renderer::pushClipRect(getGlobalOffset(), getSize()); + //Renderer::pushClipRect(getGlobalOffset(), getSize()); - Renderer::drawWrappedText(mText, (int)-mScrollPos.x, (int)-mScrollPos.y, mSize.x, mColor >> 8 << 8 | getOpacity(), font); + Renderer::drawWrappedText(mText, 0, 0, mSize.x, mColor >> 8 << 8 | getOpacity(), font); - Renderer::popClipRect(); + //Renderer::popClipRect(); GuiComponent::onRender(); } @@ -90,75 +79,9 @@ void TextComponent::calculateExtent() return; } - font->sizeText(mText, (int*)&mSize.x, (int*)&mSize.y); -} - -void TextComponent::setAutoScroll(int delay, double speed) -{ - mAutoScrollDelay = delay; - mAutoScrollSpeed = speed; - resetAutoScrollTimer(); -} - -void TextComponent::resetAutoScrollTimer() -{ - mAutoScrollTimer = 0; -} - -Vector2d TextComponent::getScrollPos() const -{ - return mScrollPos; -} - -void TextComponent::setScrollPos(const Vector2d& pos) -{ - mScrollPos = pos; -} - -void TextComponent::update(int deltaTime) -{ - double scrollAmt = (double)deltaTime; - - if(mAutoScrollSpeed != 0) - { - mAutoScrollTimer += deltaTime; - - scrollAmt = (float)(mAutoScrollTimer - mAutoScrollDelay); - - if(scrollAmt > 0) - { - //scroll the amount of time left over from the delay - mAutoScrollTimer = mAutoScrollDelay; - - //scale speed by our width! more text per line = slower scrolling - const double widthMod = (680.0 / getSize().x); - mScrollDir = Vector2d(0, mAutoScrollSpeed * widthMod); - }else{ - //not enough to pass the delay, do nothing - scrollAmt = 0; - } - } - - Vector2d scroll = mScrollDir * scrollAmt; - mScrollPos += scroll; - - //clip scrolling within bounds - if(mScrollPos.x < 0) - mScrollPos.x = 0; - if(mScrollPos.y < 0) - mScrollPos.y = 0; - - Font* font = getFont(); - if(font != NULL) - { - Vector2i textSize; - Renderer::sizeWrappedText(mText, getSize().x, getFont(), &textSize.x, &textSize.y); - - if(mScrollPos.x + getSize().x > textSize.x) - mScrollPos.x = (double)textSize.x - getSize().x; - if(mScrollPos.y + getSize().y > textSize.y) - mScrollPos.y = (double)textSize.y - getSize().y; - } - - GuiComponent::update(deltaTime); + if(mAutoCalcExtent.x) + font->sizeText(mText, (int*)&mSize.x, (int*)&mSize.y); + else + if(mAutoCalcExtent.y) + Renderer::sizeWrappedText(mText, getSize().x, mFont, NULL, (int*)&mSize.y); } diff --git a/src/components/TextComponent.h b/src/components/TextComponent.h index a77a7ee5a..1a1c90eb4 100644 --- a/src/components/TextComponent.h +++ b/src/components/TextComponent.h @@ -12,33 +12,21 @@ public: void setFont(Font* font); void setBox(Vector2i pos, Vector2u size); - void setExtent(Vector2u size); //Use Vector2u(0, 0) to automatically generate extent. + void setExtent(Vector2u size); //Use Vector2u(0, 0) to automatically generate extent on a single line. Use Vector2(value, 0) to automatically generate extent for wrapped text. void setText(const std::string& text); void setColor(unsigned int color); - Vector2d getScrollPos() const; - void setScrollPos(const Vector2d& pos); - void setAutoScroll(int delay, double speed); //Use 0 for speed to disable. - void resetAutoScrollTimer(); - - void update(int deltaTime) override; void onRender() override; private: Font* getFont() const; + void calculateExtent(); unsigned int mColor; Font* mFont; - bool mAutoCalcExtent; + Vector2 mAutoCalcExtent; std::string mText; - - //scrolling - Vector2d mScrollPos; - Vector2d mScrollDir; - int mAutoScrollDelay; - double mAutoScrollSpeed; - int mAutoScrollTimer; }; #endif From 04946d9fede467fe131d65a2e3c9e6a686331c35 Mon Sep 17 00:00:00 2001 From: Aloshi Date: Tue, 2 Jul 2013 22:25:48 -0500 Subject: [PATCH 15/16] Remove red test background on ScrollableContainer --- src/components/ScrollableContainer.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/ScrollableContainer.cpp b/src/components/ScrollableContainer.cpp index 38f05f56f..890f849d6 100644 --- a/src/components/ScrollableContainer.cpp +++ b/src/components/ScrollableContainer.cpp @@ -15,8 +15,6 @@ void ScrollableContainer::render() Renderer::translatef(translate.x, translate.y); - Renderer::drawRect(0, 0, 800, 800, 0xFF0000FF); - GuiComponent::onRender(); Renderer::translatef(-translate.x, -translate.y); From 341aa766d8c7af1923b7122a731ca0b2a809cbae Mon Sep 17 00:00:00 2001 From: Aloshi Date: Tue, 2 Jul 2013 22:48:03 -0500 Subject: [PATCH 16/16] Use path.generic_string() for game paths. Should now only use forward slashes, regardless of platform. --- src/SystemData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SystemData.cpp b/src/SystemData.cpp index 041b47716..a34ecf4e6 100644 --- a/src/SystemData.cpp +++ b/src/SystemData.cpp @@ -145,7 +145,7 @@ void SystemData::populateFolder(FolderData* folder) //if it matches, add it if(chkExt == extension) { - GameData* newGame = new GameData(this, filePath.string(), filePath.stem().string()); + GameData* newGame = new GameData(this, filePath.generic_string(), filePath.stem().string()); folder->pushFileData(newGame); isGame = true; break;