diff --git a/THEMES.md b/THEMES.md index 6c6748239..639e06e25 100644 --- a/THEMES.md +++ b/THEMES.md @@ -5,8 +5,7 @@ EmulationStation allows each system to have its own "theme." A theme is a collec ES will check two places for a theme: first, the system's search directory for theme.xml. Then, if that's not found, $HOME/.emulationstation/es_theme.xml. -Almost all positions, dimensions, etc. work in percentages - that is, they are a decimal between 0 and 1, representing the percentage of the screen on that axis to use. -This ensures that themes look similar at every resolution. +Almost all positions, dimensions, origins, etc. work in percentages - that is, they are a decimal between 0 and 1, representing the percentage of the screen on that axis to use. This ensures that themes look similar at every resolution. Example @@ -61,13 +60,13 @@ Display tags must be at the root of the tree - for example, they can't b `` - the hex font color to use for the description on the GuiGameList. -`` - the hex color to use for the "selector bar" on the GuiGameList. +`` - the hex color to use for the "selector bar" on the GuiGameList. Default is `000000`. `` - the hex color to use for selected text on the GuiGameList. Default is -1, which will not change the color. `` - if present, the games list names will be left aligned to the value of `` (default 0.5). -`` - if present, the system name header won't be displayed (useful for replacing it with an image). +`` - if present, the system name header won't be displayed (useful for replacing it with an image). If you're making a complete custom theme, you probably want to use this. `` - if present, the divider between games on the detailed GuiGameList won't be displayed. @@ -75,11 +74,17 @@ Display tags must be at the root of the tree - for example, they can't b `` - the percentage to offset the text in the list by. Default is 0.005. Only works in combination with ``. -`` - the percentage to offset the displayed game image by. Default is the height of the header font. +~~`` - the percentage to offset the displayed game image by. Default is the height of the header font.~~ + +`` - two values for the position of the game art, in the form of `[x] [y]`, as a percentage. Default is `$infoWidth/2 $headerHeight`. + +`' - two values for the dimensions of the game art, in the form of `[width] [height]`, as a percentage of the screen. Default is `0 0` (not resized). The image will only be resized if at least one axis is nonzero *and* exceeded by the image's size. You should always leave at least one axis as zero to preserve the aspect ratio. + +`` two values for the origin of the game art, in the form of `[x] [y]`, as a percentage. Default is `0.5 0`. -The Fast Select box can be themed with these tags: +**The Fast Select box can be themed with these tags:** `` - the hex color to use for the letter display on the Fast Select box. @@ -117,9 +122,11 @@ List of variables Variables can be used in position and dimension definitions. They can be added, subtracted, multiplied, and divided. Parenthesis are valid. They are a percentage of the screen. +For example, if you wanted to place an image that covered the left half of the screen, up to the game list, you could use `$infoWidth 1`. + `$headerHeight` - height of the system name header. -`$infoWidth` - where the center of the horizontal divider is drawn. +`$infoWidth` - where the left of the game list begins. Will follow ``. -Aloshi diff --git a/changelog.txt b/changelog.txt index 4962ee839..c6b749e1c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,7 @@ October 13 -Added sound support through SDL_mixer. -Added new theme tags for defining menu sounds. See THEMES.md for details. +-Added new theme tags for defining game art information. See THEMES.md for details. October 10 -Added a theming tag for the Fast Select box's text. diff --git a/src/MathExp.cpp b/src/MathExp.cpp index 5055e6ef3..be5d42d6f 100644 --- a/src/MathExp.cpp +++ b/src/MathExp.cpp @@ -109,7 +109,7 @@ void MathExp::doNextOperation() { val = mOperands.top(); mOperands.pop(); - val -= mOperands.top(); + val = mOperands.top() - val; mOperands.pop(); } if(top == *"*") @@ -123,7 +123,7 @@ void MathExp::doNextOperation() { val = mOperands.top(); mOperands.pop(); - val /= mOperands.top(); + val = mOperands.top() / val; mOperands.pop(); } diff --git a/src/components/GuiGameList.cpp b/src/components/GuiGameList.cpp index 2d0954802..4da5523fc 100644 --- a/src/components/GuiGameList.cpp +++ b/src/components/GuiGameList.cpp @@ -5,7 +5,7 @@ #include "GuiFastSelect.h" #include - +//this is just a default value; the true value is in mTheme->getListOffsetX(); const float GuiGameList::sInfoWidth = 0.5; @@ -23,8 +23,8 @@ GuiGameList::GuiGameList(bool useDetail) { mList = new GuiList(Renderer::getScreenWidth() * sInfoWidth, Renderer::getFontHeight(Renderer::LARGE) + 2); - mScreenshot = new GuiImage(Renderer::getScreenWidth() * sInfoWidth * 0.5, Renderer::getScreenHeight() * mTheme->getGameImageOffsetY(), "", Renderer::getScreenWidth() * sInfoWidth * 0.7); - mScreenshot->setOrigin(0.5, 0.0); + mScreenshot = new GuiImage(Renderer::getScreenWidth() * sInfoWidth * 0.5, Renderer::getScreenHeight() * mTheme->getGameImageOffsetY(), "", Renderer::getScreenWidth() * sInfoWidth * 0.7, 0, false); + mScreenshot->setOrigin(mTheme->getGameImageOriginX(), mTheme->getGameImageOriginY()); addChild(mScreenshot); }else{ mList = new GuiList(0, Renderer::getFontHeight(Renderer::LARGE) + 2); @@ -70,7 +70,7 @@ void GuiGameList::setSystemId(int id) mSystemId = id; mSystem = SystemData::sSystemVector.at(mSystemId); - //clear the folder stack (I can't look up the proper method right now) + //clear the folder stack while(mFolderStack.size()){ mFolderStack.pop(); } mFolder = mSystem->getRootFolder(); @@ -93,7 +93,7 @@ void GuiGameList::onRender() { //divider if(!mTheme->getDividersHidden()) - Renderer::drawRect(Renderer::getScreenWidth() * sInfoWidth - 4, Renderer::getFontHeight(Renderer::LARGE) + 2, 8, Renderer::getScreenHeight(), 0x0000FF); + Renderer::drawRect(Renderer::getScreenWidth() * mTheme->getListOffsetX() - 4, Renderer::getFontHeight(Renderer::LARGE) + 2, 8, Renderer::getScreenHeight(), 0x0000FF); //if we're not scrolling and we have selected a non-folder if(!mList->isScrolling() && mList->getSelectedObject() && !mList->getSelectedObject()->isFolder()) @@ -102,7 +102,7 @@ void GuiGameList::onRender() std::string desc = game->getDescription(); if(!desc.empty()) - Renderer::drawWrappedText(desc, Renderer::getScreenWidth() * 0.03, mScreenshot->getOffsetY() + mScreenshot->getHeight() + 8, Renderer::getScreenWidth() * (sInfoWidth - 0.03), mTheme->getDescColor(), Renderer::SMALL); + Renderer::drawWrappedText(desc, Renderer::getScreenWidth() * 0.03, mScreenshot->getOffsetY() + mScreenshot->getHeight() + 12, Renderer::getScreenWidth() * (mTheme->getListOffsetX() - 0.03), mTheme->getDescColor(), Renderer::SMALL); } } } @@ -223,7 +223,10 @@ void GuiGameList::updateTheme() { mList->setOffsetX(mTheme->getListOffsetX() * Renderer::getScreenWidth()); mList->setTextOffsetX(mTheme->getListTextOffsetX() * Renderer::getScreenWidth()); + + mScreenshot->setOffsetX(mTheme->getGameImageOffsetX() * Renderer::getScreenWidth()); mScreenshot->setOffsetY(mTheme->getGameImageOffsetY() * Renderer::getScreenHeight()); + mScreenshot->setOrigin(mTheme->getGameImageOriginX(), mTheme->getGameImageOriginY()); } } diff --git a/src/components/GuiList.cpp b/src/components/GuiList.cpp index 2d7f79471..9e72a6b3c 100644 --- a/src/components/GuiList.cpp +++ b/src/components/GuiList.cpp @@ -197,7 +197,7 @@ int GuiList::getSelection() template bool GuiList::isScrolling() { - return mScrolling; + return mScrollDir != 0; } template diff --git a/src/components/GuiTheme.cpp b/src/components/GuiTheme.cpp index 58f515a7c..7d7681b2d 100644 --- a/src/components/GuiTheme.cpp +++ b/src/components/GuiTheme.cpp @@ -15,9 +15,7 @@ int GuiTheme::getFastSelectColor() { return mFastSelectColor; } bool GuiTheme::getHeaderHidden() { return mHideHeader; } bool GuiTheme::getDividersHidden() { return mHideDividers; } bool GuiTheme::getListCentered() { return mListCentered; } - float GuiTheme::getListOffsetX() { return mListOffsetX; } -float GuiTheme::getGameImageOffsetY() { return mGameImageOffsetY; } float GuiTheme::getListTextOffsetX() { return mListTextOffsetX; } int GuiTheme::getSelectedTextColor() { return mListSelectedColor; } @@ -29,6 +27,13 @@ Sound* GuiTheme::getMenuSelectSound() { return &mMenuSelectSound; } Sound* GuiTheme::getMenuBackSound() { return &mMenuBackSound; } Sound* GuiTheme::getMenuOpenSound() { return &mMenuOpenSound; } +float GuiTheme::getGameImageOffsetX() { return mGameImageOffsetX; } +float GuiTheme::getGameImageOffsetY() { return mGameImageOffsetY; } +float GuiTheme::getGameImageWidth() { return mGameImageWidth; } +float GuiTheme::getGameImageHeight() { return mGameImageHeight; } +float GuiTheme::getGameImageOriginX() { return mGameImageOriginX; } +float GuiTheme::getGameImageOriginY() { return mGameImageOriginY; } + GuiTheme::GuiTheme(std::string path) { setDefaults(); @@ -56,7 +61,13 @@ void GuiTheme::setDefaults() mListOffsetX = 0.5; mListTextOffsetX = 0.005; + + mGameImageOriginX = 0.5; + mGameImageOriginY = 0; + mGameImageOffsetX = mListOffsetX / 2; mGameImageOffsetY = (float)Renderer::getFontHeight(Renderer::LARGE) / Renderer::getScreenHeight(); + mGameImageWidth = 0; + mGameImageHeight = 0; mBoxData.backgroundPath = ""; mBoxData.backgroundTiled = false; @@ -122,7 +133,6 @@ void GuiTheme::readXML(std::string path) mFastSelectColor = resolveColor(root.child("fastSelectColor").text().get(), mFastSelectColor); mHideHeader = root.child("hideHeader"); mHideDividers = root.child("hideDividers"); - mListCentered = !root.child("listLeftAlign"); //GuiBox theming data mBoxData.backgroundPath = expandPath(root.child("boxBackground").text().get()); @@ -133,20 +143,40 @@ void GuiTheme::readXML(std::string path) mBoxData.verticalTiled = root.child("boxVerticalTiled"); mBoxData.cornerPath = expandPath(root.child("boxCorner").text().get()); + //list stuff + mListCentered = !root.child("listLeftAlign"); mListOffsetX = strToFloat(root.child("listOffsetX").text().get(), mListOffsetX); - mGameImageOffsetY = strToFloat(root.child("gameImageOffsetY").text().get(), mGameImageOffsetY); mListTextOffsetX = strToFloat(root.child("listTextOffsetX").text().get(), mListTextOffsetX); + + //game image stuff + std::string artPos = root.child("gameImagePos").text().get(); + std::string artDim = root.child("gameImageDim").text().get(); + std::string artOrigin = root.child("gameImageOrigin").text().get(); + + std::string artPosX, artPosY, artWidth, artHeight, artOriginX, artOriginY; + splitString(artPos, ' ', &artPosX, &artPosY); + splitString(artDim, ' ', &artWidth, &artHeight); + splitString(artOrigin, ' ', &artOriginX, &artOriginY); + + mGameImageOffsetX = resolveExp(artPosX, mGameImageOffsetX); + mGameImageOffsetY = resolveExp(artPosY, mGameImageOffsetY); + mGameImageWidth = resolveExp(artWidth, mGameImageWidth); + mGameImageHeight = resolveExp(artHeight, mGameImageHeight); + mGameImageOriginX = resolveExp(artOriginX, mGameImageOriginX); + mGameImageOriginY = resolveExp(artOriginY, mGameImageOriginY); + //sounds mMenuScrollSound.loadFile(expandPath(root.child("menuScrollSound").text().get())); mMenuSelectSound.loadFile(expandPath(root.child("menuSelectSound").text().get())); mMenuBackSound.loadFile(expandPath(root.child("menuBackSound").text().get())); mMenuOpenSound.loadFile(expandPath(root.child("menuOpenSound").text().get())); - //recursively create children for all with proper parenting + //actually read the components createComponentChildren(root, this); } +//recursively creates components (with proper parenting) void GuiTheme::createComponentChildren(pugi::xml_node node, GuiComponent* parent) { for(pugi::xml_node data = node.child("component"); data; data = data.next_sibling("component")) @@ -158,6 +188,7 @@ void GuiTheme::createComponentChildren(pugi::xml_node node, GuiComponent* parent } } +//takes an XML element definition and creates an object from it GuiComponent* GuiTheme::createElement(pugi::xml_node data, GuiComponent* parent) { std::string type = data.child("type").text().get(); @@ -188,8 +219,6 @@ GuiComponent* GuiTheme::createElement(pugi::xml_node data, GuiComponent* parent) std::string originX, originY; splitString(origin, ' ', &originX, &originY); - //std::cout << "image, x: " << posX << " y: " << posY << " w: " << dimW << " h: " << dimH << " ox: " << originX << " oy: " << originY << " tiled: " << tiled << "\n"; - //resolve to pixels from percentages/variables int x = resolveExp(posX) * Renderer::getScreenWidth(); int y = resolveExp(posY) * Renderer::getScreenHeight(); @@ -210,10 +239,11 @@ GuiComponent* GuiTheme::createElement(pugi::xml_node data, GuiComponent* parent) } - std::cerr << "Type \"" << type << "\" unknown!\n"; + std::cerr << "Theme component type \"" << type << "\" unknown!\n"; return NULL; } +//expands a file path (./ becomes the directory of this theme file, ~/ becomes $HOME/) std::string GuiTheme::expandPath(std::string path) { if(path[0] == '~') @@ -224,18 +254,23 @@ std::string GuiTheme::expandPath(std::string path) return path; } -float GuiTheme::resolveExp(std::string str) +//takes a string containing a mathematical expression (possibly including variables) and resolves it to a float value +float GuiTheme::resolveExp(std::string str, float defaultVal) { + if(str.empty()) + return defaultVal; + MathExp exp; exp.setExpression(str); //set variables exp.setVariable("headerHeight", Renderer::getFontHeight(Renderer::LARGE) / Renderer::getScreenHeight()); - exp.setVariable("infoWidth", GuiGameList::sInfoWidth); + exp.setVariable("infoWidth", mListOffsetX); return exp.eval(); } +//takes a string of hex and resolves it to an integer int GuiTheme::resolveColor(std::string str, int defaultColor) { if(str.empty()) @@ -249,16 +284,23 @@ int GuiTheme::resolveColor(std::string str, int defaultColor) return ret; } +//splits a string in two at the first instance of the delimiter void GuiTheme::splitString(std::string str, char delim, std::string* before, std::string* after) { if(str.empty()) return; size_t split = str.find(delim); - *before = str.substr(0, split); - *after = str.substr(split + 1, str.length() - split - 1); + if(split != std::string::npos) + { + *before = str.substr(0, split); + *after = str.substr(split + 1, str.length() - split - 1); + }else{ + std::cerr << " Error: tried to splt string \"" << str << "\" with delimiter '" << delim << "', but delimiter was not found!\n"; + } } +//converts a string to a float float GuiTheme::strToFloat(std::string str, float defaultVal) { if(str.empty()) diff --git a/src/components/GuiTheme.h b/src/components/GuiTheme.h index 43305569e..e3715ade6 100644 --- a/src/components/GuiTheme.h +++ b/src/components/GuiTheme.h @@ -27,7 +27,13 @@ public: float getListOffsetX(); float getListTextOffsetX(); + + float getGameImageOffsetX(); float getGameImageOffsetY(); + float getGameImageWidth(); + float getGameImageHeight(); + float getGameImageOriginX(); + float getGameImageOriginY(); GuiBoxData getBoxData(); @@ -43,7 +49,7 @@ private: //utility functions std::string expandPath(std::string path); - float resolveExp(std::string str); + float resolveExp(std::string str, float defaultVal = 0.0); int resolveColor(std::string str, int defaultColor = 0x000000); void splitString(std::string str, char delim, std::string* before, std::string* after); float strToFloat(std::string str, float defaultVal = 0.0f); @@ -53,7 +59,7 @@ private: int mListPrimaryColor, mListSecondaryColor, mListSelectorColor, mListSelectedColor, mDescColor, mFastSelectColor; bool mHideHeader, mHideDividers, mListCentered; - float mListOffsetX, mGameImageOffsetY, mListTextOffsetX; + float mListOffsetX, mListTextOffsetX, mGameImageOffsetX, mGameImageOffsetY, mGameImageWidth, mGameImageHeight, mGameImageOriginX, mGameImageOriginY; GuiBoxData mBoxData;