Added initial theme support. More work on this to come.

Sped up image rendering a bit.
Added a framerate display - uncomment the #define DRAWFRAMERATE line in GuiGameList.cpp to see it.
This commit is contained in:
Aloshi 2012-08-10 14:28:34 -05:00
parent 2a0c338cdf
commit 5ae029cd89
12 changed files with 414 additions and 27 deletions

View file

@ -1,7 +1,7 @@
CC=g++
CFLAGS=-c -Wall
LDFLAGS=-lSDL -lSDL_ttf -lSDL_image -lSDL_gfx -lboost_system -lboost_filesystem
SRCSOURCES=main.cpp Renderer.cpp Renderer_draw.cpp GuiComponent.cpp InputManager.cpp SystemData.cpp GameData.cpp FolderData.cpp XMLReader.cpp components/GuiGameList.cpp components/GuiInputConfig.cpp components/GuiImage.cpp components/GuiMenu.cpp pugiXML/pugixml.cpp
SRCSOURCES=main.cpp Renderer.cpp Renderer_draw.cpp GuiComponent.cpp InputManager.cpp SystemData.cpp GameData.cpp FolderData.cpp XMLReader.cpp MathExp.cpp components/GuiGameList.cpp components/GuiInputConfig.cpp components/GuiImage.cpp components/GuiMenu.cpp components/GuiTheme.cpp pugiXML/pugixml.cpp
SOURCES=$(addprefix src/,$(SRCSOURCES))
OBJECTS=$(SOURCES:.cpp=.o)
EXECUTABLE=emulationstation
@ -16,3 +16,4 @@ $(EXECUTABLE): $(OBJECTS)
clean:
rm -rf src/*o src/components/*o $(EXECUTABLE)

View file

@ -1,6 +1,11 @@
August 9
-Removed multithreaded image loading
-Improved GuiImage rendering speed by adding additional processing at load time (SDL_DisplayFormat)
-Began work on the GuiTheme class, allowing custom theming with an XML file
August 8
-Added automatic resizing of images using SDL_gfx
-Multithreaded image loading for the GuiImage class!
-Experimenting with multithreaded image loading for the GuiImage class
-Removed warning if an unknown variable is found in a systems config file (useful for additional utilities)
August 7

View file

@ -33,7 +33,7 @@ void InputManager::unregisterComponent(GuiComponent* comp)
void InputManager::processEvent(SDL_Event* event)
{
bool keyDown = false;
InputButton button;
InputButton button = UNKNOWN;
lastEvent = event;

163
src/MathExp.cpp Normal file
View file

@ -0,0 +1,163 @@
#include "MathExp.h"
#include <iostream>
#include <sstream>
bool MathExp::isOperator(const char c)
{
if(c == *"+" || c == *"-" || c == *"*" || c == *"/" || c == *"(")
return true;
else
return false;
}
bool MathExp::isRParen(const char c)
{
if(c == *")")
return true;
else
return false;
}
int MathExp::getPrecedence(const char c)
{
if(c == *"(")
return -5;
if(c == *"+" || c == *"-")
return 0;
if(c == *"*" || c == *"/")
return 1;
std::cout << "Error - getPrecedence(): unknown character '" << c << "'\n";
return -1;
}
float MathExp::eval()
{
unsigned int start = 0;
for(unsigned int i = 0; i < mExpression.length(); i++)
{
if(isOperator(mExpression.at(i)))
{
//the string from start to i is an operand, and i is an operator
if(start != i) //if we actually do have an operand
mOperands.push(strToVal(mExpression.substr(start, i - start)));
else
std::cout << "skipping operand, start == i\n";
//now we must decide what to do with the operator
const char op = mExpression.at(i);
if(op != *"(")
{
while(mOperators.size() && getPrecedence(mOperators.top()) >= getPrecedence(op))
{
doNextOperation();
}
}
mOperators.push(op);
start = i + 1;
}else{
if(isRParen(mExpression.at(i)))
{
while(mOperators.top() != *"(")
{
doNextOperation();
}
mOperators.pop();
}
}
}
mOperands.push(strToVal(mExpression.substr(start, mExpression.length() - start)));
while(mOperators.size() > 0)
doNextOperation();
if(mOperands.size() != 1)
{
std::cout << "Error - mOperands.size() = " << mOperands.size() << " at the end of evaluation!\n";
return 0;
}
float final = mOperands.top();
mOperands.pop();
return final;
}
void MathExp::doNextOperation()
{
//pop operator off and apply it, then push the value onto the operand stack
const char top = mOperators.top();
float val = 0;
if(top == *"+")
{
val = mOperands.top();
mOperands.pop();
val += mOperands.top();
mOperands.pop();
}
if(top == *"-")
{
val = mOperands.top();
mOperands.pop();
val -= mOperands.top();
mOperands.pop();
}
if(top == *"*")
{
val = mOperands.top();
mOperands.pop();
val *= mOperands.top();
mOperands.pop();
}
if(top == *"/")
{
val = mOperands.top();
mOperands.pop();
val /= mOperands.top();
mOperands.pop();
}
mOperands.push(val);
mOperators.pop();
}
void MathExp::setExpression(std::string str)
{
mExpression = str;
}
void MathExp::setVariable(std::string name, float val)
{
mVariables[name] = val;
}
float MathExp::getVariable(std::string name)
{
return mVariables[name];
}
float MathExp::strToVal(std::string str)
{
if(str[0] == *"$")
return getVariable(str.substr(1, str.length() - 1)); //it's a variable!
//it's a value!
std::stringstream stream;
stream << str;
float value;
stream >> value;
return value;
}

35
src/MathExp.h Normal file
View file

@ -0,0 +1,35 @@
#ifndef _MATHEXP_H_
#define _MATHEXP_H_
#include <string>
#include <stack>
#include <map>
class MathExp {
public:
void setExpression(std::string str);
void setVariable(std::string name, float val);
float getVariable(std::string name);
float eval();
private:
//float apply(const char operatorChar, std::string operand);
void doNextOperation();
bool isOperator(const char c);
bool isRParen(const char c);
int getPrecedence(const char c);
float strToVal(std::string str);
std::string mExpression;
std::stack<float> mOperands;
std::stack<char> mOperators;
std::map<std::string, float> mVariables;
};
#endif

View file

@ -2,9 +2,14 @@
#include "../InputManager.h"
#include <iostream>
#include "GuiMenu.h"
#include <boost/filesystem.hpp>
#define SCREENSHOTWIDTH 256
#define SCREENSHOTHEIGHT 256
//#define DRAWFRAMERATE
#ifdef DRAWFRAMERATE
#include <sstream>
extern float FRAMERATE;
#endif
GuiGameList::GuiGameList(bool useDetail)
{
@ -12,17 +17,22 @@ GuiGameList::GuiGameList(bool useDetail)
mDetailed = useDetail;
//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 a screenshto and description.
//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 GuiList<FileData*>(Renderer::getScreenWidth() * 0.4, Renderer::getFontHeight(Renderer::LARGE) + 2);
mTheme = new GuiTheme();
//addChild(mTheme);
updateTheme();
mScreenshot = new GuiImage(Renderer::getScreenWidth() * 0.2, Renderer::getFontHeight(Renderer::LARGE) + 2, "", Renderer::getScreenWidth() * 0.3);
addChild(mScreenshot);
}else{
mList = new GuiList<FileData*>(0, Renderer::getFontHeight(Renderer::LARGE) + 2);
mScreenshot = NULL;
mTheme = NULL;
}
addChild(mList);
@ -41,6 +51,7 @@ GuiGameList::~GuiGameList()
if(mDetailed)
{
delete mScreenshot;
delete mTheme;
}
InputManager::unregisterComponent(this);
@ -73,11 +84,23 @@ void GuiGameList::setSystemId(int id)
void GuiGameList::onRender()
{
Renderer::drawRect(0, 0, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0xFFFFFF);
if(mTheme)
mTheme->render();
else
Renderer::drawRect(0, 0, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0xFFFFFF);
#ifdef DRAWFRAMERATE
std::stringstream ss;
ss << FRAMERATE;
std::string fps;
ss >> fps;
Renderer::drawText(fps, 0, 0, 0x00FF00);
#endif
Renderer::drawCenteredText(mSystem->getName(), 0, 1, 0x0000FF, Renderer::LARGE);
if(mDetailed)
{
Renderer::drawRect(Renderer::getScreenWidth() * 0.4, Renderer::getFontHeight(Renderer::LARGE) + 2, 8, Renderer::getScreenHeight(), 0x0000FF);
@ -90,7 +113,7 @@ void GuiGameList::onRender()
//todo: cache this
std::string desc = game->getDescription();
if(!desc.empty())
Renderer::drawWrappedText(desc, 2, Renderer::getFontHeight(Renderer::LARGE) + SCREENSHOTHEIGHT + 12, Renderer::getScreenWidth() * 0.4, 0xFF0000);
Renderer::drawWrappedText(desc, 2, Renderer::getFontHeight(Renderer::LARGE) + mScreenshot->getHeight() + 10, Renderer::getScreenWidth() * 0.4, 0xFF0000, Renderer::SMALL);
}
}
}
@ -167,6 +190,17 @@ void GuiGameList::updateList()
}
}
void GuiGameList::updateTheme()
{
std::string themePath = getenv("HOME");
themePath += "/.emulationstation/theme.xml";
if(boost::filesystem::exists(themePath))
{
mTheme->readXML(themePath);
}
}
//these are called when the menu opens/closes
//the second bit should be moved to GuiList
void GuiGameList::onPause()

View file

@ -4,6 +4,7 @@
#include "../GuiComponent.h"
#include "GuiList.h"
#include "GuiImage.h"
#include "GuiTheme.h"
#include <string>
#include <stack>
#include "../SystemData.h"
@ -25,6 +26,8 @@ public:
void onResume();
private:
void updateTheme();
SystemData* mSystem;
FolderData* mFolder;
std::stack<FolderData*> mFolderStack;
@ -33,6 +36,7 @@ private:
GuiList<FileData*>* mList;
GuiImage* mScreenshot;
GuiTheme* mTheme;
};
#endif

View file

@ -7,12 +7,8 @@
int GuiImage::getWidth() { if(mSurface) return mSurface->w; else return 0; }
int GuiImage::getHeight() { if(mSurface) return mSurface->h; else return 0; }
int startImageLoadThread(void*);
GuiImage::GuiImage(int offsetX, int offsetY, std::string path, unsigned int maxWidth, unsigned int maxHeight)
GuiImage::GuiImage(int offsetX, int offsetY, std::string path, unsigned int maxWidth, unsigned int maxHeight, bool resizeExact)
{
std::cout << "Creating GuiImage\n";
mSurface = NULL;
mOffsetX = offsetX;
@ -20,6 +16,7 @@ GuiImage::GuiImage(int offsetX, int offsetY, std::string path, unsigned int maxW
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
mResizeExact = resizeExact;
if(!path.empty())
setImage(path);
@ -47,24 +44,41 @@ void GuiImage::loadImage(std::string path)
//resize it
if(mMaxWidth && newSurf->w > mMaxWidth)
if(mResizeExact)
{
double scale = (double)mMaxWidth / (double)newSurf->w;
double scaleX = (double)mMaxWidth / (double)newSurf->w;
double scaleY = (double)mMaxHeight / (double)newSurf->h;
SDL_Surface* resSurf = zoomSurface(newSurf, scale, scale, SMOOTHING_OFF);
SDL_Surface* resSurf = zoomSurface(newSurf, scaleX, scaleY, SMOOTHING_OFF);
SDL_FreeSurface(newSurf);
newSurf = resSurf;
}
}else{
if(mMaxWidth && newSurf->w > mMaxWidth)
{
double scale = (double)mMaxWidth / (double)newSurf->w;
if(mMaxHeight && newSurf->h > mMaxHeight)
{
double scale = (double)mMaxHeight / (double)newSurf->h;
SDL_Surface* resSurf = zoomSurface(newSurf, scale, scale, SMOOTHING_OFF);
SDL_FreeSurface(newSurf);
newSurf = resSurf;
}
SDL_Surface* resSurf = zoomSurface(newSurf, scale, scale, SMOOTHING_OFF);
SDL_FreeSurface(newSurf);
newSurf = resSurf;
if(mMaxHeight && newSurf->h > mMaxHeight)
{
double scale = (double)mMaxHeight / (double)newSurf->h;
SDL_Surface* resSurf = zoomSurface(newSurf, scale, scale, SMOOTHING_OFF);
SDL_FreeSurface(newSurf);
newSurf = resSurf;
}
}
//convert it into display format for faster rendering
SDL_Surface* dispSurf = SDL_DisplayFormat(newSurf);
SDL_FreeSurface(newSurf);
newSurf = dispSurf;
//finally set the image and delete the old one
if(mSurface)
SDL_FreeSurface(mSurface);
@ -74,8 +88,8 @@ void GuiImage::loadImage(std::string path)
//Also update the rect
mRect.x = mOffsetX - (mSurface->w / 2);
mRect.y = mOffsetY;
mRect.w = 0;
mRect.h = 0;
mRect.w = mSurface->w;
mRect.h = mSurface->h;
}else{
std::cerr << "File \"" << path << "\" not found!\n";
}

View file

@ -9,7 +9,7 @@
class GuiImage : public GuiComponent
{
public:
GuiImage(int offsetX = 0, int offsetY = 0, std::string path = "", unsigned int maxWidth = 0, unsigned int maxHeight = 0);
GuiImage(int offsetX = 0, int offsetY = 0, std::string path = "", unsigned int maxWidth = 0, unsigned int maxHeight = 0, bool resizeExact = false);
~GuiImage();
void setImage(std::string path);
@ -21,6 +21,7 @@ public:
private:
int mMaxWidth, mMaxHeight;
bool mResizeExact;
void loadImage(std::string path);

104
src/components/GuiTheme.cpp Normal file
View file

@ -0,0 +1,104 @@
#include "GuiTheme.h"
#include "../MathExp.h"
#include <iostream>
#include "GuiImage.h"
GuiTheme::GuiTheme(std::string path)
{
if(!path.empty())
readXML(path);
}
GuiTheme::~GuiTheme()
{
deleteComponents();
}
void GuiTheme::deleteComponents()
{
for(unsigned int i = 0; i < mComponentVector.size(); i++)
{
delete mComponentVector.at(i);
}
mComponentVector.clear();
}
void GuiTheme::readXML(std::string path)
{
deleteComponents();
std::cout << "Loading theme \"" << path << "\"...\n";
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(path.c_str());
if(!result)
{
std::cerr << "Error parsing theme \"" << path << "\"!\n";
std::cerr << " " << result.description() << "\n";
return;
}
pugi::xml_node root = doc.child("theme");
for(pugi::xml_node data = root.child("component"); data; data = data.next_sibling("component"))
{
createElement(data, this);
}
std::cout << "Finished parsing theme.\n";
}
GuiComponent* GuiTheme::createElement(pugi::xml_node data, GuiComponent* parent)
{
std::string type = data.child("type").text().get();
if(type == "image")
{
std::string path = data.child("path").text().get();
std::string pos = data.child("pos").text().get();
std::string dim = data.child("dim").text().get();
//split position and dimension information
size_t posSplit = pos.find(' ');
std::string posX = pos.substr(0, posSplit);
std::string posY = pos.substr(posSplit + 1, pos.length() - posSplit - 1);
size_t dimSplit = dim.find(' ');
std::string dimW = dim.substr(0, dimSplit);
std::string dimH = dim.substr(dimSplit + 1, dim.length() - dimSplit - 1);
std::cout << "image, x: " << posX << " y: " << posY << " w: " << dimW << " h: " << dimH << "\n";
//resolve to pixels from percentages/variables
int x = resolveExp(posX) * Renderer::getScreenWidth();
int y = resolveExp(posY) * Renderer::getScreenHeight();
int w = resolveExp(dimW) * Renderer::getScreenWidth();
int h = resolveExp(dimH) * Renderer::getScreenHeight();
std::cout << "w: " << w << " h: " << h << "\n";
GuiComponent* comp = new GuiImage(x, y, path, w, h, true);
parent->addChild(comp);
mComponentVector.push_back(comp);
return comp;
}
std::cerr << "Type \"" << type << "\" unknown!\n";
return NULL;
}
int GuiTheme::resolveExp(std::string str)
{
MathExp exp;
exp.setExpression(str);
//set variables
exp.setVariable("headerHeight", Renderer::getFontHeight(Renderer::LARGE) / Renderer::getScreenHeight());
return (int)exp.eval();
}

24
src/components/GuiTheme.h Normal file
View file

@ -0,0 +1,24 @@
#ifndef _GUITHEME_H_
#define _GUITHEME_H_
#include "../GuiComponent.h"
#include "../pugiXML/pugixml.hpp"
class GuiTheme : public GuiComponent
{
public:
GuiTheme(std::string path = "");
~GuiTheme();
void readXML(std::string path);
private:
void deleteComponents();
GuiComponent* createElement(pugi::xml_node data, GuiComponent* parent);
int resolveExp(std::string str);
std::vector<GuiComponent*> mComponentVector;
};
#endif

View file

@ -8,6 +8,7 @@
#include "components/GuiInputConfig.h"
bool PARSEGAMELISTONLY = false;
float FRAMERATE = 0;
int main(int argc, char* argv[])
{
@ -166,6 +167,7 @@ int main(int argc, char* argv[])
int deltaTime = curTime - lastTime;
lastTime = curTime;
FRAMERATE = 1/((float)deltaTime)*1000;
GuiComponent::processTicks(deltaTime);
Renderer::render();