diff --git a/Makefile b/Makefile index 1ba4cdde7..a421c73b6 100644 --- a/Makefile +++ b/Makefile @@ -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) + diff --git a/changelog.txt b/changelog.txt index 6135e6cdb..eb584fa15 100644 --- a/changelog.txt +++ b/changelog.txt @@ -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 diff --git a/src/InputManager.cpp b/src/InputManager.cpp index 242139072..bc7886765 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -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; diff --git a/src/MathExp.cpp b/src/MathExp.cpp new file mode 100644 index 000000000..5055e6ef3 --- /dev/null +++ b/src/MathExp.cpp @@ -0,0 +1,163 @@ +#include "MathExp.h" +#include +#include + +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; +} diff --git a/src/MathExp.h b/src/MathExp.h new file mode 100644 index 000000000..a2fe41eaf --- /dev/null +++ b/src/MathExp.h @@ -0,0 +1,35 @@ +#ifndef _MATHEXP_H_ +#define _MATHEXP_H_ + +#include +#include +#include + +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 mOperands; + std::stack mOperators; + std::map mVariables; + +}; + +#endif diff --git a/src/components/GuiGameList.cpp b/src/components/GuiGameList.cpp index d8057c981..cc9719d71 100644 --- a/src/components/GuiGameList.cpp +++ b/src/components/GuiGameList.cpp @@ -2,9 +2,14 @@ #include "../InputManager.h" #include #include "GuiMenu.h" +#include -#define SCREENSHOTWIDTH 256 -#define SCREENSHOTHEIGHT 256 +//#define DRAWFRAMERATE + +#ifdef DRAWFRAMERATE + #include + 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(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(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() diff --git a/src/components/GuiGameList.h b/src/components/GuiGameList.h index fb69cbd9d..b84e42c6d 100644 --- a/src/components/GuiGameList.h +++ b/src/components/GuiGameList.h @@ -4,6 +4,7 @@ #include "../GuiComponent.h" #include "GuiList.h" #include "GuiImage.h" +#include "GuiTheme.h" #include #include #include "../SystemData.h" @@ -25,6 +26,8 @@ public: void onResume(); private: + void updateTheme(); + SystemData* mSystem; FolderData* mFolder; std::stack mFolderStack; @@ -33,6 +36,7 @@ private: GuiList* mList; GuiImage* mScreenshot; + GuiTheme* mTheme; }; #endif diff --git a/src/components/GuiImage.cpp b/src/components/GuiImage.cpp index 850d55956..f0af5720f 100644 --- a/src/components/GuiImage.cpp +++ b/src/components/GuiImage.cpp @@ -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"; } diff --git a/src/components/GuiImage.h b/src/components/GuiImage.h index f259137fa..4d4d22432 100644 --- a/src/components/GuiImage.h +++ b/src/components/GuiImage.h @@ -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); diff --git a/src/components/GuiTheme.cpp b/src/components/GuiTheme.cpp new file mode 100644 index 000000000..a33207ae9 --- /dev/null +++ b/src/components/GuiTheme.cpp @@ -0,0 +1,104 @@ +#include "GuiTheme.h" +#include "../MathExp.h" +#include +#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(); +} diff --git a/src/components/GuiTheme.h b/src/components/GuiTheme.h new file mode 100644 index 000000000..8449164e0 --- /dev/null +++ b/src/components/GuiTheme.h @@ -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 mComponentVector; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index f405284b2..278284c83 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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();