New game image theming tags! See THEMES.md for details.

Also, some minor fixes all around (game descriptions no longer appear while scrolling, woo!).
This commit is contained in:
Aloshi 2012-10-13 18:37:51 -05:00
parent 65701c58c1
commit 61c9b10abd
7 changed files with 89 additions and 30 deletions

View file

@ -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. 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. 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.
This ensures that themes look similar at every resolution.
Example Example
@ -61,13 +60,13 @@ Display tags must be at the root of the <theme> tree - for example, they can't b
`<descColor>` - the hex font color to use for the description on the GuiGameList. `<descColor>` - the hex font color to use for the description on the GuiGameList.
`<listSelectorColor>` - the hex color to use for the "selector bar" on the GuiGameList. `<listSelectorColor>` - the hex color to use for the "selector bar" on the GuiGameList. Default is `000000`.
`<listSelectedColor>` - the hex color to use for selected text on the GuiGameList. Default is -1, which will not change the color. `<listSelectedColor>` - the hex color to use for selected text on the GuiGameList. Default is -1, which will not change the color.
`<listLeftAlign />` - if present, the games list names will be left aligned to the value of `<listOffsetX>` (default 0.5). `<listLeftAlign />` - if present, the games list names will be left aligned to the value of `<listOffsetX>` (default 0.5).
`<hideHeader />` - if present, the system name header won't be displayed (useful for replacing it with an image). `<hideHeader />` - 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.
`<hideDividers />` - if present, the divider between games on the detailed GuiGameList won't be displayed. `<hideDividers />` - 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 <theme> tree - for example, they can't b
`<listTextOffsetX>` - the percentage to offset the text in the list by. Default is 0.005. Only works in combination with `<listLeftAlign />`. `<listTextOffsetX>` - the percentage to offset the text in the list by. Default is 0.005. Only works in combination with `<listLeftAlign />`.
`<gameImageOffsetY>` - the percentage to offset the displayed game image by. Default is the height of the header font. ~~`<gameImageOffsetY>` - the percentage to offset the displayed game image by. Default is the height of the header font.~~
`<gameImagePos>` - two values for the position of the game art, in the form of `[x] [y]`, as a percentage. Default is `$infoWidth/2 $headerHeight`.
`<gameImageDim>' - 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.
`<gameImageOrigin>` 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:**
`<fastSelectColor>` - the hex color to use for the letter display on the Fast Select box. `<fastSelectColor>` - 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. 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 `<dim>$infoWidth 1</dim>`.
`$headerHeight` - height of the system name header. `$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 `<listOffsetX>`.
-Aloshi -Aloshi

View file

@ -1,6 +1,7 @@
October 13 October 13
-Added sound support through SDL_mixer. -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 menu sounds. See THEMES.md for details.
-Added new theme tags for defining game art information. See THEMES.md for details.
October 10 October 10
-Added a theming tag for the Fast Select box's text. -Added a theming tag for the Fast Select box's text.

View file

@ -109,7 +109,7 @@ void MathExp::doNextOperation()
{ {
val = mOperands.top(); val = mOperands.top();
mOperands.pop(); mOperands.pop();
val -= mOperands.top(); val = mOperands.top() - val;
mOperands.pop(); mOperands.pop();
} }
if(top == *"*") if(top == *"*")
@ -123,7 +123,7 @@ void MathExp::doNextOperation()
{ {
val = mOperands.top(); val = mOperands.top();
mOperands.pop(); mOperands.pop();
val /= mOperands.top(); val = mOperands.top() / val;
mOperands.pop(); mOperands.pop();
} }

View file

@ -5,7 +5,7 @@
#include "GuiFastSelect.h" #include "GuiFastSelect.h"
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
//this is just a default value; the true value is in mTheme->getListOffsetX();
const float GuiGameList::sInfoWidth = 0.5; const float GuiGameList::sInfoWidth = 0.5;
@ -23,8 +23,8 @@ GuiGameList::GuiGameList(bool useDetail)
{ {
mList = new GuiList<FileData*>(Renderer::getScreenWidth() * sInfoWidth, Renderer::getFontHeight(Renderer::LARGE) + 2); mList = new GuiList<FileData*>(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 = new GuiImage(Renderer::getScreenWidth() * sInfoWidth * 0.5, Renderer::getScreenHeight() * mTheme->getGameImageOffsetY(), "", Renderer::getScreenWidth() * sInfoWidth * 0.7, 0, false);
mScreenshot->setOrigin(0.5, 0.0); mScreenshot->setOrigin(mTheme->getGameImageOriginX(), mTheme->getGameImageOriginY());
addChild(mScreenshot); addChild(mScreenshot);
}else{ }else{
mList = new GuiList<FileData*>(0, Renderer::getFontHeight(Renderer::LARGE) + 2); mList = new GuiList<FileData*>(0, Renderer::getFontHeight(Renderer::LARGE) + 2);
@ -70,7 +70,7 @@ void GuiGameList::setSystemId(int id)
mSystemId = id; mSystemId = id;
mSystem = SystemData::sSystemVector.at(mSystemId); 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(); } while(mFolderStack.size()){ mFolderStack.pop(); }
mFolder = mSystem->getRootFolder(); mFolder = mSystem->getRootFolder();
@ -93,7 +93,7 @@ void GuiGameList::onRender()
{ {
//divider //divider
if(!mTheme->getDividersHidden()) 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 we're not scrolling and we have selected a non-folder
if(!mList->isScrolling() && mList->getSelectedObject() && !mList->getSelectedObject()->isFolder()) if(!mList->isScrolling() && mList->getSelectedObject() && !mList->getSelectedObject()->isFolder())
@ -102,7 +102,7 @@ void GuiGameList::onRender()
std::string desc = game->getDescription(); std::string desc = game->getDescription();
if(!desc.empty()) 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->setOffsetX(mTheme->getListOffsetX() * Renderer::getScreenWidth());
mList->setTextOffsetX(mTheme->getListTextOffsetX() * Renderer::getScreenWidth()); mList->setTextOffsetX(mTheme->getListTextOffsetX() * Renderer::getScreenWidth());
mScreenshot->setOffsetX(mTheme->getGameImageOffsetX() * Renderer::getScreenWidth());
mScreenshot->setOffsetY(mTheme->getGameImageOffsetY() * Renderer::getScreenHeight()); mScreenshot->setOffsetY(mTheme->getGameImageOffsetY() * Renderer::getScreenHeight());
mScreenshot->setOrigin(mTheme->getGameImageOriginX(), mTheme->getGameImageOriginY());
} }
} }

View file

@ -197,7 +197,7 @@ int GuiList<listType>::getSelection()
template <typename listType> template <typename listType>
bool GuiList<listType>::isScrolling() bool GuiList<listType>::isScrolling()
{ {
return mScrolling; return mScrollDir != 0;
} }
template <typename listType> template <typename listType>

View file

@ -15,9 +15,7 @@ int GuiTheme::getFastSelectColor() { return mFastSelectColor; }
bool GuiTheme::getHeaderHidden() { return mHideHeader; } bool GuiTheme::getHeaderHidden() { return mHideHeader; }
bool GuiTheme::getDividersHidden() { return mHideDividers; } bool GuiTheme::getDividersHidden() { return mHideDividers; }
bool GuiTheme::getListCentered() { return mListCentered; } bool GuiTheme::getListCentered() { return mListCentered; }
float GuiTheme::getListOffsetX() { return mListOffsetX; } float GuiTheme::getListOffsetX() { return mListOffsetX; }
float GuiTheme::getGameImageOffsetY() { return mGameImageOffsetY; }
float GuiTheme::getListTextOffsetX() { return mListTextOffsetX; } float GuiTheme::getListTextOffsetX() { return mListTextOffsetX; }
int GuiTheme::getSelectedTextColor() { return mListSelectedColor; } int GuiTheme::getSelectedTextColor() { return mListSelectedColor; }
@ -29,6 +27,13 @@ Sound* GuiTheme::getMenuSelectSound() { return &mMenuSelectSound; }
Sound* GuiTheme::getMenuBackSound() { return &mMenuBackSound; } Sound* GuiTheme::getMenuBackSound() { return &mMenuBackSound; }
Sound* GuiTheme::getMenuOpenSound() { return &mMenuOpenSound; } 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) GuiTheme::GuiTheme(std::string path)
{ {
setDefaults(); setDefaults();
@ -56,7 +61,13 @@ void GuiTheme::setDefaults()
mListOffsetX = 0.5; mListOffsetX = 0.5;
mListTextOffsetX = 0.005; mListTextOffsetX = 0.005;
mGameImageOriginX = 0.5;
mGameImageOriginY = 0;
mGameImageOffsetX = mListOffsetX / 2;
mGameImageOffsetY = (float)Renderer::getFontHeight(Renderer::LARGE) / Renderer::getScreenHeight(); mGameImageOffsetY = (float)Renderer::getFontHeight(Renderer::LARGE) / Renderer::getScreenHeight();
mGameImageWidth = 0;
mGameImageHeight = 0;
mBoxData.backgroundPath = ""; mBoxData.backgroundPath = "";
mBoxData.backgroundTiled = false; mBoxData.backgroundTiled = false;
@ -122,7 +133,6 @@ void GuiTheme::readXML(std::string path)
mFastSelectColor = resolveColor(root.child("fastSelectColor").text().get(), mFastSelectColor); mFastSelectColor = resolveColor(root.child("fastSelectColor").text().get(), mFastSelectColor);
mHideHeader = root.child("hideHeader"); mHideHeader = root.child("hideHeader");
mHideDividers = root.child("hideDividers"); mHideDividers = root.child("hideDividers");
mListCentered = !root.child("listLeftAlign");
//GuiBox theming data //GuiBox theming data
mBoxData.backgroundPath = expandPath(root.child("boxBackground").text().get()); mBoxData.backgroundPath = expandPath(root.child("boxBackground").text().get());
@ -133,20 +143,40 @@ void GuiTheme::readXML(std::string path)
mBoxData.verticalTiled = root.child("boxVerticalTiled"); mBoxData.verticalTiled = root.child("boxVerticalTiled");
mBoxData.cornerPath = expandPath(root.child("boxCorner").text().get()); mBoxData.cornerPath = expandPath(root.child("boxCorner").text().get());
//list stuff
mListCentered = !root.child("listLeftAlign");
mListOffsetX = strToFloat(root.child("listOffsetX").text().get(), mListOffsetX); mListOffsetX = strToFloat(root.child("listOffsetX").text().get(), mListOffsetX);
mGameImageOffsetY = strToFloat(root.child("gameImageOffsetY").text().get(), mGameImageOffsetY);
mListTextOffsetX = strToFloat(root.child("listTextOffsetX").text().get(), mListTextOffsetX); 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 //sounds
mMenuScrollSound.loadFile(expandPath(root.child("menuScrollSound").text().get())); mMenuScrollSound.loadFile(expandPath(root.child("menuScrollSound").text().get()));
mMenuSelectSound.loadFile(expandPath(root.child("menuSelectSound").text().get())); mMenuSelectSound.loadFile(expandPath(root.child("menuSelectSound").text().get()));
mMenuBackSound.loadFile(expandPath(root.child("menuBackSound").text().get())); mMenuBackSound.loadFile(expandPath(root.child("menuBackSound").text().get()));
mMenuOpenSound.loadFile(expandPath(root.child("menuOpenSound").text().get())); mMenuOpenSound.loadFile(expandPath(root.child("menuOpenSound").text().get()));
//recursively create children for all <components> with proper parenting //actually read the components
createComponentChildren(root, this); createComponentChildren(root, this);
} }
//recursively creates components (with proper parenting)
void GuiTheme::createComponentChildren(pugi::xml_node node, GuiComponent* parent) void GuiTheme::createComponentChildren(pugi::xml_node node, GuiComponent* parent)
{ {
for(pugi::xml_node data = node.child("component"); data; data = data.next_sibling("component")) 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) GuiComponent* GuiTheme::createElement(pugi::xml_node data, GuiComponent* parent)
{ {
std::string type = data.child("type").text().get(); 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; std::string originX, originY;
splitString(origin, ' ', &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 //resolve to pixels from percentages/variables
int x = resolveExp(posX) * Renderer::getScreenWidth(); int x = resolveExp(posX) * Renderer::getScreenWidth();
int y = resolveExp(posY) * Renderer::getScreenHeight(); 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; return NULL;
} }
//expands a file path (./ becomes the directory of this theme file, ~/ becomes $HOME/)
std::string GuiTheme::expandPath(std::string path) std::string GuiTheme::expandPath(std::string path)
{ {
if(path[0] == '~') if(path[0] == '~')
@ -224,18 +254,23 @@ std::string GuiTheme::expandPath(std::string path)
return 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; MathExp exp;
exp.setExpression(str); exp.setExpression(str);
//set variables //set variables
exp.setVariable("headerHeight", Renderer::getFontHeight(Renderer::LARGE) / Renderer::getScreenHeight()); exp.setVariable("headerHeight", Renderer::getFontHeight(Renderer::LARGE) / Renderer::getScreenHeight());
exp.setVariable("infoWidth", GuiGameList::sInfoWidth); exp.setVariable("infoWidth", mListOffsetX);
return exp.eval(); return exp.eval();
} }
//takes a string of hex and resolves it to an integer
int GuiTheme::resolveColor(std::string str, int defaultColor) int GuiTheme::resolveColor(std::string str, int defaultColor)
{ {
if(str.empty()) if(str.empty())
@ -249,16 +284,23 @@ int GuiTheme::resolveColor(std::string str, int defaultColor)
return ret; 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) void GuiTheme::splitString(std::string str, char delim, std::string* before, std::string* after)
{ {
if(str.empty()) if(str.empty())
return; return;
size_t split = str.find(delim); size_t split = str.find(delim);
*before = str.substr(0, split); if(split != std::string::npos)
*after = str.substr(split + 1, str.length() - split - 1); {
*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) float GuiTheme::strToFloat(std::string str, float defaultVal)
{ {
if(str.empty()) if(str.empty())

View file

@ -27,7 +27,13 @@ public:
float getListOffsetX(); float getListOffsetX();
float getListTextOffsetX(); float getListTextOffsetX();
float getGameImageOffsetX();
float getGameImageOffsetY(); float getGameImageOffsetY();
float getGameImageWidth();
float getGameImageHeight();
float getGameImageOriginX();
float getGameImageOriginY();
GuiBoxData getBoxData(); GuiBoxData getBoxData();
@ -43,7 +49,7 @@ private:
//utility functions //utility functions
std::string expandPath(std::string path); 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); int resolveColor(std::string str, int defaultColor = 0x000000);
void splitString(std::string str, char delim, std::string* before, std::string* after); void splitString(std::string str, char delim, std::string* before, std::string* after);
float strToFloat(std::string str, float defaultVal = 0.0f); float strToFloat(std::string str, float defaultVal = 0.0f);
@ -53,7 +59,7 @@ private:
int mListPrimaryColor, mListSecondaryColor, mListSelectorColor, mListSelectedColor, mDescColor, mFastSelectColor; int mListPrimaryColor, mListSecondaryColor, mListSelectorColor, mListSelectedColor, mDescColor, mFastSelectColor;
bool mHideHeader, mHideDividers, mListCentered; bool mHideHeader, mHideDividers, mListCentered;
float mListOffsetX, mGameImageOffsetY, mListTextOffsetX; float mListOffsetX, mListTextOffsetX, mGameImageOffsetX, mGameImageOffsetY, mGameImageWidth, mGameImageHeight, mGameImageOriginX, mGameImageOriginY;
GuiBoxData mBoxData; GuiBoxData mBoxData;