diff --git a/CMakeLists.txt b/CMakeLists.txt index 67177e5bb..d0dd734f5 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 @@ -178,6 +179,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/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/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..ac9e08477 100644 --- a/src/FolderData.cpp +++ b/src/FolderData.cpp @@ -1,19 +1,29 @@ #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; } + +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; } 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) + : 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() @@ -31,8 +41,24 @@ 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 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,29 +69,118 @@ 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(); } -//sort this folder and any subfolders -void FolderData::sort() +bool FolderData::compareRating(const FileData* file1, const FileData* file2) { - std::sort(mFileVector.begin(), mFileVector.end(), filesort); - - for(unsigned int i = 0; i < mFileVector.size(); i++) - { - if(mFileVector.at(i)->isFolder()) - ((FolderData*)mFileVector.at(i))->sort(); + //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::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 + const GameData * game1 = dynamic_cast(file1); + const GameData * game2 = dynamic_cast(file2); + if (game1 != nullptr && game2 != nullptr) { + return game1->getTimesPlayed() < game2->getTimesPlayed(); + } + 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; +} + +std::string FolderData::getSortStateName(ComparisonFunction & comparisonFunction, bool ascending) +{ + std::string temp = sortStateNameMap[comparisonFunction]; + if (ascending) { + temp.append(" (ascending)"); + } + else { + temp.append(" (descending)"); + } + return temp; +} + +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..ecbd844fb 100644 --- a/src/FolderData.h +++ b/src/FolderData.h @@ -1,28 +1,54 @@ #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; + std::string description; + + SortState(ComparisonFunction & sortFunction, bool sortAscending, const std::string & sortDescription) : comparisonFunction(sortFunction), ascending(sortAscending), description(sortDescription) {} + }; + +private: + static std::map sortStateNameMap; + 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(); + 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/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/GameData.cpp b/src/GameData.cpp index bb656be01..ef8384dfd 100644 --- a/src/GameData.cpp +++ b/src/GameData.cpp @@ -2,23 +2,110 @@ #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::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), mUserRating(0.0f), mTimesPlayed(0), mLastPlayed(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; +} + +float GameData::getRating() const +{ + return mRating; +} + +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; +} + +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 std::string path = mPath; @@ -44,18 +131,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..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" @@ -9,19 +11,49 @@ 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 xmlTagUserRating; + static const std::string xmlTagTimesPlayed; + static const std::string xmlTagLastPlayed; + 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); + + 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; + + bool isFolder() const; private: SystemData* mSystem; std::string mPath; @@ -30,6 +62,10 @@ private: //extra data std::string mDescription; std::string mImagePath; + float mRating; + float mUserRating; + size_t mTimesPlayed; + std::time_t mLastPlayed; }; #endif diff --git a/src/GuiComponent.cpp b/src/GuiComponent.cpp index 4a5ee0195..3b3057764 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) { @@ -156,3 +169,14 @@ GuiComponent* GuiComponent::getParent() { return mParent; } + + +unsigned char GuiComponent::getOpacity() +{ + return mOpacity; +} + +void GuiComponent::setOpacity(unsigned char opacity) +{ + mOpacity = opacity; +} diff --git a/src/GuiComponent.h b/src/GuiComponent.h index b7f36af64..fe441b171 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(); @@ -45,11 +48,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/InputManager.cpp b/src/InputManager.cpp index 168787a9f..fb76e48b1 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -423,6 +423,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/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..05b8f4ae5 100644 --- a/src/Renderer_draw_gl.cpp +++ b/src/Renderer_draw_gl.cpp @@ -252,4 +252,63 @@ namespace Renderer { } } + void sizeWrappedText(std::string text, int xLen, Font* font, int* xOut, int* yOut) + { + if(xOut != NULL) + *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; + } + + } + + if(yOut != NULL) + *yOut = y; + } + }; 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/SystemData.cpp b/src/SystemData.cpp index 2e983e5e4..a34ecf4e6 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,10 @@ void SystemData::launchGame(Window* window, GameData* game) window->init(); VolumeControl::getInstance()->init(); AudioManager::getInstance()->init(); + + //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) @@ -137,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; 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/XMLReader.cpp b/src/XMLReader.cpp index d1e19b8ae..d8a8f0eea 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,148 @@ 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())) + { + float rating; + 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!"; } } } + +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((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) +{ + //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 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); 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 c91d48ade..c354a0478 100644 --- a/src/components/GuiGameList.cpp +++ b/src/components/GuiGameList.cpp @@ -7,38 +7,53 @@ #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) +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); - - //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)); +GuiGameList::GuiGameList(Window* window) : GuiComponent(window), + mTheme(new ThemeComponent(mWindow)), + mList(window, 0, 0, Renderer::getDefaultFont(Renderer::MEDIUM)), + mScreenshot(window), + mDescription(window), + mDescContainer(window), + mTransitionImage(window, 0, 0, "", Renderer::getScreenWidth(), Renderer::getScreenHeight(), true), + sortStateIndex(Settings::getInstance()->getInt("GameListSortIndex")) +{ + //first object initializes the vector + if (sortStates.empty()) { + 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")); } - mScreenshot->setOrigin(mTheme->getFloat("gameImageOriginX"), mTheme->getFloat("gameImageOriginY")); + 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 + mDescContainer.setAutoScroll((int)(1500 + (Renderer::getScreenWidth() * 0.5)), 0.025f); - 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)); - mTransitionImage.setOffset(Renderer::getScreenWidth(), 0); mTransitionImage.setOrigin(0, 0); mTransitionAnimation.addChild(&mTransitionImage); @@ -47,7 +62,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); } @@ -55,15 +70,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; } @@ -110,22 +118,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(); + mDescContainer.render(); } - mList->render(); + mList.render(); Renderer::translate(-mOffset); @@ -134,14 +137,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); @@ -150,7 +153,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()); @@ -191,6 +194,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) { @@ -201,11 +214,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)); return true; } - if(mDetailed) + if(isDetailed()) { if(config->isMappedTo("up", input) || config->isMappedTo("down", input) || config->isMappedTo("pageup", input) || config->isMappedTo("pagedown", input)) { @@ -220,21 +233,64 @@ 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 + 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); + } + //save new index to settings + Settings::getInstance()->setInt("GameListSortIndex", sortStateIndex); +} + +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) - 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")); } } @@ -261,64 +317,77 @@ 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), mScreenshot->getOffset().y + mScreenshot->getSize().y + 12)); - 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); + + 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(""); + mDescription.setText(""); + } } } void GuiGameList::clearDetailData() { - if(mDetailed) + if(isDetailed()) { - mImageAnimation->fadeOut(35); + mImageAnimation.fadeOut(35); + mDescription.setText(""); } } @@ -326,11 +395,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(); } @@ -338,41 +404,25 @@ 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); + + mDescContainer.update(deltaTime); } void GuiGameList::doTransition(int dir) diff --git a/src/components/GuiGameList.h b/src/components/GuiGameList.h index 7f51a0f15..7b2b38660 100644 --- a/src/components/GuiGameList.h +++ b/src/components/GuiGameList.h @@ -12,19 +12,23 @@ #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. class GuiGameList : public GuiComponent { + static std::vector sortStates; + size_t sortStateIndex; + 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(); @@ -32,8 +36,16 @@ public: void updateDetailData(); + const FolderData::SortState & getSortState() const; + void setSortIndex(size_t index); + void setNextSortIndex(); + void setPreviousSortIndex(); + void sort(FolderData::ComparisonFunction & comparisonFunction = FolderData::compareFileName, bool ascending = true); + static GuiGameList* create(Window* window); + bool isDetailed() const; + static const float sInfoWidth; private: void updateList(); @@ -47,12 +59,12 @@ private: FolderData* mFolder; std::stack mFolderStack; int mSystemId; - bool mDetailed; - TextListComponent* mList; - ImageComponent* mScreenshot; + TextListComponent mList; + ImageComponent mScreenshot; TextComponent mDescription; - AnimationComponent* mImageAnimation; + ScrollableContainer mDescContainer; + AnimationComponent mImageAnimation; ThemeComponent* mTheme; ImageComponent mTransitionImage; diff --git a/src/components/ImageComponent.cpp b/src/components/ImageComponent.cpp index f89368eb7..3aa689839 100644 --- a/src/components/ImageComponent.cpp +++ b/src/components/ImageComponent.cpp @@ -224,8 +224,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 12318c413..062285367 100644 --- a/src/components/ImageComponent.h +++ b/src/components/ImageComponent.h @@ -32,9 +32,6 @@ public: bool hasImage(); - unsigned char getOpacity(); - void setOpacity(unsigned char opacity); - protected: void onRender(); @@ -44,8 +41,6 @@ private: bool mAllowUpscale, mTiled, mFlipX, mFlipY; - unsigned char mOpacity; - 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 void drawImageArray(GLfloat* points, GLfloat* texs, GLubyte* colors, unsigned int count = 6); //draws the given set of points and texture coordinates, number of coordinate pairs may be specified (default 6) diff --git a/src/components/ScrollableContainer.cpp b/src/components/ScrollableContainer.cpp new file mode 100644 index 000000000..890f849d6 --- /dev/null +++ b/src/components/ScrollableContainer.cpp @@ -0,0 +1,109 @@ +#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); + + 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 76312e80c..fc8dec8f3 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, true) { } 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, true) { setText(text); setFont(font); @@ -22,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) @@ -49,36 +44,44 @@ void TextComponent::setText(const std::string& text) { mText = text; - if(mAutoCalcExtent) - calculateExtent(); + calculateExtent(); +} + +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!"; return; } - Renderer::pushClipRect(getGlobalOffset(), getSize()); + //Renderer::pushClipRect(getGlobalOffset(), getSize()); - Renderer::drawWrappedText(mText, 0, 0, mSize.x, mColor, font); + Renderer::drawWrappedText(mText, 0, 0, mSize.x, mColor >> 8 << 8 | getOpacity(), font); - Renderer::popClipRect(); + //Renderer::popClipRect(); GuiComponent::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!"; return; } - font->sizeText(mText, (int*)&mSize.x, (int*)&mSize.y); + 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 67a6f9b5d..1a1c90eb4 100644 --- a/src/components/TextComponent.h +++ b/src/components/TextComponent.h @@ -12,18 +12,20 @@ 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); - void onRender(); + void onRender() override; private: + Font* getFont() const; + void calculateExtent(); unsigned int mColor; Font* mFont; - bool mAutoCalcExtent; + Vector2 mAutoCalcExtent; std::string mText; }; 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) { 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;