Merge branch 'master' into unstable

Conflicts:
	src/components/ImageComponent.h
This commit is contained in:
Aloshi 2013-07-03 01:30:44 -05:00
commit fe8c592623
34 changed files with 1093 additions and 320 deletions

View file

@ -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

View file

@ -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:
```

View file

@ -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

View file

@ -1,19 +1,29 @@
#include "FolderData.h"
#include "SystemData.h"
#include "GameData.h"
#include <algorithm>
#include <iostream>
bool FolderData::isFolder() { return true; }
std::string FolderData::getName() { return mName; }
std::string FolderData::getPath() { return mPath; }
std::map<FolderData::ComparisonFunction*, std::string> 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<const GameData*>(file1);
const GameData * game2 = dynamic_cast<const GameData*>(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<const GameData*>(file1);
const GameData * game2 = dynamic_cast<const GameData*>(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<const GameData*>(file1);
const GameData * game2 = dynamic_cast<const GameData*>(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<const GameData*>(file1);
const GameData * game2 = dynamic_cast<const GameData*>(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<FileData*> FolderData::getFiles(bool onlyFiles) const
{
std::vector<FileData*> temp;
//now check if a child is a folder and get those children in turn
std::vector<FileData*>::const_iterator fdit = mFileVector.cbegin();
while(fdit != mFileVector.cend()) {
//dynamically try to cast to FolderData type
FolderData * folder = dynamic_cast<FolderData*>(*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<FileData*> FolderData::getFilesRecursive(bool onlyFiles) const
{
std::vector<FileData*> temp;
//now check if a child is a folder and get those children in turn
std::vector<FileData*>::const_iterator fdit = mFileVector.cbegin();
while(fdit != mFileVector.cend()) {
//dynamically try to cast to FolderData type
FolderData * folder = dynamic_cast<FolderData*>(*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<FileData*> 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;
}

View file

@ -1,28 +1,54 @@
#ifndef _FOLDER_H_
#define _FOLDER_H_
#include "FileData.h"
#include <map>
#include <vector>
#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<ComparisonFunction*, std::string> 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<FileData*> getFiles(bool onlyFiles = false) const;
std::vector<FileData*> 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;

View file

@ -5,7 +5,6 @@
#include "Renderer.h"
#include <boost/filesystem.hpp>
#include "Log.h"
#include "Vector2.h"
FT_Library Font::sLibrary;
bool Font::libraryInitialized = false;
@ -220,13 +219,14 @@ Font::~Font()
}
struct Vertex
{
Vector2<GLfloat> pos;
Vector2<GLfloat> 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<GLubyte*>(colors), color, vertCount);
}

View file

@ -6,6 +6,9 @@
#include GLHEADER
#include <ft2build.h>
#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<GLfloat> pos;
Vector2<GLfloat> 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

View file

@ -2,23 +2,110 @@
#include <boost/filesystem.hpp>
#include <iostream>
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;
}

View file

@ -2,6 +2,8 @@
#define _GAMEDATA_H_
#include <string>
#include <ctime>
#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

View file

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

View file

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

View file

@ -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()

View file

@ -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

View file

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

View file

@ -33,6 +33,8 @@ void Settings::setDefaults()
mBoolMap["WINDOWED"] = false;
mIntMap["DIMTIME"] = 30*1000;
mIntMap["GameListSortIndex"] = 0;
}
template <typename K, typename V>

View file

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

View file

@ -93,6 +93,7 @@ bool operator !=(const Vector2<T>& left, const Vector2<T>& right)
typedef Vector2<int> Vector2i;
typedef Vector2<unsigned int> Vector2u;
typedef Vector2<float> Vector2f;
typedef Vector2<double> Vector2d;
class Rect
{

View file

@ -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 <gameList> 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) << "<game> node contains no <path> 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<FileData*> files = rootFolder->getFilesRecursive(true);
//iterate through all files, checking if they're already in the XML
std::vector<FileData*>::const_iterator fit = files.cbegin();
while(fit != files.cend()) {
//try to cast to gamedata
const GameData * game = dynamic_cast<const GameData*>(*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() << "\"!";
}
}

View file

@ -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

View file

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

View file

@ -2,7 +2,6 @@
#define _ANIMATIONCOMPONENT_H_
#include "../GuiComponent.h"
#include "ImageComponent.h"
#include <vector>
#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<ImageComponent*> mChildren;
std::vector<GuiComponent*> mChildren;
void moveChildren(int offsetx, int offsety);
void setChildrenOpacity(unsigned char opacity);

View file

@ -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<FileData*>* list, char startLetter, GuiBoxData data,
int textcolor, std::shared_ptr<Sound> & scrollsound, Font* font) : GuiComponent(window)
GuiFastSelect::GuiFastSelect(Window* window, GuiGameList* parent, TextListComponent<FileData*>* 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;

View file

@ -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<FileData*>* list, char startLetter, GuiBoxData data,
int textcolor, std::shared_ptr<Sound> & scrollsound, Font* font);
GuiFastSelect(Window* window, GuiGameList* parent, TextListComponent<FileData*>* list, char startLetter, ThemeComponent * theme);
~GuiFastSelect();
bool input(InputConfig* config, Input input);
@ -41,7 +41,7 @@ private:
bool mScrolling;
std::shared_ptr<Sound> mScrollSound;
Font* mFont;
ThemeComponent * mTheme;
};
#endif

View file

@ -7,37 +7,52 @@
#include "../Log.h"
#include "../Settings.h"
std::vector<FolderData::SortState> 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<FileData*>(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<FileData*>(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);
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));
//scale delay with screen width (higher width = more text per line)
//the scroll speed is automatically scaled by component size
mDescContainer.setAutoScroll((int)(1500 + (Renderer::getScreenWidth() * 0.5)), 0.025f);
mTransitionImage.setOffset(Renderer::getScreenWidth(), 0);
mTransitionImage.setOrigin(0, 0);
@ -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,14 +70,7 @@ 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,10 +395,7 @@ 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)

View file

@ -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<FolderData::SortState> 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<FolderData*> mFolderStack;
int mSystemId;
bool mDetailed;
TextListComponent<FileData*>* mList;
ImageComponent* mScreenshot;
TextListComponent<FileData*> mList;
ImageComponent mScreenshot;
TextComponent mDescription;
AnimationComponent* mImageAnimation;
ScrollableContainer mDescContainer;
AnimationComponent mImageAnimation;
ThemeComponent* mTheme;
ImageComponent mTransitionImage;

View file

@ -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()
{

View file

@ -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)

View file

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

View file

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

View file

@ -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<bool>(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);
}

View file

@ -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<bool> mAutoCalcExtent;
std::string mText;
};

View file

@ -117,7 +117,7 @@ void TextListComponent<T>::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)
{

View file

@ -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<Sound>(new Sound);
mSoundMap["menuSelect"] = std::shared_ptr<Sound>(new Sound);
mSoundMap["menuBack"] = std::shared_ptr<Sound>(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");

View file

@ -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<std::string, unsigned int> mColorMap;
std::map<std::string, bool> mBoolMap;